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 : }
|