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/. */
10 #include "js/shadow/Shape.h" // JS::shadow::Shape, JS::shadow::BaseShape
12 #include "mozilla/Attributes.h"
13 #include "mozilla/MemoryReporting.h"
16 #include "NamespaceImports.h"
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/RootingAPI.h"
24 #include "js/UbiNode.h"
25 #include "util/EnumFlags.h"
26 #include "vm/ObjectFlags.h"
27 #include "vm/PropertyInfo.h"
28 #include "vm/PropMap.h"
29 #include "vm/TaggedProto.h"
33 // A Shape represents the layout of an object. It stores and implies:
35 // * The object's JSClass, Realm, prototype (see BaseShape section below).
36 // * The object's flags (ObjectFlags).
37 // * For native objects, the object's properties (PropMap and map length).
38 // * For native objects, the fixed slot capacity of the object (numFixedSlots).
40 // For native objects, the shape implies the property structure (keys,
41 // attributes, property order for enumeration) but not the property values.
42 // The values are stored in object slots.
44 // Every JSObject has a pointer, |shape_|, accessible via shape(), to the
45 // current shape of the object. This pointer permits fast object layout tests.
47 // Shapes use the following C++ class hierarchy:
50 // ============================ ====================================
51 // Shape (abstract) JSObject
53 // +-- NativeShape (abstract) NativeObject
55 // | +-- SharedShape NativeObject with a shared shape
57 // | +-- DictionaryShape NativeObject with a dictionary shape
59 // +-- ProxyShape ProxyObject
61 // +-- WasmGCShape WasmGCObject
63 // Classes marked with (abstract) above are not literally C++ Abstract Base
64 // Classes (since there are no virtual functions, pure or not, in this
65 // hierarchy), but have the same meaning: there are no shapes with this type as
66 // its most-derived type.
70 // Used only for native objects. This is either an initial shape (no property
71 // map) or SharedPropMap shape (for objects with at least one property).
73 // These are immutable tuples stored in a hash table, so that objects with the
74 // same structure end up with the same shape (this both saves memory and allows
75 // JIT optimizations based on this shape).
77 // To avoid hash table lookups on the hot addProperty path, shapes have a
78 // ShapeCachePtr that's used as cache for this. This cache is purged on GC.
79 // The shape cache is also used as cache for prototype shapes, to point to the
80 // initial shape for objects using that shape, and for cached iterators.
84 // Used only for native objects. An object with a dictionary shape is "in
85 // dictionary mode". Certain property operations are not supported for shared
86 // maps so in these cases we need to convert the object to dictionary mode by
87 // creating a dictionary property map and a dictionary shape. An object is
88 // converted to dictionary mode in the following cases:
90 // - Changing a property's flags/attributes and the property is not the last
92 // - Removing a property other than the object's last property.
93 // - The object has many properties. See maybeConvertToDictionaryForAdd for the
96 // Dictionary shapes are unshared, private to a single object, and always have a
97 // a DictionaryPropMap that's similarly unshared. Dictionary shape mutations do
98 // require allocating a new dictionary shape for the object, to properly
99 // invalidate JIT inline caches and other shape guards.
100 // See NativeObject::generateNewDictionaryShape.
104 // Shape used for proxy objects (including wrappers). Proxies with the same
105 // JSClass, Realm, prototype and ObjectFlags will have the same shape.
109 // Shape used for Wasm GC objects. Wasm GC objects with the same JSClass, Realm,
110 // prototype and ObjectFlags will have the same shape.
114 // Because many Shapes have similar data, there is actually a secondary type
115 // called a BaseShape that holds some of a Shape's data (the JSClass, Realm,
116 // prototype). Many shapes can share a single BaseShape.
118 MOZ_ALWAYS_INLINE
size_t JSSLOT_FREE(const JSClass
* clasp
) {
119 // Proxy classes have reserved slots, but proxies manage their own slot
121 MOZ_ASSERT(!clasp
->isProxyObject());
122 return JSCLASS_RESERVED_SLOTS(clasp
);
129 class PropertyIteratorObject
;
132 class TenuringTracer
;
139 // Hash policy for ShapeCachePtr's ShapeSetForAdd. Maps the new property key and
140 // flags to the new shape.
141 struct ShapeForAddHasher
: public DefaultHasher
<Shape
*> {
142 using Key
= SharedShape
*;
148 Lookup(PropertyKey key
, PropertyFlags flags
) : key(key
), flags(flags
) {}
151 static MOZ_ALWAYS_INLINE HashNumber
hash(const Lookup
& l
);
152 static MOZ_ALWAYS_INLINE
bool match(SharedShape
* shape
, const Lookup
& l
);
154 using ShapeSetForAdd
=
155 HashSet
<SharedShape
*, ShapeForAddHasher
, SystemAllocPolicy
>;
157 // Each shape has a cache pointer that's either:
160 // * For shared shapes, a single shape used to speed up addProperty.
161 // * For shared shapes, a set of shapes used to speed up addProperty.
162 // * For prototype shapes, the most recently used initial shape allocated for a
163 // prototype object with this shape.
164 // * For any shape, a PropertyIteratorObject used to speed up GetIterator.
166 // The cache is purely an optimization and is purged on GC (all shapes with a
167 // non-None ShapeCachePtr are added to a vector in the Zone).
168 class ShapeCachePtr
{
170 SINGLE_SHAPE_FOR_ADD
= 0,
171 SHAPE_SET_FOR_ADD
= 1,
172 SHAPE_WITH_PROTO
= 2,
180 bool isNone() const { return !bits
; }
181 void setNone() { bits
= 0; }
183 bool isSingleShapeForAdd() const {
184 return (bits
& MASK
) == SINGLE_SHAPE_FOR_ADD
&& !isNone();
186 SharedShape
* toSingleShapeForAdd() const {
187 MOZ_ASSERT(isSingleShapeForAdd());
188 return reinterpret_cast<SharedShape
*>(bits
& ~uintptr_t(MASK
));
190 void setSingleShapeForAdd(SharedShape
* shape
) {
192 MOZ_ASSERT((uintptr_t(shape
) & MASK
) == 0);
193 MOZ_ASSERT(!isShapeSetForAdd()); // Don't leak the ShapeSet.
194 bits
= uintptr_t(shape
) | SINGLE_SHAPE_FOR_ADD
;
197 bool isShapeSetForAdd() const { return (bits
& MASK
) == SHAPE_SET_FOR_ADD
; }
198 ShapeSetForAdd
* toShapeSetForAdd() const {
199 MOZ_ASSERT(isShapeSetForAdd());
200 return reinterpret_cast<ShapeSetForAdd
*>(bits
& ~uintptr_t(MASK
));
202 void setShapeSetForAdd(ShapeSetForAdd
* hash
) {
204 MOZ_ASSERT((uintptr_t(hash
) & MASK
) == 0);
205 bits
= uintptr_t(hash
) | SHAPE_SET_FOR_ADD
;
208 bool isForAdd() const { return isSingleShapeForAdd() || isShapeSetForAdd(); }
210 bool isShapeWithProto() const { return (bits
& MASK
) == SHAPE_WITH_PROTO
; }
211 SharedShape
* toShapeWithProto() const {
212 MOZ_ASSERT(isShapeWithProto());
213 return reinterpret_cast<SharedShape
*>(bits
& ~uintptr_t(MASK
));
215 void setShapeWithProto(SharedShape
* shape
) {
217 MOZ_ASSERT((uintptr_t(shape
) & MASK
) == 0);
218 MOZ_ASSERT(!isShapeSetForAdd()); // Don't leak the ShapeSet.
219 bits
= uintptr_t(shape
) | SHAPE_WITH_PROTO
;
222 bool isIterator() const { return (bits
& MASK
) == ITERATOR
; }
223 PropertyIteratorObject
* toIterator() const {
224 MOZ_ASSERT(isIterator());
225 return reinterpret_cast<PropertyIteratorObject
*>(bits
& ~uintptr_t(MASK
));
227 void setIterator(PropertyIteratorObject
* iter
) {
229 MOZ_ASSERT((uintptr_t(iter
) & MASK
) == 0);
230 MOZ_ASSERT(!isShapeSetForAdd()); // Don't leak the ShapeSet.
231 bits
= uintptr_t(iter
) | ITERATOR
;
233 friend class js::jit::MacroAssembler
;
236 // BaseShapes store the object's class, realm and prototype. BaseShapes are
237 // immutable tuples stored in a per-Zone hash table.
238 class BaseShape
: public gc::TenuredCellWithNonGCPointer
<const JSClass
> {
240 /* Class of referring object, stored in the cell header */
241 const JSClass
* clasp() const { return headerPtr(); }
245 GCPtr
<TaggedProto
> proto_
;
247 BaseShape(const BaseShape
& base
) = delete;
248 BaseShape
& operator=(const BaseShape
& other
) = delete;
251 void finalize(JS::GCContext
* gcx
) {}
253 BaseShape(const JSClass
* clasp
, JS::Realm
* realm
, TaggedProto proto
);
255 /* Not defined: BaseShapes must not be stack allocated. */
256 ~BaseShape() = delete;
258 JS::Realm
* realm() const { return realm_
; }
259 JS::Compartment
* compartment() const {
260 return JS::GetCompartmentForRealm(realm());
262 JS::Compartment
* maybeCompartment() const { return compartment(); }
264 TaggedProto
proto() const { return proto_
; }
267 * Lookup base shapes from the zone's baseShapes table, adding if not
270 static BaseShape
* get(JSContext
* cx
, const JSClass
* clasp
, JS::Realm
* realm
,
271 Handle
<TaggedProto
> proto
);
273 static const JS::TraceKind TraceKind
= JS::TraceKind::BaseShape
;
275 void traceChildren(JSTracer
* trc
);
277 static constexpr size_t offsetOfClasp() { return offsetOfHeaderPtr(); }
279 static constexpr size_t offsetOfRealm() {
280 return offsetof(BaseShape
, realm_
);
283 static constexpr size_t offsetOfProto() {
284 return offsetof(BaseShape
, proto_
);
288 static void staticAsserts() {
289 static_assert(offsetOfClasp() == offsetof(JS::shadow::BaseShape
, clasp
));
290 static_assert(offsetOfRealm() == offsetof(JS::shadow::BaseShape
, realm
));
291 static_assert(sizeof(BaseShape
) % gc::CellAlignBytes
== 0,
292 "Things inheriting from gc::Cell must have a size that's "
293 "a multiple of gc::CellAlignBytes");
294 // Sanity check BaseShape size is what we expect.
296 static_assert(sizeof(BaseShape
) == 3 * sizeof(void*));
298 static_assert(sizeof(BaseShape
) == 4 * sizeof(void*));
303 class Shape
: public gc::CellWithTenuredGCPointer
<gc::TenuredCell
, BaseShape
> {
304 friend class ::JSObject
;
305 friend class ::JSFunction
;
306 friend class GCMarker
;
307 friend class NativeObject
;
308 friend class SharedShape
;
309 friend class PropertyTree
;
310 friend class gc::TenuringTracer
;
311 friend class JS::ubi::Concrete
<Shape
>;
312 friend class gc::RelocationOverlay
;
315 // Base shape, stored in the cell header.
316 BaseShape
* base() const { return headerPtr(); }
318 using Kind
= JS::shadow::Shape::Kind
;
321 // Flags that are not modified after the Shape is created. Off-thread Ion
322 // compilation can access the immutableFlags word, so we don't want any
323 // mutable state here to avoid (TSan) races.
324 enum ImmutableFlags
: uint32_t {
325 // For NativeShape: the length associated with the property map. This is a
326 // value in the range [0, PropMap::Capacity]. A length of 0 indicates the
327 // object is empty (has no properties).
328 MAP_LENGTH_MASK
= BitMask(4),
330 // The Shape Kind. The NativeObject kinds have the low bit set.
333 IS_NATIVE_BIT
= 0x1 << KIND_SHIFT
,
335 // For NativeShape: the number of fixed slots in objects with this shape.
336 // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store.
337 FIXED_SLOTS_MAX
= 0x1f,
338 FIXED_SLOTS_SHIFT
= 6,
339 FIXED_SLOTS_MASK
= uint32_t(FIXED_SLOTS_MAX
<< FIXED_SLOTS_SHIFT
),
341 // For SharedShape: the slot span of the object, if it fits in a single
342 // byte. If the value is SMALL_SLOTSPAN_MAX, the slot span has to be
343 // computed based on the property map (which is slower).
345 // Note: NativeObject::addProperty will convert to dictionary mode before we
346 // reach this limit, but there are other places where we add properties to
347 // shapes, for example environment object shapes.
348 SMALL_SLOTSPAN_MAX
= 0x3ff, // 10 bits.
349 SMALL_SLOTSPAN_SHIFT
= 11,
350 SMALL_SLOTSPAN_MASK
= uint32_t(SMALL_SLOTSPAN_MAX
<< SMALL_SLOTSPAN_SHIFT
),
353 uint32_t immutableFlags
; // Immutable flags, see above.
354 ObjectFlags objectFlags_
; // Immutable object flags, see ObjectFlags.
356 // Cache used to speed up common operations on shapes.
357 ShapeCachePtr cache_
;
359 // Give the object a shape that's similar to its current shape, but with the
360 // passed objectFlags, proto, and nfixed values.
361 static bool replaceShape(JSContext
* cx
, HandleObject obj
,
362 ObjectFlags objectFlags
, TaggedProto proto
,
366 void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
,
367 JS::ShapeInfo
* info
) const {
368 if (cache_
.isShapeSetForAdd()) {
369 info
->shapesMallocHeapCache
+=
370 cache_
.toShapeSetForAdd()->shallowSizeOfIncludingThis(mallocSizeOf
);
374 ShapeCachePtr
& cacheRef() { return cache_
; }
375 ShapeCachePtr
cache() const { return cache_
; }
377 void maybeCacheIterator(JSContext
* cx
, PropertyIteratorObject
* iter
);
379 const JSClass
* getObjectClass() const { return base()->clasp(); }
380 JS::Realm
* realm() const { return base()->realm(); }
382 JS::Compartment
* compartment() const { return base()->compartment(); }
383 JS::Compartment
* maybeCompartment() const {
384 return base()->maybeCompartment();
387 TaggedProto
proto() const { return base()->proto(); }
389 ObjectFlags
objectFlags() const { return objectFlags_
; }
390 bool hasObjectFlag(ObjectFlag flag
) const {
391 return objectFlags_
.hasFlag(flag
);
395 Shape(Kind kind
, BaseShape
* base
, ObjectFlags objectFlags
)
396 : CellWithTenuredGCPointer(base
),
397 immutableFlags(uint32_t(kind
) << KIND_SHIFT
),
398 objectFlags_(objectFlags
) {
400 MOZ_ASSERT(this->kind() == kind
, "kind must fit in KIND_MASK");
401 MOZ_ASSERT(isNative() == base
->clasp()->isNativeObject());
404 Shape(const Shape
& other
) = delete;
407 Kind
kind() const { return Kind((immutableFlags
>> KIND_SHIFT
) & KIND_MASK
); }
409 bool isNative() const {
410 // Note: this is equivalent to `isShared() || isDictionary()`.
411 return immutableFlags
& IS_NATIVE_BIT
;
414 bool isShared() const { return kind() == Kind::Shared
; }
415 bool isDictionary() const { return kind() == Kind::Dictionary
; }
416 bool isProxy() const { return kind() == Kind::Proxy
; }
417 bool isWasmGC() const { return kind() == Kind::WasmGC
; }
419 inline NativeShape
& asNative();
420 inline SharedShape
& asShared();
421 inline DictionaryShape
& asDictionary();
422 inline WasmGCShape
& asWasmGC();
424 inline const NativeShape
& asNative() const;
425 inline const SharedShape
& asShared() const;
426 inline const DictionaryShape
& asDictionary() const;
427 inline const WasmGCShape
& asWasmGC() const;
430 void dump(js::GenericPrinter
& out
) const;
434 inline void purgeCache(JS::GCContext
* gcx
);
435 inline void finalize(JS::GCContext
* gcx
);
437 static const JS::TraceKind TraceKind
= JS::TraceKind::Shape
;
439 void traceChildren(JSTracer
* trc
);
442 static constexpr size_t offsetOfBaseShape() { return offsetOfHeaderPtr(); }
444 static constexpr size_t offsetOfObjectFlags() {
445 return offsetof(Shape
, objectFlags_
);
448 static inline size_t offsetOfImmutableFlags() {
449 return offsetof(Shape
, immutableFlags
);
452 static constexpr uint32_t kindShift() { return KIND_SHIFT
; }
453 static constexpr uint32_t kindMask() { return KIND_MASK
; }
454 static constexpr uint32_t isNativeBit() { return IS_NATIVE_BIT
; }
456 static constexpr size_t offsetOfCachePtr() { return offsetof(Shape
, cache_
); }
459 static void staticAsserts() {
460 static_assert(offsetOfBaseShape() == offsetof(JS::shadow::Shape
, base
));
461 static_assert(offsetof(Shape
, immutableFlags
) ==
462 offsetof(JS::shadow::Shape
, immutableFlags
));
463 static_assert(KIND_SHIFT
== JS::shadow::Shape::KIND_SHIFT
);
464 static_assert(KIND_MASK
== JS::shadow::Shape::KIND_MASK
);
465 static_assert(FIXED_SLOTS_SHIFT
== JS::shadow::Shape::FIXED_SLOTS_SHIFT
);
466 static_assert(FIXED_SLOTS_MASK
== JS::shadow::Shape::FIXED_SLOTS_MASK
);
470 // Shared or dictionary shape for a NativeObject.
471 class NativeShape
: public Shape
{
473 // The shape's property map. This is either nullptr (for an
474 // initial SharedShape with no properties), a SharedPropMap (for
475 // SharedShape) or a DictionaryPropMap (for DictionaryShape).
476 GCPtr
<PropMap
*> propMap_
;
478 NativeShape(Kind kind
, BaseShape
* base
, ObjectFlags objectFlags
,
479 uint32_t nfixed
, PropMap
* map
, uint32_t mapLength
)
480 : Shape(kind
, base
, objectFlags
), propMap_(map
) {
481 MOZ_ASSERT(base
->clasp()->isNativeObject());
482 MOZ_ASSERT(mapLength
<= PropMap::Capacity
);
483 immutableFlags
|= (nfixed
<< FIXED_SLOTS_SHIFT
) | mapLength
;
487 void traceChildren(JSTracer
* trc
);
489 PropMap
* propMap() const { return propMap_
; }
490 uint32_t propMapLength() const { return immutableFlags
& MAP_LENGTH_MASK
; }
492 PropertyInfoWithKey
lastProperty() const {
493 MOZ_ASSERT(propMapLength() > 0);
494 size_t index
= propMapLength() - 1;
495 return propMap()->getPropertyInfoWithKey(index
);
498 MOZ_ALWAYS_INLINE PropMap
* lookup(JSContext
* cx
, PropertyKey key
,
500 MOZ_ALWAYS_INLINE PropMap
* lookupPure(PropertyKey key
, uint32_t* index
);
502 uint32_t numFixedSlots() const {
503 return (immutableFlags
& FIXED_SLOTS_MASK
) >> FIXED_SLOTS_SHIFT
;
507 static constexpr uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK
; }
508 static constexpr uint32_t fixedSlotsShift() { return FIXED_SLOTS_SHIFT
; }
511 // Shared shape for a NativeObject.
512 class SharedShape
: public NativeShape
{
513 friend class js::gc::CellAllocator
;
514 SharedShape(BaseShape
* base
, ObjectFlags objectFlags
, uint32_t nfixed
,
515 SharedPropMap
* map
, uint32_t mapLength
)
516 : NativeShape(Kind::Shared
, base
, objectFlags
, nfixed
, map
, mapLength
) {
520 static SharedShape
* new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
521 ObjectFlags objectFlags
, uint32_t nfixed
,
522 Handle
<SharedPropMap
*> map
, uint32_t mapLength
);
524 void initSmallSlotSpan() {
525 MOZ_ASSERT(isShared());
526 uint32_t slotSpan
= slotSpanSlow();
527 if (slotSpan
> SMALL_SLOTSPAN_MAX
) {
528 slotSpan
= SMALL_SLOTSPAN_MAX
;
530 MOZ_ASSERT((immutableFlags
& SMALL_SLOTSPAN_MASK
) == 0);
531 immutableFlags
|= (slotSpan
<< SMALL_SLOTSPAN_SHIFT
);
535 SharedPropMap
* propMap() const {
536 MOZ_ASSERT(isShared());
537 return propMap_
? propMap_
->asShared() : nullptr;
539 inline SharedPropMap
* propMapMaybeForwarded() const;
541 bool lastPropertyMatchesForAdd(PropertyKey key
, PropertyFlags flags
,
542 uint32_t* slot
) const {
543 MOZ_ASSERT(isShared());
544 MOZ_ASSERT(propMapLength() > 0);
545 uint32_t index
= propMapLength() - 1;
546 SharedPropMap
* map
= propMap();
547 if (map
->getKey(index
) != key
) {
550 PropertyInfo prop
= map
->getPropertyInfo(index
);
551 if (prop
.flags() != flags
) {
554 *slot
= prop
.maybeSlot();
558 uint32_t slotSpanSlow() const {
559 MOZ_ASSERT(isShared());
560 const JSClass
* clasp
= getObjectClass();
561 return SharedPropMap::slotSpan(clasp
, propMap(), propMapLength());
563 uint32_t slotSpan() const {
564 MOZ_ASSERT(isShared());
566 (immutableFlags
& SMALL_SLOTSPAN_MASK
) >> SMALL_SLOTSPAN_SHIFT
;
567 if (MOZ_LIKELY(span
< SMALL_SLOTSPAN_MAX
)) {
568 MOZ_ASSERT(slotSpanSlow() == span
);
571 return slotSpanSlow();
575 * Lookup an initial shape matching the given parameters, creating an empty
576 * shape if none was found.
578 static SharedShape
* getInitialShape(JSContext
* cx
, const JSClass
* clasp
,
579 JS::Realm
* realm
, TaggedProto proto
,
581 ObjectFlags objectFlags
= {});
582 static SharedShape
* getInitialShape(JSContext
* cx
, const JSClass
* clasp
,
583 JS::Realm
* realm
, TaggedProto proto
,
585 ObjectFlags objectFlags
= {});
587 static SharedShape
* getPropMapShape(JSContext
* cx
, BaseShape
* base
,
588 size_t nfixed
, Handle
<SharedPropMap
*> map
,
590 ObjectFlags objectFlags
,
591 bool* allocatedNewShape
= nullptr);
593 static SharedShape
* getInitialOrPropMapShape(
594 JSContext
* cx
, const JSClass
* clasp
, JS::Realm
* realm
, TaggedProto proto
,
595 size_t nfixed
, Handle
<SharedPropMap
*> map
, uint32_t mapLength
,
596 ObjectFlags objectFlags
);
599 * Reinsert an alternate initial shape, to be returned by future
600 * getInitialShape calls, until the new shape becomes unreachable in a GC
601 * and the table entry is purged.
603 static void insertInitialShape(JSContext
* cx
, Handle
<SharedShape
*> shape
);
606 * Some object subclasses are allocated with a built-in set of properties.
607 * The first time such an object is created, these built-in properties must
608 * be set manually, to compute an initial shape. Afterward, that initial
609 * shape can be reused for newly-created objects that use the subclass's
610 * standard prototype. This method should be used in a post-allocation
611 * init method, to ensure that objects of such subclasses compute and cache
612 * the initial shape, if it hasn't already been computed.
614 template <class ObjectSubclass
>
615 static inline bool ensureInitialCustomShape(JSContext
* cx
,
616 Handle
<ObjectSubclass
*> obj
);
619 // Dictionary shape for a NativeObject.
620 class DictionaryShape
: public NativeShape
{
621 friend class ::JSObject
;
622 friend class js::gc::CellAllocator
;
623 friend class NativeObject
;
625 DictionaryShape(BaseShape
* base
, ObjectFlags objectFlags
, uint32_t nfixed
,
626 DictionaryPropMap
* map
, uint32_t mapLength
)
627 : NativeShape(Kind::Dictionary
, base
, objectFlags
, nfixed
, map
,
631 explicit DictionaryShape(NativeObject
* nobj
);
633 // Methods to set fields of a new dictionary shape. Must not be used for
634 // shapes that might have been exposed to script.
635 void updateNewShape(ObjectFlags flags
, DictionaryPropMap
* map
,
636 uint32_t mapLength
) {
637 MOZ_ASSERT(isDictionary());
638 objectFlags_
= flags
;
640 immutableFlags
= (immutableFlags
& ~MAP_LENGTH_MASK
) | mapLength
;
641 MOZ_ASSERT(propMapLength() == mapLength
);
643 void setObjectFlagsOfNewShape(ObjectFlags flags
) {
644 MOZ_ASSERT(isDictionary());
645 objectFlags_
= flags
;
649 static DictionaryShape
* new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
650 ObjectFlags objectFlags
, uint32_t nfixed
,
651 Handle
<DictionaryPropMap
*> map
,
653 static DictionaryShape
* new_(JSContext
* cx
, Handle
<NativeObject
*> obj
);
655 DictionaryPropMap
* propMap() const {
656 MOZ_ASSERT(isDictionary());
657 MOZ_ASSERT(propMap_
);
658 return propMap_
->asDictionary();
662 // Shape used for a ProxyObject.
663 class ProxyShape
: public Shape
{
664 // Needed to maintain the same size as other shapes.
667 friend class js::gc::CellAllocator
;
668 ProxyShape(BaseShape
* base
, ObjectFlags objectFlags
)
669 : Shape(Kind::Proxy
, base
, objectFlags
) {
670 MOZ_ASSERT(base
->clasp()->isProxyObject());
673 static ProxyShape
* new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
674 ObjectFlags objectFlags
);
677 static ProxyShape
* getShape(JSContext
* cx
, const JSClass
* clasp
,
678 JS::Realm
* realm
, TaggedProto proto
,
679 ObjectFlags objectFlags
);
682 static void staticAsserts() {
683 // Silence unused field warning.
684 static_assert(sizeof(padding_
) == sizeof(uintptr_t));
688 // Shape used for a WasmGCObject.
689 class WasmGCShape
: public Shape
{
690 // The shape's recursion group.
691 const wasm::RecGroup
* recGroup_
;
693 friend class js::gc::CellAllocator
;
694 WasmGCShape(BaseShape
* base
, const wasm::RecGroup
* recGroup
,
695 ObjectFlags objectFlags
)
696 : Shape(Kind::WasmGC
, base
, objectFlags
), recGroup_(recGroup
) {
697 MOZ_ASSERT(!base
->clasp()->isProxyObject());
698 MOZ_ASSERT(!base
->clasp()->isNativeObject());
701 static WasmGCShape
* new_(JSContext
* cx
, Handle
<BaseShape
*> base
,
702 const wasm::RecGroup
* recGroup
,
703 ObjectFlags objectFlags
);
705 // Take a reference to the recursion group.
709 static WasmGCShape
* getShape(JSContext
* cx
, const JSClass
* clasp
,
710 JS::Realm
* realm
, TaggedProto proto
,
711 const wasm::RecGroup
* recGroup
,
712 ObjectFlags objectFlags
);
714 // Release the reference to the recursion group.
715 inline void finalize(JS::GCContext
* gcx
);
717 const wasm::RecGroup
* recGroup() const {
718 MOZ_ASSERT(isWasmGC());
723 // A type that can be used to get the size of the Shape alloc kind.
724 class SizedShape
: public Shape
{
725 // The various shape kinds have an extra word that is used defined
726 // differently depending on the type.
729 static void staticAsserts() {
730 // Silence unused field warning.
731 static_assert(sizeof(padding_
) == sizeof(uintptr_t));
733 // Sanity check Shape size is what we expect.
735 static_assert(sizeof(SizedShape
) == 4 * sizeof(void*));
737 static_assert(sizeof(SizedShape
) == 6 * sizeof(void*));
740 // All shape kinds must have the same size.
741 static_assert(sizeof(NativeShape
) == sizeof(SizedShape
));
742 static_assert(sizeof(SharedShape
) == sizeof(SizedShape
));
743 static_assert(sizeof(DictionaryShape
) == sizeof(SizedShape
));
744 static_assert(sizeof(ProxyShape
) == sizeof(SizedShape
));
745 static_assert(sizeof(WasmGCShape
) == sizeof(SizedShape
));
749 inline NativeShape
& js::Shape::asNative() {
750 MOZ_ASSERT(isNative());
751 return *static_cast<NativeShape
*>(this);
754 inline SharedShape
& js::Shape::asShared() {
755 MOZ_ASSERT(isShared());
756 return *static_cast<SharedShape
*>(this);
759 inline DictionaryShape
& js::Shape::asDictionary() {
760 MOZ_ASSERT(isDictionary());
761 return *static_cast<DictionaryShape
*>(this);
764 inline WasmGCShape
& js::Shape::asWasmGC() {
765 MOZ_ASSERT(isWasmGC());
766 return *static_cast<WasmGCShape
*>(this);
769 inline const NativeShape
& js::Shape::asNative() const {
770 MOZ_ASSERT(isNative());
771 return *static_cast<const NativeShape
*>(this);
774 inline const SharedShape
& js::Shape::asShared() const {
775 MOZ_ASSERT(isShared());
776 return *static_cast<const SharedShape
*>(this);
779 inline const DictionaryShape
& js::Shape::asDictionary() const {
780 MOZ_ASSERT(isDictionary());
781 return *static_cast<const DictionaryShape
*>(this);
784 inline const WasmGCShape
& js::Shape::asWasmGC() const {
785 MOZ_ASSERT(isWasmGC());
786 return *static_cast<const WasmGCShape
*>(this);
789 // Iterator for iterating over a shape's properties. It can be used like this:
791 // for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) {
792 // PropertyKey key = iter->key();
793 // if (iter->isDataProperty() && iter->enumerable()) { .. }
796 // Properties are iterated in reverse order (i.e., iteration starts at the most
797 // recently added property).
798 template <AllowGC allowGC
>
799 class MOZ_RAII ShapePropertyIter
{
800 typename MaybeRooted
<PropMap
*, allowGC
>::RootType map_
;
802 const bool isDictionary_
;
805 ShapePropertyIter(JSContext
* cx
, NativeShape
* shape
, bool isDictionary
)
806 : map_(cx
, shape
->propMap()),
807 mapLength_(shape
->propMapLength()),
808 isDictionary_(isDictionary
) {
809 static_assert(allowGC
== CanGC
);
810 MOZ_ASSERT(shape
->isDictionary() == isDictionary
);
811 MOZ_ASSERT(shape
->isNative());
813 ShapePropertyIter(NativeShape
* shape
, bool isDictionary
)
814 : map_(nullptr, shape
->propMap()),
815 mapLength_(shape
->propMapLength()),
816 isDictionary_(isDictionary
) {
817 static_assert(allowGC
== NoGC
);
818 MOZ_ASSERT(shape
->isDictionary() == isDictionary
);
819 MOZ_ASSERT(shape
->isNative());
823 ShapePropertyIter(JSContext
* cx
, NativeShape
* shape
)
824 : ShapePropertyIter(cx
, shape
, shape
->isDictionary()) {}
826 explicit ShapePropertyIter(NativeShape
* shape
)
827 : ShapePropertyIter(shape
, shape
->isDictionary()) {}
829 // Deleted constructors: use SharedShapePropertyIter instead.
830 ShapePropertyIter(JSContext
* cx
, SharedShape
* shape
) = delete;
831 explicit ShapePropertyIter(SharedShape
* shape
) = delete;
833 bool done() const { return mapLength_
== 0; }
835 void operator++(int) {
838 if (mapLength_
> 1) {
840 } else if (map_
->hasPrevious()) {
841 map_
= map_
->asLinked()->previous();
842 mapLength_
= PropMap::Capacity
;
849 // Dictionary maps can have "holes" for removed properties, so keep going
850 // until we find a non-hole slot.
851 } while (MOZ_UNLIKELY(isDictionary_
&& !map_
->hasKey(mapLength_
- 1)));
854 PropertyInfoWithKey
get() const {
856 return map_
->getPropertyInfoWithKey(mapLength_
- 1);
859 PropertyInfoWithKey
operator*() const { return get(); }
861 // Fake pointer struct to make operator-> work.
862 // See https://stackoverflow.com/a/52856349.
864 PropertyInfoWithKey val_
;
865 const PropertyInfoWithKey
* operator->() const { return &val_
; }
867 FakePtr
operator->() const { return {get()}; }
870 // Optimized version of ShapePropertyIter for non-dictionary shapes. It passes
871 // `false` for `isDictionary_`, which will let the compiler optimize away the
872 // loop structure in ShapePropertyIter::operator++.
873 template <AllowGC allowGC
>
874 class MOZ_RAII SharedShapePropertyIter
: public ShapePropertyIter
<allowGC
> {
876 SharedShapePropertyIter(JSContext
* cx
, SharedShape
* shape
)
877 : ShapePropertyIter
<allowGC
>(cx
, shape
, /* isDictionary = */ false) {}
879 explicit SharedShapePropertyIter(SharedShape
* shape
)
880 : ShapePropertyIter
<allowGC
>(shape
, /* isDictionary = */ false) {}
885 // JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell
886 // instances that occupy a compartment.
891 class Concrete
<js::Shape
> : TracerConcrete
<js::Shape
> {
893 explicit Concrete(js::Shape
* ptr
) : TracerConcrete
<js::Shape
>(ptr
) {}
896 static void construct(void* storage
, js::Shape
* ptr
) {
897 new (storage
) Concrete(ptr
);
900 Size
size(mozilla::MallocSizeOf mallocSizeOf
) const override
;
902 const char16_t
* typeName() const override
{ return concreteTypeName
; }
903 static const char16_t concreteTypeName
[];
907 class Concrete
<js::BaseShape
> : TracerConcrete
<js::BaseShape
> {
909 explicit Concrete(js::BaseShape
* ptr
) : TracerConcrete
<js::BaseShape
>(ptr
) {}
912 static void construct(void* storage
, js::BaseShape
* ptr
) {
913 new (storage
) Concrete(ptr
);
916 Size
size(mozilla::MallocSizeOf mallocSizeOf
) const override
;
918 const char16_t
* typeName() const override
{ return concreteTypeName
; }
919 static const char16_t concreteTypeName
[];
925 #endif /* vm_Shape_h */