Fix buggy interactions between IC patching and invalidation (bug 793165 part 2, r=jandem).
authorDavid Anderson <danderson@mozilla.com>
Tue, 02 Oct 2012 13:43:47 -0700
changeset 109125 28739db13abea3763c0aadaa7b3440e0364b4823
parent 109124 799bd5855c27fe9a225f1d6516cbdea40891f392
child 109126 2af592592319f162b0a884089217fc3b5101e19b
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersjandem
bugs793165
milestone18.0a1
Fix buggy interactions between IC patching and invalidation (bug 793165 part 2, r=jandem).
js/src/ion/CodeGenerator.cpp
js/src/ion/Ion.cpp
js/src/ion/IonCaches.cpp
js/src/ion/IonCaches.h
js/src/ion/shared/CodeGenerator-shared.cpp
js/src/ion/shared/CodeGenerator-shared.h
js/src/ion/x86/Assembler-x86.h
js/src/methodjit/MonoIC.cpp
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -3136,16 +3136,23 @@ class OutOfLineCache : public OutOfLineC
 
 bool
 CodeGenerator::visitCache(LInstruction *ins)
 {
     OutOfLineCache *ool = new OutOfLineCache(ins);
     if (!addOutOfLineCode(ool))
         return false;
 
+#if 0
+    // NOTE: This is currently disabled. OSI and IC interaction are  protected
+    // through other means in ICs, since the nops incur significant overhead.
+    //
+    // ensureOsiSpace();
+#endif
+
     CodeOffsetJump jump = masm.jumpWithPatch(ool->repatchEntry());
     CodeOffsetLabel label = masm.labelForPatch();
     masm.bind(ool->rejoin());
 
     ool->setInlineJump(jump, label);
     return true;
 }
 
@@ -3940,16 +3947,20 @@ CodeGenerator::emitInstanceOf(LInstructi
     }
 
     // Get prototype-class by using a OutOfLine GetProperty Cache
     // It will use register 'rhsTmp' as input and register 'output' as output, see r1889
     OutOfLineCache *ool = new OutOfLineCache(ins);
     if (!addOutOfLineCode(ool))
         return false;
 
+    // If the IC code wants to patch, make sure there is enough space to that
+    // the patching does not overwrite an invalidation marker.
+    ensureOsiSpace();
+
     CodeOffsetJump jump = masm.jumpWithPatch(ool->repatchEntry());
     CodeOffsetLabel label = masm.labelForPatch();
     masm.bind(ool->rejoin());
     ool->setInlineJump(jump, label);
 
     // Move the OutOfLineCache return value and set the output on false
     masm.mov(output, rhsTmp);
     masm.mov(Imm32(0), output);
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -721,16 +721,24 @@ IonScript::toggleBarriers(bool enabled)
         else
             Assembler::ToggleToJmp(loc);
     }
 }
 
 void
 IonScript::purgeCaches(JSCompartment *c)
 {
+    // Don't reset any ICs if we're invalidated, otherwise, repointing the
+    // inline jump could overwrite an invalidation marker. These ICs can
+    // no longer run, however, the IC slow paths may be active on the stack.
+    // ICs therefore are required to check for invalidation before patching,
+    // to ensure the same invariant.
+    if (invalidated())
+        return;
+
     // This is necessary because AutoFlushCache::updateTop()
     // looks up the current flusher in the IonContext.  Without one
     // it cannot work.
     js::ion::IonContext ictx(NULL, c, NULL);
     AutoFlushCache afc("purgeCaches");
     for (size_t i = 0; i < numCaches(); i++)
         getCache(i).reset();
 }
@@ -1476,16 +1484,23 @@ InvalidateActivation(FreeOp *fop, uint8 
 
         JSScript *script = it.script();
         if (!script->hasIonScript())
             continue;
 
         if (!invalidateAll && !script->ion->invalidated())
             continue;
 
+        IonScript *ionScript = script->ion;
+
+        // Purge ICs before we mark this script as invalidated. This will
+        // prevent lastJump_ from appearing to be a bogus pointer, just
+        // in case anyone tries to read it.
+        ionScript->purgeCaches(script->compartment());
+
         // This frame needs to be invalidated. We do the following:
         //
         // 1. Increment the reference counter to keep the ionScript alive
         //    for the invalidation bailout or for the exception handler.
         // 2. Determine safepoint that corresponds to the current call.
         // 3. From safepoint, get distance to the OSI-patchable offset.
         // 4. From the IonScript, determine the distance between the
         //    call-patchable offset and the invalidation epilogue.
@@ -1497,17 +1512,16 @@ InvalidateActivation(FreeOp *fop, uint8 
         // point.
         //
         // 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 *ionScript = script->ion;
         ionScript->incref();
 
         const SafepointIndex *si = ionScript->getSafepointIndex(it.returnAddressToFp());
         IonCode *ionCode = ionScript->method();
 
         JSCompartment *compartment = script->compartment();
         if (compartment->needsBarrier()) {
             // We're about to remove edges from the JSScript to gcthings
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -194,32 +194,36 @@ struct GetNativePropertyStub
             masm.bind(&exit_);
         } else {
             masm.bind(failures);
         }
     }
 };
 
 bool
-IonCacheGetProperty::attachNative(JSContext *cx, JSObject *obj, JSObject *holder, const Shape *shape)
+IonCacheGetProperty::attachNative(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
+                                  const Shape *shape)
 {
     MacroAssembler masm;
     RepatchLabel failures;
 
     GetNativePropertyStub getprop;
     getprop.generate(cx, masm, obj, holder, shape, object(), output(), &failures);
 
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     getprop.rejoinOffset.fixup(&masm);
     getprop.exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, getprop.rejoinOffset);
     CodeLocationJump exitJump(code, getprop.exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
@@ -251,17 +255,18 @@ IsCacheableGetProp(JSObject *obj, JSObje
 {
     return (shape &&
             IsCacheableProtoChain(obj, holder) &&
             shape->hasSlot() &&
             shape->hasDefaultGetter());
 }
 
 static bool
-TryAttachNativeStub(JSContext *cx, IonCacheGetProperty &cache, HandleObject obj,
+TryAttachNativeStub(JSContext *cx, IonScript *ion,
+                    IonCacheGetProperty &cache, HandleObject obj,
                     HandlePropertyName name, bool *isCacheableNative)
 {
     JS_ASSERT(!*isCacheableNative);
 
     if (!obj->isNative())
         return true;
 
     // If the cache is idempotent, watch out for resolve hooks or non-native
@@ -290,17 +295,17 @@ TryAttachNativeStub(JSContext *cx, IonCa
         return true;
     }
 
     *isCacheableNative = true;
 
     if (cache.stubCount() < MAX_STUBS) {
         cache.incrementStubCount();
 
-        if (!cache.attachNative(cx, obj, holder, shape))
+        if (!cache.attachNative(cx, ion, obj, holder, shape))
             return false;
     }
 
     return true;
 }
 
 bool
 js::ion::GetPropertyCache(JSContext *cx, size_t cacheIndex, HandleObject obj, MutableHandleValue vp)
@@ -322,17 +327,17 @@ js::ion::GetPropertyCache(JSContext *cx,
     // If the cache is idempotent, we will redo the op in the interpreter.
     if (cache.idempotent())
         adi.disable();
 
     // For now, just stop generating new stubs once we hit the stub count
     // limit. Once we can make calls from within generated stubs, a new call
     // stub will be generated instead and the previous stubs unlinked.
     bool isCacheableNative = false;
-    if (!TryAttachNativeStub(cx, cache, obj, name, &isCacheableNative))
+    if (!TryAttachNativeStub(cx, ion, cache, obj, name, &isCacheableNative))
         return false;
 
     if (cache.idempotent() && !isCacheableNative) {
         // Invalidate the cache if the property was not found, or was found on
         // a non-native object. This ensures:
         // 1) The property read has no observable side-effects.
         // 2) There's no need to dynamically monitor the return type. This would
         //    be complicated since (due to GVN) there can be multiple pc's
@@ -390,17 +395,17 @@ IonCache::reset()
 {
     PatchJump(initialJump_, cacheLabel_);
 
     this->stubCount_ = 0;
     this->lastJump_ = initialJump_;
 }
 
 bool
-IonCacheSetProperty::attachNativeExisting(JSContext *cx, JSObject *obj, const Shape *shape)
+IonCacheSetProperty::attachNativeExisting(JSContext *cx, IonScript *ion, JSObject *obj, const Shape *shape)
 {
     MacroAssembler masm;
 
     RepatchLabel exit_;
     CodeOffsetJump exitOffset =
         masm.branchPtrWithPatch(Assembler::NotEqual,
                                 Address(object(), JSObject::offsetOfShape()),
                                 ImmGCPtr(obj->lastProperty()),
@@ -433,31 +438,35 @@ IonCacheSetProperty::attachNativeExistin
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     rejoinOffset.fixup(&masm);
     exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, rejoinOffset);
     CodeLocationJump exitJump(code, exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
     IonSpew(IonSpew_InlineCaches, "Generated native SETPROP setting case stub at %p", code->raw());
 
     return true;
 }
 
 bool
-IonCacheSetProperty::attachNativeAdding(JSContext *cx, JSObject *obj, const Shape *oldShape, const Shape *newShape,
+IonCacheSetProperty::attachNativeAdding(JSContext *cx, IonScript *ion, JSObject *obj,
+                                        const Shape *oldShape, const Shape *newShape,
                                         const Shape *propShape)
 {
     MacroAssembler masm;
 
     Label failures;
 
     /* Guard the type of the object */
     masm.branchPtr(Assembler::NotEqual, Address(object(), JSObject::offsetOfType()),
@@ -520,16 +529,19 @@ IonCacheSetProperty::attachNativeAdding(
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     rejoinOffset.fixup(&masm);
     exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, rejoinOffset);
     CodeLocationJump exitJump(code, exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
@@ -634,41 +646,42 @@ js::ion::SetPropertyCache(JSContext *cx,
     IonCacheSetProperty &cache = ion->getCache(cacheIndex).toSetProperty();
     RootedPropertyName name(cx, cache.name());
     jsid id;
     const Shape *shape = NULL;
 
     bool inlinable = IsPropertyInlineable(obj, cache);
     if (inlinable && IsPropertySetInlineable(cx, obj, name, &id, &shape)) {
         cache.incrementStubCount();
-        if (!cache.attachNativeExisting(cx, obj, shape))
+        if (!cache.attachNativeExisting(cx, ion, obj, shape))
             return false;
     }
 
     uint32_t oldSlots = obj->numDynamicSlots();
     const Shape *oldShape = obj->lastProperty();
 
     // Set/Add the property on the object, the inlined cache are setup for the next execution.
     if (!SetProperty(cx, obj, name, value, cache.strict(), isSetName))
         return false;
 
     // The property did not exists before, now we can try again to inline the
     // procedure which is adding the property.
     if (inlinable && IsPropertyAddInlineable(cx, obj, id, oldSlots, &shape)) {
         const Shape *newShape = obj->lastProperty();
         cache.incrementStubCount();
-        if (!cache.attachNativeAdding(cx, obj, oldShape, newShape, shape))
+        if (!cache.attachNativeAdding(cx, ion, obj, oldShape, newShape, shape))
             return false;
     }
 
     return true;
 }
 
 bool
-IonCacheGetElement::attachGetProp(JSContext *cx, HandleObject obj, const Value &idval, PropertyName *name)
+IonCacheGetElement::attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj,
+                                  const Value &idval, PropertyName *name)
 {
     RootedObject holder(cx);
     RootedShape shape(cx);
     if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape))
         return false;
 
     if (!IsCacheableGetProp(obj, holder, shape)) {
         IonSpew(IonSpew_InlineCaches, "GETELEM uncacheable property");
@@ -691,16 +704,19 @@ IonCacheGetElement::attachGetProp(JSCont
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     getprop.rejoinOffset.fixup(&masm);
     getprop.exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, getprop.rejoinOffset);
     CodeLocationJump exitJump(code, getprop.exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
@@ -716,17 +732,17 @@ GetDenseArrayShape(JSContext *cx, JSObje
     if (!proto)
         return NULL;
 
     return EmptyShape::getInitialShape(cx, &ArrayClass, proto,
                                        proto->getParent(), gc::FINALIZE_OBJECT0);
 }
 
 bool
-IonCacheGetElement::attachDenseArray(JSContext *cx, JSObject *obj, const Value &idval)
+IonCacheGetElement::attachDenseArray(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval)
 {
     JS_ASSERT(obj->isDenseArray());
     JS_ASSERT(idval.isInt32());
 
     Label failures;
     MacroAssembler masm;
 
     // Guard object is a dense array.
@@ -777,16 +793,19 @@ IonCacheGetElement::attachDenseArray(JSC
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     rejoinOffset.fixup(&masm);
     exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, rejoinOffset);
     CodeLocationJump exitJump(code, exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
@@ -814,24 +833,24 @@ js::ion::GetElementCache(JSContext *cx, 
         return false;
 
     if (cache.stubCount() < MAX_STUBS) {
         if (obj->isNative() && cache.monitoredResult()) {
             cache.incrementStubCount();
 
             uint32_t dummy;
             if (idval.isString() && JSID_IS_ATOM(id) && !JSID_TO_ATOM(id)->isIndex(&dummy)) {
-                if (!cache.attachGetProp(cx, obj, idval, JSID_TO_ATOM(id)->asPropertyName()))
+                if (!cache.attachGetProp(cx, ion, obj, idval, JSID_TO_ATOM(id)->asPropertyName()))
                     return false;
             }
         } else if (!cache.hasDenseArrayStub() && obj->isDenseArray() && idval.isInt32()) {
             // Generate at most one dense array stub.
             cache.incrementStubCount();
 
-            if (!cache.attachDenseArray(cx, obj, idval))
+            if (!cache.attachDenseArray(cx, ion, obj, idval))
                 return false;
         }
     }
 
     RootedScript script(cx);
     jsbytecode *pc;
     cache.getScriptedLocation(&script, &pc);
 
@@ -839,17 +858,17 @@ js::ion::GetElementCache(JSContext *cx, 
     if (!GetElementOperation(cx, JSOp(*pc), lval, idval, res))
         return false;
 
     types::TypeScript::Monitor(cx, script, pc, res);
     return true;
 }
 
 bool
-IonCacheBindName::attachGlobal(JSContext *cx, JSObject *scopeChain)
+IonCacheBindName::attachGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain)
 {
     JS_ASSERT(scopeChain->isGlobal());
 
     MacroAssembler masm;
 
     // Guard on the scope chain.
     RepatchLabel exit_;
     CodeOffsetJump exitOffset = masm.branchPtrWithPatch(Assembler::NotEqual, scopeChainReg(),
@@ -864,16 +883,19 @@ IonCacheBindName::attachGlobal(JSContext
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     rejoinOffset.fixup(&masm);
     exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, rejoinOffset);
     CodeLocationJump exitJump(code, exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
@@ -925,17 +947,17 @@ GenerateScopeChainGuards(MacroAssembler 
 
         // Load the next link.
         tobj = &tobj->asScope().enclosingScope();
         masm.extractObject(Address(outputReg, ScopeObject::offsetOfEnclosingScope()), outputReg);
     }
 }
 
 bool
-IonCacheBindName::attachNonGlobal(JSContext *cx, JSObject *scopeChain, JSObject *holder)
+IonCacheBindName::attachNonGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain, JSObject *holder)
 {
     JS_ASSERT(IsCacheableNonGlobalScope(scopeChain));
 
     MacroAssembler masm;
 
     // Guard on the shape of the scope chain.
     RepatchLabel failures;
     Label nonRepatchFailures;
@@ -972,16 +994,19 @@ IonCacheBindName::attachNonGlobal(JSCont
     Linker linker(masm);
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     rejoinOffset.fixup(&masm);
     exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, rejoinOffset);
     CodeLocationJump exitJump(code, exitOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     PatchJump(exitJump, cacheLabel());
     updateLastJump(exitJump);
 
@@ -1030,31 +1055,31 @@ js::ion::BindNameCache(JSContext *cx, si
     }
 
     // Stop generating new stubs once we hit the stub count limit, see
     // GetPropertyCache.
     if (cache.stubCount() < MAX_STUBS) {
         cache.incrementStubCount();
 
         if (scopeChain->isGlobal()) {
-            if (!cache.attachGlobal(cx, scopeChain))
+            if (!cache.attachGlobal(cx, ion, scopeChain))
                 return NULL;
         } else if (IsCacheableScopeChain(scopeChain, holder)) {
-            if (!cache.attachNonGlobal(cx, scopeChain, holder))
+            if (!cache.attachNonGlobal(cx, ion, scopeChain, holder))
                 return NULL;
         } else {
             IonSpew(IonSpew_InlineCaches, "BINDNAME uncacheable scope chain");
         }
     }
 
     return holder;
 }
 
 bool
-IonCacheName::attach(JSContext *cx, HandleObject scopeChain, HandleObject holder, Shape *shape)
+IonCacheName::attach(JSContext *cx, IonScript *ion, HandleObject scopeChain, HandleObject holder, Shape *shape)
 {
     MacroAssembler masm;
     Label failures;
 
     Register scratchReg = outputReg().valueReg().scratchReg();
 
     masm.mov(scopeChainReg(), scratchReg);
     GenerateScopeChainGuards(masm, scopeChain, holder, scratchReg, &failures);
@@ -1088,16 +1113,19 @@ IonCacheName::attach(JSContext *cx, Hand
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     rejoinOffset.fixup(&masm);
     if (failures.bound())
         exitOffset.fixup(&masm);
 
+    if (ion->invalidated())
+        return true;
+
     CodeLocationJump rejoinJump(code, rejoinOffset);
     CodeLocationJump lastJump_ = lastJump();
     PatchJump(lastJump_, CodeLocationLabel(code));
     PatchJump(rejoinJump, rejoinLabel());
     if (failures.bound()) {
         CodeLocationJump exitJump(code, exitOffset);
         PatchJump(exitJump, cacheLabel());
         updateLastJump(exitJump);
@@ -1163,17 +1191,17 @@ js::ion::GetNameCache(JSContext *cx, siz
     RootedObject holder(cx);
     RootedShape shape(cx);
     if (!LookupName(cx, name, scopeChain, &obj, &holder, &shape))
         return false;
 
     if (cache.stubCount() < MAX_STUBS &&
         IsCacheableName(cx, scopeChain, obj, holder, shape))
     {
-        if (!cache.attach(cx, scopeChain, obj, shape))
+        if (!cache.attach(cx, ion, scopeChain, obj, shape))
             return false;
         cache.incrementStubCount();
     }
 
     if (cache.isTypeOf()) {
         if (!FetchName<true>(cx, obj, holder, name, shape, vp))
             return false;
     } else {
--- a/js/src/ion/IonCaches.h
+++ b/js/src/ion/IonCaches.h
@@ -263,17 +263,18 @@ class IonCacheGetProperty : public IonCa
         u.getprop.name = name;
         u.getprop.output.data() = output;
     }
 
     Register object() const { return u.getprop.object; }
     PropertyName *name() const { return u.getprop.name; }
     TypedOrValueRegister output() const { return u.getprop.output.data(); }
 
-    bool attachNative(JSContext *cx, JSObject *obj, JSObject *holder, const Shape *shape);
+    bool attachNative(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
+                      const Shape *shape);
 };
 
 class IonCacheSetProperty : public IonCache
 {
   public:
     IonCacheSetProperty(CodeOffsetJump initialJump,
                         CodeOffsetLabel rejoinLabel,
                         CodeOffsetLabel cacheLabel,
@@ -289,19 +290,19 @@ class IonCacheSetProperty : public IonCa
         u.setprop.strict = strict;
     }
 
     Register object() const { return u.setprop.object; }
     PropertyName *name() const { return u.setprop.name; }
     ConstantOrRegister value() const { return u.setprop.value.data(); }
     bool strict() const { return u.setprop.strict; }
 
-    bool attachNativeExisting(JSContext *cx, JSObject *obj, const Shape *shape);
-    bool attachNativeAdding(JSContext *cx, JSObject *obj, const Shape *oldshape, const Shape *newshape,
-                            const Shape *propshape);
+    bool attachNativeExisting(JSContext *cx, IonScript *ion, JSObject *obj, const Shape *shape);
+    bool attachNativeAdding(JSContext *cx, IonScript *ion, JSObject *obj, const Shape *oldshape,
+                            const Shape *newshape, const Shape *propshape);
 };
 
 class IonCacheGetElement : public IonCache
 {
   public:
     IonCacheGetElement(CodeOffsetJump initialJump,
                        CodeOffsetLabel rejoinLabel,
                        CodeOffsetLabel cacheLabel,
@@ -332,18 +333,18 @@ class IonCacheGetElement : public IonCac
     bool hasDenseArrayStub() const {
         return u.getelem.hasDenseArrayStub;
     }
     void setHasDenseArrayStub() {
         JS_ASSERT(!hasDenseArrayStub());
         u.getelem.hasDenseArrayStub = true;
     }
 
-    bool attachGetProp(JSContext *cx, HandleObject obj, const Value &idval, PropertyName *name);
-    bool attachDenseArray(JSContext *cx, JSObject *obj, const Value &idval);
+    bool attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj, const Value &idval, PropertyName *name);
+    bool attachDenseArray(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval);
 };
 
 class IonCacheBindName : public IonCache
 {
   public:
     IonCacheBindName(CodeOffsetJump initialJump,
                      CodeOffsetLabel rejoinLabel,
                      CodeOffsetLabel cacheLabel,
@@ -362,18 +363,18 @@ class IonCacheBindName : public IonCache
     }
     HandlePropertyName name() const {
         return HandlePropertyName::fromMarkedLocation(&u.bindname.name);
     }
     Register outputReg() const {
         return u.bindname.output;
     }
 
-    bool attachGlobal(JSContext *cx, JSObject *scopeChain);
-    bool attachNonGlobal(JSContext *cx, JSObject *scopeChain, JSObject *holder);
+    bool attachGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain);
+    bool attachNonGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain, JSObject *holder);
 };
 
 class IonCacheName : public IonCache
 {
   public:
     IonCacheName(Kind kind,
                  CodeOffsetJump initialJump,
                  CodeOffsetLabel rejoinLabel,
@@ -396,17 +397,18 @@ class IonCacheName : public IonCache
     }
     TypedOrValueRegister outputReg() const {
         return u.name.output.data();
     }
     bool isTypeOf() const {
         return kind_ == NameTypeOf;
     }
 
-    bool attach(JSContext *cx, HandleObject scopeChain, HandleObject obj, Shape *shape);
+    bool attach(JSContext *cx, IonScript *ion, HandleObject scopeChain, HandleObject obj,
+                Shape *shape);
 };
 
 bool
 GetPropertyCache(JSContext *cx, size_t cacheIndex, HandleObject obj, MutableHandleValue vp);
 
 bool
 SetPropertyCache(JSContext *cx, size_t cacheIndex, HandleObject obj, HandleValue value,
                  bool isSetName);
--- a/js/src/ion/shared/CodeGenerator-shared.cpp
+++ b/js/src/ion/shared/CodeGenerator-shared.cpp
@@ -303,33 +303,51 @@ CodeGeneratorShared::markSafepoint(LInst
 bool
 CodeGeneratorShared::markSafepointAt(uint32 offset, LInstruction *ins)
 {
     JS_ASSERT_IF(safepointIndices_.length(),
                  offset - safepointIndices_.back().displacement() >= sizeof(uint32));
     return safepointIndices_.append(SafepointIndex(offset, ins->safepoint()));
 }
 
-bool
-CodeGeneratorShared::markOsiPoint(LOsiPoint *ins, uint32 *callPointOffset)
+void
+CodeGeneratorShared::ensureOsiSpace()
 {
-    if (!encode(ins->snapshot()))
-        return false;
-
-    // We need to ensure that two OSI point patches don't overlap with
-    // each other, so we check that there's enough space for the near
-    // call between any two OSI points.
+    // For a refresher, an invalidation point is of the form:
+    // 1: call <target>
+    // 2: ...
+    // 3: <osipoint>
+    //
+    // The four bytes *before* instruction 2 are overwritten with an offset.
+    // Callers must ensure that the instruction itself has enough bytes to
+    // support this.
+    //
+    // The bytes *at* instruction 3 are overwritten with an invalidation jump.
+    // jump. These bytes may be in a completely different IR sequence, but
+    // represent the join point of the call out of the function.
+    //
+    // At points where we want to ensure that invalidation won't corrupt an
+    // important instruction, we make sure to pad with nops.
     if (masm.currentOffset() - lastOsiPointOffset_ < Assembler::patchWrite_NearCallSize()) {
         int32 paddingSize = Assembler::patchWrite_NearCallSize();
         paddingSize -= masm.currentOffset() - lastOsiPointOffset_;
         for (int32 i = 0; i < paddingSize; ++i)
             masm.nop();
     }
     JS_ASSERT(masm.currentOffset() - lastOsiPointOffset_ >= Assembler::patchWrite_NearCallSize());
     lastOsiPointOffset_ = masm.currentOffset();
+}
+
+bool
+CodeGeneratorShared::markOsiPoint(LOsiPoint *ins, uint32 *callPointOffset)
+{
+    if (!encode(ins->snapshot()))
+        return false;
+
+    ensureOsiSpace();
 
     *callPointOffset = masm.currentOffset();
     SnapshotOffset so = ins->snapshot()->snapshotOffset();
     return osiIndices_.append(OsiIndex(*callPointOffset, so));
 }
 
 // Before doing any call to Cpp, you should ensure that volatile
 // registers are evicted by the register allocator.
--- a/js/src/ion/shared/CodeGenerator-shared.h
+++ b/js/src/ion/shared/CodeGenerator-shared.h
@@ -185,16 +185,23 @@ class CodeGeneratorShared : public LInst
     bool markSafepointAt(uint32 offset, LInstruction *ins);
 
     // Mark the OSI point |ins| as corresponding to the current
     // assembler location inside the |osiIndices_|. Return the assembler
     // location for the OSI point return location within
     // |returnPointOffset|.
     bool markOsiPoint(LOsiPoint *ins, uint32 *returnPointOffset);
 
+    // Ensure that there is enough room between the last OSI point and the
+    // current instruction, such that:
+    //  (1) Invalidation will not overwrite the current instruction, and
+    //  (2) Overwriting the current instruction will not overwrite
+    //      an invalidation marker.
+    void ensureOsiSpace();
+
     bool emitTruncateDouble(const FloatRegister &src, const Register &dest);
 
     void emitPreBarrier(Register base, const LAllocation *index, MIRType type);
     void emitPreBarrier(Address address, MIRType type);
 
     inline bool isNextBlock(LBlock *block) {
         return (current->mir()->id() + 1 == block->mir()->id());
     }
--- a/js/src/ion/x86/Assembler-x86.h
+++ b/js/src/ion/x86/Assembler-x86.h
@@ -177,16 +177,24 @@ class Operand
 #include "ion/shared/Assembler-x86-shared.h"
 
 namespace js {
 namespace ion {
 
 static inline void
 PatchJump(CodeLocationJump jump, CodeLocationLabel label)
 {
+#ifdef DEBUG
+    // Assert that we're overwriting a jump instruction, either:
+    //   0F 80+cc <imm32>, or
+    //   E9 <imm32>
+    unsigned char *x = (unsigned char *)jump.raw() - 5;
+    JS_ASSERT(((*x >= 0x80 && *x <= 0x8F) && *(x - 1) == 0x0F) ||
+              (*x == 0xE9));
+#endif
     JSC::X86Assembler::setRel32(jump.raw(), label.raw());
 }
 
 // Return operand from a JS -> JS call.
 static const ValueOperand JSReturnOperand = ValueOperand(JSReturnReg_Type, JSReturnReg_Data);
 
 class Assembler : public AssemblerX86Shared
 {
--- a/js/src/methodjit/MonoIC.cpp
+++ b/js/src/methodjit/MonoIC.cpp
@@ -705,17 +705,17 @@ class CallCompiler : public BaseCompiler
         masm.push(stackPushed);
 
         /* Call into Ion. */
         masm.loadPtr(Address(ionScript, ion::IonScript::offsetOfMethod()), t0);
 #if defined(JS_CPU_X86) || defined(JS_CPU_X64)
         masm.loadPtr(Address(t0, ion::IonCode::offsetOfCode()), t0);
         masm.call(t0);
 #elif defined(JS_CPU_ARM)
-        masm.loadPtr(Address(t0, ion::IonCode::OffsetOfCode()), JSC::ARMRegisters::ip);
+        masm.loadPtr(Address(t0, ion::IonCode::offsetOfCode()), JSC::ARMRegisters::ip);
         masm.callAddress(JS_FUNC_TO_DATA_PTR(void *, IonVeneer));
 #endif
 
         /* Pop arugments off the stack. */
         masm.pop(Registers::ReturnReg);
         masm.rshift32(Imm32(ion::FRAMESIZE_SHIFT), Registers::ReturnReg);
         masm.addPtr(Registers::ReturnReg, Registers::StackPointer);