diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/vm/Shape.h | 1854 |
1 files changed, 1854 insertions, 0 deletions
diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h new file mode 100644 index 0000000000..fee07a2f0d --- /dev/null +++ b/js/src/vm/Shape.h @@ -0,0 +1,1854 @@ +/* -*- 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/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/TemplateLib.h" + +#include <algorithm> + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jstypes.h" +#include "NamespaceImports.h" + +#include "gc/Barrier.h" +#include "gc/FreeOp.h" +#include "gc/MaybeRooted.h" +#include "gc/Rooting.h" +#include "js/HashTable.h" +#include "js/Id.h" // JS::PropertyKey +#include "js/MemoryMetrics.h" +#include "js/RootingAPI.h" +#include "js/UbiNode.h" +#include "vm/JSAtom.h" +#include "vm/ObjectGroup.h" +#include "vm/Printer.h" +#include "vm/StringType.h" +#include "vm/SymbolType.h" + +/* + * [SMDOC] Shapes + * + * In isolation, a Shape represents a property that exists in one or more + * objects; it has an id, flags, etc. (But it doesn't represent the property's + * value.) However, Shapes are always stored in linked linear sequence of + * Shapes, called "shape lineages". Each shape lineage represents the layout of + * an entire object. + * + * Every JSObject has a pointer, |shape_|, accessible via lastProperty(), to + * the last Shape in a shape lineage, which identifies the property most + * recently added to the object. This pointer permits fast object layout + * tests. The shape lineage order also dictates the enumeration order for the + * object; ECMA requires no particular order but this implementation has + * promised and delivered property definition order. + * + * Shape lineages occur in two kinds of data structure. + * + * 1. N-ary property trees. Each path from a non-root node to the root node in + * a property tree is a shape lineage. Property trees permit full (or + * partial) sharing of Shapes between objects that have fully (or partly) + * identical layouts. The root is an EmptyShape whose identity is determined + * by the object's class, compartment and prototype. These Shapes are shared + * and immutable. + * + * 2. Dictionary mode lists. Shapes in such lists are said to be "in + * dictionary mode", as are objects that point to such Shapes. These Shapes + * are unshared, private to a single object, and immutable except for their + * links in the dictionary list. + * + * All shape lineages are bi-directionally linked, via the |parent| and + * |children|/|listp| members. + * + * Shape lineages start out life in the property tree. They can be converted + * (by copying) to dictionary mode lists in the following circumstances. + * + * 1. The shape lineage's size reaches MAX_HEIGHT. This reasonable limit avoids + * potential worst cases involving shape lineage mutations. + * + * 2. A property represented by a non-last Shape in a shape lineage is removed + * from an object. (In the last Shape case, obj->shape_ can be easily + * adjusted to point to obj->shape_->parent.) We originally tried lazy + * forking of the property tree, but this blows up for delete/add + * repetitions. + * + * 3. A property represented by a non-last Shape in a shape lineage has its + * attributes modified. + * + * To find the Shape for a particular property of an object initially requires + * a linear search. But if the number of searches starting at any particular + * Shape in the property tree exceeds LINEAR_SEARCHES_MAX and the Shape's + * lineage has (excluding the EmptyShape) at least MIN_ENTRIES, we create an + * auxiliary hash table -- the ShapeTable -- that allows faster lookup. + * Furthermore, a ShapeTable is always created for dictionary mode lists, + * and it is attached to the last Shape in the lineage. Shape tables for + * property tree Shapes never change, but shape tables for dictionary mode + * Shapes can grow and shrink. + * + * To save memory, shape tables can be discarded on GC and recreated when + * needed. AutoKeepShapeCaches can be used to avoid discarding shape tables + * for a particular zone. Methods operating on ShapeTables take either an + * AutoCheckCannotGC or AutoKeepShapeCaches argument, to help ensure tables + * are not purged while we're using them. + * + * There used to be a long, math-heavy comment here explaining why property + * trees are more space-efficient than alternatives. This was removed in bug + * 631138; see that bug for the full details. + * + * For getters/setters, an AccessorShape is allocated. This is a slightly fatter + * type with extra fields for the getter/setter data. + * + * Because many Shapes have similar data, there is actually a secondary type + * called a BaseShape that holds some of a Shape's data. 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->isProxy()); + return JSCLASS_RESERVED_SLOTS(clasp); +} + +namespace js { + +class Shape; +struct StackShape; + +struct ShapeHasher : public DefaultHasher<Shape*> { + using Key = Shape*; + using Lookup = StackShape; + + static MOZ_ALWAYS_INLINE HashNumber hash(const Lookup& l); + static MOZ_ALWAYS_INLINE bool match(Key k, const Lookup& l); +}; + +using ShapeSet = HashSet<Shape*, ShapeHasher, SystemAllocPolicy>; + +// A tagged pointer to null, a single child, or a many-children data structure. +class ShapeChildren { + // Tag bits must not overlap with DictionaryShapeLink. + enum { SINGLE_SHAPE = 0, SHAPE_SET = 1, MASK = 3 }; + + uintptr_t bits = 0; + + public: + bool isNone() const { return !bits; } + void setNone() { bits = 0; } + + bool isSingleShape() const { + return (bits & MASK) == SINGLE_SHAPE && !isNone(); + } + Shape* toSingleShape() const { + MOZ_ASSERT(isSingleShape()); + return reinterpret_cast<Shape*>(bits & ~uintptr_t(MASK)); + } + void setSingleShape(Shape* shape) { + MOZ_ASSERT(shape); + MOZ_ASSERT((uintptr_t(shape) & MASK) == 0); + bits = uintptr_t(shape) | SINGLE_SHAPE; + } + + bool isShapeSet() const { return (bits & MASK) == SHAPE_SET; } + ShapeSet* toShapeSet() const { + MOZ_ASSERT(isShapeSet()); + return reinterpret_cast<ShapeSet*>(bits & ~uintptr_t(MASK)); + } + void setShapeSet(ShapeSet* hash) { + MOZ_ASSERT(hash); + MOZ_ASSERT((uintptr_t(hash) & MASK) == 0); + bits = uintptr_t(hash) | SHAPE_SET; + } + +#ifdef DEBUG + void checkHasChild(Shape* child) const; +#endif +} JS_HAZ_GC_POINTER; + +// For dictionary mode shapes, a tagged pointer to the next shape or associated +// object if this is the last shape. +class DictionaryShapeLink { + // Tag bits must not overlap with ShapeChildren. + enum { SHAPE = 2, OBJECT = 3, MASK = 3 }; + + uintptr_t bits = 0; + + public: + // XXX Using = default on the default ctor causes rooting hazards for some + // reason. + DictionaryShapeLink() {} + explicit DictionaryShapeLink(JSObject* obj) { setObject(obj); } + explicit DictionaryShapeLink(Shape* shape) { setShape(shape); } + + bool isNone() const { return !bits; } + void setNone() { bits = 0; } + + bool isShape() const { return (bits & MASK) == SHAPE; } + Shape* toShape() const { + MOZ_ASSERT(isShape()); + return reinterpret_cast<Shape*>(bits & ~uintptr_t(MASK)); + } + void setShape(Shape* shape) { + MOZ_ASSERT(shape); + MOZ_ASSERT((uintptr_t(shape) & MASK) == 0); + bits = uintptr_t(shape) | SHAPE; + } + + bool isObject() const { return (bits & MASK) == OBJECT; } + JSObject* toObject() const { + MOZ_ASSERT(isObject()); + return reinterpret_cast<JSObject*>(bits & ~uintptr_t(MASK)); + } + void setObject(JSObject* obj) { + MOZ_ASSERT(obj); + MOZ_ASSERT((uintptr_t(obj) & MASK) == 0); + bits = uintptr_t(obj) | OBJECT; + } + + bool operator==(const DictionaryShapeLink& other) const { + return bits == other.bits; + } + bool operator!=(const DictionaryShapeLink& other) const { + return !((*this) == other); + } + + GCPtrShape* prevPtr(); +} JS_HAZ_GC_POINTER; + +class PropertyTree { + friend class ::JSFunction; + +#ifdef DEBUG + JS::Zone* zone_; +#endif + + bool insertChild(JSContext* cx, Shape* parent, Shape* child); + + PropertyTree(); + + public: + /* + * Use a lower limit for objects that are accessed using SETELEM (o[x] = y). + * These objects are likely used as hashmaps and dictionary mode is more + * efficient in this case. + */ + enum { MAX_HEIGHT = 512, MAX_HEIGHT_WITH_ELEMENTS_ACCESS = 128 }; + + explicit PropertyTree(JS::Zone* zone) +#ifdef DEBUG + : zone_(zone) +#endif + { + } + + MOZ_ALWAYS_INLINE Shape* inlinedGetChild(JSContext* cx, Shape* parent, + JS::Handle<StackShape> childSpec); + Shape* getChild(JSContext* cx, Shape* parent, JS::Handle<StackShape> child); +}; + +class TenuringTracer; + +using GetterOp = JSGetterOp; +using SetterOp = JSSetterOp; + +/* Limit on the number of slotful properties in an object. */ +static const uint32_t SHAPE_INVALID_SLOT = Bit(24) - 1; +static const uint32_t SHAPE_MAXIMUM_SLOT = Bit(24) - 2; + +enum class MaybeAdding { Adding = true, NotAdding = false }; + +class AutoKeepShapeCaches; + +/* + * ShapeIC uses a small array that is linearly searched. + */ +class ShapeIC { + public: + friend class NativeObject; + friend class BaseShape; + friend class Shape; + + ShapeIC() : size_(0), nextFreeIndex_(0), entries_(nullptr) {} + + ~ShapeIC() = default; + + bool isFull() const { + MOZ_ASSERT(nextFreeIndex_ <= size_); + return size_ == nextFreeIndex_; + } + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + mallocSizeOf(entries_.get()); + } + + uint32_t entryCount() { return nextFreeIndex_; } + + bool init(JSContext* cx); + void trace(JSTracer* trc); + +#ifdef JSGC_HASH_TABLE_CHECKS + void checkAfterMovingGC(); +#endif + + MOZ_ALWAYS_INLINE bool search(jsid id, Shape** foundShape); + + MOZ_ALWAYS_INLINE bool appendEntry(jsid id, Shape* shape) { + MOZ_ASSERT(nextFreeIndex_ <= size_); + if (nextFreeIndex_ == size_) { + return false; + } + + entries_[nextFreeIndex_].id_ = id; + entries_[nextFreeIndex_].shape_ = shape; + nextFreeIndex_++; + return true; + } + + private: + static const uint32_t MAX_SIZE = 7; + + class Entry { + public: + jsid id_; + Shape* shape_; + + Entry() = delete; + Entry(const Entry&) = delete; + Entry& operator=(const Entry&) = delete; + }; + + uint8_t size_; + uint8_t nextFreeIndex_; + + /* table of ptrs to {jsid,Shape*} pairs */ + UniquePtr<Entry[], JS::FreePolicy> entries_; +}; + +/* + * ShapeTable uses multiplicative hashing, but specialized to + * minimize footprint. + */ +class ShapeTable { + public: + friend class NativeObject; + friend class BaseShape; + friend class Shape; + friend class ShapeCachePtr; + + class Entry { + // js::Shape pointer tag bit indicating a collision. + static const uintptr_t SHAPE_COLLISION = 1; + static Shape* const SHAPE_REMOVED; // = SHAPE_COLLISION + + Shape* shape_; + + Entry() = delete; + Entry(const Entry&) = delete; + Entry& operator=(const Entry&) = delete; + + public: + bool isFree() const { return shape_ == nullptr; } + bool isRemoved() const { return shape_ == SHAPE_REMOVED; } + bool isLive() const { return !isFree() && !isRemoved(); } + bool hadCollision() const { return uintptr_t(shape_) & SHAPE_COLLISION; } + + void setFree() { shape_ = nullptr; } + void setRemoved() { shape_ = SHAPE_REMOVED; } + + Shape* shape() const { + return reinterpret_cast<Shape*>(uintptr_t(shape_) & ~SHAPE_COLLISION); + } + + void setShape(Shape* shape) { + MOZ_ASSERT(isFree()); + MOZ_ASSERT(shape); + MOZ_ASSERT(shape != SHAPE_REMOVED); + shape_ = shape; + MOZ_ASSERT(!hadCollision()); + } + + void flagCollision() { + shape_ = reinterpret_cast<Shape*>(uintptr_t(shape_) | SHAPE_COLLISION); + } + void setPreservingCollision(Shape* shape) { + shape_ = reinterpret_cast<Shape*>(uintptr_t(shape) | + uintptr_t(hadCollision())); + } + }; + + private: + static const uint32_t HASH_BITS = mozilla::tl::BitSize<HashNumber>::value; + + // This value is low because it's common for a ShapeTable to be created + // with an entryCount of zero. + static const uint32_t MIN_SIZE_LOG2 = 2; + static const uint32_t MIN_SIZE = Bit(MIN_SIZE_LOG2); + + uint32_t hashShift_; /* multiplicative hash shift */ + + uint32_t entryCount_; /* number of entries in table */ + uint32_t removedCount_; /* removed entry sentinels in table */ + + uint32_t freeList_; /* SHAPE_INVALID_SLOT or head of slot + freelist in owning dictionary-mode + object */ + + UniquePtr<Entry[], JS::FreePolicy> + entries_; /* table of ptrs to shared tree nodes */ + + template <MaybeAdding Adding> + MOZ_ALWAYS_INLINE Entry& searchUnchecked(jsid id); + + public: + explicit ShapeTable(uint32_t nentries) + : hashShift_(HASH_BITS - MIN_SIZE_LOG2), + entryCount_(nentries), + removedCount_(0), + freeList_(SHAPE_INVALID_SLOT), + entries_(nullptr) { + /* NB: entries is set by init, which must be called. */ + } + + ~ShapeTable() = default; + + uint32_t entryCount() const { return entryCount_; } + + uint32_t freeList() const { return freeList_; } + void setFreeList(uint32_t slot) { freeList_ = slot; } + + /* + * This counts the ShapeTable object itself (which must be + * heap-allocated) and its |entries| array. + */ + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + mallocSizeOf(entries_.get()); + } + + // init() is fallible and reports OOM to the context. + bool init(JSContext* cx, Shape* lastProp); + + // change() is fallible but does not report OOM. + bool change(JSContext* cx, int log2Delta); + + template <MaybeAdding Adding> + MOZ_ALWAYS_INLINE Entry& search(jsid id, const AutoKeepShapeCaches&); + + template <MaybeAdding Adding> + MOZ_ALWAYS_INLINE Entry& search(jsid id, const JS::AutoCheckCannotGC&); + + void trace(JSTracer* trc); +#ifdef JSGC_HASH_TABLE_CHECKS + void checkAfterMovingGC(); +#endif + + private: + Entry& getEntry(uint32_t i) const { + MOZ_ASSERT(i < capacity()); + return entries_[i]; + } + void decEntryCount() { + MOZ_ASSERT(entryCount_ > 0); + entryCount_--; + } + void incEntryCount() { + entryCount_++; + MOZ_ASSERT(entryCount_ + removedCount_ <= capacity()); + } + void incRemovedCount() { + removedCount_++; + MOZ_ASSERT(entryCount_ + removedCount_ <= capacity()); + } + + // By definition, hashShift = HASH_BITS - log2(capacity). + uint32_t capacity() const { return Bit(HASH_BITS - hashShift_); } + + // Whether we need to grow. We want to do this if the load factor + // is >= 0.75 + bool needsToGrow() const { + uint32_t size = capacity(); + return entryCount_ + removedCount_ >= size - (size >> 2); + } + + // Try to grow the table. On failure, reports out of memory on cx + // and returns false. This will make any extant pointers into the + // table invalid. Don't call this unless needsToGrow() is true. + bool grow(JSContext* cx); +}; + +/* + * Wrapper class to either ShapeTable or ShapeIC optimization. + * + * Shapes are initially cached in a linear cache from the ShapeIC class that is + * lazily initialized after LINEAR_SEARCHES_MAX searches have been reached, and + * the Shape has at least MIN_ENTRIES parents in the lineage. + * + * We use the population of the cache as an indicator of whether the ShapeIC is + * working or not. Once it is full, it is destroyed and a ShapeTable is + * created instead. + * + * For dictionaries, the linear cache is skipped entirely and hashify is used + * to generate the ShapeTable immediately. + */ +class ShapeCachePtr { + // To reduce impact on memory usage, p is the only data member for this class. + uintptr_t p; + + enum class CacheType { + IC = 0x1, + Table = 0x2, + }; + + static const uint32_t MASK_BITS = 0x3; + static const uintptr_t CACHETYPE_MASK = 0x3; + + void* getPointer() const { + uintptr_t ptrVal = p & ~CACHETYPE_MASK; + return reinterpret_cast<void*>(ptrVal); + } + + CacheType getType() const { + return static_cast<CacheType>(p & CACHETYPE_MASK); + } + + public: + static const uint32_t MIN_ENTRIES = 3; + + ShapeCachePtr() : p(0) {} + + template <MaybeAdding Adding> + MOZ_ALWAYS_INLINE bool search(jsid id, Shape* start, Shape** foundShape); + + bool isIC() const { return (getType() == CacheType::IC); } + bool isTable() const { return (getType() == CacheType::Table); } + bool isInitialized() const { return isTable() || isIC(); } + + ShapeTable* getTablePointer() const { + MOZ_ASSERT(isTable()); + return reinterpret_cast<ShapeTable*>(getPointer()); + } + + ShapeIC* getICPointer() const { + MOZ_ASSERT(isIC()); + return reinterpret_cast<ShapeIC*>(getPointer()); + } + + // Use ShapeTable implementation. + // The caller must have purged any existing IC implementation. + void initializeTable(ShapeTable* table) { + MOZ_ASSERT(!isTable() && !isIC()); + + uintptr_t tableptr = uintptr_t(table); + + // Double check that pointer is 4 byte aligned. + MOZ_ASSERT((tableptr & CACHETYPE_MASK) == 0); + + tableptr |= static_cast<uintptr_t>(CacheType::Table); + p = tableptr; + } + + // Use ShapeIC implementation. + // This cannot clobber an existing Table implementation. + void initializeIC(ShapeIC* ic) { + MOZ_ASSERT(!isTable() && !isIC()); + + uintptr_t icptr = uintptr_t(ic); + + // Double check that pointer is 4 byte aligned. + MOZ_ASSERT((icptr & CACHETYPE_MASK) == 0); + + icptr |= static_cast<uintptr_t>(CacheType::IC); + p = icptr; + } + + void destroy(JSFreeOp* fop, BaseShape* base); + + void maybePurgeCache(JSFreeOp* fop, BaseShape* base); + + void trace(JSTracer* trc); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + size_t size = 0; + if (isIC()) { + size = getICPointer()->sizeOfIncludingThis(mallocSizeOf); + } else if (isTable()) { + size = getTablePointer()->sizeOfIncludingThis(mallocSizeOf); + } + return size; + } + + uint32_t entryCount() { + uint32_t count = 0; + if (isIC()) { + count = getICPointer()->entryCount(); + } else if (isTable()) { + count = getTablePointer()->entryCount(); + } + return count; + } + +#ifdef JSGC_HASH_TABLE_CHECKS + void checkAfterMovingGC(); +#endif +}; + +// Ensures no shape tables are purged in the current zone. +class MOZ_RAII AutoKeepShapeCaches { + JSContext* cx_; + bool prev_; + + public: + void operator=(const AutoKeepShapeCaches&) = delete; + AutoKeepShapeCaches(const AutoKeepShapeCaches&) = delete; + explicit inline AutoKeepShapeCaches(JSContext* cx); + inline ~AutoKeepShapeCaches(); +}; + +/* + * Shapes encode information about both a property lineage *and* a particular + * property. This information is split across the Shape and the BaseShape + * at shape->base(). Both Shape and BaseShape can be either owned or unowned + * by, respectively, the Object or Shape referring to them. + * + * Owned Shapes are used in dictionary objects, and form a doubly linked list + * whose entries are all owned by that dictionary. Unowned Shapes are all in + * the property tree. + * + * Owned BaseShapes are used for shapes which have shape tables, including the + * last properties in all dictionaries. Unowned BaseShapes compactly store + * information common to many shapes. In a given zone there is a single + * BaseShape for each combination of BaseShape information. This information is + * cloned in owned BaseShapes so that information can be quickly looked up for a + * given object or shape without regard to whether the base shape is owned or + * not. + * + * All combinations of owned/unowned Shapes/BaseShapes are possible: + * + * Owned Shape, Owned BaseShape: + * + * Last property in a dictionary object. The BaseShape is transferred from + * property to property as the object's last property changes. + * + * Owned Shape, Unowned BaseShape: + * + * Property in a dictionary object other than the last one. + * + * Unowned Shape, Owned BaseShape: + * + * Property in the property tree which has a shape table. + * + * Unowned Shape, Unowned BaseShape: + * + * Property in the property tree which does not have a shape table. + * + * BaseShapes additionally encode some information about the referring object + * itself. This includes the object's class and various flags that may be set + * for the object. Except for the class, this information is mutable and may + * change when the object has an established property lineage. On such changes + * the entire property lineage is not updated, but rather only the last property + * (and its base shape). This works because only the object's last property is + * used to query information about the object. Care must be taken to call + * JSObject::canRemoveLastProperty when unwinding an object to an earlier + * property, however. + */ + +class AccessorShape; +class Shape; +class UnownedBaseShape; +struct StackBaseShape; + +class BaseShape : public gc::TenuredCellWithNonGCPointer<const JSClass> { + public: + friend class Shape; + friend struct StackBaseShape; + friend struct StackShape; + + enum Flag { + /* Owned by the referring shape. */ + OWNED_SHAPE = 0x1, + + /* (0x2 and 0x4 are unused) */ + + /* + * Flags set which describe the referring object. Once set these cannot + * be unset (except during object densification of sparse indexes), and + * are transferred from shape to shape as the object's last property + * changes. + * + * If you add a new flag here, please add appropriate code to + * JSObject::dump to dump it as part of object representation. + */ + + DELEGATE = 0x8, + NOT_EXTENSIBLE = 0x10, + INDEXED = 0x20, + HAS_INTERESTING_SYMBOL = 0x40, + HAD_ELEMENTS_ACCESS = 0x80, + FROZEN_ELEMENTS = 0x100, // See ObjectElements::FROZEN comment. + // 0x200 is unused. + // 0x400 is unused. + UNCACHEABLE_PROTO = 0x800, + IMMUTABLE_PROTOTYPE = 0x1000, + + // See JSObject::isQualifiedVarObj(). + QUALIFIED_VAROBJ = 0x2000, + + // 0x4000 is unused. + // 0x8000 is unused. + + OBJECT_FLAG_MASK = 0xfff8 + }; + + private: + /* Class of referring object, stored in the cell header */ + const JSClass* clasp() const { return headerPtr(); } + + uint32_t flags; /* Vector of above flags. */ + + /* For owned BaseShapes, the canonical unowned BaseShape. */ + GCPtrUnownedBaseShape unowned_; + + /* For owned BaseShapes, the shape's shape table. */ + ShapeCachePtr cache_; + + BaseShape(const BaseShape& base) = delete; + BaseShape& operator=(const BaseShape& other) = delete; + + public: + void finalize(JSFreeOp* fop); + + explicit inline BaseShape(const StackBaseShape& base); + + /* Not defined: BaseShapes must not be stack allocated. */ + ~BaseShape(); + + bool isOwned() const { return !!(flags & OWNED_SHAPE); } + + static void copyFromUnowned(BaseShape& dest, UnownedBaseShape& src); + inline void adoptUnowned(UnownedBaseShape* other); + + void setOwned(UnownedBaseShape* unowned) { + flags |= OWNED_SHAPE; + unowned_ = unowned; + } + + uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; } + + bool hasTable() const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return cache_.isTable(); + } + + bool hasIC() const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return cache_.isIC(); + } + + void setTable(ShapeTable* table) { + MOZ_ASSERT(isOwned()); + cache_.initializeTable(table); + } + + void setIC(ShapeIC* ic) { + MOZ_ASSERT(isOwned()); + cache_.initializeIC(ic); + } + + ShapeCachePtr getCache(const AutoKeepShapeCaches&) const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return cache_; + } + + ShapeCachePtr getCache(const JS::AutoCheckCannotGC&) const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return cache_; + } + + ShapeTable* maybeTable(const AutoKeepShapeCaches&) const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return (cache_.isTable()) ? cache_.getTablePointer() : nullptr; + } + + ShapeTable* maybeTable(const JS::AutoCheckCannotGC&) const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return (cache_.isTable()) ? cache_.getTablePointer() : nullptr; + } + + ShapeIC* maybeIC(const AutoKeepShapeCaches&) const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return (cache_.isIC()) ? cache_.getICPointer() : nullptr; + } + + ShapeIC* maybeIC(const JS::AutoCheckCannotGC&) const { + MOZ_ASSERT_IF(cache_.isInitialized(), isOwned()); + return (cache_.isIC()) ? cache_.getICPointer() : nullptr; + } + + void maybePurgeCache(JSFreeOp* fop) { cache_.maybePurgeCache(fop, this); } + + /* + * Lookup base shapes from the zone's baseShapes table, adding if not + * already found. + */ + static UnownedBaseShape* getUnowned(JSContext* cx, StackBaseShape& base); + + /* Get the canonical base shape. */ + inline UnownedBaseShape* unowned(); + + /* Get the canonical base shape for an owned one. */ + inline UnownedBaseShape* baseUnowned(); + + /* Get the canonical base shape for an unowned one (i.e. identity). */ + inline UnownedBaseShape* toUnowned(); + + /* Check that an owned base shape is consistent with its unowned base. */ + void assertConsistency(); + + /* For JIT usage */ + static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); } + + static const JS::TraceKind TraceKind = JS::TraceKind::BaseShape; + + void traceChildren(JSTracer* trc); + void traceChildrenSkipShapeCache(JSTracer* trc); + +#ifdef DEBUG + bool canSkipMarkingShapeCache(Shape* lastShape); +#endif + + private: + static void staticAsserts() { + static_assert(offsetOfHeaderPtr() == + offsetof(JS::shadow::BaseShape, clasp_)); + static_assert(sizeof(BaseShape) % gc::CellAlignBytes == 0, + "Things inheriting from gc::Cell must have a size that's " + "a multiple of gc::CellAlignBytes"); + } + + void traceShapeCache(JSTracer* trc); +}; + +class UnownedBaseShape : public BaseShape {}; + +UnownedBaseShape* BaseShape::unowned() { + return isOwned() ? baseUnowned() : toUnowned(); +} + +UnownedBaseShape* BaseShape::toUnowned() { + MOZ_ASSERT(!isOwned() && !unowned_); + return static_cast<UnownedBaseShape*>(this); +} + +UnownedBaseShape* BaseShape::baseUnowned() { + MOZ_ASSERT(isOwned() && unowned_); + return unowned_; +} + +/* Entries for the per-zone baseShapes set of unowned base shapes. */ +struct StackBaseShape : public DefaultHasher<WeakHeapPtr<UnownedBaseShape*>> { + uint32_t flags; + const JSClass* clasp; + + explicit StackBaseShape(BaseShape* base) + : flags(base->flags & BaseShape::OBJECT_FLAG_MASK), + clasp(base->clasp()) {} + + inline StackBaseShape(const JSClass* clasp, uint32_t objectFlags); + explicit inline StackBaseShape(Shape* shape); + + struct Lookup { + uint32_t flags; + const JSClass* clasp; + + MOZ_IMPLICIT Lookup(const StackBaseShape& base) + : flags(base.flags), clasp(base.clasp) {} + + MOZ_IMPLICIT Lookup(UnownedBaseShape* base) + : flags(base->getObjectFlags()), clasp(base->clasp()) { + MOZ_ASSERT(!base->isOwned()); + } + + explicit Lookup(const WeakHeapPtr<UnownedBaseShape*>& base) + : flags(base.unbarrieredGet()->getObjectFlags()), + clasp(base.unbarrieredGet()->clasp()) { + MOZ_ASSERT(!base.unbarrieredGet()->isOwned()); + } + }; + + static HashNumber hash(const Lookup& lookup) { + return mozilla::HashGeneric(lookup.flags, lookup.clasp); + } + static inline bool match(const WeakHeapPtr<UnownedBaseShape*>& key, + const Lookup& lookup) { + return key.unbarrieredGet()->flags == lookup.flags && + key.unbarrieredGet()->clasp() == lookup.clasp; + } +}; + +static MOZ_ALWAYS_INLINE js::HashNumber HashId(jsid id) { + // HashGeneric alone would work, but bits of atom and symbol addresses + // could then be recovered from the hash code. See bug 1330769. + if (MOZ_LIKELY(JSID_IS_ATOM(id))) { + return JSID_TO_ATOM(id)->hash(); + } + if (JSID_IS_SYMBOL(id)) { + return JSID_TO_SYMBOL(id)->hash(); + } + return mozilla::HashGeneric(JSID_BITS(id)); +} + +} // namespace js + +namespace mozilla { + +template <> +struct DefaultHasher<jsid> { + using Lookup = jsid; + static HashNumber hash(jsid id) { return js::HashId(id); } + static bool match(jsid id1, jsid id2) { return id1 == id2; } +}; + +} // namespace mozilla + +namespace js { + +using BaseShapeSet = + JS::WeakCache<JS::GCHashSet<WeakHeapPtr<UnownedBaseShape*>, StackBaseShape, + SystemAllocPolicy>>; + +class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> { + friend class ::JSObject; + friend class ::JSFunction; + friend class GCMarker; + friend class NativeObject; + friend class PropertyTree; + friend class TenuringTracer; + friend struct StackBaseShape; + friend struct StackShape; + friend class JS::ubi::Concrete<Shape>; + friend class js::gc::RelocationOverlay; + + public: + // Base shape, stored in the cell header. + BaseShape* base() const { return headerPtr(); } + + protected: + const GCPtr<JS::PropertyKey> propid_; + + // 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 { + // Mask to get the index in object slots for isDataProperty() shapes. + // For other shapes in the property tree with a parent, stores the + // parent's slot index (which may be invalid), and invalid for all + // other shapes. + SLOT_MASK = BitMask(24), + + // 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 = 24, + FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT), + + // Property stored in per-object dictionary, not shared property tree. + IN_DICTIONARY = 1 << 29, + + // This shape is an AccessorShape, a fat Shape that can store + // getter/setter information. + ACCESSOR_SHAPE = 1 << 30, + }; + + // Flags stored in mutableFlags. + enum MutableFlags : uint8_t { + // numLinearSearches starts at zero and is incremented initially on + // search() calls. Once numLinearSearches reaches LINEAR_SEARCHES_MAX, + // the inline cache is created on the next search() call. Once the + // cache is full, it self transforms into a hash table. The hash table + // can also be created directly when hashifying for dictionary mode. + LINEAR_SEARCHES_MAX = 0x5, + LINEAR_SEARCHES_MASK = 0x7, + + // Flags used to speed up isBigEnoughForAShapeTable(). + HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x08, + CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE = 0x10, + }; + + uint32_t immutableFlags; /* immutable flags, see above */ + uint8_t attrs; /* attributes, see jsapi.h JSPROP_* */ + uint8_t mutableFlags; /* mutable flags, see below for defines */ + + GCPtrShape parent; /* parent node, reverse for..in order */ + friend class DictionaryShapeLink; + + union { + // Valid when !inDictionary(). + ShapeChildren children; + + // Valid when inDictionary(). + DictionaryShapeLink dictNext; + }; + + void setNextDictionaryShape(Shape* shape); + void setDictionaryObject(JSObject* obj); + void setDictionaryNextPtr(DictionaryShapeLink next); + void clearDictionaryNextPtr(); + void dictNextPreWriteBarrier(); + + template <MaybeAdding Adding = MaybeAdding::NotAdding> + static MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, Shape* start, jsid id); + + template <MaybeAdding Adding = MaybeAdding::NotAdding> + static inline MOZ_MUST_USE bool search(JSContext* cx, Shape* start, jsid id, + const AutoKeepShapeCaches&, + Shape** pshape, ShapeTable** ptable, + ShapeTable::Entry** pentry); + + static inline Shape* searchNoHashify(Shape* start, jsid id); + + void removeFromDictionary(NativeObject* obj); + void insertIntoDictionaryBefore(DictionaryShapeLink next); + + inline void initDictionaryShape(const StackShape& child, uint32_t nfixed, + DictionaryShapeLink next); + + // Replace the base shape of the last shape in a non-dictionary lineage with + // base. + static Shape* replaceLastProperty(JSContext* cx, StackBaseShape& base, + TaggedProto proto, HandleShape shape); + + /* + * This function is thread safe if every shape in the lineage of |shape| + * is thread local, which is the case when we clone the entire shape + * lineage in preparation for converting an object to dictionary mode. + */ + static bool hashify(JSContext* cx, Shape* shape); + static bool cachify(JSContext* cx, Shape* shape); + void handoffTableTo(Shape* newShape); + + void setParent(Shape* p) { + MOZ_ASSERT_IF(p && !p->hasMissingSlot() && !inDictionary(), + p->maybeSlot() <= maybeSlot()); + MOZ_ASSERT_IF(p && !inDictionary(), + isDataProperty() == (p->maybeSlot() != maybeSlot())); + parent = p; + } + + bool ensureOwnBaseShape(JSContext* cx) { + if (base()->isOwned()) { + return true; + } + return makeOwnBaseShape(cx); + } + + bool makeOwnBaseShape(JSContext* cx); + + MOZ_ALWAYS_INLINE MOZ_MUST_USE bool maybeCreateCacheForLookup(JSContext* cx); + + MOZ_ALWAYS_INLINE void updateDictionaryTable(ShapeTable* table, + ShapeTable::Entry* entry, + const AutoKeepShapeCaches& keep); + + public: + bool hasTable() const { return base()->hasTable(); } + bool hasIC() const { return base()->hasIC(); } + + ShapeIC* maybeIC(const AutoKeepShapeCaches& keep) const { + return base()->maybeIC(keep); + } + ShapeIC* maybeIC(const JS::AutoCheckCannotGC& check) const { + return base()->maybeIC(check); + } + ShapeTable* maybeTable(const AutoKeepShapeCaches& keep) const { + return base()->maybeTable(keep); + } + ShapeTable* maybeTable(const JS::AutoCheckCannotGC& check) const { + return base()->maybeTable(check); + } + ShapeCachePtr getCache(const AutoKeepShapeCaches& keep) const { + return base()->getCache(keep); + } + ShapeCachePtr getCache(const JS::AutoCheckCannotGC& check) const { + return base()->getCache(check); + } + + bool appendShapeToIC(jsid id, Shape* shape, + const JS::AutoCheckCannotGC& check) { + MOZ_ASSERT(hasIC()); + ShapeCachePtr cache = getCache(check); + return cache.getICPointer()->appendEntry(id, shape); + } + + template <typename T> + MOZ_MUST_USE ShapeTable* ensureTableForDictionary(JSContext* cx, + const T& nogc) { + MOZ_ASSERT(inDictionary()); + if (ShapeTable* table = maybeTable(nogc)) { + return table; + } + if (!hashify(cx, this)) { + return nullptr; + } + ShapeTable* table = maybeTable(nogc); + MOZ_ASSERT(table); + return table; + } + + void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, + JS::ShapeInfo* info) const { + JS::AutoCheckCannotGC nogc; + if (inDictionary()) { + info->shapesMallocHeapDictTables += + getCache(nogc).sizeOfExcludingThis(mallocSizeOf); + } else { + info->shapesMallocHeapTreeTables += + getCache(nogc).sizeOfExcludingThis(mallocSizeOf); + } + + if (!inDictionary() && children.isShapeSet()) { + info->shapesMallocHeapTreeChildren += + children.toShapeSet()->shallowSizeOfIncludingThis(mallocSizeOf); + } + } + + bool isAccessorShape() const { + MOZ_ASSERT_IF(immutableFlags & ACCESSOR_SHAPE, + getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE); + return immutableFlags & ACCESSOR_SHAPE; + } + AccessorShape& asAccessorShape() const { + MOZ_ASSERT(isAccessorShape()); + return *(AccessorShape*)this; + } + + const GCPtrShape& previous() const { return parent; } + + template <AllowGC allowGC> + class Range { + protected: + friend class Shape; + + typename MaybeRooted<Shape*, allowGC>::RootType cursor; + + public: + Range(JSContext* cx, Shape* shape) : cursor(cx, shape) { + static_assert(allowGC == CanGC); + } + + explicit Range(Shape* shape) : cursor((JSContext*)nullptr, shape) { + static_assert(allowGC == NoGC); + } + + bool empty() const { return !cursor || cursor->isEmptyShape(); } + + Shape& front() const { + MOZ_ASSERT(!empty()); + return *cursor; + } + + void popFront() { + MOZ_ASSERT(!empty()); + cursor = cursor->parent; + } + }; + + const JSClass* getObjectClass() const { return base()->clasp(); } + + static Shape* setObjectFlags(JSContext* cx, BaseShape::Flag flag, + TaggedProto proto, Shape* last); + + uint32_t getObjectFlags() const { return base()->getObjectFlags(); } + bool hasAllObjectFlags(BaseShape::Flag flags) const { + MOZ_ASSERT(flags); + MOZ_ASSERT(!(flags & ~BaseShape::OBJECT_FLAG_MASK)); + return (base()->flags & flags) == flags; + } + + protected: + /* Get a shape identical to this one, without parent/children information. */ + inline Shape(const StackShape& other, uint32_t nfixed); + + /* Used by EmptyShape (see jsscopeinlines.h). */ + inline Shape(UnownedBaseShape* base, uint32_t nfixed); + + /* Copy constructor disabled, to avoid misuse of the above form. */ + Shape(const Shape& other) = delete; + + /* Allocate a new shape based on the given StackShape. */ + static inline Shape* new_(JSContext* cx, Handle<StackShape> other, + uint32_t nfixed); + + /* + * Whether this shape has a valid slot value. This may be true even if + * !isDataProperty() (see SlotInfo comment above), and may be false even if + * isDataProperty() if the shape is being constructed and has not had a slot + * assigned yet. After construction, isDataProperty() implies + * !hasMissingSlot(). + */ + bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; } + + public: + bool inDictionary() const { return immutableFlags & IN_DICTIONARY; } + + inline GetterOp getter() const; + bool hasDefaultGetter() const { return !getter(); } + GetterOp getterOp() const { + MOZ_ASSERT(!hasGetterValue()); + return getter(); + } + inline JSObject* getterObject() const; + bool hasGetterObject() const { return hasGetterValue() && getterObject(); } + + // Per ES5, decode null getterObj as the undefined value, which encodes as + // null. + Value getterValue() const { + MOZ_ASSERT(hasGetterValue()); + if (JSObject* getterObj = getterObject()) { + return ObjectValue(*getterObj); + } + return UndefinedValue(); + } + + Value getterOrUndefined() const { + return hasGetterValue() ? getterValue() : UndefinedValue(); + } + + inline SetterOp setter() const; + bool hasDefaultSetter() const { return !setter(); } + SetterOp setterOp() const { + MOZ_ASSERT(!hasSetterValue()); + return setter(); + } + inline JSObject* setterObject() const; + bool hasSetterObject() const { return hasSetterValue() && setterObject(); } + + // Per ES5, decode null setterObj as the undefined value, which encodes as + // null. + Value setterValue() const { + MOZ_ASSERT(hasSetterValue()); + if (JSObject* setterObj = setterObject()) { + return ObjectValue(*setterObj); + } + return UndefinedValue(); + } + + Value setterOrUndefined() const { + return hasSetterValue() ? setterValue() : UndefinedValue(); + } + + bool matches(const Shape* other) const { + return propid_.get() == other->propid_.get() && + matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, + other->getter(), other->setter()); + } + + inline bool matches(const StackShape& other) const; + + bool matchesParamsAfterId(BaseShape* base, uint32_t aslot, unsigned aattrs, + GetterOp rawGetter, SetterOp rawSetter) const { + return base->unowned() == this->base()->unowned() && maybeSlot() == aslot && + attrs == aattrs && getter() == rawGetter && setter() == rawSetter; + } + + static bool isDataProperty(unsigned attrs, GetterOp getter, SetterOp setter) { + return !(attrs & (JSPROP_GETTER | JSPROP_SETTER)) && !getter && !setter; + } + + bool isDataProperty() const { + MOZ_ASSERT(!isEmptyShape()); + return isDataProperty(attrs, getter(), setter()); + } + uint32_t slot() const { + MOZ_ASSERT(isDataProperty() && !hasMissingSlot()); + return maybeSlot(); + } + uint32_t maybeSlot() const { return immutableFlags & SLOT_MASK; } + + bool isEmptyShape() const { + MOZ_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot()); + return JSID_IS_EMPTY(propid_); + } + + uint32_t slotSpan(const JSClass* clasp) const { + MOZ_ASSERT(!inDictionary()); + // Proxy classes have reserved slots, but proxies manage their own slot + // layout. This means all non-native object shapes have nfixed == 0 and + // slotSpan == 0. + uint32_t free = clasp->isProxy() ? 0 : JSSLOT_FREE(clasp); + return hasMissingSlot() ? free : std::max(free, maybeSlot() + 1); + } + + uint32_t slotSpan() const { return slotSpan(getObjectClass()); } + + void setSlot(uint32_t slot) { + MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); + immutableFlags = (immutableFlags & ~Shape::SLOT_MASK) | slot; + } + + uint32_t numFixedSlots() const { + return (immutableFlags & FIXED_SLOTS_MASK) >> FIXED_SLOTS_SHIFT; + } + + void setNumFixedSlots(uint32_t nfixed) { + MOZ_ASSERT(nfixed < FIXED_SLOTS_MAX); + immutableFlags = immutableFlags & ~FIXED_SLOTS_MASK; + immutableFlags = immutableFlags | (nfixed << FIXED_SLOTS_SHIFT); + } + + uint32_t numLinearSearches() const { + return mutableFlags & LINEAR_SEARCHES_MASK; + } + + void incrementNumLinearSearches() { + uint32_t count = numLinearSearches(); + MOZ_ASSERT(count < LINEAR_SEARCHES_MAX); + mutableFlags = (mutableFlags & ~LINEAR_SEARCHES_MASK) | (count + 1); + } + + const GCPtrId& propid() const { + MOZ_ASSERT(!isEmptyShape()); + MOZ_ASSERT(!JSID_IS_VOID(propid_)); + return propid_; + } + const GCPtrId& propidRef() { + MOZ_ASSERT(!JSID_IS_VOID(propid_)); + return propid_; + } + jsid propidRaw() const { + // Return the actual jsid, not an internal reference. + return propid(); + } + + uint8_t attributes() const { return attrs; } + bool configurable() const { return (attrs & JSPROP_PERMANENT) == 0; } + bool enumerable() const { return (attrs & JSPROP_ENUMERATE) != 0; } + bool writable() const { return (attrs & JSPROP_READONLY) == 0; } + bool hasGetterValue() const { return attrs & JSPROP_GETTER; } + bool hasSetterValue() const { return attrs & JSPROP_SETTER; } + + bool isDataDescriptor() const { + return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) == 0; + } + bool isAccessorDescriptor() const { + return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) != 0; + } + + uint32_t entryCount() { + JS::AutoCheckCannotGC nogc; + if (ShapeTable* table = maybeTable(nogc)) { + return table->entryCount(); + } + uint32_t count = 0; + for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) { + ++count; + } + return count; + } + + private: + void setBase(BaseShape* base) { + MOZ_ASSERT(base); + setHeaderPtr(base); + } + + bool isBigEnoughForAShapeTableSlow() { + uint32_t count = 0; + for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) { + ++count; + if (count >= ShapeCachePtr::MIN_ENTRIES) { + return true; + } + } + return false; + } + void clearCachedBigEnoughForShapeTable() { + mutableFlags &= ~(HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE | + CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE); + } + + public: + bool isBigEnoughForAShapeTable() { + MOZ_ASSERT(!hasTable()); + + // isBigEnoughForAShapeTableSlow is pretty inefficient so we only call + // it once and cache the result. + + if (mutableFlags & HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE) { + bool res = mutableFlags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE; + MOZ_ASSERT(res == isBigEnoughForAShapeTableSlow()); + return res; + } + + MOZ_ASSERT(!(mutableFlags & CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE)); + + bool res = isBigEnoughForAShapeTableSlow(); + if (res) { + mutableFlags |= CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE; + } + mutableFlags |= HAS_CACHED_BIG_ENOUGH_FOR_SHAPE_TABLE; + return res; + } + +#ifdef DEBUG + void dump(js::GenericPrinter& out) const; + void dump() const; + void dumpSubtree(int level, js::GenericPrinter& out) const; +#endif + + void sweep(JSFreeOp* fop); + void finalize(JSFreeOp* fop); + void removeChild(JSFreeOp* fop, Shape* child); + + static const JS::TraceKind TraceKind = JS::TraceKind::Shape; + + void traceChildren(JSTracer* trc); + + MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, jsid id); + MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id); + + void fixupAfterMovingGC(); + void fixupGetterSetterForBarrier(JSTracer* trc); + void updateBaseShapeAfterMovingGC(); + + // For JIT usage. + static constexpr size_t offsetOfBaseShape() { return offsetOfHeaderPtr(); } + +#ifdef DEBUG + static inline size_t offsetOfImmutableFlags() { + return offsetof(Shape, immutableFlags); + } + static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; } +#endif + + private: + void fixupDictionaryShapeAfterMovingGC(); + void fixupShapeTreeAfterMovingGC(); + + static void staticAsserts() { + static_assert(offsetOfBaseShape() == offsetof(JS::shadow::Shape, base)); + static_assert(offsetof(Shape, immutableFlags) == + offsetof(JS::shadow::Shape, immutableFlags)); + static_assert(FIXED_SLOTS_SHIFT == JS::shadow::Shape::FIXED_SLOTS_SHIFT); + static_assert(FIXED_SLOTS_MASK == JS::shadow::Shape::FIXED_SLOTS_MASK); + } +}; + +/* Fat Shape used for accessor properties. */ +class AccessorShape : public Shape { + friend class Shape; + friend class NativeObject; + + union { + GetterOp rawGetter; /* getter hook for shape */ + JSObject* getterObj; /* user-defined callable "get" object or + null if shape->hasGetterValue() */ + }; + union { + SetterOp rawSetter; /* setter hook for shape */ + JSObject* setterObj; /* user-defined callable "set" object or + null if shape->hasSetterValue() */ + }; + + public: + /* Get a shape identical to this one, without parent/children information. */ + inline AccessorShape(const StackShape& other, uint32_t nfixed); +}; + +inline StackBaseShape::StackBaseShape(Shape* shape) + : flags(shape->getObjectFlags()), clasp(shape->getObjectClass()) {} + +class MOZ_RAII AutoRooterGetterSetter { + class Inner { + public: + inline Inner(uint8_t attrs, GetterOp* pgetter_, SetterOp* psetter_); + + void trace(JSTracer* trc); + + private: + uint8_t attrs; + GetterOp* pgetter; + SetterOp* psetter; + }; + + public: + inline AutoRooterGetterSetter(JSContext* cx, uint8_t attrs, GetterOp* pgetter, + SetterOp* psetter); + + private: + mozilla::Maybe<Rooted<Inner>> inner; +}; + +struct EmptyShape : public js::Shape { + EmptyShape(UnownedBaseShape* base, uint32_t nfixed) + : js::Shape(base, nfixed) {} + + static Shape* new_(JSContext* cx, Handle<UnownedBaseShape*> base, + uint32_t nfixed); + + /* + * Lookup an initial shape matching the given parameters, creating an empty + * shape if none was found. + */ + static Shape* getInitialShape(JSContext* cx, const JSClass* clasp, + TaggedProto proto, size_t nfixed, + uint32_t objectFlags = 0); + static Shape* getInitialShape(JSContext* cx, const JSClass* clasp, + TaggedProto proto, gc::AllocKind kind, + uint32_t objectFlags = 0); + + /* + * 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, HandleShape shape, + HandleObject proto); + + /* + * 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); +}; + +/* + * Entries for the per-zone initialShapes set indexing initial shapes for + * objects in the zone and the associated types. + */ +struct InitialShapeEntry { + /* + * Initial shape to give to the object. This is an empty shape, except for + * certain classes (e.g. String, RegExp) which may add certain baked-in + * properties. + */ + WeakHeapPtr<Shape*> shape; + + /* + * Matching prototype for the entry. The shape of an object determines its + * prototype, but the prototype cannot be determined from the shape itself. + */ + WeakHeapPtr<TaggedProto> proto; + + /* State used to determine a match on an initial shape. */ + struct Lookup { + const JSClass* clasp; + TaggedProto proto; + uint32_t nfixed; + uint32_t baseFlags; + + Lookup(const JSClass* clasp, const TaggedProto& proto, uint32_t nfixed, + uint32_t baseFlags) + : clasp(clasp), proto(proto), nfixed(nfixed), baseFlags(baseFlags) {} + }; + + inline InitialShapeEntry(); + inline InitialShapeEntry(Shape* shape, const TaggedProto& proto); + + static HashNumber hash(const Lookup& lookup) { + HashNumber hash = MovableCellHasher<TaggedProto>::hash(lookup.proto); + return mozilla::AddToHash( + hash, mozilla::HashGeneric(lookup.clasp, lookup.nfixed)); + } + static inline bool match(const InitialShapeEntry& key, const Lookup& lookup) { + const Shape* shape = key.shape.unbarrieredGet(); + return lookup.clasp == shape->getObjectClass() && + lookup.nfixed == shape->numFixedSlots() && + lookup.baseFlags == shape->getObjectFlags() && + key.proto.unbarrieredGet() == lookup.proto; + } + static void rekey(InitialShapeEntry& k, const InitialShapeEntry& newKey) { + k = newKey; + } + + bool needsSweep() { + Shape* ushape = shape.unbarrieredGet(); + TaggedProto uproto = proto.unbarrieredGet(); + JSObject* protoObj = uproto.raw(); + return ( + gc::IsAboutToBeFinalizedUnbarriered(&ushape) || + (uproto.isObject() && gc::IsAboutToBeFinalizedUnbarriered(&protoObj))); + } + + bool operator==(const InitialShapeEntry& other) const { + return shape == other.shape && proto == other.proto; + } +}; + +using InitialShapeSet = JS::WeakCache< + JS::GCHashSet<InitialShapeEntry, InitialShapeEntry, SystemAllocPolicy>>; + +struct StackShape { + /* For performance, StackShape only roots when absolutely necessary. */ + UnownedBaseShape* base; + jsid propid; + GetterOp rawGetter; + SetterOp rawSetter; + uint32_t immutableFlags; + uint8_t attrs; + uint8_t mutableFlags; + + explicit StackShape(UnownedBaseShape* base, jsid propid, uint32_t slot, + unsigned attrs) + : base(base), + propid(propid), + rawGetter(nullptr), + rawSetter(nullptr), + immutableFlags(slot), + attrs(uint8_t(attrs)), + mutableFlags(0) { + MOZ_ASSERT(base); + MOZ_ASSERT(!JSID_IS_VOID(propid)); + MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); + } + + explicit StackShape(Shape* shape) + : base(shape->base()->unowned()), + propid(shape->propidRef()), + rawGetter(shape->getter()), + rawSetter(shape->setter()), + immutableFlags(shape->immutableFlags), + attrs(shape->attrs), + mutableFlags(shape->mutableFlags) {} + + void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) { + if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER | JSPROP_SETTER))) { + immutableFlags |= Shape::ACCESSOR_SHAPE; + } else { + immutableFlags &= ~Shape::ACCESSOR_SHAPE; + } + + this->rawGetter = rawGetter; + this->rawSetter = rawSetter; + } + + bool isDataProperty() const { + MOZ_ASSERT(!JSID_IS_EMPTY(propid)); + return Shape::isDataProperty(attrs, rawGetter, rawSetter); + } + bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; } + + uint32_t slot() const { + MOZ_ASSERT(isDataProperty() && !hasMissingSlot()); + return maybeSlot(); + } + uint32_t maybeSlot() const { return immutableFlags & Shape::SLOT_MASK; } + + void setSlot(uint32_t slot) { + MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); + immutableFlags = (immutableFlags & ~Shape::SLOT_MASK) | slot; + } + + bool isAccessorShape() const { + return immutableFlags & Shape::ACCESSOR_SHAPE; + } + + HashNumber hash() const { + HashNumber hash = HashId(propid); + return mozilla::AddToHash( + hash, + mozilla::HashGeneric(base, attrs, maybeSlot(), rawGetter, rawSetter)); + } + + // StructGCPolicy implementation. + void trace(JSTracer* trc); +}; + +template <typename Wrapper> +class WrappedPtrOperations<StackShape, Wrapper> { + const StackShape& ss() const { + return static_cast<const Wrapper*>(this)->get(); + } + + public: + bool isDataProperty() const { return ss().isDataProperty(); } + bool hasMissingSlot() const { return ss().hasMissingSlot(); } + uint32_t slot() const { return ss().slot(); } + uint32_t maybeSlot() const { return ss().maybeSlot(); } + uint32_t slotSpan() const { return ss().slotSpan(); } + bool isAccessorShape() const { return ss().isAccessorShape(); } + uint8_t attrs() const { return ss().attrs; } +}; + +template <typename Wrapper> +class MutableWrappedPtrOperations<StackShape, Wrapper> + : public WrappedPtrOperations<StackShape, Wrapper> { + StackShape& ss() { return static_cast<Wrapper*>(this)->get(); } + + public: + void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) { + ss().updateGetterSetter(rawGetter, rawSetter); + } + void setSlot(uint32_t slot) { ss().setSlot(slot); } + void setBase(UnownedBaseShape* base) { ss().base = base; } + void setAttrs(uint8_t attrs) { ss().attrs = attrs; } +}; + +inline Shape::Shape(const StackShape& other, uint32_t nfixed) + : CellWithTenuredGCPointer(other.base), + propid_(other.propid), + immutableFlags(other.immutableFlags), + attrs(other.attrs), + mutableFlags(other.mutableFlags), + parent(nullptr) { + setNumFixedSlots(nfixed); + +#ifdef DEBUG + gc::AllocKind allocKind = getAllocKind(); + MOZ_ASSERT_IF(other.isAccessorShape(), + allocKind == gc::AllocKind::ACCESSOR_SHAPE); + MOZ_ASSERT_IF(allocKind == gc::AllocKind::SHAPE, !other.isAccessorShape()); +#endif + + MOZ_ASSERT_IF(!isEmptyShape(), AtomIsMarked(zone(), propid())); + + children.setNone(); +} + +// This class is used to update any shapes in a zone that have nursery objects +// as getters/setters. It updates the pointers and the shapes' entries in the +// parents' ShapeSet tables. +class NurseryShapesRef : public gc::BufferableRef { + Zone* zone_; + + public: + explicit NurseryShapesRef(Zone* zone) : zone_(zone) {} + void trace(JSTracer* trc) override; +}; + +inline Shape::Shape(UnownedBaseShape* base, uint32_t nfixed) + : CellWithTenuredGCPointer(base), + propid_(JSID_EMPTY), + immutableFlags(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)), + attrs(0), + mutableFlags(0), + parent(nullptr) { + MOZ_ASSERT(base); + children.setNone(); +} + +inline GetterOp Shape::getter() const { + return isAccessorShape() ? asAccessorShape().rawGetter : nullptr; +} + +inline SetterOp Shape::setter() const { + return isAccessorShape() ? asAccessorShape().rawSetter : nullptr; +} + +inline JSObject* Shape::getterObject() const { + MOZ_ASSERT(hasGetterValue()); + return asAccessorShape().getterObj; +} + +inline JSObject* Shape::setterObject() const { + MOZ_ASSERT(hasSetterValue()); + return asAccessorShape().setterObj; +} + +inline Shape* Shape::searchLinear(jsid id) { + for (Shape* shape = this; shape;) { + if (shape->propidRef() == id) { + return shape; + } + shape = shape->parent; + } + + return nullptr; +} + +inline bool Shape::matches(const StackShape& other) const { + return propid_.get() == other.propid && + matchesParamsAfterId(other.base, other.maybeSlot(), other.attrs, + other.rawGetter, other.rawSetter); +} + +template <MaybeAdding Adding> +MOZ_ALWAYS_INLINE bool ShapeCachePtr::search(jsid id, Shape* start, + Shape** foundShape) { + bool found = false; + if (isIC()) { + ShapeIC* ic = getICPointer(); + found = ic->search(id, foundShape); + } else if (isTable()) { + ShapeTable* table = getTablePointer(); + ShapeTable::Entry& entry = table->searchUnchecked<Adding>(id); + *foundShape = entry.shape(); + found = true; + } + return found; +} + +MOZ_ALWAYS_INLINE bool ShapeIC::search(jsid id, Shape** foundShape) { + // This loop needs to be as fast as possible, so use a direct pointer + // to the array instead of going through the UniquePtr methods. + Entry* entriesArray = entries_.get(); + for (uint8_t i = 0; i < nextFreeIndex_; i++) { + Entry& entry = entriesArray[i]; + if (entry.id_ == id) { + *foundShape = entry.shape_; + return true; + } + } + + return 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 */ |