Implemented the new MulDiv128 stuff on macOS. To make things easier in regards to signedness, this implementation also uses a base, like the Unix one, and the mach_timebase_info() conversion is done in timerMonotonicTime() instead. (Also added the admittedly-redundant-since-the-current(ish)-implementation-cannot-fail error check to mach_timebase_info().) As a result, timerTimeSub() can now be end - start on all platforms. Also found and fixed bugs in the actual muldiv128 algorithm implementation. Next up: Windows.

This commit is contained in:
Pietro Gagliardi 2019-05-05 03:17:11 -04:00
parent 952b36b1c2
commit 288c74b026
4 changed files with 48 additions and 25 deletions

View File

@ -259,14 +259,14 @@ static void int128MulDiv64(timerprivInt128 *x, timerprivInt128 *y, timerprivInt1
numer.low = x64low * y64low; // b * d + numer.low = x64low * y64low; // b * d +
add.neg = 0; add.neg = 0;
add.high = x64high * y64low; // a * d * 2^32 + add.high = x64high * y64low; // a * d * 2^32 +
add.low = add.high & 0xFFFFFFFF00000000; add.low = (add.high & 0xFFFFFFFF) << 32;
add.high >>= 32; add.high >>= 32;
int128UAdd(&numer, &add); int128UAdd(&numer, &add);
add.high = x64low * y64high; // b * c * 2^32 add.high = x64low * y64high; // b * c * 2^32
add.low = add.high & 0xFFFFFFFF00000000; add.low = (add.high & 0xFFFFFFFF) << 32;
add.high >>= 32; add.high >>= 32;
int128UAdd(&numer, &add); int128UAdd(&numer, &add);
// I did type this all by hand, btw; the idea does come from Apple's implementation, though they explain it a bit more obtusely, and the odd behavior with anding high and shifting it right is to avoid looking like I directly copied their code which does the opposite // I did type this all by hand, btw; the idea does come from Apple's implementation, though they explain it a bit more obtusely, and the odd behavior with anding high into low is to avoid looking like I directly copied their code which does the opposite
// and now long-divide // and now long-divide
// Apple's implementation uses NewtonRaphson division using doubles to store 1/z but I'd rather go with "slow but guaranteed to be accurate" // Apple's implementation uses NewtonRaphson division using doubles to store 1/z but I'd rather go with "slow but guaranteed to be accurate"
@ -286,7 +286,7 @@ static void int128MulDiv64(timerprivInt128 *x, timerprivInt128 *y, timerprivInt1
} }
} }
void timerprivMulDiv64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot) void timerprivMulDivInt64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot)
{ {
timerprivInt128 a, b, c; timerprivInt128 a, b, c;
@ -296,7 +296,7 @@ void timerprivMulDiv64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot)
int128MulDiv64(&a, &b, &c, quot); int128MulDiv64(&a, &b, &c, quot);
} }
void timerprivMulDivU64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot) void timerprivMulDivUint64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot)
{ {
timerprivInt128 a, b, c; timerprivInt128 a, b, c;

View File

@ -5,6 +5,8 @@
typedef int64_t timerDuration; typedef int64_t timerDuration;
typedef int64_t timerTime; typedef int64_t timerTime;
#define timerTimeMax ((timerTime) INT64_MAX)
#define timerNanosecond ((timerDuration) 1) #define timerNanosecond ((timerDuration) 1)
#define timerMicrosecond ((timerDuration) 1000) #define timerMicrosecond ((timerDuration) 1000)
#define timerMillisecond ((timerDuration) 1000000) #define timerMillisecond ((timerDuration) 1000000)

View File

@ -15,24 +15,50 @@
#include <mach/mach_time.h> #include <mach/mach_time.h>
#endif #endif
#include "timer.h" #include "timer.h"
#include "timerpriv.h"
static void mustpthread_once(pthread_once_t *once, void (*init)(void))
{
int err;
err = pthread_once(once, init);
if (err != 0) {
fprintf(stderr, "*** internal error in timerMonotonicNow(): pthread_once() failed: %s (%d)\n", strerror(err), err);
abort();
}
}
#ifdef __APPLE__ #ifdef __APPLE__
timerTime timerMonotonicNow(void) static uint64_t base;
static mach_timebase_info_data_t mt;
static pthread_once_t baseOnce = PTHREAD_ONCE_INIT;
static void baseInit(void)
{ {
return (timerTime) mach_absolute_time(); kern_return_t err;
base = mach_absolute_time();
err = mach_timebase_info(&mt);
if (err != KERN_SUCCESS) {
fprintf(stderr, "*** internal error in timerMonotonicNow(): mach_timebase_info() failed: kern_return_t %d\n", err);
abort();
}
} }
timerDuration timerTimeSub(timerTime end, timerTime start) timerTime timerMonotonicNow(void)
{ {
mach_timebase_info_data_t mt; uint64_t t;
timerTime c; timerprivInt128 quot;
timerDuration ret;
mach_timebase_info(&mt); mustpthread_once(&baseOnce, baseInit);
c = end - start; t = mach_absolute_time() - base;
ret = ((timerDuration) c) * mt.numer / mt.denom; timerprivMulDivUint64(t, mt.numer, mt.denom, &quot);
return ret; // on overflow, return the maximum possible timerTime; this is inspired by what Go does
if (quot.high == 0)
if (quot.low <= ((uint64_t) timerTimeMax))
return (timerTime) (quot.low);
return timerTimeMax;
} }
#else #else
@ -61,13 +87,8 @@ timerTime timerMonotonicNow(void)
{ {
struct timespec ts; struct timespec ts;
timerTime ret; timerTime ret;
int err;
err = pthread_once(&baseOnce, baseInit); mustpthread_once(&baseOnce, baseInit);
if (err != 0) {
fprintf(stderr, "*** internal error in timerMonotonicNow(): pthread_once() failed: %s (%d)\n", strerror(err), err);
abort();
}
mustclock_gettime(CLOCK_MONOTONIC, &ts); mustclock_gettime(CLOCK_MONOTONIC, &ts);
ts.tv_sec -= base.tv_sec; ts.tv_sec -= base.tv_sec;
ts.tv_nsec -= base.tv_nsec; ts.tv_nsec -= base.tv_nsec;
@ -80,13 +101,13 @@ timerTime timerMonotonicNow(void)
return ret; return ret;
} }
#endif
timerDuration timerTimeSub(timerTime end, timerTime start) timerDuration timerTimeSub(timerTime end, timerTime start)
{ {
return end - start; return end - start;
} }
#endif
struct timeoutParams { struct timeoutParams {
jmp_buf retpos; jmp_buf retpos;
struct itimerval prevDuration; struct itimerval prevDuration;

View File

@ -8,5 +8,5 @@ struct timerprivInt128 {
uint64_t low; uint64_t low;
}; };
extern void timerprivMulDiv64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot); extern void timerprivMulDivInt64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot);
extern void timerprivMulDivU64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot); extern void timerprivMulDivUint64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot);