From 790d87d4be2821905e2940c76f1e6f630b0bd982 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Fri, 30 Oct 2015 16:14:32 -0700 Subject: [PATCH] Bug 1208808 - Move time zone adjustment information out of JSRuntime, into global state protected by a spinlock. r=till --HG-- extra : rebase_source : b412e298217a2857bf34b73b1128c97cc047e6af --- dom/time/DateCacheCleaner.cpp | 4 +- dom/time/DateCacheCleaner.h | 2 +- js/public/Date.h | 16 +++ js/public/Initialization.h | 84 +++++++++++++++ js/src/gdb/gdb-tests.cpp | 1 + js/src/jsapi-tests/tests.cpp | 1 + js/src/jsapi.cpp | 163 +---------------------------- js/src/jsapi.h | 67 +----------- js/src/jscompartment.cpp | 6 +- js/src/jsdate.cpp | 190 +++++++++++++++++----------------- js/src/moz.build | 2 + js/src/shell/js.cpp | 1 + js/src/vm/DateObject.h | 6 +- js/src/vm/DateTime.cpp | 38 ++++++- js/src/vm/DateTime.h | 68 ++++++++++-- js/src/vm/Initialization.cpp | 166 +++++++++++++++++++++++++++++ js/src/vm/Runtime.cpp | 3 +- js/src/vm/Runtime.h | 1 - js/src/vm/SelfHosting.cpp | 2 +- xpcom/build/XPCOMInit.cpp | 1 + 20 files changed, 481 insertions(+), 341 deletions(-) create mode 100644 js/public/Initialization.h create mode 100644 js/src/vm/Initialization.cpp diff --git a/dom/time/DateCacheCleaner.cpp b/dom/time/DateCacheCleaner.cpp index 0bff04034cba..b2cc03fa8936 100644 --- a/dom/time/DateCacheCleaner.cpp +++ b/dom/time/DateCacheCleaner.cpp @@ -6,7 +6,7 @@ #include "DateCacheCleaner.h" -#include "jsapi.h" +#include "js/Date.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Hal.h" @@ -33,7 +33,7 @@ public: void Notify(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) { mozilla::AutoSafeJSContext cx; - JS_ClearDateCaches(cx); + JS::ResetTimeZone(); } }; diff --git a/dom/time/DateCacheCleaner.h b/dom/time/DateCacheCleaner.h index e96462915212..5d734b2c4442 100644 --- a/dom/time/DateCacheCleaner.h +++ b/dom/time/DateCacheCleaner.h @@ -7,7 +7,7 @@ /* * InitializeDateCacheCleaner registers DateCacheCleaner to * SystemTimeChangeObserver. When time zone is changed, DateCacheCleaner calls - * JS_ClearDateCaches to update the time zone information. + * JS::ResetTimeZone to update the time zone information. */ namespace mozilla { diff --git a/js/public/Date.h b/js/public/Date.h index 2324ad7104cd..f9df9672ce21 100644 --- a/js/public/Date.h +++ b/js/public/Date.h @@ -39,6 +39,22 @@ struct JSContext; namespace JS { +/** + * Re-query the system to determine the current time zone adjustment from UTC, + * including any component due to DST. If the time zone has changed, this will + * cause all Date object non-UTC methods and formatting functions to produce + * appropriately adjusted results. + * + * Left to its own devices, SpiderMonkey itself may occasionally call this + * method to attempt to keep up with system time changes. However, no + * particular frequency of checking is guaranteed. Embedders unable to accept + * occasional inaccuracies should call this method in response to system time + * changes, or immediately before operations requiring instantaneous + * correctness, to guarantee correct behavior. + */ +extern JS_PUBLIC_API(void) +ResetTimeZone(); + class ClippedTime; inline ClippedTime TimeClip(double time); diff --git a/js/public/Initialization.h b/js/public/Initialization.h new file mode 100644 index 000000000000..fa022c9f60de --- /dev/null +++ b/js/public/Initialization.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* SpiderMonkey initialization and shutdown APIs. */ + +#ifndef js_Initialization_h +#define js_Initialization_h + +#include "jstypes.h" + +namespace JS { +namespace detail { + +enum class InitState { Uninitialized = 0, Running, ShutDown }; + +/** + * SpiderMonkey's initialization status is tracked here, and it controls things + * that should happen only once across all runtimes. It's an API requirement + * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware + * manner, so this (internal -- embedders, don't use!) variable doesn't need to + * be atomic. + */ +extern JS_PUBLIC_DATA(InitState) +libraryInitState; + +} // namespace detail +} // namespace JS + +// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and +// |UMemFreeFn| types. The first argument (called |context| in the ICU docs) +// will always be nullptr and should be ignored. +typedef void* (*JS_ICUAllocFn)(const void*, size_t size); +typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size); +typedef void (*JS_ICUFreeFn)(const void*, void* p); + +/** + * This function can be used to track memory used by ICU. If it is called, it + * *must* be called before JS_Init. Don't use it unless you know what you're + * doing! + */ +extern JS_PUBLIC_API(bool) +JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, + JS_ICUReallocFn reallocFn, + JS_ICUFreeFn freeFn); + +/** + * Initialize SpiderMonkey, returning true only if initialization succeeded. + * Once this method has succeeded, it is safe to call JS_NewRuntime and other + * JSAPI methods. + * + * This method must be called before any other JSAPI method is used on any + * thread. Once it has been used, it is safe to call any JSAPI method, and it + * remains safe to do so until JS_ShutDown is correctly called. + * + * It is currently not possible to initialize SpiderMonkey multiple times (that + * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so + * again). This restriction may eventually be lifted. + */ +extern JS_PUBLIC_API(bool) +JS_Init(void); + +/** + * Destroy free-standing resources allocated by SpiderMonkey, not associated + * with any runtime, context, or other structure. + * + * This method should be called after all other JSAPI data has been properly + * cleaned up: every new runtime must have been destroyed, every new context + * must have been destroyed, and so on. Calling this method before all other + * resources have been destroyed has undefined behavior. + * + * Failure to call this method, at present, has no adverse effects other than + * leaking memory. This may not always be the case; it's recommended that all + * embedders call this method when all other JSAPI operations have completed. + * + * It is currently not possible to initialize SpiderMonkey multiple times (that + * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so + * again). This restriction may eventually be lifted. + */ +extern JS_PUBLIC_API(void) +JS_ShutDown(void); + +#endif /* js_Initialization_h */ diff --git a/js/src/gdb/gdb-tests.cpp b/js/src/gdb/gdb-tests.cpp index 9ab81825c70d..c9f74a37191c 100644 --- a/js/src/gdb/gdb-tests.cpp +++ b/js/src/gdb/gdb-tests.cpp @@ -9,6 +9,7 @@ #include "gdb-tests.h" #include "jsapi.h" #include "jsfriendapi.h" +#include "js/Initialization.h" using namespace JS; diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp index 3ed4bf03ef0e..a40c99b738a7 100644 --- a/js/src/jsapi-tests/tests.cpp +++ b/js/src/jsapi-tests/tests.cpp @@ -8,6 +8,7 @@ #include +#include "js/Initialization.h" #include "js/RootingAPI.h" JSAPITest* JSAPITest::list; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index b8818774a4ec..fa4b966a4311 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -62,14 +62,10 @@ #include "js/CharacterEncoding.h" #include "js/Conversions.h" #include "js/Date.h" +#include "js/Initialization.h" #include "js/Proxy.h" #include "js/SliceBudget.h" #include "js/StructuredClone.h" -#if ENABLE_INTL_API -#include "unicode/timezone.h" -#include "unicode/uclean.h" -#include "unicode/utypes.h" -#endif // ENABLE_INTL_API #include "vm/DateObject.h" #include "vm/Debugger.h" #include "vm/ErrorObject.h" @@ -445,129 +441,6 @@ JS_IsBuiltinFunctionConstructor(JSFunction* fun) /************************************************************************/ -/* - * SpiderMonkey's initialization status is tracked here, and it controls things - * that should happen only once across all runtimes. It's an API requirement - * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware - * manner, so this variable doesn't need to be atomic. - * - * The only reason at present for the restriction that you can't call - * JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ NowInit - * initialization code, which uses PR_CallOnce to initialize the PRMJ_Now - * subsystem. (For reinitialization to be permitted, we'd need to "reset" the - * called-once status -- doable, but more trouble than it's worth now.) - * Initializing that subsystem from JS_Init eliminates the problem, but - * initialization can take a comparatively long time (15ms or so), so we - * really don't want to do it in JS_Init, and we really do want to do it only - * when PRMJ_Now is eventually called. - */ -enum InitState { Uninitialized, Running, ShutDown }; -static InitState jsInitState = Uninitialized; - -#ifdef DEBUG -static unsigned -MessageParameterCount(const char* format) -{ - unsigned numfmtspecs = 0; - for (const char* fmt = format; *fmt != '\0'; fmt++) { - if (*fmt == '{' && isdigit(fmt[1])) - ++numfmtspecs; - } - return numfmtspecs; -} - -static void -CheckMessageParameterCounts() -{ - // Assert that each message format has the correct number of braced - // parameters. -# define MSG_DEF(name, count, exception, format) \ - MOZ_ASSERT(MessageParameterCount(format) == count); -# include "js.msg" -# undef MSG_DEF -} -#endif /* DEBUG */ - -JS_PUBLIC_API(bool) -JS_Init(void) -{ - MOZ_ASSERT(jsInitState == Uninitialized, - "must call JS_Init once before any JSAPI operation except " - "JS_SetICUMemoryFunctions"); - MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(), - "how do we have live runtimes before JS_Init?"); - - PRMJ_NowInit(); - -#ifdef DEBUG - CheckMessageParameterCounts(); -#endif - - using js::TlsPerThreadData; - if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init()) - return false; - -#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) - if (!js::oom::InitThreadType()) - return false; - js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN); -#endif - - jit::ExecutableAllocator::initStatic(); - - if (!jit::InitializeIon()) - return false; - -#if EXPOSE_INTL_API - UErrorCode err = U_ZERO_ERROR; - u_init(&err); - if (U_FAILURE(err)) - return false; -#endif // EXPOSE_INTL_API - - if (!CreateHelperThreadsState()) - return false; - - if (!FutexRuntime::initialize()) - return false; - - jsInitState = Running; - return true; -} - -JS_PUBLIC_API(void) -JS_ShutDown(void) -{ - MOZ_ASSERT(jsInitState == Running, - "JS_ShutDown must only be called after JS_Init and can't race with it"); -#ifdef DEBUG - if (JSRuntime::hasLiveRuntimes()) { - // Gecko is too buggy to assert this just yet. - fprintf(stderr, - "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime " - "and everything alive inside it, that is) AT JS_ShutDown " - "TIME. FIX THIS!\n"); - } -#endif - - FutexRuntime::destroy(); - - DestroyHelperThreadsState(); - -#ifdef JS_TRACE_LOGGING - DestroyTraceLoggerThreadState(); - DestroyTraceLoggerGraphState(); -#endif - - PRMJ_NowShutdown(); - -#if EXPOSE_INTL_API - u_cleanup(); -#endif // EXPOSE_INTL_API - - jsInitState = ShutDown; -} - #ifdef DEBUG JS_FRIEND_API(bool) JS::isGCEnabled() @@ -581,7 +454,7 @@ JS_FRIEND_API(bool) JS::isGCEnabled() { return true; } JS_PUBLIC_API(JSRuntime*) JS_NewRuntime(uint32_t maxbytes, uint32_t maxNurseryBytes, JSRuntime* parentRuntime) { - MOZ_ASSERT(jsInitState == Running, + MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running, "must call JS_Init prior to creating any JSRuntimes"); // Make sure that all parent runtimes are the topmost parent. @@ -606,22 +479,6 @@ JS_DestroyRuntime(JSRuntime* rt) js_delete(rt); } -JS_PUBLIC_API(bool) -JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn) -{ - MOZ_ASSERT(jsInitState == Uninitialized, - "must call JS_SetICUMemoryFunctions before any other JSAPI " - "operation (including JS_Init)"); - -#if EXPOSE_INTL_API - UErrorCode status = U_ZERO_ERROR; - u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status); - return U_SUCCESS(status); -#else - return true; -#endif -} - static JS_CurrentEmbedderTimeFunction currentEmbedderTimeFunction; JS_PUBLIC_API(void) @@ -5573,14 +5430,6 @@ JS_ObjectIsDate(JSContext* cx, HandleObject obj, bool* isDate) return true; } -JS_PUBLIC_API(void) -JS_ClearDateCaches(JSContext* cx) -{ - AssertHeapIsIdle(cx); - CHECK_REQUEST(cx); - cx->runtime()->dateTimeInfo.updateTimeZoneAdjustment(); -} - /************************************************************************/ /* @@ -6353,11 +6202,3 @@ JS::GetObjectZone(JSObject* obj) { return obj->zone(); } - -JS_PUBLIC_API(void) -JS::ResetTimeZone() -{ -#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) - icu::TimeZone::recreateDefault(); -#endif -} diff --git a/js/src/jsapi.h b/js/src/jsapi.h index f2471ece15bf..8660e959124e 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -40,9 +40,6 @@ namespace JS { -extern JS_PUBLIC_API(void) -ResetTimeZone(); - class TwoByteChars; #ifdef JS_DEBUG @@ -953,49 +950,14 @@ JS_IsBuiltinFunctionConstructor(JSFunction* fun); /************************************************************************/ /* - * Initialization, locking, contexts, and memory allocation. + * Locking, contexts, and memory allocation. * - * It is important that the first runtime and first context be created in a - * single-threaded fashion, otherwise the behavior of the library is undefined. + * It is important that SpiderMonkey be initialized, and the first runtime and + * first context be created, in a single-threaded fashion. Otherwise the + * behavior of the library is undefined. * See: http://developer.mozilla.org/en/docs/Category:JSAPI_Reference */ -/** - * Initialize SpiderMonkey, returning true only if initialization succeeded. - * Once this method has succeeded, it is safe to call JS_NewRuntime and other - * JSAPI methods. - * - * This method must be called before any other JSAPI method is used on any - * thread. Once it has been used, it is safe to call any JSAPI method, and it - * remains safe to do so until JS_ShutDown is correctly called. - * - * It is currently not possible to initialize SpiderMonkey multiple times (that - * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so - * again). This restriction may eventually be lifted. - */ -extern JS_PUBLIC_API(bool) -JS_Init(void); - -/** - * Destroy free-standing resources allocated by SpiderMonkey, not associated - * with any runtime, context, or other structure. - * - * This method should be called after all other JSAPI data has been properly - * cleaned up: every new runtime must have been destroyed, every new context - * must have been destroyed, and so on. Calling this method before all other - * resources have been destroyed has undefined behavior. - * - * Failure to call this method, at present, has no adverse effects other than - * leaking memory. This may not always be the case; it's recommended that all - * embedders call this method when all other JSAPI operations have completed. - * - * It is currently not possible to initialize SpiderMonkey multiple times (that - * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so - * again). This restriction may eventually be lifted. - */ -extern JS_PUBLIC_API(void) -JS_ShutDown(void); - extern JS_PUBLIC_API(JSRuntime*) JS_NewRuntime(uint32_t maxbytes, uint32_t maxNurseryBytes = JS::DefaultNurseryBytes, @@ -1004,20 +966,6 @@ JS_NewRuntime(uint32_t maxbytes, extern JS_PUBLIC_API(void) JS_DestroyRuntime(JSRuntime* rt); -// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and -// |UMemFreeFn| types. The first argument (called |context| in the ICU docs) -// will always be nullptr, and should be ignored. -typedef void* (*JS_ICUAllocFn)(const void*, size_t size); -typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size); -typedef void (*JS_ICUFreeFn)(const void*, void* p); - -/** - * This function can be used to track memory used by ICU. - * Do not use it unless you know what you are doing! - */ -extern JS_PUBLIC_API(bool) -JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn); - typedef double (*JS_CurrentEmbedderTimeFunction)(); /** @@ -4992,13 +4940,6 @@ JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, extern JS_PUBLIC_API(bool) JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj, bool* isDate); -/** - * Clears the cache of calculated local time from each Date object. - * Call to propagate a system timezone change. - */ -extern JS_PUBLIC_API(void) -JS_ClearDateCaches(JSContext* cx); - /************************************************************************/ /* diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index d5e08532e561..6927d9246a73 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -18,6 +18,7 @@ #include "gc/Marking.h" #include "jit/JitCompartment.h" +#include "js/Date.h" #include "js/Proxy.h" #include "js/RootingAPI.h" #include "proxy/DeadObjectProxy.h" @@ -120,11 +121,10 @@ JSCompartment::init(JSContext* maybecx) * * As a hack, we clear our timezone cache every time we create a new * compartment. This ensures that the cache is always relatively fresh, but - * shouldn't interfere with benchmarks which create tons of date objects + * shouldn't interfere with benchmarks that create tons of date objects * (unless they also create tons of iframes, which seems unlikely). */ - if (maybecx) - maybecx->runtime()->dateTimeInfo.updateTimeZoneAdjustment(); + JS::ResetTimeZone(); if (!crossCompartmentWrappers.init(0)) { if (maybecx) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 96c04fd2d783..963f211593df 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -407,7 +407,7 @@ EquivalentYearForDST(int year) /* ES5 15.9.1.8. */ static double -DaylightSavingTA(double t, DateTimeInfo* dtInfo) +DaylightSavingTA(double t) { if (!IsFinite(t)) return GenericNaN(); @@ -423,29 +423,30 @@ DaylightSavingTA(double t, DateTimeInfo* dtInfo) } int64_t utcMilliseconds = static_cast(t); - int64_t offsetMilliseconds = dtInfo->getDSTOffsetMilliseconds(utcMilliseconds); + int64_t offsetMilliseconds = DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds); return static_cast(offsetMilliseconds); } static double -AdjustTime(double date, DateTimeInfo* dtInfo) +AdjustTime(double date) { - double t = DaylightSavingTA(date, dtInfo) + dtInfo->localTZA(); - t = (dtInfo->localTZA() >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay); + double localTZA = DateTimeInfo::localTZA(); + double t = DaylightSavingTA(date) + localTZA; + t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay); return t; } /* ES5 15.9.1.9. */ static double -LocalTime(double t, DateTimeInfo* dtInfo) +LocalTime(double t) { - return t + AdjustTime(t, dtInfo); + return t + AdjustTime(t); } static double -UTC(double t, DateTimeInfo* dtInfo) +UTC(double t) { - return t - AdjustTime(t - dtInfo->localTZA(), dtInfo); + return t - AdjustTime(t - DateTimeInfo::localTZA()); } /* ES5 15.9.1.10. */ @@ -764,7 +765,7 @@ DaysInMonth(int year, int month) */ template static bool -ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo) +ParseISODate(const CharT* s, size_t length, ClippedTime* result) { size_t i = 0; int tzMul = 1; @@ -864,7 +865,7 @@ ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* d MakeTime(hour, min, sec, frac * 1000.0)); if (isLocalTime) - msec = UTC(msec, dtInfo); + msec = UTC(msec); else msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute); @@ -879,9 +880,9 @@ ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* d template static bool -ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo) +ParseDate(const CharT* s, size_t length, ClippedTime* result) { - if (ParseISODate(s, length, result, dtInfo)) + if (ParseISODate(s, length, result)) return true; if (length == 0) @@ -1144,7 +1145,7 @@ ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtIn double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0)); if (tzOffset == -1) /* no time zone specified, have to use local */ - msec = UTC(msec, dtInfo); + msec = UTC(msec); else msec += tzOffset * msPerMinute; @@ -1153,12 +1154,12 @@ ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtIn } static bool -ParseDate(JSLinearString* s, ClippedTime* result, DateTimeInfo* dtInfo) +ParseDate(JSLinearString* s, ClippedTime* result) { AutoCheckCannotGC nogc; return s->hasLatin1Chars() - ? ParseDate(s->latin1Chars(nogc), s->length(), result, dtInfo) - : ParseDate(s->twoByteChars(nogc), s->length(), result, dtInfo); + ? ParseDate(s->latin1Chars(nogc), s->length(), result) + : ParseDate(s->twoByteChars(nogc), s->length(), result); } static bool @@ -1179,7 +1180,7 @@ date_parse(JSContext* cx, unsigned argc, Value* vp) return false; ClippedTime result; - if (!ParseDate(linearStr, &result, &cx->runtime()->dateTimeInfo)) { + if (!ParseDate(linearStr, &result)) { args.rval().setNaN(); return true; } @@ -1219,17 +1220,17 @@ DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) } void -DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo) +DateObject::fillLocalTimeSlots() { /* Check if the cache is already populated. */ if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() && - getReservedSlot(TZA_SLOT).toDouble() == dtInfo->localTZA()) + getReservedSlot(TZA_SLOT).toDouble() == DateTimeInfo::localTZA()) { return; } /* Remember timezone used to generate the local cache. */ - setReservedSlot(TZA_SLOT, DoubleValue(dtInfo->localTZA())); + setReservedSlot(TZA_SLOT, DoubleValue(DateTimeInfo::localTZA())); double utcTime = UTCTime().toNumber(); @@ -1239,7 +1240,7 @@ DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo) return; } - double localTime = LocalTime(utcTime, dtInfo); + double localTime = LocalTime(utcTime); setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime)); @@ -1349,9 +1350,9 @@ DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo) } inline double -DateObject::cachedLocalTime(DateTimeInfo* dtInfo) +DateObject::cachedLocalTime() { - fillLocalTimeSlots(dtInfo); + fillLocalTimeSlots(); return getReservedSlot(LOCAL_TIME_SLOT).toDouble(); } @@ -1382,7 +1383,7 @@ date_getTime(JSContext* cx, unsigned argc, Value* vp) DateObject::getYear_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT); if (yearVal.isInt32()) { @@ -1407,7 +1408,7 @@ date_getYear(JSContext* cx, unsigned argc, Value* vp) DateObject::getFullYear_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT)); return true; @@ -1442,7 +1443,7 @@ date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) DateObject::getMonth_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT)); return true; @@ -1474,7 +1475,7 @@ date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) DateObject::getDate_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT)); return true; @@ -1509,7 +1510,7 @@ date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) DateObject::getDay_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT)); return true; @@ -1544,7 +1545,7 @@ date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) DateObject::getHours_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_HOURS_SLOT)); return true; @@ -1579,7 +1580,7 @@ date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) DateObject::getMinutes_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_MINUTES_SLOT)); return true; @@ -1616,7 +1617,7 @@ date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) DateObject::getUTCSeconds_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_SECONDS_SLOT)); return true; @@ -1654,7 +1655,7 @@ DateObject::getTimezoneOffset_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); double utctime = dateObj->UTCTime().toNumber(); - double localtime = dateObj->cachedLocalTime(&cx->runtime()->dateTimeInfo); + double localtime = dateObj->cachedLocalTime(); /* * Return the time zone offset in minutes for the current locale that is @@ -1727,25 +1728,27 @@ GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, doub return ToNumber(cx, args[i], mins); } -/* ES5 15.9.5.28. */ +/* ES6 20.3.4.23. */ MOZ_ALWAYS_INLINE bool date_setMilliseconds_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ - double milli; - if (!ToNumber(cx, args.get(0), &milli)) + // Steps 3-4. + double ms; + if (!ToNumber(cx, args.get(0), &ms)) return false; - double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); - /* Step 3. */ - ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo)); + // Step 5. + double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms); - /* Steps 4-5. */ + // Step 6. + ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time))); + + // Steps 7-8. dateObj->setUTCTime(u, args.rval()); return true; } @@ -1793,31 +1796,31 @@ date_setSeconds_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ + // Steps 3-4. double s; if (!ToNumber(cx, args.get(0), &s)) return false; - /* Step 3. */ + // Steps 5-6. double milli; if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) return false; - /* Step 4. */ + // Step 7. double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); - /* Step 5. */ - ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); + // Step 8. + ClippedTime u = TimeClip(UTC(date)); - /* Steps 6-7. */ + // Step 9. dateObj->setUTCTime(u, args.rval()); return true; } -/* ES5 15.9.5.31. */ +/* ES6 20.3.4.26. */ static bool date_setSeconds(JSContext* cx, unsigned argc, Value* vp) { @@ -1867,39 +1870,40 @@ date_setMinutes_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ + // Steps 3-4. double m; if (!ToNumber(cx, args.get(0), &m)) return false; - /* Step 3. */ + // Steps 5-6. double s; if (!GetSecsOrDefault(cx, args, 1, t, &s)) return false; - /* Step 4. */ + // Steps 7-8. double milli; if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) return false; - /* Step 5. */ + // Step 9. double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); - /* Step 6. */ - ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); + // Step 10. + ClippedTime u = TimeClip(UTC(date)); - /* Steps 7-8. */ + // Steps 11-12. dateObj->setUTCTime(u, args.rval()); return true; } -/* ES5 15.9.5.33. */ +/* ES6 20.3.4.24. */ static bool date_setMinutes(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2 (the effectful parts). CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } @@ -1951,36 +1955,36 @@ date_setHours_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ + // Steps 3-4. double h; if (!ToNumber(cx, args.get(0), &h)) return false; - /* Step 3. */ + // Steps 5-6. double m; if (!GetMinsOrDefault(cx, args, 1, t, &m)) return false; - /* Step 4. */ + // Steps 7-8. double s; if (!GetSecsOrDefault(cx, args, 2, t, &s)) return false; - /* Step 5. */ + // Steps 9-10. double milli; if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) return false; - /* Step 6. */ + // Step 11. double date = MakeDate(Day(t), MakeTime(h, m, s, milli)); - /* Step 6. */ - ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); + // Step 12. + ClippedTime u = TimeClip(UTC(date)); - /* Steps 7-8. */ + // Steps 13-14. dateObj->setUTCTime(u, args.rval()); return true; } @@ -2046,7 +2050,7 @@ date_setDate_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + double t = LocalTime(dateObj->UTCTime().toNumber()); /* Step 2. */ double date; @@ -2057,7 +2061,7 @@ date_setDate_impl(JSContext* cx, const CallArgs& args) double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)); /* Step 4. */ - ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); + ClippedTime u = TimeClip(UTC(newDate)); /* Steps 5-6. */ dateObj->setUTCTime(u, args.rval()); @@ -2130,7 +2134,7 @@ date_setMonth_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + double t = LocalTime(dateObj->UTCTime().toNumber()); /* Step 2. */ double m; @@ -2146,7 +2150,7 @@ date_setMonth_impl(JSContext* cx, const CallArgs& args) double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t)); /* Step 5. */ - ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); + ClippedTime u = TimeClip(UTC(newDate)); /* Steps 6-7. */ dateObj->setUTCTime(u, args.rval()); @@ -2198,12 +2202,12 @@ date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) } static double -ThisLocalTimeOrZero(Handle dateObj, DateTimeInfo* dtInfo) +ThisLocalTimeOrZero(Handle dateObj) { double t = dateObj->UTCTime().toNumber(); if (IsNaN(t)) return +0; - return LocalTime(t, dtInfo); + return LocalTime(t); } static double @@ -2220,7 +2224,7 @@ date_setFullYear_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo); + double t = ThisLocalTimeOrZero(dateObj); /* Step 2. */ double y; @@ -2241,7 +2245,7 @@ date_setFullYear_impl(JSContext* cx, const CallArgs& args) double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t)); /* Step 6. */ - ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); + ClippedTime u = TimeClip(UTC(newDate)); /* Steps 7-8. */ dateObj->setUTCTime(u, args.rval()); @@ -2304,7 +2308,7 @@ date_setYear_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo); + double t = ThisLocalTimeOrZero(dateObj); /* Step 2. */ double y; @@ -2326,7 +2330,7 @@ date_setYear_impl(JSContext* cx, const CallArgs& args) double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t)); /* Step 6. */ - double u = UTC(MakeDate(day, TimeWithinDay(t)), &cx->runtime()->dateTimeInfo); + double u = UTC(MakeDate(day, TimeWithinDay(t))); /* Steps 7-8. */ dateObj->setUTCTime(TimeClip(u), args.rval()); @@ -2505,7 +2509,7 @@ date_toJSON(JSContext* cx, unsigned argc, Value* vp) /* for Date.toLocaleFormat; interface to PRMJTime date struct. */ static void -new_explode(double timeval, PRMJTime* split, DateTimeInfo* dtInfo) +new_explode(double timeval, PRMJTime* split) { double year = YearFromTime(timeval); @@ -2521,7 +2525,7 @@ new_explode(double timeval, PRMJTime* split, DateTimeInfo* dtInfo) /* not sure how this affects things, but it doesn't seem to matter. */ - split->tm_isdst = (DaylightSavingTA(timeval, dtInfo) != 0); + split->tm_isdst = (DaylightSavingTA(timeval) != 0); } typedef enum formatspec { @@ -2543,11 +2547,11 @@ date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rv } else { MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date)); - double local = LocalTime(date, &cx->runtime()->dateTimeInfo); + double local = LocalTime(date); /* offset from GMT in minutes. The offset includes daylight savings, if it applies. */ - int minutes = (int) floor(AdjustTime(date, &cx->runtime()->dateTimeInfo) / msPerMinute); + int minutes = (int) floor(AdjustTime(date) / msPerMinute); /* map 510 minutes to 0830 hours */ int offset = (minutes / 60) * 100 + minutes % 60; @@ -2563,7 +2567,7 @@ date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rv /* get a timezone string from the OS to include as a comment. */ - new_explode(date, &split, &cx->runtime()->dateTimeInfo); + new_explode(date, &split); if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) { /* Decide whether to use the resulting timezone string. @@ -2653,9 +2657,9 @@ ToLocaleFormatHelper(JSContext* cx, HandleObject obj, const char* format, Mutabl JS_snprintf(buf, sizeof buf, js_NaN_date_str); } else { int result_len; - double local = LocalTime(utctime, &cx->runtime()->dateTimeInfo); + double local = LocalTime(utctime); PRMJTime split; - new_explode(local, &split, &cx->runtime()->dateTimeInfo); + new_explode(local, &split); /* Let PRMJTime format it. */ result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split); @@ -2673,7 +2677,7 @@ ToLocaleFormatHelper(JSContext* cx, HandleObject obj, const char* format, Mutabl /* ...but not if starts with 4-digit year, like 2022/3/11. */ !(isdigit(buf[0]) && isdigit(buf[1]) && isdigit(buf[2]) && isdigit(buf[3]))) { - double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); + double localtime = obj->as().cachedLocalTime(); int year = IsNaN(localtime) ? 0 : (int) YearFromTime(localtime); JS_snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d", year); @@ -3070,7 +3074,7 @@ DateOneArgument(JSContext* cx, const CallArgs& args) if (!linearStr) return false; - if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo)) + if (!ParseDate(linearStr, &t)) t = ClippedTime::invalid(); } else { double d; @@ -3159,7 +3163,7 @@ DateMultipleArguments(JSContext* cx, const CallArgs& args) double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)); // Steps 3q-t. - return NewDateObject(cx, args, TimeClip(UTC(finalDate, &cx->runtime()->dateTimeInfo))); + return NewDateObject(cx, args, TimeClip(UTC(finalDate))); } return ToDateString(cx, args, NowAsMillis()); @@ -3271,7 +3275,7 @@ js::NewDateObject(JSContext* cx, int year, int mon, int mday, { MOZ_ASSERT(mon < 12); double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0)); - return NewDateObjectMsec(cx, TimeClip(UTC(msec_time, &cx->runtime()->dateTimeInfo))); + return NewDateObjectMsec(cx, TimeClip(UTC(msec_time))); } JS_FRIEND_API(bool) diff --git a/js/src/moz.build b/js/src/moz.build index 36c653f952ca..146f44e2055d 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -114,6 +114,7 @@ EXPORTS.js += [ '../public/HashTable.h', '../public/HeapAPI.h', '../public/Id.h', + '../public/Initialization.h', '../public/LegacyIntTypes.h', '../public/MemoryMetrics.h', '../public/Principals.h', @@ -362,6 +363,7 @@ SOURCES += [ 'jsatom.cpp', 'jsmath.cpp', 'jsutil.cpp', + 'vm/Initialization.cpp', ] if CONFIG['JS_POSIX_NSPR']: diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a4e78055b4a1..f7841a8159f9 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -67,6 +67,7 @@ #include "jit/OptimizationTracking.h" #include "js/Debug.h" #include "js/GCAPI.h" +#include "js/Initialization.h" #include "js/StructuredClone.h" #include "js/TrackedOptimizationInfo.h" #include "perf/jsperf.h" diff --git a/js/src/vm/DateObject.h b/js/src/vm/DateObject.h index 7b28b9db33b4..b0f43d46e3c1 100644 --- a/js/src/vm/DateObject.h +++ b/js/src/vm/DateObject.h @@ -14,8 +14,6 @@ namespace js { -class DateTimeInfo; - class DateObject : public NativeObject { static const uint32_t UTC_TIME_SLOT = 0; @@ -58,12 +56,12 @@ class DateObject : public NativeObject void setUTCTime(JS::ClippedTime t); void setUTCTime(JS::ClippedTime t, MutableHandleValue vp); - inline double cachedLocalTime(DateTimeInfo* dtInfo); + inline double cachedLocalTime(); // Cache the local time, year, month, and so forth of the object. // If UTC time is not finite (e.g., NaN), the local time // slots will be set to the UTC time without conversion. - void fillLocalTimeSlots(DateTimeInfo* dtInfo); + void fillLocalTimeSlots(); static MOZ_ALWAYS_INLINE bool getTime_impl(JSContext* cx, const CallArgs& args); static MOZ_ALWAYS_INLINE bool getYear_impl(JSContext* cx, const CallArgs& args); diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp index a044dc1276fc..4dd6d408e58d 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -10,8 +10,19 @@ #include "jsutil.h" +#include "js/Date.h" +#if ENABLE_INTL_API +#include "unicode/timezone.h" +#endif + using mozilla::UnspecifiedNaN; +/* static */ js::DateTimeInfo +js::DateTimeInfo::instance; + +/* static */ mozilla::Atomic +js::DateTimeInfo::AcquireLock::spinLock; + static bool ComputeLocalTime(time_t local, struct tm* ptm) { @@ -131,7 +142,7 @@ UTCToLocalStandardOffsetSeconds() } void -js::DateTimeInfo::updateTimeZoneAdjustment() +js::DateTimeInfo::internalUpdateTimeZoneAdjustment() { /* * The difference between local standard time and UTC will never change for @@ -164,12 +175,19 @@ js::DateTimeInfo::updateTimeZoneAdjustment() * negative numbers to ensure the first computation is always a cache miss and * doesn't return a bogus offset. */ -js::DateTimeInfo::DateTimeInfo() +/* static */ void +js::DateTimeInfo::init() { + DateTimeInfo* dtInfo = &DateTimeInfo::instance; + + MOZ_ASSERT(dtInfo->localTZA_ == 0, + "we should be initializing only once, and the static instance " + "should have started out zeroed"); + // Set to a totally impossible TZA so that the comparison above will fail // and all fields will be properly initialized. - localTZA_ = UnspecifiedNaN(); - updateTimeZoneAdjustment(); + dtInfo->localTZA_ = UnspecifiedNaN(); + dtInfo->internalUpdateTimeZoneAdjustment(); } int64_t @@ -201,7 +219,7 @@ js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) } int64_t -js::DateTimeInfo::getDSTOffsetMilliseconds(int64_t utcMilliseconds) +js::DateTimeInfo::internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds) { sanityCheck(); @@ -288,3 +306,13 @@ js::DateTimeInfo::sanityCheck() MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN, rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT); } + +JS_PUBLIC_API(void) +JS::ResetTimeZone() +{ + js::DateTimeInfo::updateTimeZoneAdjustment(); + +#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) + icu::TimeZone::recreateDefault(); +#endif +} diff --git a/js/src/vm/DateTime.h b/js/src/vm/DateTime.h index 4ec477b3ee61..5c68ca13d07e 100644 --- a/js/src/vm/DateTime.h +++ b/js/src/vm/DateTime.h @@ -7,12 +7,17 @@ #ifndef vm_DateTime_h #define vm_DateTime_h +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" #include "mozilla/FloatingPoint.h" #include "mozilla/MathAlgorithms.h" #include #include "js/Conversions.h" +#include "js/Date.h" +#include "js/Initialization.h" #include "js/Value.h" namespace js { @@ -88,23 +93,71 @@ const double EndOfTime = 8.64e15; */ class DateTimeInfo { - public: - DateTimeInfo(); + static DateTimeInfo instance; + // Date/time info is shared across all threads in DateTimeInfo::instance, + // for consistency with ICU's handling of its default time zone. Thus we + // need something to protect concurrent accesses. + // + // The spec implicitly assumes DST and time zone adjustment information + // never change in the course of a function -- sometimes even across + // reentrancy. So make critical sections as narrow as possible, and use a + // bog-standard spinlock with busy-waiting in case of contention for + // simplicity. + class MOZ_RAII AcquireLock + { + static mozilla::Atomic spinLock; + + public: + AcquireLock() { + while (!spinLock.compareExchange(false, true)) + continue; + } + ~AcquireLock() { + MOZ_ASSERT(spinLock, "spinlock should have been acquired"); + spinLock = false; + } + }; + + friend bool ::JS_Init(); + + // Initialize global date/time tracking state. This operation occurs + // during, and is restricted to, SpiderMonkey initialization. + static void init(); + + public: /* * Get the DST offset in milliseconds at a UTC time. This is usually * either 0 or |msPerSecond * SecondsPerHour|, but at least one exotic time * zone (Lord Howe Island, Australia) has a fractional-hour offset, just to * keep things interesting. */ - int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds); + static int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds) { + AcquireLock lock; - void updateTimeZoneAdjustment(); + return DateTimeInfo::instance.internalGetDSTOffsetMilliseconds(utcMilliseconds); + } /* ES5 15.9.1.7. */ - double localTZA() { return localTZA_; } + static double localTZA() { + AcquireLock lock; + + return DateTimeInfo::instance.localTZA_; + } private: + // We don't want anyone accidentally calling *only* + // DateTimeInfo::updateTimeZoneAdjustment() to respond to a system time + // zone change (missing the necessary poking of ICU as well), so ensure + // only JS::ResetTimeZone() can call this via access restrictions. + friend void JS::ResetTimeZone(); + + static void updateTimeZoneAdjustment() { + AcquireLock lock; + + DateTimeInfo::instance.internalUpdateTimeZoneAdjustment(); + } + /* * The current local time zone adjustment, cached because retrieving this * dynamically is Slow, and a certain venerable benchmark which shall not @@ -113,7 +166,7 @@ class DateTimeInfo * SpiderMonkey occasionally and arbitrarily updates this value from the * system time zone to attempt to keep this reasonably up-to-date. If * temporary inaccuracy can't be tolerated, JSAPI clients may call - * JS_ClearDateCaches to forcibly sync this with the system time zone. + * JS::ResetTimeZone to forcibly sync this with the system time zone. */ double localTZA_; @@ -141,6 +194,9 @@ class DateTimeInfo static const int64_t RangeExpansionAmount = 30 * SecondsPerDay; + int64_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds); + void internalUpdateTimeZoneAdjustment(); + void sanityCheck(); }; diff --git a/js/src/vm/Initialization.cpp b/js/src/vm/Initialization.cpp new file mode 100644 index 000000000000..cf5d21aa230f --- /dev/null +++ b/js/src/vm/Initialization.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* SpiderMonkey initialization and shutdown code. */ + +#include "js/Initialization.h" + +#include "mozilla/Assertions.h" + +#include + +#include "jstypes.h" + +#include "builtin/AtomicsObject.h" +#include "jit/ExecutableAllocator.h" +#include "jit/Ion.h" +#include "js/Utility.h" +#if ENABLE_INTL_API +#include "unicode/uclean.h" +#include "unicode/utypes.h" +#endif // ENABLE_INTL_API +#include "vm/DateTime.h" +#include "vm/HelperThreads.h" +#include "vm/Runtime.h" +#include "vm/Time.h" +#include "vm/TraceLogging.h" + +using JS::detail::InitState; +using JS::detail::libraryInitState; +using js::FutexRuntime; + +InitState JS::detail::libraryInitState; + +#ifdef DEBUG +static unsigned +MessageParameterCount(const char* format) +{ + unsigned numfmtspecs = 0; + for (const char* fmt = format; *fmt != '\0'; fmt++) { + if (*fmt == '{' && isdigit(fmt[1])) + ++numfmtspecs; + } + return numfmtspecs; +} + +static void +CheckMessageParameterCounts() +{ + // Assert that each message format has the correct number of braced + // parameters. +# define MSG_DEF(name, count, exception, format) \ + MOZ_ASSERT(MessageParameterCount(format) == count); +# include "js.msg" +# undef MSG_DEF +} +#endif /* DEBUG */ + +JS_PUBLIC_API(bool) +JS_Init(void) +{ + MOZ_ASSERT(libraryInitState == InitState::Uninitialized, + "must call JS_Init once before any JSAPI operation except " + "JS_SetICUMemoryFunctions"); + MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(), + "how do we have live runtimes before JS_Init?"); + + PRMJ_NowInit(); + +#ifdef DEBUG + CheckMessageParameterCounts(); +#endif + + using js::TlsPerThreadData; + if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init()) + return false; + +#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) + if (!js::oom::InitThreadType()) + return false; + js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN); +#endif + + js::jit::ExecutableAllocator::initStatic(); + + if (!js::jit::InitializeIon()) + return false; + + js::DateTimeInfo::init(); + +#if EXPOSE_INTL_API + UErrorCode err = U_ZERO_ERROR; + u_init(&err); + if (U_FAILURE(err)) + return false; +#endif // EXPOSE_INTL_API + + if (!js::CreateHelperThreadsState()) + return false; + + if (!FutexRuntime::initialize()) + return false; + + libraryInitState = InitState::Running; + return true; +} + +JS_PUBLIC_API(void) +JS_ShutDown(void) +{ + MOZ_ASSERT(libraryInitState == InitState::Running, + "JS_ShutDown must only be called after JS_Init and can't race with it"); +#ifdef DEBUG + if (JSRuntime::hasLiveRuntimes()) { + // Gecko is too buggy to assert this just yet. + fprintf(stderr, + "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime " + "and everything alive inside it, that is) AT JS_ShutDown " + "TIME. FIX THIS!\n"); + } +#endif + + FutexRuntime::destroy(); + + js::DestroyHelperThreadsState(); + +#ifdef JS_TRACE_LOGGING + js::DestroyTraceLoggerThreadState(); + js::DestroyTraceLoggerGraphState(); +#endif + + // The only difficult-to-address reason for the restriction that you can't + // call JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ + // NowInit initialization code, which uses PR_CallOnce to initialize the + // PRMJ_Now subsystem. (For reinitialization to be permitted, we'd need to + // "reset" the called-once status -- doable, but more trouble than it's + // worth now.) Initializing that subsystem from JS_Init eliminates the + // problem, but initialization can take a comparatively long time (15ms or + // so), so we really don't want to do it in JS_Init, and we really do want + // to do it only when PRMJ_Now is eventually called. + PRMJ_NowShutdown(); + +#if EXPOSE_INTL_API + u_cleanup(); +#endif // EXPOSE_INTL_API + + libraryInitState = InitState::ShutDown; +} + +JS_PUBLIC_API(bool) +JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn) +{ + MOZ_ASSERT(libraryInitState == InitState::Uninitialized, + "must call JS_SetICUMemoryFunctions before any other JSAPI " + "operation (including JS_Init)"); + +#if EXPOSE_INTL_API + UErrorCode status = U_ZERO_ERROR; + u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status); + return U_SUCCESS(status); +#else + return true; +#endif +} diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 298ee115ff67..734c7ea85c17 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -45,6 +45,7 @@ #include "jit/JitCompartment.h" #include "jit/mips32/Simulator-mips32.h" #include "jit/PcScriptCache.h" +#include "js/Date.h" #include "js/MemoryMetrics.h" #include "js/SliceBudget.h" #include "vm/Debugger.h" @@ -333,7 +334,7 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) if (!InitRuntimeNumberState(this)) return false; - dateTimeInfo.updateTimeZoneAdjustment(); + JS::ResetTimeZone(); #ifdef JS_SIMULATOR simulator_ = js::jit::Simulator::Create(); diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index afaecaac9452..bce003128ba2 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1205,7 +1205,6 @@ struct JSRuntime : public JS::shadow::Runtime, js::LazyScriptCache lazyScriptCache; js::CompressedSourceSet compressedSourceSet; - js::DateTimeInfo dateTimeInfo; // Pool of maps used during parse/emit. This may be modified by threads // with an ExclusiveContext and requires a lock. Active compilations diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index b7840d9bfd15..a1e93f7900be 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1263,7 +1263,7 @@ intrinsic_LocalTZA(JSContext* cx, unsigned argc, Value* vp) CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 0, "the LocalTZA intrinsic takes no arguments"); - args.rval().setDouble(cx->runtime()->dateTimeInfo.localTZA()); + args.rval().setDouble(DateTimeInfo::localTZA()); return true; } diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index 7e4e00adcd2a..d2c8b6d9e3b5 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -155,6 +155,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports*, REFNSIID, void**); #include "GeckoProfiler.h" #include "jsapi.h" +#include "js/Initialization.h" #include "gfxPlatform.h"