From bc4983bc806f1cb04bbb6c32248f60af088ed4c4 Mon Sep 17 00:00:00 2001 From: Daniel Dragan Date: Mon, 23 Jun 2025 23:12:58 -0400 Subject: [PATCH 01/24] Time::HiRes.xs: hrstatns() change dTHX -> pTHX inside static func More efficient. This is a static, there are no binary compat concerns. The dTHX is from initial commit of hrstatns() in commit: 75d5269bea - Steve Peters - 10/13/2006 10:11:04 AM Upgrade to Time-HiRes-1.92. --- dist/Time-HiRes/HiRes.pm | 2 +- dist/Time-HiRes/HiRes.xs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/dist/Time-HiRes/HiRes.pm b/dist/Time-HiRes/HiRes.pm index 69436376f1da..158560cab55e 100644 --- a/dist/Time-HiRes/HiRes.pm +++ b/dist/Time-HiRes/HiRes.pm @@ -50,7 +50,7 @@ our @EXPORT_OK = qw (usleep sleep ualarm alarm gettimeofday time tv_interval stat lstat utime ); -our $VERSION = '1.9778'; +our $VERSION = '1.9779'; our $XS_VERSION = $VERSION; $VERSION = eval $VERSION; diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index b879198a30a2..a50748365c48 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -630,10 +630,13 @@ myNVtime() #endif /* #ifdef HAS_GETTIMEOFDAY */ -static void -hrstatns(UV *atime_nsec, UV *mtime_nsec, UV *ctime_nsec) +/* Force inline this because it has only 1 caller: + XSUB void stat(...) PROTOTYPE: ;$ + Change back to plain "static", if in the future a 2nd call site is added */ + +PERL_STATIC_FORCE_INLINE void +S_hrstatns(pTHX_ UV *atime_nsec, UV *mtime_nsec, UV *ctime_nsec) { - dTHX; #if TIME_HIRES_STAT == 1 *atime_nsec = PL_statcache.st_atimespec.tv_nsec; *mtime_nsec = PL_statcache.st_mtimespec.tv_nsec; @@ -661,6 +664,8 @@ hrstatns(UV *atime_nsec, UV *mtime_nsec, UV *ctime_nsec) #endif /* !TIME_HIRES_STAT */ } +#define hrstatns(_at,_mt,_ct) S_hrstatns(aTHX_ (_at),(_mt),(_ct)) + /* Until Apple implements clock_gettime() * (ditto clock_getres() and clock_nanosleep()) * we will emulate them using the Mach kernel interfaces. */ From 3a58a8fd60fbf8d76263aec288a7b9612d00ba29 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 01:08:15 -0400 Subject: [PATCH 02/24] Time::HiRes::stat() cleanup and optimize fake OP struct construction -XPUSHs() requires saving the SV* retval of sv_2mortal(newSVsv()) around a possible Perl_stack_grow(), split the EXTEND from the PUSH, so SV* is held only in volatile registers (liveness). -over EXTEND to 13 elements instead of 1 element. Why not? pp_stat()/pp_lstat() have to do the Perl_stack_grow() call if we don't do it. -remove Zero() macro and use a function call free struct initializer. Just b/c GCC and its offshoot Clang will inline a fixed length memset() doesn't make it part of ISO C. MSVC compiler never inlines memset() calls on WinPerl (b/c P5P never added the magic sauce to ask for that feature). More portably, P5P has never verified the machine code output of all known commercial Unix CCs on all CPU archs regarding inlining memset(). -when filling out the fake OP, do some instruction level parallelism like filling in fakeop with 0s, while digging through my_perl->Iop->op_flags, my_perl->Icurstackinfo->si_cxsubix, my_perl->Icurstackinfo->si_cxstack, and etc, as part of GIMME_V macro, which used to be a libperl.so exported function call a very long time ago IIRC. Another example, translate ix?OP_LSTAT:OP_STAT while translating gm==G_LIST?OPf_WANT_LIST:gm==G_SCALAR?OPf_WANT_SCALAR:OPf_WANT_VOID. Dig through PLT/GOT/PE sym table as part of PL_ppaddr[op_type] while writing to C stk mem as part of fakeop.op_type = op_type -change fakeop.op_ppaddr(aTHX); to ppaddr(aTHX); b/c some CCs have a low IQ and can't prove statement "PL_op = &fakeop;" won't modify field fakeop.op_ppaddr in our C auto storage OP struct var. -don't execute the Perl_sv_2uv_flags() getter method pointlessly inside UV atime = SvUV(ST( 8)); if static function hrstatns() is a NOOP and is inlined and totally optimized away since in some build configs, hrstatns() only does atime_nsec = 0; mtime_nsec = 0; ctime_nsec = 0; Windows is an example. -change SvUV(ST( 8)); to SvUV(SPBASE[ 8]); don't deref my_perl->Istack_base over and over --- dist/Time-HiRes/HiRes.xs | 59 ++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index a50748365c48..0f188cc58b8f 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -1515,39 +1515,56 @@ void stat(...) PROTOTYPE: ;$ PREINIT: - OP fakeop; - int nret; + SSize_t nret; + SV* sv_arg; + SV** SPBASE; ALIAS: Time::HiRes::lstat = 1 PPCODE: - XPUSHs(sv_2mortal(newSVsv(items == 1 ? ST(0) : DEFSV))); + sv_arg = items == 1 ? ST(0) : DEFSV; + EXTEND(SP, 13); + /* XXX will pp_stat()/pp_lstat() really modify $_[0] ? */ + PUSHs(sv_2mortal(newSVsv(sv_arg))); PUTBACK; ENTER; PL_laststatval = -1; SAVEOP(); - Zero(&fakeop, 1, OP); - fakeop.op_type = ix ? OP_LSTAT : OP_STAT; - fakeop.op_ppaddr = PL_ppaddr[fakeop.op_type]; - fakeop.op_flags = GIMME_V == G_LIST ? OPf_WANT_LIST : - GIMME_V == G_SCALAR ? OPf_WANT_SCALAR : OPf_WANT_VOID; - PL_op = &fakeop; - (void)fakeop.op_ppaddr(aTHX); - SPAGAIN; + { + OP* (*ppaddr)(pTHX); + U8 gimme = GIMME_V; /* ILP */ +/* extern "C" memset() doesn't know struct OP's alignment. ISO C doesn't + promise Zero(); and memset(); will inline. But this does. Now the CC can + detangle for us, what OP fields will get a 0/NULL, or our values. */ + OP fakeop = {0}; + U16 op_type = ix ? OP_LSTAT : OP_STAT; + fakeop.op_flags = gimme == G_LIST ? OPf_WANT_LIST : + gimme == G_SCALAR ? OPf_WANT_SCALAR : OPf_WANT_VOID; /* ILP */ + ppaddr = PL_ppaddr[op_type]; + fakeop.op_type = op_type; + fakeop.op_ppaddr = ppaddr; /* ILP */ + PL_op = &fakeop; + (void)ppaddr(aTHX); + } LEAVE; - nret = SP+1 - &ST(0); + SPAGAIN; + SPBASE = &ST(0); + nret = SP+1 - SPBASE; if (nret == 13) { - UV atime = SvUV(ST( 8)); - UV mtime = SvUV(ST( 9)); - UV ctime = SvUV(ST(10)); UV atime_nsec; UV mtime_nsec; UV ctime_nsec; hrstatns(&atime_nsec, &mtime_nsec, &ctime_nsec); - if (atime_nsec) - ST( 8) = sv_2mortal(newSVnv(atime + (NV) atime_nsec / NV_1E9)); - if (mtime_nsec) - ST( 9) = sv_2mortal(newSVnv(mtime + (NV) mtime_nsec / NV_1E9)); - if (ctime_nsec) - ST(10) = sv_2mortal(newSVnv(ctime + (NV) ctime_nsec / NV_1E9)); + if (atime_nsec) { /* on certain configs hrstatns() is a NOOP */ + UV atime = SvUV(SPBASE[ 8]); + SPBASE[ 8] = sv_2mortal(newSVnv(atime + (NV) atime_nsec / NV_1E9)); + } + if (mtime_nsec) { + UV mtime = SvUV(SPBASE[ 9]); + SPBASE[ 9] = sv_2mortal(newSVnv(mtime + (NV) mtime_nsec / NV_1E9)); + } + if (ctime_nsec) { + UV ctime = SvUV(SPBASE[10]); + SPBASE[10] = sv_2mortal(newSVnv(ctime + (NV) ctime_nsec / NV_1E9)); + } } XSRETURN(nret); From 847b776560f74cc645c62ea91d36168e4420af75 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 01:19:05 -0400 Subject: [PATCH 03/24] Time::HiRes.xs shorten multiple very long croak("unimplemented") strings -strings "Time::HiRes::clock", "Time::HiRes::clock_nanosleep", etc will be inside HiRes.dll.so no matter what, b/c BOOT: and newXS_flags() requires them no matter what -type NV_DIE is an un-invasive LOC-wise quick fix to get rid of the tons of EU::PXS injected dXSTARG; statements which execute Perl_sv_newmortal() right before executing croak("%s(): unimplemented in this platform","Time::HiRes::clock"); The retval types could be changed to void, or SV* instead to eliminate the Perl_sv_newmortal() before croak() calls. But for some hysterical raisens, Time::HiRes.xs is confusing Perl_warn() with Perl_croak() in dozens of places. Fixing that is out of scope for this patch. --- dist/Time-HiRes/HiRes.xs | 43 +++++++++++++++++++++------------------- dist/Time-HiRes/typemap | 3 +++ 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 0f188cc58b8f..db2c3af34aa1 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -89,6 +89,9 @@ # undef ITIMER_REALPROF #endif +/* special type used by croak("unimplemented") XSUBs to neutralize */ +typedef NV NV_DIE; /* unused dXSTARG/sv_newmortal() calls */ + #ifndef TIME_HIRES_CLOCKID_T typedef int clockid_t; #endif @@ -1010,12 +1013,12 @@ nanosleep(nsec) # else /* #if defined(TIME_HIRES_NANOSLEEP) */ -NV +NV_DIE nanosleep(nsec) NV nsec CODE: PERL_UNUSED_ARG(nsec); - croak("Time::HiRes::nanosleep(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::nanosleep"); RETVAL = 0.0; OUTPUT: RETVAL @@ -1066,12 +1069,12 @@ sleep(...) #else /* #if defined(HAS_USLEEP) && defined(HAS_GETTIMEOFDAY) */ -NV +NV_DIE usleep(useconds) NV useconds CODE: PERL_UNUSED_ARG(useconds); - croak("Time::HiRes::usleep(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::usleep"); RETVAL = 0.0; OUTPUT: RETVAL @@ -1169,19 +1172,19 @@ ualarm(useconds,interval=0) CODE: PERL_UNUSED_ARG(useconds); PERL_UNUSED_ARG(interval); - croak("Time::HiRes::ualarm(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::ualarm"); RETVAL = -1; OUTPUT: RETVAL -NV +NV_DIE alarm(seconds,interval=0) NV seconds NV interval CODE: PERL_UNUSED_ARG(seconds); PERL_UNUSED_ARG(interval); - croak("Time::HiRes::alarm(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::alarm"); RETVAL = 0.0; OUTPUT: RETVAL @@ -1338,10 +1341,10 @@ PROTOTYPE: $$@ tot++; } } else { - croak("futimens unimplemented in this platform"); + croak("%s unimplemented in this platform", "futimens"); } # else /* HAS_FUTIMENS */ - croak("futimens unimplemented in this platform"); + croak("%s unimplemented in this platform", "futimens"); # endif /* HAS_FUTIMENS */ } } @@ -1356,10 +1359,10 @@ PROTOTYPE: $$@ tot++; } } else { - croak("utimensat unimplemented in this platform"); + croak("%s unimplemented in this platform", "utimensat"); } # else /* HAS_UTIMENSAT */ - croak("utimensat unimplemented in this platform"); + croak("%s unimplemented in this platform", "utimensat"); # endif /* HAS_UTIMENSAT */ } } /* while items */ @@ -1373,7 +1376,7 @@ PROTOTYPE: $$@ I32 utime(accessed, modified, ...) CODE: - croak("Time::HiRes::utime(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::utime"); RETVAL = 0; OUTPUT: RETVAL @@ -1401,12 +1404,12 @@ clock_gettime(clock_id = CLOCK_REALTIME) #else /* if defined(TIME_HIRES_CLOCK_GETTIME) */ -NV +NV_DIE clock_gettime(clock_id = 0) clockid_t clock_id CODE: PERL_UNUSED_ARG(clock_id); - croak("Time::HiRes::clock_gettime(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::clock_gettime"); RETVAL = 0.0; OUTPUT: RETVAL @@ -1434,12 +1437,12 @@ clock_getres(clock_id = CLOCK_REALTIME) #else /* if defined(TIME_HIRES_CLOCK_GETRES) */ -NV +NV_DIE clock_getres(clock_id = 0) clockid_t clock_id CODE: PERL_UNUSED_ARG(clock_id); - croak("Time::HiRes::clock_getres(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::clock_getres"); RETVAL = 0.0; OUTPUT: RETVAL @@ -1470,7 +1473,7 @@ clock_nanosleep(clock_id, nsec, flags = 0) #else /* if defined(TIME_HIRES_CLOCK_NANOSLEEP) && defined(TIMER_ABSTIME) */ -NV +NV_DIE clock_nanosleep(clock_id, nsec, flags = 0) clockid_t clock_id NV nsec @@ -1479,7 +1482,7 @@ clock_nanosleep(clock_id, nsec, flags = 0) PERL_UNUSED_ARG(clock_id); PERL_UNUSED_ARG(nsec); PERL_UNUSED_ARG(flags); - croak("Time::HiRes::clock_nanosleep(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::clock_nanosleep"); RETVAL = 0.0; OUTPUT: RETVAL @@ -1501,10 +1504,10 @@ clock() #else /* if defined(TIME_HIRES_CLOCK) && defined(CLOCKS_PER_SEC) */ -NV +NV_DIE clock() CODE: - croak("Time::HiRes::clock(): unimplemented in this platform"); + croak("%s(): unimplemented in this platform", "Time::HiRes::clock"); RETVAL = 0.0; OUTPUT: RETVAL diff --git a/dist/Time-HiRes/typemap b/dist/Time-HiRes/typemap index 2772c92582e8..fa98deccea83 100644 --- a/dist/Time-HiRes/typemap +++ b/dist/Time-HiRes/typemap @@ -33,6 +33,7 @@ clockid_t T_IV IV T_IV UV T_UV NV T_NV +NV_DIE T_NV_DIE I32 T_IV I16 T_IV I8 T_IV @@ -238,6 +239,8 @@ T_FLOAT sv_setnv($arg, (double)$var); T_NV sv_setnv($arg, (NV)$var); +T_NV_DIE + croak_xs_usage(cv, "T_NV_DIE"); T_DOUBLE sv_setnv($arg, (double)$var); T_PV From fa6e2529c4f438b538211e0f86ff1107044bef82 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 01:57:15 -0400 Subject: [PATCH 04/24] Time::HiRes.xs don't Win32's QueryPerformanceFrequency() over and over -It is a boot time constant. It will not change without a motherboard or CPU swap and then rebooting. The actual 64 bit integer returned, reflects if the NT Kernel wants to use Intel's APIC Timer or Intel's 8253/8254 PIT Timer, or Intel's RDTSC instruction. NT Kernel will only use RDTSC backend if both the CPU and Northbridge swear upon a holy book, that they will fire an interrupt at every Intel/AMD SpeedSwitch/TurboBoost transition. The dynamic CPU speed correction factor logic lives inside the machine code of QueryPerformanceCounter(). Not inside QueryPerformanceFrequency() which has been part of MS;s frozen Public API since 1993. -the test: if (!QueryPerformanceFrequency(&l_tick_frequency)) croak("WT???"); can probably be removed one day, only Win2K or NT4 or Win95/98, running on any 32-bit CISC or 32-but RISC CPU arch, are capable of retval FALSE. The test is added out of paranoia. IDK what in real life on real HW can cause retval FALSE. -calc and save var unsigned __int64 qpc_res_ns; and unsigned __int64 qpc_res_ns_realtime; exactly once instead of re-calcing in the runloop, why not? HiRes.dll's .data section is only 0x650 bytes long and granularity is 0x1000/4096 bytes. -the BOOT: initialization code of the 3 true C static global vars, is written, to assume 2 ithreads, or 2 my_perl ptrs, or 2 different embbeding consumers of perl5XX.dll inside 1 OS process, can simultaneously call Dynaloader::bootstrap() or Time::HiRes::bootstrap() on 2 different CPU cores. This is unrealistic paranoia IMO, but CPU op lock xchg reg, [addr]; and mov [addr], reg; are both 7 bytes long. Maybe Windows >= 8.0 on ARM32/ARM64, want their memory fence/barrier formalities writing to an aligned 64 bit integer. So why not? -#define S_InterlockedExchange64(_d,_s) has S_ prefix, so no assumptions are made on MSVC and Mingw GCC, if InterlockedExchange64() is a macro or a symbol. Any age, any version, any build number, any FOSS project code owner, or any FOSS binary packager, of those 2 C compiler families. --- dist/Time-HiRes/HiRes.xs | 51 +++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index db2c3af34aa1..bb10c19a384c 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -135,11 +135,17 @@ typedef union { typedef struct { unsigned long run_count; unsigned __int64 base_ticks; - unsigned __int64 tick_frequency; FT_t base_systime_as_filetime; unsigned __int64 reset_time; } my_cxt_t; +static unsigned __int64 tick_frequency = 0; +static unsigned __int64 qpc_res_ns = 0; +static unsigned __int64 qpc_res_ns_realtime = 0; + +#define S_InterlockedExchange64(_d,_s) \ + InterlockedExchange64((LONG64 volatile *)(_d),(LONG64)(_s)) + /* Visual C++ 2013 and older don't have the timespec structure. * Neither do mingw.org compilers with MinGW runtimes older than 3.22. */ # if((defined(_MSC_VER) && _MSC_VER < 1900) || \ @@ -220,7 +226,6 @@ _GetSystemTimePreciseAsFileTime(pTHX_ FILETIME *out) if (MY_CXT.run_count++ == 0 || MY_CXT.base_systime_as_filetime.ft_i64 > MY_CXT.reset_time) { - QueryPerformanceFrequency((LARGE_INTEGER*)&MY_CXT.tick_frequency); QueryPerformanceCounter((LARGE_INTEGER*)&MY_CXT.base_ticks); GetSystemTimeAsFileTime(&MY_CXT.base_systime_as_filetime.ft_val); ft.ft_i64 = MY_CXT.base_systime_as_filetime.ft_i64; @@ -232,8 +237,8 @@ _GetSystemTimePreciseAsFileTime(pTHX_ FILETIME *out) QueryPerformanceCounter((LARGE_INTEGER*)&ticks); ticks -= MY_CXT.base_ticks; ft.ft_i64 = MY_CXT.base_systime_as_filetime.ft_i64 - + Const64(IV_1E7) * (ticks / MY_CXT.tick_frequency) - +(Const64(IV_1E7) * (ticks % MY_CXT.tick_frequency)) / MY_CXT.tick_frequency; + + Const64(IV_1E7) * (ticks / tick_frequency) + +(Const64(IV_1E7) * (ticks % tick_frequency)) / tick_frequency; diff = ft.ft_i64 - MY_CXT.base_systime_as_filetime.ft_i64; if (diff < -MAX_PERF_COUNTER_SKEW || diff > MAX_PERF_COUNTER_SKEW) { MY_CXT.base_ticks += ticks; @@ -278,13 +283,12 @@ _clock_gettime(pTHX_ clockid_t clock_id, struct timespec *tp) break; } case CLOCK_MONOTONIC: { - unsigned __int64 freq, ticks; + unsigned __int64 ticks; - QueryPerformanceFrequency((LARGE_INTEGER*)&freq); QueryPerformanceCounter((LARGE_INTEGER*)&ticks); - tp->tv_sec = (time_t)(ticks / freq); - tp->tv_nsec = (long)((IV_1E9 * (ticks % freq)) / freq); + tp->tv_sec = (time_t)(ticks / tick_frequency); + tp->tv_nsec = (long)((IV_1E9 * (ticks % tick_frequency)) / tick_frequency); break; } default: @@ -298,17 +302,10 @@ _clock_gettime(pTHX_ clockid_t clock_id, struct timespec *tp) static int _clock_getres(clockid_t clock_id, struct timespec *tp) { - unsigned __int64 freq, qpc_res_ns; - - QueryPerformanceFrequency((LARGE_INTEGER*)&freq); - qpc_res_ns = IV_1E9 > freq ? IV_1E9 / freq : 1; - switch (clock_id) { case CLOCK_REALTIME: tp->tv_sec = 0; - /* the resolution can't be smaller than 100ns because our implementation - * of CLOCK_REALTIME is using FILETIME internally */ - tp->tv_nsec = (long)(qpc_res_ns > 100 ? qpc_res_ns : 100); + tp->tv_nsec = (long)qpc_res_ns_realtime; break; case CLOCK_MONOTONIC: @@ -929,6 +926,28 @@ BOOT: #ifdef MY_CXT_KEY MY_CXT_INIT; #endif +#if defined(WIN32) || defined(CYGWIN_WITH_W32API) + if (tick_frequency == 0) { /* no DllMain() in very rare static Perls */ + unsigned __int64 l_tick_frequency; +/* from MSDN: >= WinXP, function will always succeed and never return zero */ + if (!QueryPerformanceFrequency((LARGE_INTEGER*)&l_tick_frequency)) + croak("%s(): unimplemented in this platform", "QueryPerformanceFrequency"); + /* 32-bit CPU anti-sharding paranoia */ + S_InterlockedExchange64(&tick_frequency, l_tick_frequency); + } + if (qpc_res_ns == 0) { + unsigned __int64 l_qpc_res_ns = + IV_1E9 > tick_frequency ? IV_1E9 / tick_frequency : 1; + S_InterlockedExchange64(&qpc_res_ns, l_qpc_res_ns); + } + if (qpc_res_ns_realtime == 0) { + /* the resolution can't be smaller than 100ns because our implementation + * of CLOCK_REALTIME is using FILETIME internally */ + unsigned __int64 l_qpc_res_ns_realtime = + qpc_res_ns > 100 ? qpc_res_ns : 100; + S_InterlockedExchange64(&qpc_res_ns_realtime, l_qpc_res_ns_realtime); + } +#endif #ifdef HAS_GETTIMEOFDAY { (void) hv_store(PL_modglobal, "Time::NVtime", 12, From 43963340a00c05d4d313a34cc63f421f02c484d6 Mon Sep 17 00:00:00 2001 From: Daniel Dragan Date: Tue, 24 Jun 2025 03:14:17 -0400 Subject: [PATCH 05/24] Time::HiRes.xs rmv SvIV()/SvNV() calls from croak("unimplemented") stubs Macros SvIV()/SvNV()/SvUV() contain getter function calls. Don't execute the getters, if we will croak() no matter what. The end user doesn't need to see an "Uninitialized variable in" STDERR warning right before croak("unimplemented"); executes. Same goes for SvGETMAGIC() methods firing right before croak("unimplemented"); I picked "int die_t" vs "int_die_t" so IDE syntax highlight keeps working on token "int". --- dist/Time-HiRes/HiRes.xs | 29 ++++++++++++++++------------- dist/Time-HiRes/typemap | 9 +++++++++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index bb10c19a384c..236851126142 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -91,6 +91,9 @@ /* special type used by croak("unimplemented") XSUBs to neutralize */ typedef NV NV_DIE; /* unused dXSTARG/sv_newmortal() calls */ +typedef I32 I32_DIE; + +#define die_t #ifndef TIME_HIRES_CLOCKID_T typedef int clockid_t; @@ -1034,7 +1037,7 @@ nanosleep(nsec) NV_DIE nanosleep(nsec) - NV nsec + NV_DIE nsec CODE: PERL_UNUSED_ARG(nsec); croak("%s(): unimplemented in this platform", "Time::HiRes::nanosleep"); @@ -1090,7 +1093,7 @@ sleep(...) NV_DIE usleep(useconds) - NV useconds + NV_DIE useconds CODE: PERL_UNUSED_ARG(useconds); croak("%s(): unimplemented in this platform", "Time::HiRes::usleep"); @@ -1184,10 +1187,10 @@ alarm(seconds,interval=0) #else /* #ifdef HAS_UALARM */ -int +int die_t ualarm(useconds,interval=0) - int useconds - int interval + int die_t useconds + int die_t interval CODE: PERL_UNUSED_ARG(useconds); PERL_UNUSED_ARG(interval); @@ -1198,8 +1201,8 @@ ualarm(useconds,interval=0) NV_DIE alarm(seconds,interval=0) - NV seconds - NV interval + NV_DIE seconds + NV_DIE interval CODE: PERL_UNUSED_ARG(seconds); PERL_UNUSED_ARG(interval); @@ -1392,7 +1395,7 @@ PROTOTYPE: $$@ #else /* #if defined(TIME_HIRES_UTIME) */ -I32 +I32_DIE utime(accessed, modified, ...) CODE: croak("%s(): unimplemented in this platform", "Time::HiRes::utime"); @@ -1425,7 +1428,7 @@ clock_gettime(clock_id = CLOCK_REALTIME) NV_DIE clock_gettime(clock_id = 0) - clockid_t clock_id + clockid_t die_t clock_id CODE: PERL_UNUSED_ARG(clock_id); croak("%s(): unimplemented in this platform", "Time::HiRes::clock_gettime"); @@ -1458,7 +1461,7 @@ clock_getres(clock_id = CLOCK_REALTIME) NV_DIE clock_getres(clock_id = 0) - clockid_t clock_id + clockid_t die_t clock_id CODE: PERL_UNUSED_ARG(clock_id); croak("%s(): unimplemented in this platform", "Time::HiRes::clock_getres"); @@ -1494,9 +1497,9 @@ clock_nanosleep(clock_id, nsec, flags = 0) NV_DIE clock_nanosleep(clock_id, nsec, flags = 0) - clockid_t clock_id - NV nsec - int flags + clockid_t die_t clock_id + NV_DIE nsec + int die_t flags CODE: PERL_UNUSED_ARG(clock_id); PERL_UNUSED_ARG(nsec); diff --git a/dist/Time-HiRes/typemap b/dist/Time-HiRes/typemap index fa98deccea83..f06b500bb6f7 100644 --- a/dist/Time-HiRes/typemap +++ b/dist/Time-HiRes/typemap @@ -1,5 +1,6 @@ # basic C types int T_IV +int die_t T_IV_DIE unsigned T_UV unsigned int T_UV long T_IV @@ -29,12 +30,14 @@ HV * T_HVREF CV * T_CVREF clockid_t T_IV +clockid_t die_t T_IV_DIE IV T_IV UV T_UV NV T_NV NV_DIE T_NV_DIE I32 T_IV +I32_DIE T_IV_DIE I16 T_IV I8 T_IV STRLEN T_UV @@ -85,6 +88,8 @@ T_UV $var = ($type)SvUV($arg) T_IV $var = ($type)SvIV($arg) +T_IV_DIE + $var = ($type)0 T_INT $var = (int)SvIV($arg) T_ENUM @@ -109,6 +114,8 @@ T_FLOAT $var = (float)SvNV($arg) T_NV $var = ($type)SvNV($arg) +T_NV_DIE + $var = ($type)0 T_DOUBLE $var = (double)SvNV($arg) T_PV @@ -206,6 +213,8 @@ T_CVREF $arg = newRV((SV*)$var); T_IV sv_setiv($arg, (IV)$var); +T_IV_DIE + croak_xs_usage(cv, "T_IV_DIE"); T_UV sv_setuv($arg, (UV)$var); T_INT From 7c0d4cd89fff0ecbb08c718328ee0be874735817 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 03:33:25 -0400 Subject: [PATCH 06/24] Time::HiRes: tweak Win32 static polyfill _GetSystemTimePreciseAsFileTime() To summarize, MS's FILETIME type is an 8 bytes long, 64 bit integer, that might aligned to 4 bytes, not 8. SW E-Attorneys, will vigorously argue, MS's FILETIME type, is an 8 byte long C struct, wrapping a union that wraps a U8 array[8]; string that is 8 bytes long. Claiming type FILETIME is a 64 bit int is libel and slander. Since P5P does not publish a C compiler or C linker. That alignment detail for Windows on RISC machine code is irrelavent. This commit was written to preventing redundant re-reads of a C auto U64 from C stack memory to a CPU register around any possible function call, if they exist, and to narrow down the peak width of each caller function's callstack frame on the C stack. --- dist/Time-HiRes/HiRes.xs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 236851126142..ba936a7d8844 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -193,7 +193,7 @@ START_MY_CXT # define gettimeofday(tp, not_used) _gettimeofday(aTHX_ tp, not_used) # undef GetSystemTimePreciseAsFileTime -# define GetSystemTimePreciseAsFileTime(out) _GetSystemTimePreciseAsFileTime(aTHX_ out) +# define GetSystemTimePreciseAsFileTime(out) (void)(*(out) = _GetSystemTimePreciseAsFileTime(aTHX)) # undef clock_gettime # define clock_gettime(clock_id, tp) _clock_gettime(aTHX_ clock_id, tp) @@ -219,9 +219,28 @@ START_MY_CXT * Windows 8 introduced GetSystemTimePreciseAsFileTime(), but currently we have * to support older systems, so for now we provide our own implementation. * In the future we will switch to the real deal. + * + * FILETIME, switch to "return by copy", vs MS's "return by reference" prototype. + * We never take the fn ptr of static fn _GetSystemTimePreciseAsFileTime(pTHX). + * The MS API GetSystemTimePreciseAsFileTime() has a void return type but we + * have no reason to match ABI compatibility with MS's function symbol. + * Return by copy, encourages CC optimizations, since the C stack FILETIME var + * never escaped the function that declared it. This allows the CC, in the + * caller of _GetSystemTimePreciseAsFileTime(), to keep C stack FILETIME var + * in CPU registers at all times in its function body, if the CC wants to + * do that. + * + * Note even on Win64 x64, where "return by copy" return types > 8 bytes, become + * secret C++ "this"-style first arguments, a > 8 bytes "return by copy" retval + * is still more efficient!!! than explicitly passing a ptr to a C stack alloced + * temporary C struct in C code. The latter requires the CC to re-read the + * temporary C struct each time after any child function call, since the CC + * can't know if SvPV() or GetSystemTimePreciseAsFileTime(), permanently saved + * the pointer for long term Interlocked or Atomic message passing from an + * unknown 2nd OS thread running on another CPU Core. */ -static void -_GetSystemTimePreciseAsFileTime(pTHX_ FILETIME *out) +static FILETIME +_GetSystemTimePreciseAsFileTime(pTHX) { dMY_CXT; FT_t ft; @@ -250,9 +269,7 @@ _GetSystemTimePreciseAsFileTime(pTHX_ FILETIME *out) } } - *out = ft.ft_val; - - return; + return ft.ft_val; } static int From de4257d20f6009f0b1d49dfbb0301bd4af7f21a9 Mon Sep 17 00:00:00 2001 From: Daniel Dragan Date: Tue, 24 Jun 2025 06:02:44 -0400 Subject: [PATCH 07/24] Time::HiRes.xs factor out "negative time not invented yet" croaks() msgs --- dist/Time-HiRes/HiRes.xs | 47 ++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index ba936a7d8844..ce0bbae1d4ad 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -1014,9 +1014,9 @@ usleep(useconds) useconds -= NV_1E6 * seconds; } } else if (useconds < 0.0) - croak("Time::HiRes::usleep(%" NVgf - "): negative time not invented yet", useconds); - + croak("%s(%" NVgf "%s", + "Time::HiRes::usleep", useconds, + "): negative time not invented yet"); usleep((U32)useconds); } else PerlProc_pause(); @@ -1039,8 +1039,8 @@ nanosleep(nsec) struct timespec sleepfor, unslept; CODE: if (nsec < 0.0) - croak("Time::HiRes::nanosleep(%" NVgf - "): negative time not invented yet", nsec); + croak("%s(%" NVgf "%s", "Time::HiRes::nanosleep", nsec, + "): negative time not invented yet"); nanosleep_init(nsec, &sleepfor, &unslept); if (nanosleep(&sleepfor, &unslept) == 0) { RETVAL = nsec; @@ -1085,15 +1085,16 @@ sleep(...) useconds = -(IV)useconds; # endif /* #if defined(__sparc64__) && defined(__GNUC__) */ if ((IV)useconds < 0) - croak("Time::HiRes::sleep(%" NVgf + croak("%s(%" NVgf "): internal error: useconds < 0 (unsigned %" UVuf - " signed %" IVdf ")", + " signed %" IVdf ")", "Time::HiRes::sleep", seconds, useconds, (IV)useconds); } usleep(useconds); } else - croak("Time::HiRes::sleep(%" NVgf - "): negative time not invented yet", seconds); + croak("%s(%" NVgf "%s", + "Time::HiRes::sleep", seconds, + "): negative time not invented yet"); } else PerlProc_pause(); @@ -1128,7 +1129,9 @@ ualarm(useconds,uinterval=0) int uinterval CODE: if (useconds < 0 || uinterval < 0) - croak("Time::HiRes::ualarm(%d, %d): negative time not invented yet", useconds, uinterval); + croak("%s(%d, %d%s", + "Time::HiRes::ualarm", useconds, uinterval, + "): negative time not invented yet"); # if defined(HAS_SETITIMER) && defined(ITIMER_REAL) { struct itimerval itv; @@ -1158,9 +1161,9 @@ alarm(seconds,interval=0) NV interval CODE: if (seconds < 0.0 || interval < 0.0) - croak("Time::HiRes::alarm(%" NVgf ", %" NVgf - "): negative time not invented yet", seconds, interval); - + croak("%s(%" NVgf ", %" NVgf "%s", + "Time::HiRes::alarm", seconds, interval, + "): negative time not invented yet"); { IV iseconds = (IV)seconds; IV iinterval = (IV)interval; @@ -1281,9 +1284,10 @@ setitimer(which, seconds, interval = 0) struct itimerval oldit; PPCODE: if (seconds < 0.0 || interval < 0.0) - croak("Time::HiRes::setitimer(%" IVdf ", %" NVgf ", %" NVgf - "): negative time not invented yet", - (IV)which, seconds, interval); + croak("%s(%" IVdf ", %" NVgf ", %" NVgf "%s", + "Time::HiRes::setitimer", + (IV)which, seconds, interval, + "): negative time not invented yet"); newit.it_value.tv_sec = (IV)seconds; newit.it_value.tv_usec = (IV)((seconds - (NV)newit.it_value.tv_sec) * NV_1E6); @@ -1350,9 +1354,9 @@ PROTOTYPE: $$@ utbufp = NULL; else { if (SvNV(accessed) < 0.0 || SvNV(modified) < 0.0) - croak("Time::HiRes::utime(%" NVgf ", %" NVgf - "): negative time not invented yet", - SvNV(accessed), SvNV(modified)); + croak("%s(%" NVgf ", %" NVgf "%s", "Time::HiRes::utime", + SvNV(accessed), SvNV(modified), + "): negative time not invented yet"); Zero(&utbuf, sizeof utbuf, char); utbuf[0].tv_sec = (Time_t)SvNV(accessed); /* time accessed */ @@ -1499,8 +1503,9 @@ clock_nanosleep(clock_id, nsec, flags = 0) struct timespec sleepfor, unslept; CODE: if (nsec < 0.0) - croak("Time::HiRes::clock_nanosleep(..., %" NVgf - "): negative time not invented yet", nsec); + croak("%s(..., %" NVgf "%s", + "Time::HiRes::clock_nanosleep", nsec, + "): negative time not invented yet"); nanosleep_init(nsec, &sleepfor, &unslept); if (clock_nanosleep(clock_id, flags, &sleepfor, &unslept) == 0) { RETVAL = nsec; From bb623af5cf550eeecbda91b3a7f4324743be48ee Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 06:22:25 -0400 Subject: [PATCH 08/24] Time::HiRes.xs: clock_getres() clock_gettime() remove unused initializers -all C branches/CPP branches in these 2 XSUBs return and set "int status" --- dist/Time-HiRes/HiRes.xs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index ce0bbae1d4ad..c1a503f533e1 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -1433,7 +1433,7 @@ clock_gettime(clock_id = CLOCK_REALTIME) clockid_t clock_id PREINIT: struct timespec ts; - int status = -1; + int status; CODE: # ifdef TIME_HIRES_CLOCK_GETTIME_SYSCALL status = syscall(SYS_clock_gettime, clock_id, &ts); @@ -1465,7 +1465,7 @@ NV clock_getres(clock_id = CLOCK_REALTIME) clockid_t clock_id PREINIT: - int status = -1; + int status; struct timespec ts; CODE: # ifdef TIME_HIRES_CLOCK_GETRES_SYSCALL From 6f61580b3b038b158ce5822ef27e101437bd507f Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 06:55:33 -0400 Subject: [PATCH 09/24] Time::HiRes slim down Win32's _gettimeofday() _clock_gettime() polyfills -remove align padding bytes from struct my_cxt_t{}. unsigned long run_count; is always 4 bytes, the other 3 members are always 8 bytes -cleanup ABI/machine code gen of Win32-only static fn _gettimeofday() It never leaves this TU as a fn ptr. MSVC 2022 -O1/-O2 optimizer can only create unitialzed reg/C stk "holes" for args that are unused in all callers and unused in callee. It can't shift left or collapse any both sides, unused registers/C arguments, in 1 TU, even if no fn ptr if taken in a static function. The new macro remains POSIX-like. -In _GetSystemTimePreciseAsFileTime(), immediatly copy contents of our " &C_auto_u64 " var, to a new C auto var, so the 64-bit value "outputs" or psuedo-retvals of the MS Win API funcs, can be manipulated for the rest of the function's body, completly in CPU registers, with 0% chance of re-reading or pointlessly writing back to the C stack memory address. -Do the same for _gettimeofday_x() when _gettimeofday_x() calls the MS public Win API funcs. -Inside _GetSystemTimePreciseAsFileTime(), hoist/combine/factor out the 2 different callsites of QueryPerformanceCounter() to the root block. All branches will execute QueryPerformanceCounter() anyways. MSVC 2022 refused to hoist the QueryPerformanceCounter() call, around the statement if(MY_CXT.run_count++==0 ||MY_CXT.base_systime_as_filetime.ft_i64>MY_CXT.reset_time){ -add PERL_STATIC_FORCE_INLINE for static funcs like _clock_gettime() that have exactly 1 caller/callsite, usually this is XSUB function with a CV* argument. -add PERL_STATIC_FORCE_INLINE to _gettimeofday(), even though it has 8 different callers/callsites. The reason is because _gettimeofday() has a huge amount of U64 math at its bottom. All the callers then do a huge amount of mostly FP NV/double math, before saving the final NV value to a SV* with NOK_on. To allow the CC to optimize/combine/simplify these 2 large groups of U64 math and NV math, they must be in the same function. So add PERL_STATIC_FORCE_INLINE to _gettimeofday(). sortunsigned long run_count --- dist/Time-HiRes/HiRes.xs | 111 ++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 43 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index c1a503f533e1..e449152f5372 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -55,6 +55,10 @@ # define GCC_DIAG_IGNORE_CPP_COMPAT_RESTORE_STMT GCC_DIAG_RESTORE_STMT #endif +#ifndef PERL_STATIC_FORCE_INLINE +# define PERL_STATIC_FORCE_INLINE STATIC +#endif + #if PERL_VERSION_GE(5,7,3) && !PERL_VERSION_GE(5,10,1) # undef SAVEOP # define SAVEOP() SAVEVPTR(PL_op) @@ -136,10 +140,10 @@ typedef union { # define MY_CXT_KEY "Time::HiRes_" XS_VERSION typedef struct { - unsigned long run_count; unsigned __int64 base_ticks; FT_t base_systime_as_filetime; unsigned __int64 reset_time; + unsigned long run_count; } my_cxt_t; static unsigned __int64 tick_frequency = 0; @@ -190,7 +194,7 @@ START_MY_CXT for performance reasons */ # undef gettimeofday -# define gettimeofday(tp, not_used) _gettimeofday(aTHX_ tp, not_used) +# define gettimeofday(tp, not_used) ((*(tp) = _gettimeofday_x(aTHX)), 0) # undef GetSystemTimePreciseAsFileTime # define GetSystemTimePreciseAsFileTime(out) (void)(*(out) = _GetSystemTimePreciseAsFileTime(aTHX)) @@ -239,78 +243,99 @@ START_MY_CXT * the pointer for long term Interlocked or Atomic message passing from an * unknown 2nd OS thread running on another CPU Core. */ + static FILETIME _GetSystemTimePreciseAsFileTime(pTHX) { - dMY_CXT; - FT_t ft; - - if (MY_CXT.run_count++ == 0 || - MY_CXT.base_systime_as_filetime.ft_i64 > MY_CXT.reset_time) { - - QueryPerformanceCounter((LARGE_INTEGER*)&MY_CXT.base_ticks); - GetSystemTimeAsFileTime(&MY_CXT.base_systime_as_filetime.ft_val); - ft.ft_i64 = MY_CXT.base_systime_as_filetime.ft_i64; - MY_CXT.reset_time = ft.ft_i64 + MAX_PERF_COUNTER_TICKS; +#define MY_CXTX (*MY_CXT_x) + unsigned __int64 ticks; + unsigned __int64 ticks_mem; + unsigned __int64 timesys; + __int64 diff; +/* If no threads, CC will probably optimize away all MY_CXT_x references + so they directly access the C static global struct. */ + my_cxt_t * MY_CXT_x; + + QueryPerformanceCounter((LARGE_INTEGER*)&ticks_mem); + /* Inform the CC nothing external or in this fn (ptr aliasing) can ever + rewrite the value in ticks. Increases chance of CC using registers. */ + ticks = ticks_mem; + { + dMY_CXT; + MY_CXT_x = &(MY_CXT); + } + if (MY_CXTX.run_count++ == 0 || + MY_CXTX.base_systime_as_filetime.ft_i64 > MY_CXTX.reset_time) { + MY_CXTX.base_ticks = ticks; + GetSystemTimeAsFileTime(&MY_CXTX.base_systime_as_filetime.ft_val); + timesys = MY_CXTX.base_systime_as_filetime.ft_i64; + MY_CXTX.reset_time = timesys + MAX_PERF_COUNTER_TICKS; } else { - __int64 diff; - unsigned __int64 ticks; - QueryPerformanceCounter((LARGE_INTEGER*)&ticks); - ticks -= MY_CXT.base_ticks; - ft.ft_i64 = MY_CXT.base_systime_as_filetime.ft_i64 + ticks -= MY_CXTX.base_ticks; + timesys = MY_CXTX.base_systime_as_filetime.ft_i64 + Const64(IV_1E7) * (ticks / tick_frequency) +(Const64(IV_1E7) * (ticks % tick_frequency)) / tick_frequency; - diff = ft.ft_i64 - MY_CXT.base_systime_as_filetime.ft_i64; + diff = timesys - MY_CXTX.base_systime_as_filetime.ft_i64; if (diff < -MAX_PERF_COUNTER_SKEW || diff > MAX_PERF_COUNTER_SKEW) { - MY_CXT.base_ticks += ticks; - GetSystemTimeAsFileTime(&MY_CXT.base_systime_as_filetime.ft_val); - ft.ft_i64 = MY_CXT.base_systime_as_filetime.ft_i64; + MY_CXTX.base_ticks += ticks; + GetSystemTimeAsFileTime(&MY_CXTX.base_systime_as_filetime.ft_val); + timesys = MY_CXTX.base_systime_as_filetime.ft_i64; } } - - return ft.ft_val; +#undef MY_CXTX + { + FT_t ft; + ft.ft_i64 = timesys; + return ft.ft_val; + } } -static int -_gettimeofday(pTHX_ struct timeval *tp, void *not_used) +/* former prototype: static int _gettimeofday(pTHX_ struct timeval *tp, void *not_used); + + B/c _gettimeofday_x() is not capable of failing, and retval was always + constant 0, and its a static fn that never leaves this TU, repurpose the + retval for something better. */ + +PERL_STATIC_FORCE_INLINE struct timeval +_gettimeofday_x(pTHX) { FT_t ft; - - PERL_UNUSED_ARG(not_used); + struct timeval tp; GetSystemTimePreciseAsFileTime(&ft.ft_val); /* seconds since epoch */ - tp->tv_sec = (long)((ft.ft_i64 - EPOCH_BIAS) / Const64(IV_1E7)); + tp.tv_sec = (long)((ft.ft_i64 - EPOCH_BIAS) / Const64(IV_1E7)); /* microseconds remaining */ - tp->tv_usec = (long)((ft.ft_i64 / Const64(10)) % Const64(IV_1E6)); + tp.tv_usec = (long)((ft.ft_i64 / Const64(10)) % Const64(IV_1E6)); - return 0; + return tp; } -static int +/* force inline it, because XS_Time__HiRes_clock_gettime() is the only caller */ + +PERL_STATIC_FORCE_INLINE int _clock_gettime(pTHX_ clockid_t clock_id, struct timespec *tp) { - switch (clock_id) { - case CLOCK_REALTIME: { - FT_t ft; + FT_t ft; + unsigned __int64 ticks; + unsigned __int64 time_sys; + switch (clock_id) { + case CLOCK_REALTIME: GetSystemTimePreciseAsFileTime(&ft.ft_val); - tp->tv_sec = (time_t)((ft.ft_i64 - EPOCH_BIAS) / IV_1E7); - tp->tv_nsec = (long)((ft.ft_i64 % IV_1E7) * 100); + time_sys = ft.ft_i64; + tp->tv_sec = (time_t)((time_sys - EPOCH_BIAS) / IV_1E7); + tp->tv_nsec = (long)((time_sys % IV_1E7) * 100); break; - } - case CLOCK_MONOTONIC: { - unsigned __int64 ticks; - - QueryPerformanceCounter((LARGE_INTEGER*)&ticks); - + case CLOCK_MONOTONIC: + QueryPerformanceCounter((LARGE_INTEGER*)&ft.ft_i64); + ticks = ft.ft_i64; tp->tv_sec = (time_t)(ticks / tick_frequency); tp->tv_nsec = (long)((IV_1E9 * (ticks % tick_frequency)) / tick_frequency); break; - } default: errno = EINVAL; return 1; From 52db2ed59e0ea9b1000ead94be0c583f326dd65b Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 22:59:18 -0400 Subject: [PATCH 10/24] Time::HiRes::bootstrap() use more local vars in registers vs global derefs -each reference to a global var like qpc_res_ns or tick_frequency is 7 bytes in machine code, or a couple more bytes than 7. Since BOOT:{} runs only once, and the chance 2 parallel BOOT:{} XSUBs in 2 different my_perls is almost zero, and even if there are 2 parallel OS threads executing, 1 OS thread isn't going help shave time off the 2nd OS thread. So to reduce the number of 7 byte opcodes that are reading from the global vars, maximize C auto vars as much as possible. QueryPerformanceFrequency() internally on Win7 is around 1-3 ptr derefs into NT's "VDSO" aka KUSER_SHARED_DATA. On Win2k, QPF() is a ring 0 call. -slide indent level to the left b/c the Win32 code block is nested too deep and almost ever statement would exceed 80 chars -cache PL_modglobal to a register, PL_modglobal is a big U32 offset 0x698 into my_perl struct " 48 8B 9F 98 06 00 00 mov rbx, [rdi+698h] " --- dist/Time-HiRes/HiRes.xs | 47 ++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index e449152f5372..aee286822c0d 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -972,33 +972,38 @@ BOOT: MY_CXT_INIT; #endif #if defined(WIN32) || defined(CYGWIN_WITH_W32API) - if (tick_frequency == 0) { /* no DllMain() in very rare static Perls */ - unsigned __int64 l_tick_frequency; +{ + unsigned __int64 l_qpc_res_ns; + unsigned __int64 l_qpc_res_ns_realtime; + unsigned __int64 l_tick_frequency = tick_frequency; + if (l_tick_frequency == 0) { /* no DllMain() in very rare static Perls */ /* from MSDN: >= WinXP, function will always succeed and never return zero */ - if (!QueryPerformanceFrequency((LARGE_INTEGER*)&l_tick_frequency)) + unsigned __int64 l_tick_frequency_mem; + if (!QueryPerformanceFrequency((LARGE_INTEGER*)&l_tick_frequency_mem)) croak("%s(): unimplemented in this platform", "QueryPerformanceFrequency"); + l_tick_frequency = l_tick_frequency_mem; /* 32-bit CPU anti-sharding paranoia */ - S_InterlockedExchange64(&tick_frequency, l_tick_frequency); - } - if (qpc_res_ns == 0) { - unsigned __int64 l_qpc_res_ns = - IV_1E9 > tick_frequency ? IV_1E9 / tick_frequency : 1; - S_InterlockedExchange64(&qpc_res_ns, l_qpc_res_ns); - } - if (qpc_res_ns_realtime == 0) { - /* the resolution can't be smaller than 100ns because our implementation - * of CLOCK_REALTIME is using FILETIME internally */ - unsigned __int64 l_qpc_res_ns_realtime = - qpc_res_ns > 100 ? qpc_res_ns : 100; - S_InterlockedExchange64(&qpc_res_ns_realtime, l_qpc_res_ns_realtime); - } + S_InterlockedExchange64(&tick_frequency, l_tick_frequency); + } + l_qpc_res_ns = qpc_res_ns; + if (l_qpc_res_ns == 0) { + l_qpc_res_ns = IV_1E9 > l_tick_frequency ? IV_1E9 / l_tick_frequency : 1; + S_InterlockedExchange64(&qpc_res_ns, l_qpc_res_ns); + } + l_qpc_res_ns_realtime = qpc_res_ns_realtime; + if (l_qpc_res_ns_realtime == 0) { + /* the resolution can't be smaller than 100ns because our implementation + * of CLOCK_REALTIME is using FILETIME internally */ + l_qpc_res_ns_realtime = l_qpc_res_ns > 100 ? l_qpc_res_ns : 100; + S_InterlockedExchange64(&qpc_res_ns_realtime, l_qpc_res_ns_realtime); + } +} #endif #ifdef HAS_GETTIMEOFDAY { - (void) hv_store(PL_modglobal, "Time::NVtime", 12, - newSViv(PTR2IV(myNVtime)), 0); - (void) hv_store(PL_modglobal, "Time::U2time", 12, - newSViv(PTR2IV(myU2time)), 0); + HV* const modglobal = PL_modglobal; + (void)hv_stores(modglobal, "Time::NVtime", newSViv(PTR2IV(myNVtime))); + (void)hv_stores(modglobal, "Time::U2time", newSViv(PTR2IV(myU2time))); } #endif #if defined(PERL_DARWIN) From 4fd0d8a4d9054203f546b879c5f4961df4b5b450 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 24 Jun 2025 23:04:17 -0400 Subject: [PATCH 11/24] Time::HiRes::stat() change ix from 0/1 to OP_STAT/OP_LSTAT, add $_[0] COW -we dont need to map values 0/1 to OP_STAT/OP_LSTAT at runtime, it can be done once at CC time / BOOT:{} time -IDK why $_[0] is being duped, the pp_stat*() functions aren't supposed to modify incoming @_ args, but if we are going to dupe $_[0], atleast try to use COW semantics if available --- dist/Time-HiRes/HiRes.xs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index aee286822c0d..8d0ea762009e 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -64,6 +64,16 @@ # define SAVEOP() SAVEVPTR(PL_op) #endif +#if defined(SV_COW_SHARED_HASH_KEYS) && defined(SV_COW_OTHER_PVS) +# define THR_newSVsv_cow(sv) newSVsv_flags((sv), SV_GMAGIC|SV_NOSTEAL|SV_COW_SHARED_HASH_KEYS|SV_COW_OTHER_PVS) +#elif defined(SV_COW_SHARED_HASH_KEYS) +# define THR_newSVsv_cow(sv) newSVsv_flags((sv), SV_GMAGIC|SV_NOSTEAL|SV_COW_SHARED_HASH_KEYS) +#elif defined(SV_COW_OTHER_PVS) +# define THR_newSVsv_cow(sv) newSVsv_flags((sv), SV_GMAGIC|SV_NOSTEAL|SV_COW_OTHER_PVS) +#else +# define THR_newSVsv_cow(sv) newSVsv_flags((sv), SV_GMAGIC|SV_NOSTEAL) +#endif + #define IV_1E6 1000000 #define IV_1E7 10000000 #define IV_1E9 1000000000 @@ -1595,13 +1605,15 @@ PROTOTYPE: ;$ SSize_t nret; SV* sv_arg; SV** SPBASE; + U32 op_type = (U32)ix; ALIAS: - Time::HiRes::lstat = 1 + Time::HiRes::stat = OP_STAT + Time::HiRes::lstat = OP_LSTAT PPCODE: - sv_arg = items == 1 ? ST(0) : DEFSV; EXTEND(SP, 13); + sv_arg = items == 1 ? ST(0) : DEFSV; /* XXX will pp_stat()/pp_lstat() really modify $_[0] ? */ - PUSHs(sv_2mortal(newSVsv(sv_arg))); + PUSHs(sv_2mortal(THR_newSVsv_cow(sv_arg))); PUTBACK; ENTER; PL_laststatval = -1; @@ -1613,11 +1625,10 @@ PROTOTYPE: ;$ promise Zero(); and memset(); will inline. But this does. Now the CC can detangle for us, what OP fields will get a 0/NULL, or our values. */ OP fakeop = {0}; - U16 op_type = ix ? OP_LSTAT : OP_STAT; fakeop.op_flags = gimme == G_LIST ? OPf_WANT_LIST : gimme == G_SCALAR ? OPf_WANT_SCALAR : OPf_WANT_VOID; /* ILP */ ppaddr = PL_ppaddr[op_type]; - fakeop.op_type = op_type; + fakeop.op_type = (U16)op_type; fakeop.op_ppaddr = ppaddr; /* ILP */ PL_op = &fakeop; (void)ppaddr(aTHX); From 49c2088784246888c7298d5f556dbddaf75bfed7 Mon Sep 17 00:00:00 2001 From: Daniel Dragan Date: Wed, 25 Jun 2025 02:44:45 -0400 Subject: [PATCH 12/24] Time::HiRes.xs add a specialized croak_unimplemented(cv) function croak("%s(): unimplemented in this platform", "Time::HiRes::ualarm"); This can be estimated at 6 + 7 + 7 = 20 bytes of machine code on Intel. My guess on a RISC CPU is 3 * 2 * 4 = 24 bytes. On any CPU arch, the asm code will look like: mov rel_U32; mov rel_U32; call rel_U32; So create a dedicated static croak func, so these unimplemented stubs are smaller, and will look like: mov reg, reg; call rel_U32; RISC: 4 + (4 || 8) Intel: 3 + (5 || 6) --- dist/Time-HiRes/HiRes.xs | 54 ++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 8d0ea762009e..64d4a3f1d7d1 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -972,6 +972,39 @@ nsec_without_unslept(struct timespec *sleepfor, # define IS_SAFE_PATHNAME(pv, len, opname) (((len)>1)&&memchr((pv), 0, (len)-1)?(SETERRNO(ENOENT, LIB_INVARG),WARNEMU(opname),FALSE):(TRUE)) #endif +static void +S_croak_xs_unimplemented(const CV *const cv); + +static void +S_croak_xs_unimplemented(const CV *const cv) +{ + dTHX; + char buf[sizeof("CODE(0x%" UVxf ")") + (sizeof(UV)*8)]; + const char * pv1; + const GV *const gv = CvGV(cv); + if (gv) { + const char *const gvname = GvNAME(gv); + const HV *const stash = GvSTASH(gv); + const char *const hvname = stash ? HvNAME(stash) : NULL; + if (hvname) + Perl_croak_nocontext("%s::%s(): unimplemented in this platform", + hvname, gvname); + else { + pv1 = gvname; + goto one_str; + } + } else { + my_sprintf(buf, sizeof(buf), "CODE(0x%" UVxf ")", PTR2UV(cv)); + pv1 = buf; + + one_str: + Perl_croak_nocontext( + "%s::%s(): unimplemented in this platform" + (sizeof("%s::")-1), + pv1); + } +} +#define croak_xs_unimplemented S_croak_xs_unimplemented + MODULE = Time::HiRes PACKAGE = Time::HiRes PROTOTYPES: ENABLE @@ -990,7 +1023,8 @@ BOOT: /* from MSDN: >= WinXP, function will always succeed and never return zero */ unsigned __int64 l_tick_frequency_mem; if (!QueryPerformanceFrequency((LARGE_INTEGER*)&l_tick_frequency_mem)) - croak("%s(): unimplemented in this platform", "QueryPerformanceFrequency"); + croak("%s::%s(): unimplemented in this platform" + (sizeof("%s::")-1), + "QueryPerformanceFrequency"); l_tick_frequency = l_tick_frequency_mem; /* 32-bit CPU anti-sharding paranoia */ S_InterlockedExchange64(&tick_frequency, l_tick_frequency); @@ -1097,7 +1131,7 @@ nanosleep(nsec) NV_DIE nsec CODE: PERL_UNUSED_ARG(nsec); - croak("%s(): unimplemented in this platform", "Time::HiRes::nanosleep"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL @@ -1154,7 +1188,7 @@ usleep(useconds) NV_DIE useconds CODE: PERL_UNUSED_ARG(useconds); - croak("%s(): unimplemented in this platform", "Time::HiRes::usleep"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL @@ -1254,7 +1288,7 @@ ualarm(useconds,interval=0) CODE: PERL_UNUSED_ARG(useconds); PERL_UNUSED_ARG(interval); - croak("%s(): unimplemented in this platform", "Time::HiRes::ualarm"); + croak_xs_unimplemented(cv); RETVAL = -1; OUTPUT: RETVAL @@ -1266,7 +1300,7 @@ alarm(seconds,interval=0) CODE: PERL_UNUSED_ARG(seconds); PERL_UNUSED_ARG(interval); - croak("%s(): unimplemented in this platform", "Time::HiRes::alarm"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL @@ -1459,7 +1493,7 @@ PROTOTYPE: $$@ I32_DIE utime(accessed, modified, ...) CODE: - croak("%s(): unimplemented in this platform", "Time::HiRes::utime"); + croak_xs_unimplemented(cv); RETVAL = 0; OUTPUT: RETVAL @@ -1492,7 +1526,7 @@ clock_gettime(clock_id = 0) clockid_t die_t clock_id CODE: PERL_UNUSED_ARG(clock_id); - croak("%s(): unimplemented in this platform", "Time::HiRes::clock_gettime"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL @@ -1525,7 +1559,7 @@ clock_getres(clock_id = 0) clockid_t die_t clock_id CODE: PERL_UNUSED_ARG(clock_id); - croak("%s(): unimplemented in this platform", "Time::HiRes::clock_getres"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL @@ -1566,7 +1600,7 @@ clock_nanosleep(clock_id, nsec, flags = 0) PERL_UNUSED_ARG(clock_id); PERL_UNUSED_ARG(nsec); PERL_UNUSED_ARG(flags); - croak("%s(): unimplemented in this platform", "Time::HiRes::clock_nanosleep"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL @@ -1591,7 +1625,7 @@ clock() NV_DIE clock() CODE: - croak("%s(): unimplemented in this platform", "Time::HiRes::clock"); + croak_xs_unimplemented(cv); RETVAL = 0.0; OUTPUT: RETVAL From b5c9bacbb6a2cf9aa39a09f1339ce8e5be68791c Mon Sep 17 00:00:00 2001 From: bulk88 Date: Wed, 25 Jun 2025 03:01:16 -0400 Subject: [PATCH 13/24] Time::HiRes.xs remove or collapse redundant EXTEND()s -gettimeofday() EXTEND is only need if > 1 retval b/c pp_entersub promises @_ 1 slot, lift C stack memory var values to registers, this way if gettimeofday() is a static P5P written polyfill, and if the CC decides to inline it, the struct timeval Tp; C stack var will optimize away -setitimer() min 2 incoming args + PPCODE: is proof we have atleast 2 retval slots -getitimer() 1 in arg + PPCODE: is proof we have atleast 1 retval slot -utime() don't execute SvNV() over and over, don't exec sv_2io() 2x, add SvPV_const() for anti-de-COW future-proofing --- dist/Time-HiRes/HiRes.xs | 42 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 64d4a3f1d7d1..8badee4d3c53 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -1313,17 +1313,21 @@ void gettimeofday() PREINIT: struct timeval Tp; - PPCODE: int status; + U8 is_G_LIST = GIMME_V == G_LIST; + PPCODE: + if (is_G_LIST) + EXTEND(sp, 2); status = gettimeofday (&Tp, NULL); if (status == 0) { - if (GIMME_V == G_LIST) { - EXTEND(sp, 2); - PUSHs(sv_2mortal(newSViv(Tp.tv_sec))); - PUSHs(sv_2mortal(newSViv(Tp.tv_usec))); + if (is_G_LIST) { /* copy to registers to prove sv_2mortal/newSViv */ + IV sec = Tp.tv_sec; /* can't modify the values */ + IV usec = Tp.tv_usec; + PUSHs(sv_2mortal(newSViv(sec))); + PUSHs(sv_2mortal(newSViv(usec))); } else { - EXTEND(sp, 1); - PUSHs(sv_2mortal(newSVnv(Tp.tv_sec + (Tp.tv_usec / NV_1E6)))); + NV nv = Tp.tv_sec + (Tp.tv_usec / NV_1E6); + PUSHs(sv_2mortal(newSVnv(nv))); } } @@ -1373,10 +1377,8 @@ setitimer(which, seconds, interval = 0) */ GCC_DIAG_IGNORE_CPP_COMPAT_STMT; if (setitimer(which, &newit, &oldit) == 0) { - EXTEND(sp, 1); PUSHs(sv_2mortal(newSVnv(TV2NV(oldit.it_value)))); if (GIMME_V == G_LIST) { - EXTEND(sp, 1); PUSHs(sv_2mortal(newSVnv(TV2NV(oldit.it_interval)))); } } @@ -1393,7 +1395,6 @@ getitimer(which) */ GCC_DIAG_IGNORE_CPP_COMPAT_STMT; if (getitimer(which, &nowit) == 0) { - EXTEND(sp, 1); PUSHs(sv_2mortal(newSVnv(TV2NV(nowit.it_value)))); if (GIMME_V == G_LIST) { EXTEND(sp, 1); @@ -1427,28 +1428,31 @@ PROTOTYPE: $$@ if ( accessed == &PL_sv_undef && modified == &PL_sv_undef ) utbufp = NULL; else { - if (SvNV(accessed) < 0.0 || SvNV(modified) < 0.0) + NV modified_nv = SvNV(modified); + NV accessed_nv = SvNV(accessed); + if (accessed_nv < 0.0 || modified_nv < 0.0) croak("%s(%" NVgf ", %" NVgf "%s", "Time::HiRes::utime", - SvNV(accessed), SvNV(modified), + accessed_nv, modified_nv, "): negative time not invented yet"); Zero(&utbuf, sizeof utbuf, char); - utbuf[0].tv_sec = (Time_t)SvNV(accessed); /* time accessed */ + utbuf[0].tv_sec = (Time_t)accessed_nv; /* time accessed */ utbuf[0].tv_nsec = (long)( - (SvNV(accessed) - (NV)utbuf[0].tv_sec) + (accessed_nv - (NV)utbuf[0].tv_sec) * NV_1E9 + (NV)0.5); - utbuf[1].tv_sec = (Time_t)SvNV(modified); /* time modified */ + utbuf[1].tv_sec = (Time_t)modified_nv; /* time modified */ utbuf[1].tv_nsec = (long)( - (SvNV(modified) - (NV)utbuf[1].tv_sec) + (modified_nv - (NV)utbuf[1].tv_sec) * NV_1E9 + (NV)0.5); } while (items > 0) { + PerlIO * pio; file = POPs; items--; - if (SvROK(file) && GvIO(SvRV(file)) && IoIFP(sv_2io(SvRV(file)))) { - int fd = PerlIO_fileno(IoIFP(sv_2io(file))); + if (SvROK(file) && GvIO(SvRV(file)) && (pio = IoIFP(sv_2io(SvRV(file))))) { + int fd = PerlIO_fileno(pio); if (fd < 0) { SETERRNO(EBADF,RMS_IFI); } else { @@ -1469,7 +1473,7 @@ PROTOTYPE: $$@ # ifdef HAS_UTIMENSAT if (UTIMENSAT_AVAILABLE) { STRLEN len; - char * name = SvPV(file, len); + const char * name = SvPV_const(file, len); if (IS_SAFE_PATHNAME(name, len, "utime") && utimensat(AT_FDCWD, name, utbufp, 0) == 0) { From 52959c89309bec74e57bb731a64be3da7c845e1e Mon Sep 17 00:00:00 2001 From: bulk88 Date: Wed, 25 Jun 2025 03:53:10 -0400 Subject: [PATCH 14/24] Time::HiRes.xs switch croak_xs_unimplemented() to cv_name(), less bloat -I measured S_croak_xs_unimplemented() at 0x88 bytes of MSVC 2022 -O1 x64 machine code. The optimization probably isn't worth it if break even is 0x88/(7*3) = 6.47 unimpl stubs. Just use exported function cv_name(), we don't need to perfectly match croak_xs_usage()'s text/logic. --- dist/Time-HiRes/HiRes.xs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 8badee4d3c53..f32e17634f2b 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -979,6 +979,10 @@ static void S_croak_xs_unimplemented(const CV *const cv) { dTHX; + SV* sv = cv_name(cv, NULL, 0); + Perl_croak_nocontext( + "%s::%s(): unimplemented in this platform" + (sizeof("%s::")-1), SvPVX(sv)); +#if 0 char buf[sizeof("CODE(0x%" UVxf ")") + (sizeof(UV)*8)]; const char * pv1; const GV *const gv = CvGV(cv); @@ -1002,6 +1006,7 @@ S_croak_xs_unimplemented(const CV *const cv) "%s::%s(): unimplemented in this platform" + (sizeof("%s::")-1), pv1); } +#endif } #define croak_xs_unimplemented S_croak_xs_unimplemented From 4f8f8dd91f0ada8c09cd8f58aa33b1c7a3194ab5 Mon Sep 17 00:00:00 2001 From: Daniel Dragan Date: Wed, 25 Jun 2025 15:03:57 -0400 Subject: [PATCH 15/24] Time::HiRes add fast no dTHX; myNVtime() variant for CPAN XS devs (exprmt) -TMHR has a fancy Perl maintained Win32 high precision GTOD() polyfill impl inside it. But it can't be used for actual benchmarking by CPAN authors b/c it's do a very slow Perl_get_context() call every time to get access to MY_CXT struct. So add a pTHX_ version of myNVtime(). Add tests that prove TMHR's C level public API for CPAN authors actually exists and works. Nothing inside the P5P repo, ever tries to use TMHR's C level Time::HiRes::myNVtime / Time::HiRes::myU2time function pointers. -The 3 XSUBs for calling the TMHR C func ptrs, really should be in a new .xs file inside ext/XS-APItest/ called "benchmark.xs" or "noplgetcxt.xs" that has #define NO_PERL_GET_CONTEXT at the top, UNLIKE all the other XS-APItest .xs files, which try to prove the very slot ithreads-unaware CPAN XS legacy src code compat mode actually works. -POK and SvPVX() store the 2nd fn ptr, in the same SV*, POK flag can be used by CPAN XS authors to separate old TMHR releases w/o the new fn ptr from new TMHR releases that have it. NOK and SvNVX() and using union _xnvu { NV xnv_nv; HV * xgv_stash; <<<<<<<< line_t xnv_lines; bool xnv_bm_tail; }; is an alternative design, but I went with POK and SvPVX, because even with SvREADONLY(), I have paranoia, some C code on some OS on some CPU arch somewhere, will do a random read -> round_and_or_fire_IEEE_OS_signals -> write to SvNVX() operation on the SvNVX() slot, for no good reason, b/c of academic purity/standards body compliance/ABI requirements of that CPU/OS arch, and the function ptr is now giberish, or was converted from a denormal NaN to a normal NaN or SIG_DIV0-ed. -future expansion provision exists, if SvPOK_on && SvCUR() > sizeof(void*), SvPVX() is now a pointer to a C struct/C array, with the 1st 4/8 bytes being a header, and not a fn ptr. -TODO return by copy version of Time::U2time fn ptr, more efficient on certain ABIs (__vectorcall/SysV) that allow 128 bit structs/arrays to be returned in 2 registers back to the caller, and not secret pointers as a secret 1st arg --- dist/Time-HiRes/HiRes.xs | 43 +++++++++++++- ext/XS-APItest/APItest.xs | 120 ++++++++++++++++++++++++++++++++++++++ ext/XS-APItest/t/xsub_h.t | 14 +++++ 3 files changed, 174 insertions(+), 3 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index f32e17634f2b..082ecd76b653 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -671,11 +671,18 @@ myU2time(pTHX_ UV *ret) return status; } +#ifdef PERL_IMPLICIT_CONTEXT +static NV myNVtime_cxt(pTHX); +#endif + static NV myNVtime() { # ifdef WIN32 dTHX; +# ifdef PERL_IMPLICIT_CONTEXT + return myNVtime_cxt(aTHX); +# endif # endif struct timeval Tp; int status; @@ -683,6 +690,19 @@ myNVtime() return status == 0 ? Tp.tv_sec + (Tp.tv_usec / NV_1E6) : -1.0; } +#ifdef PERL_IMPLICIT_CONTEXT + +static NV +myNVtime_cxt(pTHX) +{ + struct timeval Tp; + int status; + status = gettimeofday (&Tp, NULL); + return status == 0 ? Tp.tv_sec + (Tp.tv_usec / NV_1E6) : -1.0; +} + +#endif + #endif /* #ifdef HAS_GETTIMEOFDAY */ /* Force inline this because it has only 1 caller: @@ -1050,9 +1070,26 @@ BOOT: #endif #ifdef HAS_GETTIMEOFDAY { - HV* const modglobal = PL_modglobal; - (void)hv_stores(modglobal, "Time::NVtime", newSViv(PTR2IV(myNVtime))); - (void)hv_stores(modglobal, "Time::U2time", newSViv(PTR2IV(myU2time))); + SV* sv = newSV_type(SVt_PVIV); +#ifdef PERL_IMPLICIT_CONTEXT + static NV (* const pMyNVtime_cxt)(pTHX) = myNVtime_cxt; +#else + static NV (* const pMyNVtime_cxt)(pTHX) = myNVtime; +#endif +/* Don't bother making a 5/9 byte struct{void*; char;} just for '\0'. + It is 8/16 bytes after padding. This SVPV will never be "printed". */ + SvCUR_set(sv, sizeof(pMyNVtime_cxt)); + SvLEN_set(sv, 0); + SvIV_set(sv, PTR2IV(myNVtime)); + SvPV_set(sv, (char *)(&pMyNVtime_cxt)); + SvPOK_on(sv); + SvIOK_on(sv); + SvREADONLY_on(sv); + { + HV* const modglobal = PL_modglobal; + (void)hv_stores(modglobal, "Time::NVtime", sv); + (void)hv_stores(modglobal, "Time::U2time", newSViv(PTR2IV(myU2time))); + } } #endif #if defined(PERL_DARWIN) diff --git a/ext/XS-APItest/APItest.xs b/ext/XS-APItest/APItest.xs index f2d2774a655a..ae034c55d7cf 100644 --- a/ext/XS-APItest/APItest.xs +++ b/ext/XS-APItest/APItest.xs @@ -1593,6 +1593,121 @@ XSPP_wrapped(my_pp_anonlist, 0, 1) RETURN; } +static NV (*myNVtime)() = NULL; +static NV (*myNVtime_cxt)(pTHX) = NULL; +static void (*myU2time)(pTHX_ UV ret[2]) = NULL; + +#if defined(MULTIPLICITY) && !defined(PERL_NO_GET_CONTEXT) && !defined(PERL_CORE) +# undef aTHX +# undef aTHX_ +# define aTHX my_perl +# define aTHX_ aTHX, +#endif + +XS_INTERNAL(XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_Init) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + PERL_UNUSED_VAR(ax); /* -Wall */ + { + HV* modglobal = PL_modglobal; + SV **svp = hv_fetchs(modglobal, "Time::NVtime", 0); + SV* sv; + if (!svp) + croak("Time::HiRes is required"); + sv = *svp; + if (!SvIOK(sv) || !SvIVX(sv)) + croak("Time::NVtime isn't a function pointer"); + myNVtime = INT2PTR(NV(*)(), SvIVX(sv)); + if (!SvPOK(sv) || SvCUR(sv) != sizeof(void*) || SvPVX(sv) == NULL + || *((void**)SvPVX(sv)) == NULL) + croak("Time::NVtime_cxt isn't a function pointer"); + else + myNVtime_cxt = INT2PTR(NV(*)(pTHX), *((void**)SvPVX(sv))); + + svp = hv_fetchs(modglobal, "Time::U2time", 0); + if (!svp) + croak("Time::HiRes is required"); + sv = *svp; + if (!SvIOK(sv) || !SvIVX(sv)) + croak("Time::U2time isn't a function pointer"); + myU2time = INT2PTR(void(*)(pTHX_ UV ret[2]), SvIVX(sv)); + } + XSRETURN_YES; +} + +XS_INTERNAL(XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_myNVtime) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + PERL_UNUSED_VAR(ax); /* -Wall */ + { + dXSTARG; + PUSHs(TARG); + PUTBACK; + { + NV nv = myNVtime(); + TARGn(nv,1); + } + } + return; +} + +XS_INTERNAL(XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_myNVtime_cxt) +{ + dVAR; dXSARGS; + if (items != 0) + croak_xs_usage(cv, ""); + PERL_UNUSED_VAR(ax); /* -Wall */ + { + SV* TARG; + SV* TARG2; + if(GIMME_V != G_LIST) { + dXSTARG; + TARG2 = TARG; + } + TARG = TARG2; + PUSHs(TARG); + PUTBACK; + { + NV nv = myNVtime_cxt(aTHX); + TARGn(nv,1); + } + } + return; +} + +XS_INTERNAL(XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_myU2time) +{ + dVAR; + dXSARGS; + EXTEND(SP, 2); + if (items != 0) + croak_xs_usage(cv, ""); + PERL_UNUSED_VAR(ax); /* -Wall */ + { + dXSTARG; + UV ret[2]; + SV* sv2; + PUSHs(TARG); + sv2 = sv_2mortal(newSVuv(0)); + PUSHs(sv2); + PUTBACK; + myU2time(aTHX_ ret); + TARGu(ret[0],1); + SvUV_set(sv2, ret[1]); + } + return; +} + +#if defined(MULTIPLICITY) && !defined(PERL_NO_GET_CONTEXT) && !defined(PERL_CORE) +# undef aTHX +# undef aTHX_ +# define aTHX PERL_GET_THX +# define aTHX_ aTHX, +#endif #include "const-c.inc" @@ -1873,6 +1988,11 @@ BOOT: newXS("XS::APItest::XSUB::XS_VERSION_undef", XS_XS__APItest__XSUB_XS_VERSION_undef, __FILE__); newXS("XS::APItest::XSUB::XS_VERSION_empty", XS_XS__APItest__XSUB_XS_VERSION_empty, __FILE__); newXS("XS::APItest::XSUB::XS_APIVERSION_invalid", XS_XS__APItest__XSUB_XS_APIVERSION_invalid, __FILE__); + newXS("XS::APItest::XSUB::Time::HiRes::Init", XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_Init, __FILE__); + newXS("XS::APItest::XSUB::Time::HiRes::myNVtime", XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_myNVtime, __FILE__); + newXS("XS::APItest::XSUB::Time::HiRes::myNVtime_cxt", XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_myNVtime_cxt, __FILE__); + newXS("XS::APItest::XSUB::Time::HiRes::myU2time", XS__APItest__XSUB_XS_APIVERSION_Time_HiRes_myU2time , __FILE__); + void XS_VERSION_defined(...) diff --git a/ext/XS-APItest/t/xsub_h.t b/ext/XS-APItest/t/xsub_h.t index a7e2541e425f..cf0b3bc9fce2 100644 --- a/ext/XS-APItest/t/xsub_h.t +++ b/ext/XS-APItest/t/xsub_h.t @@ -4,6 +4,7 @@ use strict; use Test::More; BEGIN { use_ok('XS::APItest') }; +BEGIN { use_ok('Time::HiRes') }; our ($XS_VERSION, $VERSION); @@ -150,5 +151,18 @@ is scalar @xs_empty, 0, 'XSRETURN_EMPTY returns empty list in array context'; my $xs_empty = XS::APItest::XSUB::xsreturn_empty(); is $xs_empty, undef, 'XSRETURN_EMPTY returns undef in scalar context'; +{ + ok(XS::APItest::XSUB::Time::HiRes::Init(), "XS::APItest::XSUB::Time::HiRes::Init"); + my $num = XS::APItest::XSUB::Time::HiRes::myNVtime(); + ok($num && $num != -1.0 && int($num) != -1, "XS::APItest::XSUB::Time::HiRes::myNVtime true"); + $num = XS::APItest::XSUB::Time::HiRes::myNVtime_cxt(); + ok($num && $num != -1.0 && int($num) != -1, "XS::APItest::XSUB::Time::HiRes::myNVtime_cxt true"); + $num = [XS::APItest::XSUB::Time::HiRes::myU2time()]; + ok(scalar(@{$num}) == 2, "XS::APItest::XSUB::Time::HiRes::myNVtime_cxt 2 element array"); + ok($num->[0] && $num->[0] != -1, "XS::APItest::XSUB::Time::HiRes::myNVtime_cxt array[0] true"); + ok($num->[1] && $num->[1] != -1, "XS::APItest::XSUB::Time::HiRes::myNVtime_cxt array[1] true"); + ok(!defined($num->[2]), "XS::APItest::XSUB::Time::HiRes::myNVtime_cxt array[2] is undef"); +} + done_testing(); From e5ca53418cbab67e27f9581698d8567a2a6e6b3a Mon Sep 17 00:00:00 2001 From: bulk88 Date: Fri, 27 Jun 2025 06:46:45 -0400 Subject: [PATCH 16/24] Time::HiRes Win32 remove a few layers of jump fn calls from QPC() --- dist/Time-HiRes/HiRes.xs | 41 +++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 082ecd76b653..f182db73673a 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -156,12 +156,20 @@ typedef struct { unsigned long run_count; } my_cxt_t; +typedef BOOL (WINAPI *pfnQueryPerformanceCounter_T)(LARGE_INTEGER*); + static unsigned __int64 tick_frequency = 0; static unsigned __int64 qpc_res_ns = 0; static unsigned __int64 qpc_res_ns_realtime = 0; +static pfnQueryPerformanceCounter_T pfnQueryPerformanceCounter = NULL; #define S_InterlockedExchange64(_d,_s) \ InterlockedExchange64((LONG64 volatile *)(_d),(LONG64)(_s)) +#define S_InterlockedExchangePointer(_d,_s) \ + InterlockedExchangePointer((PVOID volatile *)(_d),(PVOID)(_s)) + +#undef QueryPerformanceCounter +#define QueryPerformanceCounter pfnQueryPerformanceCounter /* Visual C++ 2013 and older don't have the timespec structure. * Neither do mingw.org compilers with MinGW runtimes older than 3.22. */ @@ -259,17 +267,19 @@ _GetSystemTimePreciseAsFileTime(pTHX) { #define MY_CXTX (*MY_CXT_x) unsigned __int64 ticks; - unsigned __int64 ticks_mem; + unsigned __int64 timesys; - __int64 diff; /* If no threads, CC will probably optimize away all MY_CXT_x references so they directly access the C static global struct. */ my_cxt_t * MY_CXT_x; - QueryPerformanceCounter((LARGE_INTEGER*)&ticks_mem); + { + unsigned __int64 ticks_mem; + QueryPerformanceCounter((LARGE_INTEGER*)&ticks_mem); /* Inform the CC nothing external or in this fn (ptr aliasing) can ever rewrite the value in ticks. Increases chance of CC using registers. */ - ticks = ticks_mem; + ticks = ticks_mem; + } { dMY_CXT; MY_CXT_x = &(MY_CXT); @@ -282,6 +292,7 @@ _GetSystemTimePreciseAsFileTime(pTHX) MY_CXTX.reset_time = timesys + MAX_PERF_COUNTER_TICKS; } else { + __int64 diff; ticks -= MY_CXTX.base_ticks; timesys = MY_CXTX.base_systime_as_filetime.ft_i64 + Const64(IV_1E7) * (ticks / tick_frequency) @@ -292,6 +303,7 @@ _GetSystemTimePreciseAsFileTime(pTHX) GetSystemTimeAsFileTime(&MY_CXTX.base_systime_as_filetime.ft_val); timesys = MY_CXTX.base_systime_as_filetime.ft_i64; } + /* Note this invisible else {} branch, SKIPS calling GetSystemTimeAsFileTime() */ } #undef MY_CXTX { @@ -1002,7 +1014,7 @@ S_croak_xs_unimplemented(const CV *const cv) SV* sv = cv_name(cv, NULL, 0); Perl_croak_nocontext( "%s::%s(): unimplemented in this platform" + (sizeof("%s::")-1), SvPVX(sv)); -#if 0 +#if 0 /* former implementation, retired because of machine code bloat */ char buf[sizeof("CODE(0x%" UVxf ")") + (sizeof(UV)*8)]; const char * pv1; const GV *const gv = CvGV(cv); @@ -1066,6 +1078,25 @@ BOOT: l_qpc_res_ns_realtime = l_qpc_res_ns > 100 ? l_qpc_res_ns : 100; S_InterlockedExchange64(&qpc_res_ns_realtime, l_qpc_res_ns_realtime); } + {/* Remove a couple jump stub funcs between kernel32->kernelbase->ntdll + for perf reasons. RtlQueryPerformanceCounter() was added in NT 6.1, + so a fallback path is still required to QPC()@K32.dll. */ + pfnQueryPerformanceCounter_T QPCfn = pfnQueryPerformanceCounter; + if (!QPCfn) { + HMODULE hmod = GetModuleHandleW(L"NTDLL.DLL"); + if (hmod) { + QPCfn = (pfnQueryPerformanceCounter_T)GetProcAddress(hmod,"RtlQueryPerformanceCounter"); + if (QPCfn) + goto QPC_done; + } +#undef QueryPerformanceCounter + QPCfn = QueryPerformanceCounter; /* Get the public API fallback sym. */ +#undef QueryPerformanceCounter +#QueryPerformanceCounter pfnQueryPerformanceCounter + QPC_done: + S_InterlockedExchangePointer(&pfnQueryPerformanceCounter, QPCfn); + } + } } #endif #ifdef HAS_GETTIMEOFDAY From 11ac8fc32e8ec20ca04547f3b3348958518377b0 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Fri, 27 Jun 2025 06:57:04 -0400 Subject: [PATCH 17/24] Time::HiRes aggressive max perf implementation of dXSTARG; TARGi_u_n(,); -reason, make these XSUBs as fast as possible so these XSUBs are more accurate for benchmarking, or contribute less overhead to the final numeric time deltas vs the time of whatever PP code was being measured The sv_newmortal()+sv_set_i_u_n_v_mg() permutation is unacceptable. Stepping into sv_upgrade() is unacceptable to do SVt_NULL->SVt_IV. -TMR_TARG***(rsv, RETVAL, 1); macros could be further optimized here vs pp.h's impl of TARG***(RETVAL,1), but that is left for the future. --- dist/Time-HiRes/HiRes.xs | 221 +++++++++++++++++++++++++++++++++------ 1 file changed, 190 insertions(+), 31 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index f182db73673a..ee14506e3420 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -74,6 +74,117 @@ # define THR_newSVsv_cow(sv) newSVsv_flags((sv), SV_GMAGIC|SV_NOSTEAL) #endif +/* PL_op->op_private & OPpENTERSUB_HASTARG feature was added in + +d30110745a - Ilya Zakharevich -8/26/1999 11:33:01 PM - 5.5.61 +Speeding up XSUB calls up to 66% +Addendum: it's "only" 33% speedup. + + These 3 are highly optimized version of 3 macros from pp.h that were + purpose made mostly for EU::PXS's private use, but we DO NOT want to execute + a slower sv_newmortal() + sv_set_i_u_n_v_mg(), instead of + sv_2mortal(newSV_i_u_n_v()). + + These macros do not put the new SV* on the stack. Caller is responsible for + that. + + Arg _nsv is an uninitialized SV* variable, a new SV* will be placed in + the _nsv var. SvREFCNT()/SV* lifecycle details are handled by the macro. + The caller IS NOT allowed to execute a "sv_2mortal(_nsv);" on the new SV*. + + sv_set_i_u_n_v_mg() is required to a huge amount of safety checks like + de-COW PVs RVs, COWs, sv_upgrade(), copy old SV body contents to a higher + order SV body, etc. + + Also if G_LIST context, we do not want Perl_leave_adjust_stacks() to create + a mortal copy of our PAD SV* TARG. Example of returning a dXSTARG, and + Perl_leave_adjust_stacks() instantly makes a mortal dup of it is this code + $self->logtime(time()); +*/ + + + +/* set TARG to the IV value i. If do_taint is false, + * assume that PL_tainted can never be true */ +#define TMR_TARGi(_nsv, i, do_taint) \ +STMT_START { \ + IV TARGi_iv = i; \ + if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) \ + _nsv = sv_2mortal(newSViv(TARGi_iv)); \ + else { \ + _nsv = PAD_SV(PL_op->op_targ); \ + if (LIKELY( \ + ((SvFLAGS(_nsv) & (SVTYPEMASK|SVf_THINKFIRST|SVf_IVisUV)) == SVt_IV) \ + & (do_taint ? !TAINT_get : 1))) \ + { \ + /* Cheap SvIOK_only(). \ + * Assert that flags which SvIOK_only() would test or \ + * clear can't be set, because we're SVt_IV */ \ + assert(!(SvFLAGS(_nsv) & \ + (SVf_OOK|SVf_UTF8|(SVf_OK & ~(SVf_IOK|SVp_IOK))))); \ + SvFLAGS(_nsv) |= (SVf_IOK|SVp_IOK); \ + /* SvIV_set() where sv_any points to head */ \ + _nsv->sv_u.svu_iv = TARGi_iv; \ + } \ + else \ + sv_setiv_mg(_nsv, TARGi_iv); \ + } \ +} STMT_END + +/* set TARG to the UV value u. If do_taint is false, + * assume that PL_tainted can never be true */ +#define TMR_TARGu(_nsv, u, do_taint) \ +STMT_START { \ + UV TARGu_uv = u; \ + if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) \ + _nsv = sv_2mortal(newSVuv(TARGu_uv)); \ + else { \ + _nsv = PAD_SV(PL_op->op_targ); \ + if (LIKELY( \ + ((SvFLAGS(_nsv) & (SVTYPEMASK|SVf_THINKFIRST|SVf_IVisUV)) == SVt_IV) \ + & (do_taint ? !TAINT_get : 1) \ + & (TARGu_uv <= (UV)IV_MAX))) \ + { \ + /* Cheap SvIOK_only(). \ + * Assert that flags which SvIOK_only() would test or \ + * clear can't be set, because we're SVt_IV */ \ + assert(!(SvFLAGS(_nsv) & \ + (SVf_OOK|SVf_UTF8|(SVf_OK & ~(SVf_IOK|SVp_IOK))))); \ + SvFLAGS(_nsv) |= (SVf_IOK|SVp_IOK); \ + /* SvIV_set() where sv_any points to head */ \ + _nsv->sv_u.svu_iv = TARGu_uv; \ + } \ + else \ + sv_setuv_mg(_nsv, TARGu_uv); \ + } \ +} STMT_END + +/* set TARG to the NV value n. If do_taint is false, + * assume that PL_tainted can never be true */ +#define TMR_TARGn(_nsv, n, do_taint) \ +STMT_START { \ + NV TARGn_nv = n; \ + if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) \ + _nsv = sv_2mortal(newSVnv(TARGn_nv)); \ + else { \ + _nsv = PAD_SV(PL_op->op_targ); \ + if (LIKELY( \ + ((SvFLAGS(_nsv) & (SVTYPEMASK|SVf_THINKFIRST)) == SVt_NV) \ + & (do_taint ? !TAINT_get : 1))) \ + { \ + /* Cheap SvNOK_only(). \ + * Assert that flags which SvNOK_only() would test or \ + * clear can't be set, because we're SVt_NV */ \ + assert(!(SvFLAGS(_nsv) & \ + (SVf_OOK|SVf_UTF8|(SVf_OK & ~(SVf_NOK|SVp_NOK))))); \ + SvFLAGS(_nsv) |= (SVf_NOK|SVp_NOK); \ + SvNV_set(_nsv, TARGn_nv); \ + } \ + else \ + sv_setnv_mg(_nsv, TARGn_nv); \ + } \ +} STMT_END + #define IV_1E6 1000000 #define IV_1E7 10000000 #define IV_1E9 1000000000 @@ -1143,11 +1254,13 @@ INCLUDE: const-xs.inc #if defined(HAS_USLEEP) && defined(HAS_GETTIMEOFDAY) -NV +void usleep(useconds) NV useconds PREINIT: struct timeval Ta, Tb; + SV* rsv; + NV RETVAL; CODE: gettimeofday(&Ta, NULL); if (items > 0) { @@ -1173,17 +1286,19 @@ usleep(useconds) printf("[%ld %ld] [%ld %ld]\n", Tb.tv_sec, Tb.tv_usec, Ta.tv_sec, Ta.tv_usec); # endif RETVAL = NV_1E6*(Tb.tv_sec-Ta.tv_sec)+(NV)((IV)Tb.tv_usec-(IV)Ta.tv_usec); - - OUTPUT: - RETVAL + TMR_TARGn(rsv, RETVAL, 1); + SETs(rsv); + return; /* no PUTBACK no PUSH, 1 in, 1 out */ # if defined(TIME_HIRES_NANOSLEEP) -NV +void nanosleep(nsec) NV nsec PREINIT: struct timespec sleepfor, unslept; + SV* rsv; + NV RETVAL; CODE: if (nsec < 0.0) croak("%s(%" NVgf "%s", "Time::HiRes::nanosleep", nsec, @@ -1194,8 +1309,9 @@ nanosleep(nsec) } else { RETVAL = nsec_without_unslept(&sleepfor, &unslept); } - OUTPUT: - RETVAL + TMR_TARGn(rsv, RETVAL, 1); + SETs(rsv); + return; /* no PUTBACK no PUSH, 1 in, 1 out */ # else /* #if defined(TIME_HIRES_NANOSLEEP) */ @@ -1211,11 +1327,13 @@ nanosleep(nsec) # endif /* #if defined(TIME_HIRES_NANOSLEEP) */ -NV +void sleep(...) PREINIT: struct timeval Ta, Tb; - CODE: + SV* rsv; + NV RETVAL; + PPCODE: gettimeofday(&Ta, NULL); if (items > 0) { NV seconds = SvNV(ST(0)); @@ -1250,9 +1368,10 @@ sleep(...) printf("[%ld %ld] [%ld %ld]\n", Tb.tv_sec, Tb.tv_usec, Ta.tv_sec, Ta.tv_usec); # endif RETVAL = (NV)(Tb.tv_sec-Ta.tv_sec)+0.000001*(NV)(Tb.tv_usec-Ta.tv_usec); - - OUTPUT: - RETVAL + TMR_TARGn(rsv, RETVAL, 1); + PUSHs(rsv); + PUTBACK; + return; #else /* #if defined(HAS_USLEEP) && defined(HAS_GETTIMEOFDAY) */ @@ -1270,11 +1389,14 @@ usleep(useconds) #ifdef HAS_UALARM -IV +void ualarm(useconds,uinterval=0) int useconds int uinterval - CODE: + PREINIT: + SV* rsv; + IV RETVAL; + PPCODE: if (useconds < 0 || uinterval < 0) croak("%s(%d, %d%s", "Time::HiRes::ualarm", useconds, uinterval, @@ -1298,15 +1420,19 @@ ualarm(useconds,uinterval=0) RETVAL = ualarm(useconds, uinterval); # endif + TMR_TARGi(rsv, RETVAL, 1); + PUSHs(rsv); + PUTBACK; + return; - OUTPUT: - RETVAL - -NV +void alarm(seconds,interval=0) NV seconds NV interval - CODE: + PREINIT: + SV* rsv; + NV RETVAL; + PPCODE: if (seconds < 0.0 || interval < 0.0) croak("%s(%" NVgf ", %" NVgf "%s", "Time::HiRes::alarm", seconds, interval, @@ -1348,9 +1474,10 @@ alarm(seconds,interval=0) RETVAL = (NV)ualarm( useconds, uinterval ) / NV_1E6; # endif } - - OUTPUT: - RETVAL + TMR_TARGn(rsv, RETVAL, 1); + PUSHs(rsv); + PUTBACK; + return; #else /* #ifdef HAS_UALARM */ @@ -1387,6 +1514,7 @@ gettimeofday() PREINIT: struct timeval Tp; int status; + OP* const op = PL_op; U8 is_G_LIST = GIMME_V == G_LIST; PPCODE: if (is_G_LIST) @@ -1399,15 +1527,41 @@ gettimeofday() PUSHs(sv_2mortal(newSViv(sec))); PUSHs(sv_2mortal(newSViv(usec))); } else { + /* no Perl_leave_adjust_stacks() hazard here, + only a PP vs call_sv() hazard */ NV nv = Tp.tv_sec + (Tp.tv_usec / NV_1E6); - PUSHs(sv_2mortal(newSVnv(nv))); + const U8 do_taint = 1; + NV TARGn_nv = nv; + SV* rsv; + if (op->op_private & OPpENTERSUB_HASTARG) { + rsv = PAD_SV(op->op_targ); + if (LIKELY( + ((SvFLAGS(rsv) & (SVTYPEMASK|SVf_THINKFIRST)) == SVt_NV) + & (do_taint ? !TAINT_get : 1))) + { + /* Cheap SvNOK_only(). + * Assert that flags which SvNOK_only() would test or + * clear can't be set, because we're SVt_NV */ + assert(!(SvFLAGS(rsv) & + (SVf_OOK|SVf_UTF8|(SVf_OK & ~(SVf_NOK|SVp_NOK))))); + SvFLAGS(rsv) |= (SVf_NOK|SVp_NOK); + SvNV_set(rsv, TARGn_nv); + } + else + sv_setnv_mg(rsv, TARGn_nv); + } + else + rsv = sv_2mortal(newSVnv(TARGn_nv)); + PUSHs(rsv); } } -NV +void time() PREINIT: struct timeval Tp; + SV* rsv; + NV RETVAL; CODE: int status; status = gettimeofday (&Tp, NULL); @@ -1416,8 +1570,10 @@ time() } else { RETVAL = -1.0; } - OUTPUT: - RETVAL + TMR_TARGn(rsv, RETVAL, 1); + PUSHs(rsv); /* 0 in, 1 out, entersub guarenteed 1 slot */ + PUTBACK; + return; #endif /* #ifdef HAS_GETTIMEOFDAY */ @@ -1579,22 +1735,25 @@ utime(accessed, modified, ...) #if defined(TIME_HIRES_CLOCK_GETTIME) -NV +void clock_gettime(clock_id = CLOCK_REALTIME) clockid_t clock_id PREINIT: struct timespec ts; int status; - CODE: + SV* rsv; + NV RETVAL; + PPCODE: # ifdef TIME_HIRES_CLOCK_GETTIME_SYSCALL status = syscall(SYS_clock_gettime, clock_id, &ts); # else status = clock_gettime(clock_id, &ts); # endif RETVAL = status == 0 ? ts.tv_sec + (NV) ts.tv_nsec / NV_1E9 : -1; - - OUTPUT: - RETVAL + TMR_TARGn(rsv, RETVAL, 1); + PUSHs(rsv); /* 0 or 1 in, 1 out, PPCODE: did rewind */ + PUTBACK; + return; #else /* if defined(TIME_HIRES_CLOCK_GETTIME) */ From 26e9c3f7dff22f556632e77ec90473afd38cb734 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Fri, 27 Jun 2025 11:01:45 -0400 Subject: [PATCH 18/24] Time::HiRes Win32 add nv_gettimeofday() nv_clock_gettime() anti-precsn loss -add NV retval variants nv_gettimeofday() and nv_clock_gettime(clock_id, &status), the splitting of the solo U64, into 2 IVs/UVs (64b IVs/UVs on my system), then recombing those 2 integers with integer or FP double logic, was very messy and verbose machine code and no, MSVC didn't "algebra" const fold away the splitting and recombing logic, so just create polyfills that always return NVs from the start -do "- ((U64)EPOCH_BIAS" with U64 logic, for maximum chance of no rounding/no precision loss, then do division with FP logic for maximum fractional number precision -"NV nv = nv_clock_gettime(clock_id, &status);" is inlined away, var bool status; has no C stack or register representation in mach code with MSVC 2022 -O1. Returning a pass by copy struct {NV nv; bool success;}; was considered, but never tried, b/c of Win64 AMD64 ABI's "rule" of all retval types > 8 bytes become secret ptrs and a secret 1st arg. Maybe MSVC would inline and fold away the struct, maybe it would not. I didn't try it. Current impl is working as intended. -nv_clock_gettime() still needs to reject junk values in clock_id remember -add tick_frequency_nv, so U64 -> NV is done 1x at startup, not in the run loop -S_croak_xs_unimplemented(const CV *const cv) silence CC warning, cv_name() doesn't want a const CV* head struct --- dist/Time-HiRes/HiRes.xs | 105 +++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index ee14506e3420..88be1c8b8783 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -246,6 +246,10 @@ typedef int clockid_t; # define HAS_GETTIMEOFDAY # endif +# ifndef HAS_NV_GETTIMEOFDAY +# define HAS_NV_GETTIMEOFDAY +# endif + /* shows up in winsock.h? struct timeval { long tv_sec; @@ -270,6 +274,7 @@ typedef struct { typedef BOOL (WINAPI *pfnQueryPerformanceCounter_T)(LARGE_INTEGER*); static unsigned __int64 tick_frequency = 0; +static NV tick_frequency_nv = 0; static unsigned __int64 qpc_res_ns = 0; static unsigned __int64 qpc_res_ns_realtime = 0; static pfnQueryPerformanceCounter_T pfnQueryPerformanceCounter = NULL; @@ -331,6 +336,10 @@ START_MY_CXT # undef clock_gettime # define clock_gettime(clock_id, tp) _clock_gettime(aTHX_ clock_id, tp) +# define TIME_HIRES_NV_CLOCK_GETTIME +# undef nv_clock_gettime +# define nv_clock_gettime(clock_id, _bp) _nv_clock_gettime(aTHX_ clock_id, _bp) + # undef clock_getres # define clock_getres(clock_id, tp) _clock_getres(clock_id, tp) @@ -447,6 +456,18 @@ _gettimeofday_x(pTHX) return tp; } +PERL_STATIC_FORCE_INLINE NV +nv_gettimeofday_x(pTHX) +{ + FT_t ft; + + GetSystemTimePreciseAsFileTime(&ft.ft_val); + + /* FP seconds since epoch */ + return ((NV)((U64)((U64)ft.ft_i64) - ((U64)EPOCH_BIAS))) / ((NV)NV_1E7); +} +#define nv_gettimeofday() nv_gettimeofday_x(aTHX) + /* force inline it, because XS_Time__HiRes_clock_gettime() is the only caller */ PERL_STATIC_FORCE_INLINE int @@ -477,6 +498,30 @@ _clock_gettime(pTHX_ clockid_t clock_id, struct timespec *tp) return 0; } +PERL_STATIC_FORCE_INLINE NV +_nv_clock_gettime(pTHX_ clockid_t clock_id, bool * statusp) +{ + FT_t ft; + unsigned __int64 ticks; + unsigned __int64 time_sys; + + *statusp = 0; + switch (clock_id) { + case CLOCK_REALTIME: + GetSystemTimePreciseAsFileTime(&ft.ft_val); + time_sys = ft.ft_i64; + return ((NV)((U64)((U64)time_sys) - ((U64)EPOCH_BIAS))) / ((NV)NV_1E7); + case CLOCK_MONOTONIC: + QueryPerformanceCounter((LARGE_INTEGER*)&ft.ft_i64); + ticks = ft.ft_i64; + return ((NV)ticks) / tick_frequency_nv; + default: + *statusp = 1; + errno = EINVAL; + return -1.0; + } +} + static int _clock_getres(clockid_t clock_id, struct timespec *tp) { @@ -807,10 +852,14 @@ myNVtime() return myNVtime_cxt(aTHX); # endif # endif +#ifdef HAS_NV_GETTIMEOFDAY + return nv_gettimeofday(); +#else struct timeval Tp; int status; status = gettimeofday (&Tp, NULL); return status == 0 ? Tp.tv_sec + (Tp.tv_usec / NV_1E6) : -1.0; +#endif } #ifdef PERL_IMPLICIT_CONTEXT @@ -818,10 +867,14 @@ myNVtime() static NV myNVtime_cxt(pTHX) { +#ifdef HAS_NV_GETTIMEOFDAY + return nv_gettimeofday(); +#else struct timeval Tp; int status; status = gettimeofday (&Tp, NULL); return status == 0 ? Tp.tv_sec + (Tp.tv_usec / NV_1E6) : -1.0; +#endif } #endif @@ -1116,10 +1169,10 @@ nsec_without_unslept(struct timespec *sleepfor, #endif static void -S_croak_xs_unimplemented(const CV *const cv); +S_croak_xs_unimplemented(CV *const cv); static void -S_croak_xs_unimplemented(const CV *const cv) +S_croak_xs_unimplemented(CV *const cv) { dTHX; SV* sv = cv_name(cv, NULL, 0); @@ -1175,6 +1228,7 @@ BOOT: "QueryPerformanceFrequency"); l_tick_frequency = l_tick_frequency_mem; /* 32-bit CPU anti-sharding paranoia */ + tick_frequency_nv = (NV)l_tick_frequency; S_InterlockedExchange64(&tick_frequency, l_tick_frequency); } l_qpc_res_ns = qpc_res_ns; @@ -1509,6 +1563,12 @@ alarm(seconds,interval=0) #ifdef HAS_GETTIMEOFDAY +#ifdef HAS_NV_GETTIMEOFDAY +# define HAS_NV_GETTIMEOFDAY_BOOL 1 +#else +# define HAS_NV_GETTIMEOFDAY_BOOL 0 +#endif + void gettimeofday() PREINIT: @@ -1516,12 +1576,21 @@ gettimeofday() int status; OP* const op = PL_op; U8 is_G_LIST = GIMME_V == G_LIST; + NV nv; + const U8 do_taint = 1; PPCODE: if (is_G_LIST) EXTEND(sp, 2); + else if(HAS_NV_GETTIMEOFDAY_BOOL) { +#ifdef HAS_NV_GETTIMEOFDAY + nv = nv_gettimeofday(); +#endif + goto ret_1_nv; + } status = gettimeofday (&Tp, NULL); if (status == 0) { - if (is_G_LIST) { /* copy to registers to prove sv_2mortal/newSViv */ + if (HAS_NV_GETTIMEOFDAY_BOOL || is_G_LIST) { + /* copy to registers to prove sv_2mortal/newSViv */ IV sec = Tp.tv_sec; /* can't modify the values */ IV usec = Tp.tv_usec; PUSHs(sv_2mortal(newSViv(sec))); @@ -1529,10 +1598,12 @@ gettimeofday() } else { /* no Perl_leave_adjust_stacks() hazard here, only a PP vs call_sv() hazard */ - NV nv = Tp.tv_sec + (Tp.tv_usec / NV_1E6); - const U8 do_taint = 1; - NV TARGn_nv = nv; + NV TARGn_nv; SV* rsv; + nv = Tp.tv_sec + (Tp.tv_usec / NV_1E6); + + ret_1_nv: + TARGn_nv = nv; if (op->op_private & OPpENTERSUB_HASTARG) { rsv = PAD_SV(op->op_targ); if (LIKELY( @@ -1559,17 +1630,23 @@ gettimeofday() void time() PREINIT: - struct timeval Tp; SV* rsv; NV RETVAL; - CODE: +#ifndef HAS_NV_GETTIMEOFDAY + struct timeval Tp; int status; +#endif + CODE: +#ifndef HAS_NV_GETTIMEOFDAY status = gettimeofday (&Tp, NULL); if (status == 0) { RETVAL = Tp.tv_sec + (Tp.tv_usec / NV_1E6); } else { RETVAL = -1.0; } +#else + RETVAL = nv_gettimeofday(); +#endif TMR_TARGn(rsv, RETVAL, 1); PUSHs(rsv); /* 0 in, 1 out, entersub guarenteed 1 slot */ PUTBACK; @@ -1739,17 +1816,27 @@ void clock_gettime(clock_id = CLOCK_REALTIME) clockid_t clock_id PREINIT: +#ifndef TIME_HIRES_NV_CLOCK_GETTIME struct timespec ts; int status; +#endif SV* rsv; NV RETVAL; PPCODE: # ifdef TIME_HIRES_CLOCK_GETTIME_SYSCALL status = syscall(SYS_clock_gettime, clock_id, &ts); # else +# ifndef TIME_HIRES_NV_CLOCK_GETTIME status = clock_gettime(clock_id, &ts); -# endif RETVAL = status == 0 ? ts.tv_sec + (NV) ts.tv_nsec / NV_1E9 : -1; +# else + { + bool status; + NV nv = nv_clock_gettime(clock_id, &status); + RETVAL = status == 0 ? nv : -1; + } +# endif +# endif TMR_TARGn(rsv, RETVAL, 1); PUSHs(rsv); /* 0 or 1 in, 1 out, PPCODE: did rewind */ PUTBACK; From a1e63804d76df81301158a9f94be85d59646cac8 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Fri, 27 Jun 2025 11:15:50 -0400 Subject: [PATCH 19/24] Time::HiRes add nv_gettimeofday() to usleep() and sleep() --- dist/Time-HiRes/HiRes.xs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 88be1c8b8783..c5cc11f5278f 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -1312,11 +1312,19 @@ void usleep(useconds) NV useconds PREINIT: +#ifndef HAS_NV_GETTIMEOFDAY struct timeval Ta, Tb; +#else + NV Ta_nv, Tb_nv; +#endif SV* rsv; NV RETVAL; CODE: +#ifndef HAS_NV_GETTIMEOFDAY gettimeofday(&Ta, NULL); +#else + Ta_nv = nv_gettimeofday(); +#endif if (items > 0) { if (useconds >= NV_1E6) { IV seconds = (IV) (useconds / NV_1E6); @@ -1334,12 +1342,16 @@ usleep(useconds) usleep((U32)useconds); } else PerlProc_pause(); - +#ifndef HAS_NV_GETTIMEOFDAY gettimeofday(&Tb, NULL); # if 0 printf("[%ld %ld] [%ld %ld]\n", Tb.tv_sec, Tb.tv_usec, Ta.tv_sec, Ta.tv_usec); # endif RETVAL = NV_1E6*(Tb.tv_sec-Ta.tv_sec)+(NV)((IV)Tb.tv_usec-(IV)Ta.tv_usec); +#else + Tb_nv = nv_gettimeofday(); + RETVAL = NV_1E6*(Tb_nv - Ta_nv); +#endif TMR_TARGn(rsv, RETVAL, 1); SETs(rsv); return; /* no PUTBACK no PUSH, 1 in, 1 out */ @@ -1384,11 +1396,19 @@ nanosleep(nsec) void sleep(...) PREINIT: +#ifndef HAS_NV_GETTIMEOFDAY struct timeval Ta, Tb; +#else + NV Ta_nv, Tb_nv; +#endif SV* rsv; NV RETVAL; PPCODE: +#ifndef HAS_NV_GETTIMEOFDAY gettimeofday(&Ta, NULL); +#else + Ta_nv = nv_gettimeofday(); +#endif if (items > 0) { NV seconds = SvNV(ST(0)); if (seconds >= 0.0) { @@ -1416,12 +1436,16 @@ sleep(...) "): negative time not invented yet"); } else PerlProc_pause(); - +#ifndef HAS_NV_GETTIMEOFDAY gettimeofday(&Tb, NULL); # if 0 printf("[%ld %ld] [%ld %ld]\n", Tb.tv_sec, Tb.tv_usec, Ta.tv_sec, Ta.tv_usec); # endif RETVAL = (NV)(Tb.tv_sec-Ta.tv_sec)+0.000001*(NV)(Tb.tv_usec-Ta.tv_usec); +#else + Tb_nv = nv_gettimeofday(); + RETVAL = Tb_nv - Ta_nv; +#endif TMR_TARGn(rsv, RETVAL, 1); PUSHs(rsv); PUTBACK; From e75eb18b19d574af81e3e80ddcf980d7af8f2394 Mon Sep 17 00:00:00 2001 From: Daniel Dragan Date: Fri, 27 Jun 2025 13:30:15 -0400 Subject: [PATCH 20/24] Time::HiRes move @EXPORT_OK init to XS save disk and mem (SVPV HEK* COW) -EU::Constant already has all these AUTOLOAD macro const C strings in the binary, and they aren't going away any time soon. So use those C strings to make SVPV HEK* COWs, and stick them in @EXPORT_OK, instead of @EXPORT_OK holding SVPV Newx() non-COW strings. Besides, most or all all of these C strings will become HV* stash HEs, CV*s, or GV*s, and all of those hold PL_strtab HEK*s, so lets same private bytes phy/virtual memory of a Perl proc at runtime b/c @EXPORT_OK's SV*s are all COWs. And speed up Time::HiRes initial load time since yylex/ck_op*() doesn't have to parse, alloc OPs, alloc pad consts, then run BEGIN, then DTOR all the OPs and pad consts. --- dist/Time-HiRes/HiRes.pm | 43 +------------- dist/Time-HiRes/HiRes.xs | 115 ++++++++++++++++++++++++++++++++++-- dist/Time-HiRes/Makefile.PL | 8 +++ dist/Time-HiRes/t/clock.t | 45 +++++++++++++- 4 files changed, 166 insertions(+), 45 deletions(-) diff --git a/dist/Time-HiRes/HiRes.pm b/dist/Time-HiRes/HiRes.pm index 158560cab55e..9806f7c3f340 100644 --- a/dist/Time-HiRes/HiRes.pm +++ b/dist/Time-HiRes/HiRes.pm @@ -10,45 +10,8 @@ our @ISA = qw(Exporter); our @EXPORT = qw( ); # More or less this same list is in Makefile.PL. Should unify. -our @EXPORT_OK = qw (usleep sleep ualarm alarm gettimeofday time tv_interval - getitimer setitimer nanosleep clock_gettime clock_getres - clock clock_nanosleep - CLOCKS_PER_SEC - CLOCK_BOOTTIME - CLOCK_HIGHRES - CLOCK_MONOTONIC - CLOCK_MONOTONIC_COARSE - CLOCK_MONOTONIC_FAST - CLOCK_MONOTONIC_PRECISE - CLOCK_MONOTONIC_RAW - CLOCK_PROCESS_CPUTIME_ID - CLOCK_PROF - CLOCK_REALTIME - CLOCK_REALTIME_COARSE - CLOCK_REALTIME_FAST - CLOCK_REALTIME_PRECISE - CLOCK_REALTIME_RAW - CLOCK_SECOND - CLOCK_SOFTTIME - CLOCK_THREAD_CPUTIME_ID - CLOCK_TIMEOFDAY - CLOCK_UPTIME - CLOCK_UPTIME_COARSE - CLOCK_UPTIME_FAST - CLOCK_UPTIME_PRECISE - CLOCK_UPTIME_RAW - CLOCK_VIRTUAL - ITIMER_PROF - ITIMER_REAL - ITIMER_REALPROF - ITIMER_VIRTUAL - TIMER_ABSTIME - d_usleep d_ualarm d_gettimeofday d_getitimer d_setitimer - d_nanosleep d_clock_gettime d_clock_getres - d_clock d_clock_nanosleep d_hires_stat - d_futimens d_utimensat d_hires_utime - stat lstat utime - ); +# Set by XSLoader::load()/::bootstrap(). See t/clock.t or HiRes.xs for contents. +our @EXPORT_OK; our $VERSION = '1.9779'; our $XS_VERSION = $VERSION; @@ -90,7 +53,7 @@ sub import { goto &Exporter::import; } -XSLoader::load( 'Time::HiRes', $XS_VERSION ); +XSLoader::load( 'Time::HiRes', $XS_VERSION); # Preloaded methods go here. diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index c5cc11f5278f..25d9fb1bf44b 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -1293,6 +1293,113 @@ BOOT: MUTEX_INIT(&darwin_time_mutex); # endif #endif +#if defined(HAS_GETITIMER) && defined(HAS_SETITIMER) +# define GETITIMER_SUBSTR "Time::HiRes::getitimer"+13 +# define SETITIMER_SUBSTR "Time::HiRes::setitimer"+13 +#else +# define GETITIMER_SUBSTR "d_getitimer"+2 +# define SETITIMER_SUBSTR "d_setitimer"+2 +#endif +#define INIT1 INIT2(sym_usleep, "Time::HiRes::usleep"+13, sizeof("usleep")-1, NULL, 0) \ +INIT2(sym_sleep, "Time::HiRes::sleep"+13, sizeof("sleep")-1, NULL, 0) \ +INIT2(sym_ualarm, "Time::HiRes::ualarm"+13, sizeof("ualarm")-1, NULL, 0) \ +INIT2(sym_alarm, "Time::HiRes::alarm"+13, sizeof("alarm")-1, NULL, 0) \ +INIT2(sym_gettimeofday, "Time::HiRes::gettimeofday"+13, sizeof("gettimeofday")-1, NULL, 0) \ +INIT2(sym_time, "Time::HiRes::time"+13, sizeof("time")-1, NULL, 0) \ +INIT2(sym_tv_interval, "tv_interval", sizeof("tv_interval")-1, NULL, 0) \ +INIT2(sym_getitimer, GETITIMER_SUBSTR, sizeof("getitimer")-1, NULL, 0) \ +INIT2(sym_setitimer, SETITIMER_SUBSTR, sizeof("setitimer")-1, NULL, 0) \ +INIT2(sym_nanosleep, "Time::HiRes::nanosleep"+13, sizeof("nanosleep")-1, NULL, 0) \ +INIT2(sym_clock_gettime, "Time::HiRes::clock_gettime"+13, sizeof("clock_gettime")-1, NULL, 0) \ +INIT2(sym_clock_getres, "Time::HiRes::clock_getres"+13, sizeof("clock_getres")-1, NULL, 0) \ +INIT2(sym_clock, "Time::HiRes::clock"+13, sizeof("clock")-1, NULL, 0) \ +INIT2(sym_clock_nanosleep, "Time::HiRes::clock_nanosleep"+13, sizeof("clock_nanosleep")-1, NULL, 0) \ +INIT2(sym_CLOCKS_PER_SEC, "CLOCKS_PER_SEC", sizeof("CLOCKS_PER_SEC")-1, NULL, 0) \ +INIT2(sym_CLOCK_BOOTTIME, "CLOCK_BOOTTIME", sizeof("CLOCK_BOOTTIME")-1, NULL, 0) \ +INIT2(sym_CLOCK_HIGHRES, "CLOCK_HIGHRES", sizeof("CLOCK_HIGHRES")-1, NULL, 0) \ +INIT2(sym_CLOCK_MONOTONIC, "CLOCK_MONOTONIC", sizeof("CLOCK_MONOTONIC")-1, NULL, 0) \ +INIT2(sym_CLOCK_MONOTONIC_COARSE, "CLOCK_MONOTONIC_COARSE", sizeof("CLOCK_MONOTONIC_COARSE")-1, NULL, 0) \ +INIT2(sym_CLOCK_MONOTONIC_FAST, "CLOCK_MONOTONIC_FAST", sizeof("CLOCK_MONOTONIC_FAST")-1, NULL, 0) \ +INIT2(sym_CLOCK_MONOTONIC_PRECISE, "CLOCK_MONOTONIC_PRECISE", sizeof("CLOCK_MONOTONIC_PRECISE")-1, NULL, 0) \ +INIT2(sym_CLOCK_MONOTONIC_RAW, "CLOCK_MONOTONIC_RAW", sizeof("CLOCK_MONOTONIC_RAW")-1, NULL, 0) \ +INIT2(sym_CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID", sizeof("CLOCK_PROCESS_CPUTIME_ID")-1, NULL, 0) \ +INIT2(sym_CLOCK_PROF, "CLOCK_PROF", sizeof("CLOCK_PROF")-1, NULL, 0) \ +INIT2(sym_CLOCK_REALTIME, "CLOCK_REALTIME", sizeof("CLOCK_REALTIME")-1, NULL, 0) \ +INIT2(sym_CLOCK_REALTIME_COARSE, "CLOCK_REALTIME_COARSE", sizeof("CLOCK_REALTIME_COARSE")-1, NULL, 0) \ +INIT2(sym_CLOCK_REALTIME_FAST, "CLOCK_REALTIME_FAST", sizeof("CLOCK_REALTIME_FAST")-1, NULL, 0) \ +INIT2(sym_CLOCK_REALTIME_PRECISE, "CLOCK_REALTIME_PRECISE", sizeof("CLOCK_REALTIME_PRECISE")-1, NULL, 0) \ +INIT2(sym_CLOCK_REALTIME_RAW, "CLOCK_REALTIME_RAW", sizeof("CLOCK_REALTIME_RAW")-1, NULL, 0) \ +INIT2(sym_CLOCK_SECOND, "CLOCK_SECOND", sizeof("CLOCK_SECOND")-1, NULL, 0) \ +INIT2(sym_CLOCK_SOFTTIME, "CLOCK_SOFTTIME", sizeof("CLOCK_SOFTTIME")-1, NULL, 0) \ +INIT2(sym_CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID", sizeof("CLOCK_THREAD_CPUTIME_ID")-1, NULL, 0) \ +INIT2(sym_CLOCK_TIMEOFDAY, "CLOCK_TIMEOFDAY", sizeof("CLOCK_TIMEOFDAY")-1, NULL, 0) \ +INIT2(sym_CLOCK_UPTIME, "CLOCK_UPTIME", sizeof("CLOCK_UPTIME")-1, NULL, 0) \ +INIT2(sym_CLOCK_UPTIME_COARSE, "CLOCK_UPTIME_COARSE", sizeof("CLOCK_UPTIME_COARSE")-1, NULL, 0) \ +INIT2(sym_CLOCK_UPTIME_FAST, "CLOCK_UPTIME_FAST", sizeof("CLOCK_UPTIME_FAST")-1, NULL, 0) \ +INIT2(sym_CLOCK_UPTIME_PRECISE, "CLOCK_UPTIME_PRECISE", sizeof("CLOCK_UPTIME_PRECISE")-1, NULL, 0) \ +INIT2(sym_CLOCK_UPTIME_RAW, "CLOCK_UPTIME_RAW", sizeof("CLOCK_UPTIME_RAW")-1, NULL, 0) \ +INIT2(sym_CLOCK_VIRTUAL, "CLOCK_VIRTUAL", sizeof("CLOCK_VIRTUAL")-1, NULL, 0) \ +INIT2(sym_ITIMER_PROF, "ITIMER_PROF", sizeof("ITIMER_PROF")-1, NULL, 0) \ +INIT2(sym_ITIMER_REAL, "ITIMER_REAL", sizeof("ITIMER_REAL")-1, NULL, 0) \ +INIT2(sym_ITIMER_REALPROF, "ITIMER_REALPROF", sizeof("ITIMER_REALPROF")-1, NULL, 0) \ +INIT2(sym_ITIMER_VIRTUAL, "ITIMER_VIRTUAL", sizeof("ITIMER_VIRTUAL")-1, NULL, 0) \ +INIT2(sym_TIMER_ABSTIME, "TIMER_ABSTIME", sizeof("TIMER_ABSTIME")-1, NULL, 0) \ +INIT2(sym_d_usleep, "d_usleep", sizeof("d_usleep")-1, "Time::HiRes::usleep"+13, 1) \ +INIT2(sym_d_ualarm, "d_ualarm", sizeof("d_ualarm")-1, "Time::HiRes::ualarm"+13, 1) \ +INIT2(sym_d_gettimeofday, "d_gettimeofday", sizeof("d_gettimeofday")-1, "Time::HiRes::gettimeofday"+13, 1) \ +INIT2(sym_d_getitimer, "d_getitimer", sizeof("d_getitimer")-1, GETITIMER_SUBSTR, 1) \ +INIT2(sym_d_setitimer, "d_setitimer", sizeof("d_setitimer")-1, SETITIMER_SUBSTR, 1) \ +INIT2(sym_d_nanosleep, "d_nanosleep", sizeof("d_nanosleep")-1, "Time::HiRes::nanosleep"+13, 1) \ +INIT2(sym_d_clock_gettime, "d_clock_gettime", sizeof("d_clock_gettime")-1, "Time::HiRes::clock_gettime"+13, 1) \ +INIT2(sym_d_clock_getres, "d_clock_getres", sizeof("d_clock_getres")-1, "Time::HiRes::clock_getres"+13, 1) \ +INIT2(sym_d_clock, "d_clock", sizeof("d_clock")-1, "Time::HiRes::clock"+13, 1) \ +INIT2(sym_d_clock_nanosleep, "d_clock_nanosleep", sizeof("d_clock_nanosleep")-1, "Time::HiRes::clock_nanosleep"+13, 1) \ +INIT2(sym_d_hires_stat, "d_hires_stat", sizeof("d_hires_stat")-1, NULL, 0) \ +INIT2(sym_d_futimens, "d_futimens", sizeof("d_futimens")-1, NULL, 0) \ +INIT2(sym_d_utimensat, "d_utimensat", sizeof("d_utimensat")-1, NULL, 0) \ +INIT2(sym_d_hires_utime, "d_hires_utime", sizeof("d_hires_utime")-1, NULL, 0) \ +INIT2(sym_stat, "Time::HiRes::stat"+13, sizeof("stat")-1, NULL, 0) \ +INIT2(sym_lstat, "Time::HiRes::lstat"+13, sizeof("lstat")-1, NULL, 0) \ +INIT2(sym_utime, "Time::HiRes::utime"+13, sizeof("utime")-1, NULL, 0) +/* A test inside ../dist/XSLoader/t/XSLoader.t, doesn't allow us to + pass any args from our .pm to .xs. So this idea is rejected: + XSLoader::load( 'Time::HiRes', $XS_VERSION, \@EXPORT_OK ); + if (items != 3 || !SvROK((rv=ST(2))) || (SvTYPE(SvRV(rv))!=SVt_PVAV) + croak_xs_usage(cv, "class, version, export_ok"); */ +#undef INIT2 +#define INIT2(_s, _str, _l, _d, _db) ((_db) ? (_d) : (_str)), + { /* All C strings are shared with EU::PXS's or constant()'s codegen. */ + static const char * const expokpv[] = { + INIT1 + }; +#undef INIT2 +#define INIT2(_s, _str, _l, _d, _db) ((_db) ? ((I8)-((I8)(_l))) : (_l)), + static const I8 expoklen[] = { + INIT1 + }; +#undef INIT2 + char buf [64]; + GV* gv = gv_fetchpvs("Time::HiRes::EXPORT_OK", GV_ADDMULTI, SVt_PVAV); + AV* av = GvAV(gv); + int i = 0; + buf[0] = 'd'; + buf[1] = '_'; + av_extend(av, C_ARRAY_LENGTH(expoklen)); + for(;i < C_ARRAY_LENGTH(expoklen); i++) { + I8 l = expoklen[i]; + const char * pv = expokpv[i]; + if (l < 0) { /* neg val is a "d_" prefixed identifier */ + l = -l; /* +1 for "\0" -2 for "d_" */ + Copy(pv, &buf[2], (l+1)-2, char); + pv = buf; + } +#ifdef av_store_simple + av_store_simple(av, i, newSVpvn_share(pv, l, 0)); +#else + av_store(av, i, newSVpvn_share(pv, l, 0)); +#endif + } + } } #if defined(USE_ITHREADS) && defined(MY_CXT_KEY) @@ -1792,10 +1899,10 @@ PROTOTYPE: $$@ tot++; } } else { - croak("%s unimplemented in this platform", "futimens"); + croak("%s unimplemented in this platform", "d_futimens"+2); } # else /* HAS_FUTIMENS */ - croak("%s unimplemented in this platform", "futimens"); + croak("%s unimplemented in this platform", "d_futimens"+2); # endif /* HAS_FUTIMENS */ } } @@ -1810,10 +1917,10 @@ PROTOTYPE: $$@ tot++; } } else { - croak("%s unimplemented in this platform", "utimensat"); + croak("%s unimplemented in this platform", "d_utimensat"+2); } # else /* HAS_UTIMENSAT */ - croak("%s unimplemented in this platform", "utimensat"); + croak("%s unimplemented in this platform", "d_utimensat"+2); # endif /* HAS_UTIMENSAT */ } } /* while items */ diff --git a/dist/Time-HiRes/Makefile.PL b/dist/Time-HiRes/Makefile.PL index ac56d8df2795..dc7c2f5401db 100644 --- a/dist/Time-HiRes/Makefile.PL +++ b/dist/Time-HiRes/Makefile.PL @@ -1015,6 +1015,14 @@ sub doConstants { push @names, {name => $_, macro => $macro, value => 1, default => ["IV", "0"]}; } + { + #disarm chopping off first or last letter of each C string for memEQs() + my $sub = \&ExtUtils::Constant::Base::memEQ_clause; + *ExtUtils::Constant::Base::memEQ_clause = sub { + $_[1]->{checked_at} = 32; + return $sub->(@_); + }; + } ExtUtils::Constant::WriteConstants( NAME => 'Time::HiRes', NAMES => \@names, diff --git a/dist/Time-HiRes/t/clock.t b/dist/Time-HiRes/t/clock.t index 810d63a272fe..ad5f728970bb 100644 --- a/dist/Time-HiRes/t/clock.t +++ b/dist/Time-HiRes/t/clock.t @@ -1,11 +1,54 @@ use strict; -use Test::More tests => 5; +use Test::More tests => 6; BEGIN { push @INC, '.' } use t::Watchdog; BEGIN { require_ok "Time::HiRes"; } + +my @EXPORT_OK_valid = qw (usleep sleep ualarm alarm gettimeofday time tv_interval + getitimer setitimer nanosleep clock_gettime clock_getres + clock clock_nanosleep + CLOCKS_PER_SEC + CLOCK_BOOTTIME + CLOCK_HIGHRES + CLOCK_MONOTONIC + CLOCK_MONOTONIC_COARSE + CLOCK_MONOTONIC_FAST + CLOCK_MONOTONIC_PRECISE + CLOCK_MONOTONIC_RAW + CLOCK_PROCESS_CPUTIME_ID + CLOCK_PROF + CLOCK_REALTIME + CLOCK_REALTIME_COARSE + CLOCK_REALTIME_FAST + CLOCK_REALTIME_PRECISE + CLOCK_REALTIME_RAW + CLOCK_SECOND + CLOCK_SOFTTIME + CLOCK_THREAD_CPUTIME_ID + CLOCK_TIMEOFDAY + CLOCK_UPTIME + CLOCK_UPTIME_COARSE + CLOCK_UPTIME_FAST + CLOCK_UPTIME_PRECISE + CLOCK_UPTIME_RAW + CLOCK_VIRTUAL + ITIMER_PROF + ITIMER_REAL + ITIMER_REALPROF + ITIMER_VIRTUAL + TIMER_ABSTIME + d_usleep d_ualarm d_gettimeofday d_getitimer d_setitimer + d_nanosleep d_clock_gettime d_clock_getres + d_clock d_clock_nanosleep d_hires_stat + d_futimens d_utimensat d_hires_utime + stat lstat utime + ); + +is_deeply(\@Time::HiRes::EXPORT_OK, \@EXPORT_OK_valid, '@Time::HiRes::EXPORT_OK has correct strings'); + sub has_symbol { my $symbol = shift; eval "use Time::HiRes qw($symbol)"; From 80a5909dc1e19e7878dcc210b4b7a0d39f838b41 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Thu, 3 Jul 2025 03:32:36 -0400 Subject: [PATCH 21/24] Time::HiRes add TMR_newSVi_u_nv_mortal() fast path macros for newer perls -sv_2mortal() does alot of checks internally that we don't need, like test for NULL and IMMs, we are trying to write PUSH_EXTEND_MORTAL__SV_C() without actually using that private API from sv.c, but newSV_type_mortal() if it is available has the same optimizations in it as PUSH_EXTEND_MORTAL__SV_C() --- dist/Time-HiRes/HiRes.xs | 73 ++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 25d9fb1bf44b..e0609ea9d47d 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -109,8 +109,9 @@ Addendum: it's "only" 33% speedup. #define TMR_TARGi(_nsv, i, do_taint) \ STMT_START { \ IV TARGi_iv = i; \ - if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) \ - _nsv = sv_2mortal(newSViv(TARGi_iv)); \ + if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) { \ + TMR_newSViv_mortal(_nsv, TARGi_iv); \ + } \ else { \ _nsv = PAD_SV(PL_op->op_targ); \ if (LIKELY( \ @@ -136,8 +137,9 @@ STMT_START { \ #define TMR_TARGu(_nsv, u, do_taint) \ STMT_START { \ UV TARGu_uv = u; \ - if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) \ - _nsv = sv_2mortal(newSVuv(TARGu_uv)); \ + if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) { \ + TMR_newSVuv_mortal(_nsv, TARGu_uv); \ + } \ else { \ _nsv = PAD_SV(PL_op->op_targ); \ if (LIKELY( \ @@ -164,8 +166,9 @@ STMT_START { \ #define TMR_TARGn(_nsv, n, do_taint) \ STMT_START { \ NV TARGn_nv = n; \ - if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) \ - _nsv = sv_2mortal(newSVnv(TARGn_nv)); \ + if (GIMME_V == G_LIST || !(PL_op->op_private & OPpENTERSUB_HASTARG)) { \ + TMR_newSVnv_mortal(_nsv, TARGn_nv); \ + } \ else { \ _nsv = PAD_SV(PL_op->op_targ); \ if (LIKELY( \ @@ -185,6 +188,52 @@ STMT_START { \ } \ } STMT_END + +/* newSV_type_mortal() is faster than sv_2mortal() */ + +#ifdef newSV_type_mortal + +# define TMR_newSViv_mortal(_nsv, _iv) STMT_START { \ + _nsv = newSV_type_mortal(SVt_IV); \ + SvIOK_on(_nsv); \ + SvIV_set(_nsv, _iv); \ +} STMT_END + +# define TMR_newSVuv_mortal(_nsv, _uv) STMT_START { \ + _nsv = newSV_type_mortal(SVt_IV); \ + SvIOK_on(_nsv); \ + if (_uv <= (UV)IV_MAX) { \ + SvIV_set(_nsv, (IV)_uv); \ + } \ + else { \ + SvIsUV_on(_nsv);\ + SvUV_set(_nsv, _uv); \ + } \ +} STMT_END + +# define TMR_newSVnv_mortal(_nsv, _nv) STMT_START { \ + _nsv = newSV_type_mortal(SVt_NV); \ + SvNV_set(_nsv, _nv); \ + SvNOK_on(_nsv); \ +} STMT_END + +#else + +# define TMR_newSViv_mortal(_nsv, _iv) STMT_START { \ + _nsv = sv_2mortal(newSViv(_iv)); \ +} STMT_END + +# define TMR_newSVuv_mortal(_nsv, _uv) STMT_START { \ + _nsv = sv_2mortal(newSVuv(_uv)); \ +} STMT_END + +# define TMR_newSVnv_mortal(_nsv, _nv) STMT_START { \ + _nsv = sv_2mortal(newSVnv(_nv)); \ +} STMT_END + +#endif + + #define IV_1E6 1000000 #define IV_1E7 10000000 #define IV_1E9 1000000000 @@ -1724,8 +1773,11 @@ gettimeofday() /* copy to registers to prove sv_2mortal/newSViv */ IV sec = Tp.tv_sec; /* can't modify the values */ IV usec = Tp.tv_usec; - PUSHs(sv_2mortal(newSViv(sec))); - PUSHs(sv_2mortal(newSViv(usec))); + SV* rsv; + TMR_newSViv_mortal(rsv, sec); + PUSHs(rsv); + TMR_newSViv_mortal(rsv, usec); + PUSHs(rsv); } else { /* no Perl_leave_adjust_stacks() hazard here, only a PP vs call_sv() hazard */ @@ -1752,8 +1804,9 @@ gettimeofday() else sv_setnv_mg(rsv, TARGn_nv); } - else - rsv = sv_2mortal(newSVnv(TARGn_nv)); + else { + TMR_newSVnv_mortal(rsv, TARGn_nv); + } PUSHs(rsv); } } From 485fa08485e3ddb4af19a03330fd318ec9f8eb4d Mon Sep 17 00:00:00 2001 From: bulk88 Date: Thu, 3 Jul 2025 04:22:58 -0400 Subject: [PATCH 22/24] Time::HiRes Win32, unwrap jump stubs from GetSystemTimeAsFileTime() -if possible, wont work < NT6.0, but if we get the kernelbase fn ptr on a sys with kernelbase, then we were successful at removing 2 stubs inside kernel32.dll and going straight to real impl inside kernelbase.dll. On older OSes they use their real impl inside k32.dll like before, --- dist/Time-HiRes/HiRes.xs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index e0609ea9d47d..8af69cfd4f7b 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -321,12 +321,14 @@ typedef struct { } my_cxt_t; typedef BOOL (WINAPI *pfnQueryPerformanceCounter_T)(LARGE_INTEGER*); +typedef VOID (WINAPI *pfnGetSystemTimeAsFileTime_T) (PFILETIME); static unsigned __int64 tick_frequency = 0; static NV tick_frequency_nv = 0; static unsigned __int64 qpc_res_ns = 0; static unsigned __int64 qpc_res_ns_realtime = 0; static pfnQueryPerformanceCounter_T pfnQueryPerformanceCounter = NULL; +static pfnGetSystemTimeAsFileTime_T pfnGetSystemTimeAsFileTime = NULL; #define S_InterlockedExchange64(_d,_s) \ InterlockedExchange64((LONG64 volatile *)(_d),(LONG64)(_s)) @@ -335,6 +337,8 @@ static pfnQueryPerformanceCounter_T pfnQueryPerformanceCounter = NULL; #undef QueryPerformanceCounter #define QueryPerformanceCounter pfnQueryPerformanceCounter +#undef GetSystemTimeAsFileTime +#define GetSystemTimeAsFileTime pfnGetSystemTimeAsFileTime /* Visual C++ 2013 and older don't have the timespec structure. * Neither do mingw.org compilers with MinGW runtimes older than 3.22. */ @@ -1311,6 +1315,24 @@ BOOT: S_InterlockedExchangePointer(&pfnQueryPerformanceCounter, QPCfn); } } + {/* Remove 2 jump stub funcs between kernel32->kernelbase for perf reasons. + kernelbase.dll is somewhat new to the Win32/NT OS, so keep the fallback. */ + pfnGetSystemTimeAsFileTime_T GSTAFTfn = pfnGetSystemTimeAsFileTime; + if (!GSTAFTfn) { + HMODULE hmod = GetModuleHandleW(L"KERNELBASE.DLL"); + if (hmod) { + GSTAFTfn = (pfnGetSystemTimeAsFileTime_T)GetProcAddress(hmod,"GetSystemTimeAsFileTime"); + if (GSTAFTfn) + goto GSTAFT_done; + } +#undef GetSystemTimeAsFileTime + GSTAFTfn = GetSystemTimeAsFileTime; /* Get the public API fallback sym. */ +#undef GetSystemTimeAsFileTime +#GetSystemTimeAsFileTime pfnGetSystemTimeAsFileTime + GSTAFT_done: + S_InterlockedExchangePointer(&pfnGetSystemTimeAsFileTime, GSTAFTfn); + } + } } #endif #ifdef HAS_GETTIMEOFDAY From abb1e3cc4f8b174ea31cbba287fa240ec2a6b9b6 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Thu, 3 Jul 2025 05:41:37 -0400 Subject: [PATCH 23/24] Time::HiRes TMR_newSVnv_mortal() move SvNV_set() after SvNOK_on(), aliasing -Originally I thought SvANY() aka SvNV_set() needs to be read/written first so CC doesn't think SvNOK_on() somehow rewrote SvANY or SvNVX() but I was wrong, SvNV_set(_nsv, _nv) was breaking up SvNOK_on() and SvTEMP_on() from combining. --- dist/Time-HiRes/HiRes.xs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 8af69cfd4f7b..60847fdb9e34 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -213,8 +213,8 @@ STMT_START { \ # define TMR_newSVnv_mortal(_nsv, _nv) STMT_START { \ _nsv = newSV_type_mortal(SVt_NV); \ - SvNV_set(_nsv, _nv); \ SvNOK_on(_nsv); \ + SvNV_set(_nsv, _nv); \ } STMT_END #else From 941710ae8f5f94d32bade4e7507c5b06d36470c4 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Thu, 3 Jul 2025 08:29:33 -0400 Subject: [PATCH 24/24] Time::HiRes back compat fixes for 5.22 and older, and for C++ --- dist/Time-HiRes/HiRes.xs | 80 ++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/dist/Time-HiRes/HiRes.xs b/dist/Time-HiRes/HiRes.xs index 60847fdb9e34..9a9534cc8423 100644 --- a/dist/Time-HiRes/HiRes.xs +++ b/dist/Time-HiRes/HiRes.xs @@ -74,6 +74,20 @@ # define THR_newSVsv_cow(sv) newSVsv_flags((sv), SV_GMAGIC|SV_NOSTEAL) #endif +/* Added in 5.17.6 in commit 284167a54e2 10/9/2012 5:19:37 AM + Add C define to remove taint support from perl */ +#ifndef TAINT_get +# define TAINT_get (PL_tainted) +#endif + +#ifndef LIKELY +# define LIKELY(x) (x) +#endif + +#ifndef UNLIKELY +# define UNLIKELY(x) (x) +#endif + /* PL_op->op_private & OPpENTERSUB_HASTARG feature was added in d30110745a - Ilya Zakharevich -8/26/1999 11:33:01 PM - 5.5.61 @@ -1228,10 +1242,12 @@ static void S_croak_xs_unimplemented(CV *const cv) { dTHX; + /* added in 5.21.4 commit c5569a55d2 - 8/28/2014 6:56:30 PM - cv_name */ +#ifdef cv_name SV* sv = cv_name(cv, NULL, 0); Perl_croak_nocontext( "%s::%s(): unimplemented in this platform" + (sizeof("%s::")-1), SvPVX(sv)); -#if 0 /* former implementation, retired because of machine code bloat */ +#else char buf[sizeof("CODE(0x%" UVxf ")") + (sizeof(UV)*8)]; const char * pv1; const GV *const gv = CvGV(cv); @@ -1365,26 +1381,26 @@ BOOT: # endif #endif #if defined(HAS_GETITIMER) && defined(HAS_SETITIMER) -# define GETITIMER_SUBSTR "Time::HiRes::getitimer"+13 -# define SETITIMER_SUBSTR "Time::HiRes::setitimer"+13 +# define GETITIMER_SUBSTR NUM2PTR(const char *, "Time::HiRes::getitimer")+13 +# define SETITIMER_SUBSTR NUM2PTR(const char *, "Time::HiRes::setitimer")+13 #else -# define GETITIMER_SUBSTR "d_getitimer"+2 -# define SETITIMER_SUBSTR "d_setitimer"+2 +# define GETITIMER_SUBSTR NUM2PTR(const char *, "d_getitimer")+2 +# define SETITIMER_SUBSTR NUM2PTR(const char *, "d_setitimer")+2 #endif -#define INIT1 INIT2(sym_usleep, "Time::HiRes::usleep"+13, sizeof("usleep")-1, NULL, 0) \ -INIT2(sym_sleep, "Time::HiRes::sleep"+13, sizeof("sleep")-1, NULL, 0) \ -INIT2(sym_ualarm, "Time::HiRes::ualarm"+13, sizeof("ualarm")-1, NULL, 0) \ -INIT2(sym_alarm, "Time::HiRes::alarm"+13, sizeof("alarm")-1, NULL, 0) \ -INIT2(sym_gettimeofday, "Time::HiRes::gettimeofday"+13, sizeof("gettimeofday")-1, NULL, 0) \ -INIT2(sym_time, "Time::HiRes::time"+13, sizeof("time")-1, NULL, 0) \ +#define INIT1 INIT2(sym_usleep, NUM2PTR(const char *, "Time::HiRes::usleep")+13, sizeof("usleep")-1, NULL, 0) \ +INIT2(sym_sleep, NUM2PTR(const char *, "Time::HiRes::sleep")+13, sizeof("sleep")-1, NULL, 0) \ +INIT2(sym_ualarm, NUM2PTR(const char *, "Time::HiRes::ualarm")+13, sizeof("ualarm")-1, NULL, 0) \ +INIT2(sym_alarm, NUM2PTR(const char *, "Time::HiRes::alarm")+13, sizeof("alarm")-1, NULL, 0) \ +INIT2(sym_gettimeofday, NUM2PTR(const char *, "Time::HiRes::gettimeofday")+13, sizeof("gettimeofday")-1, NULL, 0) \ +INIT2(sym_time, NUM2PTR(const char *, "Time::HiRes::time")+13, sizeof("time")-1, NULL, 0) \ INIT2(sym_tv_interval, "tv_interval", sizeof("tv_interval")-1, NULL, 0) \ INIT2(sym_getitimer, GETITIMER_SUBSTR, sizeof("getitimer")-1, NULL, 0) \ INIT2(sym_setitimer, SETITIMER_SUBSTR, sizeof("setitimer")-1, NULL, 0) \ -INIT2(sym_nanosleep, "Time::HiRes::nanosleep"+13, sizeof("nanosleep")-1, NULL, 0) \ -INIT2(sym_clock_gettime, "Time::HiRes::clock_gettime"+13, sizeof("clock_gettime")-1, NULL, 0) \ -INIT2(sym_clock_getres, "Time::HiRes::clock_getres"+13, sizeof("clock_getres")-1, NULL, 0) \ -INIT2(sym_clock, "Time::HiRes::clock"+13, sizeof("clock")-1, NULL, 0) \ -INIT2(sym_clock_nanosleep, "Time::HiRes::clock_nanosleep"+13, sizeof("clock_nanosleep")-1, NULL, 0) \ +INIT2(sym_nanosleep, NUM2PTR(const char *, "Time::HiRes::nanosleep")+13, sizeof("nanosleep")-1, NULL, 0) \ +INIT2(sym_clock_gettime, NUM2PTR(const char *, "Time::HiRes::clock_gettime")+13, sizeof("clock_gettime")-1, NULL, 0) \ +INIT2(sym_clock_getres, NUM2PTR(const char *, "Time::HiRes::clock_getres")+13, sizeof("clock_getres")-1, NULL, 0) \ +INIT2(sym_clock, NUM2PTR(const char *, "Time::HiRes::clock")+13, sizeof("clock")-1, NULL, 0) \ +INIT2(sym_clock_nanosleep, NUM2PTR(const char *, "Time::HiRes::clock_nanosleep")+13, sizeof("clock_nanosleep")-1, NULL, 0) \ INIT2(sym_CLOCKS_PER_SEC, "CLOCKS_PER_SEC", sizeof("CLOCKS_PER_SEC")-1, NULL, 0) \ INIT2(sym_CLOCK_BOOTTIME, "CLOCK_BOOTTIME", sizeof("CLOCK_BOOTTIME")-1, NULL, 0) \ INIT2(sym_CLOCK_HIGHRES, "CLOCK_HIGHRES", sizeof("CLOCK_HIGHRES")-1, NULL, 0) \ @@ -1415,23 +1431,23 @@ INIT2(sym_ITIMER_REAL, "ITIMER_REAL", sizeof("ITIMER_REAL")-1, NULL, 0) \ INIT2(sym_ITIMER_REALPROF, "ITIMER_REALPROF", sizeof("ITIMER_REALPROF")-1, NULL, 0) \ INIT2(sym_ITIMER_VIRTUAL, "ITIMER_VIRTUAL", sizeof("ITIMER_VIRTUAL")-1, NULL, 0) \ INIT2(sym_TIMER_ABSTIME, "TIMER_ABSTIME", sizeof("TIMER_ABSTIME")-1, NULL, 0) \ -INIT2(sym_d_usleep, "d_usleep", sizeof("d_usleep")-1, "Time::HiRes::usleep"+13, 1) \ -INIT2(sym_d_ualarm, "d_ualarm", sizeof("d_ualarm")-1, "Time::HiRes::ualarm"+13, 1) \ -INIT2(sym_d_gettimeofday, "d_gettimeofday", sizeof("d_gettimeofday")-1, "Time::HiRes::gettimeofday"+13, 1) \ +INIT2(sym_d_usleep, "d_usleep", sizeof("d_usleep")-1, NUM2PTR(const char *, "Time::HiRes::usleep")+13, 1) \ +INIT2(sym_d_ualarm, "d_ualarm", sizeof("d_ualarm")-1, NUM2PTR(const char *, "Time::HiRes::ualarm")+13, 1) \ +INIT2(sym_d_gettimeofday, "d_gettimeofday", sizeof("d_gettimeofday")-1, NUM2PTR(const char *, "Time::HiRes::gettimeofday")+13, 1) \ INIT2(sym_d_getitimer, "d_getitimer", sizeof("d_getitimer")-1, GETITIMER_SUBSTR, 1) \ INIT2(sym_d_setitimer, "d_setitimer", sizeof("d_setitimer")-1, SETITIMER_SUBSTR, 1) \ -INIT2(sym_d_nanosleep, "d_nanosleep", sizeof("d_nanosleep")-1, "Time::HiRes::nanosleep"+13, 1) \ -INIT2(sym_d_clock_gettime, "d_clock_gettime", sizeof("d_clock_gettime")-1, "Time::HiRes::clock_gettime"+13, 1) \ -INIT2(sym_d_clock_getres, "d_clock_getres", sizeof("d_clock_getres")-1, "Time::HiRes::clock_getres"+13, 1) \ -INIT2(sym_d_clock, "d_clock", sizeof("d_clock")-1, "Time::HiRes::clock"+13, 1) \ -INIT2(sym_d_clock_nanosleep, "d_clock_nanosleep", sizeof("d_clock_nanosleep")-1, "Time::HiRes::clock_nanosleep"+13, 1) \ +INIT2(sym_d_nanosleep, "d_nanosleep", sizeof("d_nanosleep")-1, NUM2PTR(const char *, "Time::HiRes::nanosleep")+13, 1) \ +INIT2(sym_d_clock_gettime, "d_clock_gettime", sizeof("d_clock_gettime")-1, NUM2PTR(const char *, "Time::HiRes::clock_gettime")+13, 1) \ +INIT2(sym_d_clock_getres, "d_clock_getres", sizeof("d_clock_getres")-1, NUM2PTR(const char *, "Time::HiRes::clock_getres")+13, 1) \ +INIT2(sym_d_clock, "d_clock", sizeof("d_clock")-1, NUM2PTR(const char *, "Time::HiRes::clock")+13, 1) \ +INIT2(sym_d_clock_nanosleep, "d_clock_nanosleep", sizeof("d_clock_nanosleep")-1, NUM2PTR(const char *, "Time::HiRes::clock_nanosleep")+13, 1) \ INIT2(sym_d_hires_stat, "d_hires_stat", sizeof("d_hires_stat")-1, NULL, 0) \ INIT2(sym_d_futimens, "d_futimens", sizeof("d_futimens")-1, NULL, 0) \ INIT2(sym_d_utimensat, "d_utimensat", sizeof("d_utimensat")-1, NULL, 0) \ INIT2(sym_d_hires_utime, "d_hires_utime", sizeof("d_hires_utime")-1, NULL, 0) \ -INIT2(sym_stat, "Time::HiRes::stat"+13, sizeof("stat")-1, NULL, 0) \ -INIT2(sym_lstat, "Time::HiRes::lstat"+13, sizeof("lstat")-1, NULL, 0) \ -INIT2(sym_utime, "Time::HiRes::utime"+13, sizeof("utime")-1, NULL, 0) +INIT2(sym_stat, NUM2PTR(const char *, "Time::HiRes::stat")+13, sizeof("stat")-1, NULL, 0) \ +INIT2(sym_lstat, NUM2PTR(const char *, "Time::HiRes::lstat")+13, sizeof("lstat")-1, NULL, 0) \ +INIT2(sym_utime, NUM2PTR(const char *, "Time::HiRes::utime")+13, sizeof("utime")-1, NULL, 0) /* A test inside ../dist/XSLoader/t/XSLoader.t, doesn't allow us to pass any args from our .pm to .xs. So this idea is rejected: XSLoader::load( 'Time::HiRes', $XS_VERSION, \@EXPORT_OK ); @@ -1444,7 +1460,7 @@ INIT2(sym_utime, "Time::HiRes::utime"+13, sizeof("utime")-1, NULL, 0) INIT1 }; #undef INIT2 -#define INIT2(_s, _str, _l, _d, _db) ((_db) ? ((I8)-((I8)(_l))) : (_l)), +#define INIT2(_s, _str, _l, _d, _db) ((_db) ? NUM2PTR(I8,-NUM2PTR(I8,_l)) : NUM2PTR(I8,_l)), static const I8 expoklen[] = { INIT1 }; @@ -1974,10 +1990,10 @@ PROTOTYPE: $$@ tot++; } } else { - croak("%s unimplemented in this platform", "d_futimens"+2); + croak("%s unimplemented in this platform", NUM2PTR(const char *, "d_futimens")+2); } # else /* HAS_FUTIMENS */ - croak("%s unimplemented in this platform", "d_futimens"+2); + croak("%s unimplemented in this platform", NUM2PTR(const char *, "d_futimens")+2); # endif /* HAS_FUTIMENS */ } } @@ -1992,10 +2008,10 @@ PROTOTYPE: $$@ tot++; } } else { - croak("%s unimplemented in this platform", "d_utimensat"+2); + croak("%s unimplemented in this platform", NUM2PTR(const char *, "d_utimensat")+2); } # else /* HAS_UTIMENSAT */ - croak("%s unimplemented in this platform", "d_utimensat"+2); + croak("%s unimplemented in this platform", NUM2PTR(const char *, "d_utimensat")+2); # endif /* HAS_UTIMENSAT */ } } /* while items */