michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* PR time code. */ michael@0: michael@0: #include "prmjtime.h" michael@0: michael@0: #include "mozilla/MathAlgorithms.h" michael@0: michael@0: #ifdef SOLARIS michael@0: #define _REENTRANT 1 michael@0: #endif michael@0: #include michael@0: #include michael@0: michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: michael@0: #define PRMJ_DO_MILLISECONDS 1 michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: #include michael@0: #include /* for timeBegin/EndPeriod */ michael@0: /* VC++ 8.0 or later */ michael@0: #if _MSC_VER >= 1400 michael@0: #define NS_HAVE_INVALID_PARAMETER_HANDLER 1 michael@0: #endif michael@0: #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER michael@0: #include /* for _CrtSetReportMode */ michael@0: #include /* for _set_invalid_parameter_handler */ michael@0: #endif michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: #include "prinit.h" michael@0: #endif michael@0: michael@0: #endif michael@0: michael@0: #ifdef XP_UNIX michael@0: michael@0: #ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ michael@0: extern int gettimeofday(struct timeval *tv); michael@0: #endif michael@0: michael@0: #include michael@0: michael@0: #endif /* XP_UNIX */ michael@0: michael@0: #define PRMJ_YEAR_DAYS 365L michael@0: #define PRMJ_FOUR_YEARS_DAYS (4 * PRMJ_YEAR_DAYS + 1) michael@0: #define PRMJ_CENTURY_DAYS (25 * PRMJ_FOUR_YEARS_DAYS - 1) michael@0: #define PRMJ_FOUR_CENTURIES_DAYS (4 * PRMJ_CENTURY_DAYS + 1) michael@0: #define PRMJ_HOUR_SECONDS 3600L michael@0: #define PRMJ_DAY_SECONDS (24L * PRMJ_HOUR_SECONDS) michael@0: #define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * PRMJ_YEAR_DAYS) michael@0: #define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */ michael@0: michael@0: /* Constants for GMT offset from 1970 */ michael@0: #define G1970GMTMICROHI 0x00dcdcad /* micro secs to 1970 hi */ michael@0: #define G1970GMTMICROLOW 0x8b3fa000 /* micro secs to 1970 low */ michael@0: michael@0: #define G2037GMTMICROHI 0x00e45fab /* micro secs to 2037 high */ michael@0: #define G2037GMTMICROLOW 0x7a238000 /* micro secs to 2037 low */ michael@0: michael@0: #if defined(XP_WIN) michael@0: michael@0: static const int64_t win2un = 0x19DB1DED53E8000; michael@0: michael@0: #define FILETIME2INT64(ft) (((int64_t)ft.dwHighDateTime) << 32LL | (int64_t)ft.dwLowDateTime) michael@0: michael@0: typedef struct CalibrationData { michael@0: long double freq; /* The performance counter frequency */ michael@0: long double offset; /* The low res 'epoch' */ michael@0: long double timer_offset; /* The high res 'epoch' */ michael@0: michael@0: /* The last high res time that we returned since recalibrating */ michael@0: int64_t last; michael@0: michael@0: bool calibrated; michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: CRITICAL_SECTION data_lock; michael@0: CRITICAL_SECTION calibration_lock; michael@0: #endif michael@0: } CalibrationData; michael@0: michael@0: static CalibrationData calibration = { 0 }; michael@0: michael@0: static void michael@0: NowCalibrate() michael@0: { michael@0: FILETIME ft, ftStart; michael@0: LARGE_INTEGER liFreq, now; michael@0: michael@0: if (calibration.freq == 0.0) { michael@0: if(!QueryPerformanceFrequency(&liFreq)) { michael@0: /* High-performance timer is unavailable */ michael@0: calibration.freq = -1.0; michael@0: } else { michael@0: calibration.freq = (long double) liFreq.QuadPart; michael@0: } michael@0: } michael@0: if (calibration.freq > 0.0) { michael@0: int64_t calibrationDelta = 0; michael@0: michael@0: /* By wrapping a timeBegin/EndPeriod pair of calls around this loop, michael@0: the loop seems to take much less time (1 ms vs 15ms) on Vista. */ michael@0: timeBeginPeriod(1); michael@0: GetSystemTimeAsFileTime(&ftStart); michael@0: do { michael@0: GetSystemTimeAsFileTime(&ft); michael@0: } while (memcmp(&ftStart,&ft, sizeof(ft)) == 0); michael@0: timeEndPeriod(1); michael@0: michael@0: /* michael@0: calibrationDelta = (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10; michael@0: fprintf(stderr, "Calibration delta was %I64d us\n", calibrationDelta); michael@0: */ michael@0: michael@0: QueryPerformanceCounter(&now); michael@0: michael@0: calibration.offset = (long double) FILETIME2INT64(ft); michael@0: calibration.timer_offset = (long double) now.QuadPart; michael@0: michael@0: /* The windows epoch is around 1600. The unix epoch is around michael@0: 1970. win2un is the difference (in windows time units which michael@0: are 10 times more highres than the JS time unit) */ michael@0: calibration.offset -= win2un; michael@0: calibration.offset *= 0.1; michael@0: calibration.last = 0; michael@0: michael@0: calibration.calibrated = true; michael@0: } michael@0: } michael@0: michael@0: #define CALIBRATIONLOCK_SPINCOUNT 0 michael@0: #define DATALOCK_SPINCOUNT 4096 michael@0: #define LASTLOCK_SPINCOUNT 4096 michael@0: michael@0: #ifdef JS_THREADSAFE michael@0: static PRStatus michael@0: NowInit(void) michael@0: { michael@0: memset(&calibration, 0, sizeof(calibration)); michael@0: NowCalibrate(); michael@0: InitializeCriticalSectionAndSpinCount(&calibration.calibration_lock, CALIBRATIONLOCK_SPINCOUNT); michael@0: InitializeCriticalSectionAndSpinCount(&calibration.data_lock, DATALOCK_SPINCOUNT); michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: void michael@0: PRMJ_NowShutdown() michael@0: { michael@0: DeleteCriticalSection(&calibration.calibration_lock); michael@0: DeleteCriticalSection(&calibration.data_lock); michael@0: } michael@0: michael@0: #define MUTEX_LOCK(m) EnterCriticalSection(m) michael@0: #define MUTEX_TRYLOCK(m) TryEnterCriticalSection(m) michael@0: #define MUTEX_UNLOCK(m) LeaveCriticalSection(m) michael@0: #define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m),(c)) michael@0: michael@0: static PRCallOnceType calibrationOnce = { 0 }; michael@0: michael@0: #else michael@0: michael@0: #define MUTEX_LOCK(m) michael@0: #define MUTEX_TRYLOCK(m) 1 michael@0: #define MUTEX_UNLOCK(m) michael@0: #define MUTEX_SETSPINCOUNT(m, c) michael@0: michael@0: #endif michael@0: michael@0: #endif /* XP_WIN */ michael@0: michael@0: michael@0: #if defined(XP_UNIX) michael@0: int64_t michael@0: PRMJ_Now(void) michael@0: { michael@0: struct timeval tv; michael@0: michael@0: #ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris */ michael@0: gettimeofday(&tv); michael@0: #else michael@0: gettimeofday(&tv, 0); michael@0: #endif /* _SVID_GETTOD */ michael@0: michael@0: return int64_t(tv.tv_sec) * PRMJ_USEC_PER_SEC + int64_t(tv.tv_usec); michael@0: } michael@0: michael@0: #else michael@0: /* michael@0: michael@0: Win32 python-esque pseudo code michael@0: Please see bug 363258 for why the win32 timing code is so complex. michael@0: michael@0: calibration mutex : Win32CriticalSection(spincount=0) michael@0: data mutex : Win32CriticalSection(spincount=4096) michael@0: michael@0: def NowInit(): michael@0: init mutexes michael@0: PRMJ_NowCalibration() michael@0: michael@0: def NowCalibration(): michael@0: expensive up-to-15ms call michael@0: michael@0: def PRMJ_Now(): michael@0: returnedTime = 0 michael@0: needCalibration = False michael@0: cachedOffset = 0.0 michael@0: calibrated = False michael@0: PR_CallOnce(PRMJ_NowInit) michael@0: do michael@0: if not global.calibrated or needCalibration: michael@0: acquire calibration mutex michael@0: acquire data mutex michael@0: michael@0: // Only recalibrate if someone didn't already michael@0: if cachedOffset == calibration.offset: michael@0: // Have all waiting threads immediately wait michael@0: set data mutex spin count = 0 michael@0: PRMJ_NowCalibrate() michael@0: calibrated = 1 michael@0: michael@0: set data mutex spin count = default michael@0: release data mutex michael@0: release calibration mutex michael@0: michael@0: calculate lowres time michael@0: michael@0: if highres timer available: michael@0: acquire data mutex michael@0: calculate highres time michael@0: cachedOffset = calibration.offset michael@0: highres time = calibration.last = max(highres time, calibration.last) michael@0: release data mutex michael@0: michael@0: get kernel tick interval michael@0: michael@0: if abs(highres - lowres) < kernel tick: michael@0: returnedTime = highres time michael@0: needCalibration = False michael@0: else: michael@0: if calibrated: michael@0: returnedTime = lowres michael@0: needCalibration = False michael@0: else: michael@0: needCalibration = True michael@0: else: michael@0: returnedTime = lowres michael@0: while needCalibration michael@0: michael@0: */ michael@0: michael@0: int64_t michael@0: PRMJ_Now(void) michael@0: { michael@0: static int nCalls = 0; michael@0: long double lowresTime, highresTimerValue; michael@0: FILETIME ft; michael@0: LARGE_INTEGER now; michael@0: bool calibrated = false; michael@0: bool needsCalibration = false; michael@0: int64_t returnedTime; michael@0: long double cachedOffset = 0.0; michael@0: michael@0: /* For non threadsafe platforms, NowInit is not necessary */ michael@0: #ifdef JS_THREADSAFE michael@0: PR_CallOnce(&calibrationOnce, NowInit); michael@0: #endif michael@0: do { michael@0: if (!calibration.calibrated || needsCalibration) { michael@0: MUTEX_LOCK(&calibration.calibration_lock); michael@0: MUTEX_LOCK(&calibration.data_lock); michael@0: michael@0: /* Recalibrate only if no one else did before us */ michael@0: if(calibration.offset == cachedOffset) { michael@0: /* Since calibration can take a while, make any other michael@0: threads immediately wait */ michael@0: MUTEX_SETSPINCOUNT(&calibration.data_lock, 0); michael@0: michael@0: NowCalibrate(); michael@0: michael@0: calibrated = true; michael@0: michael@0: /* Restore spin count */ michael@0: MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT); michael@0: } michael@0: MUTEX_UNLOCK(&calibration.data_lock); michael@0: MUTEX_UNLOCK(&calibration.calibration_lock); michael@0: } michael@0: michael@0: michael@0: /* Calculate a low resolution time */ michael@0: GetSystemTimeAsFileTime(&ft); michael@0: lowresTime = 0.1*(long double)(FILETIME2INT64(ft) - win2un); michael@0: michael@0: if (calibration.freq > 0.0) { michael@0: long double highresTime, diff; michael@0: michael@0: DWORD timeAdjustment, timeIncrement; michael@0: BOOL timeAdjustmentDisabled; michael@0: michael@0: /* Default to 15.625 ms if the syscall fails */ michael@0: long double skewThreshold = 15625.25; michael@0: /* Grab high resolution time */ michael@0: QueryPerformanceCounter(&now); michael@0: highresTimerValue = (long double)now.QuadPart; michael@0: michael@0: MUTEX_LOCK(&calibration.data_lock); michael@0: highresTime = calibration.offset + PRMJ_USEC_PER_SEC* michael@0: (highresTimerValue-calibration.timer_offset)/calibration.freq; michael@0: cachedOffset = calibration.offset; michael@0: michael@0: /* On some dual processor/core systems, we might get an earlier time michael@0: so we cache the last time that we returned */ michael@0: calibration.last = js::Max(calibration.last, int64_t(highresTime)); michael@0: returnedTime = calibration.last; michael@0: MUTEX_UNLOCK(&calibration.data_lock); michael@0: michael@0: /* Rather than assume the NT kernel ticks every 15.6ms, ask it */ michael@0: if (GetSystemTimeAdjustment(&timeAdjustment, michael@0: &timeIncrement, michael@0: &timeAdjustmentDisabled)) { michael@0: if (timeAdjustmentDisabled) { michael@0: /* timeAdjustment is in units of 100ns */ michael@0: skewThreshold = timeAdjustment/10.0; michael@0: } else { michael@0: /* timeIncrement is in units of 100ns */ michael@0: skewThreshold = timeIncrement/10.0; michael@0: } michael@0: } michael@0: michael@0: /* Check for clock skew */ michael@0: diff = lowresTime - highresTime; michael@0: michael@0: /* For some reason that I have not determined, the skew can be michael@0: up to twice a kernel tick. This does not seem to happen by michael@0: itself, but I have only seen it triggered by another program michael@0: doing some kind of file I/O. The symptoms are a negative diff michael@0: followed by an equally large positive diff. */ michael@0: if (mozilla::Abs(diff) > 2 * skewThreshold) { michael@0: /*fprintf(stderr,"Clock skew detected (diff = %f)!\n", diff);*/ michael@0: michael@0: if (calibrated) { michael@0: /* If we already calibrated once this instance, and the michael@0: clock is still skewed, then either the processor(s) are michael@0: wildly changing clockspeed or the system is so busy that michael@0: we get switched out for long periods of time. In either michael@0: case, it would be infeasible to make use of high michael@0: resolution results for anything, so let's resort to old michael@0: behavior for this call. It's possible that in the michael@0: future, the user will want the high resolution timer, so michael@0: we don't disable it entirely. */ michael@0: returnedTime = int64_t(lowresTime); michael@0: needsCalibration = false; michael@0: } else { michael@0: /* It is possible that when we recalibrate, we will return a michael@0: value less than what we have returned before; this is michael@0: unavoidable. We cannot tell the different between a michael@0: faulty QueryPerformanceCounter implementation and user michael@0: changes to the operating system time. Since we must michael@0: respect user changes to the operating system time, we michael@0: cannot maintain the invariant that Date.now() never michael@0: decreases; the old implementation has this behavior as michael@0: well. */ michael@0: needsCalibration = true; michael@0: } michael@0: } else { michael@0: /* No detectable clock skew */ michael@0: returnedTime = int64_t(highresTime); michael@0: needsCalibration = false; michael@0: } michael@0: } else { michael@0: /* No high resolution timer is available, so fall back */ michael@0: returnedTime = int64_t(lowresTime); michael@0: } michael@0: } while (needsCalibration); michael@0: michael@0: return returnedTime; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER michael@0: static void michael@0: PRMJ_InvalidParameterHandler(const wchar_t *expression, michael@0: const wchar_t *function, michael@0: const wchar_t *file, michael@0: unsigned int line, michael@0: uintptr_t pReserved) michael@0: { michael@0: /* empty */ michael@0: } michael@0: #endif michael@0: michael@0: /* Format a time value into a buffer. Same semantics as strftime() */ michael@0: size_t michael@0: PRMJ_FormatTime(char *buf, int buflen, const char *fmt, PRMJTime *prtm) michael@0: { michael@0: size_t result = 0; michael@0: #if defined(XP_UNIX) || defined(XP_WIN) michael@0: struct tm a; michael@0: int fake_tm_year = 0; michael@0: #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER michael@0: _invalid_parameter_handler oldHandler; michael@0: int oldReportMode; michael@0: #endif michael@0: michael@0: memset(&a, 0, sizeof(struct tm)); michael@0: michael@0: a.tm_sec = prtm->tm_sec; michael@0: a.tm_min = prtm->tm_min; michael@0: a.tm_hour = prtm->tm_hour; michael@0: a.tm_mday = prtm->tm_mday; michael@0: a.tm_mon = prtm->tm_mon; michael@0: a.tm_wday = prtm->tm_wday; michael@0: michael@0: /* michael@0: * On systems where |struct tm| has members tm_gmtoff and tm_zone, we michael@0: * must fill in those values, or else strftime will return wrong results michael@0: * (e.g., bug 511726, bug 554338). michael@0: */ michael@0: #if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF) michael@0: { michael@0: /* michael@0: * Fill out |td| to the time represented by |prtm|, leaving the michael@0: * timezone fields zeroed out. localtime_r will then fill in the michael@0: * timezone fields for that local time according to the system's michael@0: * timezone parameters. michael@0: */ michael@0: struct tm td; michael@0: memset(&td, 0, sizeof(td)); michael@0: td.tm_sec = prtm->tm_sec; michael@0: td.tm_min = prtm->tm_min; michael@0: td.tm_hour = prtm->tm_hour; michael@0: td.tm_mday = prtm->tm_mday; michael@0: td.tm_mon = prtm->tm_mon; michael@0: td.tm_wday = prtm->tm_wday; michael@0: td.tm_year = prtm->tm_year - 1900; michael@0: td.tm_yday = prtm->tm_yday; michael@0: td.tm_isdst = prtm->tm_isdst; michael@0: time_t t = mktime(&td); michael@0: localtime_r(&t, &td); michael@0: michael@0: a.tm_gmtoff = td.tm_gmtoff; michael@0: a.tm_zone = td.tm_zone; michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * Years before 1900 and after 9999 cause strftime() to abort on Windows. michael@0: * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then michael@0: * replace matching substrings in the strftime() result with the real year. michael@0: * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit michael@0: * year formats (%y) work correctly (since we won't find the fake year michael@0: * in that case). michael@0: * e.g. new Date(1873, 0).toLocaleFormat('%Y %y') => "1873 73" michael@0: * See bug 327869. michael@0: */ michael@0: #define FAKE_YEAR_BASE 9900 michael@0: if (prtm->tm_year < 1900 || prtm->tm_year > 9999) { michael@0: fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100; michael@0: a.tm_year = fake_tm_year - 1900; michael@0: } michael@0: else { michael@0: a.tm_year = prtm->tm_year - 1900; michael@0: } michael@0: a.tm_yday = prtm->tm_yday; michael@0: a.tm_isdst = prtm->tm_isdst; michael@0: michael@0: /* michael@0: * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff michael@0: * are null. This doesn't quite work, though - the timezone is off by michael@0: * tzoff + dst. (And mktime seems to return -1 for the exact dst michael@0: * changeover time.) michael@0: */ michael@0: michael@0: #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER michael@0: oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler); michael@0: oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0); michael@0: #endif michael@0: michael@0: result = strftime(buf, buflen, fmt, &a); michael@0: michael@0: #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER michael@0: _set_invalid_parameter_handler(oldHandler); michael@0: _CrtSetReportMode(_CRT_ASSERT, oldReportMode); michael@0: #endif michael@0: michael@0: if (fake_tm_year && result) { michael@0: char real_year[16]; michael@0: char fake_year[16]; michael@0: size_t real_year_len; michael@0: size_t fake_year_len; michael@0: char* p; michael@0: michael@0: sprintf(real_year, "%d", prtm->tm_year); michael@0: real_year_len = strlen(real_year); michael@0: sprintf(fake_year, "%d", fake_tm_year); michael@0: fake_year_len = strlen(fake_year); michael@0: michael@0: /* Replace the fake year in the result with the real year. */ michael@0: for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) { michael@0: size_t new_result = result + real_year_len - fake_year_len; michael@0: if ((int)new_result >= buflen) { michael@0: return 0; michael@0: } michael@0: memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len)); michael@0: memcpy(p, real_year, real_year_len); michael@0: result = new_result; michael@0: *(buf + result) = '\0'; michael@0: } michael@0: } michael@0: #endif michael@0: return result; michael@0: }