diff options
Diffstat (limited to 'js/src/vm/Shape.h')
-rw-r--r-- | js/src/vm/Shape.h | 941 |
1 files changed, 941 insertions, 0 deletions
diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h new file mode 100644 index 0000000000..3571a3df45 --- /dev/null +++ b/js/src/vm/Shape.h @@ -0,0 +1,941 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_Shape_h +#define vm_Shape_h + +#include "js/shadow/Shape.h" // JS::shadow::Shape, JS::shadow::BaseShape + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" + +#include "jstypes.h" +#include "NamespaceImports.h" + +#include "gc/Barrier.h" +#include "gc/MaybeRooted.h" +#include "js/HashTable.h" +#include "js/Id.h" // JS::PropertyKey +#include "js/MemoryMetrics.h" +#include "js/Printer.h" // js::GenericPrinter +#include "js/RootingAPI.h" +#include "js/UbiNode.h" +#include "util/EnumFlags.h" +#include "vm/ObjectFlags.h" +#include "vm/PropertyInfo.h" +#include "vm/PropMap.h" +#include "vm/TaggedProto.h" + +// [SMDOC] Shapes +// +// A Shape represents the layout of an object. It stores and implies: +// +// * The object's JSClass, Realm, prototype (see BaseShape section below). +// * The object's flags (ObjectFlags). +// * For native objects, the object's properties (PropMap and map length). +// * For native objects, the fixed slot capacity of the object (numFixedSlots). +// +// For native objects, the shape implies the property structure (keys, +// attributes, property order for enumeration) but not the property values. +// The values are stored in object slots. +// +// Every JSObject has a pointer, |shape_|, accessible via shape(), to the +// current shape of the object. This pointer permits fast object layout tests. +// +// Shapes use the following C++ class hierarchy: +// +// C++ Type Used by +// ============================ ==================================== +// Shape (abstract) JSObject +// | +// +-- NativeShape (abstract) NativeObject +// | | +// | +-- SharedShape NativeObject with a shared shape +// | | +// | +-- DictionaryShape NativeObject with a dictionary shape +// | +// +-- ProxyShape ProxyObject +// | +// +-- WasmGCShape WasmGCObject +// +// Classes marked with (abstract) above are not literally C++ Abstract Base +// Classes (since there are no virtual functions, pure or not, in this +// hierarchy), but have the same meaning: there are no shapes with this type as +// its most-derived type. +// +// SharedShape +// =========== +// Used only for native objects. This is either an initial shape (no property +// map) or SharedPropMap shape (for objects with at least one property). +// +// These are immutable tuples stored in a hash table, so that objects with the +// same structure end up with the same shape (this both saves memory and allows +// JIT optimizations based on this shape). +// +// To avoid hash table lookups on the hot addProperty path, shapes have a +// ShapeCachePtr that's used as cache for this. This cache is purged on GC. +// The shape cache is also used as cache for prototype shapes, to point to the +// initial shape for objects using that shape, and for cached iterators. +// +// DictionaryShape +// =============== +// Used only for native objects. An object with a dictionary shape is "in +// dictionary mode". Certain property operations are not supported for shared +// maps so in these cases we need to convert the object to dictionary mode by +// creating a dictionary property map and a dictionary shape. An object is +// converted to dictionary mode in the following cases: +// +// - Changing a property's flags/attributes and the property is not the last +// property. +// - Removing a property other than the object's last property. +// - The object has many properties. See maybeConvertToDictionaryForAdd for the +// heuristics. +// +// Dictionary shapes are unshared, private to a single object, and always have a +// a DictionaryPropMap that's similarly unshared. Dictionary shape mutations do +// require allocating a new dictionary shape for the object, to properly +// invalidate JIT inline caches and other shape guards. +// See NativeObject::generateNewDictionaryShape. +// +// ProxyShape +// ========== +// Shape used for proxy objects (including wrappers). Proxies with the same +// JSClass, Realm, prototype and ObjectFlags will have the same shape. +// +// WasmGCShape +// =========== +// Shape used for Wasm GC objects. Wasm GC objects with the same JSClass, Realm, +// prototype and ObjectFlags will have the same shape. +// +// BaseShape +// ========= +// Because many Shapes have similar data, there is actually a secondary type +// called a BaseShape that holds some of a Shape's data (the JSClass, Realm, +// prototype). Many shapes can share a single BaseShape. + +MOZ_ALWAYS_INLINE size_t JSSLOT_FREE(const JSClass* clasp) { + // Proxy classes have reserved slots, but proxies manage their own slot + // layout. + MOZ_ASSERT(!clasp->isProxyObject()); + return JSCLASS_RESERVED_SLOTS(clasp); +} + +namespace js { + +class JSONPrinter; +class NativeShape; +class Shape; +class PropertyIteratorObject; + +namespace gc { +class TenuringTracer; +} // namespace gc + +namespace wasm { +class RecGroup; +} // namespace wasm + +// Hash policy for ShapeCachePtr's ShapeSetForAdd. Maps the new property key and +// flags to the new shape. +struct ShapeForAddHasher : public DefaultHasher<Shape*> { + using Key = SharedShape*; + + struct Lookup { + PropertyKey key; + PropertyFlags flags; + + Lookup(PropertyKey key, PropertyFlags flags) : key(key), flags(flags) {} + }; + + static MOZ_ALWAYS_INLINE HashNumber hash(const Lookup& l); + static MOZ_ALWAYS_INLINE bool match(SharedShape* shape, const Lookup& l); +}; +using ShapeSetForAdd = + HashSet<SharedShape*, ShapeForAddHasher, SystemAllocPolicy>; + +// Each shape has a cache pointer that's either: +// +// * None +// * For shared shapes, a single shape used to speed up addProperty. +// * For shared shapes, a set of shapes used to speed up addProperty. +// * For prototype shapes, the most recently used initial shape allocated for a +// prototype object with this shape. +// * For any shape, a PropertyIteratorObject used to speed up GetIterator. +// +// The cache is purely an optimization and is purged on GC (all shapes with a +// non-None ShapeCachePtr are added to a vector in the Zone). +class ShapeCachePtr { + enum { + SINGLE_SHAPE_FOR_ADD = 0, + SHAPE_SET_FOR_ADD = 1, + SHAPE_WITH_PROTO = 2, + ITERATOR = 3, + MASK = 3 + }; + + uintptr_t bits = 0; + + public: + bool isNone() const { return !bits; } + void setNone() { bits = 0; } + + bool isSingleShapeForAdd() const { + return (bits & MASK) == SINGLE_SHAPE_FOR_ADD && !isNone(); + } + SharedShape* toSingleShapeForAdd() const { + MOZ_ASSERT(isSingleShapeForAdd()); + return reinterpret_cast<SharedShape*>(bits & ~uintptr_t(MASK)); + } + void setSingleShapeForAdd(SharedShape* shape) { + MOZ_ASSERT(shape); + MOZ_ASSERT((uintptr_t(shape) & MASK) == 0); + MOZ_ASSERT(!isShapeSetForAdd()); // Don't leak the ShapeSet. + bits = uintptr_t(shape) | SINGLE_SHAPE_FOR_ADD; + } + + bool isShapeSetForAdd() const { return (bits & MASK) == SHAPE_SET_FOR_ADD; } + ShapeSetForAdd* toShapeSetForAdd() const { + MOZ_ASSERT(isShapeSetForAdd()); + return reinterpret_cast<ShapeSetForAdd*>(bits & ~uintptr_t(MASK)); + } + void setShapeSetForAdd(ShapeSetForAdd* hash) { + MOZ_ASSERT(hash); + MOZ_ASSERT((uintptr_t(hash) & MASK) == 0); + bits = uintptr_t(hash) | SHAPE_SET_FOR_ADD; + } + + bool isForAdd() const { return isSingleShapeForAdd() || isShapeSetForAdd(); } + + bool isShapeWithProto() const { return (bits & MASK) == SHAPE_WITH_PROTO; } + SharedShape* toShapeWithProto() const { + MOZ_ASSERT(isShapeWithProto()); + return reinterpret_cast<SharedShape*>(bits & ~uintptr_t(MASK)); + } + void setShapeWithProto(SharedShape* shape) { + MOZ_ASSERT(shape); + MOZ_ASSERT((uintptr_t(shape) & MASK) == 0); + MOZ_ASSERT(!isShapeSetForAdd()); // Don't leak the ShapeSet. + bits = uintptr_t(shape) | SHAPE_WITH_PROTO; + } + + bool isIterator() const { return (bits & MASK) == ITERATOR; } + PropertyIteratorObject* toIterator() const { + MOZ_ASSERT(isIterator()); + return reinterpret_cast<PropertyIteratorObject*>(bits & ~uintptr_t(MASK)); + } + void setIterator(PropertyIteratorObject* iter) { + MOZ_ASSERT(iter); + MOZ_ASSERT((uintptr_t(iter) & MASK) == 0); + MOZ_ASSERT(!isShapeSetForAdd()); // Don't leak the ShapeSet. + bits = uintptr_t(iter) | ITERATOR; + } + friend class js::jit::MacroAssembler; +} JS_HAZ_GC_POINTER; + +// BaseShapes store the object's class, realm and prototype. BaseShapes are +// immutable tuples stored in a per-Zone hash table. +class BaseShape : public gc::TenuredCellWithNonGCPointer<const JSClass> { + public: + /* Class of referring object, stored in the cell header */ + const JSClass* clasp() const { return headerPtr(); } + + private: + JS::Realm* realm_; + GCPtr<TaggedProto> proto_; + + BaseShape(const BaseShape& base) = delete; + BaseShape& operator=(const BaseShape& other) = delete; + + public: + void finalize(JS::GCContext* gcx) {} + + BaseShape(JSContext* cx, const JSClass* clasp, JS::Realm* realm, + TaggedProto proto); + + /* Not defined: BaseShapes must not be stack allocated. */ + ~BaseShape() = delete; + + JS::Realm* realm() const { return realm_; } + JS::Compartment* compartment() const { + return JS::GetCompartmentForRealm(realm()); + } + JS::Compartment* maybeCompartment() const { return compartment(); } + + TaggedProto proto() const { return proto_; } + + /* + * Lookup base shapes from the zone's baseShapes table, adding if not + * already found. + */ + static BaseShape* get(JSContext* cx, const JSClass* clasp, JS::Realm* realm, + Handle<TaggedProto> proto); + + static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape; + + void traceChildren(JSTracer* trc); + + static constexpr size_t offsetOfClasp() { return offsetOfHeaderPtr(); } + + static constexpr size_t offsetOfRealm() { + return offsetof(BaseShape, realm_); + } + + static constexpr size_t offsetOfProto() { + return offsetof(BaseShape, proto_); + } + + private: + static void staticAsserts() { + static_assert(offsetOfClasp() == offsetof(JS::shadow::BaseShape, clasp)); + static_assert(offsetOfRealm() == offsetof(JS::shadow::BaseShape, realm)); + static_assert(sizeof(BaseShape) % gc::CellAlignBytes == 0, + "Things inheriting from gc::Cell must have a size that's " + "a multiple of gc::CellAlignBytes"); + // Sanity check BaseShape size is what we expect. +#ifdef JS_64BIT + static_assert(sizeof(BaseShape) == 3 * sizeof(void*)); +#else + static_assert(sizeof(BaseShape) == 4 * sizeof(void*)); +#endif + } + + public: +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::GenericPrinter& out) const; + void dump(js::JSONPrinter& json) const; + + void dumpFields(js::JSONPrinter& json) const; +#endif +}; + +class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> { + friend class ::JSObject; + friend class ::JSFunction; + friend class GCMarker; + friend class NativeObject; + friend class SharedShape; + friend class PropertyTree; + friend class gc::TenuringTracer; + friend class JS::ubi::Concrete<Shape>; + friend class gc::RelocationOverlay; + + public: + // Base shape, stored in the cell header. + BaseShape* base() const { return headerPtr(); } + + using Kind = JS::shadow::Shape::Kind; + + protected: + // Flags that are not modified after the Shape is created. Off-thread Ion + // compilation can access the immutableFlags word, so we don't want any + // mutable state here to avoid (TSan) races. + enum ImmutableFlags : uint32_t { + // For NativeShape: the length associated with the property map. This is a + // value in the range [0, PropMap::Capacity]. A length of 0 indicates the + // object is empty (has no properties). + MAP_LENGTH_MASK = BitMask(4), + + // The Shape Kind. The NativeObject kinds have the low bit set. + KIND_SHIFT = 4, + KIND_MASK = 0b11, + IS_NATIVE_BIT = 0x1 << KIND_SHIFT, + + // For NativeShape: the number of fixed slots in objects with this shape. + // FIXED_SLOTS_MAX is the biggest count of fixed slots a Shape can store. + FIXED_SLOTS_MAX = 0x1f, + FIXED_SLOTS_SHIFT = 6, + FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT), + + // For SharedShape: the slot span of the object, if it fits in a single + // byte. If the value is SMALL_SLOTSPAN_MAX, the slot span has to be + // computed based on the property map (which is slower). + // + // Note: NativeObject::addProperty will convert to dictionary mode before we + // reach this limit, but there are other places where we add properties to + // shapes, for example environment object shapes. + SMALL_SLOTSPAN_MAX = 0x3ff, // 10 bits. + SMALL_SLOTSPAN_SHIFT = 11, + SMALL_SLOTSPAN_MASK = uint32_t(SMALL_SLOTSPAN_MAX << SMALL_SLOTSPAN_SHIFT), + }; + + uint32_t immutableFlags; // Immutable flags, see above. + ObjectFlags objectFlags_; // Immutable object flags, see ObjectFlags. + + // Cache used to speed up common operations on shapes. + ShapeCachePtr cache_; + + // Give the object a shape that's similar to its current shape, but with the + // passed objectFlags, proto, and nfixed values. + static bool replaceShape(JSContext* cx, HandleObject obj, + ObjectFlags objectFlags, TaggedProto proto, + uint32_t nfixed); + + public: + void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, + JS::ShapeInfo* info) const { + if (cache_.isShapeSetForAdd()) { + info->shapesMallocHeapCache += + cache_.toShapeSetForAdd()->shallowSizeOfIncludingThis(mallocSizeOf); + } + } + + ShapeCachePtr& cacheRef() { return cache_; } + ShapeCachePtr cache() const { return cache_; } + + void maybeCacheIterator(JSContext* cx, PropertyIteratorObject* iter); + + const JSClass* getObjectClass() const { return base()->clasp(); } + JS::Realm* realm() const { return base()->realm(); } + + JS::Compartment* compartment() const { return base()->compartment(); } + JS::Compartment* maybeCompartment() const { + return base()->maybeCompartment(); + } + + TaggedProto proto() const { return base()->proto(); } + + ObjectFlags objectFlags() const { return objectFlags_; } + bool hasObjectFlag(ObjectFlag flag) const { + return objectFlags_.hasFlag(flag); + } + + protected: + Shape(Kind kind, BaseShape* base, ObjectFlags objectFlags) + : CellWithTenuredGCPointer(base), + immutableFlags(uint32_t(kind) << KIND_SHIFT), + objectFlags_(objectFlags) { + MOZ_ASSERT(base); + MOZ_ASSERT(this->kind() == kind, "kind must fit in KIND_MASK"); + MOZ_ASSERT(isNative() == base->clasp()->isNativeObject()); + } + + Shape(const Shape& other) = delete; + + public: + Kind kind() const { return Kind((immutableFlags >> KIND_SHIFT) & KIND_MASK); } + + bool isNative() const { + // Note: this is equivalent to `isShared() || isDictionary()`. + return immutableFlags & IS_NATIVE_BIT; + } + + bool isShared() const { return kind() == Kind::Shared; } + bool isDictionary() const { return kind() == Kind::Dictionary; } + bool isProxy() const { return kind() == Kind::Proxy; } + bool isWasmGC() const { return kind() == Kind::WasmGC; } + + inline NativeShape& asNative(); + inline SharedShape& asShared(); + inline DictionaryShape& asDictionary(); + inline WasmGCShape& asWasmGC(); + + inline const NativeShape& asNative() const; + inline const SharedShape& asShared() const; + inline const DictionaryShape& asDictionary() const; + inline const WasmGCShape& asWasmGC() const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::GenericPrinter& out) const; + void dump(js::JSONPrinter& json) const; + + void dumpFields(js::JSONPrinter& json) const; + void dumpStringContent(js::GenericPrinter& out) const; +#endif + + inline void purgeCache(JS::GCContext* gcx); + inline void finalize(JS::GCContext* gcx); + + static const JS::TraceKind TraceKind = JS::TraceKind::Shape; + + void traceChildren(JSTracer* trc); + + // For JIT usage. + static constexpr size_t offsetOfBaseShape() { return offsetOfHeaderPtr(); } + + static constexpr size_t offsetOfObjectFlags() { + return offsetof(Shape, objectFlags_); + } + + static inline size_t offsetOfImmutableFlags() { + return offsetof(Shape, immutableFlags); + } + + static constexpr uint32_t kindShift() { return KIND_SHIFT; } + static constexpr uint32_t kindMask() { return KIND_MASK; } + static constexpr uint32_t isNativeBit() { return IS_NATIVE_BIT; } + + static constexpr size_t offsetOfCachePtr() { return offsetof(Shape, cache_); } + + private: + static void staticAsserts() { + static_assert(offsetOfBaseShape() == offsetof(JS::shadow::Shape, base)); + static_assert(offsetof(Shape, immutableFlags) == + offsetof(JS::shadow::Shape, immutableFlags)); + static_assert(KIND_SHIFT == JS::shadow::Shape::KIND_SHIFT); + static_assert(KIND_MASK == JS::shadow::Shape::KIND_MASK); + static_assert(FIXED_SLOTS_SHIFT == JS::shadow::Shape::FIXED_SLOTS_SHIFT); + static_assert(FIXED_SLOTS_MASK == JS::shadow::Shape::FIXED_SLOTS_MASK); + } +}; + +// Shared or dictionary shape for a NativeObject. +class NativeShape : public Shape { + protected: + // The shape's property map. This is either nullptr (for an + // initial SharedShape with no properties), a SharedPropMap (for + // SharedShape) or a DictionaryPropMap (for DictionaryShape). + GCPtr<PropMap*> propMap_; + + NativeShape(Kind kind, BaseShape* base, ObjectFlags objectFlags, + uint32_t nfixed, PropMap* map, uint32_t mapLength) + : Shape(kind, base, objectFlags), propMap_(map) { + MOZ_ASSERT(base->clasp()->isNativeObject()); + MOZ_ASSERT(mapLength <= PropMap::Capacity); + immutableFlags |= (nfixed << FIXED_SLOTS_SHIFT) | mapLength; + } + + public: + void traceChildren(JSTracer* trc); + + PropMap* propMap() const { return propMap_; } + uint32_t propMapLength() const { return immutableFlags & MAP_LENGTH_MASK; } + + PropertyInfoWithKey lastProperty() const { + MOZ_ASSERT(propMapLength() > 0); + size_t index = propMapLength() - 1; + return propMap()->getPropertyInfoWithKey(index); + } + + MOZ_ALWAYS_INLINE PropMap* lookup(JSContext* cx, PropertyKey key, + uint32_t* index); + MOZ_ALWAYS_INLINE PropMap* lookupPure(PropertyKey key, uint32_t* index); + + uint32_t numFixedSlots() const { + return (immutableFlags & FIXED_SLOTS_MASK) >> FIXED_SLOTS_SHIFT; + } + + // For JIT usage. + static constexpr uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; } + static constexpr uint32_t fixedSlotsShift() { return FIXED_SLOTS_SHIFT; } +}; + +// Shared shape for a NativeObject. +class SharedShape : public NativeShape { + friend class js::gc::CellAllocator; + SharedShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed, + SharedPropMap* map, uint32_t mapLength) + : NativeShape(Kind::Shared, base, objectFlags, nfixed, map, mapLength) { + initSmallSlotSpan(); + } + + static SharedShape* new_(JSContext* cx, Handle<BaseShape*> base, + ObjectFlags objectFlags, uint32_t nfixed, + Handle<SharedPropMap*> map, uint32_t mapLength); + + void initSmallSlotSpan() { + MOZ_ASSERT(isShared()); + uint32_t slotSpan = slotSpanSlow(); + if (slotSpan > SMALL_SLOTSPAN_MAX) { + slotSpan = SMALL_SLOTSPAN_MAX; + } + MOZ_ASSERT((immutableFlags & SMALL_SLOTSPAN_MASK) == 0); + immutableFlags |= (slotSpan << SMALL_SLOTSPAN_SHIFT); + } + + public: + SharedPropMap* propMap() const { + MOZ_ASSERT(isShared()); + return propMap_ ? propMap_->asShared() : nullptr; + } + inline SharedPropMap* propMapMaybeForwarded() const; + + bool lastPropertyMatchesForAdd(PropertyKey key, PropertyFlags flags, + uint32_t* slot) const { + MOZ_ASSERT(isShared()); + MOZ_ASSERT(propMapLength() > 0); + uint32_t index = propMapLength() - 1; + SharedPropMap* map = propMap(); + if (map->getKey(index) != key) { + return false; + } + PropertyInfo prop = map->getPropertyInfo(index); + if (prop.flags() != flags) { + return false; + } + *slot = prop.maybeSlot(); + return true; + } + + uint32_t slotSpanSlow() const { + MOZ_ASSERT(isShared()); + const JSClass* clasp = getObjectClass(); + return SharedPropMap::slotSpan(clasp, propMap(), propMapLength()); + } + uint32_t slotSpan() const { + MOZ_ASSERT(isShared()); + uint32_t span = + (immutableFlags & SMALL_SLOTSPAN_MASK) >> SMALL_SLOTSPAN_SHIFT; + if (MOZ_LIKELY(span < SMALL_SLOTSPAN_MAX)) { + MOZ_ASSERT(slotSpanSlow() == span); + return span; + } + return slotSpanSlow(); + } + + /* + * Lookup an initial shape matching the given parameters, creating an empty + * shape if none was found. + */ + static SharedShape* getInitialShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + size_t nfixed, + ObjectFlags objectFlags = {}); + static SharedShape* getInitialShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + gc::AllocKind kind, + ObjectFlags objectFlags = {}); + + static SharedShape* getPropMapShape(JSContext* cx, BaseShape* base, + size_t nfixed, Handle<SharedPropMap*> map, + uint32_t mapLength, + ObjectFlags objectFlags, + bool* allocatedNewShape = nullptr); + + static SharedShape* getInitialOrPropMapShape( + JSContext* cx, const JSClass* clasp, JS::Realm* realm, TaggedProto proto, + size_t nfixed, Handle<SharedPropMap*> map, uint32_t mapLength, + ObjectFlags objectFlags); + + /* + * Reinsert an alternate initial shape, to be returned by future + * getInitialShape calls, until the new shape becomes unreachable in a GC + * and the table entry is purged. + */ + static void insertInitialShape(JSContext* cx, Handle<SharedShape*> shape); + + /* + * Some object subclasses are allocated with a built-in set of properties. + * The first time such an object is created, these built-in properties must + * be set manually, to compute an initial shape. Afterward, that initial + * shape can be reused for newly-created objects that use the subclass's + * standard prototype. This method should be used in a post-allocation + * init method, to ensure that objects of such subclasses compute and cache + * the initial shape, if it hasn't already been computed. + */ + template <class ObjectSubclass> + static inline bool ensureInitialCustomShape(JSContext* cx, + Handle<ObjectSubclass*> obj); +}; + +// Dictionary shape for a NativeObject. +class DictionaryShape : public NativeShape { + friend class ::JSObject; + friend class js::gc::CellAllocator; + friend class NativeObject; + + DictionaryShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed, + DictionaryPropMap* map, uint32_t mapLength) + : NativeShape(Kind::Dictionary, base, objectFlags, nfixed, map, + mapLength) { + MOZ_ASSERT(map); + } + explicit DictionaryShape(NativeObject* nobj); + + // Methods to set fields of a new dictionary shape. Must not be used for + // shapes that might have been exposed to script. + void updateNewShape(ObjectFlags flags, DictionaryPropMap* map, + uint32_t mapLength) { + MOZ_ASSERT(isDictionary()); + objectFlags_ = flags; + propMap_ = map; + immutableFlags = (immutableFlags & ~MAP_LENGTH_MASK) | mapLength; + MOZ_ASSERT(propMapLength() == mapLength); + } + void setObjectFlagsOfNewShape(ObjectFlags flags) { + MOZ_ASSERT(isDictionary()); + objectFlags_ = flags; + } + + public: + static DictionaryShape* new_(JSContext* cx, Handle<BaseShape*> base, + ObjectFlags objectFlags, uint32_t nfixed, + Handle<DictionaryPropMap*> map, + uint32_t mapLength); + static DictionaryShape* new_(JSContext* cx, Handle<NativeObject*> obj); + + DictionaryPropMap* propMap() const { + MOZ_ASSERT(isDictionary()); + MOZ_ASSERT(propMap_); + return propMap_->asDictionary(); + } +}; + +// Shape used for a ProxyObject. +class ProxyShape : public Shape { + // Needed to maintain the same size as other shapes. + uintptr_t padding_; + + friend class js::gc::CellAllocator; + ProxyShape(BaseShape* base, ObjectFlags objectFlags) + : Shape(Kind::Proxy, base, objectFlags) { + MOZ_ASSERT(base->clasp()->isProxyObject()); + } + + static ProxyShape* new_(JSContext* cx, Handle<BaseShape*> base, + ObjectFlags objectFlags); + + public: + static ProxyShape* getShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + ObjectFlags objectFlags); + + private: + static void staticAsserts() { + // Silence unused field warning. + static_assert(sizeof(padding_) == sizeof(uintptr_t)); + } +}; + +// Shape used for a WasmGCObject. +class WasmGCShape : public Shape { + // The shape's recursion group. + const wasm::RecGroup* recGroup_; + + friend class js::gc::CellAllocator; + WasmGCShape(BaseShape* base, const wasm::RecGroup* recGroup, + ObjectFlags objectFlags) + : Shape(Kind::WasmGC, base, objectFlags), recGroup_(recGroup) { + MOZ_ASSERT(!base->clasp()->isProxyObject()); + MOZ_ASSERT(!base->clasp()->isNativeObject()); + } + + static WasmGCShape* new_(JSContext* cx, Handle<BaseShape*> base, + const wasm::RecGroup* recGroup, + ObjectFlags objectFlags); + + // Take a reference to the recursion group. + inline void init(); + + public: + static WasmGCShape* getShape(JSContext* cx, const JSClass* clasp, + JS::Realm* realm, TaggedProto proto, + const wasm::RecGroup* recGroup, + ObjectFlags objectFlags); + + // Release the reference to the recursion group. + inline void finalize(JS::GCContext* gcx); + + const wasm::RecGroup* recGroup() const { + MOZ_ASSERT(isWasmGC()); + return recGroup_; + } +}; + +// A type that can be used to get the size of the Shape alloc kind. +class SizedShape : public Shape { + // The various shape kinds have an extra word that is used defined + // differently depending on the type. + uintptr_t padding_; + + static void staticAsserts() { + // Silence unused field warning. + static_assert(sizeof(padding_) == sizeof(uintptr_t)); + + // Sanity check Shape size is what we expect. +#ifdef JS_64BIT + static_assert(sizeof(SizedShape) == 4 * sizeof(void*)); +#else + static_assert(sizeof(SizedShape) == 6 * sizeof(void*)); +#endif + + // All shape kinds must have the same size. + static_assert(sizeof(NativeShape) == sizeof(SizedShape)); + static_assert(sizeof(SharedShape) == sizeof(SizedShape)); + static_assert(sizeof(DictionaryShape) == sizeof(SizedShape)); + static_assert(sizeof(ProxyShape) == sizeof(SizedShape)); + static_assert(sizeof(WasmGCShape) == sizeof(SizedShape)); + } +}; + +inline NativeShape& js::Shape::asNative() { + MOZ_ASSERT(isNative()); + return *static_cast<NativeShape*>(this); +} + +inline SharedShape& js::Shape::asShared() { + MOZ_ASSERT(isShared()); + return *static_cast<SharedShape*>(this); +} + +inline DictionaryShape& js::Shape::asDictionary() { + MOZ_ASSERT(isDictionary()); + return *static_cast<DictionaryShape*>(this); +} + +inline WasmGCShape& js::Shape::asWasmGC() { + MOZ_ASSERT(isWasmGC()); + return *static_cast<WasmGCShape*>(this); +} + +inline const NativeShape& js::Shape::asNative() const { + MOZ_ASSERT(isNative()); + return *static_cast<const NativeShape*>(this); +} + +inline const SharedShape& js::Shape::asShared() const { + MOZ_ASSERT(isShared()); + return *static_cast<const SharedShape*>(this); +} + +inline const DictionaryShape& js::Shape::asDictionary() const { + MOZ_ASSERT(isDictionary()); + return *static_cast<const DictionaryShape*>(this); +} + +inline const WasmGCShape& js::Shape::asWasmGC() const { + MOZ_ASSERT(isWasmGC()); + return *static_cast<const WasmGCShape*>(this); +} + +// Iterator for iterating over a shape's properties. It can be used like this: +// +// for (ShapePropertyIter<NoGC> iter(nobj->shape()); !iter.done(); iter++) { +// PropertyKey key = iter->key(); +// if (iter->isDataProperty() && iter->enumerable()) { .. } +// } +// +// Properties are iterated in reverse order (i.e., iteration starts at the most +// recently added property). +template <AllowGC allowGC> +class MOZ_RAII ShapePropertyIter { + typename MaybeRooted<PropMap*, allowGC>::RootType map_; + uint32_t mapLength_; + const bool isDictionary_; + + protected: + ShapePropertyIter(JSContext* cx, NativeShape* shape, bool isDictionary) + : map_(cx, shape->propMap()), + mapLength_(shape->propMapLength()), + isDictionary_(isDictionary) { + static_assert(allowGC == CanGC); + MOZ_ASSERT(shape->isDictionary() == isDictionary); + MOZ_ASSERT(shape->isNative()); + } + ShapePropertyIter(NativeShape* shape, bool isDictionary) + : map_(nullptr, shape->propMap()), + mapLength_(shape->propMapLength()), + isDictionary_(isDictionary) { + static_assert(allowGC == NoGC); + MOZ_ASSERT(shape->isDictionary() == isDictionary); + MOZ_ASSERT(shape->isNative()); + } + + public: + ShapePropertyIter(JSContext* cx, NativeShape* shape) + : ShapePropertyIter(cx, shape, shape->isDictionary()) {} + + explicit ShapePropertyIter(NativeShape* shape) + : ShapePropertyIter(shape, shape->isDictionary()) {} + + // Deleted constructors: use SharedShapePropertyIter instead. + ShapePropertyIter(JSContext* cx, SharedShape* shape) = delete; + explicit ShapePropertyIter(SharedShape* shape) = delete; + + bool done() const { return mapLength_ == 0; } + + void operator++(int) { + do { + MOZ_ASSERT(!done()); + if (mapLength_ > 1) { + mapLength_--; + } else if (map_->hasPrevious()) { + map_ = map_->asLinked()->previous(); + mapLength_ = PropMap::Capacity; + } else { + // Done iterating. + map_ = nullptr; + mapLength_ = 0; + return; + } + // Dictionary maps can have "holes" for removed properties, so keep going + // until we find a non-hole slot. + } while (MOZ_UNLIKELY(isDictionary_ && !map_->hasKey(mapLength_ - 1))); + } + + PropertyInfoWithKey get() const { + MOZ_ASSERT(!done()); + return map_->getPropertyInfoWithKey(mapLength_ - 1); + } + + PropertyInfoWithKey operator*() const { return get(); } + + // Fake pointer struct to make operator-> work. + // See https://stackoverflow.com/a/52856349. + struct FakePtr { + PropertyInfoWithKey val_; + const PropertyInfoWithKey* operator->() const { return &val_; } + }; + FakePtr operator->() const { return {get()}; } +}; + +// Optimized version of ShapePropertyIter for non-dictionary shapes. It passes +// `false` for `isDictionary_`, which will let the compiler optimize away the +// loop structure in ShapePropertyIter::operator++. +template <AllowGC allowGC> +class MOZ_RAII SharedShapePropertyIter : public ShapePropertyIter<allowGC> { + public: + SharedShapePropertyIter(JSContext* cx, SharedShape* shape) + : ShapePropertyIter<allowGC>(cx, shape, /* isDictionary = */ false) {} + + explicit SharedShapePropertyIter(SharedShape* shape) + : ShapePropertyIter<allowGC>(shape, /* isDictionary = */ false) {} +}; + +} // namespace js + +// JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell +// instances that occupy a compartment. +namespace JS { +namespace ubi { + +template <> +class Concrete<js::Shape> : TracerConcrete<js::Shape> { + protected: + explicit Concrete(js::Shape* ptr) : TracerConcrete<js::Shape>(ptr) {} + + public: + static void construct(void* storage, js::Shape* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +template <> +class Concrete<js::BaseShape> : TracerConcrete<js::BaseShape> { + protected: + explicit Concrete(js::BaseShape* ptr) : TracerConcrete<js::BaseShape>(ptr) {} + + public: + static void construct(void* storage, js::BaseShape* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +} // namespace ubi +} // namespace JS + +#endif /* vm_Shape_h */ |