From ab4081c5127474108bc4a722bf86db695c855d13 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Mon, 28 Apr 2025 08:22:23 -0700 Subject: [PATCH 1/8] Rough proof-of-concept of "fencepost"-style $&time --- initial.es | 13 +++++- prim-sys.c | 127 +++++++++++++++++++++-------------------------------- 2 files changed, 61 insertions(+), 79 deletions(-) diff --git a/initial.es b/initial.es index a813803f..ec4f49eb 100644 --- a/initial.es +++ b/initial.es @@ -136,7 +136,18 @@ fn-unwind-protect = $&noreturn @ body cleanup { # and get time from /bin or wherever. if {~ <=$&primitives limit} {fn-limit = $&limit} -if {~ <=$&primitives time} {fn-time = $&time} +if {~ <=$&primitives time} { + fn time cmd { + $&collect + let ((str times) = <=$&time) + unwind-protect { + $cmd + } { + (str times) = <={$&time $times} + echo >[1=2] $str^\t^$^cmd + } + } +} # These builtins are mainly useful for internal functions, but # they're there to be called if you want to use them. diff --git a/prim-sys.c b/prim-sys.c index c0b4f982..685ab0ca 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -289,7 +289,6 @@ PRIM(limit) { #endif /* BSD_LIMITS */ #if BUILTIN_TIME -#if HAVE_GETRUSAGE /* This function is provided as timersub(3) on some systems, but it's simple enough * to do ourselves. */ static void timesub(struct timeval *a, struct timeval *b, struct timeval *res) { @@ -300,88 +299,60 @@ static void timesub(struct timeval *a, struct timeval *b, struct timeval *res) { res->tv_usec += 1000000; } } -#endif - -PRIM(time) { -#if HAVE_GETRUSAGE - - int pid, status; - time_t t0, t1; - struct rusage ru_prev, ru_new, ru_diff; - - Ref(List *, lp, list); - - getrusage(RUSAGE_CHILDREN, &ru_prev); - gc(); /* do a garbage collection first to ensure reproducible results */ - t0 = time(NULL); - pid = efork(TRUE, FALSE); - if (pid == 0) - esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); - status = ewait(pid, FALSE); - t1 = time(NULL); - SIGCHK(); - printstatus(0, status); - - getrusage(RUSAGE_CHILDREN, &ru_new); - timesub(&ru_new.ru_utime, &ru_prev.ru_utime, &ru_diff.ru_utime); - timesub(&ru_new.ru_stime, &ru_prev.ru_stime, &ru_diff.ru_stime); - - eprint( - "%6ldr %5ld.%ldu %5ld.%lds\t%L\n", - t1 - t0, - ru_diff.ru_utime.tv_sec, (long) (ru_diff.ru_utime.tv_usec / 100000), - ru_diff.ru_stime.tv_sec, (long) (ru_diff.ru_stime.tv_usec / 100000), - lp, " " - ); - - RefEnd(lp); - return mklist(mkstr(mkstatus(status)), NULL); -#else /* !HAVE_GETRUSAGE */ - - int pid, status; - Ref(List *, lp, list); +static void timeadd(struct timeval *a, struct timeval *b, struct timeval *res) { + res->tv_sec = a->tv_sec + b->tv_sec; + res->tv_usec = a->tv_usec + b->tv_usec; + if (res->tv_usec >= 1000000) { + res->tv_sec += 1; + res->tv_usec -= 1000000; + } +} - gc(); /* do a garbage collection first to ensure reproducible results */ - pid = efork(TRUE, FALSE); - if (pid == 0) { - clock_t t0, t1; - struct tms tms; - static clock_t ticks = 0; - - if (ticks == 0) - ticks = sysconf(_SC_CLK_TCK); - - t0 = times(&tms); - pid = efork(TRUE, FALSE); - if (pid == 0) - esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); - - status = ewaitfor(pid); - t1 = times(&tms); - SIGCHK(); - printstatus(0, status); - - tms.tms_cutime += ticks / 20; - tms.tms_cstime += ticks / 20; - - eprint( - "%6ldr %5ld.%ldu %5ld.%lds\t%L\n", - (t1 - t0 + ticks / 2) / ticks, - tms.tms_cutime / ticks, ((tms.tms_cutime * 10) / ticks) % 10, - tms.tms_cstime / ticks, ((tms.tms_cstime * 10) / ticks) % 10, - lp, " " - ); - esexit(status); +PRIM(time) { + time_t rt; + struct rusage ru; + + rt = time(NULL); + { + struct rusage ru_self, ru_chld; + getrusage(RUSAGE_SELF, &ru_self); + getrusage(RUSAGE_CHILDREN, &ru_chld); + timeadd(&ru_self.ru_utime, &ru_chld.ru_utime, &ru.ru_utime); + timeadd(&ru_self.ru_stime, &ru_chld.ru_stime, &ru.ru_stime); } - status = ewaitfor(pid); - SIGCHK(); - printstatus(0, status); - RefEnd(lp); - return mklist(mkstr(mkstatus(status)), NULL); + if (list != NULL) { + char *suffix; + long outime, ostime; + struct timeval ut, st; + if (length(list) != 3) + fail("$&time", "ya goofed"); + + /* FIXME: error checking on all this! */ + rt -= (time_t)strtol(getstr(list->term), &suffix, 10); + outime = strtol(getstr(list->next->term), &suffix, 10); + ostime = strtol(getstr(list->next->next->term), &suffix, 10); + ut.tv_sec = outime / 1000000; + ut.tv_usec = outime % 1000000; + st.tv_sec = ostime / 1000000; + st.tv_usec = ostime % 1000000; + timesub(&ru.ru_utime, &ut, &ru.ru_utime); + timesub(&ru.ru_stime, &st, &ru.ru_stime); + } -#endif /* !HAVE_GETRUSAGE */ + gcdisable(); + list = mklist(mkstr(str( + "%6ldr %5ld.%ldu %5ld.%lds", + rt, + ru.ru_utime.tv_sec, (long) (ru.ru_utime.tv_usec / 100000), + ru.ru_stime.tv_sec, (long) (ru.ru_stime.tv_usec / 100000) + )), + mklist(mkstr(str("%ld", rt)), + mklist(mkstr(str("%ld", ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec)), + mklist(mkstr(str("%ld", ru.ru_stime.tv_sec * 1000000 + ru.ru_stime.tv_usec)), NULL)))); + gcenable(); + return list; } #endif /* BUILTIN_TIME */ From 498c26d0a6b8a261858bd0f3fcc2170538b0774e Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Mon, 28 Apr 2025 22:52:55 -0700 Subject: [PATCH 2/8] Try out better realtime precision --- prim-sys.c | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/prim-sys.c b/prim-sys.c index 685ab0ca..899a5adc 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -291,7 +291,7 @@ PRIM(limit) { #if BUILTIN_TIME /* This function is provided as timersub(3) on some systems, but it's simple enough * to do ourselves. */ -static void timesub(struct timeval *a, struct timeval *b, struct timeval *res) { +static void timevsub(struct timeval *a, struct timeval *b, struct timeval *res) { res->tv_sec = a->tv_sec - b->tv_sec; res->tv_usec = a->tv_usec - b->tv_usec; if (res->tv_usec < 0) { @@ -300,6 +300,15 @@ static void timesub(struct timeval *a, struct timeval *b, struct timeval *res) { } } +static void timessub(struct timespec *a, struct timespec *b, struct timespec *res) { + res->tv_sec = a->tv_sec - b->tv_sec; + res->tv_nsec = a->tv_nsec - b->tv_nsec; + if (res->tv_nsec < 0) { + res->tv_sec -= 1; + res->tv_nsec += 1000000000; + } +} + static void timeadd(struct timeval *a, struct timeval *b, struct timeval *res) { res->tv_sec = a->tv_sec + b->tv_sec; res->tv_usec = a->tv_usec + b->tv_usec; @@ -310,10 +319,11 @@ static void timeadd(struct timeval *a, struct timeval *b, struct timeval *res) { } PRIM(time) { - time_t rt; + struct timespec rt; struct rusage ru; - rt = time(NULL); + /* FIXME: we skip error checking here */ + clock_gettime(CLOCK_MONOTONIC, &rt); { struct rusage ru_self, ru_chld; getrusage(RUSAGE_SELF, &ru_self); @@ -324,31 +334,35 @@ PRIM(time) { if (list != NULL) { char *suffix; - long outime, ostime; - struct timeval ut, st; + long ortime, outime, ostime; + struct timespec ort; + struct timeval out, ost; if (length(list) != 3) fail("$&time", "ya goofed"); /* FIXME: error checking on all this! */ - rt -= (time_t)strtol(getstr(list->term), &suffix, 10); + ortime = strtol(getstr(list->term), &suffix, 10); outime = strtol(getstr(list->next->term), &suffix, 10); ostime = strtol(getstr(list->next->next->term), &suffix, 10); - ut.tv_sec = outime / 1000000; - ut.tv_usec = outime % 1000000; - st.tv_sec = ostime / 1000000; - st.tv_usec = ostime % 1000000; - timesub(&ru.ru_utime, &ut, &ru.ru_utime); - timesub(&ru.ru_stime, &st, &ru.ru_stime); + ort.tv_sec = ortime / 1000000; + ort.tv_nsec = (ortime % 1000000) * 1000; + out.tv_sec = outime / 1000000; + out.tv_usec = outime % 1000000; + ost.tv_sec = ostime / 1000000; + ost.tv_usec = ostime % 1000000; + timessub(&rt, &ort, &rt); + timevsub(&ru.ru_utime, &out, &ru.ru_utime); + timevsub(&ru.ru_stime, &ost, &ru.ru_stime); } gcdisable(); list = mklist(mkstr(str( - "%6ldr %5ld.%ldu %5ld.%lds", - rt, - ru.ru_utime.tv_sec, (long) (ru.ru_utime.tv_usec / 100000), - ru.ru_stime.tv_sec, (long) (ru.ru_stime.tv_usec / 100000) + "%6ld.%03ldr %5ld.%03ldu %5ld.%03lds", + rt.tv_sec, rt.tv_nsec / 1000000, + ru.ru_utime.tv_sec, ru.ru_utime.tv_usec / 1000, + ru.ru_stime.tv_sec, ru.ru_stime.tv_usec / 1000 )), - mklist(mkstr(str("%ld", rt)), + mklist(mkstr(str("%ld", rt.tv_sec * 1000000 + rt.tv_nsec / 1000)), mklist(mkstr(str("%ld", ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec)), mklist(mkstr(str("%ld", ru.ru_stime.tv_sec * 1000000 + ru.ru_stime.tv_usec)), NULL)))); gcenable(); From c2d4b57b3d247e7edf7fd394d3e36cf2e42a2fcc Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Fri, 2 May 2025 23:10:21 -0700 Subject: [PATCH 3/8] Make fencepost-time code more solid - Clean up time code to be much more legible - Use intmax_t for timestamps to be more overflow-resistant - Add fallback for time functions - Add error handling --- configure.ac | 4 +- es.h | 7 +++ main.c | 3 + prim-sys.c | 174 ++++++++++++++++++++++++++++++++------------------- stdenv.h | 13 ++++ 5 files changed, 135 insertions(+), 66 deletions(-) diff --git a/configure.ac b/configure.ac index 3977c61d..b4765d25 100644 --- a/configure.ac +++ b/configure.ac @@ -58,7 +58,7 @@ ES_WITH_READLINE dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS(fcntl.h limits.h sys/ioctl.h sys/time.h unistd.h memory.h stdarg.h sys/cdefs.h) +AC_CHECK_HEADERS(fcntl.h limits.h sys/ioctl.h sys/time.h unistd.h memory.h stdarg.h sys/cdefs.h inttypes.h) dnl Checks for typedefs, structures, and compiler characteristics. @@ -71,7 +71,7 @@ AC_TYPE_GETGROUPS AC_FUNC_MMAP AC_CHECK_FUNCS(strerror strtol lstat setrlimit sigrelse sighold sigaction \ -sysconf sigsetjmp getrusage mmap mprotect) +sysconf sigsetjmp getrusage gettimeofday mmap mprotect) AC_CACHE_CHECK(whether getenv can be redefined, es_cv_local_getenv, [if test "$ac_cv_header_stdlib_h" = no || test "$ac_cv_header_stdc" = no; then diff --git a/es.h b/es.h index 83ab2216..fc9ab730 100644 --- a/es.h +++ b/es.h @@ -376,6 +376,13 @@ extern void blocksignals(void); extern void unblocksignals(void); +/* prim-sys.c */ + +#if BUILTIN_TIME +extern void inittime(void); +#endif + + /* open.c */ typedef enum { oOpen, oCreate, oAppend, oReadWrite, oReadCreate, oReadAppend } OpenKind; diff --git a/main.c b/main.c index f8f19ad4..75b02339 100644 --- a/main.c +++ b/main.c @@ -115,6 +115,9 @@ int main(int argc, char **argv0) { initconv(); initgc(); +#if BUILTIN_TIME + inittime(); +#endif if (argc == 0) { argc = 1; diff --git a/prim-sys.c b/prim-sys.c index 899a5adc..ee92d53f 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -288,83 +288,129 @@ PRIM(limit) { } #endif /* BSD_LIMITS */ +/* + * time builtin -- this is nearly as bad as limit + */ + #if BUILTIN_TIME -/* This function is provided as timersub(3) on some systems, but it's simple enough - * to do ourselves. */ -static void timevsub(struct timeval *a, struct timeval *b, struct timeval *res) { - res->tv_sec = a->tv_sec - b->tv_sec; - res->tv_usec = a->tv_usec - b->tv_usec; - if (res->tv_usec < 0) { - res->tv_sec -= 1; - res->tv_usec += 1000000; - } +struct times { + intmax_t real_usec; + intmax_t user_usec; + intmax_t sys_usec; +}; + +static void tmerrchk(int result, Boolean throws, char *str) { + if (result != -1) + return; + if (throws) + fail("$&time", "%s: %s", str, esstrerror(errno)); + eprint("%s: %s\n", str, esstrerror(errno)); + eprint("Calls to `$&time` or `time` in this shell may produce bad values.\n"); } -static void timessub(struct timespec *a, struct timespec *b, struct timespec *res) { - res->tv_sec = a->tv_sec - b->tv_sec; - res->tv_nsec = a->tv_nsec - b->tv_nsec; - if (res->tv_nsec < 0) { - res->tv_sec -= 1; - res->tv_nsec += 1000000000; - } +static void getrealtime(struct times *ret, Boolean throws) { +#if HAVE_GETTIMEOFDAY +#define PRECISE_REALTIME 1 + struct timeval tv; + tmerrchk(gettimeofday(&tv, NULL), throws, "getrealtime()"); + ret->real_usec = (tv.tv_sec * 1000000) + tv.tv_usec; +#else /* use time(3p) */ +#define PRECISE_REALTIME 0 + time_t t = time(NULL); + tmerrchk(t, throws, "getrealtime()"); + ret->real_usec = t * 1000000; +#endif } -static void timeadd(struct timeval *a, struct timeval *b, struct timeval *res) { - res->tv_sec = a->tv_sec + b->tv_sec; - res->tv_usec = a->tv_usec + b->tv_usec; - if (res->tv_usec >= 1000000) { - res->tv_sec += 1; - res->tv_usec -= 1000000; - } +static void getusagetimes(struct times *ret, Boolean throws) { +#if HAVE_GETRUSAGE + struct rusage ru_self, ru_child; + tmerrchk(getrusage(RUSAGE_SELF, &ru_self), throws, "getrusage(RUSAGE_SELF)"); + tmerrchk(getrusage(RUSAGE_CHILDREN, &ru_child), throws, "getrusage(RUSAGE_CHILDREN)"); + ret->user_usec = (ru_self.ru_utime.tv_sec * 1000000) + + ru_self.ru_utime.tv_usec + + (ru_child.ru_utime.tv_sec * 1000000) + + ru_child.ru_utime.tv_usec; + ret->sys_usec = (ru_self.ru_stime.tv_sec * 1000000) + + ru_self.ru_stime.tv_usec + + (ru_child.ru_stime.tv_sec * 1000000) + + ru_child.ru_stime.tv_usec; +#else + struct tms tms; + static long mul = -1; + if (mul == -1) + mul = 1000000 / sysconf(_SC_CLK_TCK); + tmerrchk(times(&tms), throws, "getusagetimes()"); + ret->user_usec = (tms.tms_utime + tms.tms_cutime) * mul; + ret->sys_usec = (tms.tms_stime + tms.tms_cstime) * mul; +#endif +} + +static void gettimes(struct times *ret, Boolean throws) { + getrealtime(ret, throws); + getusagetimes(ret, throws); +} + +static void parsetimes(List *list, struct times *ret) { + char *suffix; + if (length(list) != 3) + fail("$&time", "usage: $&time [r u s]"); + + ret->real_usec = strtoimax(getstr(list->term), &suffix, 10); + if (*suffix != '\0') + fail("$&time", "real-time argument not an int", list->term); + ret->user_usec = strtoimax(getstr(list->next->term), &suffix, 10); + if (*suffix != '\0') + fail("$&time", "user-time argument not an int", list->next->term); + ret->sys_usec = strtoimax(getstr(list->next->next->term), &suffix, 10); + if (*suffix != '\0') + fail("$&time", "sys-time argument not an int", list->next->next->term); +} + +static void subtimes(struct times a, struct times b, struct times *ret) { + ret->real_usec = a.real_usec - b.real_usec; + ret->user_usec = a.user_usec - b.user_usec; + ret->sys_usec = a.sys_usec - b.sys_usec; +} + +/* FIXME: numbers are allowed to be negative */ +static char *strtimes(struct times time) { + return str( + "%6ld" +#if PRECISE_REALTIME + ".%03ld" +#endif + "r %5ld.%03ldu %5ld.%03lds", + time.real_usec / 1000000, +#if PRECISE_REALTIME + (time.real_usec % 1000000) / 1000, +#endif + time.user_usec / 1000000, (time.user_usec % 1000000) / 1000, + time.sys_usec / 1000000, (time.sys_usec % 1000000) / 1000 + ); +} + +static struct times first; +extern void inittime(void) { + gettimes(&first, FALSE); } PRIM(time) { - struct timespec rt; - struct rusage ru; - - /* FIXME: we skip error checking here */ - clock_gettime(CLOCK_MONOTONIC, &rt); - { - struct rusage ru_self, ru_chld; - getrusage(RUSAGE_SELF, &ru_self); - getrusage(RUSAGE_CHILDREN, &ru_chld); - timeadd(&ru_self.ru_utime, &ru_chld.ru_utime, &ru.ru_utime); - timeadd(&ru_self.ru_stime, &ru_chld.ru_stime, &ru.ru_stime); - } + struct times prev, time; + + gettimes(&time, TRUE); + subtimes(time, first, &time); if (list != NULL) { - char *suffix; - long ortime, outime, ostime; - struct timespec ort; - struct timeval out, ost; - if (length(list) != 3) - fail("$&time", "ya goofed"); - - /* FIXME: error checking on all this! */ - ortime = strtol(getstr(list->term), &suffix, 10); - outime = strtol(getstr(list->next->term), &suffix, 10); - ostime = strtol(getstr(list->next->next->term), &suffix, 10); - ort.tv_sec = ortime / 1000000; - ort.tv_nsec = (ortime % 1000000) * 1000; - out.tv_sec = outime / 1000000; - out.tv_usec = outime % 1000000; - ost.tv_sec = ostime / 1000000; - ost.tv_usec = ostime % 1000000; - timessub(&rt, &ort, &rt); - timevsub(&ru.ru_utime, &out, &ru.ru_utime); - timevsub(&ru.ru_stime, &ost, &ru.ru_stime); + parsetimes(list, &prev); + subtimes(time, prev, &time); } gcdisable(); - list = mklist(mkstr(str( - "%6ld.%03ldr %5ld.%03ldu %5ld.%03lds", - rt.tv_sec, rt.tv_nsec / 1000000, - ru.ru_utime.tv_sec, ru.ru_utime.tv_usec / 1000, - ru.ru_stime.tv_sec, ru.ru_stime.tv_usec / 1000 - )), - mklist(mkstr(str("%ld", rt.tv_sec * 1000000 + rt.tv_nsec / 1000)), - mklist(mkstr(str("%ld", ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec)), - mklist(mkstr(str("%ld", ru.ru_stime.tv_sec * 1000000 + ru.ru_stime.tv_usec)), NULL)))); + list = mklist(mkstr(strtimes(time)), + mklist(mkstr(str("%ld", time.real_usec)), + mklist(mkstr(str("%ld", time.user_usec)), + mklist(mkstr(str("%ld", time.sys_usec)), NULL)))); gcenable(); return list; } diff --git a/stdenv.h b/stdenv.h index 98bfbf87..32caaa4b 100644 --- a/stdenv.h +++ b/stdenv.h @@ -30,6 +30,19 @@ #include #endif +/* half-heartedly try to handle a lack of or */ +#if HAVE_STDINT_H +#include +#else +#define intmax_t long +#endif + +#if HAVE_INTTYPES_H +#include +#else +#define strtoimax strtol +#endif + #include #include #include From 79e6a05bd6e15291ddb29dd6cff1021ae6f420f6 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sat, 3 May 2025 22:58:51 -0700 Subject: [PATCH 4/8] Reduce overflow risk for times values --- es.h | 2 +- main.c | 2 +- prim-sys.c | 26 +++++++++++++------------- print.c | 11 ++++++++--- print.h | 3 ++- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/es.h b/es.h index fc9ab730..29698e22 100644 --- a/es.h +++ b/es.h @@ -379,7 +379,7 @@ extern void unblocksignals(void); /* prim-sys.c */ #if BUILTIN_TIME -extern void inittime(void); +extern void setbasetime(void); #endif diff --git a/main.c b/main.c index 75b02339..8cf9ebfd 100644 --- a/main.c +++ b/main.c @@ -116,7 +116,7 @@ int main(int argc, char **argv0) { initconv(); initgc(); #if BUILTIN_TIME - inittime(); + setbasetime(); #endif if (argc == 0) { diff --git a/prim-sys.c b/prim-sys.c index ee92d53f..acd62970 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -313,7 +313,7 @@ static void getrealtime(struct times *ret, Boolean throws) { #define PRECISE_REALTIME 1 struct timeval tv; tmerrchk(gettimeofday(&tv, NULL), throws, "getrealtime()"); - ret->real_usec = (tv.tv_sec * 1000000) + tv.tv_usec; + ret->real_usec = (tv.tv_sec * INTMAX_C(1000000)) + tv.tv_usec; #else /* use time(3p) */ #define PRECISE_REALTIME 0 time_t t = time(NULL); @@ -341,8 +341,8 @@ static void getusagetimes(struct times *ret, Boolean throws) { if (mul == -1) mul = 1000000 / sysconf(_SC_CLK_TCK); tmerrchk(times(&tms), throws, "getusagetimes()"); - ret->user_usec = (tms.tms_utime + tms.tms_cutime) * mul; - ret->sys_usec = (tms.tms_stime + tms.tms_cstime) * mul; + ret->user_usec = ((intmax_t)tms.tms_utime + tms.tms_cutime) * mul; + ret->sys_usec = ((intmax_t)tms.tms_stime + tms.tms_cstime) * mul; #endif } @@ -376,11 +376,11 @@ static void subtimes(struct times a, struct times b, struct times *ret) { /* FIXME: numbers are allowed to be negative */ static char *strtimes(struct times time) { return str( - "%6ld" + "%6jd" #if PRECISE_REALTIME - ".%03ld" + ".%03jd" #endif - "r %5ld.%03ldu %5ld.%03lds", + "r %5jd.%03jdu %5jd.%03jds", time.real_usec / 1000000, #if PRECISE_REALTIME (time.real_usec % 1000000) / 1000, @@ -390,16 +390,16 @@ static char *strtimes(struct times time) { ); } -static struct times first; -extern void inittime(void) { - gettimes(&first, FALSE); +static struct times base; +extern void setbasetime(void) { + gettimes(&base, FALSE); } PRIM(time) { struct times prev, time; gettimes(&time, TRUE); - subtimes(time, first, &time); + subtimes(time, base, &time); if (list != NULL) { parsetimes(list, &prev); @@ -408,9 +408,9 @@ PRIM(time) { gcdisable(); list = mklist(mkstr(strtimes(time)), - mklist(mkstr(str("%ld", time.real_usec)), - mklist(mkstr(str("%ld", time.user_usec)), - mklist(mkstr(str("%ld", time.sys_usec)), NULL)))); + mklist(mkstr(str("%jd", time.real_usec)), + mklist(mkstr(str("%jd", time.user_usec)), + mklist(mkstr(str("%jd", time.sys_usec)), NULL)))); gcenable(); return list; } diff --git a/print.c b/print.c index d46715ac..bcd88149 100644 --- a/print.c +++ b/print.c @@ -19,6 +19,7 @@ static Boolean name(Format *format) { \ Flag(uconv, FMT_unsigned) Flag(hconv, FMT_short) Flag(longconv, FMT_long) +Flag(maxconv, FMT_max) Flag(altconv, FMT_altform) Flag(leftconv, FMT_leftside) Flag(dotconv, FMT_f2set) @@ -79,15 +80,18 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform }; char padchar; size_t len, pre, zeroes, padding, width; - long n, flags; - unsigned long u; + long flags; + intmax_t n; + uintmax_t u; char number[64], prefix[20]; if (radix > 36) return; flags = format->flags; - if (flags & FMT_long) + if (flags & FMT_max) + n = va_arg(format->args, intmax_t); + else if (flags & FMT_long) n = va_arg(format->args, long); else n = va_arg(format->args, int); @@ -188,6 +192,7 @@ static void inittab(void) { fmttab['u'] = uconv; fmttab['h'] = hconv; fmttab['l'] = longconv; + fmttab['j'] = maxconv; fmttab['#'] = altconv; fmttab['-'] = leftconv; fmttab['.'] = dotconv; diff --git a/print.h b/print.h index 0171526c..9fcb709e 100644 --- a/print.h +++ b/print.h @@ -23,7 +23,8 @@ enum { FMT_leftside = 16, /* %- */ FMT_altform = 32, /* %# */ FMT_f1set = 64, /* % */ - FMT_f2set = 128 /* %. */ + FMT_f2set = 128, /* %. */ + FMT_max = 256 /* %j */ }; typedef Boolean (*Conv)(Format *); From 4ad5ae8be3a91cc0c288e239639c4d27066a30f5 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 19 Jun 2025 14:01:11 -0700 Subject: [PATCH 5/8] Fix formatting for negative time measurements. This uses the (AFAICT) previously-unused 'dotconv' for a new purpose. If an integer (for example, 8675309) is printed with "%.3d", the output is 8675.309, putting three decimal places after the point. This is a bit weird, but it avoids any complexity around non-integer numbers. --- prim-sys.c | 17 +++++++++-------- print.c | 27 +++++++++++++++++---------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/prim-sys.c b/prim-sys.c index acd62970..e8f82027 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -373,20 +373,21 @@ static void subtimes(struct times a, struct times b, struct times *ret) { ret->sys_usec = a.sys_usec - b.sys_usec; } -/* FIXME: numbers are allowed to be negative */ static char *strtimes(struct times time) { return str( - "%6jd" #if PRECISE_REALTIME - ".%03jd" + "%6.3jd" +#else + "%6jd" #endif - "r %5jd.%03jdu %5jd.%03jds", - time.real_usec / 1000000, + "r %7.3jdu %7.3jds", #if PRECISE_REALTIME - (time.real_usec % 1000000) / 1000, + time.real_usec / 1000, +#else + time.real_usec / 1000000, #endif - time.user_usec / 1000000, (time.user_usec % 1000000) / 1000, - time.sys_usec / 1000000, (time.sys_usec % 1000000) / 1000 + time.user_usec / 1000, + time.sys_usec / 1000 ); } diff --git a/print.c b/print.c index bcd88149..cdfc3b0c 100644 --- a/print.c +++ b/print.c @@ -79,7 +79,7 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", }; char padchar; - size_t len, pre, zeroes, padding, width; + size_t len, pre, padding, width; long flags; intmax_t n; uintmax_t u; @@ -109,12 +109,22 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform prefix[pre++] = *altform++; len = utostr(u, number, radix, table[upper]) - number; - if ((flags & FMT_f2set) && (size_t) format->f2 > len) - zeroes = format->f2 - len; - else - zeroes = 0; + if (flags & FMT_f2set) { + size_t i, figs = format->f2; + if (figs >= len) { + prefix[pre++] = '0'; + prefix[pre++] = '.'; + for (i = figs - len; i > 0; i--) + prefix[pre++] = '0'; + } else { + len++; + for (i = len; i >= len - figs; i--) + number[i] = number[i-1]; + number[i] = '.'; + } + } - width = pre + zeroes + len; + width = pre + len; if ((flags & FMT_f1set) && (size_t) format->f1 > width) { padding = format->f1 - width; } else @@ -123,16 +133,13 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform padchar = ' '; if (padding > 0 && flags & FMT_zeropad) { padchar = '0'; - if ((flags & FMT_leftside) == 0) { - zeroes += padding; + if ((flags & FMT_leftside) == 0) padding = 0; - } } if ((flags & FMT_leftside) == 0) pad(format, padding, padchar); fmtappend(format, prefix, pre); - pad(format, zeroes, '0'); fmtappend(format, number, len); if (flags & FMT_leftside) pad(format, padding, padchar); From 472f4fabbe36359c039bcf409f2c0b3e255eca76 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 19 Jun 2025 14:17:30 -0700 Subject: [PATCH 6/8] Fix "%02d" formatting --- print.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/print.c b/print.c index cdfc3b0c..c186d203 100644 --- a/print.c +++ b/print.c @@ -131,11 +131,8 @@ static void intconv(Format *format, unsigned int radix, int upper, char *altform padding = 0; padchar = ' '; - if (padding > 0 && flags & FMT_zeropad) { + if (flags & FMT_zeropad) padchar = '0'; - if ((flags & FMT_leftside) == 0) - padding = 0; - } if ((flags & FMT_leftside) == 0) pad(format, padding, padchar); From 50c4ae4d6ac68d666cd6fd26a3093b517912555a Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 19 Jun 2025 15:20:03 -0700 Subject: [PATCH 7/8] Generalize man page language to be correct for $&time --- doc/es.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/es.1 b/doc/es.1 index 3fb8e790..84ea19cf 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2756,7 +2756,7 @@ setnoexport setsignals Some primitives are included in .I es conditionally, based on compile-time configuration options. -Those primitives, and the functions to which they are bound, are +Those primitives, and the functions which use them, are .ta 2i .Ds .ft \*(Cf From c83f98f81abffb3e37623203d37c522eca297124 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 19 Jun 2025 15:59:39 -0700 Subject: [PATCH 8/8] Simplify calls to time-getting functions --- prim-sys.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/prim-sys.c b/prim-sys.c index e8f82027..58d69142 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -299,34 +299,36 @@ struct times { intmax_t sys_usec; }; -static void tmerrchk(int result, Boolean throws, char *str) { +static Boolean timethrows = TRUE; + +static void tmerrchk(int result, char *str) { if (result != -1) return; - if (throws) + if (timethrows) fail("$&time", "%s: %s", str, esstrerror(errno)); eprint("%s: %s\n", str, esstrerror(errno)); eprint("Calls to `$&time` or `time` in this shell may produce bad values.\n"); } -static void getrealtime(struct times *ret, Boolean throws) { +static void getrealtime(struct times *ret) { #if HAVE_GETTIMEOFDAY #define PRECISE_REALTIME 1 struct timeval tv; - tmerrchk(gettimeofday(&tv, NULL), throws, "getrealtime()"); + tmerrchk(gettimeofday(&tv, NULL), "getrealtime()"); ret->real_usec = (tv.tv_sec * INTMAX_C(1000000)) + tv.tv_usec; #else /* use time(3p) */ #define PRECISE_REALTIME 0 time_t t = time(NULL); - tmerrchk(t, throws, "getrealtime()"); + tmerrchk(t, "getrealtime()"); ret->real_usec = t * 1000000; #endif } -static void getusagetimes(struct times *ret, Boolean throws) { +static void getusagetimes(struct times *ret) { #if HAVE_GETRUSAGE struct rusage ru_self, ru_child; - tmerrchk(getrusage(RUSAGE_SELF, &ru_self), throws, "getrusage(RUSAGE_SELF)"); - tmerrchk(getrusage(RUSAGE_CHILDREN, &ru_child), throws, "getrusage(RUSAGE_CHILDREN)"); + tmerrchk(getrusage(RUSAGE_SELF, &ru_self), "getrusage(RUSAGE_SELF)"); + tmerrchk(getrusage(RUSAGE_CHILDREN, &ru_child), "getrusage(RUSAGE_CHILDREN)"); ret->user_usec = (ru_self.ru_utime.tv_sec * 1000000) + ru_self.ru_utime.tv_usec + (ru_child.ru_utime.tv_sec * 1000000) @@ -340,15 +342,15 @@ static void getusagetimes(struct times *ret, Boolean throws) { static long mul = -1; if (mul == -1) mul = 1000000 / sysconf(_SC_CLK_TCK); - tmerrchk(times(&tms), throws, "getusagetimes()"); + tmerrchk(times(&tms), "getusagetimes()"); ret->user_usec = ((intmax_t)tms.tms_utime + tms.tms_cutime) * mul; ret->sys_usec = ((intmax_t)tms.tms_stime + tms.tms_cstime) * mul; #endif } -static void gettimes(struct times *ret, Boolean throws) { - getrealtime(ret, throws); - getusagetimes(ret, throws); +static void gettimes(struct times *ret) { + getrealtime(ret); + getusagetimes(ret); } static void parsetimes(List *list, struct times *ret) { @@ -393,13 +395,15 @@ static char *strtimes(struct times time) { static struct times base; extern void setbasetime(void) { - gettimes(&base, FALSE); + timethrows = FALSE; + gettimes(&base); + timethrows = TRUE; } PRIM(time) { struct times prev, time; - gettimes(&time, TRUE); + gettimes(&time); subtimes(time, base, &time); if (list != NULL) {