|
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 "prmjtime.h" |
|
10 |
|
11 #include "mozilla/MathAlgorithms.h" |
|
12 |
|
13 #ifdef SOLARIS |
|
14 #define _REENTRANT 1 |
|
15 #endif |
|
16 #include <string.h> |
|
17 #include <time.h> |
|
18 |
|
19 #include "jstypes.h" |
|
20 #include "jsutil.h" |
|
21 |
|
22 #define PRMJ_DO_MILLISECONDS 1 |
|
23 |
|
24 #ifdef XP_WIN |
|
25 #include <windef.h> |
|
26 #include <winbase.h> |
|
27 #include <mmsystem.h> /* for timeBegin/EndPeriod */ |
|
28 /* VC++ 8.0 or later */ |
|
29 #if _MSC_VER >= 1400 |
|
30 #define NS_HAVE_INVALID_PARAMETER_HANDLER 1 |
|
31 #endif |
|
32 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER |
|
33 #include <crtdbg.h> /* for _CrtSetReportMode */ |
|
34 #include <stdlib.h> /* for _set_invalid_parameter_handler */ |
|
35 #endif |
|
36 |
|
37 #ifdef JS_THREADSAFE |
|
38 #include "prinit.h" |
|
39 #endif |
|
40 |
|
41 #endif |
|
42 |
|
43 #ifdef XP_UNIX |
|
44 |
|
45 #ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */ |
|
46 extern int gettimeofday(struct timeval *tv); |
|
47 #endif |
|
48 |
|
49 #include <sys/time.h> |
|
50 |
|
51 #endif /* XP_UNIX */ |
|
52 |
|
53 #define PRMJ_YEAR_DAYS 365L |
|
54 #define PRMJ_FOUR_YEARS_DAYS (4 * PRMJ_YEAR_DAYS + 1) |
|
55 #define PRMJ_CENTURY_DAYS (25 * PRMJ_FOUR_YEARS_DAYS - 1) |
|
56 #define PRMJ_FOUR_CENTURIES_DAYS (4 * PRMJ_CENTURY_DAYS + 1) |
|
57 #define PRMJ_HOUR_SECONDS 3600L |
|
58 #define PRMJ_DAY_SECONDS (24L * PRMJ_HOUR_SECONDS) |
|
59 #define PRMJ_YEAR_SECONDS (PRMJ_DAY_SECONDS * PRMJ_YEAR_DAYS) |
|
60 #define PRMJ_MAX_UNIX_TIMET 2145859200L /*time_t value equiv. to 12/31/2037 */ |
|
61 |
|
62 /* Constants for GMT offset from 1970 */ |
|
63 #define G1970GMTMICROHI 0x00dcdcad /* micro secs to 1970 hi */ |
|
64 #define G1970GMTMICROLOW 0x8b3fa000 /* micro secs to 1970 low */ |
|
65 |
|
66 #define G2037GMTMICROHI 0x00e45fab /* micro secs to 2037 high */ |
|
67 #define G2037GMTMICROLOW 0x7a238000 /* micro secs to 2037 low */ |
|
68 |
|
69 #if defined(XP_WIN) |
|
70 |
|
71 static const int64_t win2un = 0x19DB1DED53E8000; |
|
72 |
|
73 #define FILETIME2INT64(ft) (((int64_t)ft.dwHighDateTime) << 32LL | (int64_t)ft.dwLowDateTime) |
|
74 |
|
75 typedef struct CalibrationData { |
|
76 long double freq; /* The performance counter frequency */ |
|
77 long double offset; /* The low res 'epoch' */ |
|
78 long double timer_offset; /* The high res 'epoch' */ |
|
79 |
|
80 /* The last high res time that we returned since recalibrating */ |
|
81 int64_t last; |
|
82 |
|
83 bool calibrated; |
|
84 |
|
85 #ifdef JS_THREADSAFE |
|
86 CRITICAL_SECTION data_lock; |
|
87 CRITICAL_SECTION calibration_lock; |
|
88 #endif |
|
89 } CalibrationData; |
|
90 |
|
91 static CalibrationData calibration = { 0 }; |
|
92 |
|
93 static void |
|
94 NowCalibrate() |
|
95 { |
|
96 FILETIME ft, ftStart; |
|
97 LARGE_INTEGER liFreq, now; |
|
98 |
|
99 if (calibration.freq == 0.0) { |
|
100 if(!QueryPerformanceFrequency(&liFreq)) { |
|
101 /* High-performance timer is unavailable */ |
|
102 calibration.freq = -1.0; |
|
103 } else { |
|
104 calibration.freq = (long double) liFreq.QuadPart; |
|
105 } |
|
106 } |
|
107 if (calibration.freq > 0.0) { |
|
108 int64_t calibrationDelta = 0; |
|
109 |
|
110 /* By wrapping a timeBegin/EndPeriod pair of calls around this loop, |
|
111 the loop seems to take much less time (1 ms vs 15ms) on Vista. */ |
|
112 timeBeginPeriod(1); |
|
113 GetSystemTimeAsFileTime(&ftStart); |
|
114 do { |
|
115 GetSystemTimeAsFileTime(&ft); |
|
116 } while (memcmp(&ftStart,&ft, sizeof(ft)) == 0); |
|
117 timeEndPeriod(1); |
|
118 |
|
119 /* |
|
120 calibrationDelta = (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10; |
|
121 fprintf(stderr, "Calibration delta was %I64d us\n", calibrationDelta); |
|
122 */ |
|
123 |
|
124 QueryPerformanceCounter(&now); |
|
125 |
|
126 calibration.offset = (long double) FILETIME2INT64(ft); |
|
127 calibration.timer_offset = (long double) now.QuadPart; |
|
128 |
|
129 /* The windows epoch is around 1600. The unix epoch is around |
|
130 1970. win2un is the difference (in windows time units which |
|
131 are 10 times more highres than the JS time unit) */ |
|
132 calibration.offset -= win2un; |
|
133 calibration.offset *= 0.1; |
|
134 calibration.last = 0; |
|
135 |
|
136 calibration.calibrated = true; |
|
137 } |
|
138 } |
|
139 |
|
140 #define CALIBRATIONLOCK_SPINCOUNT 0 |
|
141 #define DATALOCK_SPINCOUNT 4096 |
|
142 #define LASTLOCK_SPINCOUNT 4096 |
|
143 |
|
144 #ifdef JS_THREADSAFE |
|
145 static PRStatus |
|
146 NowInit(void) |
|
147 { |
|
148 memset(&calibration, 0, sizeof(calibration)); |
|
149 NowCalibrate(); |
|
150 InitializeCriticalSectionAndSpinCount(&calibration.calibration_lock, CALIBRATIONLOCK_SPINCOUNT); |
|
151 InitializeCriticalSectionAndSpinCount(&calibration.data_lock, DATALOCK_SPINCOUNT); |
|
152 return PR_SUCCESS; |
|
153 } |
|
154 |
|
155 void |
|
156 PRMJ_NowShutdown() |
|
157 { |
|
158 DeleteCriticalSection(&calibration.calibration_lock); |
|
159 DeleteCriticalSection(&calibration.data_lock); |
|
160 } |
|
161 |
|
162 #define MUTEX_LOCK(m) EnterCriticalSection(m) |
|
163 #define MUTEX_TRYLOCK(m) TryEnterCriticalSection(m) |
|
164 #define MUTEX_UNLOCK(m) LeaveCriticalSection(m) |
|
165 #define MUTEX_SETSPINCOUNT(m, c) SetCriticalSectionSpinCount((m),(c)) |
|
166 |
|
167 static PRCallOnceType calibrationOnce = { 0 }; |
|
168 |
|
169 #else |
|
170 |
|
171 #define MUTEX_LOCK(m) |
|
172 #define MUTEX_TRYLOCK(m) 1 |
|
173 #define MUTEX_UNLOCK(m) |
|
174 #define MUTEX_SETSPINCOUNT(m, c) |
|
175 |
|
176 #endif |
|
177 |
|
178 #endif /* XP_WIN */ |
|
179 |
|
180 |
|
181 #if defined(XP_UNIX) |
|
182 int64_t |
|
183 PRMJ_Now(void) |
|
184 { |
|
185 struct timeval tv; |
|
186 |
|
187 #ifdef _SVID_GETTOD /* Defined only on Solaris, see Solaris <sys/types.h> */ |
|
188 gettimeofday(&tv); |
|
189 #else |
|
190 gettimeofday(&tv, 0); |
|
191 #endif /* _SVID_GETTOD */ |
|
192 |
|
193 return int64_t(tv.tv_sec) * PRMJ_USEC_PER_SEC + int64_t(tv.tv_usec); |
|
194 } |
|
195 |
|
196 #else |
|
197 /* |
|
198 |
|
199 Win32 python-esque pseudo code |
|
200 Please see bug 363258 for why the win32 timing code is so complex. |
|
201 |
|
202 calibration mutex : Win32CriticalSection(spincount=0) |
|
203 data mutex : Win32CriticalSection(spincount=4096) |
|
204 |
|
205 def NowInit(): |
|
206 init mutexes |
|
207 PRMJ_NowCalibration() |
|
208 |
|
209 def NowCalibration(): |
|
210 expensive up-to-15ms call |
|
211 |
|
212 def PRMJ_Now(): |
|
213 returnedTime = 0 |
|
214 needCalibration = False |
|
215 cachedOffset = 0.0 |
|
216 calibrated = False |
|
217 PR_CallOnce(PRMJ_NowInit) |
|
218 do |
|
219 if not global.calibrated or needCalibration: |
|
220 acquire calibration mutex |
|
221 acquire data mutex |
|
222 |
|
223 // Only recalibrate if someone didn't already |
|
224 if cachedOffset == calibration.offset: |
|
225 // Have all waiting threads immediately wait |
|
226 set data mutex spin count = 0 |
|
227 PRMJ_NowCalibrate() |
|
228 calibrated = 1 |
|
229 |
|
230 set data mutex spin count = default |
|
231 release data mutex |
|
232 release calibration mutex |
|
233 |
|
234 calculate lowres time |
|
235 |
|
236 if highres timer available: |
|
237 acquire data mutex |
|
238 calculate highres time |
|
239 cachedOffset = calibration.offset |
|
240 highres time = calibration.last = max(highres time, calibration.last) |
|
241 release data mutex |
|
242 |
|
243 get kernel tick interval |
|
244 |
|
245 if abs(highres - lowres) < kernel tick: |
|
246 returnedTime = highres time |
|
247 needCalibration = False |
|
248 else: |
|
249 if calibrated: |
|
250 returnedTime = lowres |
|
251 needCalibration = False |
|
252 else: |
|
253 needCalibration = True |
|
254 else: |
|
255 returnedTime = lowres |
|
256 while needCalibration |
|
257 |
|
258 */ |
|
259 |
|
260 int64_t |
|
261 PRMJ_Now(void) |
|
262 { |
|
263 static int nCalls = 0; |
|
264 long double lowresTime, highresTimerValue; |
|
265 FILETIME ft; |
|
266 LARGE_INTEGER now; |
|
267 bool calibrated = false; |
|
268 bool needsCalibration = false; |
|
269 int64_t returnedTime; |
|
270 long double cachedOffset = 0.0; |
|
271 |
|
272 /* For non threadsafe platforms, NowInit is not necessary */ |
|
273 #ifdef JS_THREADSAFE |
|
274 PR_CallOnce(&calibrationOnce, NowInit); |
|
275 #endif |
|
276 do { |
|
277 if (!calibration.calibrated || needsCalibration) { |
|
278 MUTEX_LOCK(&calibration.calibration_lock); |
|
279 MUTEX_LOCK(&calibration.data_lock); |
|
280 |
|
281 /* Recalibrate only if no one else did before us */ |
|
282 if(calibration.offset == cachedOffset) { |
|
283 /* Since calibration can take a while, make any other |
|
284 threads immediately wait */ |
|
285 MUTEX_SETSPINCOUNT(&calibration.data_lock, 0); |
|
286 |
|
287 NowCalibrate(); |
|
288 |
|
289 calibrated = true; |
|
290 |
|
291 /* Restore spin count */ |
|
292 MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT); |
|
293 } |
|
294 MUTEX_UNLOCK(&calibration.data_lock); |
|
295 MUTEX_UNLOCK(&calibration.calibration_lock); |
|
296 } |
|
297 |
|
298 |
|
299 /* Calculate a low resolution time */ |
|
300 GetSystemTimeAsFileTime(&ft); |
|
301 lowresTime = 0.1*(long double)(FILETIME2INT64(ft) - win2un); |
|
302 |
|
303 if (calibration.freq > 0.0) { |
|
304 long double highresTime, diff; |
|
305 |
|
306 DWORD timeAdjustment, timeIncrement; |
|
307 BOOL timeAdjustmentDisabled; |
|
308 |
|
309 /* Default to 15.625 ms if the syscall fails */ |
|
310 long double skewThreshold = 15625.25; |
|
311 /* Grab high resolution time */ |
|
312 QueryPerformanceCounter(&now); |
|
313 highresTimerValue = (long double)now.QuadPart; |
|
314 |
|
315 MUTEX_LOCK(&calibration.data_lock); |
|
316 highresTime = calibration.offset + PRMJ_USEC_PER_SEC* |
|
317 (highresTimerValue-calibration.timer_offset)/calibration.freq; |
|
318 cachedOffset = calibration.offset; |
|
319 |
|
320 /* On some dual processor/core systems, we might get an earlier time |
|
321 so we cache the last time that we returned */ |
|
322 calibration.last = js::Max(calibration.last, int64_t(highresTime)); |
|
323 returnedTime = calibration.last; |
|
324 MUTEX_UNLOCK(&calibration.data_lock); |
|
325 |
|
326 /* Rather than assume the NT kernel ticks every 15.6ms, ask it */ |
|
327 if (GetSystemTimeAdjustment(&timeAdjustment, |
|
328 &timeIncrement, |
|
329 &timeAdjustmentDisabled)) { |
|
330 if (timeAdjustmentDisabled) { |
|
331 /* timeAdjustment is in units of 100ns */ |
|
332 skewThreshold = timeAdjustment/10.0; |
|
333 } else { |
|
334 /* timeIncrement is in units of 100ns */ |
|
335 skewThreshold = timeIncrement/10.0; |
|
336 } |
|
337 } |
|
338 |
|
339 /* Check for clock skew */ |
|
340 diff = lowresTime - highresTime; |
|
341 |
|
342 /* For some reason that I have not determined, the skew can be |
|
343 up to twice a kernel tick. This does not seem to happen by |
|
344 itself, but I have only seen it triggered by another program |
|
345 doing some kind of file I/O. The symptoms are a negative diff |
|
346 followed by an equally large positive diff. */ |
|
347 if (mozilla::Abs(diff) > 2 * skewThreshold) { |
|
348 /*fprintf(stderr,"Clock skew detected (diff = %f)!\n", diff);*/ |
|
349 |
|
350 if (calibrated) { |
|
351 /* If we already calibrated once this instance, and the |
|
352 clock is still skewed, then either the processor(s) are |
|
353 wildly changing clockspeed or the system is so busy that |
|
354 we get switched out for long periods of time. In either |
|
355 case, it would be infeasible to make use of high |
|
356 resolution results for anything, so let's resort to old |
|
357 behavior for this call. It's possible that in the |
|
358 future, the user will want the high resolution timer, so |
|
359 we don't disable it entirely. */ |
|
360 returnedTime = int64_t(lowresTime); |
|
361 needsCalibration = false; |
|
362 } else { |
|
363 /* It is possible that when we recalibrate, we will return a |
|
364 value less than what we have returned before; this is |
|
365 unavoidable. We cannot tell the different between a |
|
366 faulty QueryPerformanceCounter implementation and user |
|
367 changes to the operating system time. Since we must |
|
368 respect user changes to the operating system time, we |
|
369 cannot maintain the invariant that Date.now() never |
|
370 decreases; the old implementation has this behavior as |
|
371 well. */ |
|
372 needsCalibration = true; |
|
373 } |
|
374 } else { |
|
375 /* No detectable clock skew */ |
|
376 returnedTime = int64_t(highresTime); |
|
377 needsCalibration = false; |
|
378 } |
|
379 } else { |
|
380 /* No high resolution timer is available, so fall back */ |
|
381 returnedTime = int64_t(lowresTime); |
|
382 } |
|
383 } while (needsCalibration); |
|
384 |
|
385 return returnedTime; |
|
386 } |
|
387 #endif |
|
388 |
|
389 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER |
|
390 static void |
|
391 PRMJ_InvalidParameterHandler(const wchar_t *expression, |
|
392 const wchar_t *function, |
|
393 const wchar_t *file, |
|
394 unsigned int line, |
|
395 uintptr_t pReserved) |
|
396 { |
|
397 /* empty */ |
|
398 } |
|
399 #endif |
|
400 |
|
401 /* Format a time value into a buffer. Same semantics as strftime() */ |
|
402 size_t |
|
403 PRMJ_FormatTime(char *buf, int buflen, const char *fmt, PRMJTime *prtm) |
|
404 { |
|
405 size_t result = 0; |
|
406 #if defined(XP_UNIX) || defined(XP_WIN) |
|
407 struct tm a; |
|
408 int fake_tm_year = 0; |
|
409 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER |
|
410 _invalid_parameter_handler oldHandler; |
|
411 int oldReportMode; |
|
412 #endif |
|
413 |
|
414 memset(&a, 0, sizeof(struct tm)); |
|
415 |
|
416 a.tm_sec = prtm->tm_sec; |
|
417 a.tm_min = prtm->tm_min; |
|
418 a.tm_hour = prtm->tm_hour; |
|
419 a.tm_mday = prtm->tm_mday; |
|
420 a.tm_mon = prtm->tm_mon; |
|
421 a.tm_wday = prtm->tm_wday; |
|
422 |
|
423 /* |
|
424 * On systems where |struct tm| has members tm_gmtoff and tm_zone, we |
|
425 * must fill in those values, or else strftime will return wrong results |
|
426 * (e.g., bug 511726, bug 554338). |
|
427 */ |
|
428 #if defined(HAVE_LOCALTIME_R) && defined(HAVE_TM_ZONE_TM_GMTOFF) |
|
429 { |
|
430 /* |
|
431 * Fill out |td| to the time represented by |prtm|, leaving the |
|
432 * timezone fields zeroed out. localtime_r will then fill in the |
|
433 * timezone fields for that local time according to the system's |
|
434 * timezone parameters. |
|
435 */ |
|
436 struct tm td; |
|
437 memset(&td, 0, sizeof(td)); |
|
438 td.tm_sec = prtm->tm_sec; |
|
439 td.tm_min = prtm->tm_min; |
|
440 td.tm_hour = prtm->tm_hour; |
|
441 td.tm_mday = prtm->tm_mday; |
|
442 td.tm_mon = prtm->tm_mon; |
|
443 td.tm_wday = prtm->tm_wday; |
|
444 td.tm_year = prtm->tm_year - 1900; |
|
445 td.tm_yday = prtm->tm_yday; |
|
446 td.tm_isdst = prtm->tm_isdst; |
|
447 time_t t = mktime(&td); |
|
448 localtime_r(&t, &td); |
|
449 |
|
450 a.tm_gmtoff = td.tm_gmtoff; |
|
451 a.tm_zone = td.tm_zone; |
|
452 } |
|
453 #endif |
|
454 |
|
455 /* |
|
456 * Years before 1900 and after 9999 cause strftime() to abort on Windows. |
|
457 * To avoid that we replace it with FAKE_YEAR_BASE + year % 100 and then |
|
458 * replace matching substrings in the strftime() result with the real year. |
|
459 * Note that FAKE_YEAR_BASE should be a multiple of 100 to make 2-digit |
|
460 * year formats (%y) work correctly (since we won't find the fake year |
|
461 * in that case). |
|
462 * e.g. new Date(1873, 0).toLocaleFormat('%Y %y') => "1873 73" |
|
463 * See bug 327869. |
|
464 */ |
|
465 #define FAKE_YEAR_BASE 9900 |
|
466 if (prtm->tm_year < 1900 || prtm->tm_year > 9999) { |
|
467 fake_tm_year = FAKE_YEAR_BASE + prtm->tm_year % 100; |
|
468 a.tm_year = fake_tm_year - 1900; |
|
469 } |
|
470 else { |
|
471 a.tm_year = prtm->tm_year - 1900; |
|
472 } |
|
473 a.tm_yday = prtm->tm_yday; |
|
474 a.tm_isdst = prtm->tm_isdst; |
|
475 |
|
476 /* |
|
477 * Even with the above, SunOS 4 seems to detonate if tm_zone and tm_gmtoff |
|
478 * are null. This doesn't quite work, though - the timezone is off by |
|
479 * tzoff + dst. (And mktime seems to return -1 for the exact dst |
|
480 * changeover time.) |
|
481 */ |
|
482 |
|
483 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER |
|
484 oldHandler = _set_invalid_parameter_handler(PRMJ_InvalidParameterHandler); |
|
485 oldReportMode = _CrtSetReportMode(_CRT_ASSERT, 0); |
|
486 #endif |
|
487 |
|
488 result = strftime(buf, buflen, fmt, &a); |
|
489 |
|
490 #ifdef NS_HAVE_INVALID_PARAMETER_HANDLER |
|
491 _set_invalid_parameter_handler(oldHandler); |
|
492 _CrtSetReportMode(_CRT_ASSERT, oldReportMode); |
|
493 #endif |
|
494 |
|
495 if (fake_tm_year && result) { |
|
496 char real_year[16]; |
|
497 char fake_year[16]; |
|
498 size_t real_year_len; |
|
499 size_t fake_year_len; |
|
500 char* p; |
|
501 |
|
502 sprintf(real_year, "%d", prtm->tm_year); |
|
503 real_year_len = strlen(real_year); |
|
504 sprintf(fake_year, "%d", fake_tm_year); |
|
505 fake_year_len = strlen(fake_year); |
|
506 |
|
507 /* Replace the fake year in the result with the real year. */ |
|
508 for (p = buf; (p = strstr(p, fake_year)); p += real_year_len) { |
|
509 size_t new_result = result + real_year_len - fake_year_len; |
|
510 if ((int)new_result >= buflen) { |
|
511 return 0; |
|
512 } |
|
513 memmove(p + real_year_len, p + fake_year_len, strlen(p + fake_year_len)); |
|
514 memcpy(p, real_year, real_year_len); |
|
515 result = new_result; |
|
516 *(buf + result) = '\0'; |
|
517 } |
|
518 } |
|
519 #endif |
|
520 return result; |
|
521 } |