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