tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Shape.h (32734B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef vm_Shape_h
      8 #define vm_Shape_h
      9 
     10 #include "js/shadow/Shape.h"  // JS::shadow::Shape, JS::shadow::BaseShape
     11 
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/MemoryReporting.h"
     14 
     15 #include "jstypes.h"
     16 #include "NamespaceImports.h"
     17 
     18 #include "gc/Barrier.h"
     19 #include "gc/MaybeRooted.h"
     20 #include "js/HashTable.h"
     21 #include "js/Id.h"  // JS::PropertyKey
     22 #include "js/MemoryMetrics.h"
     23 #include "js/Printer.h"  // js::GenericPrinter
     24 #include "js/RootingAPI.h"
     25 #include "js/UbiNode.h"
     26 #include "util/EnumFlags.h"
     27 #include "vm/ObjectFlags.h"
     28 #include "vm/PropertyInfo.h"
     29 #include "vm/PropMap.h"
     30 #include "vm/TaggedProto.h"
     31 
     32 // [SMDOC] Shapes
     33 //
     34 // A Shape represents the layout of an object. It stores and implies:
     35 //
     36 //  * The object's JSClass, Realm, prototype (see BaseShape section below).
     37 //  * The object's flags (ObjectFlags).
     38 //  * For native objects, the object's properties (PropMap and map length).
     39 //  * For native objects, the fixed slot capacity of the object (numFixedSlots).
     40 //
     41 // For native objects, the shape implies the property structure (keys,
     42 // attributes, property order for enumeration) but not the property values.
     43 // The values are stored in object slots.
     44 //
     45 // Every JSObject has a pointer, |shape_|, accessible via shape(), to the
     46 // current shape of the object. This pointer permits fast object layout tests.
     47 //
     48 // Shapes use the following C++ class hierarchy:
     49 //
     50 // C++ Type                     Used by
     51 // ============================ ====================================
     52 // Shape (abstract)             JSObject
     53 //  |
     54 //  +-- NativeShape (abstract)  NativeObject
     55 //  |    |
     56 //  |    +-- SharedShape        NativeObject with a shared shape
     57 //  |    |
     58 //  |    +-- DictionaryShape    NativeObject with a dictionary shape
     59 //  |
     60 //  +-- ProxyShape              ProxyObject
     61 //  |
     62 //  +-- WasmGCShape             WasmGCObject
     63 //
     64 // Classes marked with (abstract) above are not literally C++ Abstract Base
     65 // Classes (since there are no virtual functions, pure or not, in this
     66 // hierarchy), but have the same meaning: there are no shapes with this type as
     67 // its most-derived type.
     68 //
     69 // SharedShape
     70 // ===========
     71 // Used only for native objects. This is either an initial shape (no property
     72 // map) or SharedPropMap shape (for objects with at least one property).
     73 //
     74 // These are immutable tuples stored in a hash table, so that objects with the
     75 // same structure end up with the same shape (this both saves memory and allows
     76 // JIT optimizations based on this shape).
     77 //
     78 // To avoid hash table lookups on the hot addProperty path, shapes have a
     79 // ShapeCachePtr that's used as cache for this. This cache is purged on GC.
     80 // The shape cache is also used as cache for prototype shapes, to point to the
     81 // initial shape for objects using that shape, and for cached iterators.
     82 //
     83 // DictionaryShape
     84 // ===============
     85 // Used only for native objects. An object with a dictionary shape is "in
     86 // dictionary mode". Certain property operations are not supported for shared
     87 // maps so in these cases we need to convert the object to dictionary mode by
     88 // creating a dictionary property map and a dictionary shape. An object is
     89 // converted to dictionary mode in the following cases:
     90 //
     91 // - Changing a property's flags/attributes and the property is not the last
     92 //   property.
     93 // - Removing a property other than the object's last property.
     94 // - The object has many properties. See maybeConvertToDictionaryForAdd for the
     95 //   heuristics.
     96 //
     97 // Dictionary shapes are unshared, private to a single object, and always have a
     98 // a DictionaryPropMap that's similarly unshared. Dictionary shape mutations do
     99 // require allocating a new dictionary shape for the object, to properly
    100 // invalidate JIT inline caches and other shape guards.
    101 // See NativeObject::generateNewDictionaryShape.
    102 //
    103 // ProxyShape
    104 // ==========
    105 // Shape used for proxy objects (including wrappers). Proxies with the same
    106 // JSClass, Realm, prototype and ObjectFlags will have the same shape.
    107 //
    108 // WasmGCShape
    109 // ===========
    110 // Shape used for Wasm GC objects. Wasm GC objects with the same JSClass, Realm,
    111 // prototype and ObjectFlags will have the same shape.
    112 //
    113 // BaseShape
    114 // =========
    115 // Because many Shapes have similar data, there is actually a secondary type
    116 // called a BaseShape that holds some of a Shape's data (the JSClass, Realm,
    117 // prototype). Many shapes can share a single BaseShape.
    118 
    119 MOZ_ALWAYS_INLINE size_t JSSLOT_FREE(const JSClass* clasp) {
    120  // Proxy classes have reserved slots, but proxies manage their own slot
    121  // layout.
    122  MOZ_ASSERT(!clasp->isProxyObject());
    123  return JSCLASS_RESERVED_SLOTS(clasp);
    124 }
    125 
    126 namespace js {
    127 
    128 class JSONPrinter;
    129 class NativeShape;
    130 class Shape;
    131 class PropertyIteratorObject;
    132 
    133 namespace gc {
    134 class TenuringTracer;
    135 }  // namespace gc
    136 
    137 namespace wasm {
    138 class RecGroup;
    139 }  // namespace wasm
    140 
    141 // Hash policy for ShapeCachePtr's ShapeSetForAdd. Maps the new property key and
    142 // flags to the new shape.
    143 struct ShapeForAddHasher : public DefaultHasher<Shape*> {
    144  using Key = SharedShape*;
    145 
    146  struct Lookup {
    147    PropertyKey key;
    148    PropertyFlags flags;
    149 
    150    Lookup(PropertyKey key, PropertyFlags flags) : key(key), flags(flags) {}
    151  };
    152 
    153  static MOZ_ALWAYS_INLINE HashNumber hash(const Lookup& l);
    154  static MOZ_ALWAYS_INLINE bool match(SharedShape* shape, const Lookup& l);
    155 };
    156 using ShapeSetForAdd =
    157    HashSet<SharedShape*, ShapeForAddHasher, SystemAllocPolicy>;
    158 
    159 // Each shape has a cache pointer that's either:
    160 //
    161 // * None
    162 // * For shared shapes, a single shape used to speed up addProperty.
    163 // * For shared shapes, a set of shapes used to speed up addProperty.
    164 // * For prototype shapes, the most recently used initial shape allocated for a
    165 //   prototype object with this shape.
    166 // * For any shape, a PropertyIteratorObject used to speed up GetIterator.
    167 //
    168 // The cache is purely an optimization and is purged on GC (all shapes with a
    169 // non-None ShapeCachePtr are added to a vector in the Zone).
    170 class ShapeCachePtr {
    171  enum {
    172    SINGLE_SHAPE_FOR_ADD = 0,
    173    SHAPE_SET_FOR_ADD = 1,
    174    SHAPE_WITH_PROTO = 2,
    175    ITERATOR = 3,
    176    MASK = 3
    177  };
    178 
    179  uintptr_t bits = 0;
    180 
    181 public:
    182  bool isNone() const { return !bits; }
    183  void setNone() { bits = 0; }
    184 
    185  bool isSingleShapeForAdd() const {
    186    return (bits & MASK) == SINGLE_SHAPE_FOR_ADD && !isNone();
    187  }
    188  SharedShape* toSingleShapeForAdd() const {
    189    MOZ_ASSERT(isSingleShapeForAdd());
    190    return reinterpret_cast<SharedShape*>(bits & ~uintptr_t(MASK));
    191  }
    192  void setSingleShapeForAdd(SharedShape* shape) {
    193    MOZ_ASSERT(shape);
    194    MOZ_ASSERT((uintptr_t(shape) & MASK) == 0);
    195    MOZ_ASSERT(!isShapeSetForAdd());  // Don't leak the ShapeSet.
    196    bits = uintptr_t(shape) | SINGLE_SHAPE_FOR_ADD;
    197  }
    198 
    199  bool isShapeSetForAdd() const { return (bits & MASK) == SHAPE_SET_FOR_ADD; }
    200  ShapeSetForAdd* toShapeSetForAdd() const {
    201    MOZ_ASSERT(isShapeSetForAdd());
    202    return reinterpret_cast<ShapeSetForAdd*>(bits & ~uintptr_t(MASK));
    203  }
    204  void setShapeSetForAdd(ShapeSetForAdd* hash) {
    205    MOZ_ASSERT(hash);
    206    MOZ_ASSERT((uintptr_t(hash) & MASK) == 0);
    207    bits = uintptr_t(hash) | SHAPE_SET_FOR_ADD;
    208  }
    209 
    210  bool isForAdd() const { return isSingleShapeForAdd() || isShapeSetForAdd(); }
    211 
    212  bool isShapeWithProto() const { return (bits & MASK) == SHAPE_WITH_PROTO; }
    213  SharedShape* toShapeWithProto() const {
    214    MOZ_ASSERT(isShapeWithProto());
    215    return reinterpret_cast<SharedShape*>(bits & ~uintptr_t(MASK));
    216  }
    217  void setShapeWithProto(SharedShape* shape) {
    218    MOZ_ASSERT(shape);
    219    MOZ_ASSERT((uintptr_t(shape) & MASK) == 0);
    220    MOZ_ASSERT(!isShapeSetForAdd());  // Don't leak the ShapeSet.
    221    bits = uintptr_t(shape) | SHAPE_WITH_PROTO;
    222  }
    223 
    224  bool isIterator() const { return (bits & MASK) == ITERATOR; }
    225  PropertyIteratorObject* toIterator() const {
    226    MOZ_ASSERT(isIterator());
    227    return reinterpret_cast<PropertyIteratorObject*>(bits & ~uintptr_t(MASK));
    228  }
    229  void setIterator(PropertyIteratorObject* iter) {
    230    MOZ_ASSERT(iter);
    231    MOZ_ASSERT((uintptr_t(iter) & MASK) == 0);
    232    MOZ_ASSERT(!isShapeSetForAdd());  // Don't leak the ShapeSet.
    233    bits = uintptr_t(iter) | ITERATOR;
    234  }
    235  friend class js::jit::MacroAssembler;
    236 } JS_HAZ_GC_POINTER;
    237 
    238 // BaseShapes store the object's class, realm and prototype. BaseShapes are
    239 // immutable tuples stored in a per-Zone hash table.
    240 class BaseShape : public gc::TenuredCellWithNonGCPointer<const JSClass> {
    241 public:
    242  /* Class of referring object, stored in the cell header */
    243  const JSClass* clasp() const { return headerPtr(); }
    244 
    245 private:
    246  JS::Realm* realm_;
    247  GCPtr<TaggedProto> proto_;
    248 
    249  BaseShape(const BaseShape& base) = delete;
    250  BaseShape& operator=(const BaseShape& other) = delete;
    251 
    252 public:
    253  BaseShape(JSContext* cx, const JSClass* clasp, JS::Realm* realm,
    254            TaggedProto proto);
    255 
    256  /* Not defined: BaseShapes must not be stack allocated. */
    257  ~BaseShape() = delete;
    258 
    259  JS::Realm* realm() const { return realm_; }
    260  JS::Compartment* compartment() const {
    261    return JS::GetCompartmentForRealm(realm());
    262  }
    263  JS::Compartment* maybeCompartment() const { return compartment(); }
    264 
    265  TaggedProto proto() const { return proto_; }
    266 
    267  /*
    268   * Lookup base shapes from the zone's baseShapes table, adding if not
    269   * already found.
    270   */
    271  static BaseShape* get(JSContext* cx, const JSClass* clasp, JS::Realm* realm,
    272                        Handle<TaggedProto> proto);
    273 
    274  static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape;
    275 
    276  void traceChildren(JSTracer* trc);
    277 
    278  static constexpr size_t offsetOfClasp() { return offsetOfHeaderPtr(); }
    279 
    280  static constexpr size_t offsetOfRealm() {
    281    return offsetof(BaseShape, realm_);
    282  }
    283 
    284  static constexpr size_t offsetOfProto() {
    285    return offsetof(BaseShape, proto_);
    286  }
    287 
    288 private:
    289  static void staticAsserts() {
    290    static_assert(offsetOfClasp() == offsetof(JS::shadow::BaseShape, clasp));
    291    static_assert(offsetOfRealm() == offsetof(JS::shadow::BaseShape, realm));
    292    static_assert(sizeof(BaseShape) % gc::CellAlignBytes == 0,
    293                  "Things inheriting from gc::Cell must have a size that's "
    294                  "a multiple of gc::CellAlignBytes");
    295    // Sanity check BaseShape size is what we expect.
    296 #ifdef JS_64BIT
    297    static_assert(sizeof(BaseShape) == 3 * sizeof(void*));
    298 #else
    299    static_assert(sizeof(BaseShape) == 4 * sizeof(void*));
    300 #endif
    301  }
    302 
    303 public:
    304 #if defined(DEBUG) || defined(JS_JITSPEW)
    305  void dump() const;
    306  void dump(js::GenericPrinter& out) const;
    307  void dump(js::JSONPrinter& json) const;
    308 
    309  void dumpFields(js::JSONPrinter& json) const;
    310 #endif
    311 };
    312 
    313 class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
    314  friend class ::JSObject;
    315  friend class ::JSFunction;
    316  friend class GCMarker;
    317  friend class NativeObject;
    318  friend class SharedShape;
    319  friend class PropertyTree;
    320  friend class gc::TenuringTracer;
    321  friend class JS::ubi::Concrete<Shape>;
    322  friend class gc::RelocationOverlay;
    323 
    324 public:
    325  // Base shape, stored in the cell header.
    326  BaseShape* base() const { return headerPtr(); }
    327 
    328  using Kind = JS::shadow::Shape::Kind;
    329 
    330 protected:
    331  // Flags that are not modified after the Shape is created. Off-thread Ion
    332  // compilation can access the immutableFlags word, so we don't want any
    333  // mutable state here to avoid (TSan) races.
    334  enum ImmutableFlags : uint32_t {
    335    // For NativeShape: the length associated with the property map. This is a
    336    // value in the range [0, PropMap::Capacity]. A length of 0 indicates the
    337    // object is empty (has no properties).
    338    MAP_LENGTH_MASK = BitMask(4),
    339 
    340    // The Shape Kind. The NativeObject kinds have the low bit set.
    341    KIND_SHIFT = 4,
    342    KIND_MASK = 0b11,
    343    IS_NATIVE_BIT = 0x1 << KIND_SHIFT,
    344 
    345    // For NativeShape: the number of fixed slots in objects with this shape.
    346    // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store.
    347    FIXED_SLOTS_MAX = 0x1f,
    348    FIXED_SLOTS_SHIFT = 6,
    349    FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT),
    350 
    351    // For SharedShape: the slot span of the object, if it fits in a single
    352    // byte. If the value is SMALL_SLOTSPAN_MAX, the slot span has to be
    353    // computed based on the property map (which is slower).
    354    //
    355    // Note: NativeObject::addProperty will convert to dictionary mode before we
    356    // reach this limit, but there are other places where we add properties to
    357    // shapes, for example environment object shapes.
    358    SMALL_SLOTSPAN_MAX = 0x3ff,  // 10 bits.
    359    SMALL_SLOTSPAN_SHIFT = 11,
    360    SMALL_SLOTSPAN_MASK = uint32_t(SMALL_SLOTSPAN_MAX << SMALL_SLOTSPAN_SHIFT),
    361  };
    362 
    363  uint32_t immutableFlags;   // Immutable flags, see above.
    364  ObjectFlags objectFlags_;  // Immutable object flags, see ObjectFlags.
    365 
    366  // Cache used to speed up common operations on shapes.
    367  ShapeCachePtr cache_;
    368 
    369  // Give the object a shape that's similar to its current shape, but with the
    370  // passed objectFlags, proto, and nfixed values.
    371  static bool replaceShape(JSContext* cx, HandleObject obj,
    372                           ObjectFlags objectFlags, TaggedProto proto,
    373                           uint32_t nfixed);
    374 
    375 public:
    376  void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
    377                              JS::ShapeInfo* info) const {
    378    if (cache_.isShapeSetForAdd()) {
    379      info->shapesMallocHeapCache +=
    380          cache_.toShapeSetForAdd()->shallowSizeOfIncludingThis(mallocSizeOf);
    381    }
    382  }
    383 
    384  ShapeCachePtr& cacheRef() { return cache_; }
    385  ShapeCachePtr cache() const { return cache_; }
    386 
    387  void maybeCacheIterator(JSContext* cx, PropertyIteratorObject* iter);
    388 
    389  const JSClass* getObjectClass() const { return base()->clasp(); }
    390  JS::Realm* realm() const { return base()->realm(); }
    391 
    392  JS::Compartment* compartment() const { return base()->compartment(); }
    393  JS::Compartment* maybeCompartment() const {
    394    return base()->maybeCompartment();
    395  }
    396 
    397  TaggedProto proto() const { return base()->proto(); }
    398 
    399  ObjectFlags objectFlags() const { return objectFlags_; }
    400  bool hasObjectFlag(ObjectFlag flag) const {
    401    return objectFlags_.hasFlag(flag);
    402  }
    403 
    404 protected:
    405  Shape(Kind kind, BaseShape* base, ObjectFlags objectFlags)
    406      : CellWithTenuredGCPointer(base),
    407        immutableFlags(uint32_t(kind) << KIND_SHIFT),
    408        objectFlags_(objectFlags) {
    409    MOZ_ASSERT(base);
    410    MOZ_ASSERT(this->kind() == kind, "kind must fit in KIND_MASK");
    411    MOZ_ASSERT(isNative() == base->clasp()->isNativeObject());
    412  }
    413 
    414  Shape(const Shape& other) = delete;
    415 
    416 public:
    417  Kind kind() const { return Kind((immutableFlags >> KIND_SHIFT) & KIND_MASK); }
    418 
    419  bool isNative() const {
    420    // Note: this is equivalent to `isShared() || isDictionary()`.
    421    return immutableFlags & IS_NATIVE_BIT;
    422  }
    423 
    424  bool isShared() const { return kind() == Kind::Shared; }
    425  bool isDictionary() const { return kind() == Kind::Dictionary; }
    426  bool isProxy() const { return kind() == Kind::Proxy; }
    427  bool isWasmGC() const { return kind() == Kind::WasmGC; }
    428 
    429  inline NativeShape& asNative();
    430  inline SharedShape& asShared();
    431  inline DictionaryShape& asDictionary();
    432  inline WasmGCShape& asWasmGC();
    433 
    434  inline const NativeShape& asNative() const;
    435  inline const SharedShape& asShared() const;
    436  inline const DictionaryShape& asDictionary() const;
    437  inline const WasmGCShape& asWasmGC() const;
    438 
    439 #if defined(DEBUG) || defined(JS_JITSPEW)
    440  void dump() const;
    441  void dump(js::GenericPrinter& out) const;
    442  void dump(js::JSONPrinter& json) const;
    443 
    444  void dumpFields(js::JSONPrinter& json) const;
    445  void dumpStringContent(js::GenericPrinter& out) const;
    446 #endif
    447 
    448  inline void purgeCache(JS::GCContext* gcx);
    449  inline void finalize(JS::GCContext* gcx);
    450 
    451  static const JS::TraceKind TraceKind = JS::TraceKind::Shape;
    452 
    453  void traceChildren(JSTracer* trc);
    454 
    455  // For JIT usage.
    456  static constexpr size_t offsetOfBaseShape() { return offsetOfHeaderPtr(); }
    457 
    458  static constexpr size_t offsetOfObjectFlags() {
    459    return offsetof(Shape, objectFlags_);
    460  }
    461 
    462  static inline size_t offsetOfImmutableFlags() {
    463    return offsetof(Shape, immutableFlags);
    464  }
    465 
    466  static constexpr uint32_t kindShift() { return KIND_SHIFT; }
    467  static constexpr uint32_t kindMask() { return KIND_MASK; }
    468  static constexpr uint32_t isNativeBit() { return IS_NATIVE_BIT; }
    469 
    470  static constexpr size_t offsetOfCachePtr() { return offsetof(Shape, cache_); }
    471 
    472 private:
    473  static void staticAsserts() {
    474    static_assert(offsetOfBaseShape() == offsetof(JS::shadow::Shape, base));
    475    static_assert(offsetof(Shape, immutableFlags) ==
    476                  offsetof(JS::shadow::Shape, immutableFlags));
    477    static_assert(KIND_SHIFT == JS::shadow::Shape::KIND_SHIFT);
    478    static_assert(KIND_MASK == JS::shadow::Shape::KIND_MASK);
    479    static_assert(FIXED_SLOTS_SHIFT == JS::shadow::Shape::FIXED_SLOTS_SHIFT);
    480    static_assert(FIXED_SLOTS_MASK == JS::shadow::Shape::FIXED_SLOTS_MASK);
    481  }
    482 };
    483 
    484 // Shared or dictionary shape for a NativeObject.
    485 class NativeShape : public Shape {
    486 protected:
    487  // The shape's property map. This is either nullptr (for an
    488  // initial SharedShape with no properties), a SharedPropMap (for
    489  // SharedShape) or a DictionaryPropMap (for DictionaryShape).
    490  GCPtr<PropMap*> propMap_;
    491 
    492  NativeShape(Kind kind, BaseShape* base, ObjectFlags objectFlags,
    493              uint32_t nfixed, PropMap* map, uint32_t mapLength)
    494      : Shape(kind, base, objectFlags), propMap_(map) {
    495    MOZ_ASSERT(base->clasp()->isNativeObject());
    496    MOZ_ASSERT(mapLength <= PropMap::Capacity);
    497    immutableFlags |= (nfixed << FIXED_SLOTS_SHIFT) | mapLength;
    498  }
    499 
    500 public:
    501  void traceChildren(JSTracer* trc);
    502 
    503  PropMap* propMap() const { return propMap_; }
    504  uint32_t propMapLength() const { return immutableFlags & MAP_LENGTH_MASK; }
    505 
    506  PropertyInfoWithKey lastProperty() const {
    507    MOZ_ASSERT(propMapLength() > 0);
    508    size_t index = propMapLength() - 1;
    509    return propMap()->getPropertyInfoWithKey(index);
    510  }
    511 
    512  MOZ_ALWAYS_INLINE PropMap* lookup(JSContext* cx, PropertyKey key,
    513                                    uint32_t* index);
    514  MOZ_ALWAYS_INLINE PropMap* lookupPure(PropertyKey key, uint32_t* index);
    515 
    516  uint32_t numFixedSlots() const {
    517    return (immutableFlags & FIXED_SLOTS_MASK) >> FIXED_SLOTS_SHIFT;
    518  }
    519 
    520  // For JIT usage.
    521  static constexpr uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
    522  static constexpr uint32_t fixedSlotsShift() { return FIXED_SLOTS_SHIFT; }
    523 };
    524 
    525 // Shared shape for a NativeObject.
    526 class SharedShape : public NativeShape {
    527  friend class js::gc::CellAllocator;
    528  SharedShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed,
    529              SharedPropMap* map, uint32_t mapLength)
    530      : NativeShape(Kind::Shared, base, objectFlags, nfixed, map, mapLength) {
    531    initSmallSlotSpan();
    532  }
    533 
    534  static SharedShape* new_(JSContext* cx, Handle<BaseShape*> base,
    535                           ObjectFlags objectFlags, uint32_t nfixed,
    536                           Handle<SharedPropMap*> map, uint32_t mapLength);
    537 
    538  void initSmallSlotSpan() {
    539    MOZ_ASSERT(isShared());
    540    uint32_t slotSpan = slotSpanSlow();
    541    if (slotSpan > SMALL_SLOTSPAN_MAX) {
    542      slotSpan = SMALL_SLOTSPAN_MAX;
    543    }
    544    MOZ_ASSERT((immutableFlags & SMALL_SLOTSPAN_MASK) == 0);
    545    immutableFlags |= (slotSpan << SMALL_SLOTSPAN_SHIFT);
    546  }
    547 
    548 public:
    549  SharedPropMap* propMap() const {
    550    MOZ_ASSERT(isShared());
    551    return propMap_ ? propMap_->asShared() : nullptr;
    552  }
    553  inline SharedPropMap* propMapMaybeForwarded() const;
    554 
    555  bool lastPropertyMatchesForAdd(PropertyKey key, PropertyFlags flags,
    556                                 uint32_t* slot) const {
    557    MOZ_ASSERT(isShared());
    558    MOZ_ASSERT(propMapLength() > 0);
    559    uint32_t index = propMapLength() - 1;
    560    SharedPropMap* map = propMap();
    561    if (map->getKey(index) != key) {
    562      return false;
    563    }
    564    PropertyInfo prop = map->getPropertyInfo(index);
    565    if (prop.flags() != flags) {
    566      return false;
    567    }
    568    *slot = prop.maybeSlot();
    569    return true;
    570  }
    571 
    572  uint32_t slotSpanSlow() const {
    573    MOZ_ASSERT(isShared());
    574    const JSClass* clasp = getObjectClass();
    575    return SharedPropMap::slotSpan(clasp, propMap(), propMapLength());
    576  }
    577  uint32_t slotSpan() const {
    578    MOZ_ASSERT(isShared());
    579    uint32_t span =
    580        (immutableFlags & SMALL_SLOTSPAN_MASK) >> SMALL_SLOTSPAN_SHIFT;
    581    if (MOZ_LIKELY(span < SMALL_SLOTSPAN_MAX)) {
    582      MOZ_ASSERT(slotSpanSlow() == span);
    583      return span;
    584    }
    585    return slotSpanSlow();
    586  }
    587 
    588  /*
    589   * Lookup an initial shape matching the given parameters, creating an empty
    590   * shape if none was found.
    591   */
    592  static SharedShape* getInitialShape(JSContext* cx, const JSClass* clasp,
    593                                      JS::Realm* realm, TaggedProto proto,
    594                                      size_t nfixed,
    595                                      ObjectFlags objectFlags = {});
    596  static SharedShape* getInitialShape(JSContext* cx, const JSClass* clasp,
    597                                      JS::Realm* realm, TaggedProto proto,
    598                                      gc::AllocKind kind,
    599                                      ObjectFlags objectFlags = {});
    600 
    601  static SharedShape* getPropMapShape(JSContext* cx, BaseShape* base,
    602                                      size_t nfixed, Handle<SharedPropMap*> map,
    603                                      uint32_t mapLength,
    604                                      ObjectFlags objectFlags,
    605                                      bool* allocatedNewShape = nullptr);
    606 
    607  static SharedShape* getInitialOrPropMapShape(
    608      JSContext* cx, const JSClass* clasp, JS::Realm* realm, TaggedProto proto,
    609      size_t nfixed, Handle<SharedPropMap*> map, uint32_t mapLength,
    610      ObjectFlags objectFlags);
    611 
    612  /*
    613   * Reinsert an alternate initial shape, to be returned by future
    614   * getInitialShape calls, until the new shape becomes unreachable in a GC
    615   * and the table entry is purged.
    616   */
    617  static void insertInitialShape(JSContext* cx, Handle<SharedShape*> shape);
    618 
    619  /*
    620   * Some object subclasses are allocated with a built-in set of properties.
    621   * The first time such an object is created, these built-in properties must
    622   * be set manually, to compute an initial shape.  Afterward, that initial
    623   * shape can be reused for newly-created objects that use the subclass's
    624   * standard prototype.  This method should be used in a post-allocation
    625   * init method, to ensure that objects of such subclasses compute and cache
    626   * the initial shape, if it hasn't already been computed.
    627   */
    628  template <class ObjectSubclass>
    629  static inline bool ensureInitialCustomShape(JSContext* cx,
    630                                              Handle<ObjectSubclass*> obj);
    631 };
    632 
    633 // Dictionary shape for a NativeObject.
    634 class DictionaryShape : public NativeShape {
    635  friend class ::JSObject;
    636  friend class js::gc::CellAllocator;
    637  friend class NativeObject;
    638 
    639  DictionaryShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed,
    640                  DictionaryPropMap* map, uint32_t mapLength)
    641      : NativeShape(Kind::Dictionary, base, objectFlags, nfixed, map,
    642                    mapLength) {
    643    MOZ_ASSERT(map);
    644  }
    645  explicit DictionaryShape(NativeObject* nobj);
    646 
    647  // Methods to set fields of a new dictionary shape. Must not be used for
    648  // shapes that might have been exposed to script.
    649  void updateNewShape(ObjectFlags flags, DictionaryPropMap* map,
    650                      uint32_t mapLength) {
    651    MOZ_ASSERT(isDictionary());
    652    objectFlags_ = flags;
    653    propMap_ = map;
    654    immutableFlags = (immutableFlags & ~MAP_LENGTH_MASK) | mapLength;
    655    MOZ_ASSERT(propMapLength() == mapLength);
    656  }
    657  void setObjectFlagsOfNewShape(ObjectFlags flags) {
    658    MOZ_ASSERT(isDictionary());
    659    objectFlags_ = flags;
    660  }
    661 
    662 public:
    663  static DictionaryShape* new_(JSContext* cx, Handle<BaseShape*> base,
    664                               ObjectFlags objectFlags, uint32_t nfixed,
    665                               Handle<DictionaryPropMap*> map,
    666                               uint32_t mapLength);
    667  static DictionaryShape* new_(JSContext* cx, Handle<NativeObject*> obj);
    668 
    669  DictionaryPropMap* propMap() const {
    670    MOZ_ASSERT(isDictionary());
    671    MOZ_ASSERT(propMap_);
    672    return propMap_->asDictionary();
    673  }
    674 };
    675 
    676 // Shape used for a ProxyObject.
    677 class ProxyShape : public Shape {
    678  // Needed to maintain the same size as other shapes.
    679  uintptr_t padding_;
    680 
    681  friend class js::gc::CellAllocator;
    682  ProxyShape(BaseShape* base, ObjectFlags objectFlags)
    683      : Shape(Kind::Proxy, base, objectFlags) {
    684    MOZ_ASSERT(base->clasp()->isProxyObject());
    685  }
    686 
    687  static ProxyShape* new_(JSContext* cx, Handle<BaseShape*> base,
    688                          ObjectFlags objectFlags);
    689 
    690 public:
    691  static ProxyShape* getShape(JSContext* cx, const JSClass* clasp,
    692                              JS::Realm* realm, TaggedProto proto,
    693                              ObjectFlags objectFlags);
    694 
    695 private:
    696  static void staticAsserts() {
    697    // Silence unused field warning.
    698    static_assert(sizeof(padding_) == sizeof(uintptr_t));
    699  }
    700 };
    701 
    702 // Shape used for a WasmGCObject.
    703 class WasmGCShape : public Shape {
    704  // The shape's recursion group.
    705  const wasm::RecGroup* recGroup_;
    706 
    707  friend class js::gc::CellAllocator;
    708  WasmGCShape(BaseShape* base, const wasm::RecGroup* recGroup,
    709              ObjectFlags objectFlags)
    710      : Shape(Kind::WasmGC, base, objectFlags), recGroup_(recGroup) {
    711    MOZ_ASSERT(!base->clasp()->isProxyObject());
    712    MOZ_ASSERT(!base->clasp()->isNativeObject());
    713  }
    714 
    715  static WasmGCShape* new_(JSContext* cx, Handle<BaseShape*> base,
    716                           const wasm::RecGroup* recGroup,
    717                           ObjectFlags objectFlags);
    718 
    719  // Take a reference to the recursion group.
    720  inline void init();
    721 
    722 public:
    723  static WasmGCShape* getShape(JSContext* cx, const JSClass* clasp,
    724                               JS::Realm* realm, TaggedProto proto,
    725                               const wasm::RecGroup* recGroup,
    726                               ObjectFlags objectFlags);
    727 
    728  // Release the reference to the recursion group.
    729  inline void finalize(JS::GCContext* gcx);
    730 
    731  const wasm::RecGroup* recGroup() const {
    732    MOZ_ASSERT(isWasmGC());
    733    return recGroup_;
    734  }
    735 };
    736 
    737 // A type that can be used to get the size of the Shape alloc kind.
    738 class SizedShape : public Shape {
    739  // The various shape kinds have an extra word that is used defined
    740  // differently depending on the type.
    741  uintptr_t padding_;
    742 
    743  static void staticAsserts() {
    744    // Silence unused field warning.
    745    static_assert(sizeof(padding_) == sizeof(uintptr_t));
    746 
    747    // Sanity check Shape size is what we expect.
    748 #ifdef JS_64BIT
    749    static_assert(sizeof(SizedShape) == 4 * sizeof(void*));
    750 #else
    751    static_assert(sizeof(SizedShape) == 6 * sizeof(void*));
    752 #endif
    753 
    754    // All shape kinds must have the same size.
    755    static_assert(sizeof(NativeShape) == sizeof(SizedShape));
    756    static_assert(sizeof(SharedShape) == sizeof(SizedShape));
    757    static_assert(sizeof(DictionaryShape) == sizeof(SizedShape));
    758    static_assert(sizeof(ProxyShape) == sizeof(SizedShape));
    759    static_assert(sizeof(WasmGCShape) == sizeof(SizedShape));
    760  }
    761 };
    762 
    763 inline NativeShape& js::Shape::asNative() {
    764  MOZ_ASSERT(isNative());
    765  return *static_cast<NativeShape*>(this);
    766 }
    767 
    768 inline SharedShape& js::Shape::asShared() {
    769  MOZ_ASSERT(isShared());
    770  return *static_cast<SharedShape*>(this);
    771 }
    772 
    773 inline DictionaryShape& js::Shape::asDictionary() {
    774  MOZ_ASSERT(isDictionary());
    775  return *static_cast<DictionaryShape*>(this);
    776 }
    777 
    778 inline WasmGCShape& js::Shape::asWasmGC() {
    779  MOZ_ASSERT(isWasmGC());
    780  return *static_cast<WasmGCShape*>(this);
    781 }
    782 
    783 inline const NativeShape& js::Shape::asNative() const {
    784  MOZ_ASSERT(isNative());
    785  return *static_cast<const NativeShape*>(this);
    786 }
    787 
    788 inline const SharedShape& js::Shape::asShared() const {
    789  MOZ_ASSERT(isShared());
    790  return *static_cast<const SharedShape*>(this);
    791 }
    792 
    793 inline const DictionaryShape& js::Shape::asDictionary() const {
    794  MOZ_ASSERT(isDictionary());
    795  return *static_cast<const DictionaryShape*>(this);
    796 }
    797 
    798 inline const WasmGCShape& js::Shape::asWasmGC() const {
    799  MOZ_ASSERT(isWasmGC());
    800  return *static_cast<const WasmGCShape*>(this);
    801 }
    802 
    803 // Iterator for iterating over a shape's properties. It can be used like this:
    804 //
    805 //   for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
    806 //     PropertyKey key = iter->key();
    807 //     if (iter->isDataProperty() && iter->enumerable()) { .. }
    808 //   }
    809 //
    810 // Properties are iterated in reverse order (i.e., iteration starts at the most
    811 // recently added property).
    812 template <AllowGC allowGC>
    813 class MOZ_RAII ShapePropertyIter {
    814  typename MaybeRooted<PropMap*, allowGC>::RootType map_;
    815  uint32_t mapLength_;
    816  const bool isDictionary_;
    817 
    818 protected:
    819  ShapePropertyIter(JSContext* cx, NativeShape* shape, bool isDictionary)
    820      : map_(cx, shape->propMap()),
    821        mapLength_(shape->propMapLength()),
    822        isDictionary_(isDictionary) {
    823    static_assert(allowGC == CanGC);
    824    MOZ_ASSERT(shape->isDictionary() == isDictionary);
    825    MOZ_ASSERT(shape->isNative());
    826  }
    827  ShapePropertyIter(NativeShape* shape, bool isDictionary)
    828      : map_(nullptr, shape->propMap()),
    829        mapLength_(shape->propMapLength()),
    830        isDictionary_(isDictionary) {
    831    static_assert(allowGC == NoGC);
    832    MOZ_ASSERT(shape->isDictionary() == isDictionary);
    833    MOZ_ASSERT(shape->isNative());
    834  }
    835 
    836 public:
    837  ShapePropertyIter(JSContext* cx, NativeShape* shape)
    838      : ShapePropertyIter(cx, shape, shape->isDictionary()) {}
    839 
    840  explicit ShapePropertyIter(NativeShape* shape)
    841      : ShapePropertyIter(shape, shape->isDictionary()) {}
    842 
    843  // Deleted constructors: use SharedShapePropertyIter instead.
    844  ShapePropertyIter(JSContext* cx, SharedShape* shape) = delete;
    845  explicit ShapePropertyIter(SharedShape* shape) = delete;
    846 
    847  bool done() const { return mapLength_ == 0; }
    848 
    849  void operator++(int) {
    850    do {
    851      MOZ_ASSERT(!done());
    852      if (mapLength_ > 1) {
    853        mapLength_--;
    854      } else if (map_->hasPrevious()) {
    855        map_ = map_->asLinked()->previous();
    856        mapLength_ = PropMap::Capacity;
    857      } else {
    858        // Done iterating.
    859        map_ = nullptr;
    860        mapLength_ = 0;
    861        return;
    862      }
    863      // Dictionary maps can have "holes" for removed properties, so keep going
    864      // until we find a non-hole slot.
    865    } while (MOZ_UNLIKELY(isDictionary_ && !map_->hasKey(mapLength_ - 1)));
    866  }
    867 
    868  PropertyInfoWithKey get() const {
    869    MOZ_ASSERT(!done());
    870    return map_->getPropertyInfoWithKey(mapLength_ - 1);
    871  }
    872 
    873  PropertyInfoWithKey operator*() const { return get(); }
    874 
    875  // Fake pointer struct to make operator-> work.
    876  // See https://stackoverflow.com/a/52856349.
    877  struct FakePtr {
    878    PropertyInfoWithKey val_;
    879    const PropertyInfoWithKey* operator->() const { return &val_; }
    880  };
    881  FakePtr operator->() const { return {get()}; }
    882 };
    883 
    884 // Optimized version of ShapePropertyIter for non-dictionary shapes. It passes
    885 // `false` for `isDictionary_`, which will let the compiler optimize away the
    886 // loop structure in ShapePropertyIter::operator++.
    887 template <AllowGC allowGC>
    888 class MOZ_RAII SharedShapePropertyIter : public ShapePropertyIter<allowGC> {
    889 public:
    890  SharedShapePropertyIter(JSContext* cx, SharedShape* shape)
    891      : ShapePropertyIter<allowGC>(cx, shape, /* isDictionary = */ false) {}
    892 
    893  explicit SharedShapePropertyIter(SharedShape* shape)
    894      : ShapePropertyIter<allowGC>(shape, /* isDictionary = */ false) {}
    895 };
    896 
    897 }  // namespace js
    898 
    899 // JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell
    900 // instances that occupy a compartment.
    901 namespace JS {
    902 namespace ubi {
    903 
    904 template <>
    905 class Concrete<js::Shape> : TracerConcrete<js::Shape> {
    906 protected:
    907  explicit Concrete(js::Shape* ptr) : TracerConcrete<js::Shape>(ptr) {}
    908 
    909 public:
    910  static void construct(void* storage, js::Shape* ptr) {
    911    new (storage) Concrete(ptr);
    912  }
    913 
    914  Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
    915 
    916  const char16_t* typeName() const override { return concreteTypeName; }
    917  static const char16_t concreteTypeName[];
    918 };
    919 
    920 template <>
    921 class Concrete<js::BaseShape> : TracerConcrete<js::BaseShape> {
    922 protected:
    923  explicit Concrete(js::BaseShape* ptr) : TracerConcrete<js::BaseShape>(ptr) {}
    924 
    925 public:
    926  static void construct(void* storage, js::BaseShape* ptr) {
    927    new (storage) Concrete(ptr);
    928  }
    929 
    930  Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
    931 
    932  const char16_t* typeName() const override { return concreteTypeName; }
    933  static const char16_t concreteTypeName[];
    934 };
    935 
    936 }  // namespace ubi
    937 }  // namespace JS
    938 
    939 #endif /* vm_Shape_h */