Subject: bug#79082: [emacs-bug] bug#79082: 30.1; [reproducibility] sysinfo call during profile dump

From: "Dr. Werner Fink" <[email-masked]>
Date: Tue, 29 Jul 2025 10:57:21 +0200

On 2025/07/28 17:58:15 +0300, Eli Zaretskii wrote:
> Thanks.
> 
> However, in order for us to accept these patches, they must fulfill
> the following conditions:
> 
>  . the special code which avoids causing the build to become not
>    reproducible must be conditioned on an optional variable, or at
>    least on some compile-time symbol (like, for example, something
>    derived from --disable-build-details); and
>  . the code changes should either be portable, or, if they are
>    specific to GNU/Linux, they should be conditioned on an appropriate
>    preprocessor macro to avoid compiling it on other platforms
> 
> > --- src/pdumper.c
> > +++ src/pdumper.c	2025-07-09 07:26:00.889706813 +0000
> > @@ -2140,9 +2140,9 @@ dump_interval_node (struct dump_context
> >    if (node->parent)
> >      dump_field_fixup_later (ctx, &out, node, &node->parent);
> >    if (node->left)
> > -    dump_field_fixup_later (ctx, &out, node, &node->parent);
> > +    dump_field_fixup_later (ctx, &out, node, &node->left);
> >    if (node->right)
> > -    dump_field_fixup_later (ctx, &out, node, &node->parent);
> > +    dump_field_fixup_later (ctx, &out, node, &node->right);
> 
> This code looks differently on master.
> 
> > @@ -2213,9 +2213,9 @@ dump_finalizer (struct dump_context *ctx
> >    /* Do _not_ call dump_pseudovector_lisp_fields here: we dump the
> >       only Lisp field, finalizer->function, manually, so we can give it
> >       a low weight.  */
> > -  dump_field_lv (ctx, &out, finalizer, &finalizer->function, WEIGHT_NONE);
> > -  dump_field_finalizer_ref (ctx, &out, finalizer, &finalizer->prev);
> > -  dump_field_finalizer_ref (ctx, &out, finalizer, &finalizer->next);
> > +  dump_field_lv (ctx, out, finalizer, &finalizer->function, WEIGHT_NONE);
> > +  dump_field_finalizer_ref (ctx, out, finalizer, &finalizer->prev);
> > +  dump_field_finalizer_ref (ctx, out, finalizer, &finalizer->next);
> >    return finish_dump_pvec (ctx, &out->header);
> 
> This is already in the code on master.

I know as this was derived from master for 30.1.  In the attachment I've
skipped that and moved from will_dump_p() to !build_details, switched from
secure_getnev() to getenv(), and add a comment about SOURCE_DATE_EPOCH.

> > --- src/timefns.c
> > +++ src/timefns.c	2025-07-11 07:32:33.928031852 +0000
> > @@ -600,6 +600,40 @@ make_lisp_time (struct timespec t)
> >  Lisp_Object
> >  timespec_to_lisp (struct timespec t)
> >  {
> > +  if (will_dump_p())	/* Use provided epoch at dump to get reproducible pdmp images */
> > +    {
> > +      char *epoch;
> > +      epoch = secure_getenv("SOURCE_DATE_EPOCH");
> 
> secure_getenv is specific to glibc.  If we want to use it on other
> platforms, we need to import the Gnulib secure_getenv module.
> 
> We should also make sure this change doesn't interfere with the build,
> since we do have code in Emacs that manipulates time and is used
> during the build.

That is the reason to use timespec_to_lisp() as these seems to be called
only once at start of the internal lisp engine. Without the attached patch
and the set SOURCE_DATE_EPOCH envvar there is always a changing timestamp

BUILD/emacs-30.1-build/emacs-30.1/src> od  -tx1 emacs.pdmp > x1
BUILD/emacs-30.1-build/emacs-30.1/src> diff -up x1 x2
--- x1  2025-07-29 08:38:00.204476936 +0000
+++ x2  2025-07-29 08:37:31.027684840 +0000
@@ -173419,7 +173419,7 @@
 15427560 00 50 00 03 00 00 00 40 30 2b 36 00 00 00 00 00
 15427600 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 15427620 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
-15427640 fe 15 37 7a 29 b0 5a 61 02 28 6b ee 00 00 00 00
+15427640 1a 9b 1a d3 08 b0 5a 61 02 28 6b ee 00 00 00 00
 15427660 00 50 00 03 00 00 00 40 00 00 00 00 00 00 00 00
 15427700 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 *

In my spec file I use the line

 SOURCE_DATE_EPOCH="$(sed -n '/^----/n;s/ - .*$//;p;q' emacs.changes| date -u -f - +%%s)"
 export SOURCE_DATE_EPOCH

with the changelog which gets added to the spec file.

-- 
  "Having a smoking section in a restaurant is like having
          a peeing section in a swimming pool." -- Edward Burr
From: Werner Fink <[email-masked]>
Date: Fri, 11 Jul 2025 07:52:28 +0000
Subject: [PATCH 1/1] Avoid runtime changes during pdump for no build_details 

After some debugging with a byte parser function two two
lisp objects ahd be identified, a Lisp Float which are set
by the gc, and Lisp Cons set be the initial lisp timestamp
at startup.  Both are changing at every dump therefore
avoid them or use a fixed value.

For the fixed initial lisp timestamp the environment variable
SOURCE_DATE_EPOCH is used if set.  For this --no-build-details
has to be given on the command line.  Compare with the help
for the configure option --disable-build-details.

Signed-off-by: Werner Fink <[email-masked]>
---
 src/alloc.c   |    4 ++--
 src/timefns.c |   34 ++++++++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)

--- src/alloc.c
+++ src/alloc.c	2025-07-10 10:54:56.217020919 +0000
@@ -6702,8 +6702,8 @@ garbage_collect (void)
   image_prune_animation_caches (false);
 #endif
 
-  /* Accumulate statistics.  */
-  if (FLOATP (Vgc_elapsed))
+  /* Accumulate statistics, but not during dump to get reproducible pdmp images.  */
+  if (FLOATP (Vgc_elapsed) && build_details)
     {
       static struct timespec gc_elapsed;
       gc_elapsed = timespec_add (gc_elapsed,
--- src/timefns.c
+++ src/timefns.c	2025-07-11 07:32:33.928031852 +0000
@@ -600,6 +600,40 @@ make_lisp_time (struct timespec t)
 Lisp_Object
 timespec_to_lisp (struct timespec t)
 {
+  if (!build_details)	/* Use provided epoch at dump to get reproducible pdmp images */
+    {
+      char *epoch;
+      epoch = getenv("SOURCE_DATE_EPOCH");
+      if (epoch)
+        {
+          char *endptr;
+          const char env[] = "Environment variable SOURCE_DATE_EPOCH: ";
+          errno = 0;
+          t.tv_sec = strtoull(epoch, &endptr, 10);
+          if ((errno == ERANGE && (t.tv_sec == ULLONG_MAX || t.tv_sec == 0)) ||\
+              (errno != 0 && t.tv_sec == 0))
+            {
+              fprintf(stderr, "%s strtoull: %m\n", env);
+              exit(EXIT_FAILURE);
+            }
+          if (endptr == epoch)
+            {
+              fprintf(stderr, "%s No digits were found: %s\n", env, endptr);
+              exit(EXIT_FAILURE);
+            }
+          if (*endptr != '\0')
+            {
+              fprintf(stderr, "%s Trailing garbage: %s\n", env, endptr);
+              exit(EXIT_FAILURE);
+            }
+          if (t.tv_sec > ULONG_MAX)
+            {
+              fprintf(stderr, "%s value must be smaller than or equal to %lu but was found to be: %ld \n", env, ULONG_MAX, t.tv_sec);
+              exit(EXIT_FAILURE);
+            }
+          t.tv_nsec = 0ULL;
+        }
+    }
   return Fcons (timespec_ticks (t), timespec_hz);
 }