Bug 1178834: IonMonkey - Always lazy link code, r=jandem
authorHannes Verschore <hv1989@gmail.com>
Fri, 14 Aug 2015 17:57:57 +0200
changeset 258004 3bbd0d9291280d02bac1ed2e73298bc67b70cbda
parent 258003 2526fbcbe37a5eae117ce258e8a872a7f8ae4830
child 258005 a6eeb28458fd2652e12e57334f046b7776d75bb4
child 258023 e4b9e72f934f864375d03732d6923bb06f4e8d35
push id29238
push userryanvm@gmail.com
push dateMon, 17 Aug 2015 13:06:57 +0000
treeherdermozilla-central@a6eeb28458fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1178834
milestone43.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 1178834: IonMonkey - Always lazy link code, r=jandem
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jit/IonCode.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/HelperThreads.cpp
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -62,17 +62,18 @@ BaselineScript::BaselineScript(uint32_t 
 # endif
     traceLoggerEnterToggleOffset_(traceLoggerEnterToggleOffset),
     traceLoggerExitToggleOffset_(traceLoggerExitToggleOffset),
     traceLoggerScriptEvent_(),
 #endif
     postDebugPrologueOffset_(postDebugPrologueOffset),
     flags_(0),
     inlinedBytecodeLength_(0),
-    maxInliningDepth_(UINT8_MAX)
+    maxInliningDepth_(UINT8_MAX),
+    pendingBuilder_(nullptr)
 { }
 
 static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000;
 
 static bool
 CheckFrame(InterpreterFrame* fp)
 {
     if (fp->isDebuggerEvalFrame()) {
@@ -468,16 +469,18 @@ BaselineScript::Destroy(FreeOp* fop, Bas
      * When the script contains pointers to nursery things, the store buffer
      * will contain entries refering to the referenced things. Since we can
      * destroy scripts outside the context of a GC, this situation can result
      * in invalid store buffer entries. Assert that if we do destroy scripts
      * outside of a GC that we at least emptied the nursery first.
      */
     MOZ_ASSERT(fop->runtime()->gc.nursery.isEmpty());
 
+    MOZ_ASSERT(!script->hasPendingIonBuilder());
+
     script->unlinkDependentAsmJSModules(fop);
 
     fop->delete_(script);
 }
 
 void
 BaselineScript::unlinkDependentAsmJSModules(FreeOp* fop)
 {
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -219,16 +219,19 @@ struct BaselineScript
 
     // The max inlining depth where we can still inline all functions we inlined
     // when we Ion-compiled this script. This starts as UINT8_MAX, since we have
     // no data yet, and won't affect inlining heuristics in that case. The value
     // is updated when we Ion-compile this script. See makeInliningDecision for
     // more info.
     uint8_t maxInliningDepth_;
 
+    // An ion compilation that is ready, but isn't linked yet.
+    IonBuilder *pendingBuilder_;
+
   public:
     // Do not call directly, use BaselineScript::New. This is public for cx->new_.
     BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
                    uint32_t profilerEnterToggleOffset,
                    uint32_t profilerExitToggleOffset,
                    uint32_t traceLoggerEnterToggleOffset,
                    uint32_t traceLoggerExitToggleOffset,
                    uint32_t postDebugPrologueOffset);
@@ -451,16 +454,42 @@ struct BaselineScript
     uint16_t inlinedBytecodeLength() const {
         return inlinedBytecodeLength_;
     }
     void setInlinedBytecodeLength(uint32_t len) {
         if (len > UINT16_MAX)
             len = UINT16_MAX;
         inlinedBytecodeLength_ = len;
     }
+
+    bool hasPendingIonBuilder() const {
+        return !!pendingBuilder_;
+    }
+
+    js::jit::IonBuilder* pendingIonBuilder() {
+        MOZ_ASSERT(hasPendingIonBuilder());
+        return pendingBuilder_;
+    }
+    void setPendingIonBuilder(JSContext* maybecx, JSScript* script, js::jit::IonBuilder* builder) {
+        MOZ_ASSERT(script->baselineScript() == this);
+        MOZ_ASSERT(!builder || !hasPendingIonBuilder());
+
+        if (script->isIonCompilingOffThread())
+            script->setIonScript(maybecx, ION_PENDING_SCRIPT);
+
+        pendingBuilder_ = builder;
+
+        script->updateBaselineOrIonRaw(maybecx);
+    }
+    void removePendingIonBuilder(JSScript* script) {
+        setPendingIonBuilder(nullptr, script, nullptr);
+        if (script->maybeIonScript() == ION_PENDING_SCRIPT)
+            script->setIonScript(nullptr, nullptr);
+    }
+
 };
 static_assert(sizeof(BaselineScript) % sizeof(uintptr_t) == 0,
               "The data attached to the script must be aligned for fast JIT access.");
 
 inline bool
 IsBaselineEnabled(JSContext* cx)
 {
 #ifdef JS_CODEGEN_NONE
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -463,18 +463,21 @@ PrepareForDebuggerOnIonCompilationHook(J
         info->numBlocks = 0;
     }
 }
 
 void
 jit::FinishOffThreadBuilder(JSContext* cx, IonBuilder* builder)
 {
     // Clean the references to the pending IonBuilder, if we just finished it.
-    if (builder->script()->hasIonScript() && builder->script()->pendingIonBuilder() == builder)
-        builder->script()->setPendingIonBuilder(cx, nullptr);
+    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())
         builder->removeFrom(HelperThreadState().ionLazyLinkList());
 
     // Clear the recompiling flag of the old ionScript, since we continue to
     // use the old ionScript if recompiling fails.
     if (builder->script()->hasIonScript())
@@ -561,31 +564,23 @@ LinkBackgroundCodeGen(JSContext* cx, Ion
     // Root the assembler until the builder is finished below. As it was
     // constructed off thread, the assembler has not been rooted previously,
     // though any GC activity would discard the builder.
     codegen->masm.constructRoot(cx);
 
     return LinkCodeGen(cx, builder, codegen, scripts, info);
 }
 
-uint8_t*
-jit::LazyLinkTopActivation(JSContext* cx)
+void
+jit::LazyLink(JSContext* cx, HandleScript calleeScript)
 {
-    JitActivationIterator iter(cx->runtime());
-    AutoLazyLinkExitFrame lazyLinkExitFrame(iter->asJit());
-
-    // First frame should be an exit frame.
-    JitFrameIterator it(iter);
-    LazyLinkExitFrameLayout* ll = it.exitFrame()->as<LazyLinkExitFrameLayout>();
-    RootedScript calleeScript(cx, ScriptFromCalleeToken(ll->jsFrame()->calleeToken()));
-
-
     // Get the pending builder from the Ion frame.
-    IonBuilder* builder = calleeScript->ionScript()->pendingBuilder();
-    calleeScript->setPendingIonBuilder(cx, nullptr);
+    MOZ_ASSERT(calleeScript->hasBaselineScript());
+    IonBuilder* builder = calleeScript->baselineScript()->pendingIonBuilder();
+    calleeScript->baselineScript()->removePendingIonBuilder(calleeScript);
 
     // See PrepareForDebuggerOnIonCompilationHook
     AutoScriptVector debugScripts(cx);
     OnIonCompilationInfo info(builder->alloc().lifoAlloc());
 
     // Remove from pending.
     builder->removeFrom(HelperThreadState().ionLazyLinkList());
 
@@ -601,16 +596,30 @@ jit::LazyLinkTopActivation(JSContext* cx
 
     if (info.filled())
         Debugger::onIonCompilation(cx, debugScripts, info.graph);
 
     FinishOffThreadBuilder(cx, builder);
 
     MOZ_ASSERT(calleeScript->hasBaselineScript());
     MOZ_ASSERT(calleeScript->baselineOrIonRawPointer());
+}
+
+uint8_t*
+jit::LazyLinkTopActivation(JSContext* cx)
+{
+    JitActivationIterator iter(cx->runtime());
+    AutoLazyLinkExitFrame lazyLinkExitFrame(iter->asJit());
+
+    // First frame should be an exit frame.
+    JitFrameIterator it(iter);
+    LazyLinkExitFrameLayout* ll = it.exitFrame()->as<LazyLinkExitFrameLayout>();
+    RootedScript calleeScript(cx, ScriptFromCalleeToken(ll->jsFrame()->calleeToken()));
+
+    LazyLink(cx, calleeScript);
 
     return calleeScript->baselineOrIonRawPointer();
 }
 
 /* static */ void
 JitRuntime::Mark(JSTracer* trc)
 {
     MOZ_ASSERT(!trc->runtime()->isHeapMinorCollecting());
@@ -884,18 +893,17 @@ IonScript::IonScript()
     snapshotsListSize_(0),
     snapshotsRVATableSize_(0),
     constantTable_(0),
     constantEntries_(0),
     backedgeList_(0),
     backedgeEntries_(0),
     invalidationCount_(0),
     recompileInfo_(),
-    osrPcMismatchCounter_(0),
-    pendingBuilder_(nullptr)
+    osrPcMismatchCounter_(0)
 {
 }
 
 IonScript*
 IonScript::New(JSContext* cx, RecompileInfo recompileInfo,
                uint32_t frameSlots, uint32_t argumentSlots, uint32_t frameSize,
                size_t snapshotsListSize, size_t snapshotsRVATableSize,
                size_t recoversSize, size_t bailoutEntries,
@@ -1205,19 +1213,16 @@ IonScript::Trace(JSTracer* trc, IonScrip
 {
     if (script != ION_DISABLED_SCRIPT)
         script->trace(trc);
 }
 
 void
 IonScript::Destroy(FreeOp* fop, IonScript* script)
 {
-    if (script->pendingBuilder())
-        jit::FinishOffThreadBuilder(nullptr, script->pendingBuilder());
-
     script->unlinkFromRuntime(fop);
     fop->free_(script);
 }
 
 void
 IonScript::toggleBarriers(bool enabled)
 {
     method()->togglePreBarriers(enabled);
@@ -1758,115 +1763,44 @@ GetFinishedBuilder(JSContext* cx, Global
             HelperThreadState().remove(finished, &i);
             return testBuilder;
         }
     }
 
     return nullptr;
 }
 
-static bool
-IsBuilderScriptOnStack(JSContext* cx, IonBuilder* builder)
-{
-    for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
-        for (JitFrameIterator it(iter); !it.done(); ++it) {
-            if (!it.isIonJS())
-                continue;
-            if (it.checkInvalidation())
-                continue;
-
-            JSScript* script = it.script();
-            if (builder->script() == script)
-                return true;
-        }
-    }
-
-    return false;
-}
-
 void
 AttachFinishedCompilations(JSContext* cx)
 {
     JitCompartment* ion = cx->compartment()->jitCompartment();
     if (!ion)
         return;
 
-    LifoAlloc* debuggerAlloc = cx->new_<LifoAlloc>(TempAllocator::PreferredLifoChunkSize);
-    if (!debuggerAlloc) {
-        // Silently ignore OOM during code generation. The caller is
-        // InvokeInterruptCallback, which always runs at a nondeterministic
-        // time. It's not OK to throw a catchable exception from there.
-        cx->clearPendingException();
-        return;
-    }
-
-    // See PrepareForDebuggerOnIonCompilationHook
-    AutoScriptVector debugScripts(cx);
-    OnIonCompilationVector onIonCompilationVector(cx);
-
     {
         AutoEnterAnalysis enterTypes(cx);
         AutoLockHelperThreadState lock;
 
         GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
 
         // Incorporate any off thread compilations for the compartment which have
         // finished, failed or have been cancelled.
         while (true) {
             // Find a finished builder for the compartment.
             IonBuilder* builder = GetFinishedBuilder(cx, finished);
             if (!builder)
                 break;
 
-            // Try to defer linking if the script is on the stack, to postpone
-            // invalidating them.
-            if (builder->script()->hasIonScript() && IsBuilderScriptOnStack(cx, builder)) {
-                builder->script()->setPendingIonBuilder(cx, builder);
-                HelperThreadState().ionLazyLinkList().insertFront(builder);
-                continue;
-            }
-
-            AutoUnlockHelperThreadState unlock;
-
-            OnIonCompilationInfo info(debuggerAlloc);
-            if (!LinkBackgroundCodeGen(cx, builder, &debugScripts, &info)) {
-                // Silently ignore OOM during code generation. The caller is
-                // InvokeInterruptCallback, which always runs at a
-                // nondeterministic time. It's not OK to throw a catchable
-                // exception from there.
-                cx->clearPendingException();
-            }
-
-            if (info.filled()) {
-                if (!onIonCompilationVector.append(info))
-                    cx->clearPendingException();
-            }
-
-            FinishOffThreadBuilder(cx, builder);
+            JSScript* script = builder->script();
+            MOZ_ASSERT(script->hasBaselineScript());
+            script->baselineScript()->setPendingIonBuilder(cx, script, builder);
+            HelperThreadState().ionLazyLinkList().insertFront(builder);
+            continue;
         }
     }
-
-    for (size_t i = 0; i < onIonCompilationVector.length(); i++) {
-        OnIonCompilationInfo& info = onIonCompilationVector[i];
-
-        // As it is easier to root a vector, instead of a vector of vector, we
-        // slice for each compilation.
-        AutoScriptVector sliceScripts(cx);
-        if (!sliceScripts.reserve(info.numBlocks)) {
-            cx->clearPendingException();
-            continue;
-        }
-
-        for (size_t b = 0; b < info.numBlocks; b++)
-            sliceScripts.infallibleAppend(debugScripts[info.scriptIndex + b]);
-
-        Debugger::onIonCompilation(cx, sliceScripts, info.graph);
-    }
-
-    js_delete(debuggerAlloc);
 }
 
 static void
 TrackAllProperties(JSContext* cx, JSObject* obj)
 {
     MOZ_ASSERT(obj->isSingleton());
 
     for (Shape::Range<NoGC> range(obj->as<NativeObject>().lastProperty()); !range.empty(); range.popFront())
@@ -2210,18 +2144,18 @@ Compile(JSContext* cx, HandleScript scri
         return status;
     }
 
     bool recompile = false;
     OptimizationLevel optimizationLevel = GetOptimizationLevel(script, osrPc);
     if (optimizationLevel == Optimization_DontCompile)
         return Method_Skipped;
 
-    IonScript* scriptIon = script->maybeIonScript();
-    if (scriptIon) {
+    if (script->hasIonScript()) {
+        IonScript* scriptIon = script->ionScript();
         if (!scriptIon->method())
             return Method_CantCompile;
 
         // Don't recompile/overwrite higher optimized code,
         // with a lower optimization level.
         if (optimizationLevel <= scriptIon->optimizationLevel() && !forceRecompile)
             return Method_Compiled;
 
@@ -2230,16 +2164,24 @@ Compile(JSContext* cx, HandleScript scri
             return Method_Compiled;
 
         if (osrPc)
             scriptIon->resetOsrPcMismatchCounter();
 
         recompile = true;
     }
 
+    if (script->baselineScript()->hasPendingIonBuilder()) {
+        IonBuilder* buildIon = script->baselineScript()->pendingIonBuilder();
+        if (optimizationLevel <= buildIon->optimizationInfo().level() && !forceRecompile)
+            return Method_Compiled;
+
+        recompile = true;
+    }
+
     AbortReason reason = IonCompile(cx, script, osrFrame, osrPc, constructing,
                                     recompile, optimizationLevel);
     if (reason == AbortReason_Error)
         return Method_Error;
 
     if (reason == AbortReason_Disable)
         return Method_CantCompile;
 
@@ -2268,17 +2210,17 @@ jit::OffThreadCompilationAvailable(JSCon
     return cx->runtime()->canUseOffthreadIonCompilation()
         && HelperThreadState().cpuCount > 1
         && CanUseExtraThreads();
 }
 
 // Decide if a transition from interpreter execution to Ion code should occur.
 // May compile or recompile the target JSScript.
 MethodStatus
-jit::CanEnterAtBranch(JSContext* cx, JSScript* script, BaselineFrame* osrFrame, jsbytecode* pc)
+jit::CanEnterAtBranch(JSContext* cx, HandleScript script, BaselineFrame* osrFrame, jsbytecode* pc)
 {
     MOZ_ASSERT(jit::IsIonEnabled(cx));
     MOZ_ASSERT((JSOp)*pc == JSOP_LOOPENTRY);
     MOZ_ASSERT(LoopEntryCanIonOsr(pc));
 
     // Skip if the script has been disabled.
     if (!script->canIonCompile())
         return Method_Skipped;
@@ -2296,16 +2238,21 @@ jit::CanEnterAtBranch(JSContext* cx, JSS
         return Method_Skipped;
 
     // Mark as forbidden if frame can't be handled.
     if (!CheckFrame(cx, osrFrame)) {
         ForbidCompilation(cx, script);
         return Method_CantCompile;
     }
 
+    // Check if the jitcode still needs to get linked and do this
+    // to have a valid IonScript.
+    if (script->baselineScript()->hasPendingIonBuilder())
+        LazyLink(cx, script);
+
     // By default a recompilation doesn't happen on osr mismatch.
     // Decide if we want to force a recompilation if this happens too much.
     bool force = false;
     if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
         uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
         if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
             return Method_Skipped;
         force = true;
@@ -2324,17 +2271,17 @@ jit::CanEnterAtBranch(JSContext* cx, JSS
             ForbidCompilation(cx, script);
         return status;
     }
 
     // Return the compilation was skipped when the osr pc wasn't adjusted.
     // This can happen when there was still an IonScript available and a
     // background compilation started, but hasn't finished yet.
     // Or when we didn't force a recompile.
-    if (pc != script->ionScript()->osrPc())
+    if (script->hasIonScript() && pc != script->ionScript()->osrPc())
         return Method_Skipped;
 
     return Method_Compiled;
 }
 
 MethodStatus
 jit::CanEnter(JSContext* cx, RunState& state)
 {
@@ -2392,16 +2339,22 @@ jit::CanEnter(JSContext* cx, RunState& s
     bool constructing = state.isInvoke() && state.asInvoke()->constructing();
     MethodStatus status = Compile(cx, rscript, nullptr, nullptr, constructing);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, rscript);
         return status;
     }
 
+    if (state.script()->baselineScript()->hasPendingIonBuilder()) {
+        LazyLink(cx, state.script());
+        if (!state.script()->hasIonScript())
+            return jit::Method_Skipped;
+    }
+
     return Method_Compiled;
 }
 
 MethodStatus
 jit::CompileFunctionForBaseline(JSContext* cx, HandleScript script, BaselineFrame* frame)
 {
     MOZ_ASSERT(jit::IsIonEnabled(cx));
     MOZ_ASSERT(frame->fun()->nonLazyScript()->canIonCompile());
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -77,17 +77,17 @@ bool InitializeIon();
 // Get and set the current JIT context.
 JitContext* GetJitContext();
 JitContext* MaybeGetJitContext();
 
 void SetJitContext(JitContext* ctx);
 
 bool CanIonCompileScript(JSContext* cx, JSScript* script, bool osr);
 
-MethodStatus CanEnterAtBranch(JSContext* cx, JSScript* script,
+MethodStatus CanEnterAtBranch(JSContext* cx, HandleScript script,
                               BaselineFrame* frame, jsbytecode* pc);
 MethodStatus CanEnter(JSContext* cx, RunState& state);
 MethodStatus CompileFunctionForBaseline(JSContext* cx, HandleScript script, BaselineFrame* frame);
 MethodStatus CanEnterUsingFastInvoke(JSContext* cx, HandleScript script, uint32_t numActualArgs);
 
 MethodStatus
 Recompile(JSContext* cx, HandleScript script, BaselineFrame* osrFrame, jsbytecode* osrPc,
           bool constructing, bool force);
@@ -142,16 +142,17 @@ 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 StopAllOffThreadCompilations(Zone* zone);
 void StopAllOffThreadCompilations(JSCompartment* comp);
 
+void LazyLink(JSContext* cx, HandleScript calleescript);
 uint8_t* LazyLinkTopActivation(JSContext* cx);
 
 static inline bool
 IsIonEnabled(JSContext* cx)
 {
 #ifdef JS_CODEGEN_NONE
     return false;
 #else
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -270,36 +270,26 @@ struct IonScript
 
     // Number of times we tried to enter this script via OSR but failed due to
     // a LOOPENTRY pc other than osrPc_.
     uint32_t osrPcMismatchCounter_;
 
     // The tracelogger event used to log the start/stop of this IonScript.
     TraceLoggerEvent traceLoggerScriptEvent_;
 
-    IonBuilder* pendingBuilder_;
-
   private:
     inline uint8_t* bottomBuffer() {
         return reinterpret_cast<uint8_t*>(this);
     }
     inline const uint8_t* bottomBuffer() const {
         return reinterpret_cast<const uint8_t*>(this);
     }
 
   public:
 
-    // SHOULD ONLY BE CALLED FROM JSScript
-    void setPendingBuilderPrivate(IonBuilder* builder) {
-        pendingBuilder_ = builder;
-    }
-    IonBuilder* pendingBuilder() const {
-        return pendingBuilder_;
-    }
-
     SnapshotOffset* bailoutTable() {
         return (SnapshotOffset*) &bottomBuffer()[bailoutTable_];
     }
     PreBarrieredValue* constants() {
         return (PreBarrieredValue*) &bottomBuffer()[constantTable_];
     }
     const SafepointIndex* safepointIndices() const {
         return const_cast<IonScript*>(this)->safepointIndices();
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1323,16 +1323,27 @@ static inline ScriptCountsMap::Ptr GetSc
 js::PCCounts&
 JSScript::getPCCounts(jsbytecode* pc) {
     MOZ_ASSERT(containsPC(pc));
     ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
     return p->value().pcCountsVector[pcToOffset(pc)];
 }
 
 void
+JSScript::setIonScript(JSContext* maybecx, 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);
+}
+
+void
 JSScript::addIonCounts(jit::IonScriptCounts* ionCounts)
 {
     ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
     if (p->value().ionCounts)
         ionCounts->setPrevious(p->value().ionCounts);
     p->value().ionCounts = ionCounts;
 }
 
@@ -4040,25 +4051,24 @@ LazyScript::hasUncompiledEnclosingScript
 
     JSFunction& fun = enclosingScope()->as<JSFunction>();
     return !fun.hasScript() || fun.hasUncompiledScript() || !fun.nonLazyScript()->code();
 }
 
 void
 JSScript::updateBaselineOrIonRaw(JSContext* maybecx)
 {
-    if (hasIonScript()) {
-        if (ion->pendingBuilder()) {
-            MOZ_ASSERT(maybecx);
-            baselineOrIonRaw = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw();
-            baselineOrIonSkipArgCheck = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw();
-        } else {
-            baselineOrIonRaw = ion->method()->raw();
-            baselineOrIonSkipArgCheck = ion->method()->raw() + ion->getSkipArgCheckEntryOffset();
-        }
+    if (hasBaselineScript() && baseline->hasPendingIonBuilder()) {
+        MOZ_ASSERT(maybecx);
+        MOZ_ASSERT(!isIonCompilingOffThread());
+        baselineOrIonRaw = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw();
+        baselineOrIonSkipArgCheck = maybecx->runtime()->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;
         baselineOrIonSkipArgCheck = nullptr;
     }
 }
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -33,16 +33,17 @@ namespace js {
 
 namespace jit {
     struct BaselineScript;
     struct IonScriptCounts;
 } // namespace jit
 
 # define ION_DISABLED_SCRIPT ((js::jit::IonScript*)0x1)
 # define ION_COMPILING_SCRIPT ((js::jit::IonScript*)0x2)
+# define ION_PENDING_SCRIPT ((js::jit::IonScript*)0x3)
 
 # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1)
 
 class BreakpointSite;
 class BindingIter;
 class Debugger;
 class LazyScript;
 class RegExpObject;
@@ -960,18 +961,25 @@ class JSScript : public js::gc::TenuredC
     //
     // (When we clone a JSScript into a new compartment, we don't clone its
     // source object. Instead, the clone refers to a wrapper.)
     js::HeapPtrObject sourceObject_;
 
     js::HeapPtrFunction function_;
     js::HeapPtrObject   enclosingStaticScope_;
 
-    /* Information attached by Baseline/Ion for sequential mode execution. */
+    /*
+     * Information attached by Ion. Nexto a valid IonScript this could be
+     * ION_DISABLED_SCRIPT, ION_COMPILING_SCRIPT or ION_PENDING_SCRIPT.
+     * The later is a ion compilation that is ready, but hasn't been linked
+     * yet.
+     */
     js::jit::IonScript* ion;
+
+    /* Information attached by Baseline. */
     js::jit::BaselineScript* baseline;
 
     /* Information used to re-lazify a lazily-parsed interpreted function. */
     js::LazyScript* lazyScript;
 
     /*
      * Pointer to either baseline->method()->raw() or ion->method()->raw(), or
      * nullptr if there's no Baseline or Ion script.
@@ -1467,45 +1475,39 @@ class JSScript : public js::gc::TenuredC
         doNotRelazify_ = b;
     }
 
     bool hasAnyIonScript() const {
         return hasIonScript();
     }
 
     bool hasIonScript() const {
-        bool res = ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT;
+        bool res = ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT &&
+                          ion != ION_PENDING_SCRIPT;
         MOZ_ASSERT_IF(res, baseline);
         return res;
     }
     bool canIonCompile() const {
         return ion != ION_DISABLED_SCRIPT;
     }
-
     bool isIonCompilingOffThread() const {
         return ion == ION_COMPILING_SCRIPT;
     }
 
     js::jit::IonScript* ionScript() const {
         MOZ_ASSERT(hasIonScript());
         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) {
-        if (hasIonScript())
-            js::jit::IonScript::writeBarrierPre(zone(), ion);
-        ion = ionScript;
-        MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
-        updateBaselineOrIonRaw(maybecx);
-    }
+    void setIonScript(JSContext* maybecx, 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;
@@ -1513,26 +1515,16 @@ class JSScript : public js::gc::TenuredC
     js::jit::BaselineScript* baselineScript() const {
         MOZ_ASSERT(hasBaselineScript());
         return baseline;
     }
     inline void setBaselineScript(JSContext* maybecx, js::jit::BaselineScript* baselineScript);
 
     void updateBaselineOrIonRaw(JSContext* maybecx);
 
-    void setPendingIonBuilder(JSContext* maybecx, js::jit::IonBuilder* builder) {
-        MOZ_ASSERT(!builder || !ion->pendingBuilder());
-        ion->setPendingBuilderPrivate(builder);
-        updateBaselineOrIonRaw(maybecx);
-    }
-    js::jit::IonBuilder* pendingIonBuilder() {
-        MOZ_ASSERT(hasIonScript());
-        return ion->pendingBuilder();
-    }
-
     static size_t offsetOfBaselineScript() {
         return offsetof(JSScript, baseline);
     }
     static size_t offsetOfIonScript() {
         return offsetof(JSScript, ion);
     }
     static size_t offsetOfBaselineOrIonRaw() {
         return offsetof(JSScript, baselineOrIonRaw);
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -176,17 +176,17 @@ js::CancelOffThreadIonCompile(JSCompartm
         }
     }
 
     /* 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()->setPendingIonBuilder(nullptr, nullptr);
+            builder->script()->baselineScript()->removePendingIonBuilder(builder->script());
             jit::FinishOffThreadBuilder(nullptr, builder);
         }
         builder = next;
     }
 }
 
 static const JSClass parseTaskGlobalClass = {
     "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,