Bug 1274895 - IonMonkey: Move the ion lazy list to the JSRuntime, r=jandem
authorHannes Verschore <hv1989@gmail.com>
Thu, 23 Jun 2016 10:11:45 +0200
changeset 302442 8fba141d39a9e6ed36d8aec5e96734553cb1f3f4
parent 302441 83d444698ab88b369d955ba9f731925b468be740
child 302443 e6da60a095471f83a98b4e048c05b5d620f42299
push id19742
push usercbook@mozilla.com
push dateThu, 23 Jun 2016 13:15:07 +0000
treeherderfx-team@3c54e4e048dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1274895
milestone50.0a1
Bug 1274895 - IonMonkey: Move the ion lazy list to the JSRuntime, r=jandem
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineDebugModeOSR.cpp
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/CodeGenerator.cpp
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1486,17 +1486,17 @@ jit::BailoutIonToBaseline(JSContext* cx,
         propagatingExceptionForDebugMode = false;
     }
 
     JitSpew(JitSpew_BaselineBailouts, "  Reading from snapshot offset %u size %u",
             iter.snapshotOffset(), iter.ionScript()->snapshotsListSize());
 
     if (!excInfo)
         iter.ionScript()->incNumBailouts();
-    iter.script()->updateBaselineOrIonRaw(cx);
+    iter.script()->updateBaselineOrIonRaw(cx->runtime());
 
     // Allocate buffer to hold stack replacement data.
     BaselineStackBuilder builder(iter, 1024);
     if (!builder.init()) {
         ReportOutOfMemory(cx);
         return BAILOUT_RETURN_FATAL_ERROR;
     }
     JitSpew(JitSpew_BaselineBailouts, "  Incoming frame ptr = %p", builder.startFrame());
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -297,17 +297,17 @@ BaselineCompiler::compile()
             ReportOutOfMemory(cx);
             return Method_Error;
         }
 
         // Mark the jitcode as having a bytecode map.
         code->setHasBytecodeMap();
     }
 
-    script->setBaselineScript(cx, baselineScript.release());
+    script->setBaselineScript(cx->runtime(), baselineScript.release());
 
     return Method_Compiled;
 }
 
 void
 BaselineCompiler::emitInitializeLocals(size_t n, const Value& v)
 {
     MOZ_ASSERT(frame.nlocals() > 0 && n <= frame.nlocals());
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -677,25 +677,25 @@ RecompileBaselineScriptForDebugMode(JSCo
     // If a script is on the stack multiple times, it may have already
     // been recompiled.
     if (oldBaselineScript->hasDebugInstrumentation() == observing)
         return true;
 
     JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%d) for %s",
             script->filename(), script->lineno(), observing ? "DEBUGGING" : "NORMAL EXECUTION");
 
-    script->setBaselineScript(cx, nullptr);
+    script->setBaselineScript(cx->runtime(), nullptr);
 
     MethodStatus status = BaselineCompile(cx, script, /* forceDebugMode = */ observing);
     if (status != Method_Compiled) {
         // We will only fail to recompile for debug mode due to OOM. Restore
         // the old baseline script in case something doesn't properly
         // propagate OOM.
         MOZ_ASSERT(status == Method_Error);
-        script->setBaselineScript(cx, oldBaselineScript);
+        script->setBaselineScript(cx->runtime(), oldBaselineScript);
         return false;
     }
 
     // Don't destroy the old baseline script yet, since if we fail any of the
     // recompiles we need to rollback all the old baseline scripts.
     MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
     return true;
 }
@@ -838,17 +838,17 @@ UndoRecompileBaselineScriptsForDebugMode
 {
     // In case of failure, roll back the entire set of active scripts so that
     // we don't have to patch return addresses on the stack.
     for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
         const DebugModeOSREntry& entry = iter.entry();
         JSScript* script = entry.script;
         BaselineScript* baselineScript = script->baselineScript();
         if (entry.recompiled()) {
-            script->setBaselineScript(cx, entry.oldBaselineScript);
+            script->setBaselineScript(cx->runtime(), entry.oldBaselineScript);
             BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript);
         }
     }
 }
 
 bool
 jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext* cx,
                                                  const Debugger::ExecutionObservableSet& obs,
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -295,17 +295,17 @@ jit::BaselineCompile(JSContext* cx, JSSc
         compiler.setCompileDebugInstrumentation();
 
     MethodStatus status = compiler.compile();
 
     MOZ_ASSERT_IF(status == Method_Compiled, script->hasBaselineScript());
     MOZ_ASSERT_IF(status != Method_Compiled, !script->hasBaselineScript());
 
     if (status == Method_CantCompile)
-        script->setBaselineScript(cx, BASELINE_DISABLED_SCRIPT);
+        script->setBaselineScript(cx->runtime(), BASELINE_DISABLED_SCRIPT);
 
     return status;
 }
 
 static MethodStatus
 CanEnterBaselineJIT(JSContext* cx, HandleScript script, InterpreterFrame* osrFrame)
 {
     MOZ_ASSERT(jit::IsBaselineEnabled(cx));
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -480,29 +480,29 @@ struct BaselineScript
     bool hasPendingIonBuilder() const {
         return !!pendingBuilder_;
     }
 
     js::jit::IonBuilder* pendingIonBuilder() {
         MOZ_ASSERT(hasPendingIonBuilder());
         return pendingBuilder_;
     }
-    void setPendingIonBuilder(JSContext* maybecx, JSScript* script, js::jit::IonBuilder* builder) {
+    void setPendingIonBuilder(JSRuntime* maybeRuntime, JSScript* script, js::jit::IonBuilder* builder) {
         MOZ_ASSERT(script->baselineScript() == this);
         MOZ_ASSERT(!builder || !hasPendingIonBuilder());
 
         if (script->isIonCompilingOffThread())
-            script->setIonScript(maybecx, ION_PENDING_SCRIPT);
+            script->setIonScript(maybeRuntime, ION_PENDING_SCRIPT);
 
         pendingBuilder_ = builder;
 
         // lazy linking cannot happen during asmjs to ion.
         clearDependentWasmImports();
 
-        script->updateBaselineOrIonRaw(maybecx);
+        script->updateBaselineOrIonRaw(maybeRuntime);
     }
     void removePendingIonBuilder(JSScript* script) {
         setPendingIonBuilder(nullptr, script, nullptr);
         if (script->maybeIonScript() == ION_PENDING_SCRIPT)
             script->setIonScript(nullptr, nullptr);
     }
 
 };
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -9313,17 +9313,17 @@ CodeGenerator::link(JSContext* cx, Compi
 
     ionScript->setMethod(code);
     ionScript->setSkipArgCheckEntryOffset(getSkipArgCheckEntryOffset());
 
     // If SPS is enabled, mark IonScript as having been instrumented with SPS
     if (isProfilerInstrumentationEnabled())
         ionScript->setHasProfilingInstrumentation();
 
-    script->setIonScript(cx, ionScript);
+    script->setIonScript(cx->runtime(), ionScript);
 
     // Adopt fallback shared stubs from the compiler into the ion script.
     ionScript->adoptFallbackStubs(&stubSpace_);
 
     Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, invalidateEpilogueData_),
                                        ImmPtr(ionScript),
                                        ImmPtr((void*)-1));
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -446,41 +446,43 @@ JitCompartment::ensureIonStubsExist(JSCo
         if (!stringConcatStub_)
             return false;
     }
 
     return true;
 }
 
 void
-jit::FinishOffThreadBuilder(JSContext* cx, IonBuilder* builder)
+jit::FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
 
     // Clean the references to the pending IonBuilder, if we just finished it.
     if (builder->script()->baselineScript()->hasPendingIonBuilder() &&
         builder->script()->baselineScript()->pendingIonBuilder() == builder)
     {
         builder->script()->baselineScript()->removePendingIonBuilder(builder->script());
     }
 
     // If the builder is still in one of the helper thread list, then remove it.
-    if (builder->isInList())
-        HelperThreadState().ionLazyLinkListRemove(builder);
+    if (builder->isInList()) {
+        MOZ_ASSERT(runtime);
+        runtime->ionLazyLinkListRemove(builder);
+    }
 
     // Clear the recompiling flag of the old ionScript, since we continue to
     // use the old ionScript if recompiling fails.
     if (builder->script()->hasIonScript())
         builder->script()->ionScript()->clearRecompiling();
 
     // Clean up if compilation did not succeed.
     if (builder->script()->isIonCompilingOffThread()) {
-        builder->script()->setIonScript(cx, builder->abortReason() == AbortReason_Disable
-                                            ? ION_DISABLED_SCRIPT
-                                            : nullptr);
+        IonScript* ion =
+            builder->abortReason() == AbortReason_Disable ? ION_DISABLED_SCRIPT : nullptr;
+        builder->script()->setIonScript(runtime, ion);
     }
 
     // The builder is allocated into its LifoAlloc, so destroying that will
     // destroy the builder and all other data accumulated during compilation,
     // except any final codegen (which includes an assembler and needs to be
     // explicitly destroyed).
     js_delete(builder->backgroundCodegen());
     js_delete(builder->alloc().lifoAlloc());
@@ -542,17 +544,17 @@ jit::LazyLink(JSContext* cx, HandleScrip
         AutoLockHelperThreadState lock;
 
         // Get the pending builder from the Ion frame.
         MOZ_ASSERT(calleeScript->hasBaselineScript());
         builder = calleeScript->baselineScript()->pendingIonBuilder();
         calleeScript->baselineScript()->removePendingIonBuilder(calleeScript);
 
         // Remove from pending.
-        HelperThreadState().ionLazyLinkListRemove(builder);
+        cx->runtime()->ionLazyLinkListRemove(builder);
     }
 
     {
         AutoEnterAnalysis enterTypes(cx);
         if (!LinkBackgroundCodeGen(cx, builder)) {
             // Silently ignore OOM during code generation. The assembly code
             // doesn't has code to handle it after linking happened. So it's
             // not OK to throw a catchable exception from there.
@@ -560,17 +562,17 @@ jit::LazyLink(JSContext* cx, HandleScrip
 
             // Reset the TypeZone's compiler output for this script, if any.
             InvalidateCompilerOutputsForScript(cx, calleeScript);
         }
     }
 
     {
         AutoLockHelperThreadState lock;
-        FinishOffThreadBuilder(cx, builder);
+        FinishOffThreadBuilder(cx->runtime(), builder);
     }
 }
 
 uint8_t*
 jit::LazyLinkTopActivation(JSContext* cx)
 {
     // First frame should be an exit frame.
     JitFrameIterator it(cx);
@@ -2028,24 +2030,24 @@ AttachFinishedCompilations(JSContext* cx
         while (true) {
             // Find a finished builder for the compartment.
             IonBuilder* builder = GetFinishedBuilder(cx, finished);
             if (!builder)
                 break;
 
             JSScript* script = builder->script();
             MOZ_ASSERT(script->hasBaselineScript());
-            script->baselineScript()->setPendingIonBuilder(cx, script, builder);
-            HelperThreadState().ionLazyLinkListAdd(builder);
+            script->baselineScript()->setPendingIonBuilder(cx->runtime(), script, builder);
+            cx->runtime()->ionLazyLinkListAdd(builder);
 
             // Don't keep more than 100 lazy link builders.
             // Throw away the oldest items.
-            while (HelperThreadState().ionLazyLinkListSize() > 100) {
-                jit::IonBuilder* builder = HelperThreadState().ionLazyLinkList().getLast();
-                jit::FinishOffThreadBuilder(nullptr, builder);
+            while (cx->runtime()->ionLazyLinkListSize() > 100) {
+                jit::IonBuilder* builder = cx->runtime()->ionLazyLinkList().getLast();
+                jit::FinishOffThreadBuilder(cx->runtime(), builder);
             }
 
             continue;
         }
     }
 }
 
 static void
@@ -2243,17 +2245,17 @@ IonCompile(JSContext* cx, JSScript* scri
 
         if (!StartOffThreadIonCompile(cx, builder)) {
             JitSpew(JitSpew_IonAbort, "Unable to start off-thread ion compilation.");
             builder->graphSpewer().endFunction();
             return AbortReason_Alloc;
         }
 
         if (!recompile)
-            builderScript->setIonScript(cx, ION_COMPILING_SCRIPT);
+            builderScript->setIonScript(cx->runtime(), ION_COMPILING_SCRIPT);
 
         // The allocator and associated data will be destroyed after being
         // processed in the finishedOffThreadCompilations list.
         autoDelete.forget();
 
         return AbortReason_NoAbort;
     }
 
@@ -3281,17 +3283,17 @@ jit::ForbidCompilation(JSContext* cx, JS
     JitSpew(JitSpew_IonAbort, "Disabling Ion compilation of script %s:%" PRIuSIZE,
             script->filename(), script->lineno());
 
     CancelOffThreadIonCompile(cx->compartment(), script);
 
     if (script->hasIonScript())
         Invalidate(cx, script, false);
 
-    script->setIonScript(cx, ION_DISABLED_SCRIPT);
+    script->setIonScript(cx->runtime(), ION_DISABLED_SCRIPT);
 }
 
 AutoFlushICache*
 PerThreadData::autoFlushICache() const
 {
     return autoFlushICache_;
 }
 
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -139,17 +139,17 @@ class LIRGraph;
 class CodeGenerator;
 
 MOZ_MUST_USE bool OptimizeMIR(MIRGenerator* mir);
 LIRGraph* GenerateLIR(MIRGenerator* mir);
 CodeGenerator* GenerateCode(MIRGenerator* mir, LIRGraph* lir);
 CodeGenerator* CompileBackEnd(MIRGenerator* mir);
 
 void AttachFinishedCompilations(JSContext* cx);
-void FinishOffThreadBuilder(JSContext* cx, IonBuilder* builder);
+void FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder);
 void StopAllOffThreadCompilations(Zone* zone);
 void StopAllOffThreadCompilations(JSCompartment* comp);
 
 void LazyLink(JSContext* cx, HandleScript calleescript);
 uint8_t* LazyLinkTopActivation(JSContext* cx);
 
 static inline bool
 IsIonEnabled(JSContext* cx)
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1491,24 +1491,24 @@ ScriptCounts::getThrowCounts(size_t offs
     PCCounts searched = PCCounts(offset);
     PCCounts* elem = std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
     if (elem == throwCounts_.end() || elem->pcOffset() != offset)
         elem = throwCounts_.insert(elem, searched);
     return elem;
 }
 
 void
-JSScript::setIonScript(JSContext* maybecx, js::jit::IonScript* ionScript)
+JSScript::setIonScript(JSRuntime* maybeRuntime, js::jit::IonScript* ionScript)
 {
     MOZ_ASSERT_IF(ionScript != ION_DISABLED_SCRIPT, !baselineScript()->hasPendingIonBuilder());
     if (hasIonScript())
         js::jit::IonScript::writeBarrierPre(zone(), ion);
     ion = ionScript;
     MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
-    updateBaselineOrIonRaw(maybecx);
+    updateBaselineOrIonRaw(maybeRuntime);
 }
 
 js::PCCounts*
 JSScript::maybeGetPCCounts(jsbytecode* pc) {
     MOZ_ASSERT(containsPC(pc));
     return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
 }
 
@@ -4395,23 +4395,23 @@ LazyScript::hasUncompiledEnclosingScript
     if (!enclosingScope() || !enclosingScope()->is<JSFunction>())
         return false;
 
     JSFunction& fun = enclosingScope()->as<JSFunction>();
     return !fun.hasScript() || fun.hasUncompiledScript() || !fun.nonLazyScript()->code();
 }
 
 void
-JSScript::updateBaselineOrIonRaw(JSContext* maybecx)
+JSScript::updateBaselineOrIonRaw(JSRuntime* maybeRuntime)
 {
     if (hasBaselineScript() && baseline->hasPendingIonBuilder()) {
-        MOZ_ASSERT(maybecx);
+        MOZ_ASSERT(maybeRuntime);
         MOZ_ASSERT(!isIonCompilingOffThread());
-        baselineOrIonRaw = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw();
-        baselineOrIonSkipArgCheck = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw();
+        baselineOrIonRaw = maybeRuntime->jitRuntime()->lazyLinkStub()->raw();
+        baselineOrIonSkipArgCheck = maybeRuntime->jitRuntime()->lazyLinkStub()->raw();
     } else if (hasIonScript()) {
         baselineOrIonRaw = ion->method()->raw();
         baselineOrIonSkipArgCheck = ion->method()->raw() + ion->getSkipArgCheckEntryOffset();
     } else if (hasBaselineScript()) {
         baselineOrIonRaw = baseline->method()->raw();
         baselineOrIonSkipArgCheck = baseline->method()->raw();
     } else {
         baselineOrIonRaw = nullptr;
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1572,33 +1572,33 @@ class JSScript : public js::gc::TenuredC
         return ion;
     }
     js::jit::IonScript* maybeIonScript() const {
         return ion;
     }
     js::jit::IonScript* const* addressOfIonScript() const {
         return &ion;
     }
-    void setIonScript(JSContext* maybecx, js::jit::IonScript* ionScript);
+    void setIonScript(JSRuntime* maybeRuntime, js::jit::IonScript* ionScript);
 
     bool hasBaselineScript() const {
         bool res = baseline && baseline != BASELINE_DISABLED_SCRIPT;
         MOZ_ASSERT_IF(!res, !ion || ion == ION_DISABLED_SCRIPT);
         return res;
     }
     bool canBaselineCompile() const {
         return baseline != BASELINE_DISABLED_SCRIPT;
     }
     js::jit::BaselineScript* baselineScript() const {
         MOZ_ASSERT(hasBaselineScript());
         return baseline;
     }
-    inline void setBaselineScript(JSContext* maybecx, js::jit::BaselineScript* baselineScript);
-
-    void updateBaselineOrIonRaw(JSContext* maybecx);
+    inline void setBaselineScript(JSRuntime* maybeRuntime, js::jit::BaselineScript* baselineScript);
+
+    void updateBaselineOrIonRaw(JSRuntime* maybeRuntime);
 
     static size_t offsetOfBaselineScript() {
         return offsetof(JSScript, baseline);
     }
     static size_t offsetOfIonScript() {
         return offsetof(JSScript, ion);
     }
     static size_t offsetOfBaselineOrIonRaw() {
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -180,24 +180,24 @@ JSScript::global() const
 
 inline JSPrincipals*
 JSScript::principals()
 {
     return compartment()->principals();
 }
 
 inline void
-JSScript::setBaselineScript(JSContext* maybecx, js::jit::BaselineScript* baselineScript)
+JSScript::setBaselineScript(JSRuntime* maybeRuntime, js::jit::BaselineScript* baselineScript)
 {
     if (hasBaselineScript())
         js::jit::BaselineScript::writeBarrierPre(zone(), baseline);
     MOZ_ASSERT(!hasIonScript());
     baseline = baselineScript;
     resetWarmUpResetCounter();
-    updateBaselineOrIonRaw(maybecx);
+    updateBaselineOrIonRaw(maybeRuntime);
 }
 
 inline bool
 JSScript::ensureHasAnalyzedArgsUsage(JSContext* cx)
 {
     if (analyzedArgsUsage())
         return true;
     return js::jit::AnalyzeArgumentsUsage(cx, this);
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -127,17 +127,17 @@ CompiledScriptMatches(JSCompartment* com
     if (script)
         return target == script;
     if (compartment)
         return target->compartment() == compartment;
     return true;
 }
 
 void
-js::CancelOffThreadIonCompile(JSCompartment* compartment, JSScript* script)
+js::CancelOffThreadIonCompile(JSCompartment* compartment, JSScript* script, bool discardLazyLinkList)
 {
     if (compartment && !compartment->jitCompartment())
         return;
 
     AutoLockHelperThreadState lock;
 
     if (!HelperThreadState().threads)
         return;
@@ -173,24 +173,26 @@ js::CancelOffThreadIonCompile(JSCompartm
         jit::IonBuilder* builder = finished[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             jit::FinishOffThreadBuilder(nullptr, builder);
             HelperThreadState().remove(finished, &i);
         }
     }
 
     /* Cancel lazy linking for pending builders (attached to the ionScript). */
-    jit::IonBuilder* builder = HelperThreadState().ionLazyLinkList().getFirst();
-    while (builder) {
-        jit::IonBuilder* next = builder->getNext();
-        if (CompiledScriptMatches(compartment, script, builder->script())) {
-            builder->script()->baselineScript()->removePendingIonBuilder(builder->script());
-            jit::FinishOffThreadBuilder(nullptr, builder);
+    if (discardLazyLinkList) {
+        MOZ_ASSERT(compartment);
+        JSRuntime* runtime = compartment->runtimeFromMainThread();
+        jit::IonBuilder* builder = runtime->ionLazyLinkList().getFirst();
+        while (builder) {
+            jit::IonBuilder* next = builder->getNext();
+            if (CompiledScriptMatches(compartment, script, builder->script()))
+                jit::FinishOffThreadBuilder(runtime, builder);
+            builder = next;
         }
-        builder = next;
     }
 }
 
 static const JSClassOps parseTaskGlobalClassOps = {
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr,
     JS_GlobalObjectTraceHook
@@ -624,17 +626,16 @@ GlobalHelperThreadState::ensureInitializ
 
     return true;
 }
 
 GlobalHelperThreadState::GlobalHelperThreadState()
  : cpuCount(0),
    threadCount(0),
    threads(nullptr),
-   ionLazyLinkListSize_(0),
    wasmCompilationInProgress(false),
    numWasmFailedJobs(0),
    helperLock(nullptr),
    consumerWakeup(nullptr),
    producerWakeup(nullptr),
    pauseWakeup(nullptr)
 {
     cpuCount = GetCPUCount();
@@ -652,19 +653,16 @@ void
 GlobalHelperThreadState::finish()
 {
     finishThreads();
 
     PR_DestroyCondVar(consumerWakeup);
     PR_DestroyCondVar(producerWakeup);
     PR_DestroyCondVar(pauseWakeup);
     PR_DestroyLock(helperLock);
-
-    ionLazyLinkList_.clear();
-    ionLazyLinkListSize_ = 0;
 }
 
 void
 GlobalHelperThreadState::finishThreads()
 {
     if (!threads)
         return;
 
@@ -727,34 +725,16 @@ GlobalHelperThreadState::notifyAll(CondV
 
 void
 GlobalHelperThreadState::notifyOne(CondVar which)
 {
     MOZ_ASSERT(isLocked());
     PR_NotifyCondVar(whichWakeup(which));
 }
 
-void
-GlobalHelperThreadState::ionLazyLinkListRemove(jit::IonBuilder* builder)
-{
-    MOZ_ASSERT(ionLazyLinkListSize_ > 0);
-
-    builder->removeFrom(HelperThreadState().ionLazyLinkList());
-    ionLazyLinkListSize_--;
-
-    MOZ_ASSERT(HelperThreadState().ionLazyLinkList().isEmpty() == (ionLazyLinkListSize_ == 0));
-}
-
-void
-GlobalHelperThreadState::ionLazyLinkListAdd(jit::IonBuilder* builder)
-{
-    HelperThreadState().ionLazyLinkList().insertFront(builder);
-    ionLazyLinkListSize_++;
-}
-
 bool
 GlobalHelperThreadState::hasActiveThreads()
 {
     MOZ_ASSERT(isLocked());
     if (!threads)
         return false;
 
     for (size_t i = 0; i < threadCount; i++) {
@@ -763,17 +743,17 @@ GlobalHelperThreadState::hasActiveThread
     }
 
     return false;
 }
 
 void
 GlobalHelperThreadState::waitForAllThreads()
 {
-    CancelOffThreadIonCompile(nullptr, nullptr);
+    CancelOffThreadIonCompile(nullptr, nullptr, /* discardLazyLinkList = */ false);
 
     AutoLockHelperThreadState lock;
     while (hasActiveThreads())
         wait(CONSUMER);
 }
 
 template <typename T>
 bool
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -55,31 +55,26 @@ class GlobalHelperThreadState
     // Number of threads to create. May be accessed without locking.
     size_t threadCount;
 
     typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
     typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
     typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
     typedef Vector<GCHelperState*, 0, SystemAllocPolicy> GCHelperStateVector;
     typedef Vector<GCParallelTask*, 0, SystemAllocPolicy> GCParallelTaskVector;
-    typedef mozilla::LinkedList<jit::IonBuilder> IonBuilderList;
 
     // List of available threads, or null if the thread state has not been initialized.
     HelperThread* threads;
 
   private:
     // The lists below are all protected by |lock|.
 
     // Ion compilation worklist and finished jobs.
     IonBuilderVector ionWorklist_, ionFinishedList_;
 
-    // List of IonBuilders using lazy linking pending to get linked.
-    IonBuilderList ionLazyLinkList_;
-    size_t ionLazyLinkListSize_;
-
     // wasm worklist and finished jobs.
     wasm::IonCompileTaskVector wasmWorklist_, wasmFinishedList_;
 
   public:
     // For now, only allow a single parallel asm.js compilation to happen at a
     // time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc.
     mozilla::Atomic<bool> wasmCompilationInProgress;
 
@@ -148,26 +143,16 @@ class GlobalHelperThreadState
     IonBuilderVector& ionWorklist() {
         MOZ_ASSERT(isLocked());
         return ionWorklist_;
     }
     IonBuilderVector& ionFinishedList() {
         MOZ_ASSERT(isLocked());
         return ionFinishedList_;
     }
-    IonBuilderList& ionLazyLinkList() {
-        MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
-                   "Should only be mutated by the main thread.");
-        return ionLazyLinkList_;
-    }
-    size_t ionLazyLinkListSize() {
-        return ionLazyLinkListSize_;
-    }
-    void ionLazyLinkListRemove(jit::IonBuilder* builder);
-    void ionLazyLinkListAdd(jit::IonBuilder* builder);
 
     wasm::IonCompileTaskVector& wasmWorklist() {
         MOZ_ASSERT(isLocked());
         return wasmWorklist_;
     }
     wasm::IonCompileTaskVector& wasmFinishedList() {
         MOZ_ASSERT(isLocked());
         return wasmFinishedList_;
@@ -408,17 +393,18 @@ StartOffThreadWasmCompile(ExclusiveConte
 bool
 StartOffThreadIonCompile(JSContext* cx, jit::IonBuilder* builder);
 
 /*
  * Cancel a scheduled or in progress Ion compilation for script. If script is
  * nullptr, all compilations for the compartment are cancelled.
  */
 void
-CancelOffThreadIonCompile(JSCompartment* compartment, JSScript* script);
+CancelOffThreadIonCompile(JSCompartment* compartment, JSScript* script,
+                          bool discardLazyLinkList = true);
 
 /* Cancel all scheduled, in progress or finished parses for runtime. */
 void
 CancelOffThreadParses(JSRuntime* runtime);
 
 /*
  * Start a parse/emit cycle for a stream of source. The characters must stay
  * alive until the compilation finishes.
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -39,16 +39,17 @@
 #include "jswatchpoint.h"
 #include "jswin.h"
 #include "jswrapper.h"
 
 #include "asmjs/WasmSignalHandlers.h"
 #include "builtin/Promise.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/arm64/vixl/Simulator-vixl.h"
+#include "jit/IonBuilder.h"
 #include "jit/JitCompartment.h"
 #include "jit/mips32/Simulator-mips32.h"
 #include "jit/mips64/Simulator-mips64.h"
 #include "jit/PcScriptCache.h"
 #include "js/Date.h"
 #include "js/MemoryMetrics.h"
 #include "js/SliceBudget.h"
 #include "vm/Debugger.h"
@@ -248,17 +249,18 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     autoWritableJitCodeActive_(false),
 #ifdef DEBUG
     enteredPolicy(nullptr),
 #endif
     largeAllocationFailureCallback(nullptr),
     oomCallback(nullptr),
     debuggerMallocSizeOf(ReturnZeroSize),
     lastAnimationTime(0),
-    performanceMonitoring(thisFromCtor())
+    performanceMonitoring(thisFromCtor()),
+    ionLazyLinkListSize_(0)
 {
     setGCStoreBufferPtr(&gc.storeBuffer);
 
     liveRuntimesCount++;
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
@@ -432,16 +434,19 @@ JSRuntime::~JSRuntime()
 
         /* Set the profiler sampler buffer generation to invalid. */
         profilerSampleBufferGen_ = UINT32_MAX;
 
         JS::PrepareForFullGC(this);
         gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
     }
 
+    MOZ_ASSERT(ionLazyLinkListSize_ == 0);
+    MOZ_ASSERT(ionLazyLinkList().isEmpty());
+
     /*
      * Clear the self-hosted global and delete self-hosted classes *after*
      * GC, as finalizers for objects check for clasp->finalize during GC.
      */
     finishSelfHosting();
 
     MOZ_ASSERT(!exclusiveAccessOwner);
 
@@ -948,8 +953,39 @@ JS::UpdateJSRuntimeProfilerSampleBufferG
 }
 
 JS_FRIEND_API(bool)
 JS::IsProfilingEnabledForRuntime(JSRuntime* runtime)
 {
     MOZ_ASSERT(runtime);
     return runtime->spsProfiler.enabled();
 }
+
+JSRuntime::IonBuilderList&
+JSRuntime::ionLazyLinkList()
+{
+    MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
+            "Should only be mutated by the main thread.");
+    return ionLazyLinkList_;
+}
+
+void
+JSRuntime::ionLazyLinkListRemove(jit::IonBuilder* builder)
+{
+    MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
+            "Should only be mutated by the main thread.");
+    MOZ_ASSERT(ionLazyLinkListSize_ > 0);
+
+    builder->removeFrom(ionLazyLinkList());
+    ionLazyLinkListSize_--;
+
+    MOZ_ASSERT(ionLazyLinkList().isEmpty() == (ionLazyLinkListSize_ == 0));
+}
+
+void
+JSRuntime::ionLazyLinkListAdd(jit::IonBuilder* builder)
+{
+    MOZ_ASSERT(TlsPerThreadData.get()->runtimeFromMainThread(),
+            "Should only be mutated by the main thread.");
+    ionLazyLinkList().insertFront(builder);
+    ionLazyLinkListSize_++;
+}
+
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1630,16 +1630,33 @@ struct JSRuntime : public JS::shadow::Ru
      */
     mozilla::MallocSizeOf debuggerMallocSizeOf;
 
     /* Last time at which an animation was played for this runtime. */
     int64_t lastAnimationTime;
 
   public:
     js::PerformanceMonitoring performanceMonitoring;
+
+  private:
+    /* List of Ion compilation waiting to get linked. */
+    typedef mozilla::LinkedList<js::jit::IonBuilder> IonBuilderList;
+
+    IonBuilderList ionLazyLinkList_;
+    size_t ionLazyLinkListSize_;
+
+  public:
+    IonBuilderList& ionLazyLinkList();
+
+    size_t ionLazyLinkListSize() {
+        return ionLazyLinkListSize_;
+    }
+
+    void ionLazyLinkListRemove(js::jit::IonBuilder* builder);
+    void ionLazyLinkListAdd(js::jit::IonBuilder* builder);
 };
 
 namespace js {
 
 // When entering JIT code, the calling JSContext* is stored into the thread's
 // PerThreadData. This function retrieves the JSContext with the pre-condition
 // that the caller is JIT code or C++ called directly from JIT code. This
 // function should not be called from arbitrary locations since the JSContext