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 280049 7ccb13e479c4018ea7049ec020dff2fc412807ee
parent 280048 34335f888c3008fb9797c5de3bcdb65890b80712
child 280050 e7df200578ce326f1f53d5bdc364ce9f176c5303
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1171405
milestone41.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 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));