summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Time.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/vm/Time.cpp383
1 files changed, 383 insertions, 0 deletions
diff --git a/js/src/vm/Time.cpp b/js/src/vm/Time.cpp
new file mode 100644
index 0000000000..41ca68f8ee
--- /dev/null
+++ b/js/src/vm/Time.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* PR time code. */
+
+#include "vm/Time.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MathAlgorithms.h"
+
+#ifdef SOLARIS
+# define _REENTRANT 1
+#endif
+#include <string.h>
+#include <time.h>
+
+#include "jstypes.h"
+
+#ifdef XP_WIN
+# include <windef.h>
+# include <winbase.h>
+# include <crtdbg.h> /* for _CrtSetReportMode */
+# include <mmsystem.h> /* for timeBegin/EndPeriod */
+# include <stdlib.h> /* for _set_invalid_parameter_handler */
+#endif
+
+#ifdef XP_UNIX
+
+# ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */
+extern int gettimeofday(struct timeval* tv);
+# endif
+
+# include <sys/time.h>
+
+#endif /* XP_UNIX */
+
+using mozilla::DebugOnly;
+
+#if defined(XP_UNIX)
+int64_t PRMJ_Now() {
+ struct timeval tv;
+
+# ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */
+ gettimeofday(&tv);
+# else
+ gettimeofday(&tv, 0);
+# endif /* _SVID_GETTOD */
+
+ return int64_t(tv.tv_sec) * PRMJ_USEC_PER_SEC + int64_t(tv.tv_usec);
+}
+
+#else
+
+// Returns the number of microseconds since the Unix epoch.
+static double FileTimeToUnixMicroseconds(const FILETIME& ft) {
+ // Get the time in 100ns intervals.
+ int64_t t = (int64_t(ft.dwHighDateTime) << 32) | int64_t(ft.dwLowDateTime);
+
+ // The Windows epoch is around 1600. The Unix epoch is around 1970.
+ // Subtract the difference.
+ static const int64_t TimeToEpochIn100ns = 0x19DB1DED53E8000;
+ t -= TimeToEpochIn100ns;
+
+ // Divide by 10 to convert to microseconds.
+ return double(t) * 0.1;
+}
+
+struct CalibrationData {
+ double freq; /* The performance counter frequency */
+ double offset; /* The low res 'epoch' */
+ double timer_offset; /* The high res 'epoch' */
+
+ bool calibrated;
+
+ CRITICAL_SECTION data_lock;
+};
+
+static CalibrationData calibration = {0};
+
+static void NowCalibrate() {
+ MOZ_ASSERT(calibration.freq > 0);
+
+ // By wrapping a timeBegin/EndPeriod pair of calls around this loop,
+ // the loop seems to take much less time (1 ms vs 15ms) on Vista.
+ timeBeginPeriod(1);
+ FILETIME ft, ftStart;
+ GetSystemTimeAsFileTime(&ftStart);
+ do {
+ GetSystemTimeAsFileTime(&ft);
+ } while (memcmp(&ftStart, &ft, sizeof(ft)) == 0);
+ timeEndPeriod(1);
+
+ LARGE_INTEGER now;
+ QueryPerformanceCounter(&now);
+
+ calibration.offset = FileTimeToUnixMicroseconds(ft);
+ calibration.timer_offset = double(now.QuadPart);
+ calibration.calibrated = true;
+}
+
+static const unsigned DataLockSpinCount = 4096;
+
+static void(WINAPI* pGetSystemTimePreciseAsFileTime)(LPFILETIME) = nullptr;
+
+void PRMJ_NowInit() {
+ memset(&calibration, 0, sizeof(calibration));
+
+ // According to the documentation, QueryPerformanceFrequency will never
+ // return false or return a non-zero frequency on systems that run
+ // Windows XP or later. Also, the frequency is fixed so we only have to
+ // query it once.
+ LARGE_INTEGER liFreq;
+ DebugOnly<BOOL> res = QueryPerformanceFrequency(&liFreq);
+ MOZ_ASSERT(res);
+ calibration.freq = double(liFreq.QuadPart);
+ MOZ_ASSERT(calibration.freq > 0.0);
+
+ InitializeCriticalSectionAndSpinCount(&calibration.data_lock,
+ DataLockSpinCount);
+
+ // Windows 8 has a new API function we can use.
+ if (HMODULE h = GetModuleHandle("kernel32.dll")) {
+ pGetSystemTimePreciseAsFileTime = (void(WINAPI*)(LPFILETIME))GetProcAddress(
+ h, "GetSystemTimePreciseAsFileTime");
+ }
+}
+
+void PRMJ_NowShutdown() { DeleteCriticalSection(&calibration.data_lock); }
+
+# define MUTEX_LOCK(m) EnterCriticalSection(m)
+# define MUTEX_UNLOCK(m) LeaveCriticalSection(m)
+# define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m), (c))
+
+// Please see bug 363258 for why the win32 timing code is so complex.
+static int64_t PRMJ_Now() {
+ if (pGetSystemTimePreciseAsFileTime) {
+ // Windows 8 has a new API function that does all the work.
+ FILETIME ft;
+ pGetSystemTimePreciseAsFileTime(&ft);
+ return int64_t(FileTimeToUnixMicroseconds(ft));
+ }
+
+ bool calibrated = false;
+ bool needsCalibration = !calibration.calibrated;
+ double cachedOffset = 0.0;
+ while (true) {
+ if (needsCalibration) {
+ MUTEX_LOCK(&calibration.data_lock);
+
+ // Recalibrate only if no one else did before us.
+ if (calibration.offset == cachedOffset) {
+ // Since calibration can take a while, make any other
+ // threads immediately wait.
+ MUTEX_SETSPINCOUNT(&calibration.data_lock, 0);
+
+ NowCalibrate();
+
+ calibrated = true;
+
+ // Restore spin count.
+ MUTEX_SETSPINCOUNT(&calibration.data_lock, DataLockSpinCount);
+ }
+
+ MUTEX_UNLOCK(&calibration.data_lock);
+ }
+
+ // Calculate a low resolution time.
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+ double lowresTime = FileTimeToUnixMicroseconds(ft);
+
+ // Grab high resolution time.
+ LARGE_INTEGER now;
+ QueryPerformanceCounter(&now);
+ double highresTimerValue = double(now.QuadPart);
+
+ MUTEX_LOCK(&calibration.data_lock);
+ double highresTime = calibration.offset +
+ PRMJ_USEC_PER_SEC *
+ (highresTimerValue - calibration.timer_offset) /
+ calibration.freq;
+ cachedOffset = calibration.offset;
+ MUTEX_UNLOCK(&calibration.data_lock);
+
+ // Assume the NT kernel ticks every 15.6 ms. Unfortunately there's no
+ // good way to determine this (NtQueryTimerResolution is an undocumented
+ // API), but 15.6 ms seems to be the max possible value. Hardcoding 15.6
+ // means we'll recalibrate if the highres and lowres timers diverge by
+ // more than 30 ms.
+ static const double KernelTickInMicroseconds = 15625.25;
+
+ // Check for clock skew.
+ double diff = lowresTime - highresTime;
+
+ // For some reason that I have not determined, the skew can be
+ // up to twice a kernel tick. This does not seem to happen by
+ // itself, but I have only seen it triggered by another program
+ // doing some kind of file I/O. The symptoms are a negative diff
+ // followed by an equally large positive diff.
+ if (mozilla::Abs(diff) <= 2 * KernelTickInMicroseconds) {
+ // No detectable clock skew.
+ return int64_t(highresTime);
+ }
+
+ if (calibrated) {
+ // If we already calibrated once this instance, and the
+ // clock is still skewed, then either the processor(s) are
+ // wildly changing clockspeed or the system is so busy that
+ // we get switched out for long periods of time. In either
+ // case, it would be infeasible to make use of high
+ // resolution results for anything, so let's resort to old
+ // behavior for this call. It's possible that in the
+ // future, the user will want the high resolution timer, so
+ // we don't disable it entirely.
+ return int64_t(lowresTime);
+ }
+
+ // It is possible that when we recalibrate, we will return a
+ // value less than what we have returned before; this is
+ // unavoidable. We cannot tell the different between a
+ // faulty QueryPerformanceCounter implementation and user
+ // changes to the operating system time. Since we must
+ // respect user changes to the operating system time, we
+ // cannot maintain the invariant that Date.now() never
+ // decreases; the old implementation has this behavior as
+ // well.
+ needsCalibration = true;
+ }
+}
+#endif
+
+#if !JS_HAS_INTL_API
+# ifdef XP_WIN
+static void PRMJ_InvalidParameterHandler(const wchar_t* expression,
+ const wchar_t* function,
+ const wchar_t* file, unsigned int line,
+ uintptr_t pReserved) {
+ /* empty */
+}
+# endif
+
+/* Format a time value into a buffer. Same semantics as strftime() */
+size_t PRMJ_FormatTime(char* buf, size_t buflen, const char* fmt,
+ const PRMJTime* prtm, int timeZoneYear,
+ int offsetInSeconds) {
+ size_t result = 0;
+# if defined(XP_UNIX) || defined(XP_WIN)
+ struct tm a;
+# ifdef XP_WIN
+ _invalid_parameter_handler oldHandler;
+# ifndef __MINGW32__
+ int oldReportMode;
+# endif // __MINGW32__
+# endif // XP_WIN
+
+ memset(&a, 0, sizeof(struct tm));
+
+ a.tm_sec = prtm->tm_sec;
+ a.tm_min = prtm->tm_min;
+ a.tm_hour = prtm->tm_hour;
+ a.tm_mday = prtm->tm_mday;
+ a.tm_mon = prtm->tm_mon;
+ a.tm_wday = prtm->tm_wday;
+
+ /*
+ * On systems where |struct tm| has members tm_gmtoff and tm_zone, we
+ * must fill in those values, or else strftime will return wrong results
+ * (e.g., bug 511726, bug 554338).
+ */
+# if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF)
+ char emptyTimeZoneId[] = "";
+ {
+ /*
+ * Fill out |td| to the time represented by |prtm|, leaving the
+ * timezone fields zeroed out. localtime_r will then fill in the
+ * timezone fields for that local time according to the system's
+ * timezone parameters. Use |timeZoneYear| for the year to ensure the
+ * time zone name matches the time zone offset used by the caller.
+ */
+ struct tm td;
+ memset(&td, 0, sizeof(td));
+ td.tm_sec = prtm->tm_sec;
+ td.tm_min = prtm->tm_min;
+ td.tm_hour = prtm->tm_hour;
+ td.tm_mday = prtm->tm_mday;
+ td.tm_mon = prtm->tm_mon;
+ td.tm_wday = prtm->tm_wday;
+ td.tm_year = timeZoneYear - 1900;
+ td.tm_yday = prtm->tm_yday;
+ td.tm_isdst = prtm->tm_isdst;
+
+ time_t t = mktime(&td);
+
+ // If either mktime or localtime_r failed, fill in the fallback time
+ // zone offset |offsetInSeconds| and set the time zone identifier to
+ // the empty string.
+ if (t != static_cast<time_t>(-1) && localtime_r(&t, &td)) {
+ a.tm_gmtoff = td.tm_gmtoff;
+ a.tm_zone = td.tm_zone;
+ } else {
+ a.tm_gmtoff = offsetInSeconds;
+ a.tm_zone = emptyTimeZoneId;
+ }
+ }
+# endif
+
+ /*
+ * Years before 1900 and after 9999 cause strftime() to abort on Windows.
+ * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then
+ * replace matching substrings in the strftime() result with the real year.
+ * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit
+ * year formats (%y) work correctly (since we won't find the fake year
+ * in that case).
+ */
+ constexpr int FAKE_YEAR_BASE = 9900;
+ int fake_tm_year = 0;
+ if (prtm->tm_year < 1900 || prtm->tm_year > 9999) {
+ fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100;
+ a.tm_year = fake_tm_year - 1900;
+ } else {
+ a.tm_year = prtm->tm_year - 1900;
+ }
+ a.tm_yday = prtm->tm_yday;
+ a.tm_isdst = prtm->tm_isdst;
+
+ /*
+ * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff
+ * are null. This doesn't quite work, though - the timezone is off by
+ * tzoff + dst. (And mktime seems to return -1 for the exact dst
+ * changeover time.)
+ */
+
+# ifdef XP_WIN
+ oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
+# ifndef __MINGW32__
+ /*
+ * MinGW doesn't have _CrtSetReportMode and defines it to be a no-op.
+ * We ifdef it off to avoid warnings about unused variables
+ */
+ oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
+# endif // __MINGW32__
+# endif // XP_WIN
+
+ result = strftime(buf, buflen, fmt, &a);
+
+# ifdef XP_WIN
+ _set_invalid_parameter_handler(oldHandler);
+# ifndef __MINGW32__
+ _CrtSetReportMode(_CRT_ASSERT, oldReportMode);
+# endif // __MINGW32__
+# endif // XP_WIN
+
+ if (fake_tm_year && result) {
+ char real_year[16];
+ char fake_year[16];
+ size_t real_year_len;
+ size_t fake_year_len;
+ char* p;
+
+ sprintf(real_year, "%d", prtm->tm_year);
+ real_year_len = strlen(real_year);
+ sprintf(fake_year, "%d", fake_tm_year);
+ fake_year_len = strlen(fake_year);
+
+ /* Replace the fake year in the result with the real year. */
+ for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) {
+ size_t new_result = result + real_year_len - fake_year_len;
+ if (new_result >= buflen) {
+ return 0;
+ }
+ memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len));
+ memcpy(p, real_year, real_year_len);
+ result = new_result;
+ *(buf + result) = '\0';
+ }
+ }
+# endif
+ return result;
+}
+#endif /* !JS_HAS_INTL_API */