unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Paul Eggert <eggert@cs.ucla.edu>
To: "Eli Zaretskii" <eliz@gnu.org>,
	"Mattias Engdegård" <mattias.engdegard@gmail.com>
Cc: gerd.moellmann@gmail.com, emacs-devel@gnu.org
Subject: Re: Question about bignum usage
Date: Thu, 11 Jul 2024 16:10:00 +0200	[thread overview]
Message-ID: <1cbd7603-2185-4066-9e0f-a358daf28af1@cs.ucla.edu> (raw)
In-Reply-To: <868qz0nd81.fsf@gnu.org>

[-- Attachment #1: Type: text/plain, Size: 1046 bytes --]

On 6/20/24 12:02, Eli Zaretskii wrote:
>> From: Mattias Engdegård <mattias.engdegard@gmail.com>
>> Date: Thu, 20 Jun 2024 11:38:48 +0200
>>
>> 20 juni 2024 kl. 09.36 skrev Eli Zaretskii <eliz@gnu.org>:
>>
>>> This means we check for expired times every 100 msec, so yes, we will
>>> create a lot of bignums.
>>
>> Right, and a few hundred bignums allocated every second isn't something even the current GC should have any trouble with.
>>
>> But it is a bit wasteful, isn't it? We use very short-lived bignums for internal purposes even when the picosecond part is 0, in places like decode_timer where there should be need to cons anything at all.
> 
> Sure, if that can be avoided, it would be beneficial.  Paul, any
> suggestions?

I installed the attached. 0017 should should address the performance 
problem with decode_timer. The other patches refactor and address some 
related issues.

As I understand it decode_timer was the locus of the performance issue. 
If other places are also involved, please let me know.


[-- Attachment #2: 0001-Refactor-timefns-more-functionally.patch --]
[-- Type: text/x-patch, Size: 21236 bytes --]

From a7fee79aeba5e0d5bf8d17b92b93be4a94516219 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat, 6 Jul 2024 21:52:08 +0200
Subject: [PATCH 01/17] Refactor timefns more functionally

Use a more-functional style in timefns.c, rather than passing
pointers to objects that are filled in.  Although this does not
change behavior, it should help future improvements to the code.
* src/keyboard.c (decode_timer): Return a possibly-invalid struct
timespec instead of storing a timespec into a location specified
by an arg, and returning bool.  All callers changed.
* src/systime.h (struct lisp_time): Move from here to src/timefns.c,
since the type is private to timefns.c.
* src/timefns.c (decode_float_time, decode_ticks_hz):
Return timestamp instead of storing it into a location specified
by an arg.  All callers changed.
(enum cform, union c_time, struct err_time, struct form_time):
New types, to aid functional style.
(decode_time_components): Return struct err_time
instead of returning err and storing timestamp into a location
specified by an arg.  New arg cform.  All callers changed.
(decode_lisp_time): Return struct form_time instead of returning
form and storing timestamp into a location specified by an arg.
New arg cform, replacing decode_secs_only.  All callers changed.
(list4_to_timespec): Return possibly-invalid timestamp instead
of returning a bool and storing timestamp into a location specified
by an arg.  All callers changed.
(lisp_time_struct): Omit no-longer-needed arg PFORM.
All callers changed.
---
 src/keyboard.c |  22 ++--
 src/systime.h  |  14 +--
 src/timefns.c  | 317 ++++++++++++++++++++++++++-----------------------
 3 files changed, 184 insertions(+), 169 deletions(-)

diff --git a/src/keyboard.c b/src/keyboard.c
index c75e80d2a05..40276b4157c 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -4646,20 +4646,21 @@ timer_resume_idle (void)
    ...).  Each element has the form (FUN . ARGS).  */
 Lisp_Object pending_funcalls;
 
-/* Return true if TIMER is a valid timer, placing its value into *RESULT.  */
-static bool
-decode_timer (Lisp_Object timer, struct timespec *result)
+/* Return the value of TIMER if it is a valid timer, an invalid struct
+   timespec otherwise.  */
+static struct timespec
+decode_timer (Lisp_Object timer)
 {
   Lisp_Object *vec;
 
   if (! (VECTORP (timer) && ASIZE (timer) == 10))
-    return false;
+    return invalid_timespec ();
   vec = XVECTOR (timer)->contents;
   if (! NILP (vec[0]))
-    return false;
+    return invalid_timespec ();
   if (! FIXNUMP (vec[2]))
-    return false;
-  return list4_to_timespec (vec[1], vec[2], vec[3], vec[8], result);
+    return invalid_timespec ();
+  return list4_to_timespec (vec[1], vec[2], vec[3], vec[8]);
 }
 
 
@@ -4706,7 +4707,6 @@ timer_check_2 (Lisp_Object timers, Lisp_Object idle_timers)
   while (CONSP (timers) || CONSP (idle_timers))
     {
       Lisp_Object timer = Qnil, idle_timer = Qnil;
-      struct timespec timer_time, idle_timer_time;
       struct timespec difference;
       struct timespec timer_difference = invalid_timespec ();
       struct timespec idle_timer_difference = invalid_timespec ();
@@ -4720,7 +4720,8 @@ timer_check_2 (Lisp_Object timers, Lisp_Object idle_timers)
       if (CONSP (timers))
 	{
 	  timer = XCAR (timers);
-	  if (! decode_timer (timer, &timer_time))
+	  struct timespec timer_time = decode_timer (timer);
+	  if (! timespec_valid_p (timer_time))
 	    {
 	      timers = XCDR (timers);
 	      continue;
@@ -4737,7 +4738,8 @@ timer_check_2 (Lisp_Object timers, Lisp_Object idle_timers)
       if (CONSP (idle_timers))
 	{
 	  idle_timer = XCAR (idle_timers);
-	  if (! decode_timer (idle_timer, &idle_timer_time))
+	  struct timespec idle_timer_time = decode_timer (idle_timer);
+	  if (! timespec_valid_p (idle_timer_time))
 	    {
 	      idle_timers = XCDR (idle_timers);
 	      continue;
diff --git a/src/systime.h b/src/systime.h
index fc93ea03233..1353c7158d0 100644
--- a/src/systime.h
+++ b/src/systime.h
@@ -77,22 +77,12 @@ timespec_valid_p (struct timespec t)
    (HI << LO_TIME_BITS) + LO + US / 1e6 + PS / 1e12.  */
 enum { LO_TIME_BITS = 16 };
 
-/* Components of a new-format Lisp timestamp.  */
-struct lisp_time
-{
-  /* Clock count as a Lisp integer.  */
-  Lisp_Object ticks;
-
-  /* Clock frequency (ticks per second) as a positive Lisp integer.  */
-  Lisp_Object hz;
-};
-
 /* defined in timefns.c */
 extern struct timeval make_timeval (struct timespec) ATTRIBUTE_CONST;
 extern Lisp_Object make_lisp_time (struct timespec);
 extern Lisp_Object timespec_to_lisp (struct timespec);
-extern bool list4_to_timespec (Lisp_Object, Lisp_Object, Lisp_Object,
-			       Lisp_Object, struct timespec *);
+extern struct timespec list4_to_timespec (Lisp_Object, Lisp_Object,
+					  Lisp_Object, Lisp_Object);
 extern struct timespec lisp_time_argument (Lisp_Object);
 extern double float_time (Lisp_Object);
 extern void init_timefns (void);
diff --git a/src/timefns.c b/src/timefns.c
index 746e422ffb6..70961c1a560 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -400,10 +400,21 @@ lo_time (time_t t)
    equals FLT_RADIX**P.  */
 static Lisp_Object flt_radix_power;
 
-/* Convert the finite number T into an Emacs time *RESULT, truncating
+/* Components of a Lisp timestamp (TICKS . HZ).  Using this C struct can
+   avoid the consing overhead of creating (TICKS . HZ).  */
+struct lisp_time
+{
+  /* Clock count as a Lisp integer.  */
+  Lisp_Object ticks;
+
+  /* Clock frequency (ticks per second) as a positive Lisp integer.  */
+  Lisp_Object hz;
+};
+
+/* Convert the finite number T into an Emacs time, truncating
    toward minus infinity.  Signal an error if unsuccessful.  */
-static void
-decode_float_time (double t, struct lisp_time *result)
+static struct lisp_time
+decode_float_time (double t)
 {
   Lisp_Object ticks, hz;
   if (t == 0)
@@ -447,8 +458,7 @@ decode_float_time (double t, struct lisp_time *result)
 	  ASET (flt_radix_power, scale, hz);
 	}
     }
-  result->ticks = ticks;
-  result->hz = hz;
+  return (struct lisp_time) { .ticks = ticks, .hz = hz };
 }
 
 /* Make a 4-element timestamp (HI LO US PS) from TICKS and HZ.
@@ -688,28 +698,39 @@ frac_to_double (Lisp_Object numerator, Lisp_Object denominator)
   return scalbn (mpz_get_d (*q), -scale);
 }
 
-/* From a valid timestamp (TICKS . HZ), generate the corresponding
-   time values.
+/* C timestamp forms.  This enum is passed to conversion functions to
+   specify the desired C timestamp form.  */
+enum cform
+  {
+    CFORM_TICKS_HZ, /* struct lisp_time */
+    CFORM_SECS_ONLY, /* struct lisp_time but HZ is 1 */
+    CFORM_DOUBLE /* double */
+  };
 
-   If RESULT is not null, store into *RESULT the converted time.
-   Otherwise, store into *DRESULT the number of seconds since the
-   start of the POSIX Epoch.
+/* A C timestamp in one of the forms specified by enum cform.  */
+union c_time
+{
+  struct lisp_time lt;
+  double d;
+};
 
-   Return zero, which indicates success.  */
-static int
-decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz,
-		 struct lisp_time *result, double *dresult)
+/* From a valid timestamp (TICKS . HZ), generate the corresponding
+   time value in CFORM form.  */
+static union c_time
+decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, enum cform cform)
 {
-  if (result)
-    {
-      result->ticks = ticks;
-      result->hz = hz;
-    }
-  else
-    *dresult = frac_to_double (ticks, hz);
-  return 0;
+  return (cform == CFORM_DOUBLE
+	  ? (union c_time) { .d = frac_to_double (ticks, hz) }
+	  : (union c_time) { .lt = { .ticks = ticks, .hz = hz } });
 }
 
+/* An (error number, C timestamp) pair.  */
+struct err_time
+{
+  int err;
+  union c_time time;
+};
+
 /* Lisp timestamp classification.  */
 enum timeform
   {
@@ -723,111 +744,117 @@ decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz,
   };
 
 /* From the non-float form FORM and the time components HIGH, LOW, USEC
-   and PSEC, generate the corresponding time value.  If LOW is
+   and PSEC, generate the corresponding time value in CFORM form.  If LOW is
    floating point, the other components should be zero and FORM should
    not be TIMEFORM_TICKS_HZ.
 
-   If RESULT is not null, store into *RESULT the converted time.
-   Otherwise, store into *DRESULT the number of seconds since the
-   start of the POSIX Epoch.  Unsuccessful calls may or may not store
-   results.
-
-   Return zero if successful, an error number otherwise.  */
-static int
+   Return a (0, valid timestamp) pair if successful, an (error number,
+   unspecified timestamp) pair otherwise.  */
+static struct err_time
 decode_time_components (enum timeform form,
 			Lisp_Object high, Lisp_Object low,
 			Lisp_Object usec, Lisp_Object psec,
-			struct lisp_time *result, double *dresult)
+			enum cform cform)
 {
+  Lisp_Object ticks, hz;
+
   switch (form)
     {
     case TIMEFORM_INVALID:
-      return EINVAL;
+      return (struct err_time) { .err = EINVAL };
 
     case TIMEFORM_TICKS_HZ:
-      if (INTEGERP (high)
-	  && !NILP (Fnatnump (low)) && !BASE_EQ (low, make_fixnum (0)))
-	return decode_ticks_hz (high, low, result, dresult);
-      return EINVAL;
+      if (! (INTEGERP (high)
+	     && !NILP (Fnatnump (low)) && !BASE_EQ (low, make_fixnum (0))))
+	return (struct err_time) { .err = EINVAL };
+      ticks = high;
+      hz = low;
+      break;
 
     case TIMEFORM_FLOAT:
       eassume (false);
 
     case TIMEFORM_NIL:
-      return decode_ticks_hz (timespec_ticks (current_timespec ()),
-			      timespec_hz, result, dresult);
-
-    default:
+      ticks = timespec_ticks (current_timespec ());
+      hz = timespec_hz;
       break;
-    }
-
-  if (! (INTEGERP (high) && INTEGERP (low)
-	 && FIXNUMP (usec) && FIXNUMP (psec)))
-    return EINVAL;
-  EMACS_INT us = XFIXNUM (usec);
-  EMACS_INT ps = XFIXNUM (psec);
-
-  /* Normalize out-of-range lower-order components by carrying
-     each overflow into the next higher-order component.  */
-  us += ps / 1000000 - (ps % 1000000 < 0);
-  mpz_t *s = &mpz[1];
-  mpz_set_intmax (*s, us / 1000000 - (us % 1000000 < 0));
-  mpz_add (*s, *s, *bignum_integer (&mpz[0], low));
-  mpz_addmul_ui (*s, *bignum_integer (&mpz[0], high), 1 << LO_TIME_BITS);
-  ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
-  us = us % 1000000 + 1000000 * (us % 1000000 < 0);
 
-  Lisp_Object hz;
-  switch (form)
-    {
-    case TIMEFORM_HI_LO:
-      /* Floats and nil were handled above, so it was an integer.  */
-      mpz_swap (mpz[0], *s);
-      hz = make_fixnum (1);
-      break;
-
-    case TIMEFORM_HI_LO_US:
-      mpz_set_ui (mpz[0], us);
-      mpz_addmul_ui (mpz[0], *s, 1000000);
-      hz = make_fixnum (1000000);
-      break;
+    default:
+      if (! (INTEGERP (high) && INTEGERP (low)
+	     && FIXNUMP (usec) && FIXNUMP (psec)))
+	return (struct err_time) { .err = EINVAL };
 
-    case TIMEFORM_HI_LO_US_PS:
       {
-	#if FASTER_TIMEFNS && TRILLION <= ULONG_MAX
-	  unsigned long i = us;
-	  mpz_set_ui (mpz[0], i * 1000000 + ps);
-	  mpz_addmul_ui (mpz[0], *s, TRILLION);
-	#else
-	  intmax_t i = us;
-	  mpz_set_intmax (mpz[0], i * 1000000 + ps);
-	  mpz_addmul (mpz[0], *s, ztrillion);
-	#endif
-	hz = trillion;
+	EMACS_INT us = XFIXNUM (usec);
+	EMACS_INT ps = XFIXNUM (psec);
+
+	/* Normalize out-of-range lower-order components by carrying
+	   each overflow into the next higher-order component.  */
+	us += ps / 1000000 - (ps % 1000000 < 0);
+	mpz_t *s = &mpz[1];
+	mpz_set_intmax (*s, us / 1000000 - (us % 1000000 < 0));
+	mpz_add (*s, *s, *bignum_integer (&mpz[0], low));
+	mpz_addmul_ui (*s, *bignum_integer (&mpz[0], high), 1 << LO_TIME_BITS);
+	ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
+	us = us % 1000000 + 1000000 * (us % 1000000 < 0);
+
+	switch (form)
+	  {
+	  case TIMEFORM_HI_LO:
+	    /* Floats and nil were handled above, so it was an integer.  */
+	    mpz_swap (mpz[0], *s);
+	    hz = make_fixnum (1);
+	    break;
+
+	  case TIMEFORM_HI_LO_US:
+	    mpz_set_ui (mpz[0], us);
+	    mpz_addmul_ui (mpz[0], *s, 1000000);
+	    hz = make_fixnum (1000000);
+	    break;
+
+	  case TIMEFORM_HI_LO_US_PS:
+	    {
+	      #if FASTER_TIMEFNS && TRILLION <= ULONG_MAX
+		unsigned long i = us;
+		mpz_set_ui (mpz[0], i * 1000000 + ps);
+		mpz_addmul_ui (mpz[0], *s, TRILLION);
+	      #else
+		intmax_t i = us;
+		mpz_set_intmax (mpz[0], i * 1000000 + ps);
+		mpz_addmul (mpz[0], *s, ztrillion);
+	      #endif
+	      hz = trillion;
+	    }
+	    break;
+
+	  default:
+	    eassume (false);
+	  }
+	ticks = make_integer_mpz ();
       }
       break;
-
-    default:
-      eassume (false);
     }
 
-  return decode_ticks_hz (make_integer_mpz (), hz, result, dresult);
+  return (struct err_time) { .time = decode_ticks_hz (ticks, hz, cform) };
 }
 
+/* A (Lisp timeform, C timestamp) pair.  */
+struct form_time
+{
+  enum timeform form;
+  union c_time time;
+};
+
 /* Decode a Lisp timestamp SPECIFIED_TIME that represents a time.
 
-   If DECODE_SECS_ONLY, ignore and do not validate any sub-second
+   Return a (form, time) pair that is the form of SPECIFIED-TIME
+   and the resulting C timestamp in CFORM form.
+   If CFORM == CFORM_SECS_ONLY, ignore and do not validate any sub-second
    components of an old-format SPECIFIED_TIME.
 
-   If RESULT is not null, store into *RESULT the converted time;
-   otherwise, store into *DRESULT the number of seconds since the
-   start of the POSIX Epoch.  Unsuccessful calls may or may not store
-   results.
-
-   Return the form of SPECIFIED-TIME.  Signal an error if unsuccessful.  */
-static enum timeform
-decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only,
-		  struct lisp_time *result, double *dresult)
+   Signal an error if unsuccessful.  */
+static struct form_time
+decode_lisp_time (Lisp_Object specified_time, enum cform cform)
 {
   Lisp_Object high = make_fixnum (0);
   Lisp_Object low = specified_time;
@@ -845,7 +872,7 @@ decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only,
 	{
 	  Lisp_Object low_tail = XCDR (low);
 	  low = XCAR (low);
-	  if (! decode_secs_only)
+	  if (cform != CFORM_SECS_ONLY)
 	    {
 	      if (CONSP (low_tail))
 		{
@@ -877,27 +904,31 @@ decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only,
 	form = TIMEFORM_INVALID;
     }
   else if (FASTER_TIMEFNS && INTEGERP (specified_time))
-    {
-      decode_ticks_hz (specified_time, make_fixnum (1), result, dresult);
-      return form;
-    }
+    return (struct form_time)
+      {
+	.form = form,
+	.time = decode_ticks_hz (specified_time, make_fixnum (1), cform)
+      };
   else if (FLOATP (specified_time))
     {
       double d = XFLOAT_DATA (specified_time);
       if (!isfinite (d))
 	time_error (isnan (d) ? EDOM : EOVERFLOW);
-      if (result)
-	decode_float_time (d, result);
-      else
-	*dresult = d;
-      return TIMEFORM_FLOAT;
+      return (struct form_time)
+	{
+	  .form = TIMEFORM_FLOAT,
+	  .time
+	    = (cform == CFORM_DOUBLE
+	       ? (union c_time) { .d = d }
+	       : (union c_time) { .lt = decode_float_time (d) })
+	};
     }
 
-  int err = decode_time_components (form, high, low, usec, psec,
-				    result, dresult);
-  if (err)
-    time_error (err);
-  return form;
+  struct err_time err_time
+    = decode_time_components (form, high, low, usec, psec, cform);
+  if (err_time.err)
+    time_error (err_time.err);
+  return (struct form_time) { .form = form, .time = err_time.time };
 }
 
 /* Convert a non-float Lisp timestamp SPECIFIED_TIME to double.
@@ -905,9 +936,7 @@ decode_lisp_time (Lisp_Object specified_time, bool decode_secs_only,
 double
 float_time (Lisp_Object specified_time)
 {
-  double t;
-  decode_lisp_time (specified_time, false, 0, &t);
-  return t;
+  return decode_lisp_time (specified_time, CFORM_DOUBLE).time.d;
 }
 
 /* Convert Z to time_t, returning true if it fits.  */
@@ -1000,32 +1029,26 @@ lisp_to_timespec (struct lisp_time t)
 }
 
 /* Convert (HIGH LOW USEC PSEC) to struct timespec.
-   Return true if successful.  */
-bool
+   Return a valid timestamp if successful, an invalid one otherwise.  */
+struct timespec
 list4_to_timespec (Lisp_Object high, Lisp_Object low,
-		   Lisp_Object usec, Lisp_Object psec,
-		   struct timespec *result)
+		   Lisp_Object usec, Lisp_Object psec)
 {
-  struct lisp_time t;
-  if (decode_time_components (TIMEFORM_HI_LO_US_PS, high, low, usec, psec,
-			      &t, 0))
-    return false;
-  *result = lisp_to_timespec (t);
-  return timespec_valid_p (*result);
+  struct err_time err_time
+    = decode_time_components (TIMEFORM_HI_LO_US_PS, high, low, usec, psec,
+			      CFORM_TICKS_HZ);
+  return (err_time.err
+	  ? invalid_timespec ()
+	  : lisp_to_timespec (err_time.time.lt));
 }
 
 /* Decode a Lisp list SPECIFIED_TIME that represents a time.
    If SPECIFIED_TIME is nil, use the current time.
-   Signal an error if SPECIFIED_TIME does not represent a time.
-   If PFORM, store the time's form into *PFORM.  */
+   Signal an error if SPECIFIED_TIME does not represent a time.  */
 static struct lisp_time
-lisp_time_struct (Lisp_Object specified_time, enum timeform *pform)
+lisp_time_struct (Lisp_Object specified_time)
 {
-  struct lisp_time t;
-  enum timeform form = decode_lisp_time (specified_time, false, &t, 0);
-  if (pform)
-    *pform = form;
-  return t;
+  return decode_lisp_time (specified_time, CFORM_TICKS_HZ).time.lt;
 }
 
 /* Decode a Lisp list SPECIFIED_TIME that represents a time.
@@ -1035,7 +1058,7 @@ lisp_time_struct (Lisp_Object specified_time, enum timeform *pform)
 struct timespec
 lisp_time_argument (Lisp_Object specified_time)
 {
-  struct lisp_time lt = lisp_time_struct (specified_time, 0);
+  struct lisp_time lt = lisp_time_struct (specified_time);
   struct timespec t = lisp_to_timespec (lt);
   if (! timespec_valid_p (t))
     time_overflow ();
@@ -1047,9 +1070,8 @@ lisp_time_argument (Lisp_Object specified_time)
 static time_t
 lisp_seconds_argument (Lisp_Object specified_time)
 {
-  struct lisp_time lt;
-  decode_lisp_time (specified_time, true, &lt, 0);
-  struct timespec t = lisp_to_timespec (lt);
+  struct form_time ft = decode_lisp_time (specified_time, CFORM_SECS_ONLY);
+  struct timespec t = lisp_to_timespec (ft.time.lt);
   if (! timespec_valid_p (t))
     time_overflow ();
   return t.tv_sec;
@@ -1096,9 +1118,11 @@ lispint_arith (Lisp_Object a, Lisp_Object b, bool subtract)
 static Lisp_Object
 time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
 {
-  enum timeform aform, bform;
-  struct lisp_time ta = lisp_time_struct (a, &aform);
-  struct lisp_time tb = lisp_time_struct (b, &bform);
+  struct form_time
+    fta = decode_lisp_time (a, CFORM_TICKS_HZ),
+    ftb = decode_lisp_time (b, CFORM_TICKS_HZ);
+  enum timeform aform = fta.form, bform = ftb.form;
+  struct lisp_time ta = fta.time.lt, tb = ftb.time.lt;
   Lisp_Object ticks, hz;
 
   if (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz))
@@ -1239,8 +1263,8 @@ time_cmp (Lisp_Object a, Lisp_Object b)
 
   /* Compare (ATICKS . AZ) to (BTICKS . BHZ) by comparing
      ATICKS * BHZ to BTICKS * AHZ.  */
-  struct lisp_time ta = lisp_time_struct (a, 0);
-  struct lisp_time tb = lisp_time_struct (b, 0);
+  struct lisp_time ta = lisp_time_struct (a);
+  struct lisp_time tb = lisp_time_struct (b);
   mpz_t const *za = bignum_integer (&mpz[0], ta.ticks);
   mpz_t const *zb = bignum_integer (&mpz[1], tb.ticks);
   if (! (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz)))
@@ -1517,7 +1541,7 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
   (Lisp_Object specified_time, Lisp_Object zone, Lisp_Object form)
 {
   /* Compute broken-down local time LOCAL_TM from SPECIFIED_TIME and ZONE.  */
-  struct lisp_time lt = lisp_time_struct (specified_time, 0);
+  struct lisp_time lt = lisp_time_struct (specified_time);
   struct timespec ts = lisp_to_timespec (lt);
   if (! timespec_valid_p (ts))
     time_overflow ();
@@ -1695,8 +1719,7 @@ DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
     }
 
   /* Let SEC = floor (LT.ticks / HZ), with SUBSECTICKS the remainder.  */
-  struct lisp_time lt;
-  decode_lisp_time (secarg, false, &lt, 0);
+  struct lisp_time lt = decode_lisp_time (secarg, CFORM_TICKS_HZ).time.lt;
   Lisp_Object hz = lt.hz, sec, subsecticks;
   if (FASTER_TIMEFNS && BASE_EQ (hz, make_fixnum (1)))
     {
@@ -1765,8 +1788,8 @@ DEFUN ("time-convert", Ftime_convert, Stime_convert, 1, 2, 0,
 {
   /* FIXME: Any reason why we don't offer a `float` output format option as
      well, since we accept it as input?  */
-  struct lisp_time t;
-  enum timeform input_form = decode_lisp_time (time, false, &t, 0);
+  struct form_time form_time = decode_lisp_time (time, CFORM_TICKS_HZ);
+  struct lisp_time t = form_time.time.lt;
   form = (!NILP (form) ? maybe_remove_pos_from_symbol (form)
 	  : current_time_list ? Qlist : Qt);
   if (BASE_EQ (form, Qlist))
@@ -1776,7 +1799,7 @@ DEFUN ("time-convert", Ftime_convert, Stime_convert, 1, 2, 0,
   if (BASE_EQ (form, Qt))
     form = t.hz;
   if (FASTER_TIMEFNS
-      && input_form == TIMEFORM_TICKS_HZ && BASE_EQ (form, XCDR (time)))
+      && form_time.form == TIMEFORM_TICKS_HZ && BASE_EQ (form, XCDR (time)))
     return time;
   return Fcons (lisp_time_hz_ticks (t, form), form);
 }
-- 
2.34.1


[-- Attachment #3: 0002-Refactor-timefns-order.patch --]
[-- Type: text/x-patch, Size: 14802 bytes --]

From 7d0a769e2a275edd0b7215242170d5e4b53e668e Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 7 Jul 2024 15:42:10 +0200
Subject: [PATCH 02/17] Refactor timefns order

Move definitions around in timefns.c.  This does not affect the
implementation; it merely makes future changes easier to follow.
* src/timefns.c (frac_to_double, mpz_time, lisp_to_timespec)
(enum cform, union c_time, decode_ticks_hz): Move earlier.
---
 src/timefns.c | 400 +++++++++++++++++++++++++-------------------------
 1 file changed, 200 insertions(+), 200 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 70961c1a560..a7a7d552506 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -400,6 +400,112 @@ lo_time (time_t t)
    equals FLT_RADIX**P.  */
 static Lisp_Object flt_radix_power;
 
+/* Return NUMERATOR / DENOMINATOR, rounded to the nearest double.
+   Arguments must be Lisp integers, and DENOMINATOR must be positive.  */
+static double
+frac_to_double (Lisp_Object numerator, Lisp_Object denominator)
+{
+  intmax_t intmax_numerator, intmax_denominator;
+  if (FASTER_TIMEFNS
+      && integer_to_intmax (numerator, &intmax_numerator)
+      && integer_to_intmax (denominator, &intmax_denominator)
+      && intmax_numerator % intmax_denominator == 0)
+    return intmax_numerator / intmax_denominator;
+
+  /* Compute number of base-FLT_RADIX digits in numerator and denominator.  */
+  mpz_t const *n = bignum_integer (&mpz[0], numerator);
+  mpz_t const *d = bignum_integer (&mpz[1], denominator);
+  ptrdiff_t ndig = mpz_sizeinbase (*n, FLT_RADIX);
+  ptrdiff_t ddig = mpz_sizeinbase (*d, FLT_RADIX);
+
+  /* Scale with SCALE when doing integer division.  That is, compute
+     (N * FLT_RADIX**SCALE) / D [or, if SCALE is negative, N / (D *
+     FLT_RADIX**-SCALE)] as a bignum, convert the bignum to double,
+     then divide the double by FLT_RADIX**SCALE.  First scale N
+     (or scale D, if SCALE is negative) ...  */
+  ptrdiff_t scale = ddig - ndig + DBL_MANT_DIG;
+  if (scale < 0)
+    {
+      mpz_mul_2exp (mpz[1], *d, - (scale * LOG2_FLT_RADIX));
+      d = &mpz[1];
+    }
+  else
+    {
+      /* min so we don't scale tiny numbers as if they were normalized.  */
+      scale = min (scale, flt_radix_power_size - 1);
+
+      mpz_mul_2exp (mpz[0], *n, scale * LOG2_FLT_RADIX);
+      n = &mpz[0];
+    }
+  /* ... and then divide, with quotient Q and remainder R.  */
+  mpz_t *q = &mpz[2];
+  mpz_t *r = &mpz[3];
+  mpz_tdiv_qr (*q, *r, *n, *d);
+
+  /* The amount to add to the absolute value of Q so that truncating
+     it to double will round correctly.  */
+  int incr;
+
+  /* Round the quotient before converting it to double.
+     If the quotient is less than FLT_RADIX ** DBL_MANT_DIG,
+     round to the nearest integer; otherwise, it is less than
+     FLT_RADIX ** (DBL_MANT_DIG + 1) and round it to the nearest
+     multiple of FLT_RADIX.  Break ties to even.  */
+  if (mpz_sizeinbase (*q, FLT_RADIX) <= DBL_MANT_DIG)
+    {
+      /* Converting to double will use the whole quotient so add 1 to
+	 its absolute value as per round-to-even; i.e., if the doubled
+	 remainder exceeds the denominator, or exactly equals the
+	 denominator and adding 1 would make the quotient even.  */
+      mpz_mul_2exp (*r, *r, 1);
+      int cmp = mpz_cmpabs (*r, *d);
+      incr = cmp > 0 || (cmp == 0 && (FASTER_TIMEFNS && FLT_RADIX == 2
+				      ? mpz_odd_p (*q)
+				      : mpz_tdiv_ui (*q, FLT_RADIX) & 1));
+    }
+  else
+    {
+      /* Converting to double will discard the quotient's low-order digit,
+	 so add FLT_RADIX to its absolute value as per round-to-even.  */
+      int lo_2digits = mpz_tdiv_ui (*q, FLT_RADIX * FLT_RADIX);
+      eassume (0 <= lo_2digits && lo_2digits < FLT_RADIX * FLT_RADIX);
+      int lo_digit = lo_2digits % FLT_RADIX;
+      incr = ((lo_digit > FLT_RADIX / 2
+	       || (lo_digit == FLT_RADIX / 2 && FLT_RADIX % 2 == 0
+		   && ((lo_2digits / FLT_RADIX) & 1
+		       || mpz_sgn (*r) != 0)))
+	      ? FLT_RADIX : 0);
+    }
+
+  /* Increment the absolute value of the quotient by INCR.  */
+  if (!FASTER_TIMEFNS || incr != 0)
+    (mpz_sgn (*n) < 0 ? mpz_sub_ui : mpz_add_ui) (*q, *q, incr);
+
+  /* Rescale the integer Q back to double.  This step does not round.  */
+  return scalbn (mpz_get_d (*q), -scale);
+}
+
+/* Convert Z to time_t, returning true if it fits.  */
+static bool
+mpz_time (mpz_t const z, time_t *t)
+{
+  if (TYPE_SIGNED (time_t))
+    {
+      intmax_t i;
+      if (! (mpz_to_intmax (z, &i) && TIME_T_MIN <= i && i <= TIME_T_MAX))
+	return false;
+      *t = i;
+    }
+  else
+    {
+      uintmax_t i;
+      if (! (mpz_to_uintmax (z, &i) && i <= TIME_T_MAX))
+	return false;
+      *t = i;
+    }
+  return true;
+}
+
 /* Components of a Lisp timestamp (TICKS . HZ).  Using this C struct can
    avoid the consing overhead of creating (TICKS . HZ).  */
 struct lisp_time
@@ -411,6 +517,100 @@ lo_time (time_t t)
   Lisp_Object hz;
 };
 
+/* Convert T to struct timespec, returning an invalid timespec
+   if T does not fit.  */
+static struct timespec
+lisp_to_timespec (struct lisp_time t)
+{
+  struct timespec result = invalid_timespec ();
+  int ns;
+  mpz_t *q = &mpz[0];
+  mpz_t const *qt = q;
+
+  /* Floor-divide (T.ticks * TIMESPEC_HZ) by T.hz,
+     yielding quotient Q (tv_sec) and remainder NS (tv_nsec).
+     Return an invalid timespec if Q does not fit in time_t.
+     For speed, prefer fixnum arithmetic if it works.  */
+  if (FASTER_TIMEFNS && BASE_EQ (t.hz, timespec_hz))
+    {
+      if (FIXNUMP (t.ticks))
+	{
+	  EMACS_INT s = XFIXNUM (t.ticks) / TIMESPEC_HZ;
+	  ns = XFIXNUM (t.ticks) % TIMESPEC_HZ;
+	  if (ns < 0)
+	    s--, ns += TIMESPEC_HZ;
+	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
+	      && s <= TIME_T_MAX)
+	    {
+	      result.tv_sec = s;
+	      result.tv_nsec = ns;
+	    }
+	  return result;
+	}
+      else
+	ns = mpz_fdiv_q_ui (*q, *xbignum_val (t.ticks), TIMESPEC_HZ);
+    }
+  else if (FASTER_TIMEFNS && BASE_EQ (t.hz, make_fixnum (1)))
+    {
+      ns = 0;
+      if (FIXNUMP (t.ticks))
+	{
+	  EMACS_INT s = XFIXNUM (t.ticks);
+	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
+	      && s <= TIME_T_MAX)
+	    {
+	      result.tv_sec = s;
+	      result.tv_nsec = ns;
+	    }
+	  return result;
+	}
+      else
+	qt = xbignum_val (t.ticks);
+    }
+  else
+    {
+      mpz_mul_ui (*q, *bignum_integer (q, t.ticks), TIMESPEC_HZ);
+      mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], t.hz));
+      ns = mpz_fdiv_q_ui (*q, *q, TIMESPEC_HZ);
+    }
+
+  /* Check that Q fits in time_t, not merely in T.tv_sec.  With some versions
+     of MinGW, tv_sec is a 64-bit type, whereas time_t is a 32-bit type.  */
+  time_t sec;
+  if (mpz_time (*qt, &sec))
+    {
+      result.tv_sec = sec;
+      result.tv_nsec = ns;
+    }
+  return result;
+}
+
+/* C timestamp forms.  This enum is passed to conversion functions to
+   specify the desired C timestamp form.  */
+enum cform
+  {
+    CFORM_TICKS_HZ, /* struct lisp_time */
+    CFORM_SECS_ONLY, /* struct lisp_time but HZ is 1 */
+    CFORM_DOUBLE /* double */
+  };
+
+/* A C timestamp in one of the forms specified by enum cform.  */
+union c_time
+{
+  struct lisp_time lt;
+  double d;
+};
+
+/* From a valid timestamp (TICKS . HZ), generate the corresponding
+   time value in CFORM form.  */
+static union c_time
+decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, enum cform cform)
+{
+  return (cform == CFORM_DOUBLE
+	  ? (union c_time) { .d = frac_to_double (ticks, hz) }
+	  : (union c_time) { .lt = { .ticks = ticks, .hz = hz } });
+}
+
 /* Convert the finite number T into an Emacs time, truncating
    toward minus infinity.  Signal an error if unsuccessful.  */
 static struct lisp_time
@@ -613,117 +813,6 @@ timespec_to_lisp (struct timespec t)
   return Fcons (timespec_ticks (t), timespec_hz);
 }
 
-/* Return NUMERATOR / DENOMINATOR, rounded to the nearest double.
-   Arguments must be Lisp integers, and DENOMINATOR must be positive.  */
-static double
-frac_to_double (Lisp_Object numerator, Lisp_Object denominator)
-{
-  intmax_t intmax_numerator, intmax_denominator;
-  if (FASTER_TIMEFNS
-      && integer_to_intmax (numerator, &intmax_numerator)
-      && integer_to_intmax (denominator, &intmax_denominator)
-      && intmax_numerator % intmax_denominator == 0)
-    return intmax_numerator / intmax_denominator;
-
-  /* Compute number of base-FLT_RADIX digits in numerator and denominator.  */
-  mpz_t const *n = bignum_integer (&mpz[0], numerator);
-  mpz_t const *d = bignum_integer (&mpz[1], denominator);
-  ptrdiff_t ndig = mpz_sizeinbase (*n, FLT_RADIX);
-  ptrdiff_t ddig = mpz_sizeinbase (*d, FLT_RADIX);
-
-  /* Scale with SCALE when doing integer division.  That is, compute
-     (N * FLT_RADIX**SCALE) / D [or, if SCALE is negative, N / (D *
-     FLT_RADIX**-SCALE)] as a bignum, convert the bignum to double,
-     then divide the double by FLT_RADIX**SCALE.  First scale N
-     (or scale D, if SCALE is negative) ...  */
-  ptrdiff_t scale = ddig - ndig + DBL_MANT_DIG;
-  if (scale < 0)
-    {
-      mpz_mul_2exp (mpz[1], *d, - (scale * LOG2_FLT_RADIX));
-      d = &mpz[1];
-    }
-  else
-    {
-      /* min so we don't scale tiny numbers as if they were normalized.  */
-      scale = min (scale, flt_radix_power_size - 1);
-
-      mpz_mul_2exp (mpz[0], *n, scale * LOG2_FLT_RADIX);
-      n = &mpz[0];
-    }
-  /* ... and then divide, with quotient Q and remainder R.  */
-  mpz_t *q = &mpz[2];
-  mpz_t *r = &mpz[3];
-  mpz_tdiv_qr (*q, *r, *n, *d);
-
-  /* The amount to add to the absolute value of Q so that truncating
-     it to double will round correctly.  */
-  int incr;
-
-  /* Round the quotient before converting it to double.
-     If the quotient is less than FLT_RADIX ** DBL_MANT_DIG,
-     round to the nearest integer; otherwise, it is less than
-     FLT_RADIX ** (DBL_MANT_DIG + 1) and round it to the nearest
-     multiple of FLT_RADIX.  Break ties to even.  */
-  if (mpz_sizeinbase (*q, FLT_RADIX) <= DBL_MANT_DIG)
-    {
-      /* Converting to double will use the whole quotient so add 1 to
-	 its absolute value as per round-to-even; i.e., if the doubled
-	 remainder exceeds the denominator, or exactly equals the
-	 denominator and adding 1 would make the quotient even.  */
-      mpz_mul_2exp (*r, *r, 1);
-      int cmp = mpz_cmpabs (*r, *d);
-      incr = cmp > 0 || (cmp == 0 && (FASTER_TIMEFNS && FLT_RADIX == 2
-				      ? mpz_odd_p (*q)
-				      : mpz_tdiv_ui (*q, FLT_RADIX) & 1));
-    }
-  else
-    {
-      /* Converting to double will discard the quotient's low-order digit,
-	 so add FLT_RADIX to its absolute value as per round-to-even.  */
-      int lo_2digits = mpz_tdiv_ui (*q, FLT_RADIX * FLT_RADIX);
-      eassume (0 <= lo_2digits && lo_2digits < FLT_RADIX * FLT_RADIX);
-      int lo_digit = lo_2digits % FLT_RADIX;
-      incr = ((lo_digit > FLT_RADIX / 2
-	       || (lo_digit == FLT_RADIX / 2 && FLT_RADIX % 2 == 0
-		   && ((lo_2digits / FLT_RADIX) & 1
-		       || mpz_sgn (*r) != 0)))
-	      ? FLT_RADIX : 0);
-    }
-
-  /* Increment the absolute value of the quotient by INCR.  */
-  if (!FASTER_TIMEFNS || incr != 0)
-    (mpz_sgn (*n) < 0 ? mpz_sub_ui : mpz_add_ui) (*q, *q, incr);
-
-  /* Rescale the integer Q back to double.  This step does not round.  */
-  return scalbn (mpz_get_d (*q), -scale);
-}
-
-/* C timestamp forms.  This enum is passed to conversion functions to
-   specify the desired C timestamp form.  */
-enum cform
-  {
-    CFORM_TICKS_HZ, /* struct lisp_time */
-    CFORM_SECS_ONLY, /* struct lisp_time but HZ is 1 */
-    CFORM_DOUBLE /* double */
-  };
-
-/* A C timestamp in one of the forms specified by enum cform.  */
-union c_time
-{
-  struct lisp_time lt;
-  double d;
-};
-
-/* From a valid timestamp (TICKS . HZ), generate the corresponding
-   time value in CFORM form.  */
-static union c_time
-decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, enum cform cform)
-{
-  return (cform == CFORM_DOUBLE
-	  ? (union c_time) { .d = frac_to_double (ticks, hz) }
-	  : (union c_time) { .lt = { .ticks = ticks, .hz = hz } });
-}
-
 /* An (error number, C timestamp) pair.  */
 struct err_time
 {
@@ -939,95 +1028,6 @@ float_time (Lisp_Object specified_time)
   return decode_lisp_time (specified_time, CFORM_DOUBLE).time.d;
 }
 
-/* Convert Z to time_t, returning true if it fits.  */
-static bool
-mpz_time (mpz_t const z, time_t *t)
-{
-  if (TYPE_SIGNED (time_t))
-    {
-      intmax_t i;
-      if (! (mpz_to_intmax (z, &i) && TIME_T_MIN <= i && i <= TIME_T_MAX))
-	return false;
-      *t = i;
-    }
-  else
-    {
-      uintmax_t i;
-      if (! (mpz_to_uintmax (z, &i) && i <= TIME_T_MAX))
-	return false;
-      *t = i;
-    }
-  return true;
-}
-
-/* Convert T to struct timespec, returning an invalid timespec
-   if T does not fit.  */
-static struct timespec
-lisp_to_timespec (struct lisp_time t)
-{
-  struct timespec result = invalid_timespec ();
-  int ns;
-  mpz_t *q = &mpz[0];
-  mpz_t const *qt = q;
-
-  /* Floor-divide (T.ticks * TIMESPEC_HZ) by T.hz,
-     yielding quotient Q (tv_sec) and remainder NS (tv_nsec).
-     Return an invalid timespec if Q does not fit in time_t.
-     For speed, prefer fixnum arithmetic if it works.  */
-  if (FASTER_TIMEFNS && BASE_EQ (t.hz, timespec_hz))
-    {
-      if (FIXNUMP (t.ticks))
-	{
-	  EMACS_INT s = XFIXNUM (t.ticks) / TIMESPEC_HZ;
-	  ns = XFIXNUM (t.ticks) % TIMESPEC_HZ;
-	  if (ns < 0)
-	    s--, ns += TIMESPEC_HZ;
-	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
-	      && s <= TIME_T_MAX)
-	    {
-	      result.tv_sec = s;
-	      result.tv_nsec = ns;
-	    }
-	  return result;
-	}
-      else
-	ns = mpz_fdiv_q_ui (*q, *xbignum_val (t.ticks), TIMESPEC_HZ);
-    }
-  else if (FASTER_TIMEFNS && BASE_EQ (t.hz, make_fixnum (1)))
-    {
-      ns = 0;
-      if (FIXNUMP (t.ticks))
-	{
-	  EMACS_INT s = XFIXNUM (t.ticks);
-	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
-	      && s <= TIME_T_MAX)
-	    {
-	      result.tv_sec = s;
-	      result.tv_nsec = ns;
-	    }
-	  return result;
-	}
-      else
-	qt = xbignum_val (t.ticks);
-    }
-  else
-    {
-      mpz_mul_ui (*q, *bignum_integer (q, t.ticks), TIMESPEC_HZ);
-      mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], t.hz));
-      ns = mpz_fdiv_q_ui (*q, *q, TIMESPEC_HZ);
-    }
-
-  /* Check that Q fits in time_t, not merely in T.tv_sec.  With some versions
-     of MinGW, tv_sec is a 64-bit type, whereas time_t is a 32-bit type.  */
-  time_t sec;
-  if (mpz_time (*qt, &sec))
-    {
-      result.tv_sec = sec;
-      result.tv_nsec = ns;
-    }
-  return result;
-}
-
 /* Convert (HIGH LOW USEC PSEC) to struct timespec.
    Return a valid timestamp if successful, an invalid one otherwise.  */
 struct timespec
-- 
2.34.1


[-- Attachment #4: 0003-Refactor-decode_ticks_hz-via-switch.patch --]
[-- Type: text/x-patch, Size: 1190 bytes --]

From a79b25b987f0288bd0e773ccb9f2b4a49595898d Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 7 Jul 2024 16:05:52 +0200
Subject: [PATCH 03/17] Refactor decode_ticks_hz via switch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* src/timefns.c (decode_ticks_hz): Change ?: to ‘switch’,
for benefit of future changes.
---
 src/timefns.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index a7a7d552506..ac41a3d6958 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -606,9 +606,14 @@ lisp_to_timespec (struct lisp_time t)
 static union c_time
 decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, enum cform cform)
 {
-  return (cform == CFORM_DOUBLE
-	  ? (union c_time) { .d = frac_to_double (ticks, hz) }
-	  : (union c_time) { .lt = { .ticks = ticks, .hz = hz } });
+  switch (cform)
+    {
+    case CFORM_DOUBLE:
+      return (union c_time) { .d = frac_to_double (ticks, hz) };
+
+    default:
+      return (union c_time) { .lt = { .ticks = ticks, .hz = hz } };
+    }
 }
 
 /* Convert the finite number T into an Emacs time, truncating
-- 
2.34.1


[-- Attachment #5: 0004-Split-lisp_to_timespec-in-two.patch --]
[-- Type: text/x-patch, Size: 3776 bytes --]

From cca50c17f4edf11e4dde484d0cfc62b740b8163a Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 7 Jul 2024 16:18:18 +0200
Subject: [PATCH 04/17] Split lisp_to_timespec in two

* src/timefns.c (ticks_hz_to_timespec): New function,
which is almost all the old lisp_to_timespec but with
a 2-arg API.  This should help further changes.
(lisp_to_timespec): Use it.
---
 src/timefns.c | 42 +++++++++++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 17 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index ac41a3d6958..c748867b54d 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -517,26 +517,26 @@ mpz_time (mpz_t const z, time_t *t)
   Lisp_Object hz;
 };
 
-/* Convert T to struct timespec, returning an invalid timespec
-   if T does not fit.  */
+/* Convert (TICKS . HZ) to struct timespec, returning an invalid
+   timespec if the result would not fit.  */
 static struct timespec
-lisp_to_timespec (struct lisp_time t)
+ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
 {
   struct timespec result = invalid_timespec ();
   int ns;
   mpz_t *q = &mpz[0];
   mpz_t const *qt = q;
 
-  /* Floor-divide (T.ticks * TIMESPEC_HZ) by T.hz,
+  /* Floor-divide (TICKS * TIMESPEC_HZ) by HZ,
      yielding quotient Q (tv_sec) and remainder NS (tv_nsec).
      Return an invalid timespec if Q does not fit in time_t.
      For speed, prefer fixnum arithmetic if it works.  */
-  if (FASTER_TIMEFNS && BASE_EQ (t.hz, timespec_hz))
+  if (FASTER_TIMEFNS && BASE_EQ (hz, timespec_hz))
     {
-      if (FIXNUMP (t.ticks))
+      if (FIXNUMP (ticks))
 	{
-	  EMACS_INT s = XFIXNUM (t.ticks) / TIMESPEC_HZ;
-	  ns = XFIXNUM (t.ticks) % TIMESPEC_HZ;
+	  EMACS_INT s = XFIXNUM (ticks) / TIMESPEC_HZ;
+	  ns = XFIXNUM (ticks) % TIMESPEC_HZ;
 	  if (ns < 0)
 	    s--, ns += TIMESPEC_HZ;
 	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
@@ -548,14 +548,14 @@ lisp_to_timespec (struct lisp_time t)
 	  return result;
 	}
       else
-	ns = mpz_fdiv_q_ui (*q, *xbignum_val (t.ticks), TIMESPEC_HZ);
+	ns = mpz_fdiv_q_ui (*q, *xbignum_val (ticks), TIMESPEC_HZ);
     }
-  else if (FASTER_TIMEFNS && BASE_EQ (t.hz, make_fixnum (1)))
+  else if (FASTER_TIMEFNS && BASE_EQ (hz, make_fixnum (1)))
     {
       ns = 0;
-      if (FIXNUMP (t.ticks))
+      if (FIXNUMP (ticks))
 	{
-	  EMACS_INT s = XFIXNUM (t.ticks);
+	  EMACS_INT s = XFIXNUM (ticks);
 	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
 	      && s <= TIME_T_MAX)
 	    {
@@ -565,17 +565,17 @@ lisp_to_timespec (struct lisp_time t)
 	  return result;
 	}
       else
-	qt = xbignum_val (t.ticks);
+	qt = xbignum_val (ticks);
     }
   else
     {
-      mpz_mul_ui (*q, *bignum_integer (q, t.ticks), TIMESPEC_HZ);
-      mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], t.hz));
+      mpz_mul_ui (*q, *bignum_integer (q, ticks), TIMESPEC_HZ);
+      mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], hz));
       ns = mpz_fdiv_q_ui (*q, *q, TIMESPEC_HZ);
     }
 
-  /* Check that Q fits in time_t, not merely in T.tv_sec.  With some versions
-     of MinGW, tv_sec is a 64-bit type, whereas time_t is a 32-bit type.  */
+  /* Check that Q fits in time_t, not merely in RESULT.tv_sec.  With some MinGW
+     versions, tv_sec is a 64-bit type, whereas time_t is a 32-bit type.  */
   time_t sec;
   if (mpz_time (*qt, &sec))
     {
@@ -585,6 +585,14 @@ lisp_to_timespec (struct lisp_time t)
   return result;
 }
 
+/* Convert T to struct timespec, returning an invalid timespec
+   if T does not fit.  */
+static struct timespec
+lisp_to_timespec (struct lisp_time t)
+{
+  return ticks_hz_to_timespec (t.ticks, t.hz);
+}
+
 /* C timestamp forms.  This enum is passed to conversion functions to
    specify the desired C timestamp form.  */
 enum cform
-- 
2.34.1


[-- Attachment #6: 0005-Push-some-time-conversions-down.patch --]
[-- Type: text/x-patch, Size: 6483 bytes --]

From faf78003dfb310b2d381b1748c57be568632ca8b Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 7 Jul 2024 20:50:47 +0200
Subject: [PATCH 05/17] Push some time conversions down

* src/timefns.c: Push some time conversions down to lower level fns.
This is a win in its own right and should allow for further speedups.
(lisp_to_timespec): Remove; this convenience function is no longer
needed now that there would be only one caller.  Remaining caller
changed to use definiens.
(enum cform): New constant CFORM_TIMESPEC.  Also, CFORM_SECS_ONLY
now generates a struct timespec instead of a struct lisp_time.
(union c_time.ts): New member.
(decode_ticks_hz): Handle new struct timespec cases.
(decode_float_time, lisp_time_struct): New arg cform, and return
union c_time rather than struct lisp_time.  All callers changed.
(list4_to_timespec, lisp_time_argument, lisp_seconds_argument):
Let lower-level function do the conversion, to allow for better
optimization.
---
 src/timefns.c | 61 +++++++++++++++++++++++----------------------------
 1 file changed, 28 insertions(+), 33 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index c748867b54d..cc148fa9752 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -585,20 +585,13 @@ ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
   return result;
 }
 
-/* Convert T to struct timespec, returning an invalid timespec
-   if T does not fit.  */
-static struct timespec
-lisp_to_timespec (struct lisp_time t)
-{
-  return ticks_hz_to_timespec (t.ticks, t.hz);
-}
-
 /* C timestamp forms.  This enum is passed to conversion functions to
    specify the desired C timestamp form.  */
 enum cform
   {
     CFORM_TICKS_HZ, /* struct lisp_time */
-    CFORM_SECS_ONLY, /* struct lisp_time but HZ is 1 */
+    CFORM_TIMESPEC, /* struct timespec */
+    CFORM_SECS_ONLY, /* struct timespec but tv_nsec == 0 if timespec valid */
     CFORM_DOUBLE /* double */
   };
 
@@ -606,6 +599,7 @@ lisp_to_timespec (struct lisp_time t)
 union c_time
 {
   struct lisp_time lt;
+  struct timespec ts;
   double d;
 };
 
@@ -619,16 +613,22 @@ decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, enum cform cform)
     case CFORM_DOUBLE:
       return (union c_time) { .d = frac_to_double (ticks, hz) };
 
-    default:
+    case CFORM_TICKS_HZ:
       return (union c_time) { .lt = { .ticks = ticks, .hz = hz } };
+
+    default:
+      return (union c_time) { .ts = ticks_hz_to_timespec (ticks, hz) };
     }
 }
 
-/* Convert the finite number T into an Emacs time, truncating
+/* Convert the finite number T into a C time of form CFORM, truncating
    toward minus infinity.  Signal an error if unsuccessful.  */
-static struct lisp_time
-decode_float_time (double t)
+static union c_time
+decode_float_time (double t, enum cform cform)
 {
+  if (FASTER_TIMEFNS && cform == CFORM_DOUBLE)
+    return (union c_time) { .d = t };
+
   Lisp_Object ticks, hz;
   if (t == 0)
     {
@@ -671,7 +671,7 @@ decode_float_time (double t)
 	  ASET (flt_radix_power, scale, hz);
 	}
     }
-  return (struct lisp_time) { .ticks = ticks, .hz = hz };
+  return decode_ticks_hz (ticks, hz, cform);
 }
 
 /* Make a 4-element timestamp (HI LO US PS) from TICKS and HZ.
@@ -1019,10 +1019,7 @@ decode_lisp_time (Lisp_Object specified_time, enum cform cform)
       return (struct form_time)
 	{
 	  .form = TIMEFORM_FLOAT,
-	  .time
-	    = (cform == CFORM_DOUBLE
-	       ? (union c_time) { .d = d }
-	       : (union c_time) { .lt = decode_float_time (d) })
+	  .time = decode_float_time (d, cform)
 	};
     }
 
@@ -1049,19 +1046,18 @@ list4_to_timespec (Lisp_Object high, Lisp_Object low,
 {
   struct err_time err_time
     = decode_time_components (TIMEFORM_HI_LO_US_PS, high, low, usec, psec,
-			      CFORM_TICKS_HZ);
-  return (err_time.err
-	  ? invalid_timespec ()
-	  : lisp_to_timespec (err_time.time.lt));
+			      CFORM_TIMESPEC);
+  return err_time.err ? invalid_timespec () : err_time.time.ts;
 }
 
 /* Decode a Lisp list SPECIFIED_TIME that represents a time.
    If SPECIFIED_TIME is nil, use the current time.
+   Decode to CFORM form.
    Signal an error if SPECIFIED_TIME does not represent a time.  */
-static struct lisp_time
-lisp_time_struct (Lisp_Object specified_time)
+static union c_time
+lisp_time_struct (Lisp_Object specified_time, enum cform cform)
 {
-  return decode_lisp_time (specified_time, CFORM_TICKS_HZ).time.lt;
+  return decode_lisp_time (specified_time, cform).time;
 }
 
 /* Decode a Lisp list SPECIFIED_TIME that represents a time.
@@ -1071,8 +1067,7 @@ lisp_time_struct (Lisp_Object specified_time)
 struct timespec
 lisp_time_argument (Lisp_Object specified_time)
 {
-  struct lisp_time lt = lisp_time_struct (specified_time);
-  struct timespec t = lisp_to_timespec (lt);
+  struct timespec t = lisp_time_struct (specified_time, CFORM_TIMESPEC).ts;
   if (! timespec_valid_p (t))
     time_overflow ();
   return t;
@@ -1083,8 +1078,8 @@ lisp_time_argument (Lisp_Object specified_time)
 static time_t
 lisp_seconds_argument (Lisp_Object specified_time)
 {
-  struct form_time ft = decode_lisp_time (specified_time, CFORM_SECS_ONLY);
-  struct timespec t = lisp_to_timespec (ft.time.lt);
+  struct timespec t
+    = decode_lisp_time (specified_time, CFORM_SECS_ONLY).time.ts;
   if (! timespec_valid_p (t))
     time_overflow ();
   return t.tv_sec;
@@ -1276,8 +1271,8 @@ time_cmp (Lisp_Object a, Lisp_Object b)
 
   /* Compare (ATICKS . AZ) to (BTICKS . BHZ) by comparing
      ATICKS * BHZ to BTICKS * AHZ.  */
-  struct lisp_time ta = lisp_time_struct (a);
-  struct lisp_time tb = lisp_time_struct (b);
+  struct lisp_time ta = lisp_time_struct (a, CFORM_TICKS_HZ).lt;
+  struct lisp_time tb = lisp_time_struct (b, CFORM_TICKS_HZ).lt;
   mpz_t const *za = bignum_integer (&mpz[0], ta.ticks);
   mpz_t const *zb = bignum_integer (&mpz[1], tb.ticks);
   if (! (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz)))
@@ -1554,8 +1549,8 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
   (Lisp_Object specified_time, Lisp_Object zone, Lisp_Object form)
 {
   /* Compute broken-down local time LOCAL_TM from SPECIFIED_TIME and ZONE.  */
-  struct lisp_time lt = lisp_time_struct (specified_time);
-  struct timespec ts = lisp_to_timespec (lt);
+  struct lisp_time lt = lisp_time_struct (specified_time, CFORM_TICKS_HZ).lt;
+  struct timespec ts = ticks_hz_to_timespec (lt.ticks, lt.hz);
   if (! timespec_valid_p (ts))
     time_overflow ();
   time_t time_spec = ts.tv_sec;
-- 
2.34.1


[-- Attachment #7: 0006-Speed-up-decode-time-when-not-doing-subseconds.patch --]
[-- Type: text/x-patch, Size: 2206 bytes --]

From 18c38bf827d765e5acc533d64ed90a2bff38ec81 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 7 Jul 2024 21:34:23 +0200
Subject: [PATCH 06/17] Speed up decode-time when not doing subseconds

* src/timefns.c (Fdecode_time): Avoid some unnecessary conversions
in the common case where subsecond resolution is not required.
---
 src/timefns.c | 31 +++++++++++++++++++++++--------
 1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index cc148fa9752..ded31997620 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -1548,12 +1548,27 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
 usage: (decode-time &optional TIME ZONE FORM)  */)
   (Lisp_Object specified_time, Lisp_Object zone, Lisp_Object form)
 {
-  /* Compute broken-down local time LOCAL_TM from SPECIFIED_TIME and ZONE.  */
-  struct lisp_time lt = lisp_time_struct (specified_time, CFORM_TICKS_HZ).lt;
-  struct timespec ts = ticks_hz_to_timespec (lt.ticks, lt.hz);
-  if (! timespec_valid_p (ts))
-    time_overflow ();
-  time_t time_spec = ts.tv_sec;
+  /* Convert SPECIFIED_TIME to TIME_SPEC and HZ;
+     if HZ != 1 also set LT.ticks.  */
+  time_t time_spec;
+  Lisp_Object hz;
+  struct lisp_time lt;
+  if (EQ (form, Qt))
+    {
+      lt = lisp_time_struct (specified_time, CFORM_TICKS_HZ).lt;
+      struct timespec ts = ticks_hz_to_timespec (lt.ticks, lt.hz);
+      if (! timespec_valid_p (ts))
+	time_overflow ();
+      time_spec = ts.tv_sec;
+      hz = lt.hz;
+    }
+  else
+    {
+      time_spec = lisp_seconds_argument (specified_time);
+      hz = make_fixnum (1);
+    }
+
+  /* Compute broken-down local time LOCAL_TM from TIME_SPEC and ZONE.  */
   struct tm local_tm, gmt_tm;
   timezone_t tz = tzlookup (zone, false);
   struct tm *tm = emacs_localtime_rz (tz, &time_spec, &local_tm);
@@ -1581,8 +1596,8 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
     }
 
   /* Compute SEC from LOCAL_TM.tm_sec and HZ.  */
-  Lisp_Object hz = lt.hz, sec;
-  if (BASE_EQ (hz, make_fixnum (1)) || !EQ (form, Qt))
+  Lisp_Object sec;
+  if (BASE_EQ (hz, make_fixnum (1)))
     sec = make_fixnum (local_tm.tm_sec);
   else
     {
-- 
2.34.1


[-- Attachment #8: 0007-Rename-timefns-internals.patch --]
[-- Type: text/x-patch, Size: 8806 bytes --]

From 34e96f51ff3ed47845a5d0976f067c6f8e0c37a0 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Mon, 8 Jul 2024 09:26:14 +0200
Subject: [PATCH 07/17] Rename timefns internals
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The old names didn’t fit in the conventions used for newer names.
* src/timefns.c (struct ticks_hz): Rename from struct lisp_time.
(union c_time): Rename lt to th.
(ticks_hz_hz_ticks): Rename from lisp_time_hz_ticks.
(ticks_hz_seconds): Rename from lisp_time_seconds.
All uses changed.
---
 src/timefns.c | 62 +++++++++++++++++++++++++--------------------------
 1 file changed, 31 insertions(+), 31 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index ded31997620..0c5f3bf3ff1 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -508,7 +508,7 @@ mpz_time (mpz_t const z, time_t *t)
 
 /* Components of a Lisp timestamp (TICKS . HZ).  Using this C struct can
    avoid the consing overhead of creating (TICKS . HZ).  */
-struct lisp_time
+struct ticks_hz
 {
   /* Clock count as a Lisp integer.  */
   Lisp_Object ticks;
@@ -589,7 +589,7 @@ ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
    specify the desired C timestamp form.  */
 enum cform
   {
-    CFORM_TICKS_HZ, /* struct lisp_time */
+    CFORM_TICKS_HZ, /* struct ticks_hz */
     CFORM_TIMESPEC, /* struct timespec */
     CFORM_SECS_ONLY, /* struct timespec but tv_nsec == 0 if timespec valid */
     CFORM_DOUBLE /* double */
@@ -598,7 +598,7 @@ ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
 /* A C timestamp in one of the forms specified by enum cform.  */
 union c_time
 {
-  struct lisp_time lt;
+  struct ticks_hz th;
   struct timespec ts;
   double d;
 };
@@ -614,7 +614,7 @@ decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz, enum cform cform)
       return (union c_time) { .d = frac_to_double (ticks, hz) };
 
     case CFORM_TICKS_HZ:
-      return (union c_time) { .lt = { .ticks = ticks, .hz = hz } };
+      return (union c_time) { .th = { .ticks = ticks, .hz = hz } };
 
     default:
       return (union c_time) { .ts = ticks_hz_to_timespec (ticks, hz) };
@@ -751,7 +751,7 @@ timespec_ticks (struct timespec t)
 /* Convert T to a Lisp integer counting HZ ticks, taking the floor.
    Assume T is valid, but check HZ.  */
 static Lisp_Object
-lisp_time_hz_ticks (struct lisp_time t, Lisp_Object hz)
+ticks_hz_hz_ticks (struct ticks_hz t, Lisp_Object hz)
 {
   /* The idea is to return the floor of ((T.ticks * HZ) / T.hz).  */
 
@@ -785,19 +785,19 @@ lisp_time_hz_ticks (struct lisp_time t, Lisp_Object hz)
 
 /* Convert T to a Lisp integer counting seconds, taking the floor.  */
 static Lisp_Object
-lisp_time_seconds (struct lisp_time t)
+ticks_hz_seconds (struct ticks_hz t)
 {
   /* The idea is to return the floor of T.ticks / T.hz.  */
 
   if (!FASTER_TIMEFNS)
-    return lisp_time_hz_ticks (t, make_fixnum (1));
+    return ticks_hz_hz_ticks (t, make_fixnum (1));
 
   /* For speed, use EMACS_INT arithmetic if it will do.  */
   if (FIXNUMP (t.ticks) && FIXNUMP (t.hz))
     return make_fixnum (XFIXNUM (t.ticks) / XFIXNUM (t.hz)
 			- (XFIXNUM (t.ticks) % XFIXNUM (t.hz) < 0));
 
-  /* For speed, inline what lisp_time_hz_ticks would do.  */
+  /* For speed, inline what ticks_hz_hz_ticks would do.  */
   mpz_fdiv_q (mpz[0],
 	      *bignum_integer (&mpz[0], t.ticks),
 	      *bignum_integer (&mpz[1], t.hz));
@@ -1130,7 +1130,7 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
     fta = decode_lisp_time (a, CFORM_TICKS_HZ),
     ftb = decode_lisp_time (b, CFORM_TICKS_HZ);
   enum timeform aform = fta.form, bform = ftb.form;
-  struct lisp_time ta = fta.time.lt, tb = ftb.time.lt;
+  struct ticks_hz ta = fta.time.th, tb = ftb.time.th;
   Lisp_Object ticks, hz;
 
   if (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz))
@@ -1271,8 +1271,8 @@ time_cmp (Lisp_Object a, Lisp_Object b)
 
   /* Compare (ATICKS . AZ) to (BTICKS . BHZ) by comparing
      ATICKS * BHZ to BTICKS * AHZ.  */
-  struct lisp_time ta = lisp_time_struct (a, CFORM_TICKS_HZ).lt;
-  struct lisp_time tb = lisp_time_struct (b, CFORM_TICKS_HZ).lt;
+  struct ticks_hz ta = lisp_time_struct (a, CFORM_TICKS_HZ).th;
+  struct ticks_hz tb = lisp_time_struct (b, CFORM_TICKS_HZ).th;
   mpz_t const *za = bignum_integer (&mpz[0], ta.ticks);
   mpz_t const *zb = bignum_integer (&mpz[1], tb.ticks);
   if (! (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz)))
@@ -1549,18 +1549,18 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
   (Lisp_Object specified_time, Lisp_Object zone, Lisp_Object form)
 {
   /* Convert SPECIFIED_TIME to TIME_SPEC and HZ;
-     if HZ != 1 also set LT.ticks.  */
+     if HZ != 1 also set TH.ticks.  */
   time_t time_spec;
   Lisp_Object hz;
-  struct lisp_time lt;
+  struct ticks_hz th;
   if (EQ (form, Qt))
     {
-      lt = lisp_time_struct (specified_time, CFORM_TICKS_HZ).lt;
-      struct timespec ts = ticks_hz_to_timespec (lt.ticks, lt.hz);
+      th = lisp_time_struct (specified_time, CFORM_TICKS_HZ).th;
+      struct timespec ts = ticks_hz_to_timespec (th.ticks, th.hz);
       if (! timespec_valid_p (ts))
 	time_overflow ();
       time_spec = ts.tv_sec;
-      hz = lt.hz;
+      hz = th.hz;
     }
   else
     {
@@ -1601,20 +1601,20 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
     sec = make_fixnum (local_tm.tm_sec);
   else
     {
-      /* Let TICKS = HZ * LOCAL_TM.tm_sec + mod (LT.ticks, HZ)
+      /* Let TICKS = HZ * LOCAL_TM.tm_sec + mod (TH.ticks, HZ)
 	 and SEC = (TICKS . HZ).  */
       Lisp_Object ticks;
       intmax_t n;
-      if (FASTER_TIMEFNS && FIXNUMP (lt.ticks) && FIXNUMP (hz)
+      if (FASTER_TIMEFNS && FIXNUMP (th.ticks) && FIXNUMP (hz)
 	  && !ckd_mul (&n, XFIXNUM (hz), local_tm.tm_sec)
-	  && !ckd_add (&n, n, (XFIXNUM (lt.ticks) % XFIXNUM (hz)
-			       + (XFIXNUM (lt.ticks) % XFIXNUM (hz) < 0
+	  && !ckd_add (&n, n, (XFIXNUM (th.ticks) % XFIXNUM (hz)
+			       + (XFIXNUM (th.ticks) % XFIXNUM (hz) < 0
 				  ? XFIXNUM (hz) : 0))))
 	ticks = make_int (n);
       else
 	{
 	  mpz_fdiv_r (mpz[0],
-		      *bignum_integer (&mpz[0], lt.ticks),
+		      *bignum_integer (&mpz[0], th.ticks),
 		      *bignum_integer (&mpz[1], hz));
 	  mpz_addmul_ui (mpz[0], *bignum_integer (&mpz[1], hz),
 			 local_tm.tm_sec);
@@ -1741,18 +1741,18 @@ DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
       yeararg = args[5];
     }
 
-  /* Let SEC = floor (LT.ticks / HZ), with SUBSECTICKS the remainder.  */
-  struct lisp_time lt = decode_lisp_time (secarg, CFORM_TICKS_HZ).time.lt;
-  Lisp_Object hz = lt.hz, sec, subsecticks;
+  /* Let SEC = floor (TH.ticks / HZ), with SUBSECTICKS the remainder.  */
+  struct ticks_hz th = decode_lisp_time (secarg, CFORM_TICKS_HZ).time.th;
+  Lisp_Object hz = th.hz, sec, subsecticks;
   if (FASTER_TIMEFNS && BASE_EQ (hz, make_fixnum (1)))
     {
-      sec = lt.ticks;
+      sec = th.ticks;
       subsecticks = make_fixnum (0);
     }
   else
     {
       mpz_fdiv_qr (mpz[0], mpz[1],
-		   *bignum_integer (&mpz[0], lt.ticks),
+		   *bignum_integer (&mpz[0], th.ticks),
 		   *bignum_integer (&mpz[1], hz));
       sec = make_integer_mpz ();
       mpz_swap (mpz[0], mpz[1]);
@@ -1780,8 +1780,8 @@ DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
 	    : INT_TO_INTEGER (value));
   else
     {
-      struct lisp_time val1 = { INT_TO_INTEGER (value), make_fixnum (1) };
-      Lisp_Object secticks = lisp_time_hz_ticks (val1, hz);
+      struct ticks_hz val1 = { INT_TO_INTEGER (value), make_fixnum (1) };
+      Lisp_Object secticks = ticks_hz_hz_ticks (val1, hz);
       Lisp_Object ticks = lispint_arith (secticks, subsecticks, false);
       return Fcons (ticks, hz);
     }
@@ -1812,19 +1812,19 @@ DEFUN ("time-convert", Ftime_convert, Stime_convert, 1, 2, 0,
   /* FIXME: Any reason why we don't offer a `float` output format option as
      well, since we accept it as input?  */
   struct form_time form_time = decode_lisp_time (time, CFORM_TICKS_HZ);
-  struct lisp_time t = form_time.time.lt;
+  struct ticks_hz t = form_time.time.th;
   form = (!NILP (form) ? maybe_remove_pos_from_symbol (form)
 	  : current_time_list ? Qlist : Qt);
   if (BASE_EQ (form, Qlist))
     return ticks_hz_list4 (t.ticks, t.hz);
   if (BASE_EQ (form, Qinteger))
-    return FASTER_TIMEFNS && INTEGERP (time) ? time : lisp_time_seconds (t);
+    return FASTER_TIMEFNS && INTEGERP (time) ? time : ticks_hz_seconds (t);
   if (BASE_EQ (form, Qt))
     form = t.hz;
   if (FASTER_TIMEFNS
       && form_time.form == TIMEFORM_TICKS_HZ && BASE_EQ (form, XCDR (time)))
     return time;
-  return Fcons (lisp_time_hz_ticks (t, form), form);
+  return Fcons (ticks_hz_hz_ticks (t, form), form);
 }
 
 DEFUN ("current-time", Fcurrent_time, Scurrent_time, 0, 0, 0,
-- 
2.34.1


[-- Attachment #9: 0008-Reduce-size-of-integer-product-in-timefns.patch --]
[-- Type: text/x-patch, Size: 2307 bytes --]

From 3c0cdb365ae2bb9a21948c081de69356263112eb Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 10 Jul 2024 02:37:55 +0200
Subject: [PATCH 08/17] Reduce size of integer product in timefns
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* src/timefns.c (emacs_gcd): New static function.
(ticks_hz_hz_ticks): Use it to reduce the size of the integer
product in the common case when converting from ns to ps.  For
that, we need to multiply t.ticks only by 10³, not multiply by
10¹² and then divide by 10⁹.  This avoids the need to use bignums
in a significant number of cases.
---
 src/timefns.c | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 0c5f3bf3ff1..0a34bda28c7 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -748,6 +748,15 @@ timespec_ticks (struct timespec t)
   return make_integer_mpz ();
 }
 
+/* Return greatest common divisor of positive A and B.  */
+static EMACS_INT
+emacs_gcd (EMACS_INT a, EMACS_INT b)
+{
+  for (EMACS_INT r; (r = a % b) != 0; a = b, b = r)
+    continue;
+  return b;
+}
+
 /* Convert T to a Lisp integer counting HZ ticks, taking the floor.
    Assume T is valid, but check HZ.  */
 static Lisp_Object
@@ -766,11 +775,22 @@ ticks_hz_hz_ticks (struct ticks_hz t, Lisp_Object hz)
 	invalid_hz (hz);
 
       /* For speed, use intmax_t arithmetic if it will do.  */
-      intmax_t ticks;
-      if (FASTER_TIMEFNS && FIXNUMP (t.ticks) && FIXNUMP (t.hz)
-	  && !ckd_mul (&ticks, XFIXNUM (t.ticks), XFIXNUM (hz)))
-	return make_int (ticks / XFIXNUM (t.hz)
-			 - (ticks % XFIXNUM (t.hz) < 0));
+      if (FASTER_TIMEFNS && FIXNUMP (t.ticks) && FIXNUMP (t.hz))
+	{
+	  /* Reduce T.hz and HZ by their GCD, to avoid some intmax_t
+	     overflows that would occur in T.ticks * HZ.  */
+	  EMACS_INT ithz = XFIXNUM (t.hz), ihz = XFIXNUM (hz);
+	  EMACS_INT d = emacs_gcd (ithz, ihz);
+	  ithz /= d;
+	  ihz /= d;
+
+	  intmax_t ticks;
+	  if (!ckd_mul (&ticks, XFIXNUM (t.ticks), ihz))
+	    return make_int (ticks / ithz - (ticks % ithz < 0));
+
+	  t.hz = make_fixnum (ithz);
+	  hz = make_fixnum (ihz);
+	}
     }
   else if (! (BIGNUMP (hz) && 0 < mpz_sgn (*xbignum_val (hz))))
     invalid_hz (hz);
-- 
2.34.1


[-- Attachment #10: 0009-In-timefns-prefer-ui-mul-and-div.patch --]
[-- Type: text/x-patch, Size: 1473 bytes --]

From 4823ebd9ec3a5160f95aade0ec87d0218d1f19f3 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 10 Jul 2024 10:23:31 +0200
Subject: [PATCH 09/17] In timefns, prefer ui mul and div

* src/timefns.c (ticks_hz_hz_ticks): If the multiplier
is a fixnum that fits in unsigned long, use mpz_mul_ui
instead of the more-expensive mpz_mul.  Similarly, if the
divisor is a fixnum that fits in unsigned long, use
mpz_fdiv_q_ui instead of mpz_fdiv_q.
---
 src/timefns.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 0a34bda28c7..0df7d1f4363 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -796,10 +796,15 @@ ticks_hz_hz_ticks (struct ticks_hz t, Lisp_Object hz)
     invalid_hz (hz);
 
   /* Fall back on bignum arithmetic.  */
-  mpz_mul (mpz[0],
-	   *bignum_integer (&mpz[0], t.ticks),
-	   *bignum_integer (&mpz[1], hz));
-  mpz_fdiv_q (mpz[0], mpz[0], *bignum_integer (&mpz[1], t.hz));
+  mpz_t const *zticks = bignum_integer (&mpz[0], t.ticks);
+  if (FASTER_TIMEFNS && FIXNUMP (hz) && XFIXNUM (hz) <= ULONG_MAX)
+    mpz_mul_ui (mpz[0], *zticks, XFIXNUM (hz));
+  else
+    mpz_mul (mpz[0], *zticks, *bignum_integer (&mpz[1], hz));
+  if (FASTER_TIMEFNS && FIXNUMP (t.hz) && XFIXNUM (t.hz) <= ULONG_MAX)
+    mpz_fdiv_q_ui (mpz[0], mpz[0], XFIXNUM (t.hz));
+  else
+    mpz_fdiv_q (mpz[0], mpz[0], *bignum_integer (&mpz[1], t.hz));
   return make_integer_mpz ();
 }
 
-- 
2.34.1


[-- Attachment #11: 0010-In-timefns-do-gcd-reduction-more-often.patch --]
[-- Type: text/x-patch, Size: 1594 bytes --]

From 75dcc45c1589a5b42b8c24af448c1e7013bdf687 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 10 Jul 2024 10:36:35 +0200
Subject: [PATCH 10/17] In timefns, do gcd reduction more often
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* src/timefns.c (ticks_hz_hz_ticks): Reduce by gcd
even if t.ticks is not a fixnum, since that’s easy.
---
 src/timefns.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 0df7d1f4363..ba1ba10a809 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -774,8 +774,8 @@ ticks_hz_hz_ticks (struct ticks_hz t, Lisp_Object hz)
       if (XFIXNUM (hz) <= 0)
 	invalid_hz (hz);
 
-      /* For speed, use intmax_t arithmetic if it will do.  */
-      if (FASTER_TIMEFNS && FIXNUMP (t.ticks) && FIXNUMP (t.hz))
+      /* Prefer non-bignum arithmetic to speed up common cases.  */
+      if (FASTER_TIMEFNS && FIXNUMP (t.hz))
 	{
 	  /* Reduce T.hz and HZ by their GCD, to avoid some intmax_t
 	     overflows that would occur in T.ticks * HZ.  */
@@ -784,9 +784,12 @@ ticks_hz_hz_ticks (struct ticks_hz t, Lisp_Object hz)
 	  ithz /= d;
 	  ihz /= d;
 
-	  intmax_t ticks;
-	  if (!ckd_mul (&ticks, XFIXNUM (t.ticks), ihz))
-	    return make_int (ticks / ithz - (ticks % ithz < 0));
+	  if (FIXNUMP (t.ticks))
+	    {
+	      intmax_t ticks;
+	      if (!ckd_mul (&ticks, XFIXNUM (t.ticks), ihz))
+		return make_int (ticks / ithz - (ticks % ithz < 0));
+	    }
 
 	  t.hz = make_fixnum (ithz);
 	  hz = make_fixnum (ihz);
-- 
2.34.1


[-- Attachment #12: 0011-In-timefns-call-natnump-only-for-non-fixnums.patch --]
[-- Type: text/x-patch, Size: 892 bytes --]

From 46978e54efa91fa4489a22c6b59de0d2b66f67e1 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 10 Jul 2024 11:04:18 +0200
Subject: [PATCH 11/17] In timefns, call natnump only for non-fixnums

* src/timefns.c (decode_time_components): Call Fnatnump only
for non-fixnums, as we need to special-case 0 anyway.
---
 src/timefns.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/timefns.c b/src/timefns.c
index ba1ba10a809..45a0930f8a8 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -895,7 +895,7 @@ decode_time_components (enum timeform form,
 
     case TIMEFORM_TICKS_HZ:
       if (! (INTEGERP (high)
-	     && !NILP (Fnatnump (low)) && !BASE_EQ (low, make_fixnum (0))))
+	     && (FIXNUMP (low) ? 0 < XFIXNUM (low) : !NILP (Fnatnump (low)))))
 	return (struct err_time) { .err = EINVAL };
       ticks = high;
       hz = low;
-- 
2.34.1


[-- Attachment #13: 0012-New-FASTER_BIGNUM-macro-to-test-slow-path-code.patch --]
[-- Type: text/x-patch, Size: 1690 bytes --]

From d566616f528a55795abeaf9ea48833a6f5488759 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 11 Jul 2024 12:27:36 +0200
Subject: [PATCH 12/17] New FASTER_BIGNUM macro to test slow-path code

* src/bignum.h (FASTER_BIGNUM): New macro.
(mpz_set_intmax, mpz_set_uintmax): Optimize only if FASTER_BIGNUM.
Also, use ckd_add to test for overflow instead of doing it by hand.
---
 src/bignum.h | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/bignum.h b/src/bignum.h
index 2749f8370d0..54ba0cde410 100644
--- a/src/bignum.h
+++ b/src/bignum.h
@@ -25,6 +25,12 @@ #define BIGNUM_H
 #include <gmp.h>
 #include "lisp.h"
 
+/* Compile with -DFASTER_BIGNUM=0 to disable common optimizations and
+   allow easier testing of some slow-path code.  */
+#ifndef FASTER_BIGNUM
+# define FASTER_BIGNUM 1
+#endif
+
 /* Number of data bits in a limb.  */
 #ifndef GMP_NUMB_BITS
 enum { GMP_NUMB_BITS = TYPE_WIDTH (mp_limb_t) };
@@ -68,16 +74,18 @@ mpz_set_intmax (mpz_t result, intmax_t v)
   /* mpz_set_si works in terms of long, but Emacs may use a wider
      integer type, and so sometimes will have to construct the mpz_t
      by hand.  */
-  if (LONG_MIN <= v && v <= LONG_MAX)
-    mpz_set_si (result, v);
+  long int i;
+  if (FASTER_BIGNUM && !ckd_add (&i, v, 0))
+    mpz_set_si (result, i);
   else
     mpz_set_intmax_slow (result, v);
 }
 INLINE void ARG_NONNULL ((1))
 mpz_set_uintmax (mpz_t result, uintmax_t v)
 {
-  if (v <= ULONG_MAX)
-    mpz_set_ui (result, v);
+  unsigned long int i;
+  if (FASTER_BIGNUM && !ckd_add (&i, v, 0))
+    mpz_set_ui (result, i);
   else
     mpz_set_uintmax_slow (result, v);
 }
-- 
2.34.1


[-- Attachment #14: 0013-Optimize-smallish-mpz-to-native-int-conversion.patch --]
[-- Type: text/x-patch, Size: 2052 bytes --]

From 6923145c10b50d86d133c7f7a5bb3c407ae246e5 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 11 Jul 2024 12:42:57 +0200
Subject: [PATCH 13/17] Optimize smallish mpz to native int conversion

* src/bignum.c (make_integer_mpz, mpz_to_intmax):
If FASTER_BIGNUM, optimize the common case where the value fits in
long int.  In this case we can use mpz_fits_slong_p and mpz_get_si
instead of looping with mpz_getlimbn.
(mpz_to_uintmax): Likewise for unsigned long int and mpz_get_ui.
---
 src/bignum.c | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/bignum.c b/src/bignum.c
index 1fe195d78ea..7589691dd0c 100644
--- a/src/bignum.c
+++ b/src/bignum.c
@@ -145,9 +145,19 @@ make_neg_biguint (uintmax_t n)
 Lisp_Object
 make_integer_mpz (void)
 {
+  if (FASTER_BIGNUM && mpz_fits_slong_p (mpz[0]))
+    {
+      long int v = mpz_get_si (mpz[0]);
+      if (!FIXNUM_OVERFLOW_P (v))
+	return make_fixnum (v);
+    }
+
   size_t bits = mpz_sizeinbase (mpz[0], 2);
 
-  if (bits <= FIXNUM_BITS)
+  if (! (FASTER_BIGNUM
+	 && FIXNUM_OVERFLOW_P (LONG_MIN)
+	 && FIXNUM_OVERFLOW_P (LONG_MAX))
+      && bits <= FIXNUM_BITS)
     {
       EMACS_INT v = 0;
       int i = 0, shift = 0;
@@ -216,6 +226,17 @@ mpz_set_uintmax_slow (mpz_t result, uintmax_t v)
 bool
 mpz_to_intmax (mpz_t const z, intmax_t *pi)
 {
+  if (FASTER_BIGNUM)
+    {
+      if (mpz_fits_slong_p (z))
+	{
+	  *pi = mpz_get_si (z);
+	  return true;
+	}
+      if (LONG_MIN <= INTMAX_MIN && INTMAX_MAX <= LONG_MAX)
+	return false;
+    }
+
   ptrdiff_t bits = mpz_sizeinbase (z, 2);
   bool negative = mpz_sgn (z) < 0;
 
@@ -246,6 +267,17 @@ mpz_to_intmax (mpz_t const z, intmax_t *pi)
 bool
 mpz_to_uintmax (mpz_t const z, uintmax_t *pi)
 {
+  if (FASTER_BIGNUM)
+    {
+      if (mpz_fits_ulong_p (z))
+	{
+	  *pi = mpz_get_ui (z);
+	  return true;
+	}
+      if (UINTMAX_MAX <= ULONG_MAX)
+	return false;
+    }
+
   if (mpz_sgn (z) < 0)
     return false;
   ptrdiff_t bits = mpz_sizeinbase (z, 2);
-- 
2.34.1


[-- Attachment #15: 0014-In-timefns.c-avoid-by-hand-overflow-checking.patch --]
[-- Type: text/x-patch, Size: 5121 bytes --]

From 39d9df2181209d700f41b2064a02e5cea42c1564 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 11 Jul 2024 12:57:01 +0200
Subject: [PATCH 14/17] In timefns.c avoid by-hand overflow checking

Prefer functions like ckd_add to do overflow checking, instead of
doing it by hand, to simplify and I hope to make things a bit less
error prone.
* src/timefns.c (TIME_T_MIN, TIME_T_MAX): Remove.  All by-hand
overflow checking replaced with calls to ckd_add or ckd_mul.
(s_ns_to_timespec): New static function, that uses ckd_add
instead of by-hand overflow checking.
(ticks_hz_to_timespec): Use it.
(check_tm_member): Use mpz_fits_sint_p and mpz_get_si rather
than mpz_to_intmax and by-hand overflow checking.
---
 src/timefns.c | 76 ++++++++++++++++++---------------------------------
 1 file changed, 27 insertions(+), 49 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 45a0930f8a8..6949c83dbcb 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -60,13 +60,6 @@ Copyright (C) 1985-1987, 1989, 1993-2024 Free Software Foundation, Inc.
 # define HAVE_TM_GMTOFF false
 #endif
 
-#ifndef TIME_T_MIN
-# define TIME_T_MIN TYPE_MINIMUM (time_t)
-#endif
-#ifndef TIME_T_MAX
-# define TIME_T_MAX TYPE_MAXIMUM (time_t)
-#endif
-
 /* Compile with -DFASTER_TIMEFNS=0 to disable common optimizations and
    allow easier testing of some slow-path code.  */
 #ifndef FASTER_TIMEFNS
@@ -127,10 +120,14 @@ make_timeval (struct timespec t)
     {
       if (tv.tv_usec < 999999)
 	tv.tv_usec++;
-      else if (tv.tv_sec < TIME_T_MAX)
+      else
 	{
-	  tv.tv_sec++;
-	  tv.tv_usec = 0;
+	  time_t s1;
+	  if (!ckd_add (&s1, tv.tv_sec, 1))
+	    {
+	      tv.tv_sec = s1;
+	      tv.tv_usec = 0;
+	    }
 	}
     }
 
@@ -492,18 +489,23 @@ mpz_time (mpz_t const z, time_t *t)
   if (TYPE_SIGNED (time_t))
     {
       intmax_t i;
-      if (! (mpz_to_intmax (z, &i) && TIME_T_MIN <= i && i <= TIME_T_MAX))
-	return false;
-      *t = i;
+      return mpz_to_intmax (z, &i) && !ckd_add (t, i, 0);
     }
   else
     {
       uintmax_t i;
-      if (! (mpz_to_uintmax (z, &i) && i <= TIME_T_MAX))
-	return false;
-      *t = i;
+      return mpz_to_uintmax (z, &i) && !ckd_add (t, i, 0);
     }
-  return true;
+}
+
+/* Return a valid timespec (S, N) if S is in time_t range,
+   an invalid timespec otherwise.  */
+static struct timespec
+s_ns_to_timespec (intmax_t s, long int ns)
+{
+  time_t sec;
+  long int nsec = ckd_add (&sec, s, 0) ? -1 : ns;
+  return make_timespec (sec, nsec);
 }
 
 /* Components of a Lisp timestamp (TICKS . HZ).  Using this C struct can
@@ -522,7 +524,6 @@ mpz_time (mpz_t const z, time_t *t)
 static struct timespec
 ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
 {
-  struct timespec result = invalid_timespec ();
   int ns;
   mpz_t *q = &mpz[0];
   mpz_t const *qt = q;
@@ -539,33 +540,16 @@ ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
 	  ns = XFIXNUM (ticks) % TIMESPEC_HZ;
 	  if (ns < 0)
 	    s--, ns += TIMESPEC_HZ;
-	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
-	      && s <= TIME_T_MAX)
-	    {
-	      result.tv_sec = s;
-	      result.tv_nsec = ns;
-	    }
-	  return result;
+	  return s_ns_to_timespec (s, ns);
 	}
-      else
-	ns = mpz_fdiv_q_ui (*q, *xbignum_val (ticks), TIMESPEC_HZ);
+      ns = mpz_fdiv_q_ui (*q, *xbignum_val (ticks), TIMESPEC_HZ);
     }
   else if (FASTER_TIMEFNS && BASE_EQ (hz, make_fixnum (1)))
     {
       ns = 0;
       if (FIXNUMP (ticks))
-	{
-	  EMACS_INT s = XFIXNUM (ticks);
-	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
-	      && s <= TIME_T_MAX)
-	    {
-	      result.tv_sec = s;
-	      result.tv_nsec = ns;
-	    }
-	  return result;
-	}
-      else
-	qt = xbignum_val (ticks);
+	return s_ns_to_timespec (XFIXNUM (ticks), ns);
+      qt = xbignum_val (ticks);
     }
   else
     {
@@ -577,12 +561,7 @@ ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
   /* Check that Q fits in time_t, not merely in RESULT.tv_sec.  With some MinGW
      versions, tv_sec is a 64-bit type, whereas time_t is a 32-bit type.  */
   time_t sec;
-  if (mpz_time (*qt, &sec))
-    {
-      result.tv_sec = sec;
-      result.tv_nsec = ns;
-    }
-  return result;
+  return mpz_time (*qt, &sec) ? make_timespec (sec, ns) : invalid_timespec ();
 }
 
 /* C timestamp forms.  This enum is passed to conversion functions to
@@ -700,7 +679,7 @@ ticks_hz_list4 (Lisp_Object ticks, Lisp_Object hz)
   int us = mpz_get_ui (mpz[1]);
 #endif
 
-  /* mpz[0] = floor (mpz[0] / 1 << LO_TIME_BITS), with lo = remainder.  */
+  /* mpz[0] = floor (mpz[0] / (1 << LO_TIME_BITS)), with LO = remainder.  */
   unsigned long ulo = mpz_get_ui (mpz[0]);
   if (mpz_sgn (mpz[0]) < 0)
     ulo = -ulo;
@@ -1686,10 +1665,9 @@ check_tm_member (Lisp_Object obj, int offset)
     {
       CHECK_INTEGER (obj);
       mpz_sub_ui (mpz[0], *bignum_integer (&mpz[0], obj), offset);
-      intmax_t i;
-      if (! (mpz_to_intmax (mpz[0], &i) && INT_MIN <= i && i <= INT_MAX))
+      if (!mpz_fits_sint_p (mpz[0]))
 	time_overflow ();
-      return i;
+      return mpz_get_si (mpz[0]);
     }
 }
 
-- 
2.34.1


[-- Attachment #16: 0015-Decode-current-time-directly-to-timespec.patch --]
[-- Type: text/x-patch, Size: 1201 bytes --]

From 5a982c3e7b1e8931530e110bb5388047829fa4fe Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 11 Jul 2024 15:03:50 +0200
Subject: [PATCH 15/17] Decode current time directly to timespec

* src/timefns.c (decode_time_components): If FASTER_TIMEFNS, when
returning the current time and the desired form is struct timespec
or time_t, return it directly rather than converting it to struct
ticks_hz and then to struct timespec.  This can avoid some mpz
calculations and/or bignums.
---
 src/timefns.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 6949c83dbcb..ad39d1307cd 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -884,8 +884,14 @@ decode_time_components (enum timeform form,
       eassume (false);
 
     case TIMEFORM_NIL:
-      ticks = timespec_ticks (current_timespec ());
-      hz = timespec_hz;
+      {
+	struct timespec now = current_timespec ();
+	if (FASTER_TIMEFNS
+	    && (cform == CFORM_TIMESPEC || cform == CFORM_SECS_ONLY))
+	  return (struct err_time) { .time = { .ts = now } };
+	ticks = timespec_ticks (now);
+	hz = timespec_hz;
+      }
       break;
 
     default:
-- 
2.34.1


[-- Attachment #17: 0016-Avoid-mpz-for-some-common-timestamp-cases.patch --]
[-- Type: text/x-patch, Size: 5038 bytes --]

From 7291b20806addd95770a4b58da4049d7a14ea871 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 11 Jul 2024 15:28:58 +0200
Subject: [PATCH 16/17] Avoid mpz for some common timestamp cases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Performance problem reported by Gerd Möllmann and Mattias Engdegård in:
https://lists.gnu.org/r/emacs-devel/2024-06/msg00530.html
https://lists.gnu.org/r/emacs-devel/2024-06/msg00539.html
* src/timefns.c (CFORM_SECS_ONLY): The exact tv_nsec value is now
ignored if nonnegative (i.e., the only thing that matters is that
it’s nonnegative).
(decode_time_components): Use intmax_t instead of mpz arithmetic
if the tick count fits.  Add another ‘default: eassume (false);’
so that the revised code pacifies --enable-gcc-warnings with GCC
11.4.0 on x86-64.
---
 src/timefns.c | 84 ++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 73 insertions(+), 11 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index ad39d1307cd..8c30016360d 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -570,7 +570,8 @@ ticks_hz_to_timespec (Lisp_Object ticks, Lisp_Object hz)
   {
     CFORM_TICKS_HZ, /* struct ticks_hz */
     CFORM_TIMESPEC, /* struct timespec */
-    CFORM_SECS_ONLY, /* struct timespec but tv_nsec == 0 if timespec valid */
+    CFORM_SECS_ONLY, /* struct timespec but tv_nsec irrelevant
+			if timespec valid */
     CFORM_DOUBLE /* double */
   };
 
@@ -894,11 +895,22 @@ decode_time_components (enum timeform form,
       }
       break;
 
-    default:
-      if (! (INTEGERP (high) && INTEGERP (low)
-	     && FIXNUMP (usec) && FIXNUMP (psec)))
-	return (struct err_time) { .err = EINVAL };
+    case TIMEFORM_HI_LO:
+      hz = make_fixnum (1);
+      goto check_high_low;
+
+    case TIMEFORM_HI_LO_US:
+      hz = make_fixnum (1000000);
+      goto check_high_low_usec;
 
+    case TIMEFORM_HI_LO_US_PS:
+      hz = trillion;
+      if (!FIXNUMP (psec))
+	return (struct err_time) { .err = EINVAL };
+    check_high_low_usec:
+      if (!FIXNUMP (usec))
+	return (struct err_time) { .err = EINVAL };
+    check_high_low:
       {
 	EMACS_INT us = XFIXNUM (usec);
 	EMACS_INT ps = XFIXNUM (psec);
@@ -906,25 +918,73 @@ decode_time_components (enum timeform form,
 	/* Normalize out-of-range lower-order components by carrying
 	   each overflow into the next higher-order component.  */
 	us += ps / 1000000 - (ps % 1000000 < 0);
+	EMACS_INT s_from_us_ps = us / 1000000 - (us % 1000000 < 0);
+	ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
+	us = us % 1000000 + 1000000 * (us % 1000000 < 0);
+
+	if (FASTER_TIMEFNS && FIXNUMP (high) && FIXNUMP (low))
+	  {
+	    /* Use intmax_t arithmetic if the tick count fits.  */
+	    intmax_t iticks;
+	    bool v = false;
+	    v |= ckd_mul (&iticks, XFIXNUM (high), 1 << LO_TIME_BITS);
+	    v |= ckd_add (&iticks, iticks, XFIXNUM (low) + s_from_us_ps);
+	    if (!v)
+	      {
+		if (cform == CFORM_TIMESPEC || cform == CFORM_SECS_ONLY)
+		  return (struct err_time) {
+		    .time = {
+		      .ts = s_ns_to_timespec (iticks, us * 1000 + ps / 1000)
+		    }
+		  };
+
+		switch (form)
+		  {
+		  case TIMEFORM_HI_LO:
+		    break;
+
+		  case TIMEFORM_HI_LO_US:
+		    v |= ckd_mul (&iticks, iticks, 1000000);
+		    v |= ckd_add (&iticks, iticks, us);
+		    break;
+
+		  case TIMEFORM_HI_LO_US_PS:
+		    {
+		      int_fast64_t million = 1000000;
+		      v |= ckd_mul (&iticks, iticks, TRILLION);
+		      v |= ckd_add (&iticks, iticks, us * million + ps);
+		    }
+		    break;
+
+		  default:
+		    eassume (false);
+		  }
+
+		if (!v)
+		  return (struct err_time) {
+		    .time = decode_ticks_hz (make_int (iticks), hz, cform)
+		  };
+	      }
+	  }
+
+	if (! (INTEGERP (high) && INTEGERP (low)))
+	  return (struct err_time) { .err = EINVAL };
+
 	mpz_t *s = &mpz[1];
-	mpz_set_intmax (*s, us / 1000000 - (us % 1000000 < 0));
+	mpz_set_intmax (*s, s_from_us_ps);
 	mpz_add (*s, *s, *bignum_integer (&mpz[0], low));
 	mpz_addmul_ui (*s, *bignum_integer (&mpz[0], high), 1 << LO_TIME_BITS);
-	ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
-	us = us % 1000000 + 1000000 * (us % 1000000 < 0);
 
 	switch (form)
 	  {
 	  case TIMEFORM_HI_LO:
 	    /* Floats and nil were handled above, so it was an integer.  */
 	    mpz_swap (mpz[0], *s);
-	    hz = make_fixnum (1);
 	    break;
 
 	  case TIMEFORM_HI_LO_US:
 	    mpz_set_ui (mpz[0], us);
 	    mpz_addmul_ui (mpz[0], *s, 1000000);
-	    hz = make_fixnum (1000000);
 	    break;
 
 	  case TIMEFORM_HI_LO_US_PS:
@@ -938,7 +998,6 @@ decode_time_components (enum timeform form,
 		mpz_set_intmax (mpz[0], i * 1000000 + ps);
 		mpz_addmul (mpz[0], *s, ztrillion);
 	      #endif
-	      hz = trillion;
 	    }
 	    break;
 
@@ -948,6 +1007,9 @@ decode_time_components (enum timeform form,
 	ticks = make_integer_mpz ();
       }
       break;
+
+    default:
+      eassume (false);
     }
 
   return (struct err_time) { .time = decode_ticks_hz (ticks, hz, cform) };
-- 
2.34.1


[-- Attachment #18: 0017-Rename-timefns-static-function-lisp_time_struct.patch --]
[-- Type: text/x-patch, Size: 2792 bytes --]

From 7fe0564befaeb45f8f9f2f5753f2b24e1f891d15 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 11 Jul 2024 15:40:47 +0200
Subject: [PATCH 17/17] Rename timefns static function lisp_time_struct

* src/timefns.c (lisp_time_cform): Rename from lisp_time_struct,
since it no longer returns a struct, and now accepts CFORM.
All uses changed.
---
 src/timefns.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/timefns.c b/src/timefns.c
index 8c30016360d..1e551009df8 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -1125,24 +1125,24 @@ list4_to_timespec (Lisp_Object high, Lisp_Object low,
   return err_time.err ? invalid_timespec () : err_time.time.ts;
 }
 
-/* Decode a Lisp list SPECIFIED_TIME that represents a time.
+/* Decode a Lisp time value SPECIFIED_TIME that represents a time.
    If SPECIFIED_TIME is nil, use the current time.
    Decode to CFORM form.
    Signal an error if SPECIFIED_TIME does not represent a time.  */
 static union c_time
-lisp_time_struct (Lisp_Object specified_time, enum cform cform)
+lisp_time_cform (Lisp_Object specified_time, enum cform cform)
 {
   return decode_lisp_time (specified_time, cform).time;
 }
 
-/* Decode a Lisp list SPECIFIED_TIME that represents a time.
+/* Decode a Lisp time value SPECIFIED_TIME that represents a time.
    Discard any low-order (sub-ns) resolution.
    If SPECIFIED_TIME is nil, use the current time.
    Signal an error if SPECIFIED_TIME does not represent a timespec.  */
 struct timespec
 lisp_time_argument (Lisp_Object specified_time)
 {
-  struct timespec t = lisp_time_struct (specified_time, CFORM_TIMESPEC).ts;
+  struct timespec t = lisp_time_cform (specified_time, CFORM_TIMESPEC).ts;
   if (! timespec_valid_p (t))
     time_overflow ();
   return t;
@@ -1346,8 +1346,8 @@ time_cmp (Lisp_Object a, Lisp_Object b)
 
   /* Compare (ATICKS . AZ) to (BTICKS . BHZ) by comparing
      ATICKS * BHZ to BTICKS * AHZ.  */
-  struct ticks_hz ta = lisp_time_struct (a, CFORM_TICKS_HZ).th;
-  struct ticks_hz tb = lisp_time_struct (b, CFORM_TICKS_HZ).th;
+  struct ticks_hz ta = lisp_time_cform (a, CFORM_TICKS_HZ).th;
+  struct ticks_hz tb = lisp_time_cform (b, CFORM_TICKS_HZ).th;
   mpz_t const *za = bignum_integer (&mpz[0], ta.ticks);
   mpz_t const *zb = bignum_integer (&mpz[1], tb.ticks);
   if (! (FASTER_TIMEFNS && BASE_EQ (ta.hz, tb.hz)))
@@ -1630,7 +1630,7 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0,
   struct ticks_hz th;
   if (EQ (form, Qt))
     {
-      th = lisp_time_struct (specified_time, CFORM_TICKS_HZ).th;
+      th = lisp_time_cform (specified_time, CFORM_TICKS_HZ).th;
       struct timespec ts = ticks_hz_to_timespec (th.ticks, th.hz);
       if (! timespec_valid_p (ts))
 	time_overflow ();
-- 
2.34.1


  reply	other threads:[~2024-07-11 14:10 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-06-20  5:59 Question about bignum usage Gerd Möllmann
2024-06-20  6:26 ` Eli Zaretskii
2024-06-20  6:34   ` Gerd Möllmann
2024-06-20  7:36     ` Eli Zaretskii
2024-06-20  8:35       ` Gerd Möllmann
2024-06-20 13:07         ` Gerd Möllmann
2024-06-20 13:43           ` Helmut Eller
2024-06-20 14:17             ` Gerd Möllmann
2024-06-20 18:32               ` Gerd Möllmann
2024-06-20 18:37                 ` Gerd Möllmann
2024-06-20 18:58                 ` Eli Zaretskii
2024-06-20 19:16                   ` Gerd Möllmann
2024-06-20 19:25                     ` Eli Zaretskii
2024-06-20 19:42                       ` Gerd Möllmann
2024-06-20  9:38       ` Mattias Engdegård
2024-06-20 10:02         ` Eli Zaretskii
2024-07-11 14:10           ` Paul Eggert [this message]
2024-07-13  9:43             ` Mattias Engdegård
2024-07-14  4:34               ` Paul Eggert

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1cbd7603-2185-4066-9e0f-a358daf28af1@cs.ucla.edu \
    --to=eggert@cs.ucla.edu \
    --cc=eliz@gnu.org \
    --cc=emacs-devel@gnu.org \
    --cc=gerd.moellmann@gmail.com \
    --cc=mattias.engdegard@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).