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