Bug 1286159 - Clean up FinishOffThread* APIs for off-thread parsing. r=jonco
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 19 Jul 2016 09:19:54 +0200
changeset 330749 717184f2678e716b237a331128182869da72f943
parent 330748 ad32bf1994e92f6cbe3e54f30e5f8f804f9efb44
child 330750 727a13778dcb23aa2d264aeb8668502b27249699
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1286159
milestone50.0a1
Bug 1286159 - Clean up FinishOffThread* APIs for off-thread parsing. r=jonco
dom/base/nsJSUtils.cpp
dom/base/nsScriptLoader.cpp
dom/xul/nsXULElement.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
js/xpconnect/loader/mozJSSubScriptLoader.cpp
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -190,17 +190,17 @@ nsJSUtils::EvaluateString(JSContext* aCx
       if (!JS_WrapObject(aCx, scopeChain[i])) {
         ok = false;
         break;
       }
     }
 
     if (ok && aOffThreadToken) {
       JS::Rooted<JSScript*>
-        script(aCx, JS::FinishOffThreadScript(aCx, JS_GetRuntime(aCx), *aOffThreadToken));
+        script(aCx, JS::FinishOffThreadScript(aCx, *aOffThreadToken));
       *aOffThreadToken = nullptr; // Mark the token as having been finished.
       if (script) {
         ok = JS_ExecuteScript(aCx, scopeChain, script);
       } else {
         ok = false;
       }
     } else if (ok) {
       ok = JS::Evaluate(aCx, scopeChain, aCompileOptions, aSrcBuf, aRetValue);
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -703,18 +703,17 @@ nsScriptLoader::CreateModuleScript(nsMod
       masterScriptUpdater.emplace(master->ScriptLoader(),
                                   aRequest->mElement);
     }
 
     JSContext* cx = aes.cx();
     JS::Rooted<JSObject*> module(cx);
 
     if (aRequest->mWasCompiledOMT) {
-      module = JS::FinishOffThreadModule(cx, xpc::GetJSRuntime(),
-                                         aRequest->mOffThreadToken);
+      module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
       aRequest->mOffThreadToken = nullptr;
       rv = module ? NS_OK : NS_ERROR_FAILURE;
     } else {
       JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
 
       JS::CompileOptions options(cx);
       FillCompileOptionsForRequest(aes, aRequest, global, &options);
 
@@ -1824,17 +1823,18 @@ nsScriptLoader::ProcessRequest(nsScriptL
   }
 
   if (aRequest->mOffThreadToken) {
     // The request was parsed off-main-thread, but the result of the off
     // thread parse was not actually needed to process the request
     // (disappearing window, some other error, ...). Finish the
     // request to avoid leaks in the JS engine.
     MOZ_ASSERT(!aRequest->IsModuleRequest());
-    JS::FinishOffThreadScript(nullptr, xpc::GetJSRuntime(), aRequest->mOffThreadToken);
+    JSContext* cx = JS_GetContext(xpc::GetJSRuntime());
+    JS::CancelOffThreadScript(cx, aRequest->mOffThreadToken);
     aRequest->mOffThreadToken = nullptr;
   }
 
   // Free any source data.
   free(aRequest->mScriptTextBuf);
   aRequest->mScriptTextBuf = nullptr;
   aRequest->mScriptTextLength = 0;
 
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -2763,17 +2763,17 @@ NotifyOffThreadScriptCompletedRunnable::
     {
         AutoJSAPI jsapi;
         if (!jsapi.Init(xpc::CompilationScope())) {
             // Now what?  I guess we just leak... this should probably never
             // happen.
             return NS_ERROR_UNEXPECTED;
         }
         JSContext* cx = jsapi.cx();
-        script = JS::FinishOffThreadScript(cx, JS_GetRuntime(cx), mToken);
+        script = JS::FinishOffThreadScript(cx, mToken);
     }
 
     if (!sReceivers) {
         // We've already shut down.
         return NS_OK;
     }
 
     auto index = sReceivers->IndexOf(mReceiver);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4076,36 +4076,54 @@ JS::CompileOffThread(JSContext* cx, cons
                      const char16_t* chars, size_t length,
                      OffThreadCompileCallback callback, void* callbackData)
 {
     MOZ_ASSERT(CanCompileOffThread(cx, options, length));
     return StartOffThreadParseScript(cx, options, chars, length, callback, callbackData);
 }
 
 JS_PUBLIC_API(JSScript*)
-JS::FinishOffThreadScript(JSContext* maybecx, JSRuntime* rt, void* token)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
-    return HelperThreadState().finishScriptParseTask(maybecx, rt, token);
+JS::FinishOffThreadScript(JSContext* cx, void* token)
+{
+    MOZ_ASSERT(cx);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx));
+    return HelperThreadState().finishScriptParseTask(cx, token);
+}
+
+JS_PUBLIC_API(void)
+JS::CancelOffThreadScript(JSContext* cx, void* token)
+{
+    MOZ_ASSERT(cx);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx));
+    HelperThreadState().cancelParseTask(cx, ParseTaskKind::Script, token);
 }
 
 JS_PUBLIC_API(bool)
 JS::CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                            const char16_t* chars, size_t length,
                            OffThreadCompileCallback callback, void* callbackData)
 {
     MOZ_ASSERT(CanCompileOffThread(cx, options, length));
     return StartOffThreadParseModule(cx, options, chars, length, callback, callbackData);
 }
 
 JS_PUBLIC_API(JSObject*)
-JS::FinishOffThreadModule(JSContext* maybecx, JSRuntime* rt, void* token)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
-    return HelperThreadState().finishModuleParseTask(maybecx, rt, token);
+JS::FinishOffThreadModule(JSContext* cx, void* token)
+{
+    MOZ_ASSERT(cx);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx));
+    return HelperThreadState().finishModuleParseTask(cx, token);
+}
+
+JS_PUBLIC_API(void)
+JS::CancelOffThreadModule(JSContext* cx, void* token)
+{
+    MOZ_ASSERT(cx);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx));
+    HelperThreadState().cancelParseTask(cx, ParseTaskKind::Module, token);
 }
 
 JS_PUBLIC_API(bool)
 JS_CompileScript(JSContext* cx, const char* ascii, size_t length,
                  const JS::CompileOptions& options, MutableHandleScript script)
 {
     return Compile(cx, options, ascii, length, script);
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4045,41 +4045,48 @@ extern JS_PUBLIC_API(bool)
 CanCompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options, size_t length);
 
 /*
  * Off thread compilation control flow.
  *
  * After successfully triggering an off thread compile of a script, the
  * callback will eventually be invoked with the specified data and a token
  * for the compilation. The callback will be invoked while off the main thread,
- * so must ensure that its operations are thread safe. Afterwards,
- * FinishOffThreadScript must be invoked on the main thread to get the result
- * script or nullptr. If maybecx is not specified, the resources will be freed,
- * but no script will be returned.
+ * so must ensure that its operations are thread safe. Afterwards, one of the
+ * following functions must be invoked on the main thread:
+ *
+ * - FinishOffThreadScript, to get the result script (or nullptr on failure).
+ * - CancelOffThreadScript, to free the resources without creating a script.
  *
  * The characters passed in to CompileOffThread must remain live until the
  * callback is invoked, and the resulting script will be rooted until the call
  * to FinishOffThreadScript.
  */
 
 extern JS_PUBLIC_API(bool)
 CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options,
                  const char16_t* chars, size_t length,
                  OffThreadCompileCallback callback, void* callbackData);
 
 extern JS_PUBLIC_API(JSScript*)
-FinishOffThreadScript(JSContext* maybecx, JSRuntime* rt, void* token);
+FinishOffThreadScript(JSContext* cx, void* token);
+
+extern JS_PUBLIC_API(void)
+CancelOffThreadScript(JSContext* cx, void* token);
 
 extern JS_PUBLIC_API(bool)
 CompileOffThreadModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                        const char16_t* chars, size_t length,
                        OffThreadCompileCallback callback, void* callbackData);
 
 extern JS_PUBLIC_API(JSObject*)
-FinishOffThreadModule(JSContext* maybecx, JSRuntime* rt, void* token);
+FinishOffThreadModule(JSContext* cx, void* token);
+
+extern JS_PUBLIC_API(void)
+CancelOffThreadModule(JSContext* cx, void* token);
 
 /**
  * Compile a function with scopeChain plus the global as its scope chain.
  * scopeChain must contain objects in the current compartment of cx.  The actual
  * scope chain used for the function will consist of With wrappers for those
  * objects, followed by the current global of the compartment cx is in.  This
  * global must not be explicitly included in the scope chain.
  */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3923,27 +3923,26 @@ OffThreadCompileScript(JSContext* cx, un
     return true;
 }
 
 static bool
 runOffThreadScript(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    JSRuntime* rt = cx->runtime();
-    if (OffThreadParsingMustWaitForGC(rt))
+    if (OffThreadParsingMustWaitForGC(cx))
         gc::FinishGC(cx);
 
     void* token = offThreadState.waitUntilDone(cx, ScriptKind::Script);
     if (!token) {
         JS_ReportError(cx, "called runOffThreadScript when no compilation is pending");
         return false;
     }
 
-    RootedScript script(cx, JS::FinishOffThreadScript(cx, rt, token));
+    RootedScript script(cx, JS::FinishOffThreadScript(cx, token));
     if (!script)
         return false;
 
     return JS_ExecuteScript(cx, script, args.rval());
 }
 
 static bool
 OffThreadCompileModule(JSContext* cx, unsigned argc, Value* vp)
@@ -4009,27 +4008,26 @@ OffThreadCompileModule(JSContext* cx, un
     return true;
 }
 
 static bool
 FinishOffThreadModule(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    JSRuntime* rt = cx->runtime();
-    if (OffThreadParsingMustWaitForGC(rt))
+    if (OffThreadParsingMustWaitForGC(cx))
         gc::FinishGC(cx);
 
     void* token = offThreadState.waitUntilDone(cx, ScriptKind::Module);
     if (!token) {
         JS_ReportError(cx, "called finishOffThreadModule when no compilation is pending");
         return false;
     }
 
-    RootedObject module(cx, JS::FinishOffThreadModule(cx, rt, token));
+    RootedObject module(cx, JS::FinishOffThreadModule(cx, token));
     if (!module)
         return false;
 
     args.rval().setObject(*module);
     return true;
 }
 
 struct MOZ_RAII FreeOnReturn
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -341,18 +341,17 @@ js::CancelOffThreadParses(JSRuntime* rt)
     GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList();
     while (true) {
         bool found = false;
         for (size_t i = 0; i < finished.length(); i++) {
             ParseTask* task = finished[i];
             if (task->runtimeMatches(rt)) {
                 found = true;
                 AutoUnlockHelperThreadState unlock(lock);
-                HelperThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task->kind,
-                                                    task);
+                HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
             }
         }
         if (!found)
             break;
     }
 }
 
 bool
@@ -1114,60 +1113,59 @@ static void
 LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
 {
     // Mark the zone as no longer in use by an ExclusiveContext, and available
     // to be collected by the GC.
     task->cx->leaveCompartment(task->cx->compartment());
     rt->clearUsedByExclusiveThread(task->cx->zone());
 }
 
-JSScript*
-GlobalHelperThreadState::finishParseTask(JSContext* maybecx, JSRuntime* rt, ParseTaskKind kind,
-                                         void* token)
+ParseTask*
+GlobalHelperThreadState::removeFinishedParseTask(ParseTaskKind kind, void* token)
 {
-    ScopedJSDeletePtr<ParseTask> parseTask;
-
     // The token is a ParseTask* which should be in the finished list.
     // Find and remove its entry.
-    {
-        AutoLockHelperThreadState lock;
-        ParseTaskVector& finished = parseFinishedList();
-        for (size_t i = 0; i < finished.length(); i++) {
-            if (finished[i] == token) {
-                parseTask = finished[i];
-                remove(finished, &i);
-                break;
-            }
+
+    AutoLockHelperThreadState lock;
+    ParseTaskVector& finished = parseFinishedList();
+
+    for (size_t i = 0; i < finished.length(); i++) {
+        if (finished[i] == token) {
+            ParseTask* parseTask = finished[i];
+            remove(finished, &i);
+            MOZ_ASSERT(parseTask);
+            MOZ_ASSERT(parseTask->kind == kind);
+            return parseTask;
         }
     }
-    MOZ_ASSERT(parseTask);
-    MOZ_ASSERT(parseTask->kind == kind);
+
+    MOZ_CRASH("Invalid ParseTask token");
+}
 
-    if (!maybecx) {
-        LeaveParseTaskZone(rt, parseTask);
-        return nullptr;
-    }
+JSScript*
+GlobalHelperThreadState::finishParseTask(JSContext* cx, ParseTaskKind kind, void* token)
+{
+    MOZ_ASSERT(cx->compartment());
 
-    JSContext* cx = maybecx;
-    MOZ_ASSERT(cx->compartment());
+    ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
 
     // Make sure we have all the constructors we need for the prototype
     // remapping below, since we can't GC while that's happening.
     Rooted<GlobalObject*> global(cx, &cx->global()->as<GlobalObject>());
     if (!EnsureParserCreatedClasses(cx, kind)) {
-        LeaveParseTaskZone(rt, parseTask);
+        LeaveParseTaskZone(cx, parseTask);
         return nullptr;
     }
 
     mergeParseTaskCompartment(cx, parseTask, global, cx->compartment());
 
     if (!parseTask->finish(cx))
         return nullptr;
 
-    RootedScript script(rt, parseTask->script);
+    RootedScript script(cx, parseTask->script);
     releaseAssertSameCompartment(cx, script);
 
     // Report out of memory errors eagerly, or errors could be malformed.
     if (parseTask->outOfMemory) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
@@ -1189,43 +1187,47 @@ GlobalHelperThreadState::finishParseTask
 
     // The Debugger only needs to be told about the topmost script that was compiled.
     Debugger::onNewScript(cx, script);
 
     return script;
 }
 
 JSScript*
-GlobalHelperThreadState::finishScriptParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
+GlobalHelperThreadState::finishScriptParseTask(JSContext* cx, void* token)
 {
-    JSScript* script = finishParseTask(maybecx, rt, ParseTaskKind::Script, token);
+    JSScript* script = finishParseTask(cx, ParseTaskKind::Script, token);
     MOZ_ASSERT_IF(script, script->isGlobalCode());
     return script;
 }
 
 JSObject*
-GlobalHelperThreadState::finishModuleParseTask(JSContext* maybecx, JSRuntime* rt, void* token)
+GlobalHelperThreadState::finishModuleParseTask(JSContext* cx, void* token)
 {
-    JSScript* script = finishParseTask(maybecx, rt, ParseTaskKind::Module, token);
+    JSScript* script = finishParseTask(cx, ParseTaskKind::Module, token);
     if (!script)
         return nullptr;
 
     MOZ_ASSERT(script->module());
-    if (!maybecx)
-        return nullptr;
 
-    JSContext* cx = maybecx;
     RootedModuleObject module(cx, script->module());
     module->fixScopesAfterCompartmentMerge(cx);
     if (!ModuleObject::Freeze(cx, module))
         return nullptr;
 
     return module;
 }
 
+void
+GlobalHelperThreadState::cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token)
+{
+    ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
+    LeaveParseTaskZone(cx, parseTask);
+}
+
 JSObject*
 GlobalObject::getStarGeneratorFunctionPrototype()
 {
     const Value& v = getReservedSlot(STAR_GENERATOR_FUNCTION_PROTO);
     return v.isObject() ? &v.toObject() : nullptr;
 }
 
 void
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -96,16 +96,18 @@ class GlobalHelperThreadState
     SourceCompressionTaskVector compressionWorklist_;
 
     // Runtimes which have sweeping / allocating work to do.
     GCHelperStateVector gcHelperWorklist_;
 
     // GC tasks needing to be done in parallel.
     GCParallelTaskVector gcParallelWorklist_;
 
+    ParseTask* removeFinishedParseTask(ParseTaskKind kind, void* token);
+
   public:
     size_t maxIonCompilationThreads() const;
     size_t maxUnpausedIonCompilationThreads() const;
     size_t maxWasmCompilationThreads() const;
     size_t maxParseThreads() const;
     size_t maxCompressionThreads() const;
     size_t maxGCHelperThreads() const;
     size_t maxGCParallelThreads() const;
@@ -220,31 +222,33 @@ class GlobalHelperThreadState
         MOZ_ASSERT(isLocked());
         numWasmFailedJobs++;
     }
     bool wasmFailed() {
         MOZ_ASSERT(isLocked());
         return bool(numWasmFailedJobs);
     }
 
-    JSScript* finishParseTask(JSContext* maybecx, JSRuntime* rt, ParseTaskKind kind, void* token);
+    JSScript* finishParseTask(JSContext* cx, ParseTaskKind kind, void* token);
+    void cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token);
+
     void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
                                    Handle<GlobalObject*> global,
                                    JSCompartment* dest);
 
   private:
     /*
      * Number of wasm jobs that encountered failure for the active module.
      * Their parent is logically the main thread, and this number serves for harvesting.
      */
     uint32_t numWasmFailedJobs;
 
   public:
-    JSScript* finishScriptParseTask(JSContext* maybecx, JSRuntime* rt, void* token);
-    JSObject* finishModuleParseTask(JSContext* maybecx, JSRuntime* rt, void* token);
+    JSScript* finishScriptParseTask(JSContext* cx, void* token);
+    JSObject* finishModuleParseTask(JSContext* cx, void* token);
     bool compressionInProgress(SourceCompressionTask* task);
     SourceCompressionTask* compressionTaskForSource(ScriptSource* ss);
 
     bool hasActiveThreads();
     void waitForAllThreads();
 
     template <typename T>
     bool checkTaskThreadLimit(size_t maxThreads) const;
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -771,19 +771,19 @@ NS_IMETHODIMP
 NotifyPrecompilationCompleteRunnable::Run(void)
 {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mPrecompiler);
 
     AutoSendObserverNotification notifier(mPrecompiler);
 
     if (mToken) {
-        JSRuntime* rt = XPCJSRuntime::Get()->Runtime();
-        NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
-        JS::FinishOffThreadScript(nullptr, rt, mToken);
+        JSContext* cx = XPCJSRuntime::Get()->Context();
+        NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
+        JS::CancelOffThreadScript(cx, mToken);
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 ScriptPrecompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
                                      nsISupports* aContext,