LCOV - code coverage report
Current view: top level - js/src/vm - Time.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 3 48 6.2 %
Date: 2017-07-14 16:53:18 Functions: 1 2 50.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
       2             :  * vim: set ts=8 sts=4 et sw=4 tw=99:
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : /* PR time code. */
       8             : 
       9             : #include "vm/Time.h"
      10             : 
      11             : #include "mozilla/DebugOnly.h"
      12             : #include "mozilla/MathAlgorithms.h"
      13             : 
      14             : #ifdef SOLARIS
      15             : #define _REENTRANT 1
      16             : #endif
      17             : #include <string.h>
      18             : #include <time.h>
      19             : 
      20             : #include "jstypes.h"
      21             : #include "jsutil.h"
      22             : 
      23             : #ifdef XP_WIN
      24             : #include <windef.h>
      25             : #include <winbase.h>
      26             : #include <crtdbg.h>   /* for _CrtSetReportMode */
      27             : #include <mmsystem.h> /* for timeBegin/EndPeriod */
      28             : #include <stdlib.h>   /* for _set_invalid_parameter_handler */
      29             : 
      30             : #include "prinit.h"
      31             : 
      32             : #endif
      33             : 
      34             : #ifdef XP_UNIX
      35             : 
      36             : #ifdef _SVID_GETTOD   /* Defined only on Solaris, see Solaris <sys/types.h> */
      37             : extern int gettimeofday(struct timeval* tv);
      38             : #endif
      39             : 
      40             : #include <sys/time.h>
      41             : 
      42             : #endif /* XP_UNIX */
      43             : 
      44             : using mozilla::DebugOnly;
      45             : 
      46             : #if defined(XP_UNIX)
      47             : int64_t
      48         245 : PRMJ_Now()
      49             : {
      50             :     struct timeval tv;
      51             : 
      52             : #ifdef _SVID_GETTOD   /* Defined only on Solaris, see Solaris <sys/types.h> */
      53             :     gettimeofday(&tv);
      54             : #else
      55         245 :     gettimeofday(&tv, 0);
      56             : #endif /* _SVID_GETTOD */
      57             : 
      58         245 :     return int64_t(tv.tv_sec) * PRMJ_USEC_PER_SEC + int64_t(tv.tv_usec);
      59             : }
      60             : 
      61             : #else
      62             : 
      63             : // Returns the number of microseconds since the Unix epoch.
      64             : static double
      65             : FileTimeToUnixMicroseconds(const FILETIME& ft)
      66             : {
      67             :     // Get the time in 100ns intervals.
      68             :     int64_t t = (int64_t(ft.dwHighDateTime) << 32) | int64_t(ft.dwLowDateTime);
      69             : 
      70             :     // The Windows epoch is around 1600. The Unix epoch is around 1970.
      71             :     // Subtract the difference.
      72             :     static const int64_t TimeToEpochIn100ns = 0x19DB1DED53E8000;
      73             :     t -= TimeToEpochIn100ns;
      74             : 
      75             :     // Divide by 10 to convert to microseconds.
      76             :     return double(t) * 0.1;
      77             : }
      78             : 
      79             : struct CalibrationData {
      80             :     double freq;         /* The performance counter frequency */
      81             :     double offset;       /* The low res 'epoch' */
      82             :     double timer_offset; /* The high res 'epoch' */
      83             : 
      84             :     bool calibrated;
      85             : 
      86             :     CRITICAL_SECTION data_lock;
      87             : };
      88             : 
      89             : static CalibrationData calibration = { 0 };
      90             : 
      91             : static void
      92             : NowCalibrate()
      93             : {
      94             :     MOZ_ASSERT(calibration.freq > 0);
      95             : 
      96             :     // By wrapping a timeBegin/EndPeriod pair of calls around this loop,
      97             :     // the loop seems to take much less time (1 ms vs 15ms) on Vista.
      98             :     timeBeginPeriod(1);
      99             :     FILETIME ft, ftStart;
     100             :     GetSystemTimeAsFileTime(&ftStart);
     101             :     do {
     102             :         GetSystemTimeAsFileTime(&ft);
     103             :     } while (memcmp(&ftStart, &ft, sizeof(ft)) == 0);
     104             :     timeEndPeriod(1);
     105             : 
     106             :     LARGE_INTEGER now;
     107             :     QueryPerformanceCounter(&now);
     108             : 
     109             :     calibration.offset = FileTimeToUnixMicroseconds(ft);
     110             :     calibration.timer_offset = double(now.QuadPart);
     111             :     calibration.calibrated = true;
     112             : }
     113             : 
     114             : static const unsigned DataLockSpinCount = 4096;
     115             : 
     116             : static void (WINAPI* pGetSystemTimePreciseAsFileTime)(LPFILETIME) = nullptr;
     117             : 
     118             : void
     119             : PRMJ_NowInit()
     120             : {
     121             :     memset(&calibration, 0, sizeof(calibration));
     122             : 
     123             :     // According to the documentation, QueryPerformanceFrequency will never
     124             :     // return false or return a non-zero frequency on systems that run
     125             :     // Windows XP or later. Also, the frequency is fixed so we only have to
     126             :     // query it once.
     127             :     LARGE_INTEGER liFreq;
     128             :     DebugOnly<BOOL> res = QueryPerformanceFrequency(&liFreq);
     129             :     MOZ_ASSERT(res);
     130             :     calibration.freq = double(liFreq.QuadPart);
     131             :     MOZ_ASSERT(calibration.freq > 0.0);
     132             : 
     133             :     InitializeCriticalSectionAndSpinCount(&calibration.data_lock, DataLockSpinCount);
     134             : 
     135             :     // Windows 8 has a new API function we can use.
     136             :     if (HMODULE h = GetModuleHandle("kernel32.dll")) {
     137             :         pGetSystemTimePreciseAsFileTime =
     138             :             (void (WINAPI*)(LPFILETIME))GetProcAddress(h, "GetSystemTimePreciseAsFileTime");
     139             :     }
     140             : }
     141             : 
     142             : void
     143             : PRMJ_NowShutdown()
     144             : {
     145             :     DeleteCriticalSection(&calibration.data_lock);
     146             : }
     147             : 
     148             : #define MUTEX_LOCK(m) EnterCriticalSection(m)
     149             : #define MUTEX_UNLOCK(m) LeaveCriticalSection(m)
     150             : #define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m),(c))
     151             : 
     152             : // Please see bug 363258 for why the win32 timing code is so complex.
     153             : int64_t
     154             : PRMJ_Now()
     155             : {
     156             :     if (pGetSystemTimePreciseAsFileTime) {
     157             :         // Windows 8 has a new API function that does all the work.
     158             :         FILETIME ft;
     159             :         pGetSystemTimePreciseAsFileTime(&ft);
     160             :         return int64_t(FileTimeToUnixMicroseconds(ft));
     161             :     }
     162             : 
     163             :     bool calibrated = false;
     164             :     bool needsCalibration = !calibration.calibrated;
     165             :     double cachedOffset = 0.0;
     166             :     while (true) {
     167             :         if (needsCalibration) {
     168             :             MUTEX_LOCK(&calibration.data_lock);
     169             : 
     170             :             // Recalibrate only if no one else did before us.
     171             :             if (calibration.offset == cachedOffset) {
     172             :                 // Since calibration can take a while, make any other
     173             :                 // threads immediately wait.
     174             :                 MUTEX_SETSPINCOUNT(&calibration.data_lock, 0);
     175             : 
     176             :                 NowCalibrate();
     177             : 
     178             :                 calibrated = true;
     179             : 
     180             :                 // Restore spin count.
     181             :                 MUTEX_SETSPINCOUNT(&calibration.data_lock, DataLockSpinCount);
     182             :             }
     183             : 
     184             :             MUTEX_UNLOCK(&calibration.data_lock);
     185             :         }
     186             : 
     187             :         // Calculate a low resolution time.
     188             :         FILETIME ft;
     189             :         GetSystemTimeAsFileTime(&ft);
     190             :         double lowresTime = FileTimeToUnixMicroseconds(ft);
     191             : 
     192             :         // Grab high resolution time.
     193             :         LARGE_INTEGER now;
     194             :         QueryPerformanceCounter(&now);
     195             :         double highresTimerValue = double(now.QuadPart);
     196             : 
     197             :         MUTEX_LOCK(&calibration.data_lock);
     198             :         double highresTime = calibration.offset +
     199             :             PRMJ_USEC_PER_SEC * (highresTimerValue - calibration.timer_offset) / calibration.freq;
     200             :         cachedOffset = calibration.offset;
     201             :         MUTEX_UNLOCK(&calibration.data_lock);
     202             : 
     203             :         // Assume the NT kernel ticks every 15.6 ms. Unfortunately there's no
     204             :         // good way to determine this (NtQueryTimerResolution is an undocumented
     205             :         // API), but 15.6 ms seems to be the max possible value. Hardcoding 15.6
     206             :         // means we'll recalibrate if the highres and lowres timers diverge by
     207             :         // more than 30 ms.
     208             :         static const double KernelTickInMicroseconds = 15625.25;
     209             : 
     210             :         // Check for clock skew.
     211             :         double diff = lowresTime - highresTime;
     212             : 
     213             :         // For some reason that I have not determined, the skew can be
     214             :         // up to twice a kernel tick. This does not seem to happen by
     215             :         // itself, but I have only seen it triggered by another program
     216             :         // doing some kind of file I/O. The symptoms are a negative diff
     217             :         // followed by an equally large positive diff.
     218             :         if (mozilla::Abs(diff) <= 2 * KernelTickInMicroseconds) {
     219             :             // No detectable clock skew.
     220             :             return int64_t(highresTime);
     221             :         }
     222             : 
     223             :         if (calibrated) {
     224             :             // If we already calibrated once this instance, and the
     225             :             // clock is still skewed, then either the processor(s) are
     226             :             // wildly changing clockspeed or the system is so busy that
     227             :             // we get switched out for long periods of time. In either
     228             :             // case, it would be infeasible to make use of high
     229             :             // resolution results for anything, so let's resort to old
     230             :             // behavior for this call. It's possible that in the
     231             :             // future, the user will want the high resolution timer, so
     232             :             // we don't disable it entirely.
     233             :             return int64_t(lowresTime);
     234             :         }
     235             : 
     236             :         // It is possible that when we recalibrate, we will return a
     237             :         // value less than what we have returned before; this is
     238             :         // unavoidable. We cannot tell the different between a
     239             :         // faulty QueryPerformanceCounter implementation and user
     240             :         // changes to the operating system time. Since we must
     241             :         // respect user changes to the operating system time, we
     242             :         // cannot maintain the invariant that Date.now() never
     243             :         // decreases; the old implementation has this behavior as
     244             :         // well.
     245             :         needsCalibration = true;
     246             :     }
     247             : }
     248             : #endif
     249             : 
     250             : #ifdef XP_WIN
     251             : static void
     252             : PRMJ_InvalidParameterHandler(const wchar_t* expression,
     253             :                              const wchar_t* function,
     254             :                              const wchar_t* file,
     255             :                              unsigned int   line,
     256             :                              uintptr_t      pReserved)
     257             : {
     258             :     /* empty */
     259             : }
     260             : #endif
     261             : 
     262             : /* Format a time value into a buffer. Same semantics as strftime() */
     263             : size_t
     264           0 : PRMJ_FormatTime(char* buf, int buflen, const char* fmt, PRMJTime* prtm)
     265             : {
     266           0 :     size_t result = 0;
     267             : #if defined(XP_UNIX) || defined(XP_WIN)
     268             :     struct tm a;
     269           0 :     int fake_tm_year = 0;
     270             : #ifdef XP_WIN
     271             :     _invalid_parameter_handler oldHandler;
     272             :     int oldReportMode;
     273             : #endif
     274             : 
     275           0 :     memset(&a, 0, sizeof(struct tm));
     276             : 
     277           0 :     a.tm_sec = prtm->tm_sec;
     278           0 :     a.tm_min = prtm->tm_min;
     279           0 :     a.tm_hour = prtm->tm_hour;
     280           0 :     a.tm_mday = prtm->tm_mday;
     281           0 :     a.tm_mon = prtm->tm_mon;
     282           0 :     a.tm_wday = prtm->tm_wday;
     283             : 
     284             :     /*
     285             :      * On systems where |struct tm| has members tm_gmtoff and tm_zone, we
     286             :      * must fill in those values, or else strftime will return wrong results
     287             :      * (e.g., bug 511726, bug 554338).
     288             :      */
     289             : #if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF)
     290             :     {
     291             :         /*
     292             :          * Fill out |td| to the time represented by |prtm|, leaving the
     293             :          * timezone fields zeroed out. localtime_r will then fill in the
     294             :          * timezone fields for that local time according to the system's
     295             :          * timezone parameters.
     296             :          */
     297             :         struct tm td;
     298           0 :         memset(&td, 0, sizeof(td));
     299           0 :         td.tm_sec = prtm->tm_sec;
     300           0 :         td.tm_min = prtm->tm_min;
     301           0 :         td.tm_hour = prtm->tm_hour;
     302           0 :         td.tm_mday = prtm->tm_mday;
     303           0 :         td.tm_mon = prtm->tm_mon;
     304           0 :         td.tm_wday = prtm->tm_wday;
     305           0 :         td.tm_year = prtm->tm_year - 1900;
     306           0 :         td.tm_yday = prtm->tm_yday;
     307           0 :         td.tm_isdst = prtm->tm_isdst;
     308           0 :         time_t t = mktime(&td);
     309           0 :         localtime_r(&t, &td);
     310             : 
     311           0 :         a.tm_gmtoff = td.tm_gmtoff;
     312           0 :         a.tm_zone = td.tm_zone;
     313             :     }
     314             : #endif
     315             : 
     316             :     /*
     317             :      * Years before 1900 and after 9999 cause strftime() to abort on Windows.
     318             :      * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then
     319             :      * replace matching substrings in the strftime() result with the real year.
     320             :      * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit
     321             :      * year formats (%y) work correctly (since we won't find the fake year
     322             :      * in that case).
     323             :      * e.g. new Date(1873, 0).toLocaleFormat('%Y %y') => "1873 73"
     324             :      * See bug 327869.
     325             :      */
     326             : #define FAKE_YEAR_BASE 9900
     327           0 :     if (prtm->tm_year < 1900 || prtm->tm_year > 9999) {
     328           0 :         fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100;
     329           0 :         a.tm_year = fake_tm_year - 1900;
     330             :     }
     331             :     else {
     332           0 :         a.tm_year = prtm->tm_year - 1900;
     333             :     }
     334           0 :     a.tm_yday = prtm->tm_yday;
     335           0 :     a.tm_isdst = prtm->tm_isdst;
     336             : 
     337             :     /*
     338             :      * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff
     339             :      * are null.  This doesn't quite work, though - the timezone is off by
     340             :      * tzoff + dst.  (And mktime seems to return -1 for the exact dst
     341             :      * changeover time.)
     342             :      */
     343             : 
     344             : #ifdef XP_WIN
     345             :     oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler);
     346             :     oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0);
     347             : #endif
     348             : 
     349           0 :     result = strftime(buf, buflen, fmt, &a);
     350             : 
     351             : #ifdef XP_WIN
     352             :     _set_invalid_parameter_handler(oldHandler);
     353             :     _CrtSetReportMode(_CRT_ASSERT, oldReportMode);
     354             : #endif
     355             : 
     356           0 :     if (fake_tm_year && result) {
     357             :         char real_year[16];
     358             :         char fake_year[16];
     359             :         size_t real_year_len;
     360             :         size_t fake_year_len;
     361             :         char* p;
     362             : 
     363           0 :         sprintf(real_year, "%d", prtm->tm_year);
     364           0 :         real_year_len = strlen(real_year);
     365           0 :         sprintf(fake_year, "%d", fake_tm_year);
     366           0 :         fake_year_len = strlen(fake_year);
     367             : 
     368             :         /* Replace the fake year in the result with the real year. */
     369           0 :         for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) {
     370           0 :             size_t new_result = result + real_year_len - fake_year_len;
     371           0 :             if ((int)new_result >= buflen) {
     372           0 :                 return 0;
     373             :             }
     374           0 :             memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len));
     375           0 :             memcpy(p, real_year, real_year_len);
     376           0 :             result = new_result;
     377           0 :             *(buf + result) = '\0';
     378             :         }
     379             :     }
     380             : #endif
     381           0 :     return result;
     382             : }

Generated by: LCOV version 1.13