Bug 1381976: Part 1 - Use the shared module global for script pre-compilation. r=mccr8 draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 25 Aug 2017 19:36:44 -0700
changeset 661683 93f639022375dd3f0b8e06533e829ce4089d46b7
parent 661682 b0d2e384b6e0842e523b1922659c3a8f0c2d8ee4
child 661684 521043472a824fbcc07288e030f9c36601082c20
push id78842
push usermaglione.k@gmail.com
push dateFri, 08 Sep 2017 20:25:36 +0000
reviewersmccr8
bugs1381976
milestone57.0a1
Bug 1381976: Part 1 - Use the shared module global for script pre-compilation. r=mccr8 When we pre-compile scripts for a different global than they are eventually executed in, we need to clone them into the new global before we can execute them, which can be expensive. This also prevents us from using lazy parsing, since lazy functions are currently eagerly compiled when cloned. Since the vast majority of the scripts compiled by the preloader are executed in the shared modules scope, initially compiling them there removes a lot of startup overhead. For the few that aren't, we don't lose anything by compiling them in the shared module global, but we also don't gain anything over compiling them in the XPConnect compilation scope. MozReview-Commit-ID: CEh42BmIMhL
js/xpconnect/loader/ScriptPreloader.cpp
js/xpconnect/loader/ScriptPreloader.h
js/xpconnect/loader/mozJSComponentLoader.cpp
js/xpconnect/loader/mozJSComponentLoader.h
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "ScriptPreloader-inl.h"
 #include "mozilla/ScriptPreloader.h"
+#include "mozJSComponentLoader.h"
 #include "mozilla/loader/ScriptCacheActors.h"
 
 #include "mozilla/URLPreloader.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Logging.h"
@@ -902,35 +903,41 @@ ScriptPreloader::FinishPendingParses(Mon
 
 void
 ScriptPreloader::DoFinishOffThreadDecode()
 {
     mFinishDecodeRunnablePending = false;
     MaybeFinishOffThreadDecode();
 }
 
+JSObject*
+ScriptPreloader::CompilationScope(JSContext* cx)
+{
+    return mozJSComponentLoader::Get()->CompilationScope(cx);
+}
+
 void
 ScriptPreloader::MaybeFinishOffThreadDecode()
 {
     if (!mToken) {
         return;
     }
 
     auto cleanup = MakeScopeExit([&] () {
         mToken = nullptr;
         mParsingSources.clear();
         mParsingScripts.clear();
 
         DecodeNextBatch(OFF_THREAD_CHUNK_SIZE);
     });
 
-    AutoJSAPI jsapi;
-    MOZ_RELEASE_ASSERT(jsapi.Init(xpc::CompilationScope()));
+    AutoSafeJSAPI jsapi;
+    JSContext* cx = jsapi.cx();
 
-    JSContext* cx = jsapi.cx();
+    JSAutoCompartment ac(cx, CompilationScope(cx));
     JS::Rooted<JS::ScriptVector> jsScripts(cx, JS::ScriptVector(cx));
 
     // If this fails, we still need to mark the scripts as finished. Any that
     // weren't successfully compiled in this operation (which should never
     // happen under ordinary circumstances) will be re-decoded on the main
     // thread, and raise the appropriate errors when they're executed.
     //
     // The exception from the off-thread decode operation will be reported when
@@ -988,19 +995,19 @@ ScriptPreloader::DecodeNextBatch(size_t 
         script->remove();
         size += script->mSize;
     }
 
     if (size == 0 && mPendingScripts.isEmpty()) {
         return;
     }
 
-    AutoJSAPI jsapi;
-    MOZ_RELEASE_ASSERT(jsapi.Init(xpc::CompilationScope()));
+    AutoSafeJSAPI jsapi;
     JSContext* cx = jsapi.cx();
+    JSAutoCompartment ac(cx, CompilationScope(cx));
 
     JS::CompileOptions options(cx, JSVERSION_DEFAULT);
     options.setNoScriptRval(true)
            .setSourceIsLazy(true);
 
     if (!JS::CanCompileOffThread(cx, options, size) ||
         !JS::DecodeMultiOffThreadScripts(cx, options, mParsingSources,
                                          OffThreadDecodeCallback,
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -385,16 +385,21 @@ private:
     JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script);
 
     void DecodeNextBatch(size_t chunkSize);
 
     static void OffThreadDecodeCallback(void* token, void* context);
     void MaybeFinishOffThreadDecode();
     void DoFinishOffThreadDecode();
 
+    // Returns the global scope object for off-thread compilation. When global
+    // sharing is enabled in the component loader, this should be the shared
+    // module global. Otherwise, it should be the XPConnect compilation scope.
+    JSObject* CompilationScope(JSContext* cx);
+
     size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
     {
         return (mallocSizeOf(this) + mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
                 mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
     }
 
     using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedScript>;
 
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -296,16 +296,18 @@ mozJSComponentLoader::sSelf;
 NS_IMPL_ISUPPORTS(mozJSComponentLoader,
                   mozilla::ModuleLoader,
                   xpcIJSModuleLoader,
                   nsIObserver)
 
 nsresult
 mozJSComponentLoader::ReallyInit()
 {
+    MOZ_ASSERT(!mInitialized);
+
     mShareLoaderGlobal = Preferences::GetBool("jsloader.shareGlobal");
 
     nsresult rv;
     nsCOMPtr<nsIObserverService> obsSvc =
         do_GetService(kObserverServiceContractID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = obsSvc->AddObserver(this, "xpcom-shutdown-loaders", false);
@@ -463,17 +465,17 @@ mozJSComponentLoader::SizeOfIncludingThi
     n += SizeOfTableExcludingThis(mImports, aMallocSizeOf);
     n += mLocations.ShallowSizeOfExcludingThis(aMallocSizeOf);
     n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf);
     return n;
 }
 
 void
 mozJSComponentLoader::CreateLoaderGlobal(JSContext* aCx,
-                                         nsACString& aLocation,
+                                         const nsACString& aLocation,
                                          JSAddonId* aAddonID,
                                          MutableHandleObject aGlobal)
 {
     RefPtr<BackstagePass> backstagePass;
     nsresult rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
     NS_ENSURE_SUCCESS_VOID(rv);
 
     CompartmentOptions options;
@@ -546,51 +548,61 @@ mozJSComponentLoader::ReuseGlobal(bool a
     if (FindInReadable(NS_LITERAL_CSTRING("chrome://specialpowers/"), spec)) {
         return false;
     }
 
     return true;
 }
 
 JSObject*
+mozJSComponentLoader::GetSharedGlobal(JSContext* aCx)
+{
+    if (!mLoaderGlobal) {
+        MOZ_ASSERT(mShareLoaderGlobal);
+
+        JS::RootedObject globalObj(aCx);
+        CreateLoaderGlobal(aCx, NS_LITERAL_CSTRING("shared JSM global"),
+                           nullptr, &globalObj);
+
+        // If we fail to create a module global this early, we're not going to
+        // get very far, so just bail out now.
+        MOZ_RELEASE_ASSERT(globalObj);
+        mLoaderGlobal = globalObj;
+
+        // AutoEntryScript required to invoke debugger hook, which is a
+        // Gecko-specific concept at present.
+        dom::AutoEntryScript aes(globalObj,
+                                 "component loader report global");
+        JS_FireOnNewGlobalObject(aes.cx(), globalObj);
+    }
+
+    return mLoaderGlobal;
+}
+
+JSObject*
 mozJSComponentLoader::PrepareObjectForLocation(JSContext* aCx,
                                                nsIFile* aComponentFile,
                                                nsIURI* aURI,
                                                bool* aReuseGlobal,
                                                bool* aRealFile)
 {
     nsAutoCString nativePath;
     NS_ENSURE_SUCCESS(aURI->GetSpec(nativePath), nullptr);
 
     JSAddonId* addonId = MapURIToAddonID(aURI);
     bool reuseGlobal = ReuseGlobal(!!addonId, aURI);
 
     *aReuseGlobal = reuseGlobal;
 
     bool createdNewGlobal = false;
     RootedObject globalObj(aCx);
-    if (reuseGlobal)
-        globalObj = mLoaderGlobal;
-
-    if (!globalObj) {
-        nsAutoCString globalLocation;
-        if (reuseGlobal) {
-            globalLocation.AssignLiteral("shared JSM global");
-        } else {
-            globalLocation.Assign(nativePath);
-        }
-
-        CreateLoaderGlobal(aCx, globalLocation,
-                           addonId, &globalObj);
-        NS_ENSURE_TRUE(globalObj, nullptr);
-
-        if (reuseGlobal) {
-            mLoaderGlobal = globalObj;
-        }
-
+    if (reuseGlobal) {
+        globalObj = GetSharedGlobal(aCx);
+    } else if (!globalObj) {
+        CreateLoaderGlobal(aCx, nativePath, addonId, &globalObj);
         createdNewGlobal = true;
     }
 
     // |thisObj| is the object we set properties on for a particular .jsm.
     RootedObject thisObj(aCx, globalObj);
     NS_ENSURE_TRUE(thisObj, nullptr);
 
     JSAutoCompartment ac(aCx, thisObj);
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -15,22 +15,28 @@
 #include "nsIObserver.h"
 #include "nsIURI.h"
 #include "xpcIJSModuleLoader.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "jsapi.h"
 
 #include "xpcIJSGetFactory.h"
+#include "xpcpublic.h"
 
 class nsIFile;
 class nsIPrincipal;
 class nsIXPConnectJSObjectHolder;
 class ComponentLoaderInfo;
 
+namespace mozilla {
+    class ScriptPreloader;
+} // namespace mozilla
+
+
 /* 6bd13476-1dd2-11b2-bbef-f0ccb5fa64b6 (thanks, mozbot) */
 
 #define MOZJSCOMPONENTLOADER_CID                                              \
   {0x6bd13476, 0x1dd2, 0x11b2,                                                \
     { 0xbb, 0xef, 0xf0, 0xcc, 0xb5, 0xfa, 0x64, 0xb6 }}
 #define MOZJSCOMPONENTLOADER_CONTRACTID "@mozilla.org/moz/jsloader;1"
 
 class mozJSComponentLoader : public mozilla::ModuleLoader,
@@ -66,22 +72,38 @@ class mozJSComponentLoader : public mozi
     virtual ~mozJSComponentLoader();
 
     static mozJSComponentLoader* sSelf;
 
     nsresult ReallyInit();
     void UnloadModules();
 
     void CreateLoaderGlobal(JSContext* aCx,
-                            nsACString& aLocation,
+                            const nsACString& aLocation,
                             JSAddonId* aAddonID,
                             JS::MutableHandleObject aGlobal);
 
     bool ReuseGlobal(bool aIsAddon, nsIURI* aComponent);
 
+    friend class mozilla::ScriptPreloader;
+
+    JSObject* CompilationScope(JSContext* aCx)
+    {
+        if (mLoaderGlobal)
+            return mLoaderGlobal;
+        if ((mInitialized || NS_SUCCEEDED(ReallyInit())) &&
+            mShareLoaderGlobal)
+        {
+            return GetSharedGlobal(aCx);
+        }
+        return xpc::CompilationScope();
+    }
+
+    JSObject* GetSharedGlobal(JSContext* aCx);
+
     JSObject* PrepareObjectForLocation(JSContext* aCx,
                                        nsIFile* aComponentFile,
                                        nsIURI* aComponent,
                                        bool* aReuseGlobal,
                                        bool* aRealFile);
 
     nsresult ObjectForLocation(ComponentLoaderInfo& aInfo,
                                nsIFile* aComponentFile,