1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/prmjtime.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,521 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* PR time code. */ 1.11 + 1.12 +#include "prmjtime.h" 1.13 + 1.14 +#include "mozilla/MathAlgorithms.h" 1.15 + 1.16 +#ifdef SOLARIS 1.17 +#define _REENTRANT 1 1.18 +#endif 1.19 +#include <string.h> 1.20 +#include <time.h> 1.21 + 1.22 +#include "jstypes.h" 1.23 +#include "jsutil.h" 1.24 + 1.25 +#define PRMJ_DO_MILLISECONDS 1 1.26 + 1.27 +#ifdef XP_WIN 1.28 +#include <windef.h> 1.29 +#include <winbase.h> 1.30 +#include <mmsystem.h> /* for timeBegin/EndPeriod */ 1.31 +/* VC++ 8.0 or later */ 1.32 +#if _MSC_VER >= 1400 1.33 +#define NS_HAVE_INVALID_PARAMETER_HANDLER 1 1.34 +#endif 1.35 +#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER 1.36 +#include <crtdbg.h> /* for _CrtSetReportMode */ 1.37 +#include <stdlib.h> /* for _set_invalid_parameter_handler */ 1.38 +#endif 1.39 + 1.40 +#ifdef JS_THREADSAFE 1.41 +#include "prinit.h" 1.42 +#endif 1.43 + 1.44 +#endif 1.45 + 1.46 +#ifdef XP_UNIX 1.47 + 1.48 +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */ 1.49 +extern int gettimeofday(struct timeval *tv); 1.50 +#endif 1.51 + 1.52 +#include <sys/time.h> 1.53 + 1.54 +#endif /* XP_UNIX */ 1.55 + 1.56 +#define PRMJ_YEAR_DAYS 365L 1.57 +#define PRMJ_FOUR_YEARS_DAYS (4 * PRMJ_YEAR_DAYS + 1) 1.58 +#define PRMJ_CENTURY_DAYS (25 * PRMJ_FOUR_YEARS_DAYS - 1) 1.59 +#define PRMJ_FOUR_CENTURIES_DAYS (4 * PRMJ_CENTURY_DAYS + 1) 1.60 +#define PRMJ_HOUR_SECONDS 3600L 1.61 +#define PRMJ_DAY_SECONDS (24L * PRMJ_HOUR_SECONDS) 1.62 +#define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * PRMJ_YEAR_DAYS) 1.63 +#define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */ 1.64 + 1.65 +/* Constants for GMT offset from 1970 */ 1.66 +#define G1970GMTMICROHI 0x00dcdcad /* micro secs to 1970 hi */ 1.67 +#define G1970GMTMICROLOW 0x8b3fa000 /* micro secs to 1970 low */ 1.68 + 1.69 +#define G2037GMTMICROHI 0x00e45fab /* micro secs to 2037 high */ 1.70 +#define G2037GMTMICROLOW 0x7a238000 /* micro secs to 2037 low */ 1.71 + 1.72 +#if defined(XP_WIN) 1.73 + 1.74 +static const int64_t win2un = 0x19DB1DED53E8000; 1.75 + 1.76 +#define FILETIME2INT64(ft) (((int64_t)ft.dwHighDateTime) << 32LL | (int64_t)ft.dwLowDateTime) 1.77 + 1.78 +typedef struct CalibrationData { 1.79 + long double freq; /* The performance counter frequency */ 1.80 + long double offset; /* The low res 'epoch' */ 1.81 + long double timer_offset; /* The high res 'epoch' */ 1.82 + 1.83 + /* The last high res time that we returned since recalibrating */ 1.84 + int64_t last; 1.85 + 1.86 + bool calibrated; 1.87 + 1.88 +#ifdef JS_THREADSAFE 1.89 + CRITICAL_SECTION data_lock; 1.90 + CRITICAL_SECTION calibration_lock; 1.91 +#endif 1.92 +} CalibrationData; 1.93 + 1.94 +static CalibrationData calibration = { 0 }; 1.95 + 1.96 +static void 1.97 +NowCalibrate() 1.98 +{ 1.99 + FILETIME ft, ftStart; 1.100 + LARGE_INTEGER liFreq, now; 1.101 + 1.102 + if (calibration.freq == 0.0) { 1.103 + if(!QueryPerformanceFrequency(&liFreq)) { 1.104 + /* High-performance timer is unavailable */ 1.105 + calibration.freq = -1.0; 1.106 + } else { 1.107 + calibration.freq = (long double) liFreq.QuadPart; 1.108 + } 1.109 + } 1.110 + if (calibration.freq > 0.0) { 1.111 + int64_t calibrationDelta = 0; 1.112 + 1.113 + /* By wrapping a timeBegin/EndPeriod pair of calls around this loop, 1.114 + the loop seems to take much less time (1 ms vs 15ms) on Vista. */ 1.115 + timeBeginPeriod(1); 1.116 + GetSystemTimeAsFileTime(&ftStart); 1.117 + do { 1.118 + GetSystemTimeAsFileTime(&ft); 1.119 + } while (memcmp(&ftStart,&ft, sizeof(ft)) == 0); 1.120 + timeEndPeriod(1); 1.121 + 1.122 + /* 1.123 + calibrationDelta = (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10; 1.124 + fprintf(stderr, "Calibration delta was %I64d us\n", calibrationDelta); 1.125 + */ 1.126 + 1.127 + QueryPerformanceCounter(&now); 1.128 + 1.129 + calibration.offset = (long double) FILETIME2INT64(ft); 1.130 + calibration.timer_offset = (long double) now.QuadPart; 1.131 + 1.132 + /* The windows epoch is around 1600. The unix epoch is around 1.133 + 1970. win2un is the difference (in windows time units which 1.134 + are 10 times more highres than the JS time unit) */ 1.135 + calibration.offset -= win2un; 1.136 + calibration.offset *= 0.1; 1.137 + calibration.last = 0; 1.138 + 1.139 + calibration.calibrated = true; 1.140 + } 1.141 +} 1.142 + 1.143 +#define CALIBRATIONLOCK_SPINCOUNT 0 1.144 +#define DATALOCK_SPINCOUNT 4096 1.145 +#define LASTLOCK_SPINCOUNT 4096 1.146 + 1.147 +#ifdef JS_THREADSAFE 1.148 +static PRStatus 1.149 +NowInit(void) 1.150 +{ 1.151 + memset(&calibration, 0, sizeof(calibration)); 1.152 + NowCalibrate(); 1.153 + InitializeCriticalSectionAndSpinCount(&calibration.calibration_lock, CALIBRATIONLOCK_SPINCOUNT); 1.154 + InitializeCriticalSectionAndSpinCount(&calibration.data_lock, DATALOCK_SPINCOUNT); 1.155 + return PR_SUCCESS; 1.156 +} 1.157 + 1.158 +void 1.159 +PRMJ_NowShutdown() 1.160 +{ 1.161 + DeleteCriticalSection(&calibration.calibration_lock); 1.162 + DeleteCriticalSection(&calibration.data_lock); 1.163 +} 1.164 + 1.165 +#define MUTEX_LOCK(m) EnterCriticalSection(m) 1.166 +#define MUTEX_TRYLOCK(m) TryEnterCriticalSection(m) 1.167 +#define MUTEX_UNLOCK(m) LeaveCriticalSection(m) 1.168 +#define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m),(c)) 1.169 + 1.170 +static PRCallOnceType calibrationOnce = { 0 }; 1.171 + 1.172 +#else 1.173 + 1.174 +#define MUTEX_LOCK(m) 1.175 +#define MUTEX_TRYLOCK(m) 1 1.176 +#define MUTEX_UNLOCK(m) 1.177 +#define MUTEX_SETSPINCOUNT(m, c) 1.178 + 1.179 +#endif 1.180 + 1.181 +#endif /* XP_WIN */ 1.182 + 1.183 + 1.184 +#if defined(XP_UNIX) 1.185 +int64_t 1.186 +PRMJ_Now(void) 1.187 +{ 1.188 + struct timeval tv; 1.189 + 1.190 +#ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */ 1.191 + gettimeofday(&tv); 1.192 +#else 1.193 + gettimeofday(&tv, 0); 1.194 +#endif /* _SVID_GETTOD */ 1.195 + 1.196 + return int64_t(tv.tv_sec) * PRMJ_USEC_PER_SEC + int64_t(tv.tv_usec); 1.197 +} 1.198 + 1.199 +#else 1.200 +/* 1.201 + 1.202 +Win32 python-esque pseudo code 1.203 +Please see bug 363258 for why the win32 timing code is so complex. 1.204 + 1.205 +calibration mutex : Win32CriticalSection(spincount=0) 1.206 +data mutex : Win32CriticalSection(spincount=4096) 1.207 + 1.208 +def NowInit(): 1.209 + init mutexes 1.210 + PRMJ_NowCalibration() 1.211 + 1.212 +def NowCalibration(): 1.213 + expensive up-to-15ms call 1.214 + 1.215 +def PRMJ_Now(): 1.216 + returnedTime = 0 1.217 + needCalibration = False 1.218 + cachedOffset = 0.0 1.219 + calibrated = False 1.220 + PR_CallOnce(PRMJ_NowInit) 1.221 + do 1.222 + if not global.calibrated or needCalibration: 1.223 + acquire calibration mutex 1.224 + acquire data mutex 1.225 + 1.226 + // Only recalibrate if someone didn't already 1.227 + if cachedOffset == calibration.offset: 1.228 + // Have all waiting threads immediately wait 1.229 + set data mutex spin count = 0 1.230 + PRMJ_NowCalibrate() 1.231 + calibrated = 1 1.232 + 1.233 + set data mutex spin count = default 1.234 + release data mutex 1.235 + release calibration mutex 1.236 + 1.237 + calculate lowres time 1.238 + 1.239 + if highres timer available: 1.240 + acquire data mutex 1.241 + calculate highres time 1.242 + cachedOffset = calibration.offset 1.243 + highres time = calibration.last = max(highres time, calibration.last) 1.244 + release data mutex 1.245 + 1.246 + get kernel tick interval 1.247 + 1.248 + if abs(highres - lowres) < kernel tick: 1.249 + returnedTime = highres time 1.250 + needCalibration = False 1.251 + else: 1.252 + if calibrated: 1.253 + returnedTime = lowres 1.254 + needCalibration = False 1.255 + else: 1.256 + needCalibration = True 1.257 + else: 1.258 + returnedTime = lowres 1.259 + while needCalibration 1.260 + 1.261 +*/ 1.262 + 1.263 +int64_t 1.264 +PRMJ_Now(void) 1.265 +{ 1.266 + static int nCalls = 0; 1.267 + long double lowresTime, highresTimerValue; 1.268 + FILETIME ft; 1.269 + LARGE_INTEGER now; 1.270 + bool calibrated = false; 1.271 + bool needsCalibration = false; 1.272 + int64_t returnedTime; 1.273 + long double cachedOffset = 0.0; 1.274 + 1.275 + /* For non threadsafe platforms, NowInit is not necessary */ 1.276 +#ifdef JS_THREADSAFE 1.277 + PR_CallOnce(&calibrationOnce, NowInit); 1.278 +#endif 1.279 + do { 1.280 + if (!calibration.calibrated || needsCalibration) { 1.281 + MUTEX_LOCK(&calibration.calibration_lock); 1.282 + MUTEX_LOCK(&calibration.data_lock); 1.283 + 1.284 + /* Recalibrate only if no one else did before us */ 1.285 + if(calibration.offset == cachedOffset) { 1.286 + /* Since calibration can take a while, make any other 1.287 + threads immediately wait */ 1.288 + MUTEX_SETSPINCOUNT(&calibration.data_lock, 0); 1.289 + 1.290 + NowCalibrate(); 1.291 + 1.292 + calibrated = true; 1.293 + 1.294 + /* Restore spin count */ 1.295 + MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT); 1.296 + } 1.297 + MUTEX_UNLOCK(&calibration.data_lock); 1.298 + MUTEX_UNLOCK(&calibration.calibration_lock); 1.299 + } 1.300 + 1.301 + 1.302 + /* Calculate a low resolution time */ 1.303 + GetSystemTimeAsFileTime(&ft); 1.304 + lowresTime = 0.1*(long double)(FILETIME2INT64(ft) - win2un); 1.305 + 1.306 + if (calibration.freq > 0.0) { 1.307 + long double highresTime, diff; 1.308 + 1.309 + DWORD timeAdjustment, timeIncrement; 1.310 + BOOL timeAdjustmentDisabled; 1.311 + 1.312 + /* Default to 15.625 ms if the syscall fails */ 1.313 + long double skewThreshold = 15625.25; 1.314 + /* Grab high resolution time */ 1.315 + QueryPerformanceCounter(&now); 1.316 + highresTimerValue = (long double)now.QuadPart; 1.317 + 1.318 + MUTEX_LOCK(&calibration.data_lock); 1.319 + highresTime = calibration.offset + PRMJ_USEC_PER_SEC* 1.320 + (highresTimerValue-calibration.timer_offset)/calibration.freq; 1.321 + cachedOffset = calibration.offset; 1.322 + 1.323 + /* On some dual processor/core systems, we might get an earlier time 1.324 + so we cache the last time that we returned */ 1.325 + calibration.last = js::Max(calibration.last, int64_t(highresTime)); 1.326 + returnedTime = calibration.last; 1.327 + MUTEX_UNLOCK(&calibration.data_lock); 1.328 + 1.329 + /* Rather than assume the NT kernel ticks every 15.6ms, ask it */ 1.330 + if (GetSystemTimeAdjustment(&timeAdjustment, 1.331 + &timeIncrement, 1.332 + &timeAdjustmentDisabled)) { 1.333 + if (timeAdjustmentDisabled) { 1.334 + /* timeAdjustment is in units of 100ns */ 1.335 + skewThreshold = timeAdjustment/10.0; 1.336 + } else { 1.337 + /* timeIncrement is in units of 100ns */ 1.338 + skewThreshold = timeIncrement/10.0; 1.339 + } 1.340 + } 1.341 + 1.342 + /* Check for clock skew */ 1.343 + diff = lowresTime - highresTime; 1.344 + 1.345 + /* For some reason that I have not determined, the skew can be 1.346 + up to twice a kernel tick. This does not seem to happen by 1.347 + itself, but I have only seen it triggered by another program 1.348 + doing some kind of file I/O. The symptoms are a negative diff 1.349 + followed by an equally large positive diff. */ 1.350 + if (mozilla::Abs(diff) > 2 * skewThreshold) { 1.351 + /*fprintf(stderr,"Clock skew detected (diff = %f)!\n", diff);*/ 1.352 + 1.353 + if (calibrated) { 1.354 + /* If we already calibrated once this instance, and the 1.355 + clock is still skewed, then either the processor(s) are 1.356 + wildly changing clockspeed or the system is so busy that 1.357 + we get switched out for long periods of time. In either 1.358 + case, it would be infeasible to make use of high 1.359 + resolution results for anything, so let's resort to old 1.360 + behavior for this call. It's possible that in the 1.361 + future, the user will want the high resolution timer, so 1.362 + we don't disable it entirely. */ 1.363 + returnedTime = int64_t(lowresTime); 1.364 + needsCalibration = false; 1.365 + } else { 1.366 + /* It is possible that when we recalibrate, we will return a 1.367 + value less than what we have returned before; this is 1.368 + unavoidable. We cannot tell the different between a 1.369 + faulty QueryPerformanceCounter implementation and user 1.370 + changes to the operating system time. Since we must 1.371 + respect user changes to the operating system time, we 1.372 + cannot maintain the invariant that Date.now() never 1.373 + decreases; the old implementation has this behavior as 1.374 + well. */ 1.375 + needsCalibration = true; 1.376 + } 1.377 + } else { 1.378 + /* No detectable clock skew */ 1.379 + returnedTime = int64_t(highresTime); 1.380 + needsCalibration = false; 1.381 + } 1.382 + } else { 1.383 + /* No high resolution timer is available, so fall back */ 1.384 + returnedTime = int64_t(lowresTime); 1.385 + } 1.386 + } while (needsCalibration); 1.387 + 1.388 + return returnedTime; 1.389 +} 1.390 +#endif 1.391 + 1.392 +#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER 1.393 +static void 1.394 +PRMJ_InvalidParameterHandler(const wchar_t *expression, 1.395 + const wchar_t *function, 1.396 + const wchar_t *file, 1.397 + unsigned int line, 1.398 + uintptr_t pReserved) 1.399 +{ 1.400 + /* empty */ 1.401 +} 1.402 +#endif 1.403 + 1.404 +/* Format a time value into a buffer. Same semantics as strftime() */ 1.405 +size_t 1.406 +PRMJ_FormatTime(char *buf, int buflen, const char *fmt, PRMJTime *prtm) 1.407 +{ 1.408 + size_t result = 0; 1.409 +#if defined(XP_UNIX) || defined(XP_WIN) 1.410 + struct tm a; 1.411 + int fake_tm_year = 0; 1.412 +#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER 1.413 + _invalid_parameter_handler oldHandler; 1.414 + int oldReportMode; 1.415 +#endif 1.416 + 1.417 + memset(&a, 0, sizeof(struct tm)); 1.418 + 1.419 + a.tm_sec = prtm->tm_sec; 1.420 + a.tm_min = prtm->tm_min; 1.421 + a.tm_hour = prtm->tm_hour; 1.422 + a.tm_mday = prtm->tm_mday; 1.423 + a.tm_mon = prtm->tm_mon; 1.424 + a.tm_wday = prtm->tm_wday; 1.425 + 1.426 + /* 1.427 + * On systems where |struct tm| has members tm_gmtoff and tm_zone, we 1.428 + * must fill in those values, or else strftime will return wrong results 1.429 + * (e.g., bug 511726, bug 554338). 1.430 + */ 1.431 +#if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF) 1.432 + { 1.433 + /* 1.434 + * Fill out |td| to the time represented by |prtm|, leaving the 1.435 + * timezone fields zeroed out. localtime_r will then fill in the 1.436 + * timezone fields for that local time according to the system's 1.437 + * timezone parameters. 1.438 + */ 1.439 + struct tm td; 1.440 + memset(&td, 0, sizeof(td)); 1.441 + td.tm_sec = prtm->tm_sec; 1.442 + td.tm_min = prtm->tm_min; 1.443 + td.tm_hour = prtm->tm_hour; 1.444 + td.tm_mday = prtm->tm_mday; 1.445 + td.tm_mon = prtm->tm_mon; 1.446 + td.tm_wday = prtm->tm_wday; 1.447 + td.tm_year = prtm->tm_year - 1900; 1.448 + td.tm_yday = prtm->tm_yday; 1.449 + td.tm_isdst = prtm->tm_isdst; 1.450 + time_t t = mktime(&td); 1.451 + localtime_r(&t, &td); 1.452 + 1.453 + a.tm_gmtoff = td.tm_gmtoff; 1.454 + a.tm_zone = td.tm_zone; 1.455 + } 1.456 +#endif 1.457 + 1.458 + /* 1.459 + * Years before 1900 and after 9999 cause strftime() to abort on Windows. 1.460 + * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then 1.461 + * replace matching substrings in the strftime() result with the real year. 1.462 + * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit 1.463 + * year formats (%y) work correctly (since we won't find the fake year 1.464 + * in that case). 1.465 + * e.g. new Date(1873, 0).toLocaleFormat('%Y %y') => "1873 73" 1.466 + * See bug 327869. 1.467 + */ 1.468 +#define FAKE_YEAR_BASE 9900 1.469 + if (prtm->tm_year < 1900 || prtm->tm_year > 9999) { 1.470 + fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100; 1.471 + a.tm_year = fake_tm_year - 1900; 1.472 + } 1.473 + else { 1.474 + a.tm_year = prtm->tm_year - 1900; 1.475 + } 1.476 + a.tm_yday = prtm->tm_yday; 1.477 + a.tm_isdst = prtm->tm_isdst; 1.478 + 1.479 + /* 1.480 + * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff 1.481 + * are null. This doesn't quite work, though - the timezone is off by 1.482 + * tzoff + dst. (And mktime seems to return -1 for the exact dst 1.483 + * changeover time.) 1.484 + */ 1.485 + 1.486 +#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER 1.487 + oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler); 1.488 + oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0); 1.489 +#endif 1.490 + 1.491 + result = strftime(buf, buflen, fmt, &a); 1.492 + 1.493 +#ifdef NS_HAVE_INVALID_PARAMETER_HANDLER 1.494 + _set_invalid_parameter_handler(oldHandler); 1.495 + _CrtSetReportMode(_CRT_ASSERT, oldReportMode); 1.496 +#endif 1.497 + 1.498 + if (fake_tm_year && result) { 1.499 + char real_year[16]; 1.500 + char fake_year[16]; 1.501 + size_t real_year_len; 1.502 + size_t fake_year_len; 1.503 + char* p; 1.504 + 1.505 + sprintf(real_year, "%d", prtm->tm_year); 1.506 + real_year_len = strlen(real_year); 1.507 + sprintf(fake_year, "%d", fake_tm_year); 1.508 + fake_year_len = strlen(fake_year); 1.509 + 1.510 + /* Replace the fake year in the result with the real year. */ 1.511 + for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) { 1.512 + size_t new_result = result + real_year_len - fake_year_len; 1.513 + if ((int)new_result >= buflen) { 1.514 + return 0; 1.515 + } 1.516 + memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len)); 1.517 + memcpy(p, real_year, real_year_len); 1.518 + result = new_result; 1.519 + *(buf + result) = '\0'; 1.520 + } 1.521 + } 1.522 +#endif 1.523 + return result; 1.524 +}