Bug 1171405 - Add baseline and ion ICs for GETELEM on unboxed plain objects, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 16 Jun 2015 17:27:12 -0700
changeset 267371 7ccb13e479c4018ea7049ec020dff2fc412807ee
parent 267370 34335f888c3008fb9797c5de3bcdb65890b80712
child 267372 e7df200578ce326f1f53d5bdc364ce9f176c5303
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1171405
milestone41.0a1
Bug 1171405 - Add baseline and ion ICs for GETELEM on unboxed plain objects, r=jandem.
js/src/jit-test/tests/basic/unboxed-object-getelem.js
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineICList.h
js/src/jit/BaselineInspector.cpp
js/src/jit/IonCaches.cpp
js/src/jit/SharedIC.cpp
js/src/jit/SharedIC.h
js/src/vm/UnboxedObject.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-getelem.js
@@ -0,0 +1,20 @@
+
+function f() {
+    var propNames = ["a","b","c","d","e","f","g","h","i","j","x","y"];
+    var arr = [];
+    for (var i=0; i<64; i++)
+	arr.push({x:1, y:2});
+    for (var i=0; i<64; i++) {
+        // Make sure there are expandos with dynamic slots for each object.
+        for (var j = 0; j < propNames.length; j++)
+            arr[i][propNames[j]] = j;
+    }
+    var res = 0;
+    for (var i=0; i<100000; i++) {
+	var o = arr[i % 64];
+	var p = propNames[i % propNames.length];
+	res += o[p];
+    }
+    assertEq(res, 549984);
+}
+f();
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -40,16 +40,54 @@
 #include "vm/UnboxedObject-inl.h"
 
 using mozilla::BitwiseCast;
 using mozilla::DebugOnly;
 
 namespace js {
 namespace jit {
 
+static void
+GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard,
+                    Register object, Register scratch,
+                    size_t receiverGuardOffset, Label* failure)
+{
+    Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup());
+    Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape());
+    Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
+
+    if (guard.group) {
+        masm.loadPtr(groupAddress, scratch);
+        masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure);
+
+        if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) {
+            // Guard the unboxed object has no expando object.
+            masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
+        }
+    }
+
+    if (guard.shape) {
+        masm.loadPtr(shapeAddress, scratch);
+        if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) {
+            // Guard the unboxed object has a matching expando object.
+            masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
+            Label done;
+            masm.push(object);
+            masm.loadPtr(expandoAddress, object);
+            masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
+            masm.pop(object);
+            masm.jump(failure);
+            masm.bind(&done);
+            masm.pop(object);
+        } else {
+            masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
+        }
+    }
+}
+
 //
 // WarmUpCounter_Fallback
 //
 
 static bool
 EnsureCanEnterIon(JSContext* cx, ICWarmUpCounter_Fallback* stub, BaselineFrame* frame,
                   HandleScript script, jsbytecode* pc, void** jitcodePtr)
 {
@@ -2923,20 +2961,21 @@ LookupNoSuchMethodHandler(JSContext* cx,
 }
 
 typedef bool (*LookupNoSuchMethodHandlerFn)(JSContext*, HandleObject, HandleValue,
                                             MutableHandleValue);
 static const VMFunction LookupNoSuchMethodHandlerInfo =
     FunctionInfo<LookupNoSuchMethodHandlerFn>(LookupNoSuchMethodHandler);
 
 static bool
-GetElemNativeStubExists(ICGetElem_Fallback* stub, HandleNativeObject obj, HandleNativeObject holder,
+GetElemNativeStubExists(ICGetElem_Fallback* stub, HandleObject obj, HandleObject holder,
                         HandlePropertyName propName, bool needsAtomize)
 {
     bool indirect = (obj.get() != holder.get());
+    MOZ_ASSERT_IF(indirect, holder->isNative());
 
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (iter->kind() != ICStub::GetElem_NativeSlot &&
             iter->kind() != ICStub::GetElem_NativePrototypeSlot &&
             iter->kind() != ICStub::GetElem_NativePrototypeCallNative &&
             iter->kind() != ICStub::GetElem_NativePrototypeCallScripted)
         {
             continue;
@@ -2948,60 +2987,61 @@ GetElemNativeStubExists(ICGetElem_Fallba
         {
             continue;
         }
 
         ICGetElemNativeStub* getElemNativeStub = reinterpret_cast<ICGetElemNativeStub*>(*iter);
         if (propName != getElemNativeStub->name())
             continue;
 
-        if (obj->lastProperty() != getElemNativeStub->shape())
+        if (ReceiverGuard(obj) != getElemNativeStub->receiverGuard())
             continue;
 
         // If the new stub needs atomization, and the old stub doesn't atomize, then
         // an appropriate stub doesn't exist.
         if (needsAtomize && !getElemNativeStub->needsAtomize())
             continue;
 
         // For prototype gets, check the holder and holder shape.
         if (indirect) {
             if (iter->isGetElem_NativePrototypeSlot()) {
                 ICGetElem_NativePrototypeSlot* protoStub = iter->toGetElem_NativePrototypeSlot();
 
                 if (holder != protoStub->holder())
                     continue;
 
-                if (holder->lastProperty() != protoStub->holderShape())
+                if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape())
                     continue;
             } else {
                 MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNative() ||
                            iter->isGetElem_NativePrototypeCallScripted());
 
                 ICGetElemNativePrototypeCallStub* protoStub =
                     reinterpret_cast<ICGetElemNativePrototypeCallStub*>(*iter);
 
                 if (holder != protoStub->holder())
                     continue;
 
-                if (holder->lastProperty() != protoStub->holderShape())
+                if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape())
                     continue;
             }
         }
 
         return true;
     }
     return false;
 }
 
 static void
-RemoveExistingGetElemNativeStubs(JSContext* cx, ICGetElem_Fallback* stub, HandleNativeObject obj,
-                                 HandleNativeObject holder, HandlePropertyName propName,
+RemoveExistingGetElemNativeStubs(JSContext* cx, ICGetElem_Fallback* stub, HandleObject obj,
+                                 HandleObject holder, HandlePropertyName propName,
                                  bool needsAtomize)
 {
     bool indirect = (obj.get() != holder.get());
+    MOZ_ASSERT_IF(indirect, holder->isNative());
 
     for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
         switch (iter->kind()) {
           case ICStub::GetElem_NativeSlot:
             if (indirect)
                 continue;
           case ICStub::GetElem_NativePrototypeSlot:
           case ICStub::GetElem_NativePrototypeCallNative:
@@ -3010,46 +3050,46 @@ RemoveExistingGetElemNativeStubs(JSConte
           default:
             continue;
         }
 
         ICGetElemNativeStub* getElemNativeStub = reinterpret_cast<ICGetElemNativeStub*>(*iter);
         if (propName != getElemNativeStub->name())
             continue;
 
-        if (obj->lastProperty() != getElemNativeStub->shape())
+        if (ReceiverGuard(obj) != getElemNativeStub->receiverGuard())
             continue;
 
         // For prototype gets, check the holder and holder shape.
         if (indirect) {
             if (iter->isGetElem_NativePrototypeSlot()) {
                 ICGetElem_NativePrototypeSlot* protoStub = iter->toGetElem_NativePrototypeSlot();
 
                 if (holder != protoStub->holder())
                     continue;
 
                 // If the holder matches, but the holder's lastProperty doesn't match, then
                 // this stub is invalid anyway.  Unlink it.
-                if (holder->lastProperty() != protoStub->holderShape()) {
+                if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) {
                     iter.unlink(cx);
                     continue;
                 }
             } else {
                 MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNative() ||
                            iter->isGetElem_NativePrototypeCallScripted());
 
                 ICGetElemNativePrototypeCallStub* protoStub =
                     reinterpret_cast<ICGetElemNativePrototypeCallStub*>(*iter);
 
                 if (holder != protoStub->holder())
                     continue;
 
                 // If the holder matches, but the holder's lastProperty doesn't match, then
                 // this stub is invalid anyway.  Unlink it.
-                if (holder->lastProperty() != protoStub->holderShape()) {
+                if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) {
                     iter.unlink(cx);
                     continue;
                 }
             }
         }
 
         // If the new stub needs atomization, and the old stub doesn't atomize, then
         // remove the old stub.
@@ -3101,70 +3141,118 @@ IsOptimizableElementPropertyName(JSConte
     uint32_t dummy;
     if (!JSID_IS_ATOM(idp) || JSID_TO_ATOM(idp)->isIndex(&dummy))
         return false;
 
     return true;
 }
 
 static bool
-TryAttachNativeGetValueElemStub(JSContext* cx, HandleScript script, jsbytecode* pc,
-                                ICGetElem_Fallback* stub, HandleNativeObject obj,
-                                HandleValue key, bool* attached)
+TryAttachNativeOrUnboxedGetValueElemStub(JSContext* cx, HandleScript script, jsbytecode* pc,
+                                         ICGetElem_Fallback* stub, HandleObject obj,
+                                         HandleValue key, bool* attached)
 {
     RootedId id(cx);
     if (!IsOptimizableElementPropertyName(cx, key, &id))
         return true;
 
     RootedPropertyName propName(cx, JSID_TO_ATOM(id)->asPropertyName());
     bool needsAtomize = !key.toString()->isAtom();
     bool isCallElem = (JSOp(*pc) == JSOP_CALLELEM);
 
     RootedShape shape(cx);
-    RootedObject baseHolder(cx);
-    if (!EffectlesslyLookupProperty(cx, obj, propName, &baseHolder, &shape))
-        return false;
-    if (!baseHolder || !baseHolder->isNative())
-        return true;
-
-    HandleNativeObject holder = baseHolder.as<NativeObject>();
-
-    if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
-        // If a suitable stub already exists, nothing else to do.
-        if (GetElemNativeStubExists(stub, obj, holder, propName, needsAtomize))
+    RootedObject holder(cx);
+    if (!EffectlesslyLookupProperty(cx, obj, propName, &holder, &shape))
+        return false;
+    if (!holder || (holder != obj && !holder->isNative()))
+        return true;
+
+    // If a suitable stub already exists, nothing else to do.
+    if (GetElemNativeStubExists(stub, obj, holder, propName, needsAtomize))
+        return true;
+
+    // Remove any existing stubs that may interfere with the new stub being added.
+    RemoveExistingGetElemNativeStubs(cx, stub, obj, holder, propName, needsAtomize);
+
+    ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
+
+    if (obj->is<UnboxedPlainObject>() && holder == obj) {
+        const UnboxedLayout::Property* property =
+            obj->as<UnboxedPlainObject>().layout().lookup(propName);
+        if (property) {
+            if (!cx->runtime()->jitSupportsFloatingPoint)
+                return true;
+
+            ICGetElemNativeCompiler compiler(cx, ICStub::GetElem_UnboxedProperty, isCallElem,
+                                             monitorStub, obj, holder, propName,
+                                             ICGetElemNativeStub::UnboxedProperty, needsAtomize,
+                                             property->offset + UnboxedPlainObject::offsetOfData(),
+                                             property->type);
+            ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+            if (!newStub)
+                return false;
+
+            stub->addNewStub(newStub);
+            *attached = true;
             return true;
-
-        // Remove any existing stubs that may interfere with the new stub being added.
-        RemoveExistingGetElemNativeStubs(cx, stub, obj, holder, propName, needsAtomize);
+        }
+
+        Shape* shape = obj->as<UnboxedPlainObject>().maybeExpando()->lookup(cx, propName);
+        if (!shape->hasDefaultGetter() || !shape->hasSlot())
+            return true;
 
         bool isFixedSlot;
         uint32_t offset;
         GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
 
-        ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
+        ICGetElemNativeStub::AccessType acctype =
+            isFixedSlot ? ICGetElemNativeStub::FixedSlot
+                        : ICGetElemNativeStub::DynamicSlot;
+        ICGetElemNativeCompiler compiler(cx, ICStub::GetElem_NativeSlot, isCallElem,
+                                         monitorStub, obj, holder, propName,
+                                         acctype, needsAtomize, offset);
+        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+        if (!newStub)
+            return false;
+
+        stub->addNewStub(newStub);
+        *attached = true;
+        return true;
+    }
+
+    if (!holder->isNative())
+        return true;
+
+    if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
+        bool isFixedSlot;
+        uint32_t offset;
+        GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
+
         ICStub::Kind kind = (obj == holder) ? ICStub::GetElem_NativeSlot
                                             : ICStub::GetElem_NativePrototypeSlot;
 
         JitSpew(JitSpew_BaselineIC, "  Generating GetElem(Native %s%s slot) stub "
-                                    "(obj=%p, shape=%p, holder=%p, holderShape=%p)",
+                                    "(obj=%p, holder=%p, holderShape=%p)",
                     (obj == holder) ? "direct" : "prototype",
                     needsAtomize ? " atomizing" : "",
-                    obj.get(), obj->lastProperty(), holder.get(), holder->lastProperty());
+                obj.get(), holder.get(), holder->as<NativeObject>().lastProperty());
 
         ICGetElemNativeStub::AccessType acctype = isFixedSlot ? ICGetElemNativeStub::FixedSlot
                                                               : ICGetElemNativeStub::DynamicSlot;
         ICGetElemNativeCompiler compiler(cx, kind, isCallElem, monitorStub, obj, holder, propName,
                                          acctype, needsAtomize, offset);
         ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
         if (!newStub)
             return false;
 
         stub->addNewStub(newStub);
         *attached = true;
-    }
+        return true;
+    }
+
     return true;
 }
 
 static bool
 TryAttachNativeGetAccessorElemStub(JSContext* cx, HandleScript script, jsbytecode* pc,
                                    ICGetElem_Fallback* stub, HandleNativeObject obj,
                                    HandleValue key, bool* attached, bool* isTemporarilyUnoptimizable)
 {
@@ -3377,21 +3465,21 @@ TryAttachGetElemStub(JSContext* cx, JSSc
         if (!denseStub)
             return false;
 
         stub->addNewStub(denseStub);
         *attached = true;
         return true;
     }
 
-    // Check for NativeObject[id] shape-optimizable accesses.
-    if (obj->isNative() && rhs.isString()) {
+    // Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses.
+    if ((obj->isNative() || obj->is<UnboxedPlainObject>()) && rhs.isString()) {
         RootedScript rootedScript(cx, script);
-        if (!TryAttachNativeGetValueElemStub(cx, rootedScript, pc, stub,
-                                             obj.as<NativeObject>(), rhs, attached))
+        if (!TryAttachNativeOrUnboxedGetValueElemStub(cx, rootedScript, pc, stub,
+                                                      obj, rhs, attached))
         {
             return false;
         }
         if (*attached)
             return true;
         script = rootedScript;
     }
 
@@ -3694,20 +3782,19 @@ ICGetElemNativeCompiler::generateStubCod
     masm.branchTestString(Assembler::NotEqual, R1, &failure);
 
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
     Register scratchReg = regs.takeAny();
 
     // Unbox object.
     Register objReg = masm.extractObject(R0, ExtractTemp0);
 
-    // Check object shape.
-    masm.loadPtr(Address(objReg, JSObject::offsetOfShape()), scratchReg);
-    Address shapeAddr(ICStubReg, ICGetElemNativeStub::offsetOfShape());
-    masm.branchPtr(Assembler::NotEqual, shapeAddr, scratchReg, &failure);
+    // Check object shape/group.
+    GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratchReg,
+                        ICGetElemNativeStub::offsetOfReceiverGuard(), &failure);
 
     // Check key identity.  Don't automatically fail if this fails, since the incoming
     // key maybe a non-interned string.  Switch to a slowpath vm-call based check.
     Address nameAddr(ICStubReg, ICGetElemNativeStub::offsetOfName());
     Register strExtract = masm.extractString(R1, ExtractTemp1);
 
     // If needsAtomize_ is true, and the string is not already an atom, then atomize the
     // string before proceeding.
@@ -3741,27 +3828,35 @@ ICGetElemNativeCompiler::generateStubCod
 
         // Extract string from R1 again.
         DebugOnly<Register> strExtract2 = masm.extractString(R1, ExtractTemp1);
         MOZ_ASSERT(Register(strExtract2) == strExtract);
 
         masm.bind(&skipAtomize);
     }
 
-    // Since this stub sometimes enter a stub frame, we manually set this to true (lie).
+    // Since this stub sometimes enters a stub frame, we manually set this to true (lie).
 #ifdef DEBUG
     entersStubFrame_ = true;
 #endif
 
     // Key has been atomized if necessary.  Do identity check on string pointer.
     masm.branchPtr(Assembler::NotEqual, nameAddr, strExtract, &failure);
 
     Register holderReg;
     if (obj_ == holder_) {
         holderReg = objReg;
+
+        if (obj_->is<UnboxedPlainObject>() && acctype_ != ICGetElemNativeStub::UnboxedProperty) {
+            // The property will be loaded off the unboxed expando.
+            masm.push(R1.scratchReg());
+            popR1 = true;
+            holderReg = R1.scratchReg();
+            masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
+        }
     } else {
         // Shape guard holder.
         if (regs.empty()) {
             masm.push(R1.scratchReg());
             popR1 = true;
             holderReg = R1.scratchReg();
         } else {
             holderReg = regs.takeAny();
@@ -3850,16 +3945,23 @@ ICGetElemNativeCompiler::generateStubCod
                 masm.addToStackPtr(ImmWord(sizeof(size_t)));
         }
 #else
         masm.loadValue(valAddr, R0);
         if (popR1)
             masm.addToStackPtr(ImmWord(sizeof(size_t)));
 #endif
 
+    } else if (acctype_ == ICGetElemNativeStub::UnboxedProperty) {
+        masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub::offsetOfOffset()),
+                    scratchReg);
+        masm.loadUnboxedProperty(BaseIndex(objReg, scratchReg, TimesOne), unboxedType_,
+                                 TypedOrValueRegister(R0));
+        if (popR1)
+            masm.addPtr(ImmWord(sizeof(size_t)), BaselineStackReg);
     } else {
         MOZ_ASSERT(acctype_ == ICGetElemNativeStub::NativeGetter ||
                    acctype_ == ICGetElemNativeStub::ScriptedGetter);
         MOZ_ASSERT(kind == ICStub::GetElem_NativePrototypeCallNative ||
                    kind == ICStub::GetElem_NativePrototypeCallScripted);
 
         if (acctype_ == ICGetElemNativeStub::NativeGetter) {
             // If calling a native getter, there is no chance of failure now.
@@ -7262,54 +7364,16 @@ ICGetPropNativeCompiler::getStub(ICStubS
                                                   offset_, holder_, holderShape);
       }
 
       default:
         MOZ_CRASH("Bad stub kind");
     }
 }
 
-static void
-GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard,
-                    Register object, Register scratch,
-                    size_t receiverGuardOffset, Label* failure)
-{
-    Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup());
-    Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape());
-    Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
-
-    if (guard.group) {
-        masm.loadPtr(groupAddress, scratch);
-        masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure);
-
-        if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) {
-            // Guard the unboxed object has no expando object.
-            masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
-        }
-    }
-
-    if (guard.shape) {
-        masm.loadPtr(shapeAddress, scratch);
-        if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) {
-            // Guard the unboxed object has a matching expando object.
-            masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
-            Label done;
-            masm.push(object);
-            masm.loadPtr(expandoAddress, object);
-            masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
-            masm.pop(object);
-            masm.jump(failure);
-            masm.bind(&done);
-            masm.pop(object);
-        } else {
-            masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
-        }
-    }
-}
-
 bool
 ICGetPropNativeCompiler::generateStubCode(MacroAssembler& masm)
 {
     MOZ_ASSERT(engine_ == Engine::Baseline);
 
     Label failure;
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
     Register objReg = InvalidReg;
@@ -11918,84 +11982,84 @@ ICTypeUpdate_SingleObject::ICTypeUpdate_
 
 ICTypeUpdate_ObjectGroup::ICTypeUpdate_ObjectGroup(JitCode* stubCode, ObjectGroup* group)
   : ICStub(TypeUpdate_ObjectGroup, stubCode),
     group_(group)
 { }
 
 ICGetElemNativeStub::ICGetElemNativeStub(ICStub::Kind kind, JitCode* stubCode,
                                          ICStub* firstMonitorStub,
-                                         Shape* shape, PropertyName* name,
+                                         ReceiverGuard guard, PropertyName* name,
                                          AccessType acctype, bool needsAtomize)
   : ICMonitoredStub(kind, stubCode, firstMonitorStub),
-    shape_(shape),
+    receiverGuard_(guard),
     name_(name)
 {
     extra_ = (static_cast<uint16_t>(acctype) << ACCESSTYPE_SHIFT) |
              (static_cast<uint16_t>(needsAtomize) << NEEDS_ATOMIZE_SHIFT);
 }
 
 ICGetElemNativeStub::~ICGetElemNativeStub()
 { }
 
 ICGetElemNativeGetterStub::ICGetElemNativeGetterStub(
                         ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                        Shape* shape, PropertyName* name, AccessType acctype,
+                        ReceiverGuard guard, PropertyName* name, AccessType acctype,
                         bool needsAtomize, JSFunction* getter, uint32_t pcOffset)
-  : ICGetElemNativeStub(kind, stubCode, firstMonitorStub, shape, name, acctype, needsAtomize),
+  : ICGetElemNativeStub(kind, stubCode, firstMonitorStub, guard, name, acctype, needsAtomize),
     getter_(getter),
     pcOffset_(pcOffset)
 {
     MOZ_ASSERT(kind == GetElem_NativePrototypeCallNative ||
                kind == GetElem_NativePrototypeCallScripted);
     MOZ_ASSERT(acctype == NativeGetter || acctype == ScriptedGetter);
 }
 
 ICGetElem_NativePrototypeSlot::ICGetElem_NativePrototypeSlot(
                             JitCode* stubCode, ICStub* firstMonitorStub,
-                            Shape* shape, PropertyName* name,
+                            ReceiverGuard guard, PropertyName* name,
                             AccessType acctype, bool needsAtomize, uint32_t offset,
                             JSObject* holder, Shape* holderShape)
-  : ICGetElemNativeSlotStub(ICStub::GetElem_NativePrototypeSlot, stubCode, firstMonitorStub, shape,
+  : ICGetElemNativeSlotStub(ICStub::GetElem_NativePrototypeSlot, stubCode, firstMonitorStub, guard,
                             name, acctype, needsAtomize, offset),
     holder_(holder),
     holderShape_(holderShape)
 { }
 
 ICGetElemNativePrototypeCallStub::ICGetElemNativePrototypeCallStub(
                                 ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                                Shape* shape, PropertyName* name,
+                                ReceiverGuard guard, PropertyName* name,
                                 AccessType acctype, bool needsAtomize, JSFunction* getter,
                                 uint32_t pcOffset, JSObject* holder, Shape* holderShape)
-  : ICGetElemNativeGetterStub(kind, stubCode, firstMonitorStub, shape, name, acctype, needsAtomize,
+  : ICGetElemNativeGetterStub(kind, stubCode, firstMonitorStub, guard, name, acctype, needsAtomize,
                               getter, pcOffset),
     holder_(holder),
     holderShape_(holderShape)
 {}
 
 /* static */ ICGetElem_NativePrototypeCallNative*
 ICGetElem_NativePrototypeCallNative::Clone(JSContext* cx,
                                            ICStubSpace* space,
                                            ICStub* firstMonitorStub,
                                            ICGetElem_NativePrototypeCallNative& other)
 {
     return New<ICGetElem_NativePrototypeCallNative>(cx, space, other.jitCode(), firstMonitorStub,
-                                                    other.shape(), other.name(), other.accessType(),
+                                                    other.receiverGuard(), other.name(), other.accessType(),
                                                     other.needsAtomize(), other.getter(), other.pcOffset_,
                                                     other.holder(), other.holderShape());
 }
 
 /* static */ ICGetElem_NativePrototypeCallScripted*
 ICGetElem_NativePrototypeCallScripted::Clone(JSContext* cx,
                                              ICStubSpace* space,
                                              ICStub* firstMonitorStub,
                                              ICGetElem_NativePrototypeCallScripted& other)
 {
     return New<ICGetElem_NativePrototypeCallScripted>(cx, space, other.jitCode(), firstMonitorStub,
-                                                      other.shape(), other.name(),
+                                                      other.receiverGuard(), other.name(),
                                                       other.accessType(), other.needsAtomize(), other.getter(),
                                                       other.pcOffset_, other.holder(), other.holderShape());
 }
 
 ICGetElem_Dense::ICGetElem_Dense(JitCode* stubCode, ICStub* firstMonitorStub, Shape* shape)
     : ICMonitoredStub(GetElem_Dense, stubCode, firstMonitorStub),
       shape_(shape)
 { }
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1522,40 +1522,40 @@ class ICGetElem_Fallback : public ICMoni
             return stub;
         }
     };
 };
 
 class ICGetElemNativeStub : public ICMonitoredStub
 {
   public:
-    enum AccessType { FixedSlot = 0, DynamicSlot, NativeGetter, ScriptedGetter };
+    enum AccessType { FixedSlot = 0, DynamicSlot, UnboxedProperty, NativeGetter, ScriptedGetter };
 
   protected:
-    HeapPtrShape shape_;
+    HeapReceiverGuard receiverGuard_;
     HeapPtrPropertyName name_;
 
     static const unsigned NEEDS_ATOMIZE_SHIFT = 0;
     static const uint16_t NEEDS_ATOMIZE_MASK = 0x1;
 
     static const unsigned ACCESSTYPE_SHIFT = 1;
     static const uint16_t ACCESSTYPE_MASK = 0x3;
 
     ICGetElemNativeStub(ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                        Shape* shape, PropertyName* name, AccessType acctype,
+                        ReceiverGuard guard, PropertyName* name, AccessType acctype,
                         bool needsAtomize);
 
     ~ICGetElemNativeStub();
 
   public:
-    HeapPtrShape& shape() {
-        return shape_;
-    }
-    static size_t offsetOfShape() {
-        return offsetof(ICGetElemNativeStub, shape_);
+    HeapReceiverGuard& receiverGuard() {
+        return receiverGuard_;
+    }
+    static size_t offsetOfReceiverGuard() {
+        return offsetof(ICGetElemNativeStub, receiverGuard_);
     }
 
     HeapPtrPropertyName& name() {
         return name_;
     }
     static size_t offsetOfName() {
         return offsetof(ICGetElemNativeStub, name_);
     }
@@ -1570,23 +1570,24 @@ class ICGetElemNativeStub : public ICMon
 };
 
 class ICGetElemNativeSlotStub : public ICGetElemNativeStub
 {
   protected:
     uint32_t offset_;
 
     ICGetElemNativeSlotStub(ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                            Shape* shape, PropertyName* name,
+                            ReceiverGuard guard, PropertyName* name,
                             AccessType acctype, bool needsAtomize, uint32_t offset)
-      : ICGetElemNativeStub(kind, stubCode, firstMonitorStub, shape, name, acctype, needsAtomize),
+      : ICGetElemNativeStub(kind, stubCode, firstMonitorStub, guard, name, acctype, needsAtomize),
         offset_(offset)
     {
-        MOZ_ASSERT(kind == GetElem_NativeSlot || kind == GetElem_NativePrototypeSlot);
-        MOZ_ASSERT(acctype == FixedSlot || acctype == DynamicSlot);
+        MOZ_ASSERT(kind == GetElem_NativeSlot || kind == GetElem_NativePrototypeSlot ||
+                   kind == GetElem_UnboxedProperty);
+        MOZ_ASSERT(acctype == FixedSlot || acctype == DynamicSlot || acctype == UnboxedProperty);
     }
 
   public:
     uint32_t offset() const {
         return offset_;
     }
 
     static size_t offsetOfOffset() {
@@ -1596,17 +1597,17 @@ class ICGetElemNativeSlotStub : public I
 
 class ICGetElemNativeGetterStub : public ICGetElemNativeStub
 {
   protected:
     HeapPtrFunction getter_;
     uint32_t pcOffset_;
 
     ICGetElemNativeGetterStub(ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                              Shape* shape, PropertyName* name, AccessType acctype,
+                              ReceiverGuard guard, PropertyName* name, AccessType acctype,
                               bool needsAtomize, JSFunction* getter, uint32_t pcOffset);
 
   public:
     HeapPtrFunction& getter() {
         return getter_;
     }
     static size_t offsetOfGetter() {
         return offsetof(ICGetElemNativeGetterStub, getter_);
@@ -1616,31 +1617,42 @@ class ICGetElemNativeGetterStub : public
         return offsetof(ICGetElemNativeGetterStub, pcOffset_);
     }
 };
 
 class ICGetElem_NativeSlot : public ICGetElemNativeSlotStub
 {
     friend class ICStubSpace;
     ICGetElem_NativeSlot(JitCode* stubCode, ICStub* firstMonitorStub,
-                         Shape* shape, PropertyName* name,
+                         ReceiverGuard guard, PropertyName* name,
                          AccessType acctype, bool needsAtomize, uint32_t offset)
-      : ICGetElemNativeSlotStub(ICStub::GetElem_NativeSlot, stubCode, firstMonitorStub, shape,
+      : ICGetElemNativeSlotStub(ICStub::GetElem_NativeSlot, stubCode, firstMonitorStub, guard,
+                                name, acctype, needsAtomize, offset)
+    {}
+};
+
+class ICGetElem_UnboxedProperty : public ICGetElemNativeSlotStub
+{
+    friend class ICStubSpace;
+    ICGetElem_UnboxedProperty(JitCode* stubCode, ICStub* firstMonitorStub,
+                              ReceiverGuard guard, PropertyName* name,
+                              AccessType acctype, bool needsAtomize, uint32_t offset)
+      : ICGetElemNativeSlotStub(ICStub::GetElem_UnboxedProperty, stubCode, firstMonitorStub, guard,
                                 name, acctype, needsAtomize, offset)
     {}
 };
 
 class ICGetElem_NativePrototypeSlot : public ICGetElemNativeSlotStub
 {
     friend class ICStubSpace;
     HeapPtrObject holder_;
     HeapPtrShape holderShape_;
 
     ICGetElem_NativePrototypeSlot(JitCode* stubCode, ICStub* firstMonitorStub,
-                                  Shape* shape, PropertyName* name,
+                                  ReceiverGuard guard, PropertyName* name,
                                   AccessType acctype, bool needsAtomize, uint32_t offset,
                                   JSObject* holder, Shape* holderShape);
 
   public:
     HeapPtrObject& holder() {
         return holder_;
     }
     static size_t offsetOfHolder() {
@@ -1658,17 +1670,17 @@ class ICGetElem_NativePrototypeSlot : pu
 class ICGetElemNativePrototypeCallStub : public ICGetElemNativeGetterStub
 {
     friend class ICStubSpace;
     HeapPtrObject holder_;
     HeapPtrShape holderShape_;
 
   protected:
     ICGetElemNativePrototypeCallStub(ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub,
-                                     Shape* shape, PropertyName* name,
+                                     ReceiverGuard guard, PropertyName* name,
                                      AccessType acctype, bool needsAtomize, JSFunction* getter,
                                      uint32_t pcOffset, JSObject* holder,
                                      Shape* holderShape);
 
   public:
     HeapPtrObject& holder() {
         return holder_;
     }
@@ -1684,43 +1696,43 @@ class ICGetElemNativePrototypeCallStub :
     }
 };
 
 class ICGetElem_NativePrototypeCallNative : public ICGetElemNativePrototypeCallStub
 {
     friend class ICStubSpace;
 
     ICGetElem_NativePrototypeCallNative(JitCode* stubCode, ICStub* firstMonitorStub,
-                                        Shape* shape, PropertyName* name,
+                                        ReceiverGuard guard, PropertyName* name,
                                         AccessType acctype, bool needsAtomize,
                                         JSFunction* getter, uint32_t pcOffset,
                                         JSObject* holder, Shape* holderShape)
       : ICGetElemNativePrototypeCallStub(GetElem_NativePrototypeCallNative,
-                                         stubCode, firstMonitorStub, shape, name,
+                                         stubCode, firstMonitorStub, guard, name,
                                          acctype, needsAtomize, getter, pcOffset, holder,
                                          holderShape)
     {}
 
   public:
     static ICGetElem_NativePrototypeCallNative* Clone(JSContext* cx, ICStubSpace* space,
                                                       ICStub* firstMonitorStub,
                                                       ICGetElem_NativePrototypeCallNative& other);
 };
 
 class ICGetElem_NativePrototypeCallScripted : public ICGetElemNativePrototypeCallStub
 {
     friend class ICStubSpace;
 
     ICGetElem_NativePrototypeCallScripted(JitCode* stubCode, ICStub* firstMonitorStub,
-                                          Shape* shape, PropertyName* name,
+                                          ReceiverGuard guard, PropertyName* name,
                                           AccessType acctype, bool needsAtomize,
                                           JSFunction* getter, uint32_t pcOffset,
                                           JSObject* holder, Shape* holderShape)
       : ICGetElemNativePrototypeCallStub(GetElem_NativePrototypeCallScripted,
-                                         stubCode, firstMonitorStub, shape, name,
+                                         stubCode, firstMonitorStub, guard, name,
                                          acctype, needsAtomize, getter, pcOffset, holder,
                                          holderShape)
     {}
 
   public:
     static ICGetElem_NativePrototypeCallScripted*
     Clone(JSContext* cx, ICStubSpace* space,
           ICStub* firstMonitorStub,
@@ -1733,53 +1745,55 @@ class ICGetElemNativeCompiler : public I
     bool isCallElem_;
     ICStub* firstMonitorStub_;
     HandleObject obj_;
     HandleObject holder_;
     HandlePropertyName name_;
     ICGetElemNativeStub::AccessType acctype_;
     bool needsAtomize_;
     uint32_t offset_;
+    JSValueType unboxedType_;
     HandleFunction getter_;
     uint32_t pcOffset_;
 
     bool emitCallNative(MacroAssembler& masm, Register objReg);
     bool emitCallScripted(MacroAssembler& masm, Register objReg);
     bool generateStubCode(MacroAssembler& masm);
 
   protected:
     virtual int32_t getKey() const {
-#if JS_HAS_NO_SUCH_METHOD
+        MOZ_ASSERT(static_cast<int32_t>(acctype_) <= 7);
+        MOZ_ASSERT(static_cast<int32_t>(unboxedType_) <= 8);
         return static_cast<int32_t>(engine_) |
               (static_cast<int32_t>(kind) << 1) |
+#if JS_HAS_NO_SUCH_METHOD
               (static_cast<int32_t>(isCallElem_) << 17) |
+#endif
               (static_cast<int32_t>(needsAtomize_) << 18) |
-              (static_cast<int32_t>(acctype_) << 19);
-#else
-        return static_cast<int32_t>(engine_) |
-              (static_cast<int32_t>(kind) << 1) |
-              (static_cast<int32_t>(needsAtomize_) << 17) |
-              (static_cast<int32_t>(acctype_) << 18);
-#endif
+              (static_cast<int32_t>(acctype_) << 19) |
+              (static_cast<int32_t>(unboxedType_) << 22) |
+              (HeapReceiverGuard::keyBits(obj_) << 26);
     }
 
   public:
     ICGetElemNativeCompiler(JSContext* cx, ICStub::Kind kind, bool isCallElem,
                             ICStub* firstMonitorStub, HandleObject obj, HandleObject holder,
                             HandlePropertyName name, ICGetElemNativeStub::AccessType acctype,
-                            bool needsAtomize, uint32_t offset)
+                            bool needsAtomize, uint32_t offset,
+                            JSValueType unboxedType = JSVAL_TYPE_MAGIC)
       : ICStubCompiler(cx, kind, Engine::Baseline),
         isCallElem_(isCallElem),
         firstMonitorStub_(firstMonitorStub),
         obj_(obj),
         holder_(holder),
         name_(name),
         acctype_(acctype),
         needsAtomize_(needsAtomize),
         offset_(offset),
+        unboxedType_(unboxedType),
         getter_(nullptr),
         pcOffset_(0)
     {}
 
     ICGetElemNativeCompiler(JSContext* cx, ICStub::Kind kind, ICStub* firstMonitorStub,
                             HandleObject obj, HandleObject holder, HandlePropertyName name,
                             ICGetElemNativeStub::AccessType acctype, bool needsAtomize,
                             HandleFunction getter, uint32_t pcOffset, bool isCallElem)
@@ -1787,47 +1801,55 @@ class ICGetElemNativeCompiler : public I
         isCallElem_(false),
         firstMonitorStub_(firstMonitorStub),
         obj_(obj),
         holder_(holder),
         name_(name),
         acctype_(acctype),
         needsAtomize_(needsAtomize),
         offset_(0),
+        unboxedType_(JSVAL_TYPE_MAGIC),
         getter_(getter),
         pcOffset_(pcOffset)
     {}
 
     ICStub* getStub(ICStubSpace* space) {
-        RootedShape shape(cx, obj_->as<NativeObject>().lastProperty());
+        RootedReceiverGuard guard(cx, ReceiverGuard(obj_));
         if (kind == ICStub::GetElem_NativeSlot) {
             MOZ_ASSERT(obj_ == holder_);
             return newStub<ICGetElem_NativeSlot>(
-                    space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_,
+                    space, getStubCode(), firstMonitorStub_, guard, name_, acctype_, needsAtomize_,
+                    offset_);
+        }
+
+        if (kind == ICStub::GetElem_UnboxedProperty) {
+            MOZ_ASSERT(obj_ == holder_);
+            return newStub<ICGetElem_UnboxedProperty>(
+                    space, getStubCode(), firstMonitorStub_, guard, name_, acctype_, needsAtomize_,
                     offset_);
         }
 
         MOZ_ASSERT(obj_ != holder_);
         RootedShape holderShape(cx, holder_->as<NativeObject>().lastProperty());
         if (kind == ICStub::GetElem_NativePrototypeSlot) {
             return newStub<ICGetElem_NativePrototypeSlot>(
-                    space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_,
+                    space, getStubCode(), firstMonitorStub_, guard, name_, acctype_, needsAtomize_,
                     offset_, holder_, holderShape);
         }
 
         if (kind == ICStub::GetElem_NativePrototypeCallNative) {
             return newStub<ICGetElem_NativePrototypeCallNative>(
-                    space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_,
+                    space, getStubCode(), firstMonitorStub_, guard, name_, acctype_, needsAtomize_,
                     getter_, pcOffset_, holder_, holderShape);
         }
 
         MOZ_ASSERT(kind == ICStub::GetElem_NativePrototypeCallScripted);
         if (kind == ICStub::GetElem_NativePrototypeCallScripted) {
             return newStub<ICGetElem_NativePrototypeCallScripted>(
-                    space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_,
+                    space, getStubCode(), firstMonitorStub_, guard, name_, acctype_, needsAtomize_,
                     getter_, pcOffset_, holder_, holderShape);
         }
 
         MOZ_CRASH("Invalid kind.");
     }
 };
 
 class ICGetElem_String : public ICStub
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -72,16 +72,17 @@ namespace jit {
     _(Call_StringSplit)                          \
     _(Call_IsSuspendedStarGenerator)             \
                                                  \
     _(GetElem_Fallback)                          \
     _(GetElem_NativeSlot)                        \
     _(GetElem_NativePrototypeSlot)               \
     _(GetElem_NativePrototypeCallNative)         \
     _(GetElem_NativePrototypeCallScripted)       \
+    _(GetElem_UnboxedProperty)                   \
     _(GetElem_String)                            \
     _(GetElem_Dense)                             \
     _(GetElem_UnboxedArray)                      \
     _(GetElem_TypedArray)                        \
     _(GetElem_Arguments)                         \
                                                  \
     _(SetElem_Fallback)                          \
     _(SetElem_DenseOrUnboxedArray)               \
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -743,16 +743,17 @@ BaselineInspector::expectedPropertyAcces
           case ICStub::GetProp_CallNative:
           case ICStub::GetProp_CallDOMProxyNative:
           case ICStub::GetProp_CallDOMProxyWithGenerationNative:
           case ICStub::GetProp_DOMProxyShadowed:
           case ICStub::GetElem_NativeSlot:
           case ICStub::GetElem_NativePrototypeSlot:
           case ICStub::GetElem_NativePrototypeCallNative:
           case ICStub::GetElem_NativePrototypeCallScripted:
+          case ICStub::GetElem_UnboxedProperty:
           case ICStub::GetElem_String:
           case ICStub::GetElem_Dense:
           case ICStub::GetElem_TypedArray:
           case ICStub::GetElem_UnboxedArray:
             stubType = MIRType_Object;
             break;
 
           case ICStub::GetProp_Primitive:
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -864,28 +864,34 @@ GenerateReadSlot(JSContext* cx, IonScrip
     attacher.jumpNextStub(masm);
 
 }
 
 static void
 GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm,
                     IonCache::StubAttacher& attacher, JSObject* obj,
                     const UnboxedLayout::Property* property,
-                    Register object, TypedOrValueRegister output)
+                    Register object, TypedOrValueRegister output,
+                    Label* failures = nullptr)
 {
-    // Guard on the type of the object.
-    attacher.branchNextStub(masm, Assembler::NotEqual,
-                            Address(object, JSObject::offsetOfGroup()),
-                            ImmGCPtr(obj->group()));
+    // Guard on the group of the object.
+    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+                                   Address(object, JSObject::offsetOfGroup()),
+                                   ImmGCPtr(obj->group()), failures);
 
     Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);
 
     masm.loadUnboxedProperty(address, property->type, output);
 
     attacher.jumpRejoin(masm);
+
+    if (failures) {
+        masm.bind(failures);
+        attacher.jumpNextStub(masm);
+    }
 }
 
 static bool
 EmitGetterCall(JSContext* cx, MacroAssembler& masm,
                IonCache::StubAttacher& attacher, JSObject* obj,
                JSObject* holder, HandleShape shape,
                LiveRegisterSet liveRegs, Register object,
                TypedOrValueRegister output,
@@ -3335,17 +3341,17 @@ SetPropertyIC::reset(ReprotectCode repro
 }
 
 const size_t GetElementIC::MAX_FAILED_UPDATES = 16;
 
 /* static */ bool
 GetElementIC::canAttachGetProp(JSObject* obj, const Value& idval, jsid id)
 {
     uint32_t dummy;
-    return obj->isNative() &&
+    return (obj->isNative() || obj->is<UnboxedPlainObject>()) &&
            idval.isString() &&
            JSID_IS_ATOM(id) &&
            !JSID_TO_ATOM(id)->isIndex(&dummy);
 }
 
 static bool
 EqualStringsHelper(JSString* str1, JSString* str2)
 {
@@ -3361,28 +3367,49 @@ EqualStringsHelper(JSString* str1, JSStr
 }
 
 bool
 GetElementIC::attachGetProp(JSContext* cx, HandleScript outerScript, IonScript* ion,
                             HandleObject obj, const Value& idval, HandlePropertyName name)
 {
     MOZ_ASSERT(index().reg().hasValue());
 
-    RootedNativeObject holder(cx);
+    RootedNativeObject baseHolder(cx);
     RootedShape shape(cx);
 
     GetPropertyIC::NativeGetPropCacheability canCache =
-        CanAttachNativeGetProp(cx, *this, obj, name, &holder, &shape,
+        CanAttachNativeGetProp(cx, *this, obj, name, &baseHolder, &shape,
                                /* skipArrayLen =*/true);
 
-    bool cacheable = canCache == GetPropertyIC::CanAttachReadSlot ||
-                     (canCache == GetPropertyIC::CanAttachCallGetter &&
-                      output().hasValue());
-
-    if (!cacheable) {
+    RootedObject holder(cx, baseHolder);
+
+    if (canCache == GetPropertyIC::CanAttachReadSlot) {
+        // OK to attach.
+    } else if (canCache == GetPropertyIC::CanAttachCallGetter) {
+        if (!output().hasValue()) {
+            JitSpew(JitSpew_IonIC, "GETELEM uncacheable property");
+            return true;
+        }
+    } else if (obj->is<UnboxedPlainObject>()) {
+        MOZ_ASSERT(canCache == GetPropertyIC::CanAttachNone);
+        const UnboxedLayout::Property* property =
+            obj->as<UnboxedPlainObject>().layout().lookup(name);
+        if (property) {
+            // OK to attach.
+        } else {
+            UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+            shape = expando ? expando->lookup(cx, name) : nullptr;
+            if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) {
+                JitSpew(JitSpew_IonIC, "GETELEM uncacheable property");
+                return true;
+            }
+            canCache = GetPropertyIC::CanAttachReadSlot;
+            holder = obj;
+        }
+    } else {
         JitSpew(JitSpew_IonIC, "GETELEM uncacheable property");
         return true;
     }
 
     MOZ_ASSERT(idval.isString());
     MOZ_ASSERT(idval.toString()->length() == name->length());
 
     Label failures;
@@ -3435,26 +3462,29 @@ GetElementIC::attachGetProp(JSContext* c
 
     masm.branchIfFalseBool(scratch, &failures);
     masm.bind(&equal);
 
     StubAttacher attacher(*this);
     if (canCache == GetPropertyIC::CanAttachReadSlot) {
         GenerateReadSlot(cx, ion, masm, attacher, obj, holder, shape, object(), output(),
                          &failures);
-    } else {
-        MOZ_ASSERT(canCache == GetPropertyIC::CanAttachCallGetter);
-
+    } else if (canCache == GetPropertyIC::CanAttachCallGetter) {
         // Set the frame for bailout safety of the OOL call.
         void* returnAddr = GetReturnAddressToIonCode(cx);
         if (!GenerateCallGetter(cx, ion, masm, attacher, obj, name, holder, shape, liveRegs_,
                                 object(), output(), returnAddr, &failures))
         {
             return false;
         }
+    } else {
+        MOZ_ASSERT(canCache == GetPropertyIC::CanAttachNone);
+        GenerateReadUnboxed(cx, ion, masm, attacher, obj,
+                            obj->as<UnboxedPlainObject>().layout().lookup(name),
+                            object(), output(), &failures);
     }
 
     return linkAndAttachStub(cx, masm, attacher, ion, "property");
 }
 
 /* static */ bool
 GetElementIC::canAttachDenseElement(JSObject* obj, const Value& idval)
 {
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -184,35 +184,37 @@ ICStub::trace(JSTracer* trc)
       }
       case ICStub::Call_StringSplit: {
         ICCall_StringSplit* callStub = toCall_StringSplit();
         TraceEdge(trc, &callStub->templateObject(), "baseline-callstringsplit-template");
         TraceEdge(trc, &callStub->expectedArg(), "baseline-callstringsplit-arg");
         TraceEdge(trc, &callStub->expectedThis(), "baseline-callstringsplit-this");
         break;
       }
-      case ICStub::GetElem_NativeSlot: {
-        ICGetElem_NativeSlot* getElemStub = toGetElem_NativeSlot();
-        TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-native-shape");
+      case ICStub::GetElem_NativeSlot:
+      case ICStub::GetElem_UnboxedProperty: {
+        ICGetElemNativeSlotStub* getElemStub =
+            reinterpret_cast<ICGetElemNativeSlotStub*>(this);
+        getElemStub->receiverGuard().trace(trc);
         TraceEdge(trc, &getElemStub->name(), "baseline-getelem-native-name");
         break;
       }
       case ICStub::GetElem_NativePrototypeSlot: {
         ICGetElem_NativePrototypeSlot* getElemStub = toGetElem_NativePrototypeSlot();
-        TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-nativeproto-shape");
+        getElemStub->receiverGuard().trace(trc);
         TraceEdge(trc, &getElemStub->name(), "baseline-getelem-nativeproto-name");
         TraceEdge(trc, &getElemStub->holder(), "baseline-getelem-nativeproto-holder");
         TraceEdge(trc, &getElemStub->holderShape(), "baseline-getelem-nativeproto-holdershape");
         break;
       }
       case ICStub::GetElem_NativePrototypeCallNative:
       case ICStub::GetElem_NativePrototypeCallScripted: {
         ICGetElemNativePrototypeCallStub* callStub =
             reinterpret_cast<ICGetElemNativePrototypeCallStub*>(this);
-        TraceEdge(trc, &callStub->shape(), "baseline-getelem-nativeprotocall-shape");
+        callStub->receiverGuard().trace(trc);
         TraceEdge(trc, &callStub->name(), "baseline-getelem-nativeprotocall-name");
         TraceEdge(trc, &callStub->getter(), "baseline-getelem-nativeprotocall-getter");
         TraceEdge(trc, &callStub->holder(), "baseline-getelem-nativeprotocall-holder");
         TraceEdge(trc, &callStub->holderShape(), "baseline-getelem-nativeprotocall-holdershape");
         break;
       }
       case ICStub::GetElem_Dense: {
         ICGetElem_Dense* getElemStub = toGetElem_Dense();
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -688,16 +688,17 @@ class ICStub
           case Call_ScriptedApplyArguments:
           case Call_ScriptedFunCall:
           case Call_StringSplit:
           case WarmUpCounter_Fallback:
           case GetElem_NativeSlot:
           case GetElem_NativePrototypeSlot:
           case GetElem_NativePrototypeCallNative:
           case GetElem_NativePrototypeCallScripted:
+          case GetElem_UnboxedProperty:
           case GetProp_CallScripted:
           case GetProp_CallNative:
           case GetProp_CallDOMProxyNative:
           case GetProp_CallDOMProxyWithGenerationNative:
           case GetProp_DOMProxyShadowed:
           case GetProp_Generic:
           case SetProp_CallScripted:
           case SetProp_CallNative:
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -332,17 +332,18 @@ UnboxedPlainObject::trace(JSTracer* trc,
 }
 
 /* static */ UnboxedExpandoObject*
 UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj)
 {
     if (obj->expando_)
         return obj->expando_;
 
-    UnboxedExpandoObject* expando = NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr);
+    UnboxedExpandoObject* expando =
+        NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, AllocKind::OBJECT4);
     if (!expando)
         return nullptr;
 
     // If the expando is tenured then the original object must also be tenured.
     // Otherwise barriers triggered on the original object for writes to the
     // expando (as can happen in the JIT) won't see the tenured->nursery edge.
     // See WholeCellEdges::mark.
     MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));