Bug 1047346 - IonMonkey: Delay linking for scripts that are on the stack, r=jandem
authorHannes Verschore <hv1989@gmail.com>
Wed, 10 Sep 2014 22:39:51 +0200
changeset 204685 ab267884c5aed08004ad57c32346aa63805d27ba
parent 204684 d68e06ecb5c6361d7eecd727632e6989dee9a734
child 204686 b93eeeab6506511d5dec8150823b8a3ce8fa2c6e
push id27463
push userryanvm@gmail.com
push dateThu, 11 Sep 2014 00:30:54 +0000
treeherderautoland@bc7deafdac4b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1047346
milestone35.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 1047346 - IonMonkey: Delay linking for scripts that are on the stack, r=jandem
js/src/jit/BaselineBailouts.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/ExecutionMode-inl.h
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jit/IonBuilder.h
js/src/jit/IonCode.h
js/src/jit/JitCompartment.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1330,17 +1330,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();
+    iter.script()->updateBaselineOrIonRaw(cx);
 
     // Allocate buffer to hold stack replacement data.
     BaselineStackBuilder builder(iter, 1024);
     if (!builder.init())
         return BAILOUT_RETURN_FATAL_ERROR;
     JitSpew(JitSpew_BaselineBailouts, "  Incoming frame ptr = %p", builder.startFrame());
 
     AutoValueVector instructionResults(cx);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -18,16 +18,17 @@
 
 #include "asmjs/AsmJSModule.h"
 #include "builtin/Eval.h"
 #include "builtin/TypedObject.h"
 #ifdef JSGC_GENERATIONAL
 # include "gc/Nursery.h"
 #endif
 #include "jit/BaselineCompiler.h"
+#include "jit/IonBuilder.h"
 #include "jit/IonCaches.h"
 #include "jit/IonLinker.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitSpewer.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MoveEmitter.h"
@@ -1075,17 +1076,16 @@ CodeGenerator::visitStringReplace(LStrin
     if (lir->string()->isConstant())
         pushArg(ImmGCPtr(lir->string()->toConstant()->toString()));
     else
         pushArg(ToRegister(lir->string()));
 
     return callVM(StringReplaceInfo, lir);
 }
 
-
 typedef JSObject *(*LambdaFn)(JSContext *, HandleFunction, HandleObject);
 static const VMFunction LambdaInfo = FunctionInfo<LambdaFn>(js::Lambda);
 
 bool
 CodeGenerator::visitLambdaForSingleton(LLambdaForSingleton *lir)
 {
     pushArg(ToRegister(lir->scopeChain()));
     pushArg(ImmGCPtr(lir->mir()->info().fun));
@@ -5494,16 +5494,50 @@ JitRuntime::generateFreeStub(JSContext *
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "FreeStub");
 #endif
 
     return code;
 }
 
+
+JitCode *
+JitRuntime::generateLazyLinkStub(JSContext *cx)
+{
+    MacroAssembler masm(cx);
+
+    Label call;
+    GeneralRegisterSet regs = GeneralRegisterSet::Volatile();
+    Register temp0 = regs.takeAny();
+
+    uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS);
+    masm.Push(Imm32(descriptor));
+    masm.call(&call);
+    masm.jump(ReturnReg);
+
+    masm.bind(&call);
+    masm.enterExitFrame();
+    masm.setupUnalignedABICall(1, temp0);
+    masm.loadJSContext(temp0);
+    masm.passABIArg(temp0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, LazyLinkTopActivation));
+    masm.leaveExitFrame();
+    masm.retn(Imm32(sizeof(IonExitFrameLayout)));
+
+    Linker linker(masm);
+    AutoFlushICache afc("LazyLinkStub");
+    JitCode *code = linker.newCode<NoGC>(cx, OTHER_CODE);
+
+#ifdef JS_ION_PERF
+    writePerfSpewerJitCodeProfile(code, "LazyLinkStub");
+#endif
+    return code;
+}
+
 typedef bool (*CharCodeAtFn)(JSContext *, HandleString, int32_t, uint32_t *);
 static const VMFunction CharCodeAtInfo = FunctionInfo<CharCodeAtFn>(jit::CharCodeAt);
 
 bool
 CodeGenerator::visitCharCodeAt(LCharCodeAt *lir)
 {
     Register str = ToRegister(lir->str());
     Register index = ToRegister(lir->index());
@@ -6921,17 +6955,17 @@ CodeGenerator::link(JSContext *cx, types
 
     ionScript->setMethod(code);
     ionScript->setSkipArgCheckEntryOffset(getSkipArgCheckEntryOffset());
 
     // If SPS is enabled, mark IonScript as having been instrumented with SPS
     if (sps_.enabled())
         ionScript->setHasSPSInstrumentation();
 
-    SetIonScript(script, executionMode, ionScript);
+    SetIonScript(cx, script, executionMode, ionScript);
 
     // In parallel execution mode, when we first compile a script, we
     // don't know that its potential callees are compiled, so set a
     // flag warning that the callees may not be fully compiled.
     if (!callTargets.empty())
         ionScript->setHasUncompiledCallTarget();
 
     invalidateEpilogueData_.fixup(&masm);
--- a/js/src/jit/ExecutionMode-inl.h
+++ b/js/src/jit/ExecutionMode-inl.h
@@ -32,20 +32,20 @@ GetIonScript(JSScript *script, Execution
       case SequentialExecution: return script->maybeIonScript();
       case ParallelExecution: return script->maybeParallelIonScript();
       default:;
     }
     MOZ_CRASH("No such execution mode");
 }
 
 static inline void
-SetIonScript(JSScript *script, ExecutionMode cmode, IonScript *ionScript)
+SetIonScript(JSContext *cx, JSScript *script, ExecutionMode cmode, IonScript *ionScript)
 {
     switch (cmode) {
-      case SequentialExecution: script->setIonScript(ionScript); return;
+      case SequentialExecution: script->setIonScript(cx, ionScript); return;
       case ParallelExecution: script->setParallelIonScript(ionScript); return;
       default:;
     }
     MOZ_CRASH("No such execution mode");
 }
 
 static inline size_t
 OffsetOfIonInJSScript(ExecutionMode cmode)
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -298,16 +298,21 @@ JitRuntime::initialize(JSContext *cx)
         return false;
 
     JitSpew(JitSpew_Codegen, "# Emitting VM function wrappers");
     for (VMFunction *fun = VMFunction::functions; fun; fun = fun->next) {
         if (!generateVMWrapper(cx, *fun))
             return false;
     }
 
+    JitSpew(JitSpew_Codegen, "# Emitting lazy link stub");
+    lazyLinkStub_ = generateLazyLinkStub(cx);
+    if (!lazyLinkStub_)
+        return false;
+
     jitcodeGlobalTable_ = cx->new_<JitcodeGlobalTable>();
     if (!jitcodeGlobalTable_)
         return false;
 
     return true;
 }
 
 JitCode *
@@ -556,28 +561,34 @@ JitCompartment::notifyOfActiveParallelEn
 
 bool
 JitCompartment::hasRecentParallelActivity() const
 {
     return activeParallelEntryScripts_ && !activeParallelEntryScripts_->empty();
 }
 
 void
-jit::FinishOffThreadBuilder(IonBuilder *builder)
+jit::FinishOffThreadBuilder(JSContext *cx, IonBuilder *builder)
 {
     ExecutionMode executionMode = builder->info().executionMode();
 
+    // 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->isInList())
+        builder->remove();
+
     // Clear the recompiling flag of the old ionScript, since we continue to
     // use the old ionScript if recompiling fails.
     if (executionMode == SequentialExecution && builder->script()->hasIonScript())
         builder->script()->ionScript()->clearRecompiling();
 
     // Clean up if compilation did not succeed.
     if (CompilingOffThread(builder->script(), executionMode)) {
-        SetIonScript(builder->script(), executionMode,
+        SetIonScript(cx, builder->script(), executionMode,
                      builder->abortReason() == AbortReason_Disable
                      ? ION_DISABLED_SCRIPT
                      : nullptr);
     }
 
     // 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
@@ -590,22 +601,72 @@ static inline void
 FinishAllOffThreadCompilations(JSCompartment *comp)
 {
     AutoLockHelperThreadState lock;
     GlobalHelperThreadState::IonBuilderVector &finished = HelperThreadState().ionFinishedList();
 
     for (size_t i = 0; i < finished.length(); i++) {
         IonBuilder *builder = finished[i];
         if (builder->compartment == CompileCompartment::get(comp)) {
-            FinishOffThreadBuilder(builder);
+            FinishOffThreadBuilder(nullptr, builder);
             HelperThreadState().remove(finished, &i);
         }
     }
 }
 
+uint8_t *
+jit::LazyLinkTopActivation(JSContext *cx)
+{
+    JitActivationIterator iter(cx->runtime());
+
+    // First frame should be an exit frame.
+    JitFrameIterator it(iter.jitTop(), SequentialExecution);
+    MOZ_ASSERT(it.type() == JitFrame_Exit);
+
+    // Second frame is the Ion frame.
+    ++it;
+    MOZ_ASSERT(it.type() == JitFrame_IonJS);
+
+    // Get the pending builder from the Ion frame.
+    IonBuilder *builder = it.script()->ionScript()->pendingBuilder();
+    it.script()->setPendingIonBuilder(cx, nullptr);
+
+    types::AutoEnterAnalysis enterTypes(cx);
+    RootedScript script(cx, builder->script());
+
+    // Remove from pending.
+    builder->remove();
+
+    if (CodeGenerator *codegen = builder->backgroundCodegen()) {
+        js::TraceLogger *logger = TraceLoggerForMainThread(cx->runtime());
+        AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, script));
+        AutoTraceLog logLink(logger, TraceLogger::IonLinking);
+
+        IonContext ictx(cx, &builder->alloc());
+
+        // 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);
+
+        if (!codegen->link(cx, builder->constraints())) {
+            // 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.
+            cx->clearPendingException();
+        }
+    }
+
+    FinishOffThreadBuilder(cx, builder);
+
+    MOZ_ASSERT(script->hasBaselineScript());
+    MOZ_ASSERT(script->baselineOrIonRawPointer());
+
+    return script->baselineOrIonRawPointer();
+}
 /* static */ void
 JitRuntime::Mark(JSTracer *trc)
 {
     JS_ASSERT(!trc->runtime()->isHeapMinorCollecting());
     Zone *zone = trc->runtime()->atomsCompartment()->zone();
     for (gc::ZoneCellIterUnderGC i(zone, gc::FINALIZE_JITCODE); !i.done(); i.next()) {
         JitCode *code = i.get<JitCode>();
         MarkJitCodeRoot(trc, &code, "wrapper");
@@ -855,17 +916,18 @@ IonScript::IonScript()
     callTargetList_(0),
     callTargetEntries_(0),
     backedgeList_(0),
     backedgeEntries_(0),
     refcount_(0),
     parallelAge_(0),
     recompileInfo_(),
     osrPcMismatchCounter_(0),
-    dependentAsmJSModules(nullptr)
+    dependentAsmJSModules(nullptr),
+    pendingBuilder_(nullptr)
 {
 }
 
 IonScript *
 IonScript::New(JSContext *cx, types::RecompileInfo recompileInfo,
                uint32_t frameSlots, uint32_t frameSize,
                size_t snapshotsListSize, size_t snapshotsRVATableSize,
                size_t recoversSize, size_t bailoutEntries,
@@ -1193,16 +1255,19 @@ 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->destroyCaches();
     script->unlinkFromRuntime(fop);
     fop->free_(script);
 }
 
 void
 IonScript::toggleBarriers(bool enabled)
 {
@@ -1779,16 +1844,46 @@ AttachFinishedCompilations(JSContext *cx
                 builder = testBuilder;
                 HelperThreadState().remove(finished, &i);
                 break;
             }
         }
         if (!builder)
             break;
 
+        // Try to defer linking if the script is on the stack, to postpone
+        // invalidating them.
+        if (builder->info().executionMode() == SequentialExecution &&
+            builder->script()->hasIonScript())
+        {
+            bool onStack = false;
+            for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) {
+                for (JitFrameIterator it(iter.jitTop(), SequentialExecution); !it.done(); ++it) {
+                    if (!it.isIonJS())
+                        continue;
+                    if (it.checkInvalidation())
+                        continue;
+
+                    JSScript *script = it.script();
+                    if (builder->script() == script) {
+                        onStack = true;
+                        break;
+                    }
+                }
+                if (onStack)
+                    break;
+            }
+
+            if (onStack) {
+                builder->script()->setPendingIonBuilder(cx, builder);
+                HelperThreadState().ionLazyLinkList().insertFront(builder);
+                continue;
+            }
+        }
+
         if (CodeGenerator *codegen = builder->backgroundCodegen()) {
             RootedScript script(cx, builder->script());
             IonContext ictx(cx, &builder->alloc());
             AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, script));
             AutoTraceLog logLink(logger, TraceLogger::IonLinking);
 
             // Root the assembler until the builder is finished below. As it
             // was constructed off thread, the assembler has not been rooted
@@ -1805,17 +1900,17 @@ AttachFinishedCompilations(JSContext *cx
                 // 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();
             }
         }
 
-        FinishOffThreadBuilder(builder);
+        FinishOffThreadBuilder(cx, builder);
     }
 }
 
 static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
 
 static inline bool
 OffThreadCompilationAvailable(JSContext *cx)
 {
@@ -1977,17 +2072,17 @@ IonCompile(JSContext *cx, JSScript *scri
             }
         }
         return reason;
     }
 
     // If possible, compile the script off thread.
     if (OffThreadCompilationAvailable(cx)) {
         if (!recompile)
-            SetIonScript(builderScript, executionMode, ION_COMPILING_SCRIPT);
+            SetIonScript(cx, builderScript, executionMode, ION_COMPILING_SCRIPT);
 
         JitSpew(JitSpew_Logs, "Can't log script %s:%d. (Compiled on background thread.)",
                               builderScript->filename(), builderScript->lineno());
 
         if (!StartOffThreadIonCompile(cx, builder)) {
             JitSpew(JitSpew_Abort, "Unable to start off-thread ion compilation.");
             return AbortReason_Alloc;
         }
@@ -2602,18 +2697,26 @@ InvalidateActivation(FreeOp *fop, uint8_
             JitSpew(JitSpew_Invalidate, "#%d entry frame @ %p", frameno, it.fp());
             break;
         }
 #endif
 
         if (!it.isIonJS())
             continue;
 
+        bool calledFromLinkStub = false;
+        JitCode *lazyLinkStub = fop->runtime()->jitRuntime()->lazyLinkStub();
+        if (it.returnAddressToFp() >= lazyLinkStub->raw() &&
+            it.returnAddressToFp() < lazyLinkStub->rawEnd())
+        {
+            calledFromLinkStub = true;
+        }
+
         // See if the frame has already been invalidated.
-        if (it.checkInvalidation())
+        if (!calledFromLinkStub && it.checkInvalidation())
             continue;
 
         JSScript *script = it.script();
         if (!script->hasIonScript())
             continue;
 
         if (!invalidateAll && !script->ionScript()->invalidated())
             continue;
@@ -2647,35 +2750,39 @@ InvalidateActivation(FreeOp *fop, uint8_
         // Note: you can't simplify this mechanism to "just patch the
         // instruction immediately after the call" because things may
         // need to move into a well-defined register state (using move
         // instructions after the call) in to capture an appropriate
         // snapshot after the call occurs.
 
         ionScript->incref();
 
-        const SafepointIndex *si = ionScript->getSafepointIndex(it.returnAddressToFp());
         JitCode *ionCode = ionScript->method();
 
         JS::Zone *zone = script->zone();
         if (zone->needsIncrementalBarrier()) {
             // We're about to remove edges from the JSScript to gcthings
             // embedded in the JitCode. Perform one final trace of the
             // JitCode for the incremental GC, as it must know about
             // those edges.
             ionCode->trace(zone->barrierTracer());
         }
         ionCode->setInvalidated();
 
+        // Don't adjust OSI points in the linkStub (which don't exist).
+        if (calledFromLinkStub)
+            continue;
+
         // Write the delta (from the return address offset to the
         // IonScript pointer embedded into the invalidation epilogue)
         // where the safepointed call instruction used to be. We rely on
         // the call sequence causing the safepoint being >= the size of
         // a uint32, which is checked during safepoint index
         // construction.
+        const SafepointIndex *si = ionScript->getSafepointIndex(it.returnAddressToFp());
         CodeLocationLabel dataLabelToMunge(it.returnAddressToFp());
         ptrdiff_t delta = ionScript->invalidateEpilogueDataOffset() -
                           (it.returnAddressToFp() - ionCode->raw());
         Assembler::PatchWrite_Imm32(dataLabelToMunge, Imm32(delta));
 
         CodeLocationLabel osiPatchPoint = SafepointReader::InvalidationPatchPoint(ionScript, si);
         CodeLocationLabel invalidateEpilogue(ionCode, CodeOffsetLabel(ionScript->invalidateEpilogueOffset()));
 
@@ -2759,17 +2866,17 @@ jit::Invalidate(types::TypeZone &types, 
             continue;
 
         ExecutionMode executionMode = co.mode();
         JSScript *script = co.script();
         IonScript *ionScript = co.ion();
         if (!ionScript)
             continue;
 
-        SetIonScript(script, executionMode, nullptr);
+        SetIonScript(nullptr, script, executionMode, nullptr);
         ionScript->decref(fop);
         co.invalidate();
         numInvalidations--;
 
         // Wait for the scripts to get warm again before doing another
         // compile, unless either:
         // (1) we are recompiling *because* a script got hot;
         //     (resetUses is false); or,
@@ -2872,17 +2979,17 @@ void
 jit::FinishInvalidation(FreeOp *fop, JSScript *script)
 {
     // In all cases, nullptr out script->ion or script->parallelIon to avoid
     // re-entry.
     switch (mode) {
       case SequentialExecution:
         if (script->hasIonScript()) {
             IonScript *ion = script->ionScript();
-            script->setIonScript(nullptr);
+            script->setIonScript(nullptr, nullptr);
             FinishInvalidationOf(fop, script, ion);
         }
         return;
 
       case ParallelExecution:
         if (script->hasParallelIonScript()) {
             IonScript *parallelIon = script->parallelIonScript();
             script->setParallelIonScript(nullptr);
@@ -2922,17 +3029,17 @@ jit::ForbidCompilation(JSContext *cx, JS
             // running, because JitFrameIterator needs to tell what ionScript to
             // use (either the one on the JSScript, or the one hidden in the
             // breadcrumbs Invalidation() leaves). Therefore, if invalidation
             // fails, we cannot disable the script.
             if (!Invalidate(cx, script, mode, false))
                 return;
         }
 
-        script->setIonScript(ION_DISABLED_SCRIPT);
+        script->setIonScript(cx, ION_DISABLED_SCRIPT);
         return;
 
       case ParallelExecution:
         if (script->hasParallelIonScript()) {
             if (!Invalidate(cx, script, mode, false))
                 return;
         }
 
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -143,19 +143,21 @@ class LIRGraph;
 class CodeGenerator;
 
 bool OptimizeMIR(MIRGenerator *mir);
 LIRGraph *GenerateLIR(MIRGenerator *mir);
 CodeGenerator *GenerateCode(MIRGenerator *mir, LIRGraph *lir);
 CodeGenerator *CompileBackEnd(MIRGenerator *mir);
 
 void AttachFinishedCompilations(JSContext *cx);
-void FinishOffThreadBuilder(IonBuilder *builder);
+void FinishOffThreadBuilder(JSContext *cx, IonBuilder *builder);
 void StopAllOffThreadCompilations(JSCompartment *comp);
 
+uint8_t *LazyLinkTopActivation(JSContext *cx);
+
 static inline bool
 IsIonEnabled(JSContext *cx)
 {
 #ifdef JS_CODEGEN_NONE
     return false;
 #else
     return cx->runtime()->options().ion() &&
            cx->runtime()->options().baseline() &&
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_IonBuilder_h
 #define jit_IonBuilder_h
 
 // This file declares the data structures for building a MIRGraph from a
 // JSScript.
 
+#include "mozilla/LinkedList.h"
+
 #include "jit/BytecodeAnalysis.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/MIR.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MIRGraph.h"
 
 namespace js {
 namespace jit {
@@ -24,17 +26,19 @@ class CallInfo;
 class BaselineInspector;
 class BaselineFrameInspector;
 
 // Records information about a baseline frame for compilation that is stable
 // when later used off thread.
 BaselineFrameInspector *
 NewBaselineFrameInspector(TempAllocator *temp, BaselineFrame *frame, CompileInfo *info);
 
-class IonBuilder : public MIRGenerator
+class IonBuilder
+  : public MIRGenerator,
+    public mozilla::LinkedListElement<IonBuilder>
 {
     enum ControlStatus {
         ControlStatus_Error,
         ControlStatus_Abort,
         ControlStatus_Ended,        // There is no continuation/join point.
         ControlStatus_Joined,       // Created a join node.
         ControlStatus_Jumped,       // Parsing another branch at the same level.
         ControlStatus_None          // No control flow.
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -23,16 +23,17 @@ namespace js {
 
 class AsmJSModule;
 
 namespace jit {
 
 class MacroAssembler;
 class CodeOffsetLabel;
 class PatchableBackedge;
+class IonBuilder;
 
 class JitCode : public gc::BarrieredCell<JitCode>
 {
   protected:
     uint8_t *code_;
     ExecutablePool *pool_;
     uint32_t bufferSize_;             // Total buffer size. Does not include headerSize_.
     uint32_t insnSize_;               // Instruction stream size.
@@ -294,25 +295,36 @@ 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_;
 
     // If non-null, the list of AsmJSModules
     // that contain an optimized call directly into this IonScript.
     Vector<DependentAsmJSModuleExit> *dependentAsmJSModules;
 
+    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/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -188,16 +188,19 @@ class JitRuntime
     JitCode *valuePreBarrier_;
     JitCode *shapePreBarrier_;
     JitCode *typeObjectPreBarrier_;
 
     // Thunk to call malloc/free.
     JitCode *mallocStub_;
     JitCode *freeStub_;
 
+    // Thunk called to finish compilation of an IonScript.
+    JitCode *lazyLinkStub_;
+
     // Thunk used by the debugger for breakpoint and step mode.
     JitCode *debugTrapHandler_;
 
     // Stub used to inline the ForkJoinGetSlice intrinsic.
     JitCode *forkJoinGetSliceStub_;
 
     // Thunk used to fix up on-stack recompile of baseline scripts.
     JitCode *baselineDebugModeOSRHandler_;
@@ -233,16 +236,17 @@ class JitRuntime
     // only in callVM() targets that are about to return *and* have invalidated
     // their callee.
     js::Value ionReturnOverride_;
 
     // Global table of jitcode native address => bytecode address mappings.
     JitcodeGlobalTable *jitcodeGlobalTable_;
 
   private:
+    JitCode *generateLazyLinkStub(JSContext *cx);
     JitCode *generateExceptionTailStub(JSContext *cx);
     JitCode *generateBailoutTailStub(JSContext *cx);
     JitCode *generateEnterJIT(JSContext *cx, EnterJitType type);
     JitCode *generateArgumentsRectifier(JSContext *cx, ExecutionMode mode, void **returnAddrOut);
     JitCode *generateBailoutTable(JSContext *cx, uint32_t frameClass);
     JitCode *generateBailoutHandler(JSContext *cx, ExecutionMode mode);
     JitCode *generateInvalidator(JSContext *cx);
     JitCode *generatePreBarrier(JSContext *cx, MIRType type);
@@ -364,16 +368,20 @@ class JitRuntime
     JitCode *mallocStub() const {
         return mallocStub_;
     }
 
     JitCode *freeStub() const {
         return freeStub_;
     }
 
+    JitCode *lazyLinkStub() const {
+        return lazyLinkStub_;
+    }
+
     bool ensureForkJoinGetSliceStubExists(JSContext *cx);
     JitCode *forkJoinGetSliceStub() const {
         return forkJoinGetSliceStub_;
     }
 
     bool hasIonReturnOverride() const {
         return !ionReturnOverride_.isMagic(JS_ARG_POISON);
     }
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -29,16 +29,17 @@
 #include "jsutil.h"
 #include "jswrapper.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/BytecodeEmitter.h"
 #include "frontend/SharedContext.h"
 #include "gc/Marking.h"
 #include "jit/BaselineJIT.h"
+#include "jit/Ion.h"
 #include "jit/IonCode.h"
 #include "js/MemoryMetrics.h"
 #include "js/OldDebugAPI.h"
 #include "js/Utility.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Compression.h"
 #include "vm/Debugger.h"
 #include "vm/Opcodes.h"
@@ -3748,21 +3749,27 @@ LazyScript::staticLevel(JSContext *cx) c
     for (StaticScopeIter<NoGC> ssi(enclosingScope()); !ssi.done(); ssi++) {
         if (ssi.type() == StaticScopeIter<NoGC>::FUNCTION)
             return ssi.funScript()->staticLevel() + 1;
     }
     return 1;
 }
 
 void
-JSScript::updateBaselineOrIonRaw()
+JSScript::updateBaselineOrIonRaw(JSContext *maybecx)
 {
     if (hasIonScript()) {
-        baselineOrIonRaw = ion->method()->raw();
-        baselineOrIonSkipArgCheck = ion->method()->raw() + ion->getSkipArgCheckEntryOffset();
+        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();
+        }
     } 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
@@ -1248,39 +1248,49 @@ class JSScript : public js::gc::Barriere
         return ion;
     }
     js::jit::IonScript *maybeIonScript() const {
         return ion;
     }
     js::jit::IonScript *const *addressOfIonScript() const {
         return &ion;
     }
-    void setIonScript(js::jit::IonScript *ionScript) {
+    void setIonScript(JSContext *maybecx, js::jit::IonScript *ionScript) {
         if (hasIonScript())
             js::jit::IonScript::writeBarrierPre(tenuredZone(), ion);
         ion = ionScript;
         MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
-        updateBaselineOrIonRaw();
+        updateBaselineOrIonRaw(maybecx);
     }
 
     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 {
         JS_ASSERT(hasBaselineScript());
         return baseline;
     }
     inline void setBaselineScript(JSContext *maybecx, js::jit::BaselineScript *baselineScript);
 
-    void updateBaselineOrIonRaw();
+    void updateBaselineOrIonRaw(JSContext *maybecx);
+
+    void setPendingIonBuilder(JSContext *maybecx, js::jit::IonBuilder *builder) {
+        JS_ASSERT(!builder || !ion->pendingBuilder());
+        ion->setPendingBuilderPrivate(builder);
+        updateBaselineOrIonRaw(maybecx);
+    }
+    js::jit::IonBuilder *pendingIonBuilder() {
+        JS_ASSERT(hasIonScript());
+        return ion->pendingBuilder();
+    }
 
     bool hasParallelIonScript() const {
         return parallelIon && parallelIon != ION_DISABLED_SCRIPT && parallelIon != ION_COMPILING_SCRIPT;
     }
 
     bool canParallelIonCompile() const {
         return parallelIon != ION_DISABLED_SCRIPT;
     }
@@ -1309,16 +1319,19 @@ class JSScript : public js::gc::Barriere
         return offsetof(JSScript, ion);
     }
     static size_t offsetOfParallelIonScript() {
         return offsetof(JSScript, parallelIon);
     }
     static size_t offsetOfBaselineOrIonRaw() {
         return offsetof(JSScript, baselineOrIonRaw);
     }
+    uint8_t *baselineOrIonRawPointer() const {
+        return baselineOrIonRaw;
+    }
     static size_t offsetOfBaselineOrIonSkipArgCheck() {
         return offsetof(JSScript, baselineOrIonSkipArgCheck);
     }
 
     bool isRelazifiable() const {
         return (selfHosted() || lazyScript) &&
                !isGenerator() && !hasBaselineScript() && !hasAnyIonScript();
     }
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -164,17 +164,17 @@ JSScript::setIsCallsiteClone(JSObject *f
 
 inline void
 JSScript::setBaselineScript(JSContext *maybecx, js::jit::BaselineScript *baselineScript)
 {
     if (hasBaselineScript())
         js::jit::BaselineScript::writeBarrierPre(tenuredZone(), baseline);
     MOZ_ASSERT(!hasIonScript());
     baseline = baselineScript;
-    updateBaselineOrIonRaw();
+    updateBaselineOrIonRaw(maybecx);
 }
 
 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
@@ -152,20 +152,31 @@ js::CancelOffThreadIonCompile(JSCompartm
         }
     }
 
     /* Cancel code generation for any completed entries. */
     GlobalHelperThreadState::IonBuilderVector &finished = HelperThreadState().ionFinishedList();
     for (size_t i = 0; i < finished.length(); i++) {
         jit::IonBuilder *builder = finished[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
-            jit::FinishOffThreadBuilder(builder);
+            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()->setPendingIonBuilder(nullptr, nullptr);
+            jit::FinishOffThreadBuilder(nullptr, builder);
+        }
+        builder = next;
+    }
 }
 
 static const JSClass parseTaskGlobalClass = {
     "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
     JS_PropertyStub,  JS_DeletePropertyStub,
     JS_PropertyStub,  JS_StrictPropertyStub,
     JS_EnumerateStub, JS_ResolveStub,
     JS_ConvertStub,   nullptr,
@@ -430,19 +441,30 @@ GlobalHelperThreadState::ensureInitializ
         if (!helper.thread || !helper.threadData->init())
             CrashAtUnhandlableOOM("GlobalHelperThreadState::ensureInitialized");
     }
 
     resetAsmJSFailureState();
 }
 
 GlobalHelperThreadState::GlobalHelperThreadState()
+ : cpuCount(0),
+   threadCount(0),
+   threads(nullptr),
+   asmJSCompilationInProgress(nullptr),
+   helperLock(nullptr),
+#ifdef DEbUG
+   lockOwner(nullptr),
+#endif
+   consumerWakeup(nullptr),
+   producerWakeup(nullptr),
+   pauseWakeup(nullptr),
+   numAsmJSFailedJobs(0),
+   asmJSFailedFunction(nullptr)
 {
-    mozilla::PodZero(this);
-
     cpuCount = GetCPUCount();
     threadCount = ThreadCountForCPUCount(cpuCount);
 
     MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
 
     helperLock = PR_NewLock();
     consumerWakeup = PR_NewCondVar(helperLock);
     producerWakeup = PR_NewCondVar(helperLock);
@@ -458,16 +480,18 @@ GlobalHelperThreadState::finish()
             threads[i].destroy();
         js_free(threads);
     }
 
     PR_DestroyCondVar(consumerWakeup);
     PR_DestroyCondVar(producerWakeup);
     PR_DestroyCondVar(pauseWakeup);
     PR_DestroyLock(helperLock);
+
+    ionLazyLinkList_.clear();
 }
 
 void
 GlobalHelperThreadState::lock()
 {
     JS_ASSERT(!isLocked());
     AssertCurrentThreadCanLock(HelperThreadStateLock);
     PR_Lock(helperLock);
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -42,26 +42,30 @@ class GlobalHelperThreadState
     // Number of threads to create. May be accessed without locking.
     size_t threadCount;
 
     typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
     typedef Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> AsmJSParallelTaskVector;
     typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
     typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
     typedef Vector<GCHelperState *, 0, SystemAllocPolicy> GCHelperStateVector;
+    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_;
+
     // AsmJS worklist and finished jobs.
     //
     // Simultaneous AsmJS compilations all service the same AsmJS module.
     // The main thread must pick up finished optimizations and perform codegen.
     // |asmJSCompilationInProgress| is used to avoid triggering compilations
     // for more than one module at a time.
     AsmJSParallelTaskVector asmJSWorklist_, asmJSFinishedList_;
 
@@ -132,16 +136,20 @@ class GlobalHelperThreadState
     IonBuilderVector &ionWorklist() {
         JS_ASSERT(isLocked());
         return ionWorklist_;
     }
     IonBuilderVector &ionFinishedList() {
         JS_ASSERT(isLocked());
         return ionFinishedList_;
     }
+    IonBuilderList &ionLazyLinkList() {
+        JS_ASSERT(isLocked());
+        return ionLazyLinkList_;
+    }
 
     AsmJSParallelTaskVector &asmJSWorklist() {
         JS_ASSERT(isLocked());
         return asmJSWorklist_;
     }
     AsmJSParallelTaskVector &asmJSFinishedList() {
         JS_ASSERT(isLocked());
         return asmJSFinishedList_;