From ab4081c5127474108bc4a722bf86db695c855d13 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Mon, 28 Apr 2025 08:22:23 -0700 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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) { From b6dc15b17896a2bcbf12980c4cff05d4964ffa91 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Fri, 5 Sep 2025 22:50:41 -0700 Subject: [PATCH 09/11] Remove "fencepost" $&time changes. "Forkless" time and being able to compare arbitrary code points are neat and good, but more straightforwardly useful are improvements to timing precision and clean standard time library handling. --- es.h | 7 ------ initial.es | 13 +---------- main.c | 3 --- prim-sys.c | 67 +++++++++++++++++------------------------------------- 4 files changed, 22 insertions(+), 68 deletions(-) diff --git a/es.h b/es.h index 5f49e507..cf298c09 100644 --- a/es.h +++ b/es.h @@ -374,13 +374,6 @@ extern void blocksignals(void); extern void unblocksignals(void); -/* prim-sys.c */ - -#if BUILTIN_TIME -extern void setbasetime(void); -#endif - - /* open.c */ typedef enum { oOpen, oCreate, oAppend, oReadWrite, oReadCreate, oReadAppend } OpenKind; diff --git a/initial.es b/initial.es index 013fc2d7..987919a0 100644 --- a/initial.es +++ b/initial.es @@ -136,18 +136,7 @@ fn-unwind-protect = $&noreturn @ body cleanup { # and get time from /bin or wherever. if {~ <=$&primitives limit} {fn-limit = $&limit} -if {~ <=$&primitives time} { - fn time cmd { - $&collect - let ((str times) = <=$&time) - unwind-protect { - $cmd - } { - (str times) = <={$&time $times} - echo >[1=2] $str^\t^$^cmd - } - } -} +if {~ <=$&primitives time} {fn-time = $&time} # These builtins are mainly useful for internal functions, but # they're there to be called if you want to use them. diff --git a/main.c b/main.c index 8cf9ebfd..f8f19ad4 100644 --- a/main.c +++ b/main.c @@ -115,9 +115,6 @@ int main(int argc, char **argv0) { initconv(); initgc(); -#if BUILTIN_TIME - setbasetime(); -#endif if (argc == 0) { argc = 1; diff --git a/prim-sys.c b/prim-sys.c index 58d69142..31c134e7 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -299,15 +299,9 @@ struct times { intmax_t sys_usec; }; -static Boolean timethrows = TRUE; - static void tmerrchk(int result, char *str) { - if (result != -1) - return; - if (timethrows) + if (result == -1) 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) { @@ -353,71 +347,52 @@ static void gettimes(struct times *ret) { getusagetimes(ret); } -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; } -static char *strtimes(struct times time) { - return str( +static void strtimes(struct times time, List *list) { + eprint( #if PRECISE_REALTIME "%6.3jd" #else "%6jd" #endif - "r %7.3jdu %7.3jds", + "r %7.3jdu %7.3jds\t%L\n", #if PRECISE_REALTIME time.real_usec / 1000, #else time.real_usec / 1000000, #endif time.user_usec / 1000, - time.sys_usec / 1000 + time.sys_usec / 1000, + list, " " ); } -static struct times base; -extern void setbasetime(void) { - timethrows = FALSE; - gettimes(&base); - timethrows = TRUE; -} - PRIM(time) { + int pid, status; struct times prev, time; + Ref(List *, lp, list); + + gc(); /* do a garbage collection first to ensure reproducible results */ + gettimes(&prev); + pid = efork(TRUE, FALSE); + if (pid == 0) + esexit(exitstatus(eval(lp, NULL, evalflags | eval_inchild))); + status = ewait(pid, FALSE); gettimes(&time); - subtimes(time, base, &time); + SIGCHK(); + printstatus(0, status); - if (list != NULL) { - parsetimes(list, &prev); - subtimes(time, prev, &time); - } + subtimes(time, prev, &time); + strtimes(time, lp); - gcdisable(); - list = mklist(mkstr(strtimes(time)), - 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; + RefEnd(lp); + return mklist(mkstr(mkstatus(status)), NULL); } #endif /* BUILTIN_TIME */ From 0a4af68a815165c6b2b325c76994aec72402178d Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Fri, 12 Dec 2025 08:38:40 -0800 Subject: [PATCH 10/11] Put millisecond-precision $&time behind -DMILLISECOND_TIME=1 --- prim-sys.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/prim-sys.c b/prim-sys.c index 31c134e7..29c20064 100644 --- a/prim-sys.c +++ b/prim-sys.c @@ -305,13 +305,13 @@ static void tmerrchk(int result, char *str) { } static void getrealtime(struct times *ret) { -#if HAVE_GETTIMEOFDAY -#define PRECISE_REALTIME 1 +#if HAVE_GETTIMEOFDAY && MILLISECOND_TIME +#define HAVE_PRECISE_REALTIME 1 struct timeval tv; 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 +#define HAVE_PRECISE_REALTIME 0 time_t t = time(NULL); tmerrchk(t, "getrealtime()"); ret->real_usec = t * 1000000; @@ -354,14 +354,15 @@ static void subtimes(struct times a, struct times b, struct times *ret) { } static void strtimes(struct times time, List *list) { +#if MILLISECOND_TIME eprint( -#if PRECISE_REALTIME +#if HAVE_PRECISE_REALTIME "%6.3jd" #else "%6jd" #endif "r %7.3jdu %7.3jds\t%L\n", -#if PRECISE_REALTIME +#if HAVE_PRECISE_REALTIME time.real_usec / 1000, #else time.real_usec / 1000000, @@ -370,6 +371,15 @@ static void strtimes(struct times time, List *list) { time.sys_usec / 1000, list, " " ); +#else + eprint( + "%6jdr %7.1jdu %7.1jds\t%L\n", + time.real_usec / 1000000, + time.user_usec / 100000, + time.sys_usec / 100000, + list, " " + ); +#endif } PRIM(time) { From 73870e686175049bb7c6b42b8743bfbb4aa1ea83 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Fri, 12 Dec 2025 08:42:15 -0800 Subject: [PATCH 11/11] Add weak fallback type for uintmax_t --- stdenv.h | 1 + 1 file changed, 1 insertion(+) diff --git a/stdenv.h b/stdenv.h index 32caaa4b..aa2780ea 100644 --- a/stdenv.h +++ b/stdenv.h @@ -35,6 +35,7 @@ #include #else #define intmax_t long +#define uintmax_t unsigned long #endif #if HAVE_INTTYPES_H