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 id28362
push userryanvm@gmail.com
push dateWed, 04 Mar 2015 21:35:51 +0000
treeherdermozilla-central@56492f7244a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1137497
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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);