Bug 1433837 - Use accessors for ShapedObject::shape_ field. r=jandem
authorTed Campbell <tcampbell@mozilla.com>
Mon, 29 Jan 2018 13:19:00 +0200
changeset 454540 8009cda1a18e5e2f7bcb5b970ecab9637d124dd9
parent 454539 9add855243c1aae2681e4f21a4e38dabde8f71bd
child 454541 f59e3ddbb98b5f324ccd8617c8c0edae5daea3a4
push id8799
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 16:46:23 +0000
treeherdermozilla-beta@15334014dc67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1433837
milestone60.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 1433837 - Use accessors for ShapedObject::shape_ field. r=jandem MozReview-Commit-ID: 4Kw6iAudVyH
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/jsobj.cpp
js/src/jsobjinlines.h
js/src/proxy/Proxy.cpp
js/src/vm/Caches.h
js/src/vm/NativeObject-inl.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/Shape.cpp
js/src/vm/ShapedObject.h
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1601,17 +1601,17 @@ ReportTypedObjTypeError(JSContext* cx,
     return false;
 }
 
 /* static */ void
 OutlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
 {
     OutlineTypedObject& typedObj = object->as<OutlineTypedObject>();
 
-    TraceEdge(trc, &typedObj.shape_, "OutlineTypedObject_shape");
+    TraceEdge(trc, typedObj.shapePtr(), "OutlineTypedObject_shape");
 
     if (!typedObj.owner_)
         return;
 
     TypeDescr& descr = typedObj.typeDescr();
 
     // Mark the owner, watching in case it is moved by the tracer.
     JSObject* oldOwner = typedObj.owner_;
@@ -2113,17 +2113,17 @@ InlineTypedObject::createCopy(JSContext*
     return res;
 }
 
 /* static */ void
 InlineTypedObject::obj_trace(JSTracer* trc, JSObject* object)
 {
     InlineTypedObject& typedObj = object->as<InlineTypedObject>();
 
-    TraceEdge(trc, &typedObj.shape_, "InlineTypedObject_shape");
+    TraceEdge(trc, typedObj.shapePtr(), "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.
     if (typedObj.is<InlineTransparentTypedObject>())
         return;
 
     typedObj.typeDescr().traceInstances(trc, typedObj.inlineTypedMem(), 1);
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -587,17 +587,19 @@ class TypedObject : public ShapedObject
     // User-accessible constructor (`new TypeDescriptor(...)`). Note that the
     // callee here is the type descriptor.
     static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     /* Accessors for self hosted code. */
     static MOZ_MUST_USE bool GetBuffer(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool GetByteOffset(JSContext* cx, unsigned argc, Value* vp);
 
-    Shape** addressOfShapeFromGC() { return shape_.unsafeUnbarrieredForTracing(); }
+    Shape** addressOfShapeFromGC() {
+        return shapeRef().unsafeUnbarrieredForTracing();
+    }
 };
 
 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 GCPtr and
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1528,20 +1528,20 @@ NativeObject::fillInAfterSwap(JSContext*
 {
     // This object has just been swapped with some other object, and its shape
     // no longer reflects its allocated size. Correct this information and
     // fill the slots in with the specified values.
     MOZ_ASSERT(obj->slotSpan() == values.length());
 
     // Make sure the shape's numFixedSlots() is correct.
     size_t nfixed = gc::GetGCKindSlots(obj->asTenured().getAllocKind(), obj->getClass());
-    if (nfixed != obj->shape_->numFixedSlots()) {
+    if (nfixed != obj->shape()->numFixedSlots()) {
         if (!NativeObject::generateOwnShape(cx, obj))
             return false;
-        obj->shape_->setNumFixedSlots(nfixed);
+        obj->shape()->setNumFixedSlots(nfixed);
     }
 
     if (obj->hasPrivate())
         obj->setPrivate(priv);
     else
         MOZ_ASSERT(!priv);
 
     if (obj->slots_) {
@@ -1561,17 +1561,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())
-        as<NativeObject>().shape_->listp = &as<NativeObject>().shape_;
+        as<NativeObject>().shape()->listp = as<NativeObject>().shapePtr();
 }
 
 static MOZ_MUST_USE bool
 CopyProxyValuesBeforeSwap(ProxyObject* proxy, Vector<Value>& values)
 {
     MOZ_ASSERT(values.empty());
 
     // Remove the GCPtrValues we're about to swap from the store buffer, to
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -134,29 +134,29 @@ JSObject::finalize(js::FreeOp* fop)
 
 MOZ_ALWAYS_INLINE void
 js::NativeObject::sweepDictionaryListPointer()
 {
     // 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 so that a moving GC will
     // not try to access the dead object.
-    if (shape_->listp == &shape_)
-        shape_->listp = nullptr;
+    if (shape()->listp == shapePtr())
+        shape()->listp = nullptr;
 }
 
 MOZ_ALWAYS_INLINE void
 js::NativeObject::updateDictionaryListPointerAfterMinorGC(NativeObject* old)
 {
     MOZ_ASSERT(this == Forwarded(old));
 
     // Dictionary objects can be allocated in the nursery and when they are
     // tenured the shape's pointer into the object needs to be updated.
-    if (shape_->listp == &old->shape_)
-        shape_->listp = &shape_;
+    if (shape()->listp == old->shapePtr())
+        shape()->listp = shapePtr();
 }
 
 inline void
 js::gc::MakeAccessibleAfterMovingGC(JSObject* obj)
 {
     if (obj->isNative())
         obj->as<NativeObject>().updateShapeAfterMovingGC();
 }
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -681,17 +681,17 @@ ProxyObject::traceEdgeToTarget(JSTracer*
     TraceCrossCompartmentEdge(trc, obj, obj->slotOfPrivate(), "proxy target");
 }
 
 /* static */ void
 ProxyObject::trace(JSTracer* trc, JSObject* obj)
 {
     ProxyObject* proxy = &obj->as<ProxyObject>();
 
-    TraceEdge(trc, &proxy->shape_, "ProxyObject_shape");
+    TraceEdge(trc, proxy->shapePtr(), "ProxyObject_shape");
 
 #ifdef DEBUG
     if (TlsContext.get()->isStrictProxyCheckingEnabled() && proxy->is<WrapperObject>()) {
         JSObject* referent = MaybeForwarded(proxy->target());
         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/vm/Caches.h
+++ b/js/src/vm/Caches.h
@@ -209,18 +209,20 @@ class NewObjectCache
         entry->kind = kind;
 
         entry->nbytes = gc::Arena::thingSize(kind);
         js_memcpy(&entry->templateObject, obj, entry->nbytes);
     }
 
     static void copyCachedToObject(NativeObject* dst, NativeObject* src, gc::AllocKind kind) {
         js_memcpy(dst, src, gc::Arena::thingSize(kind));
-        Shape::writeBarrierPost(&dst->shape_, nullptr, dst->shape_);
-        ObjectGroup::writeBarrierPost(&dst->group_, nullptr, dst->group_);
+
+        // Initialize with barriers
+        dst->initGroup(src->group());
+        dst->initShape(src->shape());
     }
 };
 
 class RuntimeCaches
 {
     UniquePtr<js::MathCache> mathCache_;
 
     js::MathCache* createMathCache(JSContext* cx);
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -508,19 +508,19 @@ NativeObject::setSlotWithType(JSContext*
         shape->setOverwritten();
 
     AddTypePropertyId(cx, this, shape->propid(), value);
 }
 
 inline void
 NativeObject::updateShapeAfterMovingGC()
 {
-    Shape* shape = shape_;
+    Shape* shape = this->shape();
     if (IsForwarded(shape))
-        shape_.unsafeSet(Forwarded(shape));
+        shapeRef().unsafeSet(Forwarded(shape));
 }
 
 inline bool
 NativeObject::isInWholeCellBuffer() const
 {
     const gc::TenuredCell* cell = &asTenured();
     gc::ArenaCellSet* cells = cell->arena()->bufferedCells();
     return cells && cells->hasCell(cell);
@@ -659,24 +659,24 @@ NativeObject::setLastProperty(JSContext*
     MOZ_ASSERT(shape->zone() == zone());
     MOZ_ASSERT(shape->numFixedSlots() == numFixedSlots());
     MOZ_ASSERT(shape->getObjectClass() == getClass());
 
     size_t oldSpan = lastProperty()->slotSpan();
     size_t newSpan = shape->slotSpan();
 
     if (oldSpan == newSpan) {
-        shape_ = shape;
+        setShape(shape);
         return true;
     }
 
     if (MOZ_UNLIKELY(!updateSlotsForSpan(cx, oldSpan, newSpan)))
         return false;
 
-    shape_ = shape;
+    setShape(shape);
     return true;
 }
 
 inline js::gc::AllocKind
 NativeObject::allocKindForTenure() const
 {
     using namespace js::gc;
     AllocKind kind = GetGCObjectFixedSlotsKind(numFixedSlots());
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -185,17 +185,17 @@ js::NativeObject::checkShapeConsistency(
         }
 
         shape = lastProperty();
         while (shape) {
             MOZ_ASSERT_IF(!shape->isEmptyShape() && shape->isDataProperty(),
                           shape->slot() < slotSpan());
             if (!prev) {
                 MOZ_ASSERT(lastProperty() == shape);
-                MOZ_ASSERT(shape->listp == &shape_);
+                MOZ_ASSERT(shape->listp == &shapeRef());
             } else {
                 MOZ_ASSERT(shape->listp == &prev->parent);
             }
             prev = shape;
             shape = shape->parent;
         }
     } else {
         while (shape->parent) {
@@ -321,17 +321,17 @@ NativeObject::setLastPropertyShrinkFixed
     DebugOnly<size_t> oldFixed = numFixedSlots();
     DebugOnly<size_t> newFixed = shape->numFixedSlots();
     MOZ_ASSERT(newFixed < oldFixed);
     MOZ_ASSERT(shape->slotSpan() <= oldFixed);
     MOZ_ASSERT(shape->slotSpan() <= newFixed);
     MOZ_ASSERT(dynamicSlotsCount(oldFixed, shape->slotSpan(), getClass()) == 0);
     MOZ_ASSERT(dynamicSlotsCount(newFixed, shape->slotSpan(), getClass()) == 0);
 
-    shape_ = shape;
+    setShape(shape);
 }
 
 void
 NativeObject::setLastPropertyMakeNonNative(Shape* shape)
 {
     MOZ_ASSERT(!inDictionaryMode());
     MOZ_ASSERT(!shape->getObjectClass()->isNative());
     MOZ_ASSERT(shape->zone() == zone());
@@ -340,30 +340,30 @@ NativeObject::setLastPropertyMakeNonNati
 
     if (hasDynamicElements())
         js_free(getUnshiftedElementsHeader());
     if (hasDynamicSlots()) {
         js_free(slots_);
         slots_ = nullptr;
     }
 
-    shape_ = shape;
+    setShape(shape);
 }
 
 void
 NativeObject::setLastPropertyMakeNative(JSContext* cx, Shape* shape)
 {
     MOZ_ASSERT(getClass()->isNative());
     MOZ_ASSERT(shape->getObjectClass()->isNative());
     MOZ_ASSERT(!shape->inDictionary());
 
     // 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);
+    initShape(shape);
 
     slots_ = nullptr;
     elements_ = emptyObjectElements;
 
     size_t oldSpan = shape->numFixedSlots();
     size_t newSpan = shape->slotSpan();
 
     initializeSlotRange(0, oldSpan);
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -408,17 +408,17 @@ enum class DenseElementResult {
 enum class ShouldUpdateTypes {
     Update,
     DontUpdate
 };
 
 /*
  * NativeObject specifies the internal implementation of a native object.
  *
- * Native objects use ShapedObject::shape_ to record property information.  Two
+ * Native objects use ShapedObject::shape to record property information. Two
  * native objects with the same shape are guaranteed to have the same number of
  * fixed slots.
  *
  * Native objects extend the base implementation of an object with storage for
  * the object's named properties and indexed elements.
  *
  * These are stored separately from one another. Objects are followed by a
  * variable-sized array of values for inline storage, which may be used by
@@ -471,18 +471,18 @@ class NativeObject : public ShapedObject
         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_;
+        MOZ_ASSERT(shape());
+        return shape();
     }
 
     uint32_t propertyCount() const {
         return lastProperty()->entryCount();
     }
 
     bool hasShapeTable() const {
         return lastProperty()->hasTable();
@@ -736,17 +736,17 @@ class NativeObject : public ShapedObject
     }
 
     /*
      * The methods below shadow methods on JSObject and are more efficient for
      * known-native objects.
      */
     bool hasAllFlags(js::BaseShape::Flag flags) const {
         MOZ_ASSERT(flags);
-        return shape_->hasAllObjectFlags(flags);
+        return shape()->hasAllObjectFlags(flags);
     }
     bool nonProxyIsExtensible() const {
         return !hasAllFlags(js::BaseShape::NOT_EXTENSIBLE);
     }
 
     /*
      * Whether there may be indexed properties on this object, excluding any in
      * the object's elements.
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -78,25 +78,25 @@ ShapeTable::init(JSContext* cx, Shape* l
 
 void
 Shape::removeFromDictionary(NativeObject* obj)
 {
     MOZ_ASSERT(inDictionary());
     MOZ_ASSERT(obj->inDictionaryMode());
     MOZ_ASSERT(listp);
 
-    MOZ_ASSERT(obj->shape_->inDictionary());
-    MOZ_ASSERT(obj->shape_->listp == &obj->shape_);
+    MOZ_ASSERT(obj->shape()->inDictionary());
+    MOZ_ASSERT(obj->shape()->listp == obj->shapePtr());
 
     if (parent)
         parent->listp = listp;
     *listp = parent;
     listp = nullptr;
 
-    obj->shape_->clearCachedBigEnoughForShapeTable();
+    obj->shape()->clearCachedBigEnoughForShapeTable();
 }
 
 void
 Shape::insertIntoDictionary(GCPtrShape* dictp)
 {
     // Don't assert inDictionaryMode() here because we may be called from
     // JSObject::toDictionaryMode via JSObject::newDictionaryShape.
     MOZ_ASSERT(inDictionary());
@@ -336,17 +336,17 @@ NativeObject::getChildDataProperty(JSCon
         if (!shape)
             return nullptr;
         if (child.slot() >= obj->lastProperty()->base()->slotSpan()) {
             if (!obj->setSlotSpan(cx, child.slot() + 1)) {
                 new (shape) Shape(obj->lastProperty()->base()->unowned(), 0);
                 return nullptr;
             }
         }
-        shape->initDictionaryShape(child, obj->numFixedSlots(), &obj->shape_);
+        shape->initDictionaryShape(child, obj->numFixedSlots(), obj->shapePtr());
         return shape;
     }
 
     Shape* shape = cx->zone()->propertyTree().inlinedGetChild(cx, parent, child);
     if (!shape)
         return nullptr;
 
     MOZ_ASSERT(shape->parent == parent);
@@ -367,17 +367,17 @@ NativeObject::getChildAccessorProperty(J
     // Accessor properties have no slot, but slot_ will reflect that of parent.
     child.setSlot(parent->maybeSlot());
 
     if (obj->inDictionaryMode()) {
         MOZ_ASSERT(parent == obj->lastProperty());
         Shape* shape = Allocate<AccessorShape>(cx);
         if (!shape)
             return nullptr;
-        shape->initDictionaryShape(child, obj->numFixedSlots(), &obj->shape_);
+        shape->initDictionaryShape(child, obj->numFixedSlots(), obj->shapePtr());
         return shape;
     }
 
     Shape* shape = cx->zone()->propertyTree().inlinedGetChild(cx, parent, child);
     if (!shape)
         return nullptr;
 
     MOZ_ASSERT(shape->parent == parent);
@@ -432,18 +432,18 @@ js::NativeObject::toDictionaryMode(JSCon
     if (IsInsideNursery(obj) &&
         !cx->nursery().queueDictionaryModeObjectToSweep(obj))
     {
         ReportOutOfMemory(cx);
         return false;
     }
 
     MOZ_ASSERT(root->listp == nullptr);
-    root->listp = &obj->shape_;
-    obj->shape_ = root;
+    root->listp = obj->shapePtr();
+    obj->setShape(root);
 
     MOZ_ASSERT(obj->inDictionaryMode());
     root->base()->setSlotSpan(span);
 
     return true;
 }
 
 static bool
@@ -734,17 +734,17 @@ NativeObject::addEnumerableDataProperty(
         if (!shape)
             return nullptr;
         if (slot >= obj->lastProperty()->base()->slotSpan()) {
             if (MOZ_UNLIKELY(!obj->setSlotSpan(cx, slot + 1))) {
                 new (shape) Shape(obj->lastProperty()->base()->unowned(), 0);
                 return nullptr;
             }
         }
-        shape->initDictionaryShape(child, obj->numFixedSlots(), &obj->shape_);
+        shape->initDictionaryShape(child, obj->numFixedSlots(), obj->shapePtr());
     } else {
         uint32_t slot = obj->slotSpan();
         MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
         // Objects with many properties are converted to dictionary
         // mode, so we can't overflow SHAPE_MAXIMUM_SLOT here.
         MOZ_ASSERT(slot < JSSLOT_FREE(obj->getClass()) + PropertyTree::MAX_HEIGHT);
         MOZ_ASSERT(slot < SHAPE_MAXIMUM_SLOT);
 
@@ -1226,17 +1226,17 @@ NativeObject::clear(JSContext* cx, Handl
 
     while (shape->parent) {
         shape = shape->parent;
         MOZ_ASSERT(obj->inDictionaryMode() == shape->inDictionary());
     }
     MOZ_ASSERT(shape->isEmptyShape());
 
     if (obj->inDictionaryMode())
-        shape->listp = &obj->shape_;
+        shape->listp = obj->shapePtr();
 
     JS_ALWAYS_TRUE(obj->setLastProperty(cx, shape));
 
     obj->checkShapeConsistency();
 }
 
 /* static */ bool
 NativeObject::rollbackProperties(JSContext* cx, HandleNativeObject obj, uint32_t slotSpan)
@@ -1809,17 +1809,17 @@ Shape::fixupDictionaryShapeAfterMovingGC
         // listp points to the parent field of the next shape.
         Shape* next = reinterpret_cast<Shape*>(uintptr_t(listp) - offsetof(Shape, parent));
         if (gc::IsForwarded(next))
             listp = &gc::Forwarded(next)->parent;
     } else {
         // listp points to the shape_ field of an object.
         JSObject* last = reinterpret_cast<JSObject*>(uintptr_t(listp) - ShapedObject::offsetOfShape());
         if (gc::IsForwarded(last))
-            listp = &gc::Forwarded(last)->as<NativeObject>().shape_;
+            listp = gc::Forwarded(last)->as<NativeObject>().shapePtr();
     }
 }
 
 void
 Shape::fixupShapeTreeAfterMovingGC()
 {
     if (kids.isNull())
         return;
--- a/js/src/vm/ShapedObject.h
+++ b/js/src/vm/ShapedObject.h
@@ -19,33 +19,40 @@ namespace js {
  * never created as a most-derived class.
  */
 class ShapedObject : public JSObject
 {
   protected:
     // Property layout description and other state.
     GCPtrShape shape_;
 
+    MOZ_ALWAYS_INLINE const GCPtrShape& shapeRef() const {
+        return shape_;
+    }
+    MOZ_ALWAYS_INLINE GCPtrShape& shapeRef() {
+        return shape_;
+    }
+
+    // Used for GC tracing and Shape::listp
+    MOZ_ALWAYS_INLINE GCPtrShape* shapePtr() {
+        return &shape_;
+    }
+
   public:
     // Set the shape of an object. This pointer is valid for native objects and
     // some non-native objects. After creating an object, the objects for which
     // the shape pointer is invalid need to overwrite this pointer before a GC
     // can occur.
-    void initShape(Shape* shape) {
-        this->shape_.init(shape);
-    }
+    void initShape(Shape* shape) { shapeRef().init(shape); }
 
-    void setShape(Shape* shape) {
-        this->shape_ = shape;
-    }
-
-    Shape* shape() const { return this->shape_; }
+    void setShape(Shape* shape) { shapeRef() = shape; }
+    Shape* shape() const { return shapeRef(); }
 
     void traceShape(JSTracer* trc) {
-        TraceEdge(trc, &shape_, "shape");
+        TraceEdge(trc, shapePtr(), "shape");
     }
 
     static size_t offsetOfShape() { return offsetof(ShapedObject, shape_); }
 
   private:
     static void staticAsserts() {
         static_assert(offsetof(ShapedObject, shape_) == offsetof(shadow::Object, shape),
                       "shadow shape must match actual shape");
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -294,28 +294,27 @@ UnboxedPlainObject::getValue(const Unbox
 {
     uint8_t* p = &data_[property.offset];
     return GetUnboxedValue(p, property.type, maybeUninitialized);
 }
 
 void
 UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj)
 {
-    if (obj->as<UnboxedPlainObject>().expando_) {
-        TraceManuallyBarrieredEdge(trc,
-            reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_),
-            "unboxed_expando");
-    }
+    UnboxedPlainObject* uobj = &obj->as<UnboxedPlainObject>();
 
-    const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+    if (uobj->maybeExpando())
+        TraceManuallyBarrieredEdge(trc, uobj->addressOfExpando(), "unboxed_expando");
+
+    const UnboxedLayout& layout = uobj->layoutDontCheckGeneration();
     const int32_t* list = layout.traceList();
     if (!list)
         return;
 
-    uint8_t* data = obj->as<UnboxedPlainObject>().data();
+    uint8_t* data = uobj->data();
     while (*list != -1) {
         GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
         TraceEdge(trc, heap, "unboxed_string");
         list++;
     }
     list++;
     while (*list != -1) {
         GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
@@ -325,18 +324,18 @@ UnboxedPlainObject::trace(JSTracer* trc,
 
     // Unboxed objects don't have Values to trace.
     MOZ_ASSERT(*(list + 1) == -1);
 }
 
 /* static */ UnboxedExpandoObject*
 UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj)
 {
-    if (obj->expando_)
-        return obj->expando_;
+    if (obj->maybeExpando())
+        return obj->maybeExpando();
 
     UnboxedExpandoObject* expando =
         NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, gc::AllocKind::OBJECT4);
     if (!expando)
         return nullptr;
 
     // Don't track property types for expando objects. This allows Baseline
     // and Ion AddSlot ICs to guard on the unboxed group without guarding on
@@ -351,17 +350,17 @@ UnboxedPlainObject::ensureExpando(JSCont
 
     // As with setValue(), we need to manually trigger post barriers on the
     // whole object. If we treat the field as a GCPtrObject and later
     // convert the object to its native representation, we will end up with a
     // corrupted store buffer entry.
     if (IsInsideNursery(expando) && !IsInsideNursery(obj))
         cx->zone()->group()->storeBuffer().putWholeCell(obj);
 
-    obj->expando_ = expando;
+    obj->setExpandoUnsafe(expando);
     return expando;
 }
 
 bool
 UnboxedPlainObject::containsUnboxedOrExpandoProperty(JSContext* cx, jsid id) const
 {
     if (layoutDontCheckGeneration().lookup(id))
         return true;
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -270,16 +270,20 @@ class UnboxedPlainObject : public Unboxe
     uint8_t* data() {
         return &data_[0];
     }
 
     UnboxedExpandoObject* maybeExpando() const {
         return expando_;
     }
 
+    void setExpandoUnsafe(UnboxedExpandoObject* expando) {
+        expando_ = expando;
+    }
+
     void initExpando() {
         expando_ = nullptr;
     }
 
     // For use during GC.
     JSObject** addressOfExpando() {
         return reinterpret_cast<JSObject**>(&expando_);
     }