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 */