Bug 1113240 - Allow optimizing nursery-allocated getters/setters in Ion. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Sat, 31 Jan 2015 14:52:04 +0100
changeset 226881 cb01144424b5493c90f16733568eb109f3eb2fd0
parent 226880 76ac4c3323a95e636c240a0e050b0656fc3d7d78
child 226882 5c4b7be30f7d300f8259dd8b7f4a3a3f1180e8a6
push id54948
push userjandemooij@gmail.com
push dateSat, 31 Jan 2015 14:01:31 +0000
treeherdermozilla-inbound@cb01144424b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1113240
milestone38.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 1113240 - Allow optimizing nursery-allocated getters/setters in Ion. r=bhackett
js/src/jit-test/tests/ion/nursery-getter-setter.js
js/src/jit/BaselineIC.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/Ion.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonCode.h
js/src/jit/JitCompartment.h
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MIRGenerator.h
js/src/jit/MIRGraph.cpp
js/src/jit/MOpcodes.h
js/src/jit/arm/Assembler-arm.cpp
js/src/jit/arm/Assembler-arm.h
js/src/jit/mips/Assembler-mips.cpp
js/src/jit/mips/Assembler-mips.h
js/src/jit/none/MacroAssembler-none.h
js/src/jit/shared/Assembler-shared.h
js/src/jit/shared/Assembler-x86-shared.cpp
js/src/jit/shared/Assembler-x86-shared.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/nursery-getter-setter.js
@@ -0,0 +1,17 @@
+var threshold = getJitCompilerOptions()["ion.warmup.trigger"] + 101;
+function bar(i) {
+    if (!i)
+        with (this) {}; // Don't inline.
+    if (i === threshold)
+        minorgc();
+}
+
+function f() {
+    var o2 = Object.create({get foo() { return this.x; }, set foo(x) { this.x = x + 1; }});
+    for (var i=0; i<2000; i++) {
+        o2.foo = i;
+        assertEq(o2.foo, i + 1);
+        bar(i);
+    }
+}
+f();
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -3329,24 +3329,16 @@ IsCacheableGetPropCall(JSContext *cx, JS
 
     if (!shape->hasGetterValue())
         return false;
 
     if (!shape->getterValue().isObject() || !shape->getterObject()->is<JSFunction>())
         return false;
 
     JSFunction *func = &shape->getterObject()->as<JSFunction>();
-
-    // Information from get prop call ICs may be used directly from Ion code,
-    // and should not be nursery allocated.
-    if (IsInsideNursery(holder) || IsInsideNursery(func)) {
-        *isTemporarilyUnoptimizable = true;
-        return false;
-    }
-
     if (func->isNative()) {
         *isScripted = false;
         return true;
     }
 
     if (!func->hasJITCode()) {
         *isTemporarilyUnoptimizable = true;
         return false;
@@ -3452,23 +3444,16 @@ IsCacheableSetPropCall(JSContext *cx, JS
     if (!shape->hasSetterValue())
         return false;
 
     if (!shape->setterValue().isObject() || !shape->setterObject()->is<JSFunction>())
         return false;
 
     JSFunction *func = &shape->setterObject()->as<JSFunction>();
 
-    // Information from set prop call ICs may be used directly from Ion code,
-    // and should not be nursery allocated.
-    if (IsInsideNursery(holder) || IsInsideNursery(func)) {
-        *isTemporarilyUnoptimizable = true;
-        return false;
-    }
-
     if (func->isNative()) {
         *isScripted = false;
         return true;
     }
 
     if (!func->hasJITCode()) {
         *isTemporarilyUnoptimizable = true;
         return false;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2150,16 +2150,29 @@ CodeGenerator::visitPointer(LPointer *li
 {
     if (lir->kind() == LPointer::GC_THING)
         masm.movePtr(ImmGCPtr(lir->gcptr()), ToRegister(lir->output()));
     else
         masm.movePtr(ImmPtr(lir->ptr()), ToRegister(lir->output()));
 }
 
 void
+CodeGenerator::visitNurseryObject(LNurseryObject *lir)
+{
+    Register output = ToRegister(lir->output());
+    uint32_t index = lir->mir()->index();
+
+    // Store a dummy JSObject pointer. We will fix it up on the main thread,
+    // in JitCode::fixupNurseryObjects. The low bit is set to distinguish
+    // it from a real JSObject pointer.
+    JSObject *ptr = reinterpret_cast<JSObject*>((uintptr_t(index) << 1) | 1);
+    masm.movePtr(ImmGCPtr(IonNurseryPtr(ptr)), output);
+}
+
+void
 CodeGenerator::visitSlots(LSlots *lir)
 {
     Address slots(ToRegister(lir->object()), NativeObject::offsetOfSlots());
     masm.loadPtr(slots, ToRegister(lir->output()));
 }
 
 void
 CodeGenerator::visitLoadSlotT(LLoadSlotT *lir)
@@ -7378,16 +7391,19 @@ CodeGenerator::link(JSContext *cx, types
             patchableTLScripts_[i].fixup(&masm);
             Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, patchableTLScripts_[i]),
                                                ImmPtr((void *) uintptr_t(textId)),
                                                ImmPtr((void *)0));
         }
     }
 #endif
 
+    // Replace dummy JSObject pointers embedded by LNurseryObject.
+    code->fixupNurseryObjects(cx, gen->nurseryObjects());
+
     // The correct state for prebarriers is unknown until the end of compilation,
     // since a GC can occur during code generation. All barriers are emitted
     // off-by-default, and are toggled on here if necessary.
     if (cx->zone()->needsIncrementalBarrier())
         ionScript->toggleBarriers(true);
 
     // Attach any generated script counts to the script.
     if (IonScriptCounts *counts = extractScriptCounts())
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -106,16 +106,17 @@ class CodeGenerator : public CodeGenerat
     void visitRegExpTest(LRegExpTest *lir);
     void visitOutOfLineRegExpTest(OutOfLineRegExpTest *ool);
     void visitRegExpReplace(LRegExpReplace *lir);
     void visitStringReplace(LStringReplace *lir);
     void visitLambda(LLambda *lir);
     void visitLambdaArrow(LLambdaArrow *lir);
     void visitLambdaForSingleton(LLambdaForSingleton *lir);
     void visitPointer(LPointer *lir);
+    void visitNurseryObject(LNurseryObject *lir);
     void visitSlots(LSlots *lir);
     void visitLoadSlotT(LLoadSlotT *lir);
     void visitLoadSlotV(LLoadSlotV *lir);
     void visitStoreSlotT(LStoreSlotT *lir);
     void visitStoreSlotV(LStoreSlotV *lir);
     void visitElements(LElements *lir);
     void visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
     void visitMaybeToDoubleElement(LMaybeToDoubleElement *lir);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -158,17 +158,18 @@ JitRuntime::JitRuntime()
     argumentsRectifierReturnAddr_(nullptr),
     invalidator_(nullptr),
     debugTrapHandler_(nullptr),
     baselineDebugModeOSRHandler_(nullptr),
     functionWrappers_(nullptr),
     osrTempData_(nullptr),
     mutatingBackedgeList_(false),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
-    jitcodeGlobalTable_(nullptr)
+    jitcodeGlobalTable_(nullptr),
+    hasIonNurseryObjects_(false)
 {
 }
 
 JitRuntime::~JitRuntime()
 {
     js_delete(functionWrappers_);
     freeOsrTempData();
 
@@ -649,16 +650,27 @@ JitCode::trace(JSTracer *trc)
     if (dataRelocTableBytes_) {
         uint8_t *start = code_ + dataRelocTableOffset();
         CompactBufferReader reader(start, start + dataRelocTableBytes_);
         MacroAssembler::TraceDataRelocations(trc, this, reader);
     }
 }
 
 void
+JitCode::fixupNurseryObjects(JSContext *cx, const ObjectVector &nurseryObjects)
+{
+    if (nurseryObjects.empty() || !dataRelocTableBytes_)
+        return;
+
+    uint8_t *start = code_ + dataRelocTableOffset();
+    CompactBufferReader reader(start, start + dataRelocTableBytes_);
+    MacroAssembler::FixupNurseryObjects(cx, this, reader, nurseryObjects);
+}
+
+void
 JitCode::finalize(FreeOp *fop)
 {
     JSRuntime *rt = fop->runtime();
 
     // If this jitcode has a bytecode map, de-register it.
     if (hasBytecodeMap_) {
         MOZ_ASSERT(rt->jitRuntime()->hasJitcodeGlobalTable());
         rt->jitRuntime()->getJitcodeGlobalTable()->removeEntry(raw(), rt);
@@ -1666,16 +1678,72 @@ AttachFinishedCompilations(JSContext *cx
                 cx->clearPendingException();
             }
         }
 
         FinishOffThreadBuilder(cx, builder);
     }
 }
 
+void
+MIRGenerator::traceNurseryObjects(JSTracer *trc)
+{
+    MarkObjectRootRange(trc, nurseryObjects_.length(), nurseryObjects_.begin(), "ion-nursery-objects");
+}
+
+class MarkOffThreadNurseryObjects : public gc::BufferableRef
+{
+  public:
+    void mark(JSTracer *trc);
+};
+
+void
+MarkOffThreadNurseryObjects::mark(JSTracer *trc)
+{
+    JSRuntime *rt = trc->runtime();
+
+    MOZ_ASSERT(rt->jitRuntime()->hasIonNurseryObjects());
+    rt->jitRuntime()->setHasIonNurseryObjects(false);
+
+    AutoLockHelperThreadState lock;
+    if (!HelperThreadState().threads)
+        return;
+
+    // Trace nursery objects of any builders which haven't started yet.
+    GlobalHelperThreadState::IonBuilderVector &worklist = HelperThreadState().ionWorklist();
+    for (size_t i = 0; i < worklist.length(); i++) {
+        jit::IonBuilder *builder = worklist[i];
+        if (builder->script()->runtimeFromAnyThread() == rt)
+            builder->traceNurseryObjects(trc);
+    }
+
+    // Trace nursery objects of in-progress entries.
+    for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
+        HelperThread &helper = HelperThreadState().threads[i];
+        if (helper.ionBuilder && helper.ionBuilder->script()->runtimeFromAnyThread() == rt)
+            helper.ionBuilder->traceNurseryObjects(trc);
+    }
+
+    // Trace nursery objects of any completed entries.
+    GlobalHelperThreadState::IonBuilderVector &finished = HelperThreadState().ionFinishedList();
+    for (size_t i = 0; i < finished.length(); i++) {
+        jit::IonBuilder *builder = finished[i];
+        if (builder->script()->runtimeFromAnyThread() == rt)
+            builder->traceNurseryObjects(trc);
+    }
+
+    // Trace nursery objects of lazy-linked builders.
+    jit::IonBuilder *builder = HelperThreadState().ionLazyLinkList().getFirst();
+    while (builder) {
+        if (builder->script()->runtimeFromAnyThread() == rt)
+            builder->traceNurseryObjects(trc);
+        builder = builder->getNext();
+    }
+}
+
 static inline bool
 OffThreadCompilationAvailable(JSContext *cx)
 {
     // Even if off thread compilation is enabled, compilation must still occur
     // on the main thread in some cases.
     //
     // Require cpuCount > 1 so that Ion compilation jobs and main-thread
     // execution are not competing for the same resources.
@@ -1861,16 +1929,25 @@ IonCompile(JSContext *cx, JSScript *scri
     // If possible, compile the script off thread.
     if (OffThreadCompilationAvailable(cx)) {
         if (!recompile)
             builderScript->setIonScript(cx, ION_COMPILING_SCRIPT);
 
         JitSpew(JitSpew_IonLogs, "Can't log script %s:%d. (Compiled on background thread.)",
                 builderScript->filename(), builderScript->lineno());
 
+        JSRuntime *rt = cx->runtime();
+        if (!builder->nurseryObjects().empty() && !rt->jitRuntime()->hasIonNurseryObjects()) {
+            // Ensure the builder's nursery objects are marked when a nursery
+            // GC happens on the main thread.
+            MarkOffThreadNurseryObjects mark;
+            rt->gc.storeBuffer.putGeneric(mark);
+            rt->jitRuntime()->setHasIonNurseryObjects(true);
+        }
+
         if (!StartOffThreadIonCompile(cx, builder)) {
             JitSpew(JitSpew_IonAbort, "Unable to start off-thread ion compilation.");
             return AbortReason_Alloc;
         }
 
         // The allocator and associated data will be destroyed after being
         // processed in the finishedOffThreadCompilations list.
         autoDelete.forget();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -211,16 +211,47 @@ void
 IonBuilder::spew(const char *message)
 {
     // Don't call PCToLineNumber in release builds.
 #ifdef DEBUG
     JitSpew(JitSpew_IonMIR, "%s @ %s:%d", message, script()->filename(), PCToLineNumber(script(), pc));
 #endif
 }
 
+MInstruction *
+IonBuilder::constantMaybeNursery(JSObject *obj)
+{
+    MOZ_ASSERT(obj);
+    if (!IsInsideNursery(obj))
+        return constant(ObjectValue(*obj));
+
+    // If |obj| is in the nursery, we have to add it to the list of nursery
+    // objects that get traced during off-thread compilation. We use
+    // MNurseryObject to ensure we will patch the code with the right
+    // pointer after codegen is done.
+
+    size_t index = UINT32_MAX;
+    for (size_t i = 0, len = nurseryObjects_.length(); i < len; i++) {
+        if (nurseryObjects_[i] == obj) {
+            index = i;
+            break;
+        }
+    }
+
+    if (index == UINT32_MAX) {
+        if (!nurseryObjects_.append(obj))
+            return nullptr;
+        index = nurseryObjects_.length() - 1;
+    }
+
+    MNurseryObject *ins = MNurseryObject::New(alloc(), obj, index, constraints());
+    current->add(ins);
+    return ins;
+}
+
 static inline int32_t
 GetJumpOffset(jsbytecode *pc)
 {
     MOZ_ASSERT(js_CodeSpec[JSOp(*pc)].type() == JOF_JUMP);
     return GET_JUMP_OFFSET(pc);
 }
 
 IonBuilder::CFGState
@@ -5833,16 +5864,19 @@ IonBuilder::makeCallsiteClone(JSFunction
     current->add(clone);
     return clone;
 }
 
 bool
 IonBuilder::testShouldDOMCall(types::TypeSet *inTypes,
                               JSFunction *func, JSJitInfo::OpType opType)
 {
+    if (IsInsideNursery(func))
+        return false;
+
     if (!func->isNative() || !func->jitInfo())
         return false;
 
     // If all the DOM objects flowing through are legal with this
     // property, we can bake in a call to the bottom half of the DOM
     // accessor
     DOMInstanceClassHasProtoAtDepth instanceChecker =
         compartment->runtime()->DOMcallbacks()->instanceClassMatchesProto;
@@ -9292,27 +9326,25 @@ IonBuilder::objectsHaveCommonPrototype(t
             }
             if (singleton) {
                 if (types::CanHaveEmptyPropertyTypesForOwnProperty(singleton)) {
                     MOZ_ASSERT(singleton->is<GlobalObject>());
                     *guardGlobal = true;
                 }
             }
 
-            if (!type->hasTenuredProto())
-                return false;
-            JSObject *proto = type->proto().toObjectOrNull();
+            JSObject *proto = type->protoMaybeInNursery().toObjectOrNull();
             if (proto == foundProto)
                 break;
             if (!proto) {
                 // The foundProto being searched for did not show up on the
                 // object's prototype chain.
                 return false;
             }
-            type = types::TypeObjectKey::get(type->proto().toObjectOrNull());
+            type = types::TypeObjectKey::get(proto);
         }
     }
 
     return true;
 }
 
 void
 IonBuilder::freezePropertiesForCommonPrototype(types::TemporaryTypeSet *types, PropertyName *name,
@@ -9331,24 +9363,24 @@ IonBuilder::freezePropertiesForCommonPro
 
         while (true) {
             types::HeapTypeSetKey property = type->property(NameToId(name));
             JS_ALWAYS_TRUE(!property.isOwnProperty(constraints(), allowEmptyTypesforGlobal));
 
             // Don't mark the proto. It will be held down by the shape
             // guard. This allows us to use properties found on prototypes
             // with properties unknown to TI.
-            if (type->proto() == TaggedProto(foundProto))
+            if (type->protoMaybeInNursery() == TaggedProto(foundProto))
                 break;
-            type = types::TypeObjectKey::get(type->proto().toObjectOrNull());
-        }
-    }
-}
-
-inline bool
+            type = types::TypeObjectKey::get(type->protoMaybeInNursery().toObjectOrNull());
+        }
+    }
+}
+
+bool
 IonBuilder::testCommonGetterSetter(types::TemporaryTypeSet *types, PropertyName *name,
                                    bool isGetter, JSObject *foundProto, Shape *lastProperty,
                                    MDefinition **guard,
                                    Shape *globalShape/* = nullptr*/,
                                    MDefinition **globalGuard/* = nullptr */)
 {
     MOZ_ASSERT_IF(globalShape, globalGuard);
     bool guardGlobal;
@@ -9380,17 +9412,17 @@ IonBuilder::testCommonGetterSetter(types
     }
 
     if (foundProto->isNative()) {
         Shape *propShape = foundProto->as<NativeObject>().lookupPure(name);
         if (propShape && !propShape->configurable())
             return true;
     }
 
-    MInstruction *wrapper = constant(ObjectValue(*foundProto));
+    MInstruction *wrapper = constantMaybeNursery(foundProto);
     *guard = addShapeGuard(wrapper, lastProperty, Bailout_ShapeGuard);
     return true;
 }
 
 void
 IonBuilder::replaceMaybeFallbackFunctionGetter(MGetPropertyCache *cache)
 {
     // Discard the last prior resume point of the previous MGetPropertyCache.
@@ -10044,17 +10076,17 @@ IonBuilder::getPropTryCommonGetter(bool 
         obj = guardObj;
     }
 
     // Spoof stack to expected state for call.
 
     // Make sure there's enough room
     if (!current->ensureHasSlots(2))
         return false;
-    pushConstant(ObjectValue(*commonGetter));
+    current->push(constantMaybeNursery(commonGetter));
 
     current->push(obj);
 
     CallInfo callInfo(alloc(), false);
     if (!callInfo.init(current, 0))
         return false;
 
     if (commonGetter->isNative()) {
@@ -10084,17 +10116,18 @@ IonBuilder::getPropTryCommonGetter(bool 
           case InliningDecision_Inline:
             if (!inlineScriptedCall(callInfo, commonGetter))
                 return false;
             *emitted = true;
             return true;
         }
     }
 
-    if (!makeCall(commonGetter, callInfo, false))
+    JSFunction *tenuredCommonGetter = IsInsideNursery(commonGetter) ? nullptr : commonGetter;
+    if (!makeCall(tenuredCommonGetter, callInfo, false))
         return false;
 
     // If the getter could have been inlined, don't track success. The call to
     // makeInliningDecision above would have tracked a specific reason why we
     // couldn't inline.
     if (!commonGetter->isInterpreted())
         trackOptimizationSuccess();
 
@@ -10492,18 +10525,17 @@ IonBuilder::setPropTryCommonSetter(bool 
         obj = guardObj;
     }
 
     // Dummy up the stack, as in getprop. We are pushing an extra value, so
     // ensure there is enough space.
     if (!current->ensureHasSlots(3))
         return false;
 
-    pushConstant(ObjectValue(*commonSetter));
-
+    current->push(constantMaybeNursery(commonSetter));
     current->push(obj);
     current->push(value);
 
     // Call the setter. Note that we have to push the original value, not
     // the setter's return value.
     CallInfo callInfo(alloc(), false);
     if (!callInfo.init(current, 1))
         return false;
@@ -10523,17 +10555,18 @@ IonBuilder::setPropTryCommonSetter(bool 
           case InliningDecision_Inline:
             if (!inlineScriptedCall(callInfo, commonSetter))
                 return false;
             *emitted = true;
             return true;
         }
     }
 
-    MCall *call = makeCallHelper(commonSetter, callInfo, false);
+    JSFunction *tenuredCommonSetter = IsInsideNursery(commonSetter) ? nullptr : commonSetter;
+    MCall *call = makeCallHelper(tenuredCommonSetter, callInfo, false);
     if (!call)
         return false;
 
     current->push(value);
     if (!resumeAfter(call))
         return false;
 
     // If the setter could have been inlined, don't track success. The call to
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -233,16 +233,18 @@ class IonBuilder
     bool processIterators();
     bool inspectOpcode(JSOp op);
     uint32_t readIndex(jsbytecode *pc);
     JSAtom *readAtom(jsbytecode *pc);
     bool abort(const char *message, ...);
     void trackActionableAbort(const char *message);
     void spew(const char *message);
 
+    MInstruction *constantMaybeNursery(JSObject *obj);
+
     JSFunction *getSingleCallTarget(types::TemporaryTypeSet *calleeTypes);
     bool getPolyCallTargets(types::TemporaryTypeSet *calleeTypes, bool constructing,
                             ObjectVector &targets, uint32_t maxTargets);
 
     void popCfgStack();
     DeferredEdge *filterDeadDeferredEdges(DeferredEdge *edge);
     bool processDeferredContinues(CFGState &state);
     ControlStatus processControlEnd();
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -27,16 +27,18 @@ class AsmJSModule;
 
 namespace jit {
 
 class MacroAssembler;
 class CodeOffsetLabel;
 class PatchableBackedge;
 class IonBuilder;
 
+typedef Vector<JSObject *, 4, JitAllocPolicy> ObjectVector;
+
 class JitCode : public gc::TenuredCell
 {
   protected:
     uint8_t *code_;
     ExecutablePool *pool_;
     uint32_t bufferSize_;             // Total buffer size. Does not include headerSize_.
     uint32_t insnSize_;               // Instruction stream size.
     uint32_t dataSize_;               // Size of the read-only data area.
@@ -107,16 +109,18 @@ class JitCode : public gc::TenuredCell
     }
     void trace(JSTracer *trc);
     void finalize(FreeOp *fop);
     void fixupAfterMovingGC() {}
     void setInvalidated() {
         invalidated_ = true;
     }
 
+    void fixupNurseryObjects(JSContext *cx, const ObjectVector &nurseryObjects);
+
     void setHasBytecodeMap() {
         hasBytecodeMap_ = true;
     }
 
     void togglePreBarriers(bool enabled);
 
     // If this JitCode object has been, effectively, corrupted due to
     // invalidation patching, then we have to remember this so we don't try and
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -230,16 +230,18 @@ class JitRuntime
     // value that will be temporarily corrupt. This special override value is set
     // 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_;
 
+    bool hasIonNurseryObjects_;
+
   private:
     JitCode *generateLazyLinkStub(JSContext *cx);
     JitCode *generateProfilerExitFrameTailStub(JSContext *cx);
     JitCode *generateExceptionTailStub(JSContext *cx, void *handler);
     JitCode *generateBailoutTailStub(JSContext *cx);
     JitCode *generateEnterJIT(JSContext *cx, EnterJitType type);
     JitCode *generateArgumentsRectifier(JSContext *cx, void **returnAddrOut);
     JitCode *generateBailoutTable(JSContext *cx, uint32_t frameClass);
@@ -385,16 +387,23 @@ class JitRuntime
         return v;
     }
     void setIonReturnOverride(const js::Value &v) {
         MOZ_ASSERT(!hasIonReturnOverride());
         MOZ_ASSERT(!v.isMagic());
         ionReturnOverride_ = v;
     }
 
+    bool hasIonNurseryObjects() const {
+        return hasIonNurseryObjects_;
+    }
+    void setHasIonNurseryObjects(bool b)  {
+        hasIonNurseryObjects_ = b;
+    }
+
     bool hasJitcodeGlobalTable() const {
         return jitcodeGlobalTable_ != nullptr;
     }
 
     JitcodeGlobalTable *getJitcodeGlobalTable() {
         MOZ_ASSERT(hasJitcodeGlobalTable());
         return jitcodeGlobalTable_;
     }
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -678,16 +678,26 @@ class LValue : public LInstructionHelper
       : v_(v)
     { }
 
     Value value() const {
         return v_;
     }
 };
 
+class LNurseryObject : public LInstructionHelper<1, 0, 0>
+{
+  public:
+    LIR_HEADER(NurseryObject);
+
+    MNurseryObject *mir() const {
+        return mir_->toNurseryObject();
+    }
+};
+
 // Clone an object literal such as we are not modifying the object contained in
 // the sources.
 class LCloneLiteral : public LCallInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(CloneLiteral)
 
     explicit LCloneLiteral(const LAllocation &obj)
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -330,16 +330,17 @@
     _(RecompileCheck)               \
     _(MemoryBarrier)                \
     _(AssertRangeI)                 \
     _(AssertRangeD)                 \
     _(AssertRangeF)                 \
     _(AssertRangeV)                 \
     _(LexicalCheck)                 \
     _(ThrowUninitializedLexical)    \
+    _(NurseryObject)                \
     _(Debugger)
 
 #if defined(JS_CODEGEN_X86)
 # include "jit/x86/LOpcodes-x86.h"
 #elif defined(JS_CODEGEN_X64)
 # include "jit/x64/LOpcodes-x64.h"
 #elif defined(JS_CODEGEN_ARM)
 # include "jit/arm/LOpcodes-arm.h"
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3998,16 +3998,22 @@ LIRGenerator::visitThrowUninitializedLex
 void
 LIRGenerator::visitDebugger(MDebugger *ins)
 {
     LDebugger *lir = new(alloc()) LDebugger(tempFixed(CallTempReg0), tempFixed(CallTempReg1));
     assignSnapshot(lir, Bailout_Debugger);
     add(lir, ins);
 }
 
+void
+LIRGenerator::visitNurseryObject(MNurseryObject *ins)
+{
+    define(new(alloc()) LNurseryObject(), ins);
+}
+
 static void
 SpewResumePoint(MBasicBlock *block, MInstruction *ins, MResumePoint *resumePoint)
 {
     fprintf(JitSpewFile, "Current resume point %p details:\n", (void *)resumePoint);
     fprintf(JitSpewFile, "    frame count: %u\n", resumePoint->frameCount());
 
     if (ins) {
         fprintf(JitSpewFile, "    taken after: ");
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -282,14 +282,15 @@ class LIRGenerator : public LIRGenerator
     void visitPhi(MPhi *ins);
     void visitBeta(MBeta *ins);
     void visitObjectState(MObjectState *ins);
     void visitArrayState(MArrayState *ins);
     void visitUnknownValue(MUnknownValue *ins);
     void visitLexicalCheck(MLexicalCheck *ins);
     void visitThrowUninitializedLexical(MThrowUninitializedLexical *ins);
     void visitDebugger(MDebugger *ins);
+    void visitNurseryObject(MNurseryObject *ins);
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_Lowering_h */
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -671,16 +671,17 @@ MakeUnknownTypeSet()
 
 MConstant::MConstant(const js::Value &vp, types::CompilerConstraintList *constraints)
   : value_(vp)
 {
     setResultType(MIRTypeFromValue(vp));
     if (vp.isObject()) {
         // Create a singleton type set for the object. This isn't necessary for
         // other types as the result type encodes all needed information.
+        MOZ_ASSERT(!IsInsideNursery(&vp.toObject()));
         setResultTypeSet(MakeSingletonTypeSet(constraints, &vp.toObject()));
     }
     if (vp.isMagic() && vp.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
         // JS_UNINITIALIZED_LEXICAL does not escape to script and is not
         // observed in type sets. However, it may flow around freely during
         // Ion compilation. Give it an unknown typeset to poison any type sets
         // it merges with.
         //
@@ -690,16 +691,17 @@ MConstant::MConstant(const js::Value &vp
     }
 
     setMovable();
 }
 
 MConstant::MConstant(JSObject *obj)
   : value_(ObjectValue(*obj))
 {
+    MOZ_ASSERT(!IsInsideNursery(obj));
     setResultType(MIRType_Object);
     setMovable();
 }
 
 HashNumber
 MConstant::valueHash() const
 {
     // Fold all 64 bits into the 32-bit result. It's tempting to just discard
@@ -799,16 +801,49 @@ MConstant::canProduceFloat32() const
 
     if (type() == MIRType_Int32)
         return IsFloat32Representable(static_cast<double>(value_.toInt32()));
     if (type() == MIRType_Double)
         return IsFloat32Representable(value_.toDouble());
     return true;
 }
 
+MNurseryObject::MNurseryObject(JSObject *obj, uint32_t index, types::CompilerConstraintList *constraints)
+  : index_(index)
+{
+    setResultType(MIRType_Object);
+
+    MOZ_ASSERT(IsInsideNursery(obj));
+    MOZ_ASSERT(!obj->hasSingletonType());
+    setResultTypeSet(MakeSingletonTypeSet(constraints, obj));
+
+    setMovable();
+}
+
+MNurseryObject *
+MNurseryObject::New(TempAllocator &alloc, JSObject *obj, uint32_t index,
+                    types::CompilerConstraintList *constraints)
+{
+    return new(alloc) MNurseryObject(obj, index, constraints);
+}
+
+HashNumber
+MNurseryObject::valueHash() const
+{
+    return HashNumber(index_);
+}
+
+bool
+MNurseryObject::congruentTo(const MDefinition *ins) const
+{
+    if (!ins->isNurseryObject())
+        return false;
+    return ins->toNurseryObject()->index_ == index_;
+}
+
 MDefinition*
 MSimdValueX4::foldsTo(TempAllocator &alloc)
 {
     DebugOnly<MIRType> scalarType = SimdTypeToScalarType(type());
     bool allConstants = true;
     bool allSame = true;
 
     for (size_t i = 0; i < 4; ++i) {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1324,16 +1324,43 @@ class MConstant : public MNullaryInstruc
     bool needTruncation(TruncateKind kind) MOZ_OVERRIDE;
     void truncate() MOZ_OVERRIDE;
 
     bool canProduceFloat32() const MOZ_OVERRIDE;
 
     ALLOW_CLONE(MConstant)
 };
 
+class MNurseryObject : public MNullaryInstruction
+{
+    // Index in MIRGenerator::nurseryObjects_.
+    uint32_t index_;
+
+  protected:
+    MNurseryObject(JSObject *obj, uint32_t index, types::CompilerConstraintList *constraints);
+
+  public:
+    INSTRUCTION_HEADER(NurseryObject)
+    static MNurseryObject *New(TempAllocator &alloc, JSObject *obj, uint32_t index,
+                               types::CompilerConstraintList *constraints = nullptr);
+
+    HashNumber valueHash() const MOZ_OVERRIDE;
+    bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE;
+
+    uint32_t index() const {
+        return index_;
+    }
+
+    AliasSet getAliasSet() const MOZ_OVERRIDE {
+        return AliasSet::None();
+    }
+
+    ALLOW_CLONE(MNurseryObject)
+};
+
 // Generic constructor of SIMD valuesX4.
 class MSimdValueX4
   : public MQuaternaryInstruction,
     public Mix4Policy<SimdScalarPolicy<0>, SimdScalarPolicy<1>,
                       SimdScalarPolicy<2>, SimdScalarPolicy<3> >::Data
 {
   protected:
     MSimdValueX4(MIRType type, MDefinition *x, MDefinition *y, MDefinition *z, MDefinition *w)
@@ -9820,17 +9847,17 @@ class MGuardObjectType
     }
 };
 
 // Guard on an object's identity, inclusively or exclusively.
 class MGuardObjectIdentity
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    AlwaysTenured<JSObject *> singleObject_;
+    AlwaysTenuredObject singleObject_;
     bool bailOnEquality_;
 
     MGuardObjectIdentity(MDefinition *obj, JSObject *singleObject, bool bailOnEquality)
       : MUnaryInstruction(obj),
         singleObject_(singleObject),
         bailOnEquality_(bailOnEquality)
     {
         setGuard();
--- a/js/src/jit/MIRGenerator.h
+++ b/js/src/jit/MIRGenerator.h
@@ -188,16 +188,22 @@ class MIRGenerator
     // Keep track of whether frame arguments are modified during execution.
     // RegAlloc needs to know this as spilling values back to their register
     // slots is not compatible with that.
     bool modifiesFrameArguments_;
 
     bool instrumentedProfiling_;
     bool instrumentedProfilingIsCached_;
 
+    // List of nursery objects used by this compilation. Can be traced by a
+    // minor GC while compilation happens off-thread. This Vector should only
+    // be accessed on the main thread (IonBuilder, nursery GC or
+    // CodeGenerator::link).
+    ObjectVector nurseryObjects_;
+
     void addAbortedNewScriptPropertiesType(types::TypeObject *type);
     void setForceAbort() {
         shouldForceAbort_ = true;
     }
     bool shouldForceAbort() {
         return shouldForceAbort_;
     }
 
@@ -205,14 +211,20 @@ class MIRGenerator
     AsmJSPerfSpewer asmJSPerfSpewer_;
 
   public:
     AsmJSPerfSpewer &perfSpewer() { return asmJSPerfSpewer_; }
 #endif
 
   public:
     const JitCompileOptions options;
+
+    void traceNurseryObjects(JSTracer *trc);
+
+    const ObjectVector &nurseryObjects() const {
+        return nurseryObjects_;
+    }
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_MIRGenerator_h */
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -34,16 +34,17 @@ MIRGenerator::MIRGenerator(CompileCompar
     maxAsmJSStackArgBytes_(0),
     performsCall_(false),
     usesSimd_(false),
     usesSimdCached_(false),
     minAsmJSHeapLength_(0),
     modifiesFrameArguments_(false),
     instrumentedProfiling_(false),
     instrumentedProfilingIsCached_(false),
+    nurseryObjects_(*alloc),
     options(options)
 { }
 
 bool
 MIRGenerator::usesSimd()
 {
     if (usesSimdCached_)
         return usesSimd_;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -7,16 +7,17 @@
 #ifndef jit_MOpcodes_h
 #define jit_MOpcodes_h
 
 namespace js {
 namespace jit {
 
 #define MIR_OPCODE_LIST(_)                                                  \
     _(Constant)                                                             \
+    _(NurseryObject)                                                        \
     _(SimdBox)                                                              \
     _(SimdUnbox)                                                            \
     _(SimdValueX4)                                                          \
     _(SimdSplatX4)                                                          \
     _(SimdConstant)                                                         \
     _(SimdConvert)                                                          \
     _(SimdReinterpretCast)                                                  \
     _(SimdExtractElement)                                                   \
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -800,16 +800,22 @@ static void
 TraceOneDataRelocation(JSTracer *trc, Iter *iter, MacroAssemblerARM *masm)
 {
     Instruction *ins = iter->cur();
     Register dest;
     Assembler::RelocStyle rs;
     const void *prior = Assembler::GetPtr32Target(iter, &dest, &rs);
     void *ptr = const_cast<void *>(prior);
 
+    // The low bit shouldn't be set. If it is, we probably got a dummy
+    // pointer inserted by CodeGenerator::visitNurseryObject, but we
+    // shouldn't be able to trigger GC before those are patched to their
+    // real values.
+    MOZ_ASSERT(!(uintptr_t(ptr) & 0x1));
+
     // No barrier needed since these are constants.
     gc::MarkGCThingUnbarriered(trc, &ptr, "ion-masm-ptr");
 
     if (ptr != prior) {
         masm->ma_movPatchable(Imm32(int32_t(ptr)), dest, Assembler::Always, rs, ins);
         // L_LDR won't cause any instructions to be updated.
         if (rs != Assembler::L_LDR) {
             AutoFlushICache::flush(uintptr_t(ins), 4);
@@ -842,16 +848,61 @@ TraceDataRelocations(JSTracer *trc, ARMB
 
 void
 Assembler::TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader)
 {
     ::TraceDataRelocations(trc, code->raw(), reader, static_cast<MacroAssemblerARM *>(Dummy));
 }
 
 void
+Assembler::FixupNurseryObjects(JSContext *cx, JitCode *code, CompactBufferReader &reader,
+                               const ObjectVector &nurseryObjects)
+{
+    MOZ_ASSERT(!nurseryObjects.empty());
+
+    uint8_t *buffer = code->raw();
+    bool hasNurseryPointers = false;
+    MacroAssemblerARM *masm = static_cast<MacroAssemblerARM *>(Dummy);
+
+    while (reader.more()) {
+        size_t offset = reader.readUnsigned();
+        InstructionIterator iter((Instruction*)(buffer + offset));
+        Instruction *ins = iter.cur();
+        Register dest;
+        Assembler::RelocStyle rs;
+        const void *prior = Assembler::GetPtr32Target(&iter, &dest, &rs);
+        void *ptr = const_cast<void *>(prior);
+        uintptr_t word = reinterpret_cast<uintptr_t>(ptr);
+
+        if (!(word & 0x1))
+            continue;
+
+        uint32_t index = word >> 1;
+        JSObject *obj = nurseryObjects[index];
+        masm->ma_movPatchable(Imm32(int32_t(obj)), dest, Assembler::Always, rs, ins);
+
+        if (rs != Assembler::L_LDR) {
+            // L_LDR won't cause any instructions to be updated.
+            AutoFlushICache::flush(uintptr_t(ins), 4);
+            AutoFlushICache::flush(uintptr_t(ins->next()), 4);
+        }
+
+        // Either all objects are still in the nursery, or all objects are
+        // tenured.
+        MOZ_ASSERT_IF(hasNurseryPointers, IsInsideNursery(obj));
+
+        if (!hasNurseryPointers && IsInsideNursery(obj))
+            hasNurseryPointers = true;
+    }
+
+    if (hasNurseryPointers)
+        cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(code);
+}
+
+void
 Assembler::copyJumpRelocationTable(uint8_t *dest)
 {
     if (jumpRelocations_.length())
         memcpy(dest, jumpRelocations_.buffer(), jumpRelocations_.length());
 }
 
 void
 Assembler::copyDataRelocationTable(uint8_t *dest)
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1592,16 +1592,19 @@ class Assembler : public AssemblerShared
     void call(void *target);
 
     void as_bkpt();
 
   public:
     static void TraceJumpRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader);
     static void TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader);
 
+    static void FixupNurseryObjects(JSContext *cx, JitCode *code, CompactBufferReader &reader,
+                                    const ObjectVector &nurseryObjects);
+
     static bool SupportsFloatingPoint() {
         return HasVFP();
     }
     static bool SupportsSimd() {
         return js::jit::SupportsSimd;
     }
 
   protected:
--- a/js/src/jit/mips/Assembler-mips.cpp
+++ b/js/src/jit/mips/Assembler-mips.cpp
@@ -283,16 +283,22 @@ Assembler::TraceJumpRelocations(JSTracer
 }
 
 static void
 TraceOneDataRelocation(JSTracer *trc, Instruction *inst)
 {
     void *ptr = (void *)Assembler::ExtractLuiOriValue(inst, inst->next());
     void *prior = ptr;
 
+    // The low bit shouldn't be set. If it is, we probably got a dummy
+    // pointer inserted by CodeGenerator::visitNurseryObject, but we
+    // shouldn't be able to trigger GC before those are patched to their
+    // real values.
+    MOZ_ASSERT(!(uintptr_t(ptr) & 0x1));
+
     // No barrier needed since these are constants.
     gc::MarkGCThingUnbarriered(trc, reinterpret_cast<void **>(&ptr), "ion-masm-ptr");
     if (ptr != prior) {
         Assembler::UpdateLuiOriValue(inst, inst->next(), uint32_t(ptr));
         AutoFlushICache::flush(uintptr_t(inst), 8);
     }
 }
 
@@ -318,16 +324,53 @@ TraceDataRelocations(JSTracer *trc, MIPS
 
 void
 Assembler::TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader)
 {
     ::TraceDataRelocations(trc, code->raw(), reader);
 }
 
 void
+Assembler::FixupNurseryObjects(JSContext *cx, JitCode *code, CompactBufferReader &reader,
+                               const ObjectVector &nurseryObjects)
+{
+    MOZ_ASSERT(!nurseryObjects.empty());
+
+    uint8_t *buffer = code->raw();
+    bool hasNurseryPointers = false;
+
+    while (reader.more()) {
+        size_t offset = reader.readUnsigned();
+        Instruction *inst = (Instruction*)(buffer + offset);
+
+        void *ptr = (void *)Assembler::ExtractLuiOriValue(inst, inst->next());
+        uintptr_t word = uintptr_t(ptr);
+
+        if (!(word & 0x1))
+            continue;
+
+        uint32_t index = word >> 1;
+        JSObject *obj = nurseryObjects[index];
+
+        Assembler::UpdateLuiOriValue(inst, inst->next(), uint32_t(obj));
+        AutoFlushICache::flush(uintptr_t(inst), 8);
+
+        // Either all objects are still in the nursery, or all objects are
+        // tenured.
+        MOZ_ASSERT_IF(hasNurseryPointers, IsInsideNursery(obj));
+
+        if (!hasNurseryPointers && IsInsideNursery(obj))
+            hasNurseryPointers = true;
+    }
+
+    if (hasNurseryPointers)
+        cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(code);
+}
+
+void
 Assembler::copyJumpRelocationTable(uint8_t *dest)
 {
     if (jumpRelocations_.length())
         memcpy(dest, jumpRelocations_.buffer(), jumpRelocations_.length());
 }
 
 void
 Assembler::copyDataRelocationTable(uint8_t *dest)
--- a/js/src/jit/mips/Assembler-mips.h
+++ b/js/src/jit/mips/Assembler-mips.h
@@ -1007,16 +1007,19 @@ class Assembler : public AssemblerShared
     void call(void *target);
 
     void as_break(uint32_t code);
 
   public:
     static void TraceJumpRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader);
     static void TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader);
 
+    static void FixupNurseryObjects(JSContext *cx, JitCode *code, CompactBufferReader &reader,
+                                    const ObjectVector &nurseryObjects);
+
     static bool SupportsFloatingPoint() {
 #if (defined(__mips_hard_float) && !defined(__mips_single_float)) || defined(JS_MIPS_SIMULATOR)
         return true;
 #else
         return false;
 #endif
     }
     static bool SupportsSimd() {
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -151,16 +151,22 @@ class MacroAssemblerNone : public Assemb
 
     size_t numCodeLabels() const { MOZ_CRASH(); }
     CodeLabel codeLabel(size_t) { MOZ_CRASH(); }
 
     void trace(JSTracer *) { MOZ_CRASH(); }
     static void TraceJumpRelocations(JSTracer *, JitCode *, CompactBufferReader &) { MOZ_CRASH(); }
     static void TraceDataRelocations(JSTracer *, JitCode *, CompactBufferReader &) { MOZ_CRASH(); }
 
+    static void FixupNurseryObjects(JSContext *, JitCode *, CompactBufferReader &,
+                                    const ObjectVector &)
+    {
+        MOZ_CRASH();
+    }
+
     static bool SupportsFloatingPoint() { return false; }
     static bool SupportsSimd() { return false; }
 
     void executableCopy(void *) { MOZ_CRASH(); }
     void copyJumpRelocationTable(uint8_t *) { MOZ_CRASH(); }
     void copyDataRelocationTable(uint8_t *) { MOZ_CRASH(); }
     void copyPreBarrierTable(uint8_t *) { MOZ_CRASH(); }
     void processCodeLabels(uint8_t *) { MOZ_CRASH(); }
--- a/js/src/jit/shared/Assembler-shared.h
+++ b/js/src/jit/shared/Assembler-shared.h
@@ -228,31 +228,56 @@ class ImmMaybeNurseryPtr
     {
         MOZ_ASSERT(!IsPoisonedPtr(ptr));
 
         // asm.js shouldn't be creating GC things
         MOZ_ASSERT(!IsCompilingAsmJS());
     }
 };
 
+// Dummy value used for nursery pointers during Ion compilation, see
+// LNurseryObject.
+class IonNurseryPtr
+{
+    const gc::Cell *ptr;
+
+  public:
+    friend class ImmGCPtr;
+
+    explicit IonNurseryPtr(const gc::Cell *ptr) : ptr(ptr)
+    {
+        MOZ_ASSERT(ptr);
+        MOZ_ASSERT(uintptr_t(ptr) & 0x1);
+    }
+};
+
 // Used for immediates which require relocation.
 class ImmGCPtr
 {
   public:
     const gc::Cell *value;
 
     explicit ImmGCPtr(const gc::Cell *ptr) : value(ptr)
     {
         MOZ_ASSERT(!IsPoisonedPtr(ptr));
         MOZ_ASSERT_IF(ptr, ptr->isTenured());
 
         // asm.js shouldn't be creating GC things
         MOZ_ASSERT(!IsCompilingAsmJS());
     }
 
+    explicit ImmGCPtr(IonNurseryPtr ptr) : value(ptr.ptr)
+    {
+        MOZ_ASSERT(!IsPoisonedPtr(value));
+        MOZ_ASSERT(value);
+
+        // asm.js shouldn't be creating GC things
+        MOZ_ASSERT(!IsCompilingAsmJS());
+    }
+
   private:
     ImmGCPtr() : value(0) {}
 
     friend class AssemblerShared;
     explicit ImmGCPtr(ImmMaybeNurseryPtr ptr) : value(ptr.value)
     {
         MOZ_ASSERT(!IsPoisonedPtr(ptr.value));
 
--- a/js/src/jit/shared/Assembler-x86-shared.cpp
+++ b/js/src/jit/shared/Assembler-x86-shared.cpp
@@ -62,29 +62,74 @@ TraceDataRelocations(JSTracer *trc, uint
             layout.asBits = *word;
             Value v = IMPL_TO_JSVAL(layout);
             gc::MarkValueUnbarriered(trc, &v, "ion-masm-value");
             *word = JSVAL_TO_IMPL(v).asBits;
             continue;
         }
 #endif
 
+        // The low bit shouldn't be set. If it is, we probably got a dummy
+        // pointer inserted by CodeGenerator::visitNurseryObject, but we
+        // shouldn't be able to trigger GC before those are patched to their
+        // real values.
+        MOZ_ASSERT(!(*reinterpret_cast<uintptr_t *>(ptr) & 0x1));
+
         // No barrier needed since these are constants.
         gc::MarkGCThingUnbarriered(trc, ptr, "ion-masm-ptr");
     }
 }
 
 
 void
 AssemblerX86Shared::TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader)
 {
     ::TraceDataRelocations(trc, code->raw(), reader);
 }
 
 void
+AssemblerX86Shared::FixupNurseryObjects(JSContext *cx, JitCode *code, CompactBufferReader &reader,
+                                        const ObjectVector &nurseryObjects)
+{
+    MOZ_ASSERT(!nurseryObjects.empty());
+
+    uint8_t *buffer = code->raw();
+    bool hasNurseryPointers = false;
+
+    while (reader.more()) {
+        size_t offset = reader.readUnsigned();
+        void **ptr = X86Encoding::GetPointerRef(buffer + offset);
+
+        uintptr_t *word = reinterpret_cast<uintptr_t *>(ptr);
+
+#ifdef JS_PUNBOX64
+        if (*word >> JSVAL_TAG_SHIFT)
+            continue; // This is a Value.
+#endif
+
+        if (!(*word & 0x1))
+            continue;
+
+        uint32_t index = *word >> 1;
+        JSObject *obj = nurseryObjects[index];
+        *word = uintptr_t(obj);
+
+        // Either all objects are still in the nursery, or all objects are
+        // tenured.
+        MOZ_ASSERT_IF(hasNurseryPointers, IsInsideNursery(obj));
+
+        if (!hasNurseryPointers && IsInsideNursery(obj))
+            hasNurseryPointers = true;
+    }
+
+    if (hasNurseryPointers)
+        cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(code);
+}
+
+void
 AssemblerX86Shared::trace(JSTracer *trc)
 {
     for (size_t i = 0; i < jumps_.length(); i++) {
         RelativePatch &rp = jumps_[i];
         if (rp.kind == Relocation::JITCODE) {
             JitCode *code = JitCode::FromExecutable((uint8_t *)rp.target);
             MarkJitCodeUnbarriered(trc, &code, "masmrel32");
             MOZ_ASSERT(code == JitCode::FromExecutable((uint8_t *)rp.target));
--- a/js/src/jit/shared/Assembler-x86-shared.h
+++ b/js/src/jit/shared/Assembler-x86-shared.h
@@ -322,16 +322,19 @@ class AssemblerX86Shared : public Assemb
     // handle NaNs properly and may therefore require a secondary condition.
     // Use NaNCondFromDoubleCondition to determine what else is needed.
     static inline Condition ConditionFromDoubleCondition(DoubleCondition cond) {
         return static_cast<Condition>(cond & ~DoubleConditionBits);
     }
 
     static void TraceDataRelocations(JSTracer *trc, JitCode *code, CompactBufferReader &reader);
 
+    static void FixupNurseryObjects(JSContext *cx, JitCode *code, CompactBufferReader &reader,
+                                    const ObjectVector &nurseryObjects);
+
     // MacroAssemblers hold onto gcthings, so they are traced by the GC.
     void trace(JSTracer *trc);
 
     bool oom() const {
         return AssemblerShared::oom() ||
                masm.oom() ||
                jumpRelocations_.oom() ||
                dataRelocations_.oom() ||
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1069,16 +1069,22 @@ TypeObjectKey::clasp()
 
 TaggedProto
 TypeObjectKey::proto()
 {
     MOZ_ASSERT(hasTenuredProto());
     return isTypeObject() ? asTypeObject()->proto() : asSingleObject()->getTaggedProto();
 }
 
+TaggedProto
+TypeObjectKey::protoMaybeInNursery()
+{
+    return isTypeObject() ? asTypeObject()->proto() : asSingleObject()->getTaggedProto();
+}
+
 bool
 JSObject::hasTenuredProto() const
 {
     return type_->hasTenuredProto();
 }
 
 bool
 TypeObjectKey::hasTenuredProto()
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1516,20 +1516,18 @@ typedef HashMap<AllocationSiteKey,
 class HeapTypeSetKey;
 
 // Type set entry for either a JSObject with singleton type or a non-singleton TypeObject.
 struct TypeObjectKey
 {
     static intptr_t keyBits(TypeObjectKey *obj) { return (intptr_t) obj; }
     static TypeObjectKey *getKey(TypeObjectKey *obj) { return obj; }
 
-    static TypeObjectKey *get(JSObject *obj) {
-        MOZ_ASSERT(obj);
-        return (TypeObjectKey *) (uintptr_t(obj) | 1);
-    }
+    static inline TypeObjectKey *get(JSObject *obj);
+
     static TypeObjectKey *get(TypeObject *obj) {
         MOZ_ASSERT(obj);
         return (TypeObjectKey *) obj;
     }
 
     bool isTypeObject() {
         return (uintptr_t(this) & 1) == 0;
     }
@@ -1540,16 +1538,17 @@ struct TypeObjectKey
     inline TypeObject *asTypeObject();
     inline JSObject *asSingleObject();
 
     inline TypeObject *asTypeObjectNoBarrier();
     inline JSObject *asSingleObjectNoBarrier();
 
     const Class *clasp();
     TaggedProto proto();
+    TaggedProto protoMaybeInNursery();
     bool hasTenuredProto();
     JSObject *singleton();
     TypeNewScript *newScript();
 
     bool unknownProperties();
     bool hasFlags(CompilerConstraintList *constraints, TypeObjectFlags flags);
     bool hasStableClassAndProto(CompilerConstraintList *constraints);
     void watchStateChangeForInlinedCall(CompilerConstraintList *constraints);
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -117,16 +117,24 @@ TypeObjectKey::asTypeObject()
 inline JSObject *
 TypeObjectKey::asSingleObject()
 {
     JSObject *res = asSingleObjectNoBarrier();
     JSObject::readBarrier(res);
     return res;
 }
 
+/* static */ inline TypeObjectKey *
+TypeObjectKey::get(JSObject *obj)
+{
+    if (obj->hasSingletonType())
+        return (TypeObjectKey *) (uintptr_t(obj) | 1);
+    return TypeObjectKey::get(obj->type());
+}
+
 /* static */ inline Type
 Type::ObjectType(JSObject *obj)
 {
     if (obj->hasSingletonType())
         return Type(uintptr_t(obj) | 1);
     return Type(uintptr_t(obj->type()));
 }