Bug 1458008 - Shuffle Shape flags around to avoid races with off-thread compilation. r=jonco
☠☠ backed out by 0b48238e5e77 ☠ ☠
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 01 May 2018 13:44:21 +0200
changeset 472566 1a97035892300bf1b50130dc89d3c10f59321011
parent 472565 587a7161ba5c84abce9c9fe1e39d8c50054c2f36
child 472567 f07ea68c0fef2cb271b08358edd2c0cf0fd0aa37
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1458008
milestone61.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 1458008 - Shuffle Shape flags around to avoid races with off-thread compilation. r=jonco
js/src/jit/MacroAssembler.cpp
js/src/jsfriendapi.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/Shape-inl.h
js/src/vm/Shape.cpp
js/src/vm/Shape.h
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1107,17 +1107,17 @@ MacroAssembler::newGCFatInlineString(Reg
     allocateString(result, temp, js::gc::AllocKind::FAT_INLINE_STRING,
                    attemptNursery ? gc::DefaultHeap : gc::TenuredHeap, fail);
 }
 
 void
 MacroAssembler::copySlotsFromTemplate(Register obj, const NativeObject* templateObj,
                                       uint32_t start, uint32_t end)
 {
-    uint32_t nfixed = Min(templateObj->numFixedSlotsForCompilation(), end);
+    uint32_t nfixed = Min(templateObj->numFixedSlots(), end);
     for (unsigned i = start; i < nfixed; i++) {
         // Template objects are not exposed to script and therefore immutable.
         // However, regexp template objects are sometimes used directly (when
         // the cloning is not observable), and therefore we can end up with a
         // non-zero lastIndex. Detect this case here and just substitute 0, to
         // avoid racing with the main thread updating this slot.
         Value v;
         if (templateObj->is<RegExpObject>() && i == RegExpObject::lastIndexSlot())
@@ -1416,17 +1416,17 @@ MacroAssembler::initGCThing(Register obj
             // then this would need to store emptyObjectElementsShared in that case.
             MOZ_ASSERT(!ntemplate->isSharedMemory());
 
             storePtr(ImmPtr(emptyObjectElements), Address(obj, NativeObject::offsetOfElements()));
 
             initGCSlots(obj, temp, ntemplate, initContents);
 
             if (ntemplate->hasPrivate() && !ntemplate->is<TypedArrayObject>()) {
-                uint32_t nfixed = ntemplate->numFixedSlotsForCompilation();
+                uint32_t nfixed = ntemplate->numFixedSlots();
                 Address privateSlot(obj, NativeObject::getPrivateDataOffset(nfixed));
                 if (ntemplate->is<RegExpObject>()) {
                     // RegExpObject stores a GC thing (RegExpShared*) in its
                     // private slot, so we have to use ImmGCPtr.
                     RegExpObject* regexp = &ntemplate->as<RegExpObject>();
                     MOZ_ASSERT(regexp->hasShared());
                     MOZ_ASSERT(ntemplate->getPrivate() == regexp->sharedRef().get());
                     storePtr(ImmGCPtr(regexp->sharedRef().get()), privateSlot);
@@ -3766,17 +3766,17 @@ MacroAssembler::debugAssertIsObject(cons
 
 void
 MacroAssembler::debugAssertObjHasFixedSlots(Register obj, Register scratch)
 {
 #ifdef DEBUG
     Label hasFixedSlots;
     loadPtr(Address(obj, ShapedObject::offsetOfShape()), scratch);
     branchTest32(Assembler::NonZero,
-                 Address(scratch, Shape::offsetOfSlotInfo()),
+                 Address(scratch, Shape::offsetOfImmutableFlags()),
                  Imm32(Shape::fixedSlotsMask()),
                  &hasFixedSlots);
     assumeUnreachable("Expected a fixed slot");
     bind(&hasFixedSlots);
 #endif
 }
 
 template <typename T, size_t N, typename P>
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -574,35 +574,38 @@ struct BaseShape {
     const js::Class* clasp_;
     JSObject* parent;
 };
 
 class Shape {
 public:
     shadow::BaseShape* base;
     jsid              _1;
-    uint32_t          slotInfo;
-
-    static const uint32_t FIXED_SLOTS_SHIFT = 27;
+    uint32_t          immutableFlags;
+
+    static const uint32_t FIXED_SLOTS_SHIFT = 24;
+    static const uint32_t FIXED_SLOTS_MASK = 0x1f << FIXED_SLOTS_SHIFT;
 };
 
 /**
  * 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;
 
     static const size_t MAX_FIXED_SLOTS = 16;
 
-    size_t numFixedSlots() const { return shape->slotInfo >> Shape::FIXED_SLOTS_SHIFT; }
+    size_t numFixedSlots() const {
+        return (shape->immutableFlags & Shape::FIXED_SLOTS_MASK) >> Shape::FIXED_SLOTS_SHIFT;
+    }
     JS::Value* fixedSlots() const {
         return (JS::Value*)(uintptr_t(this) + sizeof(shadow::Object));
     }
 
     JS::Value& slotRef(size_t slot) const {
         size_t nfixed = numFixedSlots();
         if (slot < nfixed)
             return fixedSlots()[slot];
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -287,34 +287,16 @@ js::NativeObject::lookup(JSContext* cx, 
 
 Shape*
 js::NativeObject::lookupPure(jsid id)
 {
     MOZ_ASSERT(isNative());
     return Shape::searchNoHashify(lastProperty(), id);
 }
 
-uint32_t
-js::NativeObject::numFixedSlotsForCompilation() const
-{
-    // This is an alternative method for getting the number of fixed slots in an
-    // object. It requires more logic and memory accesses than numFixedSlots()
-    // but is safe to be called from the compilation thread, even if the active
-    // thread is mutating the VM.
-
-    // The compiler does not have access to nursery things.
-    MOZ_ASSERT(!IsInsideNursery(this));
-
-    if (this->is<ArrayObject>())
-        return 0;
-
-    gc::AllocKind kind = asTenured().getAllocKind();
-    return gc::GetGCKindSlots(kind, getClass());
-}
-
 void
 NativeObject::setLastPropertyShrinkFixedSlots(Shape* shape)
 {
     MOZ_ASSERT(!inDictionaryMode());
     MOZ_ASSERT(!shape->inDictionary());
     MOZ_ASSERT(shape->zone() == zone());
     MOZ_ASSERT(lastProperty()->slotSpan() == shape->slotSpan());
     MOZ_ASSERT(shape->getObjectClass() == getClass());
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -712,17 +712,16 @@ class NativeObject : public ShapedObject
 
     uint32_t numFixedSlots() const {
         return reinterpret_cast<const shadow::Object*>(this)->numFixedSlots();
     }
     uint32_t numUsedFixedSlots() const {
         uint32_t nslots = lastProperty()->slotSpan(getClass());
         return Min(nslots, numFixedSlots());
     }
-    uint32_t numFixedSlotsForCompilation() const;
 
     uint32_t slotSpan() const {
         if (inDictionaryMode())
             return lastProperty()->base()->slotSpan();
         return lastProperty()->slotSpan();
     }
 
     /* Whether a slot is at a fixed offset from this object. */
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -176,17 +176,17 @@ AccessorShape::AccessorShape(const Stack
 
 inline void
 Shape::initDictionaryShape(const StackShape& child, uint32_t nfixed, GCPtrShape* dictp)
 {
     if (child.isAccessorShape())
         new (this) AccessorShape(child, nfixed);
     else
         new (this) Shape(child, nfixed);
-    this->flags |= IN_DICTIONARY;
+    this->immutableFlags |= IN_DICTIONARY;
 
     this->listp = nullptr;
     if (dictp)
         insertIntoDictionary(dictp);
 }
 
 template<class ObjectSubclass>
 /* static */ inline bool
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -938,17 +938,18 @@ NativeObject::putDataProperty(JSContext*
 
         if (updateLast)
             shape->base()->adoptUnowned(nbase);
         else
             shape->base_ = nbase;
 
         shape->setSlot(slot);
         shape->attrs = uint8_t(attrs);
-        shape->flags = Shape::IN_DICTIONARY;
+        shape->immutableFlags &= ~Shape::ACCESSOR_SHAPE;
+        shape->immutableFlags |= Shape::IN_DICTIONARY;
     } else {
         // Updating the last property in a non-dictionary-mode object. Find an
         // alternate shared child of the last property's previous shape.
 
         MOZ_ASSERT(shape == obj->lastProperty());
 
         // Find or create a property tree node labeled by our arguments.
         Rooted<StackShape> child(cx, StackShape(nbase, id, slot, attrs));
@@ -1032,17 +1033,17 @@ NativeObject::putAccessorProperty(JSCont
 
         if (updateLast)
             shape->base()->adoptUnowned(nbase);
         else
             shape->base_ = nbase;
 
         shape->setSlot(SHAPE_INVALID_SLOT);
         shape->attrs = uint8_t(attrs);
-        shape->flags = Shape::IN_DICTIONARY | Shape::ACCESSOR_SHAPE;
+        shape->immutableFlags |= Shape::IN_DICTIONARY | Shape::ACCESSOR_SHAPE;
 
         AccessorShape& accShape = shape->asAccessorShape();
         accShape.rawGetter = getter;
         accShape.rawSetter = setter;
         GetterSetterWriteBarrierPost(&accShape);
     } else {
         // Updating the last property in a non-dictionary-mode object. Find an
         // alternate shared child of the last property's previous shape.
@@ -1843,17 +1844,17 @@ Shape::fixupShapeTreeAfterMovingGC()
             getter = GetterOp(MaybeForwarded(key->getterObject()));
 
         SetterOp setter = key->setter();
         if (key->hasSetterObject())
             setter = SetterOp(MaybeForwarded(key->setterObject()));
 
         StackShape lookup(unowned,
                           const_cast<Shape*>(key)->propidRef(),
-                          key->slotInfo & Shape::SLOT_MASK,
+                          key->immutableFlags & Shape::SLOT_MASK,
                           key->attrs);
         lookup.updateGetterSetter(getter, setter);
         e.rekeyFront(lookup, key);
     }
 }
 
 void
 Shape::fixupAfterMovingGC()
@@ -1962,21 +1963,21 @@ Shape::dump(js::GenericPrinter& out) con
         DUMP_ATTR(READONLY, readonly);
         DUMP_ATTR(PERMANENT, permanent);
         DUMP_ATTR(GETTER, getter);
         DUMP_ATTR(SETTER, setter);
 #undef  DUMP_ATTR
         out.putChar(')');
     }
 
-    out.printf("flags %x ", flags);
-    if (flags) {
+    out.printf("immutableFlags %x ", immutableFlags);
+    if (immutableFlags) {
         int first = 1;
         out.putChar('(');
-#define DUMP_FLAG(name, display) if (flags & name) out.put(&(" " #display)[first]), first = 0
+#define DUMP_FLAG(name, display) if (immutableFlags & name) out.put(&(" " #display)[first]), first = 0
         DUMP_FLAG(IN_DICTIONARY, in_dictionary);
 #undef  DUMP_FLAG
         out.putChar(')');
     }
 }
 
 void
 Shape::dump() const
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -702,46 +702,62 @@ class Shape : public gc::TenuredCell
     friend struct StackShape;
     friend class JS::ubi::Concrete<Shape>;
     friend class js::gc::RelocationOverlay;
 
   protected:
     GCPtrBaseShape base_;
     PreBarrieredId propid_;
 
-    enum SlotInfo : uint32_t
+    // Flags that are not modified after the Shape is created. Off-thread Ion
+    // compilation can access the immutableFlags word, so we don't want any
+    // mutable state here to avoid (TSan) races.
+    enum ImmutableFlags : uint32_t
     {
-        /* Number of fixed slots in objects with this shape. */
-        // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store
+        // Mask to get the index in object slots for isDataProperty() shapes.
+        // For other shapes in the property tree with a parent, stores the
+        // parent's slot index (which may be invalid), and invalid for all
+        // other shapes.
+        SLOT_MASK              = JS_BIT(24) - 1,
+
+        // Number of fixed slots in objects with this shape.
+        // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store.
         FIXED_SLOTS_MAX        = 0x1f,
-        FIXED_SLOTS_SHIFT      = 27,
+        FIXED_SLOTS_SHIFT      = 24,
         FIXED_SLOTS_MASK       = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT),
 
-        /*
-         * numLinearSearches starts at zero and is incremented initially on
-         * search() calls. Once numLinearSearches reaches LINEAR_SEARCHES_MAX,
-         * the table is created on the next search() call. The table can also
-         * be created when hashifying for dictionary mode.
-         */
-        LINEAR_SEARCHES_MAX    = 0x7,
-        LINEAR_SEARCHES_SHIFT  = 24,
-        LINEAR_SEARCHES_MASK   = LINEAR_SEARCHES_MAX << LINEAR_SEARCHES_SHIFT,
+        // Property stored in per-object dictionary, not shared property tree.
+        IN_DICTIONARY          = 1 << 29,
 
-        /*
-         * Mask to get the index in object slots for isDataProperty() shapes.
-         * For other shapes in the property tree with a parent, stores the
-         * parent's slot index (which may be invalid), and invalid for all
-         * other shapes.
-         */
-        SLOT_MASK              = JS_BIT(24) - 1
+        // This shape is an AccessorShape, a fat Shape that can store
+        // getter/setter information.
+        ACCESSOR_SHAPE         = 1 << 30,
     };
 
-    uint32_t            slotInfo;       /* mask of above info */
+    // Flags stored in mutableFlags.
+    enum MutableFlags : uint8_t {
+        // numLinearSearches starts at zero and is incremented initially on
+        // search() calls. Once numLinearSearches reaches LINEAR_SEARCHES_MAX,
+        // the table is created on the next search() call. The table can also
+        // be created when hashifying for dictionary mode.
+        LINEAR_SEARCHES_MAX = 0x7,
+        LINEAR_SEARCHES_MASK = LINEAR_SEARCHES_MAX,
+
+        // Slotful property was stored to more than once. This is used as a
+        // hint for type inference.
+        OVERWRITTEN = 0x08,
+
+        // Flags used to speed up isBigEnoughForAShapeTable().
+        HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x10,
+        CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x20,
+    };
+
+    uint32_t            immutableFlags; /* immutable flags, see above */
     uint8_t             attrs;          /* attributes, see jsapi.h JSPROP_* */
-    uint8_t             flags;          /* flags, see below for defines */
+    uint8_t             mutableFlags;   /* mutable flags, see below for defines */
 
     GCPtrShape   parent;          /* parent node, reverse for..in order */
     /* kids is valid when !inDictionary(), listp is valid when inDictionary(). */
     union {
         KidsPointer kids;         /* null, single child, or a tagged ptr
                                      to many-kids data structure */
         GCPtrShape* listp;        /* dictionary list starting at shape_
                                      has a double-indirect back pointer,
@@ -832,18 +848,19 @@ class Shape : public gc::TenuredCell
                 info->shapesMallocHeapTreeTables += table->sizeOfIncludingThis(mallocSizeOf);
         }
 
         if (!inDictionary() && kids.isHash())
             info->shapesMallocHeapTreeKids += kids.toHash()->sizeOfIncludingThis(mallocSizeOf);
     }
 
     bool isAccessorShape() const {
-        MOZ_ASSERT_IF(flags & ACCESSOR_SHAPE, getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE);
-        return flags & ACCESSOR_SHAPE;
+        MOZ_ASSERT_IF(immutableFlags & ACCESSOR_SHAPE,
+                      getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE);
+        return immutableFlags & ACCESSOR_SHAPE;
     }
     AccessorShape& asAccessorShape() const {
         MOZ_ASSERT(isAccessorShape());
         return *(AccessorShape*)this;
     }
 
     const GCPtrShape& previous() const { return parent; }
 
@@ -888,42 +905,16 @@ class Shape : public gc::TenuredCell
     uint32_t getObjectFlags() const { return base()->getObjectFlags(); }
     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.
-     */
-    enum {
-        /* Property stored in per-object dictionary, not shared property tree. */
-        IN_DICTIONARY   = 0x01,
-
-        /*
-         * Slotful property was stored to more than once. This is used as a
-         * hint for type inference.
-         */
-        OVERWRITTEN     = 0x02,
-
-        /*
-         * This shape is an AccessorShape, a fat Shape that can store
-         * getter/setter information.
-         */
-        ACCESSOR_SHAPE  = 0x04,
-
-        /* Flags used to speed up isBigEnoughForAShapeTable(). */
-        HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x08,
-        CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x10,
-    };
-
     /* Get a shape identical to this one, without parent/kids information. */
     inline Shape(const StackShape& other, uint32_t nfixed);
 
     /* Used by EmptyShape (see jsscopeinlines.h). */
     inline Shape(UnownedBaseShape* base, uint32_t nfixed);
 
     /* Copy constructor disabled, to avoid misuse of the above form. */
     Shape(const Shape& other) = delete;
@@ -937,17 +928,17 @@ class Shape : public gc::TenuredCell
      * isDataProperty() if the shape is being constructed and has not had a slot
      * assigned yet. After construction, isDataProperty() implies
      * !hasMissingSlot().
      */
     bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
 
   public:
     bool inDictionary() const {
-        return (flags & IN_DICTIONARY) != 0;
+        return immutableFlags & IN_DICTIONARY;
     }
 
     inline GetterOp getter() const;
     bool hasDefaultGetter() const { return !getter(); }
     GetterOp getterOp() const { MOZ_ASSERT(!hasGetterValue()); return getter(); }
     inline JSObject* getterObject() const;
     bool hasGetterObject() const { return hasGetterValue() && getterObject(); }
 
@@ -977,20 +968,20 @@ class Shape : public gc::TenuredCell
         return UndefinedValue();
     }
 
     Value setterOrUndefined() const {
         return hasSetterValue() ? setterValue() : UndefinedValue();
     }
 
     void setOverwritten() {
-        flags |= OVERWRITTEN;
+        mutableFlags |= OVERWRITTEN;
     }
     bool hadOverwrite() const {
-        return flags & OVERWRITTEN;
+        return mutableFlags & OVERWRITTEN;
     }
 
     bool matches(const Shape* other) const {
         return propid_.get() == other->propid_.get() &&
                matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs,
                                     other->getter(), other->setter());
     }
 
@@ -1013,17 +1004,17 @@ class Shape : public gc::TenuredCell
     }
 
     bool isDataProperty() const {
         MOZ_ASSERT(!isEmptyShape());
         return isDataProperty(attrs, getter(), setter());
     }
     uint32_t slot() const { MOZ_ASSERT(isDataProperty() && !hasMissingSlot()); return maybeSlot(); }
     uint32_t maybeSlot() const {
-        return slotInfo & SLOT_MASK;
+        return immutableFlags & SLOT_MASK;
     }
 
     bool isEmptyShape() const {
         MOZ_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot());
         return JSID_IS_EMPTY(propid_);
     }
 
     uint32_t slotSpan(const Class* clasp) const {
@@ -1036,39 +1027,37 @@ class Shape : public gc::TenuredCell
     }
 
     uint32_t slotSpan() const {
         return slotSpan(getObjectClass());
     }
 
     void setSlot(uint32_t slot) {
         MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
-        slotInfo = slotInfo & ~Shape::SLOT_MASK;
-        slotInfo = slotInfo | slot;
+        immutableFlags = (immutableFlags & ~Shape::SLOT_MASK) | slot;
     }
 
     uint32_t numFixedSlots() const {
-        return slotInfo >> FIXED_SLOTS_SHIFT;
+        return (immutableFlags & FIXED_SLOTS_MASK) >> FIXED_SLOTS_SHIFT;
     }
 
     void setNumFixedSlots(uint32_t nfixed) {
         MOZ_ASSERT(nfixed < FIXED_SLOTS_MAX);
-        slotInfo = slotInfo & ~FIXED_SLOTS_MASK;
-        slotInfo = slotInfo | (nfixed << FIXED_SLOTS_SHIFT);
+        immutableFlags = immutableFlags & ~FIXED_SLOTS_MASK;
+        immutableFlags = immutableFlags | (nfixed << FIXED_SLOTS_SHIFT);
     }
 
     uint32_t numLinearSearches() const {
-        return (slotInfo & LINEAR_SEARCHES_MASK) >> LINEAR_SEARCHES_SHIFT;
+        return mutableFlags & LINEAR_SEARCHES_MASK;
     }
 
     void incrementNumLinearSearches() {
         uint32_t count = numLinearSearches();
         MOZ_ASSERT(count < LINEAR_SEARCHES_MAX);
-        slotInfo = slotInfo & ~LINEAR_SEARCHES_MASK;
-        slotInfo = slotInfo | ((count + 1) << LINEAR_SEARCHES_SHIFT);
+        mutableFlags = (mutableFlags & ~LINEAR_SEARCHES_MASK) | (count + 1);
     }
 
     const PreBarrieredId& propid() const {
         MOZ_ASSERT(!isEmptyShape());
         MOZ_ASSERT(!JSID_IS_VOID(propid_));
         return propid_;
     }
     PreBarrieredId& propidRef() { MOZ_ASSERT(!JSID_IS_VOID(propid_)); return propid_; }
@@ -1111,38 +1100,38 @@ class Shape : public gc::TenuredCell
         for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) {
             ++count;
             if (count >= ShapeTable::MIN_ENTRIES)
                 return true;
         }
         return false;
     }
     void clearCachedBigEnoughForShapeTable() {
-        flags &= ~(HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE | CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE);
+        mutableFlags &= ~(HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE | CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE);
     }
 
   public:
     bool isBigEnoughForAShapeTable() {
         MOZ_ASSERT(!hasTable());
 
         // isBigEnoughForAShapeTableSlow is pretty inefficient so we only call
         // it once and cache the result.
 
-        if (flags & HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE) {
-            bool res = flags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
+        if (mutableFlags & HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE) {
+            bool res = mutableFlags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
             MOZ_ASSERT(res == isBigEnoughForAShapeTableSlow());
             return res;
         }
 
-        MOZ_ASSERT(!(flags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE));
+        MOZ_ASSERT(!(mutableFlags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE));
 
         bool res = isBigEnoughForAShapeTableSlow();
         if (res)
-            flags |= CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
-        flags |= HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
+            mutableFlags |= CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
+        mutableFlags |= HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE;
         return res;
     }
 
 #ifdef DEBUG
     void dump(js::GenericPrinter& out) const;
     void dump() const;
     void dumpSubtree(int level, js::GenericPrinter& out) const;
 #endif
@@ -1159,32 +1148,33 @@ class Shape : public gc::TenuredCell
     MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
 
     void fixupAfterMovingGC();
     void fixupGetterSetterForBarrier(JSTracer* trc);
     void updateBaseShapeAfterMovingGC();
 
 #ifdef DEBUG
     // For JIT usage.
-    static inline size_t offsetOfSlotInfo() { return offsetof(Shape, slotInfo); }
+    static inline size_t offsetOfImmutableFlags() { return offsetof(Shape, immutableFlags); }
     static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
 #endif
 
   private:
     void fixupDictionaryShapeAfterMovingGC();
     void fixupShapeTreeAfterMovingGC();
 
     static Shape* fromParentFieldPointer(uintptr_t p) {
         return reinterpret_cast<Shape*>(p - offsetof(Shape, parent));
     }
 
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base));
-        JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo));
+        JS_STATIC_ASSERT(offsetof(Shape, immutableFlags) == offsetof(js::shadow::Shape, immutableFlags));
         JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
+        JS_STATIC_ASSERT(FIXED_SLOTS_MASK == js::shadow::Shape::FIXED_SLOTS_MASK);
     }
 };
 
 /* Fat Shape used for accessor properties. */
 class AccessorShape : public Shape
 {
     friend class Shape;
     friend class NativeObject;
@@ -1435,77 +1425,80 @@ using InitialShapeSet = JS::WeakCache<JS
 
 struct StackShape
 {
     /* For performance, StackShape only roots when absolutely necessary. */
     UnownedBaseShape* base;
     jsid propid;
     GetterOp rawGetter;
     SetterOp rawSetter;
-    uint32_t slot_;
+    uint32_t immutableFlags;
     uint8_t attrs;
-    uint8_t flags;
+    uint8_t mutableFlags;
 
     explicit StackShape(UnownedBaseShape* base, jsid propid, uint32_t slot,
                         unsigned attrs)
       : base(base),
         propid(propid),
         rawGetter(nullptr),
         rawSetter(nullptr),
-        slot_(slot),
+        immutableFlags(slot),
         attrs(uint8_t(attrs)),
-        flags(0)
+        mutableFlags(0)
     {
         MOZ_ASSERT(base);
         MOZ_ASSERT(!JSID_IS_VOID(propid));
         MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
     }
 
     explicit StackShape(Shape* shape)
       : base(shape->base()->unowned()),
         propid(shape->propidRef()),
         rawGetter(shape->getter()),
         rawSetter(shape->setter()),
-        slot_(shape->maybeSlot()),
+        immutableFlags(shape->immutableFlags),
         attrs(shape->attrs),
-        flags(shape->flags)
+        mutableFlags(shape->mutableFlags)
     {}
 
     void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) {
         if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER|JSPROP_SETTER)))
-            flags |= Shape::ACCESSOR_SHAPE;
+            immutableFlags |= Shape::ACCESSOR_SHAPE;
         else
-            flags &= ~Shape::ACCESSOR_SHAPE;
+            immutableFlags &= ~Shape::ACCESSOR_SHAPE;
 
         this->rawGetter = rawGetter;
         this->rawSetter = rawSetter;
     }
 
     bool isDataProperty() const {
         MOZ_ASSERT(!JSID_IS_EMPTY(propid));
         return Shape::isDataProperty(attrs, rawGetter, rawSetter);
     }
     bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
 
-    uint32_t slot() const { MOZ_ASSERT(isDataProperty() && !hasMissingSlot()); return slot_; }
-    uint32_t maybeSlot() const { return slot_; }
+    uint32_t slot() const {
+        MOZ_ASSERT(isDataProperty() && !hasMissingSlot());
+        return maybeSlot();
+    }
+    uint32_t maybeSlot() const { return immutableFlags & Shape::SLOT_MASK; }
 
     void setSlot(uint32_t slot) {
         MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT);
-        slot_ = slot;
+        immutableFlags = (immutableFlags & ~Shape::SLOT_MASK) | slot;
     }
 
     bool isAccessorShape() const {
-        return flags & Shape::ACCESSOR_SHAPE;
+        return immutableFlags & Shape::ACCESSOR_SHAPE;
     }
 
     HashNumber hash() const {
         HashNumber hash = HashId(propid);
         return mozilla::AddToHash(hash,
-                   mozilla::HashGeneric(base, attrs, slot_, rawGetter, rawSetter));
+                   mozilla::HashGeneric(base, attrs, maybeSlot(), rawGetter, rawSetter));
     }
 
     // Traceable implementation.
     static void trace(StackShape* stackShape, JSTracer* trc) { stackShape->trace(trc); }
     void trace(JSTracer* trc);
 };
 
 template <typename Wrapper>
@@ -1537,21 +1530,23 @@ class MutableWrappedPtrOperations<StackS
     void setBase(UnownedBaseShape* base) { ss().base = base; }
     void setAttrs(uint8_t attrs) { ss().attrs = attrs; }
 };
 
 inline
 Shape::Shape(const StackShape& other, uint32_t nfixed)
   : base_(other.base),
     propid_(other.propid),
-    slotInfo(other.maybeSlot() | (nfixed << FIXED_SLOTS_SHIFT)),
+    immutableFlags(other.immutableFlags),
     attrs(other.attrs),
-    flags(other.flags),
+    mutableFlags(other.mutableFlags),
     parent(nullptr)
 {
+    setNumFixedSlots(nfixed);
+
 #ifdef DEBUG
     gc::AllocKind allocKind = getAllocKind();
     MOZ_ASSERT_IF(other.isAccessorShape(), allocKind == gc::AllocKind::ACCESSOR_SHAPE);
     MOZ_ASSERT_IF(allocKind == gc::AllocKind::SHAPE, !other.isAccessorShape());
 #endif
 
     MOZ_ASSERT_IF(!isEmptyShape(), AtomIsMarked(zone(), propid()));
 
@@ -1569,19 +1564,19 @@ class NurseryShapesRef : public gc::Buff
     explicit NurseryShapesRef(Zone* zone) : zone_(zone) {}
     void trace(JSTracer* trc) override;
 };
 
 inline
 Shape::Shape(UnownedBaseShape* base, uint32_t nfixed)
   : base_(base),
     propid_(JSID_EMPTY),
-    slotInfo(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)),
+    immutableFlags(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)),
     attrs(0),
-    flags(0),
+    mutableFlags(0),
     parent(nullptr)
 {
     MOZ_ASSERT(base);
     kids.setNull();
 }
 
 inline GetterOp
 Shape::getter() const
@@ -1620,17 +1615,17 @@ Shape::searchLinear(jsid id)
 
     return nullptr;
 }
 
 inline bool
 Shape::matches(const StackShape& other) const
 {
     return propid_.get() == other.propid &&
-           matchesParamsAfterId(other.base, other.slot_, other.attrs,
+           matchesParamsAfterId(other.base, other.maybeSlot(), other.attrs,
                                 other.rawGetter, other.rawSetter);
 }
 
 Shape*
 ReshapeForAllocKind(JSContext* cx, Shape* shape, TaggedProto proto,
                     gc::AllocKind allocKind);
 
 } // namespace js