Bug 1326067 part 2 - Convert Baseline setslot IC stub to CacheIR. r=h4writer
authorJan de Mooij <jdemooij@mozilla.com>
Mon, 16 Jan 2017 18:40:20 +0100
changeset 377220 18ea64fbf389b11d693899051eb182963dc283d2
parent 377219 6d0d4f9708620442c59939e7ea87cce08155e509
child 377221 97214e3bf73dbd6a11857f3fd2c2719148cfbc30
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs1326067
milestone53.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 1326067 part 2 - Convert Baseline setslot IC stub to CacheIR. r=h4writer
js/src/jit-test/tests/debug/optimized-out-01.js
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineICList.h
js/src/jit/BaselineInspector.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/CodeGenerator.cpp
js/src/jit/IonCacheIRCompiler.cpp
js/src/jit/IonIC.cpp
js/src/jit/MacroAssembler.h
js/src/jit/SharedIC.cpp
js/src/jit/SharedIC.h
js/src/jit/SharedICList.h
js/src/jit/arm/MacroAssembler-arm.cpp
js/src/jit/arm64/MacroAssembler-arm64.cpp
--- a/js/src/jit-test/tests/debug/optimized-out-01.js
+++ b/js/src/jit-test/tests/debug/optimized-out-01.js
@@ -1,13 +1,16 @@
 // Tests that we can reflect optimized out values.
 //
 // Unfortunately these tests are brittle. They depend on opaque JIT heuristics
 // kicking in.
 
+// Use gczeal 0 to keep CGC from invalidating Ion code and causing test failures.
+gczeal(0);
+
 load(libdir + "jitopts.js");
 
 if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))
   quit(0);
 
 withJitOptions(Opts_Ion2NoOffthreadCompilation, function () {
   var g = newGlobal();
   var dbg = new Debugger;
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -13,30 +13,37 @@
 
 #include "jit/MacroAssembler-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::Maybe;
 
+class AutoStubFrame;
+
 // BaselineCacheIRCompiler compiles CacheIR to BaselineIC native code.
 class MOZ_RAII BaselineCacheIRCompiler : public CacheIRCompiler
 {
     // Some Baseline IC stubs can be used in IonMonkey through SharedStubs.
     // Those stubs have different machine code, so we need to track whether
     // we're compiling for Baseline or Ion.
     ICStubEngine engine_;
 
     uint32_t stubDataOffset_;
     bool inStubFrame_;
     bool makesGCCalls_;
 
     MOZ_MUST_USE bool callVM(MacroAssembler& masm, const VMFunction& fun);
 
+    MOZ_MUST_USE bool callTypeUpdateIC(AutoStubFrame& stubFrame, Register obj, ValueOperand val,
+                                       Register scratch, LiveGeneralRegisterSet saveRegs);
+
+    MOZ_MUST_USE bool emitStoreSlotShared(bool isFixed);
+
   public:
     friend class AutoStubFrame;
 
     BaselineCacheIRCompiler(JSContext* cx, const CacheIRWriter& writer, ICStubEngine engine,
                             uint32_t stubDataOffset)
       : CacheIRCompiler(cx, writer, Mode::Baseline),
         engine_(engine),
         stubDataOffset_(stubDataOffset),
@@ -60,16 +67,18 @@ class MOZ_RAII BaselineCacheIRCompiler :
     }
 };
 
 #define DEFINE_SHARED_OP(op) \
     bool BaselineCacheIRCompiler::emit##op() { return CacheIRCompiler::emit##op(); }
     CACHE_IR_SHARED_OPS(DEFINE_SHARED_OP)
 #undef DEFINE_SHARED_OP
 
+enum class CallCanGC { CanGC, CanNotGC };
+
 // Instructions that have to perform a callVM require a stub frame. Use
 // AutoStubFrame before allocating any registers, then call its enter() and
 // leave() methods to enter/leave the stub frame.
 class MOZ_RAII AutoStubFrame
 {
     BaselineCacheIRCompiler& compiler;
 #ifdef DEBUG
     uint32_t framePushedAtEnterStubFrame_;
@@ -88,29 +97,30 @@ class MOZ_RAII AutoStubFrame
         tail()
     {
         // We use ICTailCallReg when entering the stub frame, so ensure it's not
         // used for something else.
         if (compiler.allocator.isAllocatable(ICTailCallReg))
             tail.emplace(compiler.allocator, compiler.masm, ICTailCallReg);
     }
 
-    void enter(MacroAssembler& masm, Register scratch) {
+    void enter(MacroAssembler& masm, Register scratch, CallCanGC canGC = CallCanGC::CanGC) {
         if (compiler.engine_ == ICStubEngine::Baseline) {
             EmitBaselineEnterStubFrame(masm, scratch);
 #ifdef DEBUG
             framePushedAtEnterStubFrame_ = masm.framePushed();
 #endif
         } else {
             EmitIonEnterStubFrame(masm, scratch);
         }
 
         MOZ_ASSERT(!compiler.inStubFrame_);
         compiler.inStubFrame_ = true;
-        compiler.makesGCCalls_ = true;
+        if (canGC == CallCanGC::CanGC)
+            compiler.makesGCCalls_ = true;
     }
     void leave(MacroAssembler& masm, bool calledIntoIon = false) {
         MOZ_ASSERT(compiler.inStubFrame_);
         compiler.inStubFrame_ = false;
 
         if (compiler.engine_ == ICStubEngine::Baseline) {
 #ifdef DEBUG
             masm.setFramePushed(framePushedAtEnterStubFrame_);
@@ -645,16 +655,122 @@ BaselineCacheIRCompiler::emitLoadEnviron
     masm.branchTestMagic(Assembler::Equal, slot, failure->label());
 
     // Load the value.
     masm.loadValue(slot, output.valueReg());
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::callTypeUpdateIC(AutoStubFrame& stubFrame, Register obj, ValueOperand val,
+                                          Register scratch, LiveGeneralRegisterSet saveRegs)
+{
+    // R0 contains the value that needs to be typechecked.
+    MOZ_ASSERT(val == R0);
+    MOZ_ASSERT(scratch == R1.scratchReg());
+
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+    static const bool CallClobbersTailReg = false;
+#else
+    static const bool CallClobbersTailReg = true;
+#endif
+
+    // Call the first type update stub.
+    if (CallClobbersTailReg)
+        masm.push(ICTailCallReg);
+    masm.push(ICStubReg);
+    masm.loadPtr(Address(ICStubReg, ICUpdatedStub::offsetOfFirstUpdateStub()),
+                 ICStubReg);
+    masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
+    masm.pop(ICStubReg);
+    if (CallClobbersTailReg)
+        masm.pop(ICTailCallReg);
+
+    // The update IC will store 0 or 1 in |scratch|, R1.scratchReg(), reflecting
+    // if the value in R0 type-checked properly or not.
+    Label done;
+    masm.branch32(Assembler::Equal, scratch, Imm32(1), &done);
+
+    stubFrame.enter(masm, scratch, CallCanGC::CanNotGC);
+
+    masm.PushRegsInMask(saveRegs);
+
+    masm.Push(val);
+    masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(obj)));
+    masm.Push(ICStubReg);
+
+    // Load previous frame pointer, push BaselineFrame*.
+    masm.loadPtr(Address(BaselineFrameReg, 0), scratch);
+    masm.pushBaselineFramePtr(scratch, scratch);
+
+    if (!callVM(masm, DoTypeUpdateFallbackInfo))
+        return false;
+
+    masm.PopRegsInMask(saveRegs);
+
+    stubFrame.leave(masm);
+
+    masm.bind(&done);
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitStoreSlotShared(bool isFixed)
+{
+    ObjOperandId objId = reader.objOperandId();
+    Address offsetAddr = stubAddress(reader.stubOffset());
+
+    // Allocate the fixed registers first. These need to be fixed for
+    // callTypeUpdateIC.
+    AutoStubFrame stubFrame(*this);
+    AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
+    ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
+
+    Register obj = allocator.useRegister(masm, objId);
+
+    LiveGeneralRegisterSet saveRegs;
+    saveRegs.add(obj);
+    saveRegs.add(val);
+    if (!callTypeUpdateIC(stubFrame, obj, val, scratch, saveRegs))
+        return false;
+
+    masm.load32(offsetAddr, scratch);
+
+    if (isFixed) {
+        BaseIndex slot(obj, scratch, TimesOne);
+        EmitPreBarrier(masm, slot, MIRType::Value);
+        masm.storeValue(val, slot);
+    } else {
+        // To avoid running out of registers on x86, use ICStubReg as scratch.
+        // We don't need it anymore.
+        Register slots = ICStubReg;
+        masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), slots);
+        BaseIndex slot(slots, scratch, TimesOne);
+        EmitPreBarrier(masm, slot, MIRType::Value);
+        masm.storeValue(val, slot);
+    }
+
+    if (cx_->gc.nursery.exists())
+        BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitStoreFixedSlot()
+{
+    return emitStoreSlotShared(true);
+}
+
+bool
+BaselineCacheIRCompiler::emitStoreDynamicSlot()
+{
+    return emitStoreSlotShared(false);
+}
+
+bool
 BaselineCacheIRCompiler::emitTypeMonitorResult()
 {
     allocator.discardStack(masm);
     EmitEnterTypeMonitorIC(masm);
     return true;
 }
 
 bool
@@ -743,16 +859,17 @@ BaselineCacheIRCompiler::init(CacheKind 
     AllocatableGeneralRegisterSet available(ICStubCompiler::availableGeneralRegs(numInputs));
 
     switch (kind) {
       case CacheKind::GetProp:
         MOZ_ASSERT(numInputs == 1);
         allocator.initInputLocation(0, R0);
         break;
       case CacheKind::GetElem:
+      case CacheKind::SetProp:
         MOZ_ASSERT(numInputs == 2);
         allocator.initInputLocation(0, R0);
         allocator.initInputLocation(1, R1);
         break;
       case CacheKind::GetName:
         MOZ_ASSERT(numInputs == 1);
         allocator.initInputLocation(0, R0.scratchReg(), JSVAL_TYPE_OBJECT);
 #if defined(JS_NUNBOX32)
@@ -781,19 +898,32 @@ jit::AttachBaselineCacheIRStub(JSContext
 
     if (writer.failed())
         return nullptr;
 
     // Just a sanity check: the caller should ensure we don't attach an
     // unlimited number of stubs.
     MOZ_ASSERT(stub->numOptimizedStubs() < MaxOptimizedCacheIRStubs);
 
-    MOZ_ASSERT(kind == CacheKind::GetProp || kind == CacheKind::GetElem ||
-               kind == CacheKind::GetName, "sizeof needs to change for SetProp!");
-    uint32_t stubDataOffset = sizeof(ICCacheIR_Monitored);
+    enum class CacheIRStubKind { Monitored, Updated };
+
+    uint32_t stubDataOffset;
+    CacheIRStubKind stubKind;
+    switch (kind) {
+      case CacheKind::GetProp:
+      case CacheKind::GetElem:
+      case CacheKind::GetName:
+        stubDataOffset = sizeof(ICCacheIR_Monitored);
+        stubKind = CacheIRStubKind::Monitored;
+        break;
+      case CacheKind::SetProp:
+        stubDataOffset = sizeof(ICCacheIR_Updated);
+        stubKind = CacheIRStubKind::Updated;
+        break;
+    }
 
     JitCompartment* jitCompartment = cx->compartment()->jitCompartment();
 
     // Check if we already have JitCode for this stub.
     CacheIRStubInfo* stubInfo;
     CacheIRStubKey::Lookup lookup(kind, engine, writer.codeStart(), writer.codeLength());
     JitCode* code = jitCompartment->getCacheIRStubCode(lookup, &stubInfo);
     if (!code) {
@@ -817,31 +947,44 @@ jit::AttachBaselineCacheIRStub(JSContext
 
         CacheIRStubKey key(stubInfo);
         if (!jitCompartment->putCacheIRStubCode(lookup, key, code))
             return nullptr;
     }
 
     MOZ_ASSERT(code);
     MOZ_ASSERT(stubInfo);
-    MOZ_ASSERT(stub->isMonitoredFallback());
     MOZ_ASSERT(stubInfo->stubDataSize() == writer.stubDataSize());
 
     // Ensure we don't attach duplicate stubs. This can happen if a stub failed
     // for some reason and the IR generator doesn't check for exactly the same
     // conditions.
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
-        if (!iter->isCacheIR_Monitored())
-            continue;
-
-        ICCacheIR_Monitored* otherStub = iter->toCacheIR_Monitored();
-        if (otherStub->stubInfo() != stubInfo)
-            continue;
-        if (!writer.stubDataEquals(otherStub->stubDataStart()))
-            continue;
+        switch (stubKind) {
+          case CacheIRStubKind::Monitored: {
+            if (!iter->isCacheIR_Monitored())
+                continue;
+            auto otherStub = iter->toCacheIR_Monitored();
+            if (otherStub->stubInfo() != stubInfo)
+                continue;
+            if (!writer.stubDataEquals(otherStub->stubDataStart()))
+                continue;
+            break;
+          }
+          case CacheIRStubKind::Updated: {
+            if (!iter->isCacheIR_Updated())
+                continue;
+            auto otherStub = iter->toCacheIR_Updated();
+            if (otherStub->stubInfo() != stubInfo)
+                continue;
+            if (!writer.stubDataEquals(otherStub->stubDataStart()))
+                continue;
+            break;
+          }
+        }
 
         // We found a stub that's exactly the same as the stub we're about to
         // attach. Just return nullptr, the caller should do nothing in this
         // case.
         return nullptr;
     }
 
     // Time to allocate and attach a new stub.
@@ -849,30 +992,52 @@ jit::AttachBaselineCacheIRStub(JSContext
     size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
 
     ICStubSpace* stubSpace = ICStubCompiler::StubSpaceForStub(stubInfo->makesGCCalls(),
                                                               outerScript, engine);
     void* newStubMem = stubSpace->alloc(bytesNeeded);
     if (!newStubMem)
         return nullptr;
 
-    ICStub* monitorStub = stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
-    auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
+    switch (stubKind) {
+      case CacheIRStubKind::Monitored: {
+        ICStub* monitorStub =
+            stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
+        auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
+        writer.copyStubData(newStub->stubDataStart());
+        stub->addNewStub(newStub);
+        return newStub;
+      }
+      case CacheIRStubKind::Updated: {
+        auto newStub = new(newStubMem) ICCacheIR_Updated(code, stubInfo);
+        if (!newStub->initUpdatingChain(cx, stubSpace)) {
+            cx->recoverFromOutOfMemory();
+            return nullptr;
+        }
+        writer.copyStubData(newStub->stubDataStart());
+        stub->addNewStub(newStub);
+        return newStub;
+      }
+    }
 
-    writer.copyStubData(newStub->stubDataStart());
-    stub->addNewStub(newStub);
-    return newStub;
+    MOZ_CRASH("Invalid kind");
 }
 
 uint8_t*
 ICCacheIR_Monitored::stubDataStart()
 {
     return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
 }
 
+uint8_t*
+ICCacheIR_Updated::stubDataStart()
+{
+    return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
+}
+
 /* static */ ICCacheIR_Monitored*
 ICCacheIR_Monitored::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
                            ICCacheIR_Monitored& other)
 {
     const CacheIRStubInfo* stubInfo = other.stubInfo();
     MOZ_ASSERT(stubInfo->makesGCCalls());
 
     size_t bytesNeeded = stubInfo->stubDataOffset() + stubInfo->stubDataSize();
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -275,24 +275,28 @@ DoTypeUpdateFallback(JSContext* cx, Base
 
     FallbackICSpew(cx, stub->getChainFallback(), "TypeUpdate(%s)",
                    ICStub::KindString(stub->kind()));
 
     RootedScript script(cx, frame->script());
     RootedObject obj(cx, &objval.toObject());
     RootedId id(cx);
 
-    switch(stub->kind()) {
+    switch (stub->kind()) {
+      case ICStub::CacheIR_Updated:
+        id = stub->toCacheIR_Updated()->updateStubId();
+        MOZ_ASSERT(id != JSID_EMPTY);
+        AddTypePropertyId(cx, obj, id, value);
+        break;
       case ICStub::SetElem_DenseOrUnboxedArray:
       case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
         id = JSID_VOID;
         AddTypePropertyId(cx, obj, id, value);
         break;
       }
-      case ICStub::SetProp_Native:
       case ICStub::SetProp_NativeAdd:
       case ICStub::SetProp_Unboxed: {
         MOZ_ASSERT(obj->isNative() || obj->is<UnboxedPlainObject>());
         jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(script);
         if (*pc == JSOP_SETALIASEDVAR || *pc == JSOP_INITALIASEDLEXICAL)
             id = NameToId(EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, pc));
         else
             id = NameToId(script->getName(pc));
@@ -733,33 +737,16 @@ LastPropertyForSetProp(JSObject* obj)
         UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
         return expando ? expando->lastProperty() : nullptr;
     }
 
     return nullptr;
 }
 
 static bool
-IsCacheableSetPropWriteSlot(JSObject* obj, Shape* oldShape, Shape* propertyShape)
-{
-    // Object shape must not have changed during the property set.
-    if (LastPropertyForSetProp(obj) != oldShape)
-        return false;
-
-    if (!propertyShape->hasSlot() ||
-        !propertyShape->hasDefaultSetter() ||
-        !propertyShape->writable())
-    {
-        return false;
-    }
-
-    return true;
-}
-
-static bool
 IsCacheableSetPropAddSlot(JSContext* cx, JSObject* obj, Shape* oldShape,
                           jsid id, Shape* propertyShape, size_t* protoChainDepth)
 {
     // The property must be the last added property of the object.
     if (LastPropertyForSetProp(obj) != propertyShape)
         return false;
 
     // Object must be extensible, oldShape must be immediate parent of current shape.
@@ -1526,17 +1513,17 @@ ICSetElem_DenseOrUnboxedArray::Compiler:
         // objects can be written to also need update stubs.
         masm.Push(R1);
         masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1);
 
         LiveGeneralRegisterSet saveRegs;
         saveRegs.add(R0);
         saveRegs.addUnchecked(obj);
         saveRegs.add(ICStubReg);
-        emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs);
+        BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
 
         masm.Pop(R1);
     }
 
     // Unbox key.
     Register key = masm.extractInt32(R1, ExtractTemp1);
 
     if (unboxedType_ == JSVAL_TYPE_MAGIC) {
@@ -1734,17 +1721,17 @@ ICSetElemDenseOrUnboxedArrayAddCompiler:
         // objects can be written to also need update stubs.
         masm.Push(R1);
         masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1);
 
         LiveGeneralRegisterSet saveRegs;
         saveRegs.add(R0);
         saveRegs.addUnchecked(obj);
         saveRegs.add(ICStubReg);
-        emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs);
+        BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
 
         masm.Pop(R1);
     }
 
     // Reset register set.
     regs = availableGeneralRegs(2);
     scratchReg = regs.takeAny();
 
@@ -2631,51 +2618,16 @@ TryAttachSetValuePropStub(JSContext* cx,
         if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
             return false;
 
         stub->addNewStub(newStub);
         *attached = true;
         return true;
     }
 
-    if (IsCacheableSetPropWriteSlot(obj, oldShape, shape)) {
-        // For some property writes, such as the initial overwrite of global
-        // properties, TI will not mark the property as having been
-        // overwritten. Don't attach a stub in this case, so that we don't
-        // execute another write to the property without TI seeing that write.
-        EnsureTrackPropertyTypes(cx, obj, id);
-        if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
-            *attached = true;
-            return true;
-        }
-
-        bool isFixedSlot;
-        uint32_t offset;
-        GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
-
-        JitSpew(JitSpew_BaselineIC, "  Generating SetProp(NativeObject.PROP) stub");
-        MOZ_ASSERT(LastPropertyForSetProp(obj) == oldShape,
-                   "Should this really be a SetPropWriteSlot?");
-        ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset);
-        ICSetProp_Native* newStub = compiler.getStub(compiler.getStubSpace(script));
-        if (!newStub)
-            return false;
-        if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
-            return false;
-
-        if (IsPreliminaryObject(obj))
-            newStub->notePreliminaryObject();
-        else
-            StripPreliminaryObjectStubs(cx, stub);
-
-        stub->addNewStub(newStub);
-        *attached = true;
-        return true;
-    }
-
     return true;
 }
 
 // Try to update existing SetProp setter call stubs for the given holder in
 // place with a new shape and setter.
 static bool
 UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub,
                                ICStub::Kind kind,
@@ -2930,16 +2882,40 @@ DoSetPropFallback(JSContext* cx, Baselin
     if (stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
         lhs.isObject() &&
         !TryAttachSetAccessorPropStub(cx, script, pc, stub, obj, oldGuard, name, id,
                                       rhs, &attached, &isTemporarilyUnoptimizable))
     {
         return false;
     }
 
+    if (!attached &&
+        stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
+        !JitOptions.disableCacheIR)
+    {
+        RootedValue idVal(cx, StringValue(name));
+        SetPropIRGenerator gen(cx, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
+                               lhs, idVal, rhs);
+        if (gen.tryAttachStub()) {
+            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        ICStubEngine::Baseline, frame->script(), stub);
+            if (newStub) {
+                JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
+                attached = true;
+
+                newStub->toCacheIR_Updated()->updateStubId() = gen.updateStubId();
+
+                if (gen.shouldNotePreliminaryObjectStub())
+                    newStub->toCacheIR_Updated()->notePreliminaryObject();
+                else if (gen.shouldUnlinkPreliminaryObjectStubs())
+                    StripPreliminaryObjectStubs(cx, stub);
+            }
+        }
+    }
+
     if (op == JSOP_INITPROP ||
         op == JSOP_INITLOCKEDPROP ||
         op == JSOP_INITHIDDENPROP)
     {
         if (!InitPropertyOperation(cx, op, obj, id, rhs))
             return false;
     } else if (op == JSOP_SETNAME ||
                op == JSOP_STRICTSETNAME ||
@@ -3102,87 +3078,16 @@ GuardGroupAndShapeMaybeUnboxedExpando(Ma
         masm.jump(failure);
         masm.bind(&done);
         masm.pop(object);
     } else {
         masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
     }
 }
 
-bool
-ICSetProp_Native::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    MOZ_ASSERT(engine_ == Engine::Baseline);
-
-    Label failure;
-
-    // Guard input is an object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-
-    AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
-    Register scratch = regs.takeAny();
-
-    GuardGroupAndShapeMaybeUnboxedExpando(masm, obj_, objReg, scratch,
-                                          ICSetProp_Native::offsetOfGroup(),
-                                          ICSetProp_Native::offsetOfShape(),
-                                          &failure);
-
-    // Stow both R0 and R1 (object and value).
-    EmitStowICValues(masm, 2);
-
-    // Type update stub expects the value to check in R0.
-    masm.moveValue(R1, R0);
-
-    // Call the type-update stub.
-    if (!callTypeUpdateIC(masm, sizeof(Value)))
-        return false;
-
-    // Unstow R0 and R1 (object and key)
-    EmitUnstowICValues(masm, 2);
-
-    regs.add(R0);
-    regs.takeUnchecked(objReg);
-
-    Register holderReg;
-    if (obj_->is<UnboxedPlainObject>()) {
-        // We are loading off the expando object, so use that for the holder.
-        holderReg = regs.takeAny();
-        masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
-        if (!isFixedSlot_)
-            masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
-    } else if (isFixedSlot_) {
-        holderReg = objReg;
-    } else {
-        holderReg = regs.takeAny();
-        masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg);
-    }
-
-    // Perform the store.
-    masm.load32(Address(ICStubReg, ICSetProp_Native::offsetOfOffset()), scratch);
-    EmitPreBarrier(masm, BaseIndex(holderReg, scratch, TimesOne), MIRType::Value);
-    masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne));
-    if (holderReg != objReg)
-        regs.add(holderReg);
-    if (cx->runtime()->gc.nursery.exists()) {
-        Register scr = regs.takeAny();
-        LiveGeneralRegisterSet saveRegs;
-        saveRegs.add(R1);
-        emitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs);
-        regs.add(scr);
-    }
-
-    EmitReturnFromIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
 ICUpdatedStub*
 ICSetPropNativeAddCompiler::getStub(ICStubSpace* space)
 {
     Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
     if (!shapes.append(oldShape_))
         return nullptr;
 
     if (!GetProtoShapes(obj_, protoChainDepth_, &shapes))
@@ -3318,17 +3223,17 @@ ICSetPropNativeAddCompiler::generateStub
 
     if (holderReg != objReg)
         regs.add(holderReg);
 
     if (cx->runtime()->gc.nursery.exists()) {
         Register scr = regs.takeAny();
         LiveGeneralRegisterSet saveRegs;
         saveRegs.add(R1);
-        emitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs);
+        BaselineEmitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs, cx);
     }
 
     EmitReturnFromIC(masm);
 
     // Failure case - jump to next stub
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
 
@@ -3375,17 +3280,17 @@ ICSetProp_Unboxed::Compiler::generateStu
 
         // Trigger post barriers here on the values being written. Fields which
         // objects can be written to also need update stubs.
         LiveGeneralRegisterSet saveRegs;
         saveRegs.add(R0);
         saveRegs.add(R1);
         saveRegs.addUnchecked(object);
         saveRegs.add(ICStubReg);
-        emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
+        BaselineEmitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs, cx);
     }
 
     // Compute the address being written to.
     masm.load32(Address(ICStubReg, ICSetProp_Unboxed::offsetOfFieldOffset()), scratch);
     BaseIndex address(object, scratch, TimesOne);
 
     EmitUnboxedPreBarrierForBaseline(masm, address, fieldType_);
     masm.storeUnboxedProperty(address, fieldType_,
@@ -3442,17 +3347,17 @@ ICSetProp_TypedObject::Compiler::generat
 
         // Trigger post barriers here on the values being written. Descriptors
         // which can write objects also need update stubs.
         LiveGeneralRegisterSet saveRegs;
         saveRegs.add(R0);
         saveRegs.add(R1);
         saveRegs.addUnchecked(object);
         saveRegs.add(ICStubReg);
-        emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
+        BaselineEmitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs, cx);
     }
 
     // Save the rhs on the stack so we can get a second scratch register.
     Label failurePopRHS;
     masm.pushValue(R1);
     regs = availableGeneralRegs(1);
     regs.takeUnchecked(object);
     regs.take(scratch);
@@ -6677,38 +6582,16 @@ ICGetIntrinsic_Constant::~ICGetIntrinsic
 ICInstanceOf_Function::ICInstanceOf_Function(JitCode* stubCode, Shape* shape,
                                              JSObject* prototypeObj, uint32_t slot)
   : ICStub(InstanceOf_Function, stubCode),
     shape_(shape),
     prototypeObj_(prototypeObj),
     slot_(slot)
 { }
 
-ICSetProp_Native::ICSetProp_Native(JitCode* stubCode, ObjectGroup* group, Shape* shape,
-                                   uint32_t offset)
-  : ICUpdatedStub(SetProp_Native, stubCode),
-    group_(group),
-    shape_(shape),
-    offset_(offset)
-{ }
-
-ICSetProp_Native*
-ICSetProp_Native::Compiler::getStub(ICStubSpace* space)
-{
-    RootedObjectGroup group(cx, JSObject::getGroup(cx, obj_));
-    if (!group)
-        return nullptr;
-
-    RootedShape shape(cx, LastPropertyForSetProp(obj_));
-    ICSetProp_Native* stub = newStub<ICSetProp_Native>(space, getStubCode(), group, shape, offset_);
-    if (!stub || !stub->initUpdatingChain(cx, space))
-        return nullptr;
-    return stub;
-}
-
 ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode* stubCode, ObjectGroup* group,
                                          size_t protoChainDepth,
                                          Shape* newShape,
                                          ObjectGroup* newGroup,
                                          uint32_t offset)
   : ICUpdatedStub(SetProp_NativeAdd, stubCode),
     group_(group),
     newShape_(newShape),
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1106,79 +1106,16 @@ class ICSetProp_Fallback : public ICFall
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICSetProp_Fallback>(space, getStubCode());
         }
     };
 };
 
-// Optimized SETPROP/SETGNAME/SETNAME stub.
-class ICSetProp_Native : public ICUpdatedStub
-{
-    friend class ICStubSpace;
-
-  protected: // Protected to silence Clang warning.
-    GCPtrObjectGroup group_;
-    GCPtrShape shape_;
-    uint32_t offset_;
-
-    ICSetProp_Native(JitCode* stubCode, ObjectGroup* group, Shape* shape, uint32_t offset);
-
-  public:
-    GCPtrObjectGroup& group() {
-        return group_;
-    }
-    GCPtrShape& shape() {
-        return shape_;
-    }
-    void notePreliminaryObject() {
-        extra_ = 1;
-    }
-    bool hasPreliminaryObject() const {
-        return extra_;
-    }
-    static size_t offsetOfGroup() {
-        return offsetof(ICSetProp_Native, group_);
-    }
-    static size_t offsetOfShape() {
-        return offsetof(ICSetProp_Native, shape_);
-    }
-    static size_t offsetOfOffset() {
-        return offsetof(ICSetProp_Native, offset_);
-    }
-
-    class Compiler : public ICStubCompiler {
-        RootedObject obj_;
-        bool isFixedSlot_;
-        uint32_t offset_;
-
-      protected:
-        virtual int32_t getKey() const {
-            return static_cast<int32_t>(engine_) |
-                  (static_cast<int32_t>(kind) << 1) |
-                  (static_cast<int32_t>(isFixedSlot_) << 17) |
-                  (static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18);
-        }
-
-        MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        Compiler(JSContext* cx, HandleObject obj, bool isFixedSlot, uint32_t offset)
-          : ICStubCompiler(cx, ICStub::SetProp_Native, Engine::Baseline),
-            obj_(cx, obj),
-            isFixedSlot_(isFixedSlot),
-            offset_(offset)
-        {}
-
-        ICSetProp_Native* getStub(ICStubSpace* space);
-    };
-};
-
-
 template <size_t ProtoChainDepth> class ICSetProp_NativeAddImpl;
 
 class ICSetProp_NativeAdd : public ICUpdatedStub
 {
   public:
     static const size_t MAX_PROTO_CHAIN_DEPTH = 4;
 
   protected: // Protected to silence Clang warning.
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -64,17 +64,16 @@ namespace jit {
     _(GetName_Fallback)                          \
                                                  \
     _(BindName_Fallback)                         \
                                                  \
     _(GetIntrinsic_Fallback)                     \
     _(GetIntrinsic_Constant)                     \
                                                  \
     _(SetProp_Fallback)                          \
-    _(SetProp_Native)                            \
     _(SetProp_NativeAdd)                         \
     _(SetProp_Unboxed)                           \
     _(SetProp_TypedObject)                       \
     _(SetProp_CallScripted)                      \
     _(SetProp_CallNative)                        \
                                                  \
     _(TableSwitch)                               \
                                                  \
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -166,16 +166,59 @@ GetCacheIRReceiverForUnboxedProperty(ICC
 
     if (!reader.matchOp(CacheOp::GuardGroup, objId))
         return false;
     receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
 
     return reader.matchOp(CacheOp::LoadUnboxedPropertyResult, objId);
 }
 
+static bool
+GetCacheIRReceiverForNativeSetSlot(ICCacheIR_Updated* stub, ReceiverGuard* receiver)
+{
+    // We match either:
+    //
+    //   GuardIsObject 0
+    //   GuardGroup 0
+    //   GuardShape 0
+    //   StoreFixedSlot 0 or StoreDynamicSlot 0
+    //
+    // or
+    //
+    //   GuardIsObject 0
+    //   GuardGroup 0
+    //   1: GuardAndLoadUnboxedExpando 0
+    //   GuardShape 1
+    //   StoreFixedSlot 1 or StoreDynamicSlot 1
+
+    *receiver = ReceiverGuard();
+    CacheIRReader reader(stub->stubInfo());
+
+    ObjOperandId objId = ObjOperandId(0);
+    if (!reader.matchOp(CacheOp::GuardIsObject, objId))
+        return false;
+
+    if (!reader.matchOp(CacheOp::GuardGroup, objId))
+        return false;
+    ObjectGroup* group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
+
+    if (reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId))
+        objId = reader.objOperandId();
+
+    if (!reader.matchOp(CacheOp::GuardShape, objId))
+        return false;
+    Shape* shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
+
+    if (!reader.matchOpEither(CacheOp::StoreFixedSlot, CacheOp::StoreDynamicSlot))
+        return false;
+
+    *receiver = ReceiverGuard(group, shape);
+    return true;
+}
+
 bool
 BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
                                           ObjectGroupVector& convertUnboxedGroups)
 {
     // Return a list of the receivers seen by the baseline IC for the current
     // op. Empty lists indicate no receivers are known, or there was an
     // uncacheable access. convertUnboxedGroups is used for unboxed object
     // groups which have been seen, but have had instances converted to native
@@ -194,19 +237,21 @@ BaselineInspector::maybeInfoForPropertyO
         ReceiverGuard receiver;
         if (stub->isCacheIR_Monitored()) {
             if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver) &&
                 !GetCacheIRReceiverForUnboxedProperty(stub->toCacheIR_Monitored(), &receiver))
             {
                 receivers.clear();
                 return true;
             }
-        } else if (stub->isSetProp_Native()) {
-            receiver = ReceiverGuard(stub->toSetProp_Native()->group(),
-                                     stub->toSetProp_Native()->shape());
+        } else if (stub->isCacheIR_Updated()) {
+            if (!GetCacheIRReceiverForNativeSetSlot(stub->toCacheIR_Updated(), &receiver)) {
+                receivers.clear();
+                return true;
+            }
         } else if (stub->isSetProp_Unboxed()) {
             receiver = ReceiverGuard(stub->toSetProp_Unboxed()->group(), nullptr);
         } else {
             receivers.clear();
             return true;
         }
 
         if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1518,8 +1518,146 @@ IRGenerator::maybeGuardInt32Index(const 
         StringOperandId strId = writer.guardIsString(indexId);
         *int32Index = uint32_t(indexSigned);
         *int32IndexId = writer.guardAndGetIndexFromString(strId);
         return true;
     }
 
     return false;
 }
+
+SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
+                                       bool* isTemporarilyUnoptimizable, HandleValue lhsVal,
+                                       HandleValue idVal, HandleValue rhsVal)
+  : IRGenerator(cx, pc, cacheKind),
+    lhsVal_(lhsVal),
+    idVal_(idVal),
+    rhsVal_(rhsVal),
+    isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
+    preliminaryObjectAction_(PreliminaryObjectAction::None),
+    updateStubId_(cx, JSID_EMPTY),
+    needUpdateStub_(false)
+{}
+
+bool
+SetPropIRGenerator::tryAttachStub()
+{
+    AutoAssertNoPendingException aanpe(cx_);
+
+    ValOperandId lhsValId(writer.setInputOperandId(0));
+    ValOperandId rhsValId(writer.setInputOperandId(1));
+
+    RootedId id(cx_);
+    bool nameOrSymbol;
+    if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
+        cx_->clearPendingException();
+        return false;
+    }
+
+    if (lhsVal_.isObject()) {
+        RootedObject obj(cx_, &lhsVal_.toObject());
+        if (obj->watched())
+            return false;
+
+        ObjOperandId objId = writer.guardIsObject(lhsValId);
+        if (nameOrSymbol) {
+            if (tryAttachNativeSetSlot(obj, objId, id, rhsValId))
+                return true;
+            if (tryAttachUnboxedExpandoSetSlot(obj, objId, id, rhsValId))
+                return true;
+        }
+        return false;
+    }
+
+    return false;
+}
+
+static void
+EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId, NativeObject* nobj, Shape* shape,
+                       ValOperandId rhsId)
+{
+    if (nobj->isFixedSlot(shape->slot())) {
+        size_t offset = NativeObject::getFixedSlotOffset(shape->slot());
+        writer.storeFixedSlot(objId, offset, rhsId);
+    } else {
+        size_t offset = nobj->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+        writer.storeDynamicSlot(objId, offset, rhsId);
+    }
+    writer.returnFromIC();
+}
+
+static Shape*
+LookupShapeForSetSlot(NativeObject* obj, jsid id)
+{
+    Shape* shape = obj->lookupPure(id);
+    if (shape && shape->hasSlot() && shape->hasDefaultSetter() && shape->writable())
+        return shape;
+    return nullptr;
+}
+
+bool
+SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
+                                           ValOperandId rhsId)
+{
+    if (!obj->isNative())
+        return false;
+
+    RootedShape propShape(cx_, LookupShapeForSetSlot(&obj->as<NativeObject>(), id));
+    if (!propShape)
+        return false;
+
+    RootedObjectGroup group(cx_, JSObject::getGroup(cx_, obj));
+    if (!group) {
+        cx_->recoverFromOutOfMemory();
+        return false;
+    }
+
+    // For some property writes, such as the initial overwrite of global
+    // properties, TI will not mark the property as having been
+    // overwritten. Don't attach a stub in this case, so that we don't
+    // execute another write to the property without TI seeing that write.
+    EnsureTrackPropertyTypes(cx_, obj, id);
+    if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
+        *isTemporarilyUnoptimizable_ = true;
+        return false;
+    }
+
+    // For Baseline, we have to guard on both the shape and group, because the
+    // type update IC applies to a single group. When we port the Ion IC, we can
+    // do a bit better and avoid the group guard if we don't have to guard on
+    // the property types.
+    NativeObject* nobj = &obj->as<NativeObject>();
+    writer.guardGroup(objId, nobj->group());
+    writer.guardShape(objId, nobj->lastProperty());
+
+    if (IsPreliminaryObject(obj))
+        preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
+    else
+        preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
+
+    setUpdateStubInfo(id);
+    EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId);
+    return true;
+}
+
+bool
+SetPropIRGenerator::tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId,
+                                                   HandleId id, ValOperandId rhsId)
+{
+    if (!obj->is<UnboxedPlainObject>())
+        return false;
+
+    UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+    if (!expando)
+        return false;
+
+    Shape* propShape = LookupShapeForSetSlot(expando, id);
+    if (!propShape)
+        return false;
+
+    writer.guardGroup(objId, obj->group());
+    ObjOperandId expandoId = writer.guardAndLoadUnboxedExpando(objId);
+    writer.guardShape(expandoId, expando->lastProperty());
+
+    setUpdateStubInfo(id);
+    EmitStoreSlotAndReturn(writer, expandoId, expando, propShape, rhsId);
+    return true;
+}
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -129,16 +129,17 @@ class TypedOperandId : public OperandId
     JSValueType type() const { return type_; }
 };
 
 enum class CacheKind : uint8_t
 {
     GetProp,
     GetElem,
     GetName,
+    SetProp,
 };
 
 #define CACHE_IR_OPS(_)                   \
     _(GuardIsObject)                      \
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
@@ -163,16 +164,19 @@ enum class CacheKind : uint8_t
     _(LoadEnclosingEnvironment)           \
                                           \
     /* See CacheIR.cpp 'DOM proxies' comment. */ \
     _(LoadDOMExpandoValue)                \
     _(LoadDOMExpandoValueGuardGeneration) \
     _(LoadDOMExpandoValueIgnoreGeneration)\
     _(GuardDOMExpandoMissingOrGuardShape) \
                                           \
+    _(StoreFixedSlot)                     \
+    _(StoreDynamicSlot)                   \
+                                          \
     /* The *Result ops load a value into the cache's result register. */ \
     _(LoadFixedSlotResult)                \
     _(LoadDynamicSlotResult)              \
     _(LoadUnboxedPropertyResult)          \
     _(LoadTypedObjectResult)              \
     _(LoadDenseElementResult)             \
     _(LoadDenseElementHoleResult)         \
     _(LoadUnboxedArrayElementResult)      \
@@ -542,16 +546,27 @@ class MOZ_RAII CacheIRWriter : public JS
     }
     ValOperandId loadDOMExpandoValueIgnoreGeneration(ObjOperandId obj) {
         ValOperandId res(nextOperandId_++);
         writeOpWithOperandId(CacheOp::LoadDOMExpandoValueIgnoreGeneration, obj);
         writeOperandId(res);
         return res;
     }
 
+    void storeFixedSlot(ObjOperandId obj, size_t offset, ValOperandId rhs) {
+        writeOpWithOperandId(CacheOp::StoreFixedSlot, obj);
+        addStubField(offset, StubField::Type::RawWord);
+        writeOperandId(rhs);
+    }
+    void storeDynamicSlot(ObjOperandId obj, size_t offset, ValOperandId rhs) {
+        writeOpWithOperandId(CacheOp::StoreDynamicSlot, obj);
+        addStubField(offset, StubField::Type::RawWord);
+        writeOperandId(rhs);
+    }
+
     void loadUndefinedResult() {
         writeOp(CacheOp::LoadUndefinedResult);
     }
     void loadFixedSlotResult(ObjOperandId obj, size_t offset) {
         writeOpWithOperandId(CacheOp::LoadFixedSlotResult, obj);
         addStubField(offset, StubField::Type::RawWord);
     }
     void loadDynamicSlotResult(ObjOperandId obj, size_t offset) {
@@ -819,12 +834,57 @@ class MOZ_RAII GetNameIRGenerator : publ
 
   public:
     GetNameIRGenerator(JSContext* cx, jsbytecode* pc, HandleScript script,
                        HandleObject env, HandlePropertyName name);
 
     bool tryAttachStub();
 };
 
+// SetPropIRGenerator generates CacheIR for a SetProp IC.
+class MOZ_RAII SetPropIRGenerator : public IRGenerator
+{
+    HandleValue lhsVal_;
+    HandleValue idVal_;
+    HandleValue rhsVal_;
+    bool* isTemporarilyUnoptimizable_;
+
+    enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
+    PreliminaryObjectAction preliminaryObjectAction_;
+
+    // If Baseline needs an update stub, this contains information to create it.
+    RootedId updateStubId_;
+    bool needUpdateStub_;
+
+    void setUpdateStubInfo(jsid id) {
+        MOZ_ASSERT(!needUpdateStub_);
+        needUpdateStub_ = true;
+        updateStubId_ = id;
+    }
+
+    bool tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
+                                 ValOperandId rhsId);
+    bool tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
+                                        ValOperandId rhsId);
+
+  public:
+    SetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
+                       bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal,
+                       HandleValue rhsVal);
+
+    bool tryAttachStub();
+
+    bool shouldUnlinkPreliminaryObjectStubs() const {
+        return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
+    }
+    bool shouldNotePreliminaryObjectStub() const {
+        return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary;
+    }
+    jsid updateStubId() const {
+        MOZ_ASSERT(needUpdateStub_);
+        return updateStubId_;
+    }
+};
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_CacheIR_h */
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -56,16 +56,52 @@ CacheRegisterAllocator::useValueRegister
       }
       case OperandLocation::Uninitialized:
         break;
     }
 
     MOZ_CRASH();
 }
 
+ValueOperand
+CacheRegisterAllocator::useFixedValueRegister(MacroAssembler& masm, ValOperandId valId,
+                                              ValueOperand reg)
+{
+    allocateFixedValueRegister(masm, reg);
+
+    OperandLocation& loc = operandLocations_[valId.id()];
+    switch (loc.kind()) {
+      case OperandLocation::ValueReg:
+        masm.moveValue(loc.valueReg(), reg);
+        MOZ_ASSERT(!currentOpRegs_.aliases(loc.valueReg()), "Register shouldn't be in use");
+        availableRegs_.add(loc.valueReg());
+        break;
+      case OperandLocation::ValueStack:
+        popValue(masm, &loc, reg);
+        break;
+      case OperandLocation::Constant:
+        masm.moveValue(loc.constant(), reg);
+        break;
+      case OperandLocation::PayloadReg:
+        masm.tagValue(loc.payloadType(), loc.payloadReg(), reg);
+        MOZ_ASSERT(!currentOpRegs_.has(loc.payloadReg()), "Register shouldn't be in use");
+        availableRegs_.add(loc.payloadReg());
+        break;
+      case OperandLocation::PayloadStack:
+        popPayload(masm, &loc, reg.scratchReg());
+        masm.tagValue(loc.payloadType(), reg.scratchReg(), reg);
+        break;
+      case OperandLocation::Uninitialized:
+        MOZ_CRASH();
+    }
+
+    loc.setValueReg(reg);
+    return reg;
+}
+
 Register
 CacheRegisterAllocator::useRegister(MacroAssembler& masm, TypedOperandId typedId)
 {
     OperandLocation& loc = operandLocations_[typedId.id()];
     switch (loc.kind()) {
       case OperandLocation::PayloadReg:
         currentOpRegs_.add(loc.payloadReg());
         return loc.payloadReg();
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -334,16 +334,17 @@ class MOZ_RAII CacheRegisterAllocator
 
     // Removes spilled values from the native stack. This should only be
     // called after all registers have been allocated.
     void discardStack(MacroAssembler& masm);
 
     // Returns the register for the given operand. If the operand is currently
     // not in a register, it will load it into one.
     ValueOperand useValueRegister(MacroAssembler& masm, ValOperandId val);
+    ValueOperand useFixedValueRegister(MacroAssembler& masm, ValOperandId valId, ValueOperand reg);
     Register useRegister(MacroAssembler& masm, TypedOperandId typedId);
 
     // Allocates an output register for the given operand.
     Register defineRegister(MacroAssembler& masm, TypedOperandId typedId);
     ValueOperand defineValueRegister(MacroAssembler& masm, ValOperandId val);
 
     // Returns |val|'s JSValueType or JSVAL_TYPE_UNKNOWN.
     JSValueType knownType(ValOperandId val) const;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -252,16 +252,17 @@ CodeGenerator::visitOutOfLineICFallback(
 
         StoreValueTo(getPropIC->output()).generate(this);
         restoreLiveIgnore(lir, StoreValueTo(getPropIC->output()).clobbered());
 
         masm.jump(ool->rejoin());
         return;
       }
       case CacheKind::GetName:
+      case CacheKind::SetProp:
         MOZ_CRASH("Baseline-specific for now");
     }
     MOZ_CRASH();
 }
 
 StringObject*
 MNewStringObject::templateObj() const
 {
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -816,16 +816,28 @@ IonCacheIRCompiler::emitLoadEnvironmentF
 
 bool
 IonCacheIRCompiler::emitLoadEnvironmentDynamicSlotResult()
 {
     MOZ_CRASH("Baseline-specific op");
 }
 
 bool
+IonCacheIRCompiler::emitStoreFixedSlot()
+{
+    MOZ_CRASH("Baseline-specific op");
+}
+
+bool
+IonCacheIRCompiler::emitStoreDynamicSlot()
+{
+    MOZ_CRASH("Baseline-specific op");
+}
+
+bool
 IonCacheIRCompiler::emitLoadTypedObjectResult()
 {
     AutoOutputRegister output(*this);
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch1(allocator, masm);
     AutoScratchRegister scratch2(allocator, masm);
 
     TypedThingLayout layout = reader.typedThingLayout();
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -37,16 +37,17 @@ IonIC::scratchRegisterForEntryJump()
       case CacheKind::GetElem: {
         Register temp = asGetPropertyIC()->maybeTemp();
         if (temp != InvalidReg)
             return temp;
         TypedOrValueRegister output = asGetPropertyIC()->output();
         return output.hasValue() ? output.valueReg().scratchReg() : output.typedReg().gpr();
       }
       case CacheKind::GetName:
+      case CacheKind::SetProp:
         MOZ_CRASH("Baseline-specific for now");
     }
 
     MOZ_CRASH("Invalid kind");
 }
 
 void
 IonIC::reset(Zone* zone)
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -483,17 +483,17 @@ class MacroAssembler : public MacroAssem
 #endif // DEBUG
 
   public:
     // ===============================================================
     // Simple call functions.
 
     CodeOffset call(Register reg) PER_SHARED_ARCH;
     CodeOffset call(Label* label) PER_SHARED_ARCH;
-    void call(const Address& addr) DEFINED_ON(x86_shared);
+    void call(const Address& addr) DEFINED_ON(x86_shared, arm, arm64);
     void call(ImmWord imm) PER_SHARED_ARCH;
     // Call a target native function, which is neither traceable nor movable.
     void call(ImmPtr imm) PER_SHARED_ARCH;
     void call(wasm::SymbolicAddress imm) PER_SHARED_ARCH;
     // Call a target JitCode, which must be traceable, and may be movable.
     void call(JitCode* c) PER_SHARED_ARCH;
 
     inline void call(const wasm::CallSiteDesc& desc, const Register reg);
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -188,19 +188,24 @@ ICStub::NonCacheIRStubMakesGCCalls(Kind 
       default:
         return false;
     }
 }
 
 bool
 ICStub::makesGCCalls() const
 {
-    if (isCacheIR_Monitored())
+    switch (kind()) {
+      case CacheIR_Monitored:
         return toCacheIR_Monitored()->stubInfo()->makesGCCalls();
-    return NonCacheIRStubMakesGCCalls(kind());
+      case CacheIR_Updated:
+        return toCacheIR_Updated()->stubInfo()->makesGCCalls();
+      default:
+        return NonCacheIRStubMakesGCCalls(kind());
+    }
 }
 
 void
 ICStub::traceCode(JSTracer* trc, const char* name)
 {
     JitCode* stubJitCode = jitCode();
     TraceManuallyBarrieredEdge(trc, &stubJitCode, name);
 }
@@ -346,22 +351,16 @@ ICStub::trace(JSTracer* trc)
         TraceEdge(trc, &inStub->shape(), "baseline-in-dense-shape");
         break;
       }
       case ICStub::GetIntrinsic_Constant: {
         ICGetIntrinsic_Constant* constantStub = toGetIntrinsic_Constant();
         TraceEdge(trc, &constantStub->value(), "baseline-getintrinsic-constant-value");
         break;
       }
-      case ICStub::SetProp_Native: {
-        ICSetProp_Native* propStub = toSetProp_Native();
-        TraceEdge(trc, &propStub->shape(), "baseline-setpropnative-stub-shape");
-        TraceEdge(trc, &propStub->group(), "baseline-setpropnative-stub-group");
-        break;
-      }
       case ICStub::SetProp_NativeAdd: {
         ICSetProp_NativeAdd* propStub = toSetProp_NativeAdd();
         TraceEdge(trc, &propStub->group(), "baseline-setpropnativeadd-stub-group");
         TraceEdge(trc, &propStub->newShape(), "baseline-setpropnativeadd-stub-newshape");
         TraceNullableEdge(trc, &propStub->newGroup(), "baseline-setpropnativeadd-stub-new-group");
         JS_STATIC_ASSERT(ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH == 4);
         switch (propStub->protoChainDepth()) {
           case 0: propStub->toImpl<0>()->traceShapes(trc); break;
@@ -420,16 +419,22 @@ ICStub::trace(JSTracer* trc)
       case ICStub::Rest_Fallback: {
         ICRest_Fallback* stub = toRest_Fallback();
         TraceEdge(trc, &stub->templateObject(), "baseline-rest-template");
         break;
       }
       case ICStub::CacheIR_Monitored:
         TraceCacheIRStub(trc, this, toCacheIR_Monitored()->stubInfo());
         break;
+      case ICStub::CacheIR_Updated: {
+        ICCacheIR_Updated* stub = toCacheIR_Updated();
+        TraceEdge(trc, &stub->updateStubId(), "baseline-updated-id");
+        TraceCacheIRStub(trc, this, stub->stubInfo());
+        break;
+      }
       default:
         break;
     }
 }
 
 void
 ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub)
 {
@@ -726,31 +731,32 @@ ICStubCompiler::pushStubPayload(MacroAss
 void
 ICStubCompiler::PushStubPayload(MacroAssembler& masm, Register scratch)
 {
     pushStubPayload(masm, scratch);
     masm.adjustFrame(sizeof(intptr_t));
 }
 
 void
-ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
-                                         Register scratch, LiveGeneralRegisterSet saveRegs)
+BaselineEmitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
+                                 Register scratch, LiveGeneralRegisterSet saveRegs,
+                                 JSRuntime* rt)
 {
     Label skipBarrier;
     masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier);
     masm.branchValueIsNurseryObject(Assembler::NotEqual, val, scratch, &skipBarrier);
 
     // void PostWriteBarrier(JSRuntime* rt, JSObject* obj);
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     saveRegs.add(ICTailCallReg);
 #endif
     saveRegs.set() = GeneralRegisterSet::Intersect(saveRegs.set(), GeneralRegisterSet::Volatile());
     masm.PushRegsInMask(saveRegs);
     masm.setupUnalignedABICall(scratch);
-    masm.movePtr(ImmPtr(cx->runtime()), scratch);
+    masm.movePtr(ImmPtr(rt), scratch);
     masm.passABIArg(scratch);
     masm.passABIArg(obj);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
     masm.PopRegsInMask(saveRegs);
 
     masm.bind(&skipBarrier);
 }
 
@@ -1970,17 +1976,17 @@ StripPreliminaryObjectStubs(JSContext* c
     // both the old and new number of fixed slots, the stub will look
     // polymorphic to IonBuilder when it is actually monomorphic. To avoid
     // this, strip out any stubs for preliminary objects before attaching a new
     // stub which isn't on a preliminary object.
 
     for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
         if (iter->isCacheIR_Monitored() && iter->toCacheIR_Monitored()->hasPreliminaryObject())
             iter.unlink(cx);
-        else if (iter->isSetProp_Native() && iter->toSetProp_Native()->hasPreliminaryObject())
+        else if (iter->isCacheIR_Updated() && iter->toCacheIR_Updated()->hasPreliminaryObject())
             iter.unlink(cx);
     }
 }
 
 JSObject*
 GetDOMProxyProto(JSObject* obj)
 {
     MOZ_ASSERT(IsCacheableDOMProxy(obj));
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -503,17 +503,17 @@ class ICStub
 #undef DEF_ENUM_KIND
         LIMIT
     };
 
     static bool IsValidKind(Kind k) {
         return (k > INVALID) && (k < LIMIT);
     }
     static bool IsCacheIRKind(Kind k) {
-        return k == CacheIR_Monitored;
+        return k == CacheIR_Monitored || k == CacheIR_Updated;
     }
 
     static const char* KindString(Kind k) {
         switch(k) {
 #define DEF_KIND_STR(kindName) case kindName: return #kindName;
             IC_BASELINE_STUB_KIND_LIST(DEF_KIND_STR)
             IC_SHARED_STUB_KIND_LIST(DEF_KIND_STR)
 #undef DEF_KIND_STR
@@ -951,16 +951,46 @@ class ICUpdatedStub : public ICStub
         return numOptimizedStubs_;
     }
 
     static inline size_t offsetOfFirstUpdateStub() {
         return offsetof(ICUpdatedStub, firstUpdateStub_);
     }
 };
 
+class ICCacheIR_Updated : public ICUpdatedStub
+{
+    const CacheIRStubInfo* stubInfo_;
+    GCPtrId updateStubId_;
+
+  public:
+    ICCacheIR_Updated(JitCode* stubCode, const CacheIRStubInfo* stubInfo)
+      : ICUpdatedStub(ICStub::CacheIR_Updated, stubCode),
+        stubInfo_(stubInfo),
+        updateStubId_(JSID_EMPTY)
+    {}
+
+    GCPtrId& updateStubId() {
+        return updateStubId_;
+    }
+
+    void notePreliminaryObject() {
+        extra_ = 1;
+    }
+    bool hasPreliminaryObject() const {
+        return extra_;
+    }
+
+    const CacheIRStubInfo* stubInfo() const {
+        return stubInfo_;
+    }
+
+    uint8_t* stubDataStart();
+};
+
 // Base class for stubcode compilers.
 class ICStubCompiler
 {
     // Prevent GC in the middle of stub compilation.
     js::gc::AutoSuppressGC suppressGC;
 
   public:
     using Engine = ICStubEngine;
@@ -1057,19 +1087,16 @@ class ICStubCompiler
           default:
             MOZ_CRASH("Invalid numInputs");
         }
 
         return regs;
     }
 
   protected:
-    void emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
-                                  Register scratch, LiveGeneralRegisterSet saveRegs);
-
     template <typename T, typename... Args>
     T* newStub(Args&&... args) {
         return ICStub::New<T>(cx, mozilla::Forward<Args>(args)...);
     }
 
   public:
     virtual ICStub* getStub(ICStubSpace* space) = 0;
 
@@ -1081,16 +1108,20 @@ class ICStubCompiler
         }
         return outerScript->zone()->jitZone()->optimizedStubSpace();
     }
     ICStubSpace* getStubSpace(JSScript* outerScript) {
         return StubSpaceForStub(ICStub::NonCacheIRStubMakesGCCalls(kind), outerScript, engine_);
     }
 };
 
+void BaselineEmitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
+                                      Register scratch, LiveGeneralRegisterSet saveRegs,
+                                      JSRuntime* rt);
+
 class SharedStubInfo
 {
     BaselineFrame* maybeFrame_;
     RootedScript outerScript_;
     RootedScript innerScript_;
     ICEntry* icEntry_;
 
   public:
--- a/js/src/jit/SharedICList.h
+++ b/js/src/jit/SharedICList.h
@@ -33,14 +33,15 @@ namespace jit {
     _(Compare_Object)                            \
     _(Compare_ObjectWithUndefined)               \
     _(Compare_Int32WithBoolean)                  \
                                                  \
     _(GetProp_Fallback)                          \
     _(GetProp_Generic)                           \
                                                  \
     _(CacheIR_Monitored)                         \
+    _(CacheIR_Updated)                           \
                                                  \
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_SharedICList_h */
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -5017,16 +5017,23 @@ MacroAssembler::call(ImmPtr imm)
 void
 MacroAssembler::call(wasm::SymbolicAddress imm)
 {
     movePtr(imm, CallReg);
     call(CallReg);
 }
 
 void
+MacroAssembler::call(const Address& addr)
+{
+    loadPtr(addr, CallReg);
+    call(CallReg);
+}
+
+void
 MacroAssembler::call(JitCode* c)
 {
     BufferOffset bo = m_buffer.nextOffset();
     addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE);
     ScratchRegisterScope scratch(*this);
     ma_movPatchable(ImmPtr(c->raw()), scratch, Always);
     callJitNoProfiler(scratch);
 }
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -524,16 +524,26 @@ MacroAssembler::call(wasm::SymbolicAddre
     vixl::UseScratchRegisterScope temps(this);
     const Register scratch = temps.AcquireX().asUnsized();
     syncStackPtr();
     movePtr(imm, scratch);
     call(scratch);
 }
 
 void
+MacroAssembler::call(const Address& addr)
+{
+    vixl::UseScratchRegisterScope temps(this);
+    const Register scratch = temps.AcquireX().asUnsized();
+    syncStackPtr();
+    loadPtr(addr, scratch);
+    call(scratch);
+}
+
+void
 MacroAssembler::call(JitCode* c)
 {
     vixl::UseScratchRegisterScope temps(this);
     const ARMRegister scratch64 = temps.AcquireX();
     syncStackPtr();
     BufferOffset off = immPool64(scratch64, uint64_t(c->raw()));
     addPendingJump(off, ImmPtr(c->raw()), Relocation::JITCODE);
     blr(scratch64);