Bug 896124 - Implement a JS_Init() JSAPI function, to be called before any other JSAPI operations (except JS_SetICUMemoryFunctions) are performed, to centralize whole-engine initialization tasks. r=bsmedberg for the XPCOM bits, r=luke for the JS bits
authorJeff Walden <jwalden@mit.edu>
Fri, 19 Jul 2013 20:14:34 -0700
changeset 152547 e432690bf0fb21911ef15177d36902891f1f8415
parent 152546 3bc8a3f77169b8060a69383af8578e5f9d16ab3e
child 152548 c77439f57dfc0207490e9b450466dc7c421362df
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, luke
bugs896124
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 896124 - Implement a JS_Init() JSAPI function, to be called before any other JSAPI operations (except JS_SetICUMemoryFunctions) are performed, to centralize whole-engine initialization tasks. r=bsmedberg for the XPCOM bits, r=luke for the JS bits
js/src/jsapi-tests/tests.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsfriendapi.h
js/src/shell/js.cpp
js/src/vm/ForkJoin.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/xpconnect/src/XPCJSRuntime.cpp
xpcom/build/nsXPComInit.cpp
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -72,16 +72,21 @@ JSObject * JSAPITest::createGlobal(JSPri
 }
 
 int main(int argc, char *argv[])
 {
     int total = 0;
     int failures = 0;
     const char *filter = (argc == 2) ? argv[1] : NULL;
 
+    if (!JS_Init()) {
+        printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
+        return 1;
+    }
+
     for (JSAPITest *test = JSAPITest::list; test; test = test->next) {
         const char *name = test->name();
         if (filter && strstr(name, filter) == NULL)
             continue;
 
         total += 1;
 
         printf("%s\n", name);
@@ -100,15 +105,17 @@ int main(int argc, char *argv[])
                    (test->knownFail ? "TEST-KNOWN-FAIL" : "TEST-UNEXPECTED-FAIL"),
                    name, (int) messages.length(), messages.begin());
             if (!test->knownFail)
                 failures++;
         }
         test->uninit();
     }
 
+    JS_ShutDown();
+
     if (failures) {
         printf("\n%d unexpected failure%s.\n", failures, (failures == 1 ? "" : "s"));
         return 1;
     }
     printf("\nPassed: ran %d tests.\n", total);
     return 0;
 }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5,19 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * JavaScript API.
  */
 
 #include "jsapi.h"
 
+#include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/PodOperations.h"
-#include "mozilla/ThreadLocal.h"
 
 #include <ctype.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 
 #include "jsarray.h"
@@ -74,16 +74,17 @@
 #include "unicode/uclean.h"
 #include "unicode/utypes.h"
 #endif
 #include "vm/DateObject.h"
 #include "vm/Debugger.h"
 #include "vm/ErrorObject.h"
 #include "vm/Interpreter.h"
 #include "vm/NumericConversions.h"
+#include "vm/Runtime.h"
 #include "vm/Shape.h"
 #include "vm/StopIterationObject.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WeakMapObject.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 #include "yarr/BumpPointerAllocator.h"
@@ -97,16 +98,17 @@
 #include "vm/RegExpStatics-inl.h"
 #include "vm/Shape-inl.h"
 #include "vm/String-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
 
+using mozilla::DebugOnly;
 using mozilla::Maybe;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 
 using js::frontend::Parser;
 
 bool
 JS::detail::CallMethodIfWrapped(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
@@ -627,25 +629,96 @@ JS_PUBLIC_API(JSBool)
 JS_IsBuiltinFunctionConstructor(JSFunction *fun)
 {
     return fun->isBuiltinFunctionConstructor();
 }
 
 /************************************************************************/
 
 /*
- * Has a new runtime ever been created?  This flag is used to control things
- * that should happen only once across all runtimes.
+ * 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.
  */
-static JSBool js_NewRuntimeWasCalled = JS_FALSE;
-
-/*
- * Thread Local Storage slot for storing the runtime for a thread.
- */
-mozilla::ThreadLocal<PerThreadData *> js::TlsPerThreadData;
+enum InitState { Uninitialized, Running, ShutDown };
+static InitState jsInitState = Uninitialized;
+
+JS_PUBLIC_API(JSBool)
+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?");
+
+#ifdef DEBUG
+    // Assert that the numbers associated with the error names in js.msg are
+    // monotonically increasing.  It's not a compile-time check, but it's
+    // better than nothing.
+    int errorNumber = 0;
+#define MSG_DEF(name, number, count, exception, format)                       \
+    JS_ASSERT(name == errorNumber++);
+#include "js.msg"
+#undef MSG_DEF
+
+    // Assert that each message format has the correct number of braced
+    // parameters.
+#define MSG_DEF(name, number, count, exception, format)                       \
+    JS_BEGIN_MACRO                                                            \
+        unsigned numfmtspecs = 0;                                             \
+        for (const char *fmt = format; *fmt != '\0'; fmt++) {                 \
+            if (*fmt == '{' && isdigit(fmt[1]))                               \
+                ++numfmtspecs;                                                \
+        }                                                                     \
+        JS_ASSERT(count == numfmtspecs);                                      \
+    JS_END_MACRO;
+#include "js.msg"
+#undef MSG_DEF
+#endif /* DEBUG */
+
+    using js::TlsPerThreadData;
+    if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init())
+        return false;
+
+#if defined(JS_ION)
+    if (!ion::InitializeIon())
+        return false;
+#endif
+
+    if (!ForkJoinSlice::InitializeTLS())
+        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");
+    MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
+               "forgot to destroy a runtime before shutting down");
+
+    PRMJ_NowShutdown();
+
+    jsInitState = ShutDown;
+}
 
 #ifdef DEBUG
 JS_FRIEND_API(bool)
 JS::isGCEnabled()
 {
     return !TlsPerThreadData.get()->suppressGC;
 }
 #else
@@ -893,16 +966,20 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     parallelWarmup(0),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
     requestedHelperThreadCount(-1)
 #ifdef DEBUG
     , enteredPolicy(NULL)
 #endif
 {
+    MOZ_ASSERT(jsInitState == Running,
+               "must call JS_Init prior to creating any JSRuntimes");
+    liveRuntimesCount++;
+
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodZero(&debugHooks);
     PodZero(&atomState);
 
 #if JS_STACK_GROWTH_DIRECTION > 0
     nativeStackLimit = UINTPTR_MAX;
@@ -1051,42 +1128,45 @@ JSRuntime::~JSRuntime()
 
     if (ionPcScriptCache)
         js_delete(ionPcScriptCache);
 
 #ifdef JSGC_GENERATIONAL
     gcStoreBuffer.disable();
     gcNursery.disable();
 #endif
+
+    DebugOnly<size_t> oldCount = liveRuntimesCount--;
+    JS_ASSERT(oldCount > 0);
 }
 
 #ifdef JS_THREADSAFE
 void
 JSRuntime::setOwnerThread()
 {
     JS_ASSERT(ownerThread_ == (void *)0xc1ea12);  /* "clear" */
     JS_ASSERT(requestDepth == 0);
-    JS_ASSERT(js_NewRuntimeWasCalled);
+    JS_ASSERT(jsInitState == Running);
     JS_ASSERT(js::TlsPerThreadData.get() == NULL);
     ownerThread_ = PR_GetCurrentThread();
     js::TlsPerThreadData.set(&mainThread);
     nativeStackBase = GetNativeStackBase();
     if (nativeStackQuota)
         JS_SetNativeStackQuota(this, nativeStackQuota);
 #ifdef XP_MACOSX
     asmJSMachExceptionHandler.setCurrentThread();
 #endif
 }
 
 void
 JSRuntime::clearOwnerThread()
 {
     assertValidThread();
     JS_ASSERT(requestDepth == 0);
-    JS_ASSERT(js_NewRuntimeWasCalled);
+    JS_ASSERT(jsInitState == Running);
     ownerThread_ = (void *)0xc1ea12;  /* "clear" */
     js::TlsPerThreadData.set(NULL);
     nativeStackBase = 0;
 #if JS_STACK_GROWTH_DIRECTION > 0
     mainThread.nativeStackLimit = UINTPTR_MAX;
 #else
     mainThread.nativeStackLimit = 0;
 #endif
@@ -1112,62 +1192,20 @@ JSRuntime::assertValidThread() const
     JS_ASSERT(js::TlsPerThreadData.get()->associatedWith(this));
 }
 #endif  /* DEBUG */
 #endif  /* JS_THREADSAFE */
 
 JS_PUBLIC_API(JSRuntime *)
 JS_NewRuntime(uint32_t maxbytes, JSUseHelperThreads useHelperThreads)
 {
-    if (!js_NewRuntimeWasCalled) {
-#ifdef DEBUG
-        /*
-         * This code asserts that the numbers associated with the error names
-         * in jsmsg.def are monotonically increasing.  It uses values for the
-         * error names enumerated in jscntxt.c.  It's not a compile-time check
-         * but it's better than nothing.
-         */
-        int errorNumber = 0;
-#define MSG_DEF(name, number, count, exception, format)                       \
-    JS_ASSERT(name == errorNumber++);
-#include "js.msg"
-#undef MSG_DEF
-
-#define MSG_DEF(name, number, count, exception, format)                       \
-    JS_BEGIN_MACRO                                                            \
-        unsigned numfmtspecs = 0;                                                \
-        const char *fmt;                                                      \
-        for (fmt = format; *fmt != '\0'; fmt++) {                             \
-            if (*fmt == '{' && isdigit(fmt[1]))                               \
-                ++numfmtspecs;                                                \
-        }                                                                     \
-        JS_ASSERT(count == numfmtspecs);                                      \
-    JS_END_MACRO;
-#include "js.msg"
-#undef MSG_DEF
-#endif /* DEBUG */
-
-        if (!js::TlsPerThreadData.init())
-            return NULL;
-
-        js_NewRuntimeWasCalled = JS_TRUE;
-    }
-
     JSRuntime *rt = js_new<JSRuntime>(useHelperThreads);
     if (!rt)
         return NULL;
 
-#if defined(JS_ION)
-    if (!ion::InitializeIon())
-        return NULL;
-#endif
-
-    if (!ForkJoinSlice::InitializeTLS())
-        return NULL;
-
     if (!rt->init(maxbytes)) {
         JS_DestroyRuntime(rt);
         return NULL;
     }
 
     return rt;
 }
 
@@ -1176,31 +1214,29 @@ JS_DestroyRuntime(JSRuntime *rt)
 {
     js_free(rt->defaultLocale);
     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 ENABLE_INTL_API
     UErrorCode status = U_ZERO_ERROR;
     u_setMemoryFunctions(/* context = */ NULL, allocFn, reallocFn, freeFn, &status);
     return U_SUCCESS(status);
 #else
     return true;
 #endif
 }
 
-JS_PUBLIC_API(void)
-JS_ShutDown(void)
-{
-    PRMJ_NowShutdown();
-}
-
 JS_PUBLIC_API(void *)
 JS_GetRuntimePrivate(JSRuntime *rt)
 {
     return rt->data;
 }
 
 JS_PUBLIC_API(void)
 JS_SetRuntimePrivate(JSRuntime *rt, void *data)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1765,16 +1765,52 @@ JS_IsBuiltinFunctionConstructor(JSFuncti
  */
 
 typedef enum JSUseHelperThreads
 {
     JS_NO_HELPER_THREADS,
     JS_USE_HELPER_THREADS
 } JSUseHelperThreads;
 
+/**
+ * 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(JSBool)
+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, JSUseHelperThreads useHelperThreads);
 
 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)
@@ -1783,19 +1819,16 @@ typedef void *(*JS_ICUAllocFn)(const voi
 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);
 
-extern JS_PUBLIC_API(void)
-JS_ShutDown(void);
-
 JS_PUBLIC_API(void *)
 JS_GetRuntimePrivate(JSRuntime *rt);
 
 extern JS_PUBLIC_API(JSRuntime *)
 JS_GetRuntime(JSContext *cx);
 
 JS_PUBLIC_API(void)
 JS_SetRuntimePrivate(JSRuntime *rt, void *data);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -199,18 +199,16 @@ JS_DefineFunctionsWithHelp(JSContext *cx
 typedef bool (* JS_SourceHook)(JSContext *cx, JS::Handle<JSScript*> script,
                                jschar **src, uint32_t *length);
 
 extern JS_FRIEND_API(void)
 JS_SetSourceHook(JSRuntime *rt, JS_SourceHook hook);
 
 namespace js {
 
-extern mozilla::ThreadLocal<PerThreadData *> TlsPerThreadData;
-
 inline JSRuntime *
 GetRuntime(const JSContext *cx)
 {
     return ContextFriendFields::get(cx)->runtime_;
 }
 
 inline JSCompartment *
 GetContextCompartment(const JSContext *cx)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5417,16 +5417,20 @@ main(int argc, char **argv, char **envp)
         OOM_printAllocationCount = true;
 
 #if defined(JS_CPU_X86)
     if (op.getBoolOption("no-fpu"))
         JSC::MacroAssembler::SetFloatingPointDisabled();
 #endif
 #endif
 
+    // Start the engine.
+    if (!JS_Init())
+        return 1;
+
     /* Use the same parameters as the browser in xpcjsruntime.cpp. */
     rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS);
     if (!rt)
         return 1;
     gTimeoutFunc = NullValue();
     if (!JS_AddNamedValueRootRT(rt, &gTimeoutFunc, "gTimeoutFunc"))
         return 1;
 
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -1707,19 +1707,19 @@ ForkJoinSlice::check()
     else
         return true;
 }
 
 bool
 ForkJoinSlice::InitializeTLS()
 {
     if (!TLSInitialized) {
+        if (PR_NewThreadPrivateIndex(&ThreadPrivateIndex, NULL) != PR_SUCCESS)
+            return false;
         TLSInitialized = true;
-        PRStatus status = PR_NewThreadPrivateIndex(&ThreadPrivateIndex, NULL);
-        return status == PR_SUCCESS;
     }
     return true;
 }
 
 void
 ForkJoinSlice::requestGC(JS::gcreason::Reason reason)
 {
     shared->requestGC(reason);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -1,17 +1,19 @@
 /* -*- 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/. */
 
 #include "vm/Runtime-inl.h"
 
+#include "mozilla/Atomics.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ThreadLocal.h"
 #include "mozilla/Util.h"
 
 #include <locale.h>
 #include <string.h>
 
 #include "jsatom.h"
 #include "jsgc.h"
 #include "jsmath.h"
@@ -22,17 +24,23 @@
 #include "yarr/BumpPointerAllocator.h"
 
 #include "jscntxtinlines.h"
 #include "jsgcinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
+using mozilla::Atomic;
 using mozilla::PodZero;
+using mozilla::ThreadLocal;
+
+/* static */ ThreadLocal<PerThreadData*> js::TlsPerThreadData;
+
+/* static */ Atomic<size_t> JSRuntime::liveRuntimesCount;
 
 void
 NewObjectCache::clearNurseryObjects(JSRuntime *rt)
 {
     for (unsigned i = 0; i < mozilla::ArrayLength(entries); ++i) {
         Entry &e = entries[i];
         JSObject *obj = reinterpret_cast<JSObject *>(&e.templateObject);
         if (IsInsideNursery(rt, e.key) ||
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -2,20 +2,22 @@
  * 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/. */
 
 #ifndef vm_Runtime_h
 #define vm_Runtime_h
 
+#include "mozilla/Atomics.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TemplateLib.h"
+#include "mozilla/ThreadLocal.h"
 
 #include <setjmp.h>
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jsclist.h"
 #include "jsfriendapi.h"
@@ -37,16 +39,23 @@
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
 #pragma warning(push)
 #pragma warning(disable:4355) /* Silence warning about "this" used in base member initializer list */
 #endif
 
+namespace js {
+
+/* Thread Local Storage slot for storing the runtime for a thread. */
+extern mozilla::ThreadLocal<PerThreadData*> TlsPerThreadData;
+
+} // namespace js
+
 struct DtoaState;
 
 extern void
 js_ReportOutOfMemory(js::ThreadSafeContext *cx);
 
 extern void
 js_ReportAllocationOverflow(js::ThreadSafeContext *cx);
 
@@ -1416,17 +1425,23 @@ struct JSRuntime : public JS::shadow::Ru
     //
     // To allow the GetPropertyCacheT optimization, we allow the ability for
     // GetPropertyCache to override the return value at the top of the stack - the
     // value that will be temporarily corrupt. This special override value is set
     // only in callVM() targets that are about to return *and* have invalidated
     // their callee.
     js::Value            ionReturnOverride_;
 
+    static mozilla::Atomic<size_t> liveRuntimesCount;
+
   public:
+    static bool hasLiveRuntimes() {
+        return liveRuntimesCount > 0;
+    }
+
     bool hasIonReturnOverride() const {
         return !ionReturnOverride_.isMagic();
     }
     js::Value takeIonReturnOverride() {
         js::Value v = ionReturnOverride_;
         ionReturnOverride_ = js::MagicValue(JS_ARG_POISON);
         return v;
     }
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1414,17 +1414,16 @@ XPCJSRuntime::~XPCJSRuntime()
 #ifdef XPC_DUMP_AT_SHUTDOWN
         uint32_t count = mDetachedWrappedNativeProtoMap->Count();
         if (count)
             printf("deleting XPCJSRuntime with %d live detached XPCWrappedNativeProto\n", (int)count);
 #endif
         delete mDetachedWrappedNativeProtoMap;
     }
 
-    JS_ShutDown();
 #ifdef MOZ_ENABLE_PROFILER_SPS
     // Tell the profiler that the runtime is gone
     if (PseudoStack *stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(nullptr);
 #endif
 
 #ifdef DEBUG
     for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) {
@@ -2681,66 +2680,16 @@ SourceHook(JSContext *cx, JS::Handle<JSS
   if (NS_FAILED(rv)) {
     xpc::Throw(cx, rv);
     return false;
   }
 
   return true;
 }
 
-// |sSizeOfICU| can be accessed by multiple JSRuntimes, so it must be
-// thread-safe.
-static Atomic<size_t> sSizeOfICU;
-
-static int64_t
-GetICUSize()
-{
-    return sSizeOfICU;
-}
-
-NS_MEMORY_REPORTER_IMPLEMENT(ICU,
-    "explicit/icu",
-    KIND_HEAP,
-    UNITS_BYTES,
-    GetICUSize,
-    "Memory used by ICU, a Unicode and globalization support library."
-)
-
-NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_ALLOC_FUN(ICUMallocSizeOfOnAlloc)
-NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_FREE_FUN(ICUMallocSizeOfOnFree)
-
-static void *
-ICUAlloc(const void *, size_t size)
-{
-    void *p = malloc(size);
-    sSizeOfICU += ICUMallocSizeOfOnAlloc(p);
-    return p;
-}
-
-static void *
-ICURealloc(const void *, void *p, size_t size)
-{
-    sSizeOfICU -= ICUMallocSizeOfOnFree(p);
-    void *pnew = realloc(p, size);
-    if (pnew) {
-        sSizeOfICU += ICUMallocSizeOfOnAlloc(pnew);
-    } else {
-        // realloc failed;  undo the decrement from above
-        sSizeOfICU += ICUMallocSizeOfOnAlloc(p);
-    }
-    return pnew;
-}
-
-static void
-ICUFree(const void *, void *p)
-{
-    sSizeOfICU -= ICUMallocSizeOfOnFree(p);
-    free(p);
-}
-
 XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
    : CycleCollectedJSRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS, true),
    mJSContextStack(new XPCJSContextStack()),
    mCallContext(nullptr),
    mAutoRoots(nullptr),
    mResolveName(JSID_VOID),
    mResolvingWrapper(nullptr),
    mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_SIZE)),
@@ -2833,30 +2782,26 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     // function compiled with LAZY_SOURCE, it calls SourceHook to load it.
     ///
     // Note we do have to retain the source code in memory for scripts compiled in
     // compileAndGo mode and compiled function bodies (from
     // JS_CompileFunction*). In practice, this means content scripts and event
     // handlers.
     JS_SetSourceHook(runtime, SourceHook);
 
-    if (!JS_SetICUMemoryFunctions(ICUAlloc, ICURealloc, ICUFree))
-        NS_RUNTIMEABORT("JS_SetICUMemoryFunctions failed.");
-
     // Set up locale information and callbacks for the newly-created runtime so
     // that the various toLocaleString() methods, localeCompare(), and other
     // internationalization APIs work as desired.
     if (!xpc_LocalizeRuntime(runtime))
         NS_RUNTIMEABORT("xpc_LocalizeRuntime failed.");
 
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSSystemCompartmentCount));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSUserCompartmentCount));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(JSMainRuntimeTemporaryPeak));
-    NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(ICU));
     NS_RegisterMemoryMultiReporter(new JSCompartmentsMultiReporter);
 
     // Install a JavaScript 'debugger' keyword handler in debug builds only
 #ifdef DEBUG
     if (!JS_GetGlobalDebugHooks(runtime)->debuggerHandler)
         xpc_InstallJSDebuggerKeywordHandler(runtime);
 #endif
 }
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim:set ts=4 sw=4 sts=4 ci et: */
 /* 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/. */
 
 #include "base/basictypes.h"
 
+#include "mozilla/Atomics.h"
 #include "mozilla/Poison.h"
 #include "mozilla/XPCOM.h"
 #include "nsXULAppAPI.h"
 
 #include "nsXPCOMPrivate.h"
 #include "nsXPCOMCIDInternal.h"
 
 #include "prlink.h"
@@ -124,16 +125,18 @@ extern nsresult nsStringInputStreamConst
 #include "mozilla/ClearOnShutdown.h"
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
 #include "mozilla/VisualEventTracer.h"
 #endif
 
 #include "GeckoProfiler.h"
 
+#include "jsapi.h"
+
 using namespace mozilla;
 using base::AtExitManager;
 using mozilla::ipc::BrowserProcessSubThread;
 #ifdef MOZ_VISUAL_EVENT_TRACER
 using mozilla::eventtracer::VisualEventTracer;
 #endif
 
 namespace {
@@ -327,16 +330,62 @@ NS_GetTraceRefcnt(nsITraceRefcnt** resul
 
 EXPORT_XPCOM_API(nsresult)
 NS_InitXPCOM(nsIServiceManager* *result,
                              nsIFile* binDirectory)
 {
     return NS_InitXPCOM2(result, binDirectory, nullptr);
 }
 
+// |sSizeOfICU| can be accessed by multiple JSRuntimes, so it must be
+// thread-safe.
+static Atomic<size_t> sSizeOfICU;
+
+static int64_t
+GetICUSize()
+{
+    return sSizeOfICU;
+}
+
+NS_MEMORY_REPORTER_IMPLEMENT(ICU,
+    "explicit/icu",
+    KIND_HEAP,
+    UNITS_BYTES,
+    GetICUSize,
+    "Memory used by ICU, a Unicode and globalization support library."
+)
+
+NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_ALLOC_FUN(ICUMallocSizeOfOnAlloc)
+NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_FREE_FUN(ICUMallocSizeOfOnFree)
+
+static void*
+ICUAlloc(const void*, size_t size)
+{
+    void* p = malloc(size);
+    sSizeOfICU += ICUMallocSizeOfOnAlloc(p);
+    return p;
+}
+
+static void*
+ICURealloc(const void*, void* p, size_t size)
+{
+    size_t delta = 0 - ICUMallocSizeOfOnFree(p);
+    void* pnew = realloc(p, size);
+    delta += pnew ? ICUMallocSizeOfOnAlloc(pnew) : ICUMallocSizeOfOnAlloc(p);
+    sSizeOfICU += delta;
+    return pnew;
+}
+
+static void
+ICUFree(const void*, void* p)
+{
+    sSizeOfICU -= ICUMallocSizeOfOnFree(p);
+    free(p);
+}
+
 EXPORT_XPCOM_API(nsresult)
 NS_InitXPCOM2(nsIServiceManager* *result,
               nsIFile* binDirectory,
               nsIDirectoryServiceProvider* appFileLocationProvider)
 {
     mozPoisonValueInit();
 
     char aLocal;
@@ -466,16 +515,31 @@ NS_InitXPCOM2(nsIServiceManager* *result
     if (!nsCycleCollector_init()) {
         return NS_ERROR_UNEXPECTED;
     }
 
     // And start it up for this thread too.
     rv = nsCycleCollector_startup(CCSingleThread);
     if (NS_FAILED(rv)) return rv;
 
+    // Register ICU memory functions.  This really shouldn't be necessary: the
+    // JS engine should do this on its own inside JS_Init, and memory-reporting
+    // code should call a JSAPI function to observe ICU memory usage.  But we
+    // can't define the alloc/free functions in the JS engine, because it can't
+    // depend on the XPCOM-based memory reporting goop.  So for now, we have
+    // this oddness.
+    if (!JS_SetICUMemoryFunctions(ICUAlloc, ICURealloc, ICUFree)) {
+        NS_RUNTIMEABORT("JS_SetICUMemoryFunctions failed.");
+    }
+
+    // Initialize the JS engine.
+    if (!JS_Init()) {
+        NS_RUNTIMEABORT("JS_Init failed");
+    }
+
     rv = nsComponentManagerImpl::gComponentManager->Init();
     if (NS_FAILED(rv))
     {
         NS_RELEASE(nsComponentManagerImpl::gComponentManager);
         return rv;
     }
 
     if (result) {
@@ -503,16 +567,20 @@ NS_InitXPCOM2(nsIServiceManager* *result
                                   nullptr,
                                   NS_XPCOM_STARTUP_OBSERVER_ID);
 #ifdef XP_WIN
     CreateAnonTempFileRemover();
 #endif
 
     mozilla::MapsMemoryReporter::Init();
 
+    // The memory reporter manager is up and running -- register a reporter for
+    // ICU's memory usage.
+    NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(ICU));
+
     mozilla::Telemetry::Init();
 
     mozilla::HangMonitor::Startup();
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
     mozilla::eventtracer::Init();
 #endif
 
@@ -695,18 +763,22 @@ ShutdownXPCOM(nsIServiceManager* servMgr
     NS_ShutdownNativeCharsetUtils();
 #endif
 
     // Shutdown xpcom. This will release all loaders and cause others holding
     // a refcount to the component manager to release it.
     if (nsComponentManagerImpl::gComponentManager) {
         rv = (nsComponentManagerImpl::gComponentManager)->Shutdown();
         NS_ASSERTION(NS_SUCCEEDED(rv), "Component Manager shutdown failed.");
-    } else
+    } else {
         NS_WARNING("Component Manager was never created ...");
+    }
+
+    // Shut down the JS engine.
+    JS_ShutDown();
 
     // Release our own singletons
     // Do this _after_ shutting down the component manager, because the
     // JS component loader will use XPConnect to call nsIModule::canUnload,
     // and that will spin up the InterfaceInfoManager again -- bad mojo
     XPTInterfaceInfoManager::FreeInterfaceInfoManager();
 
     // Finally, release the component manager last because it unloads the