Bug 1137497 - Remove shape from unboxed objects, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 04 Mar 2015 08:32:45 -0600
changeset 231873 afda1ff329bf2d1da22e0f09eb1c24537eefb7f4
parent 231872 0709fe191f0072fcaec3daadbe3f30d016433b1f
child 231874 aaa85558f413b82ccf321a5a738c05c5c2538b64
push idunknown
push userunknown
push dateunknown
reviewersjandem
bugs1137497
milestone39.0a1
Bug 1137497 - Remove shape from unboxed objects, r=jandem.
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/gc/Marking.cpp
js/src/gc/Nursery.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/CodeGenerator.cpp
js/src/jit/Ion.cpp
js/src/jit/IonCaches.cpp
js/src/jit/MacroAssembler.cpp
js/src/jit/ScalarReplacement.cpp
js/src/jit/VMFunctions.cpp
js/src/jsarray.cpp
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsgcinlines.h
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jspropertytree.cpp
js/src/proxy/Proxy.cpp
js/src/shell/js.cpp
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/ObjectGroup.cpp
js/src/vm/PIC.cpp
js/src/vm/ProxyObject.h
js/src/vm/Runtime-inl.h
js/src/vm/Runtime.h
js/src/vm/Shape.cpp
js/src/vm/Shape.h
js/src/vm/TypeInference.cpp
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1628,16 +1628,18 @@ ReportTypedObjTypeError(JSContext *cx,
     return false;
 }
 
 /* static */ void
 OutlineTypedObject::obj_trace(JSTracer *trc, JSObject *object)
 {
     OutlineTypedObject &typedObj = object->as<OutlineTypedObject>();
 
+    MarkShape(trc, &typedObj.shape_, "OutlineTypedObject_shape");
+
     if (!typedObj.owner_)
         return;
 
     // When this is called for compacting GC, the related objects we touch here
     // may not have had their slots updated yet. Note that this does not apply
     // to generational GC because these objects (type descriptors and
     // prototypes) are never allocated in the nursery.
     TypeDescr &descr = typedObj.maybeForwardedTypeDescr();
@@ -2151,20 +2153,23 @@ InlineTypedObject::createCopy(JSContext 
     return res;
 }
 
 /* static */ void
 InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object)
 {
     InlineTypedObject &typedObj = object->as<InlineTypedObject>();
 
-    // Inline transparent objects do not have references and do not need to be
-    // traced. If they have an entry in the compartment's LazyArrayBufferTable,
+    MarkShape(trc, &typedObj.shape_, "InlineTypedObject_shape");
+
+    // Inline transparent objects do not have references and do not need more
+    // tracing. If there is an entry in the compartment's LazyArrayBufferTable,
     // tracing that reference will be taken care of by the table itself.
-    MOZ_ASSERT(typedObj.is<InlineOpaqueTypedObject>());
+    if (typedObj.is<InlineTransparentTypedObject>())
+        return;
 
     // When this is called for compacting GC, the related objects we touch here
     // may not have had their slots updated yet.
     TypeDescr &descr = typedObj.maybeForwardedTypeDescr();
 
     descr.traceInstances(trc, typedObj.inlineTypedMem(), 1);
 }
 
@@ -2332,17 +2337,17 @@ LazyArrayBufferTable::sizeOfIncludingThi
             nullptr,   /* getElements */                 \
             TypedObject::obj_enumerate,                  \
             nullptr, /* thisObject */                    \
         }                                                \
     }
 
 DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace);
 DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,      OutlineTypedObject::obj_trace);
-DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  nullptr);
+DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  InlineTypedObject::obj_trace);
 DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,       InlineTypedObject::obj_trace);
 
 static int32_t
 LengthForType(TypeDescr &descr)
 {
     switch (descr.kind()) {
       case type::Scalar:
       case type::Reference:
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -364,17 +364,17 @@ bool CreateUserSizeAndAlignmentPropertie
 
 class ArrayTypeDescr;
 
 /*
  * Properties and methods of the `ArrayType` meta type object. There
  * is no `class_` field because `ArrayType` is just a native
  * constructor function.
  */
-class ArrayMetaTypeDescr : public JSObject
+class ArrayMetaTypeDescr : public NativeObject
 {
   private:
     // Helper for creating a new ArrayType object.
     //
     // - `arrayTypePrototype` - prototype for the new object to be created
     // - `elementType` - type object for the elements in the array
     // - `stringRepr` - canonical string representation for the array
     // - `size` - length of the array
@@ -428,17 +428,17 @@ class ArrayTypeDescr : public ComplexTyp
     }
 };
 
 /*
  * Properties and methods of the `StructType` meta type object. There
  * is no `class_` field because `StructType` is just a native
  * constructor function.
  */
-class StructMetaTypeDescr : public JSObject
+class StructMetaTypeDescr : public NativeObject
 {
   private:
     static JSObject *create(JSContext *cx, HandleObject structTypeGlobal,
                             HandleObject fields);
 
   public:
     // Properties and methods to be installed on StructType.prototype,
     // and hence inherited by all struct type objects:
@@ -505,26 +505,27 @@ class TypedObjectModuleObject : public N
     };
 
     static const Class class_;
 };
 
 /* Base type for transparent and opaque typed objects. */
 class TypedObject : public JSObject
 {
-  private:
     static const bool IsTypedObjectClass = true;
 
     static bool obj_getArrayElement(JSContext *cx,
                                     Handle<TypedObject*> typedObj,
                                     Handle<TypeDescr*> typeDescr,
                                     uint32_t index,
                                     MutableHandleValue vp);
 
   protected:
+    HeapPtrShape shape_;
+
     static bool obj_lookupProperty(JSContext *cx, HandleObject obj,
                                    HandleId id, MutableHandleObject objp,
                                    MutableHandleShape propp);
 
     static bool obj_lookupElement(JSContext *cx, HandleObject obj, uint32_t index,
                                   MutableHandleObject objp, MutableHandleShape propp);
 
     static bool obj_defineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
@@ -596,16 +597,18 @@ class TypedObject : public JSObject
 
     // User-accessible constructor (`new TypeDescriptor(...)`). Note that the
     // callee here is the type descriptor.
     static bool construct(JSContext *cx, unsigned argc, Value *vp);
 
     /* Accessors for self hosted code. */
     static bool GetBuffer(JSContext *cx, unsigned argc, Value *vp);
     static bool GetByteOffset(JSContext *cx, unsigned argc, Value *vp);
+
+    Shape *shapeFromGC() { return shape_; }
 };
 
 typedef Handle<TypedObject*> HandleTypedObject;
 
 class OutlineTypedObject : public TypedObject
 {
     // The object which owns the data this object points to. Because this
     // pointer is managed in tandem with |data|, this is not a HeapPtr and
@@ -706,18 +709,16 @@ class InlineTypedObject : public TypedOb
     static gc::AllocKind allocKindForTypeDescriptor(TypeDescr *descr) {
         size_t nbytes = descr->size();
         MOZ_ASSERT(nbytes <= MaximumSize);
 
         return gc::GetGCObjectKindForBytes(nbytes + sizeof(TypedObject));
     }
 
     uint8_t *inlineTypedMem() const {
-        static_assert(offsetof(InlineTypedObject, data_) == sizeof(JSObject),
-                      "The data for an inline typed object must follow the shape and type.");
         return (uint8_t *) &data_;
     }
 
     static void obj_trace(JSTracer *trace, JSObject *object);
     static void objectMovedDuringMinorGC(JSTracer *trc, JSObject *dst, JSObject *src);
 
     static size_t offsetOfDataStart() {
         return offsetof(InlineTypedObject, data_);
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1673,51 +1673,54 @@ GCMarker::processMarkStackTop(SliceBudge
         if (budget.isOverBudget()) {
             repush(obj);
             return;
         }
 
         ObjectGroup *group = obj->groupFromGC();
         traverse(group);
 
-        Shape *shape = obj->lastProperty();
-        PushMarkStack(this, shape);
-
         /* Call the trace hook if necessary. */
         const Class *clasp = group->clasp();
         if (clasp->trace) {
             // Global objects all have the same trace hook. That hook is safe without barriers
             // if the global has no custom trace hook of its own, or has been moved to a different
             // compartment, and so can't have one.
             MOZ_ASSERT_IF(!(clasp->trace == JS_GlobalObjectTraceHook &&
                             (!obj->compartment()->options().getTrace() || !obj->isOwnGlobal())),
                           clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS);
             if (clasp->trace == InlineTypedObject::obj_trace) {
-                TypeDescr *descr = &obj->as<InlineOpaqueTypedObject>().typeDescr();
+                Shape *shape = obj->as<InlineTypedObject>().shapeFromGC();
+                PushMarkStack(this, shape);
+                TypeDescr *descr = &obj->as<InlineTypedObject>().typeDescr();
                 if (!descr->hasTraceList())
                     return;
                 unboxedTraceList = descr->traceList();
-                unboxedMemory = obj->as<InlineOpaqueTypedObject>().inlineTypedMem();
+                unboxedMemory = obj->as<InlineTypedObject>().inlineTypedMem();
                 goto scan_unboxed;
             }
             if (clasp == &UnboxedPlainObject::class_) {
                 const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
                 unboxedTraceList = layout.traceList();
                 if (!unboxedTraceList)
                     return;
                 unboxedMemory = obj->as<UnboxedPlainObject>().data();
                 goto scan_unboxed;
             }
             clasp->trace(this, obj);
         }
 
-        if (!shape->isNative())
+        if (!clasp->isNative())
             return;
 
         NativeObject *nobj = &obj->as<NativeObject>();
+
+        Shape *shape = nobj->lastProperty();
+        PushMarkStack(this, shape);
+
         unsigned nslots = nobj->slotSpan();
 
         do {
             if (nobj->hasEmptyElements())
                 break;
 
             if (nobj->denseElementsAreCopyOnWrite()) {
                 JSObject *owner = nobj->getElementsHeader()->ownerObject();
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -686,25 +686,26 @@ js::Nursery::moveObjectToTenured(MinorCo
     if (src->is<ArrayObject>())
         tenuredSize = srcSize = sizeof(NativeObject);
 
     js_memcpy(dst, src, srcSize);
     if (src->isNative()) {
         NativeObject *ndst = &dst->as<NativeObject>(), *nsrc = &src->as<NativeObject>();
         tenuredSize += moveSlotsToTenured(ndst, nsrc, dstKind);
         tenuredSize += moveElementsToTenured(ndst, nsrc, dstKind);
+
+        // The shape's list head may point into the old object. This can only
+        // happen for dictionaries, which are native objects.
+        if (&nsrc->shape_ == ndst->shape_->listp)
+            ndst->shape_->listp = &ndst->shape_;
     }
 
     if (src->is<InlineTypedObject>())
         InlineTypedObject::objectMovedDuringMinorGC(trc, dst, src);
 
-    /* The shape's list head may point into the old object. */
-    if (&src->shape_ == dst->shape_->listp)
-        dst->shape_->listp = &dst->shape_;
-
     return tenuredSize;
 }
 
 MOZ_ALWAYS_INLINE size_t
 js::Nursery::moveSlotsToTenured(NativeObject *dst, NativeObject *src, AllocKind dstKind)
 {
     /* Fixed slots have already been copied over. */
     if (!src->hasDynamicSlots())
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -3436,17 +3436,17 @@ IsCacheableGetPropCall(JSContext *cx, JS
 
 static bool
 IsCacheableSetPropWriteSlot(JSObject *obj, Shape *oldShape, JSObject *holder, Shape *shape)
 {
     if (!shape)
         return false;
 
     // Object shape must not have changed during the property set.
-    if (obj->lastProperty() != oldShape)
+    if (!obj->isNative() || obj->as<NativeObject>().lastProperty() != oldShape)
         return false;
 
     // Currently we only optimize direct writes.
     if (obj != holder)
         return false;
 
     if (!shape->hasSlot() || !shape->hasDefaultSetter() || !shape->writable())
         return false;
@@ -3458,21 +3458,21 @@ static bool
 IsCacheableSetPropAddSlot(JSContext *cx, HandleObject obj, HandleShape oldShape, uint32_t oldSlots,
                           HandleId id, HandleObject holder, HandleShape shape,
                           size_t *protoChainDepth)
 {
     if (!shape)
         return false;
 
     // Property must be set directly on object, and be last added property of object.
-    if (obj != holder || shape != obj->lastProperty())
+    if (!obj->isNative() || obj != holder || shape != obj->as<NativeObject>().lastProperty())
         return false;
 
     // Object must be extensible, oldShape must be immediate parent of curShape.
-    if (!obj->nonProxyIsExtensible() || obj->lastProperty()->previous() != oldShape)
+    if (!obj->nonProxyIsExtensible() || shape->previous() != oldShape)
         return false;
 
     // Basic shape checks.
     if (shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter() ||
         !shape->writable())
     {
         return false;
     }
@@ -3557,17 +3557,17 @@ LookupNoSuchMethodHandler(JSContext *cx,
 }
 
 typedef bool (*LookupNoSuchMethodHandlerFn)(JSContext *, HandleObject, HandleValue,
                                             MutableHandleValue);
 static const VMFunction LookupNoSuchMethodHandlerInfo =
     FunctionInfo<LookupNoSuchMethodHandlerFn>(LookupNoSuchMethodHandler);
 
 static bool
-GetElemNativeStubExists(ICGetElem_Fallback *stub, HandleObject obj, HandleObject holder,
+GetElemNativeStubExists(ICGetElem_Fallback *stub, HandleNativeObject obj, HandleNativeObject holder,
                         HandlePropertyName propName, bool needsAtomize)
 {
     bool indirect = (obj.get() != holder.get());
 
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (iter->kind() != ICStub::GetElem_NativeSlot &&
             iter->kind() != ICStub::GetElem_NativePrototypeSlot &&
             iter->kind() != ICStub::GetElem_NativePrototypeCallNative &&
@@ -3621,18 +3621,18 @@ GetElemNativeStubExists(ICGetElem_Fallba
         }
 
         return true;
     }
     return false;
 }
 
 static void
-RemoveExistingGetElemNativeStubs(JSContext *cx, ICGetElem_Fallback *stub, HandleObject obj,
-                                 HandleObject holder, HandlePropertyName propName,
+RemoveExistingGetElemNativeStubs(JSContext *cx, ICGetElem_Fallback *stub, HandleNativeObject obj,
+                                 HandleNativeObject holder, HandlePropertyName propName,
                                  bool needsAtomize)
 {
     bool indirect = (obj.get() != holder.get());
 
     for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
         switch (iter->kind()) {
           case ICStub::GetElem_NativeSlot:
             if (indirect)
@@ -3699,17 +3699,17 @@ RemoveExistingGetElemNativeStubs(JSConte
 }
 
 static bool
 TypedArrayGetElemStubExists(ICGetElem_Fallback *stub, HandleObject obj)
 {
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (!iter->isGetElem_TypedArray())
             continue;
-        if (obj->lastProperty() == iter->toGetElem_TypedArray()->shape())
+        if (obj->maybeShape() == iter->toGetElem_TypedArray()->shape())
             return true;
     }
     return false;
 }
 
 static bool
 ArgumentsGetElemStubExists(ICGetElem_Fallback *stub, ICGetElem_Arguments::Which which)
 {
@@ -3720,17 +3720,17 @@ ArgumentsGetElemStubExists(ICGetElem_Fal
             return true;
     }
     return false;
 }
 
 
 static bool
 TryAttachNativeGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc,
-                           ICGetElem_Fallback *stub, HandleObject obj,
+                           ICGetElem_Fallback *stub, HandleNativeObject obj,
                            HandleValue key)
 {
     // Native-object GetElem stubs can't deal with non-string keys.
     if (!key.isString())
         return true;
 
     // Convert to interned property name.
     RootedId id(cx);
@@ -3741,19 +3741,23 @@ TryAttachNativeGetElemStub(JSContext *cx
     if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy))
         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 holder(cx);
-    if (!EffectlesslyLookupProperty(cx, obj, propName, &holder, &shape))
-        return false;
+    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))
             return true;
 
         // Remove any existing stubs that may interfere with the new stub being added.
         RemoveExistingGetElemNativeStubs(cx, stub, obj, holder, propName, needsAtomize);
@@ -3782,17 +3786,17 @@ TryAttachNativeGetElemStub(JSContext *cx
             return false;
 
         stub->addNewStub(newStub);
         return true;
     }
 
     bool getterIsScripted = false;
     bool isTemporarilyUnoptimizable = false;
-    if (IsCacheableGetPropCall(cx, obj, holder, shape, &getterIsScripted,
+    if (IsCacheableGetPropCall(cx, obj, baseHolder, shape, &getterIsScripted,
                                &isTemporarilyUnoptimizable, /*isDOMProxy=*/false)) {
         RootedFunction getter(cx, &shape->getterObject()->as<JSFunction>());
 
 #if JS_HAS_NO_SUCH_METHOD
         // It's unlikely that a getter function will be used in callelem locations.
         // Just don't attach stubs in that case to avoid issues with __noSuchMethod__ handling.
         if (isCallElem)
             return true;
@@ -3944,29 +3948,29 @@ TryAttachGetElemStub(JSContext *cx, JSSc
         }
     }
 
     if (obj->isNative()) {
         // Check for NativeObject[int] dense accesses.
         if (rhs.isInt32() && rhs.toInt32() >= 0 && !IsAnyTypedArray(obj.get())) {
             JitSpew(JitSpew_BaselineIC, "  Generating GetElem(Native[Int32] dense) stub");
             ICGetElem_Dense::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
-                                               obj->lastProperty(), isCallElem);
+                                               obj->as<NativeObject>().lastProperty(), isCallElem);
             ICStub *denseStub = compiler.getStub(compiler.getStubSpace(script));
             if (!denseStub)
                 return false;
 
             stub->addNewStub(denseStub);
             return true;
         }
 
         // Check for NativeObject[id] shape-optimizable accesses.
         if (rhs.isString()) {
             RootedScript rootedScript(cx, script);
-            if (!TryAttachNativeGetElemStub(cx, rootedScript, pc, stub, obj, rhs))
+            if (!TryAttachNativeGetElemStub(cx, rootedScript, pc, stub, obj.as<NativeObject>(), rhs))
                 return false;
             script = rootedScript;
         }
     }
 
     // Check for TypedArray[int] => Number and TypedObject[int] => Number accesses.
     if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) &&
         rhs.isNumber() &&
@@ -3986,18 +3990,17 @@ TryAttachGetElemStub(JSContext *cx, JSSc
         }
 
         // Don't attach typed object stubs if they might be neutered, as the
         // stub will always bail out.
         if (IsPrimitiveArrayTypedObject(obj) && cx->compartment()->neuteredTypedObjects)
             return true;
 
         JitSpew(JitSpew_BaselineIC, "  Generating GetElem(TypedArray[Int32]) stub");
-        ICGetElem_TypedArray::Compiler compiler(cx, obj->lastProperty(),
-                                                TypedThingElementType(obj));
+        ICGetElem_TypedArray::Compiler compiler(cx, obj->maybeShape(), TypedThingElementType(obj));
         ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script));
         if (!typedArrayStub)
             return false;
 
         stub->addNewStub(typedArrayStub);
         return true;
     }
 
@@ -4882,33 +4885,39 @@ ICGetElem_Arguments::Compiler::generateS
     return true;
 }
 
 //
 // SetElem_Fallback
 //
 
 static bool
-SetElemDenseAddHasSameShapes(ICSetElem_DenseAdd *stub, JSObject *obj)
+SetElemDenseAddHasSameShapes(ICSetElem_DenseAdd *stub, NativeObject *obj)
 {
     size_t numShapes = stub->protoChainDepth() + 1;
     for (size_t i = 0; i < numShapes; i++) {
         static const size_t MAX_DEPTH = ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH;
         if (obj->lastProperty() != stub->toImplUnchecked<MAX_DEPTH>()->shape(i))
             return false;
-        obj = obj->getProto();
-        if (!obj && i != numShapes - 1)
-            return false;
-    }
-
-    return true;
-}
-
-static bool
-DenseSetElemStubExists(JSContext *cx, ICStub::Kind kind, ICSetElem_Fallback *stub, HandleObject obj)
+        JSObject *proto = obj->getProto();
+        if (!proto) {
+            if (i != numShapes - 1)
+                return false;
+            break;
+        }
+        if (!proto->isNative())
+            return false;
+        obj = &proto->as<NativeObject>();
+    }
+
+    return true;
+}
+
+static bool
+DenseSetElemStubExists(JSContext *cx, ICStub::Kind kind, ICSetElem_Fallback *stub, HandleNativeObject obj)
 {
     MOZ_ASSERT(kind == ICStub::SetElem_Dense || kind == ICStub::SetElem_DenseAdd);
 
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (kind == ICStub::SetElem_Dense && iter->isSetElem_Dense()) {
             ICSetElem_Dense *dense = iter->toSetElem_Dense();
             if (obj->lastProperty() == dense->shape() && obj->getGroup(cx) == dense->group())
                 return true;
@@ -4925,30 +4934,30 @@ DenseSetElemStubExists(JSContext *cx, IC
 
 static bool
 TypedArraySetElemStubExists(ICSetElem_Fallback *stub, HandleObject obj, bool expectOOB)
 {
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (!iter->isSetElem_TypedArray())
             continue;
         ICSetElem_TypedArray *taStub = iter->toSetElem_TypedArray();
-        if (obj->lastProperty() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB)
+        if (obj->maybeShape() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB)
             return true;
     }
     return false;
 }
 
 static bool
 RemoveExistingTypedArraySetElemStub(JSContext *cx, ICSetElem_Fallback *stub, HandleObject obj)
 {
     for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
         if (!iter->isSetElem_TypedArray())
             continue;
 
-        if (obj->lastProperty() != iter->toSetElem_TypedArray()->shape())
+        if (obj->maybeShape() != iter->toSetElem_TypedArray()->shape())
             continue;
 
         // TypedArraySetElem stubs are only removed using this procedure if
         // being replaced with one that expects out of bounds index.
         MOZ_ASSERT(!iter->toSetElem_TypedArray()->expectOutOfBounds());
         iter.unlink(cx);
         return true;
     }
@@ -5044,17 +5053,17 @@ DoSetElemFallback(JSContext *cx, Baselin
                op == JSOP_INITELEM ||
                op == JSOP_INITELEM_ARRAY ||
                op == JSOP_INITELEM_INC);
 
     RootedObject obj(cx, ToObjectFromStack(cx, objv));
     if (!obj)
         return false;
 
-    RootedShape oldShape(cx, obj->lastProperty());
+    RootedShape oldShape(cx, obj->maybeShape());
 
     // Check the old capacity
     uint32_t oldCapacity = 0;
     uint32_t oldInitLength = 0;
     if (obj->isNative() && index.isInt32() && index.toInt32() >= 0) {
         oldCapacity = obj->as<NativeObject>().getDenseCapacity();
         oldInitLength = obj->as<NativeObject>().getDenseInitializedLength();
     }
@@ -5089,47 +5098,49 @@ DoSetElemFallback(JSContext *cx, Baselin
     }
 
     // Try to generate new stubs.
     if (obj->isNative() &&
         !IsAnyTypedArray(obj.get()) &&
         index.isInt32() && index.toInt32() >= 0 &&
         !rhs.isMagic(JS_ELEMENTS_HOLE))
     {
+        HandleNativeObject nobj = obj.as<NativeObject>();
+
         bool addingCase;
         size_t protoDepth;
 
-        if (CanOptimizeDenseSetElem(&obj->as<NativeObject>(), index.toInt32(),
+        if (CanOptimizeDenseSetElem(nobj, index.toInt32(),
                                     oldShape, oldCapacity, oldInitLength,
                                     &addingCase, &protoDepth))
         {
-            RootedShape shape(cx, obj->lastProperty());
+            RootedShape shape(cx, nobj->lastProperty());
             RootedObjectGroup group(cx, obj->getGroup(cx));
             if (!group)
                 return false;
 
-            if (addingCase && !DenseSetElemStubExists(cx, ICStub::SetElem_DenseAdd, stub, obj)) {
+            if (addingCase && !DenseSetElemStubExists(cx, ICStub::SetElem_DenseAdd, stub, nobj)) {
                 JitSpew(JitSpew_BaselineIC,
                         "  Generating SetElem_DenseAdd stub "
                         "(shape=%p, group=%p, protoDepth=%u)",
-                        obj->lastProperty(), group.get(), protoDepth);
+                        nobj->lastProperty(), group.get(), protoDepth);
                 ICSetElemDenseAddCompiler compiler(cx, obj, protoDepth);
                 ICUpdatedStub *denseStub = compiler.getStub(compiler.getStubSpace(script));
                 if (!denseStub)
                     return false;
                 if (!denseStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs))
                     return false;
 
                 stub->addNewStub(denseStub);
             } else if (!addingCase &&
-                       !DenseSetElemStubExists(cx, ICStub::SetElem_Dense, stub, obj))
+                       !DenseSetElemStubExists(cx, ICStub::SetElem_Dense, stub, nobj))
             {
                 JitSpew(JitSpew_BaselineIC,
                         "  Generating SetElem_Dense stub (shape=%p, group=%p)",
-                        obj->lastProperty(), group.get());
+                        nobj->lastProperty(), group.get());
                 ICSetElem_Dense::Compiler compiler(cx, shape, group);
                 ICUpdatedStub *denseStub = compiler.getStub(compiler.getStubSpace(script));
                 if (!denseStub)
                     return false;
                 if (!denseStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs))
                     return false;
 
                 stub->addNewStub(denseStub);
@@ -5166,17 +5177,17 @@ DoSetElemFallback(JSContext *cx, Baselin
                 return true;
         }
 
         if (!TypedArraySetElemStubExists(stub, obj, expectOutOfBounds)) {
             // Remove any existing TypedArraySetElemStub that doesn't handle out-of-bounds
             if (expectOutOfBounds)
                 RemoveExistingTypedArraySetElemStub(cx, stub, obj);
 
-            Shape *shape = obj->lastProperty();
+            Shape *shape = obj->maybeShape();
             Scalar::Type type = TypedThingElementType(obj);
 
             JitSpew(JitSpew_BaselineIC,
                     "  Generating SetElem_TypedArray stub (shape=%p, type=%u, oob=%s)",
                     shape, type, expectOutOfBounds ? "yes" : "no");
             ICSetElem_TypedArray::Compiler compiler(cx, shape, type, expectOutOfBounds);
             ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script));
             if (!typedArrayStub)
@@ -5368,33 +5379,33 @@ ICSetElem_Dense::Compiler::generateStubC
     return true;
 }
 
 static bool
 GetProtoShapes(JSObject *obj, size_t protoChainDepth, AutoShapeVector *shapes)
 {
     JSObject *curProto = obj->getProto();
     for (size_t i = 0; i < protoChainDepth; i++) {
-        if (!shapes->append(curProto->lastProperty()))
+        if (!shapes->append(curProto->as<NativeObject>().lastProperty()))
             return false;
         curProto = curProto->getProto();
     }
     MOZ_ASSERT(!curProto);
     return true;
 }
 
 //
 // SetElem_DenseAdd
 //
 
 ICUpdatedStub *
 ICSetElemDenseAddCompiler::getStub(ICStubSpace *space)
 {
     AutoShapeVector shapes(cx);
-    if (!shapes.append(obj_->lastProperty()))
+    if (!shapes.append(obj_->as<NativeObject>().lastProperty()))
         return nullptr;
 
     if (!GetProtoShapes(obj_, protoChainDepth_, &shapes))
         return nullptr;
 
     JS_STATIC_ASSERT(ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH == 4);
 
     ICUpdatedStub *stub = nullptr;
@@ -5757,17 +5768,17 @@ ICIn_Fallback::Compiler::generateStubCod
 // given holder in place with a new shape and getter.  fallbackStub can be
 // either an ICGetProp_Fallback or an ICGetName_Fallback.
 //
 // When kind == ICStub::GetProp_CallNative, callers should pass a null receiver,
 // since in that case receiver and holder are the same thing.
 static bool
 UpdateExistingGetPropCallStubs(ICFallbackStub* fallbackStub,
                                ICStub::Kind kind,
-                               HandleObject holder,
+                               HandleNativeObject holder,
                                HandleObject receiver,
                                HandleFunction getter)
 {
     MOZ_ASSERT(kind == ICStub::GetProp_CallScripted ||
                kind == ICStub::GetProp_CallNative ||
                kind == ICStub::GetProp_CallNativePrototype);
     MOZ_ASSERT(fallbackStub->isGetName_Fallback() ||
                fallbackStub->isGetProp_Fallback());
@@ -5810,17 +5821,17 @@ UpdateExistingGetPropCallStubs(ICFallbac
     return foundMatchingStub;
 }
 
 // Try to update existing SetProp setter call stubs for the given holder in
 // place with a new shape and setter.
 static bool
 UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub,
                                ICStub::Kind kind,
-                               JSObject *holder,
+                               NativeObject *holder,
                                ReceiverGuard::Token receiverGuard,
                                JSFunction *setter)
 {
     MOZ_ASSERT(kind == ICStub::SetProp_CallScripted ||
                kind == ICStub::SetProp_CallNative);
     bool foundMatchingStub = false;
     for (ICStubConstIterator iter = fallbackStub->beginChainConst(); !iter.atEnd(); iter++) {
         if (iter->kind() == kind) {
@@ -5958,17 +5969,17 @@ TryAttachScopeNameStub(JSContext *cx, Ha
     MOZ_ASSERT(!*attached);
 
     AutoShapeVector shapes(cx);
     RootedId id(cx, NameToId(name));
     RootedObject scopeChain(cx, initialScopeChain);
 
     Shape *shape = nullptr;
     while (scopeChain) {
-        if (!shapes.append(scopeChain->lastProperty()))
+        if (!shapes.append(scopeChain->maybeShape()))
             return false;
 
         if (scopeChain->is<GlobalObject>()) {
             shape = scopeChain->as<GlobalObject>().lookup(cx, id);
             if (shape)
                 break;
             return true;
         }
@@ -6487,17 +6498,17 @@ TryAttachNativeGetPropStub(JSContext *cx
     MOZ_ASSERT(!*attached);
     MOZ_ASSERT(!*isTemporarilyUnoptimizable);
 
     if (!val.isObject())
         return true;
 
     RootedObject obj(cx, &val.toObject());
 
-    if (oldShape != obj->lastProperty()) {
+    if (obj->isNative() && oldShape != obj->as<NativeObject>().lastProperty()) {
         // No point attaching anything, since we know the shape guard will fail
         return true;
     }
 
     bool isDOMProxy;
     bool domProxyHasGeneration;
     DOMProxyShadowsResult domProxyShadowsResult;
     RootedShape shape(cx);
@@ -6560,17 +6571,17 @@ TryAttachNativeGetPropStub(JSContext *cx
         if (obj == holder)
             return true;
 
         RootedFunction callee(cx, &shape->getterObject()->as<JSFunction>());
         MOZ_ASSERT(obj != holder);
         MOZ_ASSERT(callee->hasScript());
 
         if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallScripted,
-                                           holder, obj, callee)) {
+                                           holder.as<NativeObject>(), obj, callee)) {
             *attached = true;
             return true;
         }
 
         JitSpew(JitSpew_BaselineIC, "  Generating GetProp(NativeObj/ScriptedGetter %s:%" PRIuSIZE ") stub",
                     callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno());
 
         ICGetProp_CallScripted::Compiler compiler(cx, monitorStub, obj, holder, callee,
@@ -6666,27 +6677,27 @@ TryAttachNativeGetPropStub(JSContext *cx
             kind = ICStub::GetProp_CallDOMProxyNative;
         }
         Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
         ICGetPropCallDOMProxyNativeCompiler compiler(cx, kind, monitorStub, proxy, holder, callee,
                                                      script->pcToOffset(pc));
         newStub = compiler.getStub(compiler.getStubSpace(script));
     } else if (obj == holder) {
         if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNative,
-                                           obj, JS::NullPtr(), callee)) {
+                                           obj.as<NativeObject>(), JS::NullPtr(), callee)) {
             *attached = true;
             return true;
         }
 
         ICGetProp_CallNative::Compiler compiler(cx, monitorStub, obj, callee,
                                                 script->pcToOffset(pc), outerClass);
         newStub = compiler.getStub(compiler.getStubSpace(script));
     } else {
         if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNativePrototype,
-                                           holder, obj, callee)) {
+                                           holder.as<NativeObject>(), obj, callee)) {
             *attached = true;
             return true;
         }
 
         ICGetProp_CallNativePrototype::Compiler compiler(cx, monitorStub, obj, holder, callee,
                                                          script->pcToOffset(pc), outerClass);
         newStub = compiler.getStub(compiler.getStubSpace(script));
     }
@@ -6755,17 +6766,17 @@ TryAttachTypedObjectGetPropStub(JSContex
 
     Rooted<TypeDescr *> fieldDescr(cx, &structDescr->fieldDescr(fieldIndex));
     if (!fieldDescr->is<SimpleTypeDescr>())
         return true;
 
     uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
     ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
 
-    ICGetProp_TypedObject::Compiler compiler(cx, monitorStub, obj->lastProperty(),
+    ICGetProp_TypedObject::Compiler compiler(cx, monitorStub, obj->maybeShape(),
                                              fieldOffset, &fieldDescr->as<SimpleTypeDescr>());
     ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
     if (!newStub)
         return false;
     stub->addNewStub(newStub);
 
     *attached = true;
     return true;
@@ -6918,17 +6929,17 @@ DoGetPropFallback(JSContext *cx, Baselin
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetProp(%s)", js_CodeName[op]);
 
     MOZ_ASSERT(op == JSOP_GETPROP || op == JSOP_CALLPROP || op == JSOP_LENGTH || op == JSOP_GETXPROP);
 
     // Grab our old shape before it goes away.
     RootedShape oldShape(cx);
     if (val.isObject())
-        oldShape = val.toObject().lastProperty();
+        oldShape = val.toObject().maybeShape();
 
     // After the  Genericstub was added, we should never reach the Fallbackstub again.
     MOZ_ASSERT(!stub->hasStub(ICStub::GetProp_Generic));
 
     RootedPropertyName name(cx, frame->script()->getName(pc));
     if (!ComputeGetPropResult(cx, frame, op, name, val, res))
         return false;
 
@@ -7162,24 +7173,24 @@ ICGetProp_Primitive::Compiler::generateS
 }
 
 ICGetPropNativeStub *
 ICGetPropNativeCompiler::getStub(ICStubSpace *space)
 {
     switch (kind) {
       case ICStub::GetProp_Native: {
         MOZ_ASSERT(obj_ == holder_);
-        RootedShape shape(cx, obj_->lastProperty());
+        RootedShape shape(cx, obj_->as<NativeObject>().lastProperty());
         return ICStub::New<ICGetProp_Native>(space, getStubCode(), firstMonitorStub_, shape, offset_);
       }
 
       case ICStub::GetProp_NativePrototype: {
         MOZ_ASSERT(obj_ != holder_);
         ReceiverGuard::Token guard = ReceiverGuard::objectToken(obj_);
-        Shape *holderShape = holder_->lastProperty();
+        Shape *holderShape = holder_->as<NativeObject>().lastProperty();
         return ICStub::New<ICGetProp_NativePrototype>(space, getStubCode(), firstMonitorStub_, guard,
                                                       offset_, holder_, holderShape);
       }
 
       default:
         MOZ_CRASH("Bad stub kind");
     }
 }
@@ -7686,18 +7697,18 @@ ICGetPropCallDOMProxyNativeCompiler::gen
     Address generationAddress(BaselineStubReg,
         ICGetProp_CallDOMProxyWithGenerationNative::offsetOfGeneration());
     return generateStubCode(masm, &internalStructAddress, &generationAddress);
 }
 
 ICStub *
 ICGetPropCallDOMProxyNativeCompiler::getStub(ICStubSpace *space)
 {
-    RootedShape shape(cx, proxy_->lastProperty());
-    RootedShape holderShape(cx, holder_->lastProperty());
+    RootedShape shape(cx, proxy_->maybeShape());
+    RootedShape holderShape(cx, holder_->as<NativeObject>().lastProperty());
 
     Value expandoSlot = GetProxyExtra(proxy_, GetDOMProxyExpandoSlot());
     RootedShape expandoShape(cx, nullptr);
     ExpandoAndGeneration *expandoAndGeneration;
     int32_t generation;
     Value expandoVal;
     if (kind == ICStub::GetProp_CallDOMProxyNative) {
         expandoVal = expandoSlot;
@@ -7707,34 +7718,34 @@ ICGetPropCallDOMProxyNativeCompiler::get
         MOZ_ASSERT(kind == ICStub::GetProp_CallDOMProxyWithGenerationNative);
         MOZ_ASSERT(!expandoSlot.isObject() && !expandoSlot.isUndefined());
         expandoAndGeneration = (ExpandoAndGeneration*)expandoSlot.toPrivate();
         expandoVal = expandoAndGeneration->expando;
         generation = expandoAndGeneration->generation;
     }
 
     if (expandoVal.isObject())
-        expandoShape = expandoVal.toObject().lastProperty();
+        expandoShape = expandoVal.toObject().as<NativeObject>().lastProperty();
 
     if (kind == ICStub::GetProp_CallDOMProxyNative) {
         return ICStub::New<ICGetProp_CallDOMProxyNative>(
             space, getStubCode(), firstMonitorStub_, shape,
             expandoShape, holder_, holderShape, getter_, pcOffset_);
     }
 
     return ICStub::New<ICGetProp_CallDOMProxyWithGenerationNative>(
         space, getStubCode(), firstMonitorStub_, shape,
         expandoAndGeneration, generation, expandoShape, holder_, holderShape, getter_,
         pcOffset_);
 }
 
 ICStub *
 ICGetProp_DOMProxyShadowed::Compiler::getStub(ICStubSpace *space)
 {
-    RootedShape shape(cx, proxy_->lastProperty());
+    RootedShape shape(cx, proxy_->maybeShape());
     return New<ICGetProp_DOMProxyShadowed>(space, getStubCode(), firstMonitorStub_, shape,
                                            proxy_->handler(), name_, pcOffset_);
 }
 
 static bool
 ProxyGet(JSContext *cx, HandleObject proxy, HandlePropertyName name, MutableHandleValue vp)
 {
     RootedId id(cx, NameToId(name));
@@ -8110,17 +8121,17 @@ TryAttachSetValuePropStub(JSContext *cx,
             return true;
         }
 
         bool isFixedSlot;
         uint32_t offset;
         GetFixedOrDynamicSlotOffset(&obj->as<NativeObject>(), shape->slot(), &isFixedSlot, &offset);
 
         JitSpew(JitSpew_BaselineIC, "  Generating SetProp(NativeObject.PROP) stub");
-        MOZ_ASSERT(obj->lastProperty() == oldShape,
+        MOZ_ASSERT(obj->as<NativeObject>().lastProperty() == oldShape,
                    "Should this really be a SetPropWriteSlot?");
         ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset);
         ICSetProp_Native *newStub = compiler.getStub(compiler.getStubSpace(script));
         if (!newStub)
             return false;
         if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
             return false;
 
@@ -8162,17 +8173,17 @@ TryAttachSetAccessorPropStub(JSContext *
 
     // Try handling scripted setters.
     if (cacheableCall && isScripted) {
         RootedFunction callee(cx, &shape->setterObject()->as<JSFunction>());
         MOZ_ASSERT(obj != holder);
         MOZ_ASSERT(callee->hasScript());
 
         if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallScripted,
-                                           holder, receiverGuard, callee)) {
+                                           &holder->as<NativeObject>(), receiverGuard, callee)) {
             *attached = true;
             return true;
         }
 
         JitSpew(JitSpew_BaselineIC, "  Generating SetProp(NativeObj/ScriptedSetter %s:%" PRIuSIZE ") stub",
                     callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno());
 
         ICSetProp_CallScripted::Compiler compiler(cx, obj, holder, callee, script->pcToOffset(pc));
@@ -8187,17 +8198,17 @@ TryAttachSetAccessorPropStub(JSContext *
 
     // Try handling JSNative setters.
     if (cacheableCall && !isScripted) {
         RootedFunction callee(cx, &shape->setterObject()->as<JSFunction>());
         MOZ_ASSERT(obj != holder);
         MOZ_ASSERT(callee->isNative());
 
         if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallNative,
-                                           holder, receiverGuard, callee)) {
+                                           &holder->as<NativeObject>(), receiverGuard, callee)) {
             *attached = true;
             return true;
         }
 
         JitSpew(JitSpew_BaselineIC, "  Generating SetProp(NativeObj/NativeSetter %p) stub",
                     callee->native());
 
         ICSetProp_CallNative::Compiler compiler(cx, obj, holder, callee, script->pcToOffset(pc));
@@ -8270,17 +8281,17 @@ TryAttachTypedObjectSetPropStub(JSContex
         return true;
 
     Rooted<TypeDescr *> fieldDescr(cx, &structDescr->fieldDescr(fieldIndex));
     if (!fieldDescr->is<SimpleTypeDescr>())
         return true;
 
     uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
 
-    ICSetProp_TypedObject::Compiler compiler(cx, obj->lastProperty(), obj->group(), fieldOffset,
+    ICSetProp_TypedObject::Compiler compiler(cx, obj->maybeShape(), obj->group(), fieldOffset,
                                              &fieldDescr->as<SimpleTypeDescr>());
     ICUpdatedStub *newStub = compiler.getStub(compiler.getStubSpace(script));
     if (!newStub)
         return false;
     if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
         return false;
 
     stub->addNewStub(newStub);
@@ -8319,17 +8330,17 @@ DoSetPropFallback(JSContext *cx, Baselin
     else
         name = script->getName(pc);
     RootedId id(cx, NameToId(name));
 
     RootedObject obj(cx, ToObjectFromStack(cx, lhs));
     if (!obj)
         return false;
     ReceiverGuard::Token oldGuard = ReceiverGuard::objectToken(obj);
-    RootedShape oldShape(cx, obj->lastProperty());
+    RootedShape oldShape(cx, obj->maybeShape());
     RootedObjectGroup oldGroup(cx, obj->getGroup(cx));
     if (!oldGroup)
         return false;
     uint32_t oldSlots = obj->isNative() ? obj->as<NativeObject>().numDynamicSlots() : 0;
 
     bool attached = false;
     // There are some reasons we can fail to attach a stub that are temporary.
     // We want to avoid calling noteUnoptimizableAccess() if the reason we
@@ -11978,17 +11989,17 @@ ICSetProp_Native::ICSetProp_Native(JitCo
 
 ICSetProp_Native *
 ICSetProp_Native::Compiler::getStub(ICStubSpace *space)
 {
     RootedObjectGroup group(cx, obj_->getGroup(cx));
     if (!group)
         return nullptr;
 
-    RootedShape shape(cx, obj_->lastProperty());
+    RootedShape shape(cx, obj_->as<NativeObject>().lastProperty());
     ICSetProp_Native *stub = ICStub::New<ICSetProp_Native>(space, getStubCode(), group, shape, offset_);
     if (!stub || !stub->initUpdatingChain(cx, space))
         return nullptr;
     return stub;
 }
 
 ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode *stubCode, ObjectGroup *group,
                                          size_t protoChainDepth,
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -2916,26 +2916,26 @@ class ICGetElemNativeCompiler : public I
         acctype_(acctype),
         needsAtomize_(needsAtomize),
         offset_(0),
         getter_(getter),
         pcOffset_(pcOffset)
     {}
 
     ICStub *getStub(ICStubSpace *space) {
-        RootedShape shape(cx, obj_->lastProperty());
+        RootedShape shape(cx, obj_->as<NativeObject>().lastProperty());
         if (kind == ICStub::GetElem_NativeSlot) {
             MOZ_ASSERT(obj_ == holder_);
             return ICStub::New<ICGetElem_NativeSlot>(
                     space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_,
                     offset_);
         }
 
         MOZ_ASSERT(obj_ != holder_);
-        RootedShape holderShape(cx, holder_->lastProperty());
+        RootedShape holderShape(cx, holder_->as<NativeObject>().lastProperty());
         if (kind == ICStub::GetElem_NativePrototypeSlot) {
             return ICStub::New<ICGetElem_NativePrototypeSlot>(
                     space, getStubCode(), firstMonitorStub_, shape, name_, acctype_, needsAtomize_,
                     offset_, holder_, holderShape);
         }
 
         if (kind == ICStub::GetElem_NativePrototypeCallNative) {
             return ICStub::New<ICGetElem_NativePrototypeCallNative>(
@@ -3807,17 +3807,17 @@ class ICGetProp_Primitive : public ICMon
             firstMonitorStub_(firstMonitorStub),
             primitiveType_(primitiveType),
             prototype_(cx, prototype),
             isFixedSlot_(isFixedSlot),
             offset_(offset)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
-            RootedShape protoShape(cx, prototype_->lastProperty());
+            RootedShape protoShape(cx, prototype_->as<NativeObject>().lastProperty());
             return ICStub::New<ICGetProp_Primitive>(space, getStubCode(), firstMonitorStub_,
                                                     protoShape, offset_);
         }
     };
 };
 
 // Stub for accessing a string's length.
 class ICGetProp_StringLength : public ICStub
@@ -3924,17 +3924,17 @@ class ReceiverGuard
         if (!(token & 1))
             return reinterpret_cast<ObjectGroup *>(token);
         return nullptr;
     }
 
     static Token objectToken(JSObject *obj) {
         if (obj->is<UnboxedPlainObject>())
             return groupToken(obj->group());
-        return shapeToken(obj->lastProperty());
+        return shapeToken(obj->maybeShape());
     }
 
     explicit ReceiverGuard(Token token)
       : shape_(tokenShape(token)), group_(tokenGroup(token))
     {
         MOZ_ASSERT(shape_ || group_);
     }
 
@@ -4416,17 +4416,17 @@ class ICGetProp_CallScripted : public IC
                  HandleObject holder, HandleFunction getter, uint32_t pcOffset)
           : ICGetPropCallPrototypeGetter::Compiler(cx, ICStub::GetProp_CallScripted,
                                                    firstMonitorStub, obj, holder,
                                                    getter, pcOffset, /* outerClass = */ nullptr)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
             ReceiverGuard::Token guard = ReceiverGuard::objectToken(receiver_);
-            Shape *holderShape = holder_->lastProperty();
+            Shape *holderShape = holder_->as<NativeObject>().lastProperty();
             return ICStub::New<ICGetProp_CallScripted>(space, getStubCode(), firstMonitorStub_,
                                                        guard, holder_, holderShape, getter_,
                                                        pcOffset_);
         }
     };
 };
 
 // Stub for calling an own native getter on a native object.
@@ -4462,17 +4462,17 @@ class ICGetProp_CallNative : public ICGe
                  HandleFunction getter, uint32_t pcOffset, const Class *outerClass,
                  bool inputDefinitelyObject = false)
           : ICGetPropCallGetter::Compiler(cx, ICStub::GetProp_CallNative, firstMonitorStub,
                                           obj, getter, pcOffset, outerClass, true),
             inputDefinitelyObject_(inputDefinitelyObject)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
-            RootedShape shape(cx, holder_->lastProperty());
+            RootedShape shape(cx, holder_->as<NativeObject>().lastProperty());
             return ICStub::New<ICGetProp_CallNative>(space, getStubCode(), firstMonitorStub_,
                                                      holder_, shape, getter_, pcOffset_);
         }
     };
 };
 
 // Stub for calling an native getter on a native object when the getter is kept on the proto-chain.
 class ICGetProp_CallNativePrototype : public ICGetPropCallPrototypeGetter
@@ -4509,17 +4509,17 @@ class ICGetProp_CallNativePrototype : pu
           : ICGetPropCallPrototypeGetter::Compiler(cx, ICStub::GetProp_CallNativePrototype,
                                                    firstMonitorStub, obj, holder,
                                                    getter, pcOffset, outerClass),
             inputDefinitelyObject_(inputDefinitelyObject)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
             ReceiverGuard::Token guard = ReceiverGuard::objectToken(receiver_);
-            Shape *holderShape = holder_->lastProperty();
+            Shape *holderShape = holder_->as<NativeObject>().lastProperty();
             return ICStub::New<ICGetProp_CallNativePrototype>(space, getStubCode(), firstMonitorStub_,
                                                               guard, holder_, holderShape,
                                                               getter_, pcOffset_);
         }
     };
 };
 
 class ICGetPropCallDOMProxyNativeStub : public ICGetPropCallGetter
@@ -4959,17 +4959,17 @@ class ICSetPropNativeAddCompiler : publi
             return nullptr;
 
         // Only specify newGroup when the object's group changes due to the
         // object becoming fully initialized per the acquired properties
         // analysis.
         if (newGroup == oldGroup_)
             newGroup = nullptr;
 
-        RootedShape newShape(cx, obj_->lastProperty());
+        RootedShape newShape(cx, obj_->as<NativeObject>().lastProperty());
 
         return ICStub::New<ICSetProp_NativeAddImpl<ProtoChainDepth>>(
                     space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_);
     }
 
     ICUpdatedStub *getStub(ICStubSpace *space);
 };
 
@@ -5225,17 +5225,17 @@ class ICSetProp_CallScripted : public IC
         Compiler(JSContext *cx, HandleObject obj, HandleObject holder, HandleFunction setter,
                  uint32_t pcOffset)
           : ICSetPropCallSetter::Compiler(cx, ICStub::SetProp_CallScripted,
                                           obj, holder, setter, pcOffset)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
             ReceiverGuard::Token guard = ReceiverGuard::objectToken(obj_);
-            Shape *holderShape = holder_->lastProperty();
+            Shape *holderShape = holder_->as<NativeObject>().lastProperty();
             return ICStub::New<ICSetProp_CallScripted>(space, getStubCode(), guard, holder_,
                                                        holderShape, setter_, pcOffset_);
         }
     };
 };
 
 // Stub for calling a native setter on a native object.
 class ICSetProp_CallNative : public ICSetPropCallSetter
@@ -5261,17 +5261,17 @@ class ICSetProp_CallNative : public ICSe
         Compiler(JSContext *cx, HandleObject obj, HandleObject holder, HandleFunction setter,
                  uint32_t pcOffset)
           : ICSetPropCallSetter::Compiler(cx, ICStub::SetProp_CallNative,
                                           obj, holder, setter, pcOffset)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
             ReceiverGuard::Token guard = ReceiverGuard::objectToken(obj_);
-            Shape *holderShape = holder_->lastProperty();
+            Shape *holderShape = holder_->as<NativeObject>().lastProperty();
             return ICStub::New<ICSetProp_CallNative>(space, getStubCode(), guard, holder_,
                                                      holderShape, setter_, pcOffset_);
         }
     };
 };
 
 // Call
 //      JSOP_CALL
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4594,17 +4594,17 @@ CodeGenerator::visitNewSingletonCallObje
     Register objReg = ToRegister(lir->output());
 
     JSObject *templateObj = lir->mir()->templateObject();
 
     JSScript *script = lir->mir()->block()->info().script();
     uint32_t lexicalBegin = script->bindings.aliasedBodyLevelLexicalBegin();
     OutOfLineCode *ool;
     ool = oolCallVM(NewSingletonCallObjectInfo, lir,
-                    (ArgList(), ImmGCPtr(templateObj->lastProperty()),
+                    (ArgList(), ImmGCPtr(templateObj->as<CallObject>().lastProperty()),
                                 Imm32(lexicalBegin)),
                     StoreRegisterTo(objReg));
 
     // Objects can only be given singleton types in VM calls.  We make the call
     // out of line to not bloat inline code, even if (naively) this seems like
     // extra work.
     masm.jump(ool->entry());
     masm.bind(ool->rejoin());
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1772,17 +1772,17 @@ OffThreadCompilationAvailable(JSContext 
         && CanUseExtraThreads();
 }
 
 static void
 TrackAllProperties(JSContext *cx, JSObject *obj)
 {
     MOZ_ASSERT(obj->isSingleton());
 
-    for (Shape::Range<NoGC> range(obj->lastProperty()); !range.empty(); range.popFront())
+    for (Shape::Range<NoGC> range(obj->as<NativeObject>().lastProperty()); !range.empty(); range.popFront())
         EnsureTrackPropertyTypes(cx, obj, range.front().propid());
 }
 
 static void
 TrackPropertiesForSingletonScopes(JSContext *cx, JSScript *script, BaselineFrame *baselineFrame)
 {
     // Ensure that all properties of singleton call objects which the script
     // could access are tracked. These are generally accessed through
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -770,17 +770,17 @@ CheckDOMProxyExpandoDoesNotShadow(JSCont
         MOZ_ASSERT(!expandoVal.toObject().as<NativeObject>().contains(cx, name));
 
         // Reference object has an expando object that doesn't define the name. Check that
         // the incoming object has an expando object with the same shape.
         masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck);
         masm.extractObject(tempVal, tempVal.scratchReg());
         masm.branchPtr(Assembler::Equal,
                        Address(tempVal.scratchReg(), JSObject::offsetOfShape()),
-                       ImmGCPtr(expandoVal.toObject().lastProperty()),
+                       ImmGCPtr(expandoVal.toObject().as<NativeObject>().lastProperty()),
                        &domProxyOk);
     }
 
     // Failure case: restore the tempVal registers and jump to failures.
     masm.bind(&failDOMProxyCheck);
     masm.popValue(tempVal);
     masm.jump(stubFailure);
 
@@ -805,17 +805,17 @@ GenerateReadSlot(JSContext *cx, IonScrip
     Label failures_;
     if (multipleFailureJumps && !failures)
         failures = &failures_;
 
     // Guard on the shape or type of the object, depending on whether it is native.
     if (obj->isNative()) {
         attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                        Address(object, JSObject::offsetOfShape()),
-                                       ImmGCPtr(obj->lastProperty()),
+                                       ImmGCPtr(obj->as<NativeObject>().lastProperty()),
                                        failures);
     } else {
         attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                        Address(object, JSObject::offsetOfGroup()),
                                        ImmGCPtr(obj->group()),
                                        failures);
     }
 
@@ -869,17 +869,17 @@ GenerateReadSlot(JSContext *cx, IonScrip
             Register lastReg = object;
             MOZ_ASSERT(scratchReg != object);
             while (proto) {
                 masm.loadObjProto(lastReg, scratchReg);
 
                 // Guard the shape of the current prototype.
                 masm.branchPtr(Assembler::NotEqual,
                                Address(scratchReg, JSObject::offsetOfShape()),
-                               ImmGCPtr(proto->lastProperty()),
+                               ImmGCPtr(proto->as<NativeObject>().lastProperty()),
                                &prototypeFailures);
 
                 proto = proto->getProto();
                 lastReg = scratchReg;
             }
 
             holderReg = InvalidReg;
         }
@@ -1093,35 +1093,38 @@ EmitGetterCall(JSContext *cx, MacroAssem
 
         masm.freeStack(masm.framePushed() - framePushedBefore);
     }
 
     masm.icRestoreLive(liveRegs, aic);
     return true;
 }
 
+static void
+TestMatchingReceiver(MacroAssembler &masm, Register object, JSObject *obj, Label *failure)
+{
+    if (Shape *shape = obj->maybeShape())
+        masm.branchTestObjShape(Assembler::NotEqual, object, shape, failure);
+    else
+        masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
+}
+
 static bool
 GenerateCallGetter(JSContext *cx, IonScript *ion, MacroAssembler &masm,
                    IonCache::StubAttacher &attacher, JSObject *obj, PropertyName *name,
                    JSObject *holder, HandleShape shape, RegisterSet &liveRegs, Register object,
                    TypedOrValueRegister output, void *returnAddr, Label *failures = nullptr)
 {
     MOZ_ASSERT(output.hasValue());
 
     // Use the passed in label if there was one. Otherwise, we'll have to make our own.
     Label stubFailure;
     failures = failures ? failures : &stubFailure;
 
-    // Initial shape/group check.
-    if (obj->isNative())
-        masm.branchTestObjShape(Assembler::NotEqual, object, obj->lastProperty(), failures);
-    else if (obj->is<UnboxedPlainObject>())
-        masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failures);
-    else
-        MOZ_CRASH("Unexpected object");
+    TestMatchingReceiver(masm, object, obj, failures);
 
     Register scratchReg = output.valueReg().scratchReg();
     bool spillObjReg = scratchReg == object;
     Label pop1AndFail;
     Label *maybePopAndFail = failures;
 
     // Save off the object register if it aliases the scratchReg
     if (spillObjReg) {
@@ -1133,17 +1136,17 @@ GenerateCallGetter(JSContext *cx, IonScr
     if (obj != holder)
         GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, failures);
 
     // Guard on the holder's shape.
     Register holderReg = scratchReg;
     masm.movePtr(ImmMaybeNurseryPtr(holder), holderReg);
     masm.branchPtr(Assembler::NotEqual,
                    Address(holderReg, JSObject::offsetOfShape()),
-                   ImmGCPtr(holder->lastProperty()),
+                   ImmGCPtr(holder->as<NativeObject>().lastProperty()),
                    maybePopAndFail);
 
     if (spillObjReg)
         masm.pop(object);
 
     // Now we're good to go to invoke the native call.
     if (!EmitGetterCall(cx, masm, attacher, obj, holder, shape, liveRegs, object,
                         output, returnAddr))
@@ -1167,17 +1170,17 @@ static bool
 GenerateArrayLength(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
                     JSObject *obj, Register object, TypedOrValueRegister output)
 {
     MOZ_ASSERT(obj->is<ArrayObject>());
 
     Label failures;
 
     // Guard object is a dense array.
-    RootedShape shape(cx, obj->lastProperty());
+    RootedShape shape(cx, obj->as<ArrayObject>().lastProperty());
     if (!shape)
         return false;
     masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
 
     // Load length.
     Register outReg;
     if (output.hasValue()) {
         outReg = output.valueReg().scratchReg();
@@ -1551,17 +1554,17 @@ GetPropertyIC::tryAttachDOMProxyShadowed
 
     Label failures;
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     RepatchStubAppender attacher(*this);
 
     // Guard on the shape of the object.
     attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                    Address(object(), JSObject::offsetOfShape()),
-                                   ImmGCPtr(obj->lastProperty()),
+                                   ImmGCPtr(obj->maybeShape()),
                                    &failures);
 
     // No need for more guards: we know this is a DOM proxy, since the shape
     // guard enforces a given JSClass, so just go ahead and emit the call to
     // ProxyGet.
 
     if (!EmitCallProxyGet(cx, masm, attacher, name(), liveRegs_, object(), output(),
                           pc(), returnAddr))
@@ -1620,17 +1623,17 @@ GetPropertyIC::tryAttachDOMProxyUnshadow
 
     Label failures;
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     RepatchStubAppender attacher(*this);
 
     // Guard on the shape of the object.
     attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                    Address(object(), JSObject::offsetOfShape()),
-                                   ImmGCPtr(obj->lastProperty()),
+                                   ImmGCPtr(obj->maybeShape()),
                                    &failures);
 
     // Guard that our expando object hasn't started shadowing this property.
     CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, name, object(), &failures);
 
     if (holder) {
         // Found the property on the prototype chain. Treat it like a native
         // getprop.
@@ -2234,17 +2237,17 @@ SetPropertyIC::attachDOMProxyShadowed(JS
 
     Label failures;
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     RepatchStubAppender attacher(*this);
 
     // Guard on the shape of the object.
     masm.branchPtr(Assembler::NotEqual,
                    Address(object(), JSObject::offsetOfShape()),
-                   ImmGCPtr(obj->lastProperty()), &failures);
+                   ImmGCPtr(obj->maybeShape()), &failures);
 
     // No need for more guards: we know this is a DOM proxy, since the shape
     // guard enforces a given JSClass, so just go ahead and emit the call to
     // ProxySet.
 
     RootedId propId(cx, AtomToId(name()));
     if (!EmitCallProxySet(cx, masm, attacher, propId, liveRegs_, object(),
                           value(), returnAddr, strict()))
@@ -2284,17 +2287,17 @@ GenerateCallSetter(JSContext *cx, IonScr
 
         // Generate prototype/shape guards.
         if (obj != holder)
             GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, &protoFailure);
 
         masm.movePtr(ImmMaybeNurseryPtr(holder), scratchReg);
         masm.branchPtr(Assembler::NotEqual,
                        Address(scratchReg, JSObject::offsetOfShape()),
-                       ImmGCPtr(holder->lastProperty()),
+                       ImmGCPtr(holder->as<NativeObject>().lastProperty()),
                        &protoFailure);
 
         masm.jump(&protoSuccess);
 
         masm.bind(&protoFailure);
         masm.pop(scratchReg);
         masm.jump(failure);
 
@@ -2508,17 +2511,17 @@ SetPropertyIC::attachDOMProxyUnshadowed(
 
     Label failures;
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     RepatchStubAppender attacher(*this);
 
     // Guard on the shape of the object.
     masm.branchPtr(Assembler::NotEqual,
                    Address(object(), JSObject::offsetOfShape()),
-                   ImmGCPtr(obj->lastProperty()), &failures);
+                   ImmGCPtr(obj->maybeShape()), &failures);
 
     // Guard that our expando object hasn't started shadowing this property.
     CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, name(), object(), &failures);
 
     RootedPropertyName propName(cx, name());
     RootedObject holder(cx);
     RootedShape shape(cx);
     bool isSetter;
@@ -2559,22 +2562,17 @@ bool
 SetPropertyIC::attachCallSetter(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                 HandleObject obj, HandleObject holder, HandleShape shape,
                                 void *returnAddr)
 {
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     RepatchStubAppender attacher(*this);
 
     Label failure;
-    if (obj->isNative())
-        masm.branchTestObjShape(Assembler::NotEqual, object(), obj->lastProperty(), &failure);
-    else if (obj->is<UnboxedPlainObject>())
-        masm.branchTestObjGroup(Assembler::NotEqual, object(), obj->group(), &failure);
-    else
-        MOZ_CRASH("Unexpected object");
+    TestMatchingReceiver(masm, object(), obj, &failure);
 
     if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
                             object(), value(), &failure, liveRegs_, returnAddr))
     {
         return false;
     }
 
     // Rejoin jump.
@@ -2612,17 +2610,17 @@ GenerateAddSlot(JSContext *cx, MacroAsse
     if (checkTypeset) {
         CheckTypeSetForWrite(masm, obj, obj->lastProperty()->propid(), object, value, &failuresPopObject);
         masm.loadPtr(Address(StackPointer, 0), object);
     }
 
     JSObject *proto = obj->getProto();
     Register protoReg = object;
     while (proto) {
-        Shape *protoShape = proto->lastProperty();
+        Shape *protoShape = proto->as<NativeObject>().lastProperty();
 
         // load next prototype
         masm.loadObjProto(protoReg, protoReg);
 
         // Ensure that its shape matches.
         masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, &failuresPopObject);
 
         proto = proto->getProto();
@@ -3021,17 +3019,17 @@ SetPropertyIC::update(JSContext *cx, Han
             {
                 return false;
             }
             addedSetterStub = true;
         }
     }
 
     uint32_t oldSlots = obj->is<NativeObject>() ? obj->as<NativeObject>().numDynamicSlots() : 0;
-    RootedShape oldShape(cx, obj->lastProperty());
+    RootedShape oldShape(cx, obj->maybeShape());
 
     // Set/Add the property on the object, the inlined cache are setup for the next execution.
     if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
         return false;
 
     // The property did not exist before, now we can try to inline the property add.
     bool checkTypeset;
     if (!addedSetterStub && canCache == MaybeCanAttachAddSlot &&
@@ -3190,17 +3188,17 @@ GenerateDenseElement(JSContext *cx, Macr
                      JSObject *obj, const Value &idval, Register object,
                      ConstantOrRegister index, TypedOrValueRegister output)
 {
     MOZ_ASSERT(GetElementIC::canAttachDenseElement(obj, idval));
 
     Label failures;
 
     // Guard object's shape.
-    RootedShape shape(cx, obj->lastProperty());
+    RootedShape shape(cx, obj->as<NativeObject>().lastProperty());
     if (!shape)
         return false;
     masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
 
     // Ensure the index is an int32 value.
     Register indexReg = InvalidReg;
 
     if (index.reg().hasValue()) {
@@ -3298,48 +3296,47 @@ GetElementIC::canAttachDenseElementHole(
 }
 
 static bool
 GenerateDenseElementHole(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
                          IonScript *ion, JSObject *obj, const Value &idval,
                          Register object, ConstantOrRegister index, TypedOrValueRegister output)
 {
     MOZ_ASSERT(GetElementIC::canAttachDenseElementHole(obj, idval, output));
-    MOZ_ASSERT(obj->lastProperty());
 
     Register scratchReg = output.valueReg().scratchReg();
 
     // Guard on the shape and group, to prevent non-dense elements from appearing.
     Label failures;
     attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                    Address(object, JSObject::offsetOfShape()),
-                                   ImmGCPtr(obj->lastProperty()), &failures);
+                                   ImmGCPtr(obj->as<NativeObject>().lastProperty()), &failures);
 
 
     if (obj->hasUncacheableProto()) {
         masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg);
         Address proto(scratchReg, ObjectGroup::offsetOfProto());
         masm.branchPtr(Assembler::NotEqual, proto,
                        ImmMaybeNurseryPtr(obj->getProto()), &failures);
     }
 
     JSObject *pobj = obj->getProto();
     while (pobj) {
-        MOZ_ASSERT(pobj->lastProperty());
+        MOZ_ASSERT(pobj->as<NativeObject>().lastProperty());
 
         masm.movePtr(ImmMaybeNurseryPtr(pobj), scratchReg);
         if (pobj->hasUncacheableProto()) {
             MOZ_ASSERT(!pobj->isSingleton());
             Address groupAddr(scratchReg, JSObject::offsetOfGroup());
             masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures);
         }
 
         // Make sure the shape matches, to avoid non-dense elements.
         masm.branchPtr(Assembler::NotEqual, Address(scratchReg, JSObject::offsetOfShape()),
-                       ImmGCPtr(pobj->lastProperty()), &failures);
+                       ImmGCPtr(pobj->as<NativeObject>().lastProperty()), &failures);
 
         // Load elements vector.
         masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg);
 
         // Also make sure there are no dense elements.
         Label hole;
         Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
         masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures);
@@ -3867,17 +3864,17 @@ GenerateSetDenseElement(JSContext *cx, M
     MOZ_ASSERT(idval.isInt32());
 
     Label failures;
     Label outOfBounds; // index represents a known hole, or an illegal append
 
     Label markElem, storeElement; // used if TI protects us from worrying about holes.
 
     // Guard object is a dense array.
-    Shape *shape = obj->lastProperty();
+    Shape *shape = obj->as<NativeObject>().lastProperty();
     if (!shape)
         return false;
     masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
 
     // Ensure the index is an int32 value.
     masm.branchTestInt32(Assembler::NotEqual, indexVal, &failures);
 
     // Unbox the index.
@@ -4143,17 +4140,18 @@ GenerateScopeChainGuard(MacroAssembler &
         // If this is the last object on the scope walk, and the property we've
         // found is not configurable, then we don't need a shape guard because
         // the shape cannot be removed.
         if (shape && !shape->configurable())
             return;
     }
 
     Address shapeAddr(scopeObjReg, JSObject::offsetOfShape());
-    masm.branchPtr(Assembler::NotEqual, shapeAddr, ImmGCPtr(scopeObj->lastProperty()), failures);
+    masm.branchPtr(Assembler::NotEqual, shapeAddr,
+                   ImmGCPtr(scopeObj->as<NativeObject>().lastProperty()), failures);
 }
 
 static void
 GenerateScopeChainGuards(MacroAssembler &masm, JSObject *scopeChain, JSObject *holder,
                          Register outputReg, Label *failures, bool skipLastGuard = false)
 {
     JSObject *tobj = scopeChain;
 
@@ -4184,17 +4182,17 @@ BindNameIC::attachNonGlobal(JSContext *c
 
     MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
     RepatchStubAppender attacher(*this);
 
     // Guard on the shape of the scope chain.
     Label failures;
     attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
                                    Address(scopeChainReg(), JSObject::offsetOfShape()),
-                                   ImmGCPtr(scopeChain->lastProperty()),
+                                   ImmGCPtr(scopeChain->as<NativeObject>().lastProperty()),
                                    holder != scopeChain ? &failures : nullptr);
 
     if (holder != scopeChain) {
         JSObject *parent = &scopeChain->as<ScopeObject>().enclosingScope();
         masm.extractObject(Address(scopeChainReg(), ScopeObject::offsetOfEnclosingScope()), outputReg());
 
         GenerateScopeChainGuards(masm, parent, holder, outputReg(), &failures);
     } else {
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1214,19 +1214,21 @@ MacroAssembler::initGCSlots(Register obj
 }
 
 void
 MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj,
                             bool initFixedSlots)
 {
     // Fast initialization of an empty object returned by allocateObject().
 
-    storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape()));
     storePtr(ImmGCPtr(templateObj->group()), Address(obj, JSObject::offsetOfGroup()));
 
+    if (Shape *shape = templateObj->maybeShape())
+        storePtr(ImmGCPtr(shape), Address(obj, JSObject::offsetOfShape()));
+
     if (templateObj->isNative()) {
         NativeObject *ntemplate = &templateObj->as<NativeObject>();
         MOZ_ASSERT_IF(!ntemplate->denseElementsAreCopyOnWrite(), !ntemplate->hasDynamicElements());
 
         if (ntemplate->hasDynamicSlots())
             storePtr(slots, Address(obj, NativeObject::offsetOfSlots()));
         else
             storePtr(ImmPtr(nullptr), Address(obj, NativeObject::offsetOfSlots()));
@@ -1275,16 +1277,18 @@ MacroAssembler::initGCThing(Register obj
             storePtr(ImmWord(value),
                      Address(obj, InlineTypedObject::offsetOfDataStart() + offset));
             nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t);
             offset += sizeof(uintptr_t);
         }
     } else if (templateObj->is<UnboxedPlainObject>()) {
         const UnboxedLayout &layout = templateObj->as<UnboxedPlainObject>().layout();
 
+        storePtr(ImmWord(0), Address(obj, JSObject::offsetOfShape()));
+
         // Initialize reference fields of the object, per UnboxedPlainObject::create.
         if (const int32_t *list = layout.traceList()) {
             while (*list != -1) {
                 storePtr(ImmGCPtr(GetJitContext()->runtime->names().empty),
                          Address(obj, UnboxedPlainObject::offsetOfData() + *list));
                 list++;
             }
             list++;
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -163,17 +163,17 @@ IsObjectEscaped(MInstruction *ins, JSObj
             }
 #endif
             break;
           }
 
           case MDefinition::Op_GuardShape: {
             MGuardShape *guard = def->toGuardShape();
             MOZ_ASSERT(!ins->isGuardShape());
-            if (obj->lastProperty() != guard->shape()) {
+            if (obj->as<NativeObject>().lastProperty() != guard->shape()) {
                 JitSpewDef(JitSpew_Escape, "Object ", ins);
                 JitSpewDef(JitSpew_Escape, "  has a non-matching guard shape\n", guard);
                 return true;
             }
             if (IsObjectEscaped(def->toInstruction(), obj))
                 return true;
             break;
           }
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1138,18 +1138,18 @@ AutoDetectInvalidation::setReturnOverrid
 void
 AssertValidObjectPtr(JSContext *cx, JSObject *obj)
 {
     // Check what we can, so that we'll hopefully assert/crash if we get a
     // bogus object (pointer).
     MOZ_ASSERT(obj->compartment() == cx->compartment());
     MOZ_ASSERT(obj->runtimeFromMainThread() == cx->runtime());
 
-    MOZ_ASSERT_IF(!obj->hasLazyGroup(),
-                  obj->group()->clasp() == obj->lastProperty()->getObjectClass());
+    MOZ_ASSERT_IF(!obj->hasLazyGroup() && obj->maybeShape(),
+                  obj->group()->clasp() == obj->maybeShape()->getObjectClass());
 
     if (obj->isTenured()) {
         MOZ_ASSERT(obj->isAligned());
         gc::AllocKind kind = obj->asTenured().getAllocKind();
         MOZ_ASSERT(kind >= js::gc::FINALIZE_OBJECT0 && kind <= js::gc::FINALIZE_OBJECT_LAST);
         MOZ_ASSERT(obj->asTenured().zone() == cx->zone());
     }
 }
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2749,17 +2749,17 @@ GetIndexedPropertiesInRange(JSContext *c
             for (uint32_t i = begin; i < len && i < end; i++) {
                 if (!indexes.append(i))
                     return false;
             }
         }
 
         // Append sparse elements.
         if (pobj->isIndexed()) {
-            Shape::Range<NoGC> r(pobj->lastProperty());
+            Shape::Range<NoGC> r(pobj->as<NativeObject>().lastProperty());
             for (; !r.empty(); r.popFront()) {
                 Shape &shape = r.front();
                 jsid id = shape.propid();
                 if (!JSID_IS_INT(id))
                     continue;
 
                 uint32_t i = uint32_t(JSID_TO_INT(id));
                 if (!(begin <= i && i < end))
@@ -3530,17 +3530,17 @@ js::NewDenseCopiedArray(JSContext *cx, u
 ArrayObject *
 js::NewDenseFullyAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject)
 {
     gc::AllocKind allocKind = GuessArrayGCKind(length);
     MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
 
     RootedObjectGroup group(cx, templateObject->group());
-    RootedShape shape(cx, templateObject->lastProperty());
+    RootedShape shape(cx, templateObject->as<ArrayObject>().lastProperty());
 
     gc::InitialHeap heap = GetInitialHeap(GenericObject, &ArrayObject::class_);
     Rooted<ArrayObject *> arr(cx, ArrayObject::createArray(cx, allocKind,
                                                            heap, shape, group, length));
     if (!arr)
         return nullptr;
 
     if (!EnsureNewArrayElements(cx, arr, length))
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -316,20 +316,20 @@ js::IsScopeObject(JSObject *obj)
 }
 
 JS_FRIEND_API(bool)
 js::IsCallObject(JSObject *obj)
 {
     return obj->is<CallObject>();
 }
 
-JS_FRIEND_API(JSObject *)
-js::GetObjectParentMaybeScope(JSObject *obj)
+JS_FRIEND_API(bool)
+js::CanAccessObjectShape(JSObject *obj)
 {
-    return obj->enclosingScope();
+    return obj->maybeShape() != nullptr;
 }
 
 JS_FRIEND_API(JSObject *)
 js::GetGlobalForObjectCrossCompartment(JSObject *obj)
 {
     return &obj->global();
 }
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -558,18 +558,19 @@ class Shape {
 public:
     shadow::BaseShape *base;
     jsid              _1;
     uint32_t          slotInfo;
 
     static const uint32_t FIXED_SLOTS_SHIFT = 27;
 };
 
-// This layout is shared by all objects except for Typed Objects (which still
-// have a shape and group).
+// This layout is shared by all native objects. For non-native objects, the
+// group may always be accessed safely, and other members may be as well,
+// depending on the object's specific layout.
 struct Object {
     shadow::ObjectGroup *group;
     shadow::Shape       *shape;
     JS::Value           *slots;
     void                *_1;
 
     size_t numFixedSlots() const { return shape->slotInfo >> Shape::FIXED_SLOTS_SHIFT; }
     JS::Value *fixedSlots() const {
@@ -678,33 +679,34 @@ JS_FRIEND_API(bool)
 IsFunctionObject(JSObject *obj);
 
 JS_FRIEND_API(bool)
 IsScopeObject(JSObject *obj);
 
 JS_FRIEND_API(bool)
 IsCallObject(JSObject *obj);
 
+JS_FRIEND_API(bool)
+CanAccessObjectShape(JSObject *obj);
+
 inline JSObject *
 GetObjectParent(JSObject *obj)
 {
     MOZ_ASSERT(!IsScopeObject(obj));
+    MOZ_ASSERT(CanAccessObjectShape(obj));
     return reinterpret_cast<shadow::Object*>(obj)->shape->base->parent;
 }
 
 static MOZ_ALWAYS_INLINE JSCompartment *
 GetObjectCompartment(JSObject *obj)
 {
     return reinterpret_cast<shadow::Object*>(obj)->group->compartment;
 }
 
 JS_FRIEND_API(JSObject *)
-GetObjectParentMaybeScope(JSObject *obj);
-
-JS_FRIEND_API(JSObject *)
 GetGlobalForObjectCrossCompartment(JSObject *obj);
 
 JS_FRIEND_API(JSObject *)
 GetPrototypeNoProxy(JSObject *obj);
 
 // Sidestep the activeContext checking implicitly performed in
 // JS_SetPendingException.
 JS_FRIEND_API(void)
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -505,18 +505,18 @@ CheckIncrementalZoneState(ExclusiveConte
 template <AllowGC allowGC>
 inline JSObject *
 AllocateObject(ExclusiveContext *cx, AllocKind kind, size_t nDynamicSlots, InitialHeap heap,
                const js::Class *clasp)
 {
     size_t thingSize = Arena::thingSize(kind);
 
     MOZ_ASSERT(thingSize == Arena::thingSize(kind));
-    MOZ_ASSERT(thingSize >= sizeof(JSObject));
-    static_assert(sizeof(JSObject) >= CellSize,
+    MOZ_ASSERT(thingSize >= sizeof(JSObject_Slots0));
+    static_assert(sizeof(JSObject_Slots0) >= CellSize,
                   "All allocations must be at least the allocator-imposed minimum size.");
 
     if (!CheckAllocatorState<allowGC>(cx, kind))
         return nullptr;
 
     if (cx->isJSContext() &&
         ShouldNurseryAllocateObject(cx->asJSContext()->nursery(), heap))
     {
@@ -574,41 +574,43 @@ AllocateNonObject(ExclusiveContext *cx)
 /*
  * When allocating for initialization from a cached object copy, we will
  * potentially destroy the cache entry we want to copy if we allow GC. On the
  * other hand, since these allocations are extremely common, we don't want to
  * delay GC from these allocation sites. Instead we allow the GC, but still
  * fail the allocation, forcing the non-cached path.
  */
 template <AllowGC allowGC>
-inline JSObject *
+inline NativeObject *
 AllocateObjectForCacheHit(JSContext *cx, AllocKind kind, InitialHeap heap, const js::Class *clasp)
 {
+    MOZ_ASSERT(clasp->isNative());
+
     if (ShouldNurseryAllocateObject(cx->nursery(), heap)) {
         size_t thingSize = Arena::thingSize(kind);
 
         MOZ_ASSERT(thingSize == Arena::thingSize(kind));
         if (!CheckAllocatorState<NoGC>(cx, kind))
             return nullptr;
 
         JSObject *obj = TryNewNurseryObject<NoGC>(cx, thingSize, 0, clasp);
         if (!obj && allowGC) {
             cx->minorGC(JS::gcreason::OUT_OF_NURSERY);
             return nullptr;
         }
-        return obj;
+        return reinterpret_cast<NativeObject *>(obj);
     }
 
     JSObject *obj = AllocateObject<NoGC>(cx, kind, 0, heap, clasp);
     if (!obj && allowGC) {
         cx->runtime()->gc.maybeGC(cx->zone());
         return nullptr;
     }
 
-    return obj;
+    return reinterpret_cast<NativeObject *>(obj);
 }
 
 inline bool
 IsInsideGGCNursery(const js::gc::Cell *cell)
 {
     if (!cell)
         return false;
     uintptr_t addr = uintptr_t(cell);
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -595,17 +595,17 @@ VectorToKeyIterator(JSContext *cx, Handl
          * computed for the cache lookup earlier, as constructing iterobj could
          * have triggered a shape-regenerating GC.  Don't bother with regenerating
          * the shape key; if such a GC *does* occur, we can only get hits through
          * the one-slot lastNativeIterator cache.
          */
         JSObject *pobj = obj;
         size_t ind = 0;
         do {
-            ni->shapes_array[ind++] = pobj->lastProperty();
+            ni->shapes_array[ind++] = pobj->as<NativeObject>().lastProperty();
             pobj = pobj->getProto();
         } while (pobj);
         MOZ_ASSERT(ind == slength);
     }
 
     iterobj->setNativeIterator(ni);
     objp.set(iterobj);
 
@@ -706,22 +706,22 @@ js::GetIterator(JSContext *cx, HandleObj
          * will result in a miss.
          */
         PropertyIteratorObject *last = cx->runtime()->nativeIterCache.last;
         if (last) {
             NativeIterator *lastni = last->getNativeIterator();
             if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
                 obj->isNative() &&
                 obj->as<NativeObject>().hasEmptyElements() &&
-                obj->lastProperty() == lastni->shapes_array[0])
+                obj->as<NativeObject>().lastProperty() == lastni->shapes_array[0])
             {
                 JSObject *proto = obj->getProto();
                 if (proto->isNative() &&
                     proto->as<NativeObject>().hasEmptyElements() &&
-                    proto->lastProperty() == lastni->shapes_array[1] &&
+                    proto->as<NativeObject>().lastProperty() == lastni->shapes_array[1] &&
                     !proto->getProto())
                 {
                     objp.set(last);
                     UpdateNativeIterator(lastni, obj);
                     RegisterEnumerator(cx, last, lastni);
                     return true;
                 }
             }
@@ -742,17 +742,17 @@ js::GetIterator(JSContext *cx, HandleObj
                     pobj->hasUncacheableProto() ||
                     pobj->getOps()->enumerate ||
                     pobj->getClass()->enumerate ||
                     pobj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
                 {
                     shapes.clear();
                     goto miss;
                 }
-                Shape *shape = pobj->lastProperty();
+                Shape *shape = pobj->as<NativeObject>().lastProperty();
                 key = (key + (key << 16)) ^ (uintptr_t(shape) >> 3);
                 if (!shapes.append(shape))
                     return false;
                 pobj = pobj->getProto();
             } while (pobj);
         }
 
         PropertyIteratorObject *iterobj = cx->runtime()->nativeIterCache.get(key);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2208,17 +2208,17 @@ js::CloneObjectLiteral(JSContext *cx, Ha
         if (!group)
             return nullptr;
 
         RootedPlainObject res(cx, NewObjectWithGroup<PlainObject>(cx, group, parent, kind,
                                                                   MaybeSingletonObject));
         if (!res)
             return nullptr;
 
-        RootedShape newShape(cx, ReshapeForParentAndAllocKind(cx, srcObj->lastProperty(),
+        RootedShape newShape(cx, ReshapeForParentAndAllocKind(cx, srcObj->as<PlainObject>().lastProperty(),
                                                               TaggedProto(proto), parent, kind));
         if (!newShape || !res->setLastProperty(cx, newShape))
             return nullptr;
 
         return res;
     }
 
     RootedArrayObject srcArray(cx, &srcObj->as<ArrayObject>());
@@ -2289,17 +2289,17 @@ NativeObject::fillInAfterSwap(JSContext 
 }
 
 void
 JSObject::fixDictionaryShapeAfterSwap()
 {
     // Dictionary shapes can point back to their containing objects, so after
     // swapping the guts of those objects fix the pointers up.
     if (isNative() && as<NativeObject>().inDictionaryMode())
-        shape_->listp = &shape_;
+        as<NativeObject>().shape_->listp = &as<NativeObject>().shape_;
 }
 
 /* Use this method with extreme caution. It trades the guts of two objects. */
 bool
 JSObject::swap(JSContext *cx, HandleObject a, HandleObject b)
 {
     // Ensure swap doesn't cause a finalizer to not be run.
     MOZ_ASSERT(IsBackgroundFinalized(a->asTenured().getAllocKind()) ==
@@ -3655,17 +3655,17 @@ js::GetObjectSlotName(JSTracer *trc, cha
 {
     MOZ_ASSERT(trc->debugPrinter() == GetObjectSlotName);
 
     JSObject *obj = (JSObject *)trc->debugPrintArg();
     uint32_t slot = uint32_t(trc->debugPrintIndex());
 
     Shape *shape;
     if (obj->isNative()) {
-        shape = obj->lastProperty();
+        shape = obj->as<NativeObject>().lastProperty();
         while (shape && (!shape->hasSlot() || shape->slot() != slot))
             shape = shape->previous();
     } else {
         shape = nullptr;
     }
 
     if (!shape) {
         do {
@@ -3932,17 +3932,17 @@ JSObject::dump()
         fprintf(stderr, "= ");
         dumpValue(obj->as<NativeObject>().getSlot(i));
         fputc('\n', stderr);
     }
 
     if (obj->isNative()) {
         fprintf(stderr, "properties:\n");
         Vector<Shape *, 8, SystemAllocPolicy> props;
-        for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront())
+        for (Shape::Range<NoGC> r(obj->as<NativeObject>().lastProperty()); !r.empty(); r.popFront())
             props.append(&r.front());
         for (size_t i = props.length(); i-- != 0;)
             DumpProperty(&obj->as<NativeObject>(), *props[i]);
     }
     fputc('\n', stderr);
 }
 
 static void
@@ -4112,24 +4112,25 @@ JSObject::addSizeOfExcludingThis(mozilla
     }
 }
 
 void
 JSObject::markChildren(JSTracer *trc)
 {
     MarkObjectGroup(trc, &group_, "group");
 
-    MarkShape(trc, &shape_, "shape");
-
     const Class *clasp = group_->clasp();
     if (clasp->trace)
         clasp->trace(trc, this);
 
-    if (shape_->isNative()) {
+    if (clasp->isNative()) {
         NativeObject *nobj = &as<NativeObject>();
+
+        MarkShape(trc, &nobj->shape_, "shape");
+
         MarkObjectSlots(trc, nobj, 0, nobj->slotSpan());
 
         do {
             if (nobj->denseElementsAreCopyOnWrite()) {
                 HeapPtrNativeObject &owner = nobj->getElementsHeader()->ownerObject();
                 if (owner != nobj) {
                     MarkObject(trc, &owner, "objectElementsOwner");
                     break;
@@ -4138,8 +4139,21 @@ JSObject::markChildren(JSTracer *trc)
 
             gc::MarkArraySlots(trc,
                                nobj->getDenseInitializedLength(),
                                nobj->getDenseElementsAllowCopyOnWrite(),
                                "objectElements");
         } while (false);
     }
 }
+
+JSObject *
+JSObject::getParent() const
+{
+    if (Shape *shape = maybeShape())
+        return shape->getObjectParent();
+
+    // Avoid the parent-link checking in JSObject::global. Unboxed plain
+    // objects keep their compartment's global alive through their layout, and
+    // don't need a read barrier here.
+    MOZ_ASSERT(is<UnboxedPlainObject>());
+    return compartment()->unsafeUnbarrieredMaybeGlobal();
+}
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -87,52 +87,48 @@ class StrictArgumentsObject;
 bool PreventExtensions(JSContext *cx, JS::HandleObject obj, bool *succeeded);
 bool SetImmutablePrototype(js::ExclusiveContext *cx, JS::HandleObject obj, bool *succeeded);
 
 }  /* namespace js */
 
 /*
  * A JavaScript object. The members common to all objects are as follows:
  *
- * - The |shape_| member stores the shape of the object, which includes the
- *   object's class and the layout of all its properties.
- *
  * - The |group_| member stores the group of the object, which contains its
- *   prototype object and the possible types of its properties.
+ *   prototype object, its class and the possible types of its properties.
  *
  * Subclasses of JSObject --- mainly NativeObject and JSFunction --- add more
- * members.
+ * members. Notable among these is the object's shape, which stores flags and
+ * some other state, and, for native objects, the layout of all its properties.
+ * The second word of a JSObject generally stores its shape; if the second word
+ * stores anything else, the value stored cannot be a valid Shape* pointer, so
+ * that shape guards can be performed on objects without regard to the specific
+ * layout in use.
  */
 class JSObject : public js::gc::Cell
 {
   protected:
     js::HeapPtrObjectGroup group_;
-    js::HeapPtrShape shape_;
 
   private:
     friend class js::Shape;
     friend class js::GCMarker;
     friend class js::NewObjectCache;
     friend class js::Nursery;
     friend class js::gc::RelocationOverlay;
     friend bool js::PreventExtensions(JSContext *cx, JS::HandleObject obj, bool *succeeded);
     friend bool js::SetImmutablePrototype(js::ExclusiveContext *cx, JS::HandleObject obj,
                                           bool *succeeded);
 
     // Make a new group to use for a singleton object.
     static js::ObjectGroup *makeLazyGroup(JSContext *cx, js::HandleObject obj);
 
   public:
-    js::Shape * lastProperty() const {
-        MOZ_ASSERT(shape_);
-        return shape_;
-    }
-
     bool isNative() const {
-        return lastProperty()->isNative();
+        return getClass()->isNative();
     }
 
     const js::Class *getClass() const {
         return group_->clasp();
     }
     const JSClass *getJSClass() const {
         return Jsvalify(getClass());
     }
@@ -167,67 +163,70 @@ class JSObject : public js::gc::Cell
     bool hasLazyGroup() const {
         return group_->lazy();
     }
 
     JSCompartment *compartment() const {
         return group_->compartment();
     }
 
+    inline js::Shape *maybeShape() const;
+    inline js::Shape *ensureShape(js::ExclusiveContext *cx);
+
     /*
      * Make a non-array object with the specified initial state. This method
      * takes ownership of any extantSlots it is passed.
      */
     static inline JSObject *create(js::ExclusiveContext *cx,
                                    js::gc::AllocKind kind,
                                    js::gc::InitialHeap heap,
                                    js::HandleShape shape,
                                    js::HandleObjectGroup group);
 
+    // Set the shape of an object. This pointer is valid for native objects and
+    // some non-native objects. After creating an object, tobjects for which
+    // the shape pointer is invalid need to overwrite this pointer before a GC
+    // can occur.
+    inline void setInitialShapeMaybeNonNative(js::Shape *shape);
+    inline void setShapeMaybeNonNative(js::Shape *shape);
+
     // Set the initial slots and elements of an object. These pointers are only
     // valid for native objects, but during initialization are set for all
     // objects. For non-native objects, these must not be dynamically allocated
     // pointers which leak when the non-native object finishes initialization.
     inline void setInitialSlotsMaybeNonNative(js::HeapSlot *slots);
     inline void setInitialElementsMaybeNonNative(js::HeapSlot *elements);
 
     enum GenerateShape {
         GENERATE_NONE,
         GENERATE_SHAPE
     };
 
-    bool setFlags(js::ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flags,
+    bool setFlags(js::ExclusiveContext *cx, js::BaseShape::Flag flags,
                   GenerateShape generateShape = GENERATE_NONE);
+    inline bool hasAllFlags(js::BaseShape::Flag flags) const;
 
     /*
      * An object is a delegate if it is on another object's prototype or scope
      * chain, and therefore the delegate might be asked implicitly to get or
      * set a property on behalf of another object. Delegates may be accessed
      * directly too, as may any object, but only those objects linked after the
      * head of any prototype or scope chain are flagged as delegates. This
      * definition helps to optimize shape-based property cache invalidation
      * (see Purge{Scope,Proto}Chain in jsobj.cpp).
      */
-    bool isDelegate() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::DELEGATE);
-    }
-
+    inline bool isDelegate() const;
     bool setDelegate(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::DELEGATE, GENERATE_SHAPE);
     }
 
-    bool isBoundFunction() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::BOUND_FUNCTION);
-    }
-
+    inline bool isBoundFunction() const;
     inline bool hasSpecialEquality() const;
 
-    bool watched() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::WATCHED);
-    }
+    inline bool watched() const;
     bool setWatched(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::WATCHED, GENERATE_SHAPE);
     }
 
     /* See InterpreterFrame::varObj. */
     inline bool isQualifiedVarObj();
     bool setQualifiedVarObj(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::QUALIFIED_VAROBJ);
@@ -239,49 +238,35 @@ class JSObject : public js::gc::Cell
     }
 
     /*
      * Objects with an uncacheable proto can have their prototype mutated
      * without inducing a shape change on the object. Property cache entries
      * and JIT inline caches should not be filled for lookups across prototype
      * lookups on the object.
      */
-    bool hasUncacheableProto() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::UNCACHEABLE_PROTO);
-    }
+    inline bool hasUncacheableProto() const;
     bool setUncacheableProto(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::UNCACHEABLE_PROTO, GENERATE_SHAPE);
     }
 
     /*
      * Whether SETLELEM was used to access this object. See also the comment near
      * PropertyTree::MAX_HEIGHT.
      */
-    bool hadElementsAccess() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::HAD_ELEMENTS_ACCESS);
-    }
+    inline bool hadElementsAccess() const;
     bool setHadElementsAccess(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::HAD_ELEMENTS_ACCESS);
     }
 
     /*
      * Whether there may be indexed properties on this object, excluding any in
      * the object's elements.
      */
-    bool isIndexed() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::INDEXED);
-    }
-
-    uint32_t propertyCount() const {
-        return lastProperty()->entryCount();
-    }
-
-    bool hasShapeTable() const {
-        return lastProperty()->hasTable();
-    }
+    inline bool isIndexed() const;
 
     /* GC support. */
 
     void markChildren(JSTracer *trc);
 
     void fixupAfterMovingGC();
 
     static js::ThingRootKind rootKind() { return js::THING_ROOT_OBJECT; }
@@ -290,17 +275,17 @@ class JSObject : public js::gc::Cell
 
     MOZ_ALWAYS_INLINE JS::Zone *zone() const {
         return group_->zone();
     }
     MOZ_ALWAYS_INLINE JS::shadow::Zone *shadowZone() const {
         return JS::shadow::Zone::asShadowZone(zone());
     }
     MOZ_ALWAYS_INLINE JS::Zone *zoneFromAnyThread() const {
-        return shape_->zoneFromAnyThread();
+        return group_->zoneFromAnyThread();
     }
     MOZ_ALWAYS_INLINE JS::shadow::Zone *shadowZoneFromAnyThread() const {
         return JS::shadow::Zone::asShadowZone(zoneFromAnyThread());
     }
     static MOZ_ALWAYS_INLINE void readBarrier(JSObject *obj);
     static MOZ_ALWAYS_INLINE void writeBarrierPre(JSObject *obj);
     static MOZ_ALWAYS_INLINE void writeBarrierPost(JSObject *obj, void *cellp);
     static MOZ_ALWAYS_INLINE void writeBarrierPostRelocate(JSObject *obj, void *cellp);
@@ -372,48 +357,39 @@ class JSObject : public js::gc::Cell
     bool hasLazyPrototype() const {
         bool lazy = getTaggedProto().isLazy();
         MOZ_ASSERT_IF(lazy, uninlinedIsProxy());
         return lazy;
     }
 
     // True iff this object's [[Prototype]] is immutable.  Must not be called
     // on proxies with lazy [[Prototype]]!
-    bool nonLazyPrototypeIsImmutable() const {
-        MOZ_ASSERT(!hasLazyPrototype());
-        return lastProperty()->hasObjectFlag(js::BaseShape::IMMUTABLE_PROTOTYPE);
-    }
+    inline bool nonLazyPrototypeIsImmutable() const;
 
     inline void setGroup(js::ObjectGroup *group);
 
     /*
      * Mark an object that has been iterated over and is a singleton. We need
      * to recover this information in the object's type information after it
      * is purged on GC.
      */
-    bool isIteratedSingleton() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::ITERATED_SINGLETON);
-    }
+    inline bool isIteratedSingleton() const;
     bool setIteratedSingleton(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::ITERATED_SINGLETON);
     }
 
     /*
      * Mark an object as requiring its default 'new' type to have unknown
      * properties.
      */
-    bool isNewGroupUnknown() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::NEW_GROUP_UNKNOWN);
-    }
+    inline bool isNewGroupUnknown() const;
     static bool setNewGroupUnknown(JSContext *cx, const js::Class *clasp, JS::HandleObject obj);
 
     // Mark an object as having its 'new' script information cleared.
-    bool wasNewScriptCleared() const {
-        return lastProperty()->hasObjectFlag(js::BaseShape::NEW_SCRIPT_CLEARED);
-    }
+    inline bool wasNewScriptCleared() const;
     bool setNewScriptCleared(js::ExclusiveContext *cx) {
         return setFlags(cx, js::BaseShape::NEW_SCRIPT_CLEARED);
     }
 
     /* Set a new prototype for an object with a singleton type. */
     bool splicePrototype(JSContext *cx, const js::Class *clasp, js::Handle<js::TaggedProto> proto);
 
     /*
@@ -444,52 +420,43 @@ class JSObject : public js::gc::Cell
      * In compileAndGo code, scope chains can contain only internal scope
      * objects with a global object at the root as the scope of the outermost
      * non-function script. In non-compileAndGo code, the scope of the
      * outermost non-function script might not be a global object, and can have
      * a mix of other objects above it before the global object is reached.
      */
 
     /* Access the parent link of an object. */
-    JSObject *getParent() const {
-        return lastProperty()->getObjectParent();
-    }
+    JSObject *getParent() const;
     static bool setParent(JSContext *cx, js::HandleObject obj, js::HandleObject newParent);
 
     /*
      * Get the enclosing scope of an object. When called on non-scope object,
      * this will just be the global (the name "enclosing scope" still applies
      * in this situation because non-scope objects can be on the scope chain).
      */
     inline JSObject *enclosingScope();
 
     /* Access the metadata on an object. */
-    inline JSObject *getMetadata() const {
-        return lastProperty()->getObjectMetadata();
-    }
+    inline JSObject *getMetadata() const;
     static bool setMetadata(JSContext *cx, js::HandleObject obj, js::HandleObject newMetadata);
 
     inline js::GlobalObject &global() const;
     inline bool isOwnGlobal() const;
 
     /*
      * ES5 meta-object properties and operations.
      */
 
   public:
     // Indicates whether a non-proxy is extensible.  Don't call on proxies!
     // This method really shouldn't exist -- but there are a few internal
     // places that want it (JITs and the like), and it'd be a pain to mark them
     // all as friends.
-    bool nonProxyIsExtensible() const {
-        MOZ_ASSERT(!uninlinedIsProxy());
-
-        // [[Extensible]] for ordinary non-proxy objects is an object flag.
-        return !lastProperty()->hasObjectFlag(js::BaseShape::NOT_EXTENSIBLE);
-    }
+    inline bool nonProxyIsExtensible() const;
 
   public:
     /*
      * Iterator-specific getters and setters.
      */
 
     static const uint32_t ITER_CLASS_NFIXED_SLOTS = 1;
 
@@ -571,18 +538,18 @@ class JSObject : public js::gc::Cell
     }
 
 #ifdef DEBUG
     void dump();
 #endif
 
     /* JIT Accessors */
 
-    static size_t offsetOfShape() { return offsetof(JSObject, shape_); }
     static size_t offsetOfGroup() { return offsetof(JSObject, group_); }
+    static size_t offsetOfShape() { return sizeof(JSObject); }
 
     // Maximum size in bytes of a JSObject.
     static const size_t MAX_BYTE_SIZE = 4 * sizeof(void *) + 16 * sizeof(JS::Value);
 
   private:
     JSObject() = delete;
     JSObject(const JSObject &other) = delete;
     void operator=(const JSObject &other) = delete;
@@ -619,22 +586,22 @@ operator==(const JSObject &lhs, const JS
 
 static MOZ_ALWAYS_INLINE bool
 operator!=(const JSObject &lhs, const JSObject &rhs)
 {
     return &lhs != &rhs;
 }
 
 // Size of the various GC thing allocation sizes used for objects.
-struct JSObject_Slots0 : JSObject { void *data[2]; };
-struct JSObject_Slots2 : JSObject { void *data[2]; js::Value fslots[2]; };
-struct JSObject_Slots4 : JSObject { void *data[2]; js::Value fslots[4]; };
-struct JSObject_Slots8 : JSObject { void *data[2]; js::Value fslots[8]; };
-struct JSObject_Slots12 : JSObject { void *data[2]; js::Value fslots[12]; };
-struct JSObject_Slots16 : JSObject { void *data[2]; js::Value fslots[16]; };
+struct JSObject_Slots0 : JSObject { void *data[3]; };
+struct JSObject_Slots2 : JSObject { void *data[3]; js::Value fslots[2]; };
+struct JSObject_Slots4 : JSObject { void *data[3]; js::Value fslots[4]; };
+struct JSObject_Slots8 : JSObject { void *data[3]; js::Value fslots[8]; };
+struct JSObject_Slots12 : JSObject { void *data[3]; js::Value fslots[12]; };
+struct JSObject_Slots16 : JSObject { void *data[3]; js::Value fslots[16]; };
 
 /* static */ MOZ_ALWAYS_INLINE void
 JSObject::readBarrier(JSObject *obj)
 {
     if (!isNullLike(obj) && obj->isTenured())
         obj->asTenured().readBarrier(&obj->asTenured());
 }
 
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -20,16 +20,34 @@
 #include "vm/TypedArrayCommon.h"
 
 #include "jsatominlines.h"
 #include "jscompartmentinlines.h"
 #include "jsgcinlines.h"
 
 #include "vm/TypeInference-inl.h"
 
+inline js::Shape *
+JSObject::maybeShape() const
+{
+    if (is<js::UnboxedPlainObject>())
+        return nullptr;
+    return *reinterpret_cast<js::Shape **>(uintptr_t(this) + offsetOfShape());
+}
+
+inline js::Shape *
+JSObject::ensureShape(js::ExclusiveContext *cx)
+{
+    if (is<js::UnboxedPlainObject>() && !js::UnboxedPlainObject::convertToNative(cx->asJSContext(), this))
+        return nullptr;
+    js::Shape *shape = maybeShape();
+    MOZ_ASSERT(shape);
+    return shape;
+}
+
 inline void
 JSObject::finalize(js::FreeOp *fop)
 {
     js::probes::FinalizeObject(this);
 
 #ifdef DEBUG
     MOZ_ASSERT(isTenured());
     if (!IsBackgroundFinalized(asTenured().getAllocKind())) {
@@ -62,18 +80,18 @@ JSObject::finalize(js::FreeOp *fop)
             fop->free_(elements);
         }
     }
 
     // For dictionary objects (which must be native), it's possible that
     // unreachable shapes may be marked whose listp points into this object.
     // In case this happens, null out the shape's pointer here so that a moving
     // GC will not try to access the dead object.
-    if (shape_->listp == &shape_)
-        shape_->listp = nullptr;
+    if (nobj->shape_->listp == &nobj->shape_)
+        nobj->shape_->listp = nullptr;
 }
 
 /* static */ inline bool
 JSObject::setSingleton(js::ExclusiveContext *cx, js::HandleObject obj)
 {
     MOZ_ASSERT_IF(cx->isJSContext(), !IsInsideNursery(obj));
 
     js::ObjectGroup *group = js::ObjectGroup::lazySingletonGroup(cx, obj->getClass(),
@@ -183,25 +201,25 @@ js::DeleteElement(JSContext *cx, HandleO
 
 /* * */
 
 inline bool
 JSObject::isQualifiedVarObj()
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isQualifiedVarObj();
-    return lastProperty()->hasObjectFlag(js::BaseShape::QUALIFIED_VAROBJ);
+    return hasAllFlags(js::BaseShape::QUALIFIED_VAROBJ);
 }
 
 inline bool
 JSObject::isUnqualifiedVarObj()
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isUnqualifiedVarObj();
-    return lastProperty()->hasObjectFlag(js::BaseShape::UNQUALIFIED_VAROBJ);
+    return hasAllFlags(js::BaseShape::UNQUALIFIED_VAROBJ);
 }
 
 namespace js {
 
 inline bool
 ClassCanHaveFixedData(const Class *clasp)
 {
     // Normally, the number of fixed slots given an object is the maximum
@@ -242,19 +260,20 @@ JSObject::create(js::ExclusiveContext *c
     const js::Class *clasp = group->clasp();
     size_t nDynamicSlots =
         js::NativeObject::dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), clasp);
 
     JSObject *obj = js::NewGCObject<js::CanGC>(cx, kind, nDynamicSlots, heap, clasp);
     if (!obj)
         return nullptr;
 
-    obj->shape_.init(shape);
     obj->group_.init(group);
 
+    obj->setInitialShapeMaybeNonNative(shape);
+
     // Note: slots are created and assigned internally by NewGCObject.
     obj->setInitialElementsMaybeNonNative(js::emptyObjectElements);
 
     if (clasp->hasPrivate())
         obj->as<js::NativeObject>().privateRef(shape->numFixedSlots()) = nullptr;
 
     if (size_t span = shape->slotSpan())
         obj->as<js::NativeObject>().initializeSlotRange(0, span);
@@ -264,27 +283,48 @@ JSObject::create(js::ExclusiveContext *c
         memset(obj->as<JSFunction>().fixedSlots(), 0, sizeof(js::HeapSlot) * GetGCKindSlots(kind));
 
     js::gc::TraceCreateObject(obj);
 
     return obj;
 }
 
 inline void
+JSObject::setInitialShapeMaybeNonNative(js::Shape *shape)
+{
+    static_cast<js::NativeObject *>(this)->shape_.init(shape);
+}
+
+inline void
+JSObject::setShapeMaybeNonNative(js::Shape *shape)
+{
+    MOZ_ASSERT(!is<js::UnboxedPlainObject>());
+    static_cast<js::NativeObject *>(this)->shape_ = shape;
+}
+
+inline void
 JSObject::setInitialSlotsMaybeNonNative(js::HeapSlot *slots)
 {
     static_cast<js::NativeObject *>(this)->slots_ = slots;
 }
 
 inline void
 JSObject::setInitialElementsMaybeNonNative(js::HeapSlot *elements)
 {
     static_cast<js::NativeObject *>(this)->elements_ = elements;
 }
 
+inline JSObject *
+JSObject::getMetadata() const
+{
+    if (js::Shape *shape = maybeShape())
+        return shape->getObjectMetadata();
+    return nullptr;
+}
+
 inline js::GlobalObject &
 JSObject::global() const
 {
 #ifdef DEBUG
     JSObject *obj = const_cast<JSObject *>(this);
     while (JSObject *parent = obj->getParent())
         obj = parent;
 #endif
@@ -298,16 +338,95 @@ JSObject::global() const
 }
 
 inline bool
 JSObject::isOwnGlobal() const
 {
     return &global() == this;
 }
 
+inline bool
+JSObject::hasAllFlags(js::BaseShape::Flag flags) const
+{
+    MOZ_ASSERT(flags);
+    if (js::Shape *shape = maybeShape())
+        return shape->hasAllObjectFlags(flags);
+    return false;
+}
+
+inline bool
+JSObject::nonProxyIsExtensible() const
+{
+    MOZ_ASSERT(!uninlinedIsProxy());
+
+    // [[Extensible]] for ordinary non-proxy objects is an object flag.
+    return !hasAllFlags(js::BaseShape::NOT_EXTENSIBLE);
+}
+
+inline bool
+JSObject::isBoundFunction() const
+{
+    return hasAllFlags(js::BaseShape::BOUND_FUNCTION);
+}
+
+inline bool
+JSObject::watched() const
+{
+    return hasAllFlags(js::BaseShape::WATCHED);
+}
+
+inline bool
+JSObject::isDelegate() const
+{
+    return hasAllFlags(js::BaseShape::DELEGATE);
+}
+
+inline bool
+JSObject::hasUncacheableProto() const
+{
+    return hasAllFlags(js::BaseShape::UNCACHEABLE_PROTO);
+}
+
+inline bool
+JSObject::hadElementsAccess() const
+{
+    return hasAllFlags(js::BaseShape::HAD_ELEMENTS_ACCESS);
+}
+
+inline bool
+JSObject::isIndexed() const
+{
+    return hasAllFlags(js::BaseShape::INDEXED);
+}
+
+inline bool
+JSObject::nonLazyPrototypeIsImmutable() const
+{
+    MOZ_ASSERT(!hasLazyPrototype());
+    return hasAllFlags(js::BaseShape::IMMUTABLE_PROTOTYPE);
+}
+
+inline bool
+JSObject::isIteratedSingleton() const
+{
+    return hasAllFlags(js::BaseShape::ITERATED_SINGLETON);
+}
+
+inline bool
+JSObject::isNewGroupUnknown() const
+{
+    return hasAllFlags(js::BaseShape::NEW_GROUP_UNKNOWN);
+}
+
+inline bool
+JSObject::wasNewScriptCleared() const
+{
+    return hasAllFlags(js::BaseShape::NEW_SCRIPT_CLEARED);
+}
+
 namespace js {
 
 PropDesc::PropDesc(const Value &getter, const Value &setter,
                    Enumerability enumerable, Configurability configurable)
   : value_(UndefinedValue()),
     get_(getter), set_(setter),
     attrs(JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED |
           (enumerable ? JSPROP_ENUMERATE : 0) |
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -248,18 +248,18 @@ Shape::fixupDictionaryShapeAfterMovingGC
     if (kind == FINALIZE_SHAPE || kind == FINALIZE_ACCESSOR_SHAPE) {
         // listp points to the parent field of the next shape.
         Shape *next = reinterpret_cast<Shape *>(uintptr_t(listp) -
                                                 offsetof(Shape, parent));
         listp = &gc::MaybeForwarded(next)->parent;
     } else {
         // listp points to the shape_ field of an object.
         JSObject *last = reinterpret_cast<JSObject *>(uintptr_t(listp) -
-                                                      offsetof(JSObject, shape_));
-        listp = &gc::MaybeForwarded(last)->shape_;
+                                                      JSObject::offsetOfShape());
+        listp = &gc::MaybeForwarded(last)->as<NativeObject>().shape_;
     }
 }
 
 void
 Shape::fixupShapeTreeAfterMovingGC()
 {
     if (kids.isNull())
         return;
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -606,16 +606,18 @@ js::proxy_Trace(JSTracer *trc, JSObject 
     ProxyObject::trace(trc, obj);
 }
 
 /* static */ void
 ProxyObject::trace(JSTracer *trc, JSObject *obj)
 {
     ProxyObject *proxy = &obj->as<ProxyObject>();
 
+    MarkShape(trc, &proxy->shape, "ProxyObject_shape");
+
 #ifdef DEBUG
     if (trc->runtime()->gc.isStrictProxyCheckingEnabled() && proxy->is<WrapperObject>()) {
         JSObject *referent = MaybeForwarded(&proxy->private_().toObject());
         if (referent->compartment() != proxy->compartment()) {
             /*
              * Assert that this proxy is tracked in the wrapper map. We maintain
              * the invariant that the wrapped object is the key in the wrapper map.
              */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2879,17 +2879,17 @@ static bool
 ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.get(0).isObject()) {
         JS_ReportError(cx, "shapeOf: object expected");
         return false;
     }
     JSObject *obj = &args[0].toObject();
-    args.rval().set(JS_NumberValue(double(uintptr_t(obj->lastProperty()) >> 3)));
+    args.rval().set(JS_NumberValue(double(uintptr_t(obj->maybeShape()) >> 3)));
     return true;
 }
 
 /*
  * Check that t1 comes strictly before t2. The function correctly deals with
  * wrap-around between t2 and t1 assuming that t2 and t1 stays within INT32_MAX
  * from each other. We use MAX_TIMEOUT_INTERVAL to enforce this restriction.
  */
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -381,23 +381,24 @@ NativeObject::setLastPropertyMakeNonNati
 
     shape_ = shape;
 }
 
 void
 NativeObject::setLastPropertyMakeNative(ExclusiveContext *cx, Shape *shape)
 {
     MOZ_ASSERT(getClass()->isNative());
-    MOZ_ASSERT(!lastProperty()->isNative());
     MOZ_ASSERT(shape->isNative());
-    MOZ_ASSERT(!inDictionaryMode());
     MOZ_ASSERT(!shape->inDictionary());
-    MOZ_ASSERT(shape->compartment() == compartment());
 
-    shape_ = shape;
+    // This method is used to convert unboxed objects into native objects. In
+    // this case, the shape_ field was previously used to store other data and
+    // this should be treated as an initialization.
+    shape_.init(shape);
+
     slots_ = nullptr;
     elements_ = emptyObjectElements;
 
     size_t oldSpan = shape->numFixedSlots();
     size_t newSpan = shape->slotSpan();
 
     // A failure at this point will leave the object as a mutant, and we
     // can't recover.
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -336,16 +336,19 @@ DenseRangeWriteBarrierPost(JSRuntime *rt
  * in this case numFixedSlots() is zero) or to a dynamically allocated array.
  *
  * Slots and elements may both be non-empty. The slots may be either names or
  * indexes; no indexed property will be in both the slots and elements.
  */
 class NativeObject : public JSObject
 {
   protected:
+    // Property layout description and other state.
+    HeapPtrShape shape_;
+
     /* Slots for object properties. */
     js::HeapSlot *slots_;
 
     /* Slots for object dense elements. */
     js::HeapSlot *elements_;
 
     friend bool
     ArraySetLength(JSContext *cx, Handle<ArrayObject*> obj, HandleId id, unsigned attrs,
@@ -373,16 +376,29 @@ class NativeObject : public JSObject
 
         static_assert(MAX_FIXED_SLOTS <= Shape::FIXED_SLOTS_MAX,
                       "verify numFixedSlots() bitfield is big enough");
         static_assert(sizeof(NativeObject) + MAX_FIXED_SLOTS * sizeof(Value) == JSObject::MAX_BYTE_SIZE,
                       "inconsistent maximum object size");
     }
 
   public:
+    Shape *lastProperty() const {
+        MOZ_ASSERT(shape_);
+        return shape_;
+    }
+
+    uint32_t propertyCount() const {
+        return lastProperty()->entryCount();
+    }
+
+    bool hasShapeTable() const {
+        return lastProperty()->hasTable();
+    }
+
     HeapSlotArray getDenseElements() {
         return HeapSlotArray(elements_, !getElementsHeader()->isCopyOnWrite());
     }
     HeapSlotArray getDenseElementsAllowCopyOnWrite() {
         // Backdoor allowing direct access to copy on write elements.
         return HeapSlotArray(elements_, true);
     }
     const Value &getDenseElement(uint32_t idx) {
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -307,17 +307,17 @@ JSObject::makeLazyGroup(JSContext *cx, H
         if (!fun->getOrCreateScript(cx))
             return nullptr;
     }
 
     // Find flags which need to be specified immediately on the object.
     // Don't track whether singletons are packed.
     ObjectGroupFlags initialFlags = OBJECT_FLAG_SINGLETON | OBJECT_FLAG_NON_PACKED;
 
-    if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON))
+    if (obj->isIteratedSingleton())
         initialFlags |= OBJECT_FLAG_ITERATED;
 
     if (obj->isIndexed())
         initialFlags |= OBJECT_FLAG_SPARSE_INDEXES;
 
     if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX)
         initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW;
 
--- a/js/src/vm/PIC.cpp
+++ b/js/src/vm/PIC.cpp
@@ -165,17 +165,17 @@ js::ForOfPIC::Stub *
 js::ForOfPIC::Chain::getMatchingStub(JSObject *obj)
 {
     // Ensure PIC is initialized and not disabled.
     if (!initialized_ || disabled_)
         return nullptr;
 
     // Check if there is a matching stub.
     for (Stub *stub = stubs(); stub != nullptr; stub = stub->next()) {
-        if (stub->shape() == obj->lastProperty())
+        if (stub->shape() == obj->maybeShape())
             return stub;
     }
 
     return nullptr;
 }
 
 bool
 js::ForOfPIC::Chain::isOptimizableArray(JSObject *obj)
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -11,16 +11,18 @@
 #include "vm/NativeObject.h"
 
 namespace js {
 
 // This is the base class for the various kinds of proxy objects.  It's never
 // instantiated.
 class ProxyObject : public JSObject
 {
+    HeapPtrShape shape;
+
     // GetProxyDataLayout computes the address of this field.
     ProxyDataLayout data;
 
     void static_asserts() {
         static_assert(sizeof(ProxyObject) == sizeof(JSObject_Slots0),
                       "proxy object size must match GC thing size");
         static_assert(offsetof(ProxyObject, data) == ProxyDataOffset,
                       "proxy object layout must match shadow interface");
--- a/js/src/vm/Runtime-inl.h
+++ b/js/src/vm/Runtime-inl.h
@@ -33,38 +33,38 @@ NewObjectCache::lookupGlobal(const Class
 inline void
 NewObjectCache::fillGlobal(EntryIndex entry, const Class *clasp, js::GlobalObject *global,
                            gc::AllocKind kind, NativeObject *obj)
 {
     //MOZ_ASSERT(global == obj->getGlobal());
     return fill(entry, clasp, global, kind, obj);
 }
 
-inline JSObject *
+inline NativeObject *
 NewObjectCache::newObjectFromHit(JSContext *cx, EntryIndex entryIndex, js::gc::InitialHeap heap)
 {
     // The new object cache does not account for metadata attached via callbacks.
     MOZ_ASSERT(!cx->compartment()->hasObjectMetadataCallback());
 
     MOZ_ASSERT(unsigned(entryIndex) < mozilla::ArrayLength(entries));
     Entry *entry = &entries[entryIndex];
 
-    JSObject *templateObj = reinterpret_cast<JSObject *>(&entry->templateObject);
+    NativeObject *templateObj = reinterpret_cast<NativeObject *>(&entry->templateObject);
 
     // Do an end run around JSObject::group() to avoid doing AutoUnprotectCell
     // on the templateObj, which is not a GC thing and can't use runtimeFromAnyThread.
     ObjectGroup *group = templateObj->group_;
 
     if (group->shouldPreTenure())
         heap = gc::TenuredHeap;
 
     if (cx->runtime()->gc.upcomingZealousGC())
         return nullptr;
 
-    JSObject *obj = js::gc::AllocateObjectForCacheHit<NoGC>(cx, entry->kind, heap, group->clasp());
+    NativeObject *obj = js::gc::AllocateObjectForCacheHit<NoGC>(cx, entry->kind, heap, group->clasp());
     if (obj) {
         copyCachedToObject(obj, templateObj, entry->kind);
         probes::CreateObject(cx, obj);
         js::gc::TraceCreateObject(obj);
         return obj;
     }
 
     // Trigger an identical allocation to the one that notified us of OOM so
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -292,17 +292,17 @@ class NewObjectCache
         return lookup(group->clasp(), group, kind, pentry);
     }
 
     /*
      * Return a new object from a cache hit produced by a lookup method, or
      * nullptr if returning the object could possibly trigger GC (does not
      * indicate failure).
      */
-    inline JSObject *newObjectFromHit(JSContext *cx, EntryIndex entry, js::gc::InitialHeap heap);
+    inline NativeObject *newObjectFromHit(JSContext *cx, EntryIndex entry, js::gc::InitialHeap heap);
 
     /* Fill an entry after a cache miss. */
     void fillProto(EntryIndex entry, const Class *clasp, js::TaggedProto proto,
                    gc::AllocKind kind, NativeObject *obj);
 
     inline void fillGlobal(EntryIndex entry, const Class *clasp, js::GlobalObject *global,
                            gc::AllocKind kind, NativeObject *obj);
 
@@ -339,17 +339,17 @@ class NewObjectCache
         entry->clasp = clasp;
         entry->key = key;
         entry->kind = kind;
 
         entry->nbytes = gc::Arena::thingSize(kind);
         js_memcpy(&entry->templateObject, obj, entry->nbytes);
     }
 
-    static void copyCachedToObject(JSObject *dst, JSObject *src, gc::AllocKind kind) {
+    static void copyCachedToObject(NativeObject *dst, NativeObject *src, gc::AllocKind kind) {
         js_memcpy(dst, src, gc::Arena::thingSize(kind));
         Shape::writeBarrierPost(dst->shape_, &dst->shape_);
         ObjectGroup::writeBarrierPost(dst->group_, &dst->group_);
     }
 };
 
 /*
  * A FreeOp can do one thing: free memory. For convenience, it has delete_
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -494,17 +494,17 @@ NativeObject::addProperty(ExclusiveConte
     if (obj->inDictionaryMode())
         entry = &obj->lastProperty()->table().search(id, true);
 
     return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, entry,
                                allowDictionary);
 }
 
 static bool
-ShouldConvertToDictionary(JSObject *obj)
+ShouldConvertToDictionary(NativeObject *obj)
 {
     /*
      * Use a lower limit if this object is likely a hashmap (SETELEM was used
      * to set properties).
      */
     if (obj->hadElementsAccess())
         return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;
     return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT;
@@ -1133,31 +1133,35 @@ NativeObject::shadowingShapeChange(Exclu
 
 /* static */ bool
 JSObject::setParent(JSContext *cx, HandleObject obj, HandleObject parent)
 {
     if (parent && !parent->setDelegate(cx))
         return false;
 
     if (obj->isNative() && obj->as<NativeObject>().inDictionaryMode()) {
-        StackBaseShape base(obj->lastProperty());
+        StackBaseShape base(obj->as<NativeObject>().lastProperty());
         base.parent = parent;
         UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
         if (!nbase)
             return false;
 
-        obj->lastProperty()->base()->adoptUnowned(nbase);
+        obj->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase);
         return true;
     }
 
-    Shape *newShape = Shape::setObjectParent(cx, parent, obj->getTaggedProto(), obj->shape_);
+    Shape *existingShape = obj->ensureShape(cx);
+    if (!existingShape)
+        return false;
+
+    Shape *newShape = Shape::setObjectParent(cx, parent, obj->getTaggedProto(), existingShape);
     if (!newShape)
         return false;
 
-    obj->shape_ = newShape;
+    obj->setShapeMaybeNonNative(newShape);
     return true;
 }
 
 /* static */ Shape *
 Shape::setObjectParent(ExclusiveContext *cx, JSObject *parent, TaggedProto proto, Shape *last)
 {
     if (last->getObjectParent() == parent)
         return last;
@@ -1168,31 +1172,35 @@ Shape::setObjectParent(ExclusiveContext 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
 /* static */ bool
 JSObject::setMetadata(JSContext *cx, HandleObject obj, HandleObject metadata)
 {
     if (obj->isNative() && obj->as<NativeObject>().inDictionaryMode()) {
-        StackBaseShape base(obj->lastProperty());
+        StackBaseShape base(obj->as<NativeObject>().lastProperty());
         base.metadata = metadata;
         UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
         if (!nbase)
             return false;
 
-        obj->lastProperty()->base()->adoptUnowned(nbase);
+        obj->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase);
         return true;
     }
 
-    Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), obj->shape_);
+    Shape *existingShape = obj->ensureShape(cx);
+    if (!existingShape)
+        return false;
+
+    Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), existingShape);
     if (!newShape)
         return false;
 
-    obj->shape_ = newShape;
+    obj->setShapeMaybeNonNative(newShape);
     return true;
 }
 
 /* static */ Shape *
 Shape::setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last)
 {
     if (last->getObjectMetadata() == metadata)
         return last;
@@ -1200,55 +1208,55 @@ Shape::setObjectMetadata(JSContext *cx, 
     StackBaseShape base(last);
     base.metadata = metadata;
 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
 bool
-JSObject::setFlags(ExclusiveContext *cx, /*BaseShape::Flag*/ uint32_t flags_,
-                   GenerateShape generateShape)
+JSObject::setFlags(ExclusiveContext *cx, BaseShape::Flag flags, GenerateShape generateShape)
 {
-    BaseShape::Flag flags = (BaseShape::Flag) flags_;
-
-    if ((lastProperty()->getObjectFlags() & flags) == flags)
+    if (hasAllFlags(flags))
         return true;
 
     RootedObject self(cx, this);
 
     if (isNative() && as<NativeObject>().inDictionaryMode()) {
         if (generateShape == GENERATE_SHAPE && !as<NativeObject>().generateOwnShape(cx))
             return false;
-        StackBaseShape base(self->lastProperty());
+        StackBaseShape base(self->as<NativeObject>().lastProperty());
         base.flags |= flags;
         UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
         if (!nbase)
             return false;
 
-        self->lastProperty()->base()->adoptUnowned(nbase);
+        self->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase);
         return true;
     }
 
-    Shape *newShape =
-        Shape::setObjectFlags(cx, flags, self->getTaggedProto(), self->lastProperty());
+    Shape *existingShape = self->ensureShape(cx);
+    if (!existingShape)
+        return false;
+
+    Shape *newShape = Shape::setObjectFlags(cx, flags, self->getTaggedProto(), existingShape);
     if (!newShape)
         return false;
 
-    self->shape_ = newShape;
+    self->setShapeMaybeNonNative(newShape);
     return true;
 }
 
 bool
 NativeObject::clearFlag(ExclusiveContext *cx, BaseShape::Flag flag)
 {
     MOZ_ASSERT(inDictionaryMode());
-    MOZ_ASSERT(lastProperty()->getObjectFlags() & flag);
 
-    RootedObject self(cx, this);
+    RootedNativeObject self(cx, &as<NativeObject>());
+    MOZ_ASSERT(self->lastProperty()->getObjectFlags() & flag);
 
     StackBaseShape base(self->lastProperty());
     base.flags &= ~flag;
     UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
     if (!nbase)
         return false;
 
     self->lastProperty()->base()->adoptUnowned(nbase);
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -829,19 +829,20 @@ class Shape : public gc::TenuredCell
     static Shape *setObjectParent(ExclusiveContext *cx,
                                   JSObject *obj, TaggedProto proto, Shape *last);
     static Shape *setObjectMetadata(JSContext *cx,
                                     JSObject *metadata, TaggedProto proto, Shape *last);
     static Shape *setObjectFlags(ExclusiveContext *cx,
                                  BaseShape::Flag flag, TaggedProto proto, Shape *last);
 
     uint32_t getObjectFlags() const { return base()->getObjectFlags(); }
-    bool hasObjectFlag(BaseShape::Flag flag) const {
-        MOZ_ASSERT(!(flag & ~BaseShape::OBJECT_FLAG_MASK));
-        return !!(base()->flags & flag);
+    bool hasAllObjectFlags(BaseShape::Flag flags) const {
+        MOZ_ASSERT(flags);
+        MOZ_ASSERT(!(flags & ~BaseShape::OBJECT_FLAG_MASK));
+        return (base()->flags & flags) == flags;
     }
 
   protected:
     /*
      * Implementation-private bits stored in shape->flags. See public: enum {}
      * flags further below, which were allocated FCFS over time, so interleave
      * with these bits.
      */
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2571,17 +2571,17 @@ ObjectGroup::matchDefiniteProperties(Han
     for (unsigned i = 0; i < count; i++) {
         Property *prop = getProperty(i);
         if (!prop)
             continue;
         if (prop->types.definiteProperty()) {
             unsigned slot = prop->types.definiteSlot();
 
             bool found = false;
-            Shape *shape = obj->lastProperty();
+            Shape *shape = obj->as<NativeObject>().lastProperty();
             while (!shape->isEmptyShape()) {
                 if (shape->slot() == slot && shape->propid() == prop->id) {
                     found = true;
                     break;
                 }
                 shape = shape->previous();
             }
             if (!found)
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -283,54 +283,40 @@ UnboxedPlainObject::convertToNative(JSCo
     }
 
     AutoValueVector values(cx);
     for (size_t i = 0; i < layout.properties().length(); i++) {
         if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i])))
             return false;
     }
 
-    uint32_t objectFlags = obj->lastProperty()->getObjectFlags();
-    RootedObject metadata(cx, obj->getMetadata());
-
     obj->setGroup(layout.nativeGroup());
     obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
 
     for (size_t i = 0; i < values.length(); i++)
         obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
 
-    if (objectFlags) {
-        RootedObject objRoot(cx, obj);
-        if (!obj->setFlags(cx, objectFlags))
-            return false;
-        obj = objRoot;
-    }
-
-    if (metadata) {
-        RootedObject objRoot(cx, obj);
-        RootedObject metadataRoot(cx, metadata);
-        if (!setMetadata(cx, objRoot, metadataRoot))
-            return false;
-    }
-
     return true;
 }
 
 /* static */
 UnboxedPlainObject *
 UnboxedPlainObject::create(JSContext *cx, HandleObjectGroup group, NewObjectKind newKind)
 {
     MOZ_ASSERT(group->clasp() == &class_);
     gc::AllocKind allocKind = group->unboxedLayout().getAllocKind();
 
     UnboxedPlainObject *res = NewObjectWithGroup<UnboxedPlainObject>(cx, group, cx->global(),
                                                                      allocKind, newKind);
     if (!res)
         return nullptr;
 
+    // Avoid spurious shape guard hits.
+    res->dummy_ = nullptr;
+
     // Initialize reference fields of the object. All fields in the object will
     // be overwritten shortly, but references need to be safe for the GC.
     const int32_t *list = res->layout().traceList();
     if (list) {
         uint8_t *data = res->data();
         while (*list != -1) {
             HeapPtrString *heap = reinterpret_cast<HeapPtrString *>(data + *list);
             heap->init(cx->names().empty);
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -146,17 +146,20 @@ class UnboxedLayout : public mozilla::Li
 };
 
 // Class for a plain object using an unboxed representation. The physical
 // layout of these objects is identical to that of an InlineTypedObject, though
 // these objects use an UnboxedLayout instead of a TypeDescr to keep track of
 // how their properties are stored.
 class UnboxedPlainObject : public JSObject
 {
-    // Start of the inline data, which immediately follows the shape and type.
+    // Placeholder for extra properties. See bug 1137180.
+    void *dummy_;
+
+    // Start of the inline data, which immediately follows the group and extra properties.
     uint8_t data_[1];
 
   public:
     static const Class class_;
 
     static bool obj_lookupProperty(JSContext *cx, HandleObject obj,
                                    HandleId id, MutableHandleObject objp,
                                    MutableHandleShape propp);