Subject: bug#79001: Retain profile image files generated during build

From: Lynn Winebarger <[email-masked]>
Date: Sat, 12 Jul 2025 18:07:03 -0400

I want to use low-level profiling information generated during the
build process for evaluating changes to C primitives, particularly the
reader.

The attached patch sets up the environment variable GMON_OUT_PREFIX
when running emacs during the build so that the output incorporates
the target in the name and generates unique names during a parallel
build.

However, in a parallel build, I am now seeing the cus-load.el target
get run twice simultaneously, so that one instance will exit with a
file lock error and stop the build.

I also get random segmentation faults from emacs during the build, but
they do not repeat when I reinvoke make, and the file being
byte-compiled when the segfault occurred is not rerun.

In addition to the patches to commit
d63746d5191fb6e4ee6e39addfc1a05e0e0214b1, I have attached a list of
the names of the generated profile images for review.

I would like to have profiling start before the dump file is loaded,
but my simple minded attempt caused the shell to terminate emacs with
a message like "profiling timer expired".  I assume monstartup
(src/emacs.c:2557) does something to undo the signal handling set up
during emacs initialization, but I can't find any documentation for
that function on linux, or determine what else would have to change to
move that call up to the start of main.

You can get the new behavior with
./configure --enable-profiling=all
make PROFILE_BUILD=yes

./configure --enable-profiling=build
should have the effect of 'all' plus making PROFILE_BUILD=yes the
default in the Makefiles, but I have not tested it.

Also, I'm not sure if there are any canned analyses that should be
incorporated in the Makefiles as targets, or sets of files that should
be summarized for such analyses.

Lynn
From 48b601e5be1754a6022f397a6965f255139cd586 Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Sat, 12 Jul 2025 17:32:14 -0400
Subject: [PATCH 2/4] The @ does not work with the $\ newline escaping.

---
 lisp/Makefile.in | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lisp/Makefile.in b/lisp/Makefile.in
index 7df5be406eb..37c8ce73ea7 100644
--- a/lisp/Makefile.in
+++ b/lisp/Makefile.in
@@ -318,7 +318,7 @@ $(THEFILE)c:
 		-l comp -f byte-compile-refresh-preloaded \
 		-f batch-byte+native-compile $(THEFILE)
 else
-	$(AM_V_ELC)$\
+	$(AM_V_ELC) \
 	$(call set_gmon_prefix_env,ELC-$(basename $(notdir $(THEFILE))).out) \
 	$(emacs) $(BYTE_COMPILE_FLAGS) \
 		-l bytecomp -f byte-compile-refresh-preloaded \
-- 
2.50.0

From 697d469f922206df714cadbf5387d1d0fec19311 Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Sat, 12 Jul 2025 17:35:11 -0400
Subject: [PATCH 3/4] The recipe code for summarizing recursive targets never
 produced anything.

---
 src/profiling.mk.in | 12 ++----------
 1 file changed, 2 insertions(+), 10 deletions(-)

diff --git a/src/profiling.mk.in b/src/profiling.mk.in
index fb2112b64f7..8ee2a3527ff 100644
--- a/src/profiling.mk.in
+++ b/src/profiling.mk.in
@@ -51,13 +51,5 @@ $(if $(filter yes,$(PROFILE_BUILD)),$\
      $(and $2,$2:) override \
        GMON_OUT_PREFIX:=$(call join_args_dash,$(GMON_OUT_PREFIX),$1),)
 
-gprof_summarize =$\
-$(and $(and $(filter yes,$(PROFILE_BUILD)),$\
-           $(filter-out x,x$(GPROF))),$\
-     $(AM_V_at)$\
-     Z=`echo $(GMON_OUT_PREFIX)*.out.* $\
-          | grep -v '$(GMON_OUT_PREFIX)*.out.*' $\
-          | wc -c`; $\
-     test "$Z" != "0" $\
-     && $(GPROF) -s $(top_builddir)/src/temacs$(EXEEXT) $(GMON_OUT_PREFIX)*.out.* $\
-     ||  echo "Failed to summarize profiles $GMON_OUT_PREFIX" >&2)
+# code for running gprof -s went here, but it never produced anything
+gprof_summarize = @:
-- 
2.50.0

From d335175641d731616d3f77c737ea4284de75e790 Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Sat, 12 Jul 2025 15:55:38 -0400
Subject: [PATCH 1/4] Support for retaining profiling from build

---
 Makefile.in                |  23 +++++---
 admin/grammars/Makefile.in |  20 ++++---
 admin/unidata/Makefile.in  |  23 +++++---
 configure.ac               |  26 ++++++++--
 doc/misc/Makefile.in       |   7 ++-
 leim/Makefile.in           |  19 +++++--
 lib-src/Makefile.in        |   9 ++--
 lisp/Makefile.in           | 104 +++++++++++++++++++++++++++++--------
 src/Makefile.in            |  79 +++++++++++++++++++++-------
 src/emacs.c                |   5 ++
 src/profiling.mk.in        |  63 ++++++++++++++++++++++
 11 files changed, 302 insertions(+), 76 deletions(-)
 create mode 100644 src/profiling.mk.in

diff --git a/Makefile.in b/Makefile.in
index ea05fe6ac99..4a4d4106b6d 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -103,6 +103,7 @@ NTDIR=
 
 top_builddir = @top_builddir@
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 FIND_DELETE = @FIND_DELETE@
 
@@ -433,7 +434,8 @@ .PHONY:
 # .pdmp containing the new autoloads.
 .PHONY: src-depending-on-lisp
 src-depending-on-lisp: lisp
-	${MAKE} -C src BIN_DESTDIR='$(BIN_DESTDIR)' ELN_DESTDIR='$(ELN_DESTDIR)'
+	${MAKE} -C src BIN_DESTDIR='$(BIN_DESTDIR)' ELN_DESTDIR='$(ELN_DESTDIR)' \
+		$(call set_gmon_prefix)
 
 # If configure were to just generate emacsver.tex from emacsver.tex.in
 # in the normal way, the timestamp of emacsver.tex would always be
@@ -517,6 +519,10 @@ epaths-force-ns-self-contained:
 $(NTDIR): lib
 endif
 
+$(call extend_gmon_prefix,lisp,lisp)
+$(call extend_gmon_prefix,src,src)
+$(call extend_gmon_prefix,trampolines,trampolines)
+
 lib-src src: $(NTDIR) lib
 
 src: lib-src
@@ -526,7 +532,7 @@ lisp:
 
 # These targets should be "${SUBDIR} without 'src'".
 lib lib-src lisp nt: Makefile
-	$(MAKE) -C $@ all
+	$(MAKE) -C $@ all $(call set_gmon_prefix)
 
 java: lisp info
 	$(MAKE) -C $@ all
@@ -536,7 +542,7 @@ cross:
 
 trampolines: src lisp
 ifeq ($(HAVE_NATIVE_COMP),yes)
-	$(MAKE) -C lisp trampolines
+	$(MAKE) -C lisp trampolines $(call set_gmon_prefix)
 endif
 
 # Pass an unexpanded $srcdir to src's Makefile, which then
@@ -544,7 +550,8 @@ trampolines:
 # source directory of src/).
 src: Makefile
 	$(MAKE) -C $@ BIN_DESTDIR='$(BIN_DESTDIR)' \
-		 ELN_DESTDIR='$(ELN_DESTDIR)' all
+		 ELN_DESTDIR='$(ELN_DESTDIR)' all \
+		$(call set_gmon_prefix)
 
 blessmail: Makefile src
 	$(MAKE) -C lib-src maybe-blessmail
@@ -1272,6 +1279,8 @@ check-info:
 
 .PHONY: bootstrap actual-bootstrap
 
+$(call extend_gmon_prefix,bootstrap,actual-bootstrap)
+
 bootstrap:
 	$(MAKE) actual-bootstrap || $(MAKE) advice-on-failure make-target=bootstrap exit-status=$$?
 	$(MAKE) sanity-check make-target=bootstrap
@@ -1293,7 +1302,7 @@ actual-bootstrap:
 else
 	$(MAKE) extraclean
 endif
-	$(MAKE) actual-all
+	$(MAKE) actual-all $(call set_gmon_prefix)
 
 .PHONY: ChangeLog change-history change-history-commit change-history-nocommit
 .PHONY: preferred-branch-is-current unchanged-history-files
@@ -1352,8 +1361,8 @@ check-declare:
 	  echo "You must build Emacs to use this command"; \
 	  exit 1; \
 	fi
-	$(MAKE) -C lisp $@
-	$(MAKE) -C test $@
+	$(MAKE) -C lisp $@ $(call set_gmon_prefix)
+	$(MAKE) -C test $@ $(call set_gmon_prefix)
 
 .PHONY: gitmerge
 
diff --git a/admin/grammars/Makefile.in b/admin/grammars/Makefile.in
index 8436d7fb3c1..4e9aff70995 100644
--- a/admin/grammars/Makefile.in
+++ b/admin/grammars/Makefile.in
@@ -29,6 +29,7 @@ top_srcdir =
 top_builddir = @top_builddir@
 
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 # Prevent any settings in the user environment causing problems.
 unexport EMACSDATA EMACSDOC EMACSLOADPATH EMACSPATH
@@ -72,34 +73,41 @@ wisent:
 ## c-by.el, make-by.el.
 ${bovinedir}/%-by.el: ${srcdir}/%.by ${grammar_bovine}
 	$(AM_V_GEN)[ ! -f "$@" ] || chmod +w "$@"
-	$(AM_V_at)${make_bovine} -o "$@" $<
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${make_bovine} -o "$@" $<
 
 ${bovinedir}/scm-by.el: ${srcdir}/scheme.by ${grammar_bovine}
 	$(AM_V_GEN)[ ! -f "$@" ] || chmod +w "$@"
-	$(AM_V_at)${make_bovine} -o "$@" $<
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${make_bovine} -o "$@" $<
 
 ## grammar-wy.el
 ${cedetdir}/semantic/%-wy.el: ${srcdir}/%.wy ${grammar_wisent}
 	$(AM_V_GEN)[ ! -f "$@" ] || chmod +w "$@"
-	$(AM_V_at)${make_wisent} -o "$@" $<
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${make_wisent} -o "$@" $<
 
 ## js-wy.el, python-wy.el
 ${wisentdir}/%-wy.el: ${srcdir}/%.wy ${grammar_wisent}
 	$(AM_V_GEN)[ ! -f "$@" ] || chmod +w "$@"
-	$(AM_V_at)${make_wisent} -o "$@" $<
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${make_wisent} -o "$@" $<
 
 ${wisentdir}/javat-wy.el: ${srcdir}/java-tags.wy ${grammar_wisent}
 	$(AM_V_GEN)[ ! -f "$@" ] || chmod +w "$@"
-	$(AM_V_at)${make_wisent} -o "$@" $<
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${make_wisent} -o "$@" $<
 
 ${cedetdir}/srecode/srt-wy.el: ${srcdir}/srecode-template.wy ${grammar_wisent}
 	$(AM_V_GEN)[ ! -f "$@" ] || chmod +w "$@"
-	$(AM_V_at)${make_wisent} -o "$@" $<
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${make_wisent} -o "$@" $<
 
 .PHONY: distclean bootstrap-clean maintainer-clean gen-clean
 
 distclean:
 	rm -f Makefile
+	rm -f temacs-gmon*.out.* temacs-gmon*.sum
 
 ## IMO this should run gen-clean.
 bootstrap-clean:
diff --git a/admin/unidata/Makefile.in b/admin/unidata/Makefile.in
index 223d3fe9cab..75334249383 100644
--- a/admin/unidata/Makefile.in
+++ b/admin/unidata/Makefile.in
@@ -40,6 +40,7 @@ lparen =
 unifiles = $(addprefix ${unidir}/,$(sort $(shell sed -n 's/^[ \t][ \t]*${lparen}"\(uni-[^"]*\)"$$/\1/p' ${srcdir}/unidata-gen.el)))
 
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 .PHONY: all
 
@@ -53,12 +54,12 @@ all:
 ## Same with charprop.el below.
 ${top_srcdir}/src/macuvs.h: ${srcdir}/uvs.el ${srcdir}/IVD_Sequences.txt | \
   ${srcdir}/uvs.elc
-	$(AM_V_GEN)${emacs} -L ${srcdir} -l uvs \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-macuvs-header.out) ${emacs} -L ${srcdir} -l uvs \
 	  --eval '(uvs-print-table-ivd (unmsys--file-name "${srcdir}/IVD_Sequences.txt") "Adobe-Japan1")' \
 	  > $@
 
 %.elc: %.el
-	$(AM_V_ELC)${emacs} -f batch-byte-compile $<
+	$(AM_V_ELC)$(call set_gmon_prefix_env,ELC-$(basename $(notdir $@)).out) ${emacs} -f batch-byte-compile $<
 
 unidata.txt: ${srcdir}/UnicodeData.txt
 	$(AM_V_GEN)sed -e 's/\([^;]*\);\(.*\)/(#x\1 "\2")/' -e 's/;/" "/g' \
@@ -69,7 +70,7 @@ unidata.txt:
 ## is up-to-date, the unifiles are too.
 ${unidir}/charprop.el: ${unifiles} ${srcdir}/unidata-gen.el | ${srcdir}/unidata-gen.elc
 	$(AM_V_GEN)[ ! -f $@ ] || chmod +w $@
-	$(AM_V_at)${emacs} -L ${srcdir} -l unidata-gen \
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-charprop.out)${emacs} -L ${srcdir} -l unidata-gen \
 	  -f unidata-gen-charprop $@
 
 ${unifiles}: ${srcdir}/unidata-gen.el \
@@ -77,28 +78,33 @@ ${unifiles}:
   ${srcdir}/BidiBrackets.txt | \
   ${srcdir}/unidata-gen.elc unidata.txt
 	$(AM_V_GEN)[ ! -f $@ ] || chmod +w $@
-	$(AM_V_at)${emacs} -L ${srcdir} -l unidata-gen \
+	$(AM_V_at)$(call set_gmon_prefix_env,unidata-gen-$(basename $(notdir $@)).out) \
+	${emacs} -L ${srcdir} -l unidata-gen \
 	  -f unidata-gen-file $@ ${srcdir}
 
 ${unidir}/emoji-labels.el: ${unidir}/../international/emoji.el \
 	  ${srcdir}/emoji-test.txt
-	$(AM_V_GEN)${emacs} -l emoji.el -f emoji--generate-file $@
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-emoji-labels.out) \
+	${emacs} -l emoji.el -f emoji--generate-file $@
 
 ${unidir}/uni-scripts.el: ${srcdir}/unidata-gen.el \
 	  ${srcdir}/Scripts.txt \
 	  ${srcdir}/ScriptExtensions.txt \
 	  ${srcdir}/PropertyValueAliases.txt
-	$(AM_V_GEN)${emacs} -L ${srcdir} \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-uni-scripts.out) \
+	${emacs} -L ${srcdir} \
 		-l unidata-gen.el -f unidata-gen-scripts $@
 
 ${unidir}/uni-confusable.el: ${srcdir}/unidata-gen.el \
 	  ${srcdir}/confusables.txt
-	$(AM_V_GEN)${emacs} -L ${srcdir} \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-uni-confusable.out) \
+	${emacs} -L ${srcdir} \
 		-l unidata-gen.el -f unidata-gen-confusable $@
 
 ${unidir}/idna-mapping.el: ${srcdir}/unidata-gen.el \
 	  ${srcdir}/IdnaMappingTable.txt
-	$(AM_V_GEN)${emacs} -L ${srcdir} \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-idna-mapping.out) \
+	${emacs} -L ${srcdir} \
 		-l unidata-gen.el -f unidata-gen-idna-mapping $@
 
 .PHONY: charscript.el
@@ -127,6 +133,7 @@ .PHONY:
 
 clean:
 	rm -f ${srcdir}/*.elc unidata.txt
+	rm -f temacs-gmon*.out.* temacs-gmon*.sum
 
 ## IMO this should also run gen-clean.
 bootstrap-clean: clean
diff --git a/configure.ac b/configure.ac
index 083708b7756..c114703cd50 100644
--- a/configure.ac
+++ b/configure.ac
@@ -799,12 +799,26 @@ AC_DEFUN
                 Mainly useful for debugging Emacs itself.  May not work on
                 all platforms.  Stops profiler.el working.])],
 [ac_enable_profiling="${enableval}"],[])
-if test x$ac_enable_profiling != x ; then
-   PROFILING_CFLAGS="-DPROFILING=1 -pg"
-else
-   PROFILING_CFLAGS=
+AS_IF([test x$ac_enable_profiling = xno],[
+          PROFILING_CFLAGS=
+          PROFILE_BUILD=no
+          GPROF=],
+       [test x$ac_enable_profiling = xbuild],[
+          PROFILING_CFLAGS="-DPROFILING=1 -DPROFILING_ALL=1 -pg"
+          PROFILE_BUILD=yes],
+       [test x$ac_enable_profiling = xall],[
+          PROFILING_CFLAGS="-DPROFILING=1 -DPROFILING_ALL=1 -pg"
+          PROFILE_BUILD=no],[
+       PROFILING_CFLAGS="-DPROFILING=1 -pg"
+       PROFILE_BUILD=no])
+
+if test x$ac_enable_profiling != xno ; then
+   AC_CHECK_PROG([GPROF],[gprof])
 fi
 AC_SUBST([PROFILING_CFLAGS])
+AC_SUBST([PROFILE_BUILD])
+AC_SUBST([GPROF])
+ARCH_INDEPENDENT_CONFIG_FILES([src/profiling.mk])
 
 AC_ARG_ENABLE([autodepend],
 [AS_HELP_STRING([--enable-autodepend],
@@ -7835,6 +7849,10 @@ AC_DEFUN
     fi
   fi
 
+  if test "x$PROFILING_CFLAGS" != "x"; then
+     AS_MKDIR_P([src/profiling])
+  fi
+
   dnl Make cross/lib, which various Makefiles in cross expect to
   dnl always exist.
   AS_MKDIR_P([cross/lib])
diff --git a/doc/misc/Makefile.in b/doc/misc/Makefile.in
index 95c8a92ded1..b09efa977d1 100644
--- a/doc/misc/Makefile.in
+++ b/doc/misc/Makefile.in
@@ -25,6 +25,7 @@ srcdir=
 top_srcdir = @top_srcdir@
 top_builddir = @top_builddir@
 
+
 ## Where the output files go.
 ## Note that all the Info targets build the Info files in srcdir.
 ## There is no provision for Info files to exist in the build directory.
@@ -111,6 +112,7 @@ TEXI2PDF =
 DVIPS = dvips
 
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 ENVADD = $(AM_V_GEN)TEXINPUTS="$(srcdir):$(emacsdir):$(TEXINPUTS)" \
          MAKEINFO="$(MAKEINFO) $(MAKEINFO_OPTS)"
@@ -240,7 +242,9 @@ abs_top_builddir =
 unexport EMACSDATA EMACSDOC EMACSLOADPATH EMACSPATH
 
 EMACS = ${abs_top_builddir}/src/emacs
-emacs = "${EMACS}" -batch --no-site-file --no-site-lisp --eval '(setq load-prefer-newer t)'
+emacs = $(call set_gmon_prefix_env,$(basename $@).out) \
+	"${EMACS}" -batch --no-site-file --no-site-lisp \
+	--eval '(setq load-prefer-newer t)'
 
 # Generated .texi files go in srcdir so they can be included in the
 # release tarfile along with the others.
@@ -275,6 +279,7 @@ mostlyclean:
 
 clean: mostlyclean
 	rm -f ./*.dvi ./*.html ./*.pdf ./*.ps
+	rm -f temacs-gmon*.out.* temacs-gmon*.sum
 
 distclean: clean
 	rm -f Makefile
diff --git a/leim/Makefile.in b/leim/Makefile.in
index 8d9cdb991b6..63256aba1a1 100644
--- a/leim/Makefile.in
+++ b/leim/Makefile.in
@@ -33,6 +33,7 @@ leimdir =
 
 EXEEXT = @EXEEXT@
 
+
 SMALL_JA_DIC = @SMALL_JA_DIC@
 JA_DIC_NO_REDUCTION_OPTION = --no-reduction
 ifeq ($(SMALL_JA_DIC), yes)
@@ -40,6 +41,7 @@ JA_DIC_NO_REDUCTION_OPTION =
 endif
 
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 # Prevent any settings in the user environment causing problems.
 unexport EMACSDATA EMACSDOC EMACSLOADPATH EMACSPATH
@@ -100,11 +102,14 @@ ${leimdir}/quail ${leimdir}/ja-dic:
 
 ## All of TIT_GB and TIT_BIG5.
 ${leimdir}/quail/%.el: ${srcdir}/CXTERM-DIC/%.tit
-	$(AM_V_GEN)${RUN_EMACS} -l titdic-cnv \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${RUN_EMACS} -l titdic-cnv \
 	  -f batch-tit-dic-convert -dir ${leimdir}/quail $<

-misc_convert = $(AM_V_GEN)${RUN_EMACS} \
+misc_convert = $(AM_V_GEN) \
+$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+${RUN_EMACS} \
   -l titdic-cnv -f batch-tit-miscdic-convert -dir ${leimdir}/quail
 
 ## CTLau.el, CTLau-b5.el.
@@ -128,7 +133,8 @@ leim-list.el:
 
 ${leimdir}/leim-list.el: ${srcdir}/leim-ext.el ${TIT_MISC}
 	$(AM_V_GEN)rm -f $@
-	$(AM_V_at)${RUN_EMACS} -l international/quail \
+	$(AM_V_at)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${RUN_EMACS} -l international/quail \
 	  --eval "(update-leim-list-file (unmsys--file-name \"${leimdir}\"))"
 	$(AM_V_at)sed -n -e '/^[^;]/p' -e 's/^;\(;*\)inc /;\1 /p' < $< >> $@
 
@@ -144,11 +150,13 @@ small-ja-dic-option:
 .PHONY: generate-ja-dic
 generate-ja-dic: ${leimdir}/ja-dic/ja-dic.el
 ${leimdir}/ja-dic/ja-dic.el: $(srcdir)/SKK-DIC/SKK-JISYO.L small-ja-dic-option
-	$(AM_V_GEN)$(RUN_EMACS) -batch -l ja-dic-cnv \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	$(RUN_EMACS) -batch -l ja-dic-cnv \
 	  -f batch-skkdic-convert -dir "$(leimdir)/ja-dic" $(JA_DIC_NO_REDUCTION_OPTION) "$<"
 
 ${srcdir}/../lisp/language/pinyin.el: ${srcdir}/MISC-DIC/pinyin.map
-	$(AM_V_GEN)${RUN_EMACS} -l titdic-cnv -f tit-pinyin-convert $< $@
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-$(basename $(notdir $@)).out) \
+	${RUN_EMACS} -l titdic-cnv -f tit-pinyin-convert $< $@

 .PHONY: bootstrap-clean distclean maintainer-clean gen-clean
@@ -159,6 +167,7 @@ bootstrap-clean:
 
 distclean:
 	rm -f Makefile
+	rm -f temacs-gmon*.out.* temacs-gmon*.sum
 
 maintainer-clean: gen-clean distclean
 
diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in
index 49a243e4795..e79b575dcae 100644
--- a/lib-src/Makefile.in
+++ b/lib-src/Makefile.in
@@ -274,11 +274,14 @@ BASE_CFLAGS =
 	      -I. -I../src -I../lib \
 	      -I${srcdir} -I${srcdir}/../src -I${srcdir}/../lib
 
-ALL_CFLAGS = ${BASE_CFLAGS} ${PROFILING_CFLAGS} ${LDFLAGS} ${CPPFLAGS} ${CFLAGS}
-CPP_CFLAGS = ${BASE_CFLAGS} ${PROFILING_CFLAGS} ${CPPFLAGS} ${CFLAGS}
+# Do not compile these helper executables with profiling
+# At best, produces confusing profile images
+# At worst, a race condition with the profile image file from emacs
+ALL_CFLAGS = ${BASE_CFLAGS} ${LDFLAGS} ${CPPFLAGS} ${CFLAGS}
+CPP_CFLAGS = ${BASE_CFLAGS} ${CPPFLAGS} ${CFLAGS}
 
 ALL_CXXFLAGS = $(filter-out ${NON_CXX_CFLAGS},${BASE_CFLAGS}) \
-  ${PROFILING_CFLAGS} ${LDFLAGS} ${CPPFLAGS} ${CFLAGS} ${CXXFLAGS} ${HAIKU_CFLAGS}
+  ${LDFLAGS} ${CPPFLAGS} ${CFLAGS} ${CXXFLAGS} ${HAIKU_CFLAGS}
 
 # Configuration files for .o files to depend on.
 config_h = ../src/config.h $(srcdir)/../src/conf_post.h
diff --git a/lisp/Makefile.in b/lisp/Makefile.in
index 17182357739..7df5be406eb 100644
--- a/lisp/Makefile.in
+++ b/lisp/Makefile.in
@@ -44,6 +44,7 @@ NATIVE_SKIP_NONDUMP =
 endif
 
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 FIND_DELETE = @FIND_DELETE@
 
@@ -153,28 +154,54 @@ .PHONY:
 # Nowadays these commands don't scan automatically generated files,
 # since they will never contain any useful information
 # (see finder-no-scan-regexp and custom-dependencies-no-scan-regexp).
+
+$(call extend_gmon_prefix,custom-deps,custom-deps)
+$(call extend_gmon_prefix,finder-data,finder-data)
+$(call extend_gmon_prefix,generate-ja-dic,generate-ja-dic)
+$(call extend_gmon_prefix,autoloads,autoloads)
+$(call extend_gmon_prefix,gen-lisp,gen-lisp)
+$(call extend_gmon_prefix,leim,leim)
+$(call extend_gmon_prefix,semantic,semantic)
+$(call extend_gmon_prefix,compile,compile)
+$(call extend_gmon_prefix,compile-always,compile-always)
+$(call extend_gmon_prefix,compile-first,compile-first)
+$(call extend_gmon_prefix,compile-main,compile-main)
+$(call extend_gmon_prefix,compile-eln-aot,compile-eln-aot)
+$(call extend_gmon_prefix,trampolines,trampolines)
+$(call extend_gmon_prefix,compile-one-process,compile-one-process)
+
 custom-deps:
-	$(AM_V_at)$(MAKE) PHONY_EXTRAS=$(lisp)/cus-load.el $(lisp)/cus-load.el
+	$(AM_V_at)$(MAKE) PHONY_EXTRAS=$(lisp)/cus-load.el $(lisp)/cus-load.el \
+		$(call set_gmon_prefix)
+	$(gprof_summarize)
+
 $(lisp)/cus-load.el:
-	$(AM_V_GEN)$(emacs) -l cus-dep \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-cus-load.out) \
+	$(emacs) -l cus-dep \
 	  --eval '(setq generated-custom-dependencies-file (unmsys--file-name "$(srcdir)/cus-load.el"))' \
 	  -f custom-make-dependencies ${SUBDIRS_ALMOST}
 
 finder-data:
 	$(AM_V_at)$(MAKE) PHONY_EXTRAS=$(lisp)/finder-inf.el \
-	  $(lisp)/finder-inf.el
+	  $(lisp)/finder-inf.el $(call set_gmon_prefix)
+	$(gprof_summarize)
+
 $(lisp)/finder-inf.el:
-	$(AM_V_GEN)$(emacs) -l finder \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,gen-finder-inf.out) \
+	$(emacs) -l finder \
 	  --eval '(setq generated-finder-keywords-file (unmsys--file-name "$(srcdir)/finder-inf.el"))' \
 	  -f finder-compile-keywords-make-dist ${SUBDIRS_FINDER}
 
 # This is the OKURO-NASI compilation trigger.
-generate-ja-dic: main-first
-	$(AM_V_at)$(MAKE) -C ../leim generate-ja-dic EMACS="$(EMACS)"
-	$(AM_V_at)$(MAKE) compile-targets TARGETS="./leim/ja-dic/ja-dic.elc"
+generate-ja-dic: main-first custom-deps
+	$(AM_V_at)$(MAKE) -C ../leim generate-ja-dic EMACS="$(EMACS)" \
+		$(call set_gmon_prefix)
+	$(AM_V_at)$(MAKE) compile-targets TARGETS="./leim/ja-dic/ja-dic.elc" \
+		$(call set_gmon_prefix)
+	$(gprof_summarize)
 
 org-manuals: main-first
-	$(AM_V_at)$(MAKE) -C ../doc/misc org.texi modus-themes.texi
+	$(AM_V_at)$(MAKE) -C ../doc/misc org.texi modus-themes.texi $(call set_gmon_prefix)
 
 ## Comments on loaddefs generation:
 
@@ -200,16 +227,18 @@ org-manuals:
 # need leim-list.el at about the same time, so ensure that it's
 # generated, too.
 autoloads: $(lisp)/emacs-lisp/loaddefs-gen.elc gen-lisp
-	$(AM_V_GEN)$(emacs) \
+	$(AM_V_GEN)$(call set_gmon_prefix_env,run-loaddefs-gen.out) \
+	$(emacs) \
             -l $(lisp)/emacs-lisp/loaddefs-gen.elc \
 	    -f loaddefs-generate--emacs-batch ${SUBDIRS_ALMOST}
+	$(gprof_summarize)
 
 # autoloads always runs, but only updates when there's something new.
 # Provide a force option to enable regeneration of all loaddefs files.
 .PHONY: autoloads-force
 autoloads-force:
 	rm -f $(lisp)/loaddefs.el
-	$(MAKE) autoloads
+	$(MAKE) autoloads $(call set_gmon_prefix)
 
 ldefs-boot.el: autoloads-force
 	sed '/^;; Local Variables:/a ;; no-byte-compile: t'\
@@ -240,6 +269,7 @@ repo-update:
 # Update etc/AUTHORS
 
 update-authors:
+	$(call set_gmon_prefix_env,run-update-authors.out) \
 	$(emacs) -L "$(top_srcdir)/admin" -l authors \
 	  -f batch-update-authors "$(top_srcdir)/etc/AUTHORS" "$(top_srcdir)"
 
@@ -282,11 +312,15 @@ THEFILE =
 .PHONY: $(THEFILE)c
 $(THEFILE)c:
 ifeq ($(HAVE_NATIVE_COMP),yes)
-	$(AM_V_ELC)$(emacs) $(BYTE_COMPILE_FLAGS) \
+	$(AM_V_ELC) \
+	$(call set_gmon_prefix_env,ELC+N-$(basename $(notdir $(THEFILE))).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) \
 		-l comp -f byte-compile-refresh-preloaded \
 		-f batch-byte+native-compile $(THEFILE)
 else
-	$(AM_V_ELC)$(emacs) $(BYTE_COMPILE_FLAGS) \
+	$(AM_V_ELC)$\
+	$(call set_gmon_prefix_env,ELC-$(basename $(notdir $(THEFILE))).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) \
 		-l bytecomp -f byte-compile-refresh-preloaded \
 		-f batch-byte-compile $(THEFILE)
 endif
@@ -294,7 +328,9 @@ $(THEFILE)c:
 ifeq ($(HAVE_NATIVE_COMP),yes)
 .PHONY: $(THEFILE)n
 $(THEFILE)n:
-	$(AM_V_ELN)$(emacs) $(BYTE_COMPILE_FLAGS) \
+	$(AM_V_ELN) \
+	$(call set_gmon_prefix_env,ELN-$(basename $(notdir $(THEFILE))).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) \
 		-l comp -f byte-compile-refresh-preloaded \
 		--eval '(batch-native-compile t)' $(THEFILE)
 endif
@@ -322,17 +358,23 @@ .SUFFIXES:
 # which uses it to avoid displaying certain messages which might be
 # irritating/misleading during a bootstrap.
 .el.elc:
-	$(AM_V_ELC)$(emacs) $(BYTE_COMPILE_FLAGS) \
+	$(AM_V_ELC) \
+	$(call set_gmon_prefix_env,ancient-ELC-$(basename $(notdir $@)).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) \
 	-l comp -f batch-byte-compile $<
 	TZ=UTC0 touch -t 197001010000 $@
 else
 .el.elc:
-	$(AM_V_ELC)$(emacs) $(BYTE_COMPILE_FLAGS) \
+	$(AM_V_ELC) \
+	$(call set_gmon_prefix_env,ELC+N-$(basename $(notdir $@)).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) \
 	-l comp -f batch-byte+native-compile $<
 endif
 else
 .el.elc:
-	$(AM_V_ELC)$(emacs) $(BYTE_COMPILE_FLAGS) -f batch-byte-compile $<
+	$(AM_V_ELC) \
+	$(call set_gmon_prefix_env,ELC-$(basename $(notdir $@)).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) -f batch-byte-compile $<
 endif
 
 .PHONY: compile-first compile-main compile compile-always
@@ -373,9 +415,11 @@ compile-main:
 	done | xargs $(XARGS_LIMIT) echo) |	     \
 	while read chunk; do			     \
 	  $(MAKE) compile-targets		     \
+		  $(call set_gmon_prefix)	     \
 		  NATIVE_DISABLED=$(NATIVE_SKIP_NONDUMP) \
 		  TARGETS="$$chunk";		     \
 	done
+	$(gprof_summarize)
 
 # Compile some important files first.
 main-first:
@@ -385,9 +429,11 @@ main-first:
 	done | xargs $(XARGS_LIMIT) echo) |	     \
 	while read chunk; do			     \
 	  $(MAKE) compile-targets		     \
+		  $(call set_gmon_prefix)	     \
 		  NATIVE_DISABLED=$(NATIVE_SKIP_NONDUMP) \
 		  TARGETS="$$chunk";		     \
 	done
+	$(gprof_summarize)
 
 .PHONY: compile-clean
 # Erase left-over .elc files that do not have a corresponding .el file.
@@ -406,33 +452,40 @@ .PHONY:
 ## make -C ../admin/unidata all should be here, but that would race
 ## with ../src.  See comments above for loaddefs.
 gen-lisp: leim semantic
+	$(gprof_summarize)
 
 # (re)compile titdic-cnv before recursing into `leim` since its used to
 # generate some of the Quail source files from tables.
 leim: $(lisp)/international/titdic-cnv.elc
-	$(MAKE) -C ../leim all EMACS="$(EMACS)"
+	$(MAKE) -C ../leim all EMACS="$(EMACS)" $(call set_gmon_prefix)
+	$(gprof_summarize)
 
 semantic:
-	$(MAKE) -C ../admin/grammars all EMACS="$(EMACS:.%=../.%)"
+	$(MAKE) -C ../admin/grammars all EMACS="$(EMACS:.%=../.%)" $(call set_gmon_prefix)
+	$(gprof_summarize)
 
 # Compile all Lisp files, but don't recompile those that are up to
 # date.  Some .el files don't get compiled because they set the
 # local variable no-byte-compile.
 # Calling make recursively because suffix rule cannot have prerequisites.
 compile: $(LOADDEFS) autoloads compile-first
-	$(MAKE) compile-main
+	$(MAKE) compile-main $(call set_gmon_prefix)
+	$(gprof_summarize)
 
 # Compile all Lisp files.  This is like 'compile' but compiles files
 # unconditionally.  Some files don't actually get compiled because they
 # set the local variable no-byte-compile.
 compile-always:
 	find $(lisp) -name '*.elc' $(FIND_DELETE)
-	$(MAKE) compile
+	$(MAKE) compile $(call set_gmon_prefix)
+	$(gprof_summarize)
 
 .PHONY: trampolines
 trampolines: compile
 ifeq ($(HAVE_NATIVE_COMP),yes)
+	$(call set_gmon_prefix_env,run-compile-all-trampolines.out) \
 	$(emacs) -l comp -f comp-compile-all-trampolines
+	$(gprof_summarize)
 endif
 
 .PHONY: compile-eln-targets compile-eln-aot
@@ -442,7 +495,8 @@ .PHONY:
 # compile-eln-aot is called.
 ifeq ($(NATIVE_COMPILATION_AOT),yes)
 %.eln: %.el
-	$(AM_V_ELN)$(emacs) $(BYTE_COMPILE_FLAGS) \
+	$(AM_V_ELN)$(call set_gmon_prefix_env,ELN-$(basename $(notdir $@)).out) \
+	$(emacs) $(BYTE_COMPILE_FLAGS) \
 		-l comp -f byte-compile-refresh-preloaded \
 		--eval '(batch-native-compile t)' $<
 
@@ -467,8 +521,10 @@ compile-eln-aot:
 	done | xargs $(XARGS_LIMIT) echo) |	     \
 	while read chunk; do			     \
 	  $(MAKE) compile-eln-targets		     \
-	      TARGETS="$$chunk" ELNDONE="$(ELNDONE)"; \
+		$(call set_gmon_prefix)		     \
+		TARGETS="$$chunk" ELNDONE="$(ELNDONE)"; \
 	done
+	$(gprof_summarize)

 .PHONY: backup-compiled-files compile-after-backup
@@ -501,8 +557,10 @@ compile-after-backup:
 # core and CPU time is an issue.
 .PHONY: compile-one-process
 compile-one-process: $(LOADDEFS) compile-first
+	$(call set_gmon_prefix_env,run-batch-byte-recompile-directory.out) \
 	$(emacs) $(BYTE_COMPILE_FLAGS) \
 	    --eval "(batch-byte-recompile-directory 0)" $(lisp)
+	$(gprof_summarize)
 
 .PHONY: bootstrap-clean distclean maintainer-clean
 
@@ -512,6 +570,7 @@ bootstrap-clean:
 
 distclean:
 	-rm -f ./Makefile $(lisp)/loaddefs.el $(lisp)/loaddefs.elc
+	rm -f temacs-gmon*.out.* temacs-gmon*.sum
 
 maintainer-clean: distclean bootstrap-clean
 	rm -f TAGS
@@ -519,6 +578,7 @@ maintainer-clean:
 .PHONY: check-declare
 
 check-declare:
+	$(call set_gmon_prefix_env,run-check-declare.out) \
 	$(emacs) -l check-declare --eval '(check-declare-directory "$(lisp)")'
 
 ## This finds a lot of duplicates between foo.el and obsolete/foo.el.
diff --git a/src/Makefile.in b/src/Makefile.in
index e4fc2fef711..150d7ff0b01 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -83,7 +83,7 @@ HAVE_NATIVE_COMP =
 OTHER_FILES = @OTHER_FILES@
 
 ## Flags to pass for profiling builds
-PROFILING_CFLAGS = @PROFILING_CFLAGS@
+PROFILING_CFLAGS := @PROFILING_CFLAGS@
 
 ## Flags to pass to the compiler to enable build warnings
 WARN_CFLAGS = @WARN_CFLAGS@
@@ -398,6 +398,7 @@ HAVE_BE_APP =
 DO_CODESIGN=$(patsubst aarch64-apple-darwin%,yes,@configuration@)
 
 -include ${top_builddir}/src/verbose.mk
+-include ${top_builddir}/src/profiling.mk
 
 bootstrap_exe = ../src/bootstrap-emacs$(EXEEXT)
 ifeq ($(DUMPING),pdumper)
@@ -586,6 +587,25 @@ LIBES =
    $(TREE_SITTER_LIBS) $(SQLITE3_LIBS) $(XCOMPOSITE_LIBS) $(XSHAPE_LIBS) \
    $(ANDROID_LIBS)
 
+lispintdir = ${lispsource}/international
+charsets = ${top_srcdir}/admin/charsets/charsets.stamp
+charscript = ${lispintdir}/charscript.el
+emoji-zwj = ${lispintdir}/emoji-zwj.el
+
+## Make does not recognize the expansion of extend_gmon_prefix as
+## a variable assignment when the target has already been parsed
+## for some reason, but is ok if the target and target-specific
+## variable assignment are both produced in the same expansion.
+$(call extend_gmon_prefix,international,$(lispintdir)/charprop.el)
+$(call extend_gmon_prefix,international,$(lispintdir)/ucs-normalize.elc)
+$(call extend_gmon_prefix,international,$(lispsource)/term/ns-win.elc)
+$(call extend_gmon_prefix,international,$\
+       ${lispintdir}/cp51932.el ${lispintdir}/eucjp-ms.el)
+$(call extend_gmon_prefix,charsets,$(charsets))
+$(call extend_gmon_prefix,charscript,$(charscript))
+$(call extend_gmon_prefix,emoji-zwj,$(emoji-zwj))
+$(call extend_gmon_prefix,characters,${lispintdir}/characters.elc)
+
 ## FORCE it so that admin/unidata can decide whether this file is
 ## up-to-date.  Although since charprop depends on bootstrap-emacs,
 ## and emacs depends on charprop, in practice this rule was always run
@@ -593,7 +613,8 @@ LIBES =
 ifneq ($(XCONFIGURE),android)
 $(lispsource)/international/charprop.el: \
   FORCE | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
-	$(MAKE) -C ../admin/unidata all EMACS="../$(bootstrap_exe)"
+	$(MAKE) -C ../admin/unidata all EMACS="../$(bootstrap_exe)" \
+	   $(call set_gmon_prefix,unidata)
 endif
 
 ## We require charprop.el to exist before ucs-normalize.el is
@@ -602,21 +623,17 @@ $(lispsource)/international/charprop.el:
 $(lispsource)/international/ucs-normalize.elc $(lispsource)/term/ns-win.elc: | \
   $(lispsource)/international/charprop.el
 
-lispintdir = ${lispsource}/international
 ${lispintdir}/cp51932.el ${lispintdir}/eucjp-ms.el: FORCE
-	${MAKE} -C ../admin/charsets $(notdir $@)
+	${MAKE} -C ../admin/charsets $(notdir $@) $(call set_gmon_prefix)
 
-charsets = ${top_srcdir}/admin/charsets/charsets.stamp
 ${charsets}: FORCE
-	$(MAKE) -C ../admin/charsets all
+	$(MAKE) -C ../admin/charsets all $(call set_gmon_prefix)
 
-charscript = ${lispintdir}/charscript.el
 ${charscript}: FORCE
-	$(MAKE) -C ../admin/unidata $(notdir $@)
+	$(MAKE) -C ../admin/unidata $(notdir $@) $(call set_gmon_prefix)
 
-emoji-zwj = ${lispintdir}/emoji-zwj.el
 ${emoji-zwj}: FORCE
-	$(MAKE) -C ../admin/unidata $(notdir $@)
+	$(MAKE) -C ../admin/unidata $(notdir $@) $(call set_gmon_prefix)
 
 ${lispintdir}/characters.elc: ${charscript:.el=.elc} ${emoji-zwj:.el=.elc}
 
@@ -648,8 +665,10 @@ Emacs.pdmp:
 endif
 
 ifeq ($(DUMPING),pdumper)
+$(call extend_gmon_prefix,emacs-pdump,$(pdmp))
 $(pdmp): emacs$(EXEEXT) $(lispsource)/loaddefs.el $(lispsource)/loaddefs.elc
-	LC_ALL=C $(RUN_TEMACS) -batch $(BUILD_DETAILS) -l loadup --temacs=pdump \
+	$(call set_gmon_prefix_env,temacs-pdump.out) LC_ALL=C $(RUN_TEMACS) -batch \
+		$(BUILD_DETAILS) -l loadup --temacs=pdump \
 		--bin-dest '$(BIN_DESTDIR)' --eln-dest '$(ELN_DESTDIR)'
 	cp -f $@ $(bootstrap_pdmp)
 endif
@@ -699,6 +718,7 @@ MAKE_PDUMPER_FINGERPRINT =
 MAKE_PDUMPER_FINGERPRINT =
 endif
 
+
 ## We have to create $(etc) here because init_cmdargs tests its
 ## existence when setting Vinstallation_directory (FIXME?).
 ## This goes on to affect various things, and the emacs binary fails
@@ -808,6 +828,7 @@ versionclean:
 clean: mostlyclean versionclean
 	rm -f $(DEPDIR)/*
 
+
 ## bootstrap-clean is used to clean up just before a bootstrap.
 ## It should remove all files generated during a compilation/bootstrap,
 ## but not things like config.status or TAGS.
@@ -820,8 +841,9 @@ bootstrap-clean:
 	fi
 
 distclean: bootstrap-clean
-	rm -f Makefile lisp.mk verbose.mk
+	rm -f Makefile lisp.mk verbose.mk profiling.mk
 	rm -fr $(DEPDIR)
+	rm -f temacs-gmon*.out.* temacs-gmon*.sum
 
 maintainer-clean: distclean
 	rm -f TAGS
@@ -890,7 +912,7 @@ .PHONY:
 ifneq ($(XCONFIGURE),android)
 %.elc: %.el | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
 	@$(MAKE) $(AM_V_NO_PD) -C ../lisp EMACS="$(bootstrap_exe)"\
-		THEFILE=$< $<c
+		THEFILE=$< $<c $(call set_gmon_prefix)
 endif
 
 ifeq ($(HAVE_NATIVE_COMP):$(NATIVE_DISABLED),yes:)
@@ -913,9 +935,12 @@ elnlisp :=
 	international/emoji-zwj.eln
 elnlisp := $(addprefix ${lispsource}/,${elnlisp}) $(lisp:.elc=.eln)
 
+$(call extend_gmon_prefix,eln,%.eln)
+$(call extend_gmon_prefix,native-lisp,../native-lisp)
+
 %.eln: %.el | emacs$(EXEEXT) $(pdmp)
 	@$(MAKE) $(AM_V_NO_PD) -C ../lisp EMACS="../src/emacs$(EXEEXT)"\
-		THEFILE=$< $<n
+		THEFILE=$< $(call set_gmon_prefix) $<n
 
 ## FIXME: this is fragile!  We lie to Make about the files produced by
 ## this rule, and we rely on the absence of the native-lisp directory
@@ -932,31 +957,39 @@ elnlisp := $(addprefix ${lispsource}/,${elnlisp}) $(lisp:
 ## of *.el files that were already native-compiled.
 NATIVE_COMPILATION_AOT = @NATIVE_COMPILATION_AOT@
 ../native-lisp: | $(pdmp)
+	$(eval native_lisp_dir := $(realpath ../native-lisp))
 	@if test ! -d $@; then \
 	  mkdir $@ && $(MAKE) $(AM_V_NO_PD) $(elnlisp); \
 	  if test $(SYSTEM_TYPE) = cygwin; then \
 	    find $@ -name '*.eln' | rebase -v -O -T -; \
 	  fi; \
-	  LC_ALL=C $(RUN_TEMACS) -batch $(BUILD_DETAILS) -l loadup --temacs=pdump \
+	  $(call set_gmon_prefix_env,-temacs-pdump.out) \
+	  LC_ALL=C $(RUN_TEMACS) \
+		-batch $(BUILD_DETAILS) -l loadup --temacs=pdump \
 		--bin-dest '$(BIN_DESTDIR)' --eln-dest '$(ELN_DESTDIR)' \
 	  && cp -f emacs$(EXEEXT) bootstrap-emacs$(EXEEXT) \
 	  && cp -f $(pdmp) $(bootstrap_pdmp); \
 	  if test $(NATIVE_COMPILATION_AOT) = yes; then \
-	    $(MAKE) $(AM_V_NO_PD) -C ../lisp compile-eln-aot EMACS="../src/emacs$(EXEEXT)" ELNDONE="$(addprefix %,$(notdir $(elnlisp))))"; \
+	    $(MAKE) $(AM_V_NO_PD) -C ../lisp compile-eln-aot \
+		EMACS="../src/emacs$(EXEEXT)" \
+		ELNDONE="$(addprefix %,$(notdir $(elnlisp))))" \
+		$(call set_gmon_prefix); \
 	  fi; \
+	  $(gprof_summarize) \
 	fi
 endif
 
 ifneq ($(XCONFIGURE),android)
 $(lispsource)/loaddefs.el: | bootstrap-emacs$(EXEEXT) $(bootstrap_pdmp)
-	$(MAKE) -C ../lisp autoloads EMACS="$(bootstrap_exe)"
+	$(MAKE) -C ../lisp autoloads EMACS="$(bootstrap_exe)" \
+		$(call set_gmon_prefix)
 endif
 
 ## Dump an Emacs executable named bootstrap-emacs containing the
 ## files from loadup.el in source form.
 
 bootstrap-emacs$(EXEEXT): temacs$(EXEEXT)
-	$(MAKE) -C ../lisp update-subdirs
+	$(MAKE) -C ../lisp update-subdirs $(call set_gmon_prefix)
 	@: In the pdumper case, make compile-first after the dump
 	cp -f temacs$(EXEEXT) bootstrap-emacs$(EXEEXT)
 ifeq ($(DO_CODESIGN),yes)
@@ -964,15 +997,21 @@ bootstrap-emacs$(EXEEXT):
 endif
 
 ifeq ($(DUMPING),pdumper)
+
+$(call extend_gmon_prefix,bootstrap-pdump,$(bootstrap_pdmp))
 $(bootstrap_pdmp): bootstrap-emacs$(EXEEXT)
 	rm -f $@
+	$(call set_gmon_prefix_env,-temacs-pbootstrap.out) \
 	$(RUN_TEMACS) --batch $(BUILD_DETAILS) -l loadup --temacs=pbootstrap \
 		--bin-dest '$(BIN_DESTDIR)' --eln-dest '$(ELN_DESTDIR)'
 	@: Compile some files earlier to speed up further compilation.
 	@: First, byte compile these files, ....
-	ANCIENT=yes $(MAKE) -C ../lisp compile-first EMACS="$(bootstrap_exe)"
+	ANCIENT=yes $(MAKE) -C ../lisp compile-first EMACS="$(bootstrap_exe)" \
+		$(call set_gmon_prefix)
 	@: .... then use their .elcs in native compiling these and other files.
-	$(MAKE) -C ../lisp compile-first EMACS="$(bootstrap_exe)"
+	$(MAKE) -C ../lisp compile-first EMACS="$(bootstrap_exe)" \
+		$(call set_gmon_prefix)
+	$(gprof_summarize)
 endif
 
 ### Flymake support (for C only)
diff --git a/src/emacs.c b/src/emacs.c
index cf8f4bd63f7..3b902e7076c 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1295,6 +1295,7 @@ android_emacs_init (int argc, char **argv, char *dump_file)
   maybe_load_seccomp (argc, argv);
 #endif
 
+
   bool no_loadup = false;
   char *junk = 0;
   char *dname_arg = 0;
@@ -2548,13 +2549,17 @@ android_emacs_init (int argc, char **argv, char *dump_file)
      Give it a try and tell us if it works on your system.  To compile
      for profiling, use the configure option --enable-profiling.  */
 #ifdef PROFILING
+#ifndef PROFILING_ALL
   if (initialized)
+#endif
     {
       atexit (_mcleanup);
       monstartup ((uintptr_t) __executable_start, (uintptr_t) &etext);
     }
+#ifndef PROFILING_ALL
   else
     moncontrol (0);
+#endif
 #endif
 
   initialized = true;
diff --git a/src/profiling.mk.in b/src/profiling.mk.in
new file mode 100644
index 00000000000..fb2112b64f7
--- /dev/null
+++ b/src/profiling.mk.in
@@ -0,0 +1,63 @@
+### profiling.mk --- Makefile fragment for GNU Emacs
+
+## Copyright (C) 2021-2025 Free Software Foundation, Inc.
+
+## This file is part of GNU Emacs.
+
+## GNU Emacs is free software: you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## GNU Emacs is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+# These two variables will be passed explicitly to $(MAKE) as needed
+unexport GMON_OUT_PREFIX
+unexport PROFILE_BUILD
+
+PROFILE_BUILD := @PROFILE_BUILD@
+GPROF=@GPROF@
+GMON_OUT_PREFIX := temacs-gmon
+ifeq ($(PROFILE_BUILD),yes)
+profile_dir = $(top_builddir)/src/profiling
+else
+profile_dir =
+endif
+
+join_args_dash = $\
+$(strip $1)$\
+$(and $(filter-out -,$(patsubst %-,-,$1)),$\
+      $(filter-out -,$(patsubst -%,-,$2)),$\
+      -)$\
+$(strip $2)
+
+set_gmon_prefix_env =$\
+$(if $(filter yes,$(PROFILE_BUILD)),$\
+     export GMON_OUT_PREFIX='$(call join_args_dash,$(GMON_OUT_PREFIX),$1)';,)
+
+set_gmon_prefix =$\
+$(if $(filter yes,$(PROFILE_BUILD)),$\
+     'PROFILE_BUILD:=yes' \
+       'GMON_OUT_PREFIX:=$(call join_args_dash,$(GMON_OUT_PREFIX),$1)',)
+
+extend_gmon_prefix =$\
+$(if $(filter yes,$(PROFILE_BUILD)),$\
+     $(and $2,$2:) override \
+       GMON_OUT_PREFIX:=$(call join_args_dash,$(GMON_OUT_PREFIX),$1),)
+
+gprof_summarize =$\
+$(and $(and $(filter yes,$(PROFILE_BUILD)),$\
+           $(filter-out x,x$(GPROF))),$\
+     $(AM_V_at)$\
+     Z=`echo $(GMON_OUT_PREFIX)*.out.* $\
+          | grep -v '$(GMON_OUT_PREFIX)*.out.*' $\
+          | wc -c`; $\
+     test "$Z" != "0" $\
+     && $(GPROF) -s $(top_builddir)/src/temacs$(EXEEXT) $(GMON_OUT_PREFIX)*.out.* $\
+     ||  echo "Failed to summarize profiles $GMON_OUT_PREFIX" >&2)
-- 
2.50.0

From dda5f1adfbe625def610a261201e7061999785a7 Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Sat, 12 Jul 2025 17:41:34 -0400
Subject: [PATCH 4/4] (configure.ac): Updated help for --enable-profiling
 option

---
 configure.ac | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index c114703cd50..69cf41e8661 100644
--- a/configure.ac
+++ b/configure.ac
@@ -797,7 +797,12 @@ AC_DEFUN
 [AS_HELP_STRING([--enable-profiling],
 		[build emacs with low-level, gprof profiling support.
                 Mainly useful for debugging Emacs itself.  May not work on
-                all platforms.  Stops profiler.el working.])],
+                all platforms.  Stops profiler.el working.
+                Possible argument values:
+                <blank>, yes - enable profiling after dump file is loaded,
+                all - enable profiling even when dump file is not loaded,
+                build - all + set PROFILE_BUILD=yes by default,
+                no - disable C compiler-based profiling])],
 [ac_enable_profiling="${enableval}"],[])
 AS_IF([test x$ac_enable_profiling = xno],[
           PROFILING_CFLAGS=
-- 
2.50.0