| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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 "mozilla/Attributes.h" |
| #include "mozilla/GuardObjects.h" |
| #include "mozilla/MathAlgorithms.h" |
| #include "mozilla/Maybe.h" |
| #include "mozilla/MemoryReporting.h" |
| #include "mozilla/TemplateLib.h" |
| |
| #include "jsapi.h" |
| #include "jsfriendapi.h" |
| #include "jspropertytree.h" |
| #include "jstypes.h" |
| #include "NamespaceImports.h" |
| |
| #include "gc/Barrier.h" |
| #include "gc/Heap.h" |
| #include "gc/Marking.h" |
| #include "gc/Rooting.h" |
| #include "js/HashTable.h" |
| #include "js/MemoryMetrics.h" |
| #include "js/RootingAPI.h" |
| #include "js/UbiNode.h" |
| #include "vm/ObjectGroup.h" |
| |
| #ifdef _MSC_VER |
| #pragma warning(push) |
| #pragma warning(disable:4800) |
| #pragma warning(push) |
| #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */ |
| #endif |
| |
| /* |
| * 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 |
| * |kids|/|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. |
| * |
| * 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. |
| */ |
| |
| #define JSSLOT_FREE(clasp) JSCLASS_RESERVED_SLOTS(clasp) |
| |
| namespace js { |
| |
| class Bindings; |
| class StaticBlockObject; |
| class TenuringTracer; |
| |
| typedef JSGetterOp GetterOp; |
| typedef JSSetterOp SetterOp; |
| typedef JSPropertyDescriptor PropertyDescriptor; |
| |
| /* Limit on the number of slotful properties in an object. */ |
| static const uint32_t SHAPE_INVALID_SLOT = JS_BIT(24) - 1; |
| static const uint32_t SHAPE_MAXIMUM_SLOT = JS_BIT(24) - 2; |
| |
| /* |
| * Shapes use multiplicative hashing, but specialized to |
| * minimize footprint. |
| */ |
| class ShapeTable { |
| public: |
| friend class NativeObject; |
| static const uint32_t MIN_ENTRIES = 11; |
| |
| 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 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 = JS_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 */ |
| |
| Entry* entries_; /* table of ptrs to shared tree nodes */ |
| |
| 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() { |
| js_free(entries_); |
| } |
| |
| 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_); |
| } |
| |
| /* |
| * NB: init and change are fallible but do not report OOM, so callers can |
| * cope or ignore. They do however use the context's calloc method in |
| * order to update the malloc counter on success. |
| */ |
| bool init(ExclusiveContext* cx, Shape* lastProp); |
| bool change(int log2Delta, ExclusiveContext* cx); |
| template <bool adding = false> |
| Entry& search(jsid id); |
| |
| 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 JS_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(ExclusiveContext* cx); |
| }; |
| |
| /* |
| * Use the reserved attribute bit to mean shadowability. |
| */ |
| #define JSPROP_SHADOWABLE JSPROP_INTERNAL_USE_BIT |
| |
| /* |
| * 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 compartment 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::TenuredCell |
| { |
| public: |
| friend class Shape; |
| friend struct StackBaseShape; |
| friend struct StackShape; |
| friend void gc::MergeCompartments(JSCompartment* source, JSCompartment* target); |
| |
| 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, |
| BOUND_FUNCTION = 0x40, |
| HAD_ELEMENTS_ACCESS = 0x80, |
| WATCHED = 0x100, |
| ITERATED_SINGLETON = 0x200, |
| NEW_GROUP_UNKNOWN = 0x400, |
| UNCACHEABLE_PROTO = 0x800, |
| IMMUTABLE_PROTOTYPE = 0x1000, |
| |
| // See JSObject::isQualifiedVarObj(). |
| QUALIFIED_VAROBJ = 0x2000, |
| |
| // 0x4000 is unused. |
| |
| // For a function used as an interpreted constructor, whether a 'new' |
| // type had constructor information cleared. |
| NEW_SCRIPT_CLEARED = 0x8000, |
| |
| OBJECT_FLAG_MASK = 0xfff8 |
| }; |
| |
| private: |
| const Class* clasp_; /* Class of referring object. */ |
| JSCompartment* compartment_; /* Compartment shape belongs to. */ |
| uint32_t flags; /* Vector of above flags. */ |
| uint32_t slotSpan_; /* Object slot span for BaseShapes at |
| * dictionary last properties. */ |
| |
| /* For owned BaseShapes, the canonical unowned BaseShape. */ |
| HeapPtrUnownedBaseShape unowned_; |
| |
| /* For owned BaseShapes, the shape's shape table. */ |
| ShapeTable* table_; |
| |
| BaseShape(const BaseShape& base) = delete; |
| BaseShape& operator=(const BaseShape& other) = delete; |
| |
| public: |
| void finalize(FreeOp* fop); |
| |
| BaseShape(JSCompartment* comp, const Class* clasp, uint32_t objectFlags) |
| { |
| MOZ_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK)); |
| mozilla::PodZero(this); |
| this->clasp_ = clasp; |
| this->flags = objectFlags; |
| this->compartment_ = comp; |
| } |
| |
| explicit inline BaseShape(const StackBaseShape& base); |
| |
| /* Not defined: BaseShapes must not be stack allocated. */ |
| ~BaseShape(); |
| |
| const Class* clasp() const { return clasp_; } |
| |
| 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(table_, isOwned()); return table_ != nullptr; } |
| ShapeTable& table() const { MOZ_ASSERT(table_ && isOwned()); return *table_; } |
| void setTable(ShapeTable* table) { MOZ_ASSERT(isOwned()); table_ = table; } |
| |
| uint32_t slotSpan() const { MOZ_ASSERT(isOwned()); return slotSpan_; } |
| void setSlotSpan(uint32_t slotSpan) { MOZ_ASSERT(isOwned()); slotSpan_ = slotSpan; } |
| |
| JSCompartment* compartment() const { return compartment_; } |
| JSCompartment* maybeCompartment() const { return compartment(); } |
| |
| /* |
| * Lookup base shapes from the compartment's baseShapes table, adding if |
| * not already found. |
| */ |
| static UnownedBaseShape* getUnowned(ExclusiveContext* 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 inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; } |
| |
| void traceChildren(JSTracer* trc); |
| |
| void fixupAfterMovingGC() {} |
| |
| private: |
| static void staticAsserts() { |
| JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) == offsetof(js::shadow::BaseShape, clasp_)); |
| static_assert(sizeof(BaseShape) % gc::CellSize == 0, |
| "Things inheriting from gc::Cell must have a size that's " |
| "a multiple of gc::CellSize"); |
| } |
| }; |
| |
| 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-compartment baseShapes set of unowned base shapes. */ |
| struct StackBaseShape : public DefaultHasher<ReadBarriered<UnownedBaseShape*>> |
| { |
| uint32_t flags; |
| const Class* clasp; |
| JSCompartment* compartment; |
| |
| explicit StackBaseShape(BaseShape* base) |
| : flags(base->flags & BaseShape::OBJECT_FLAG_MASK), |
| clasp(base->clasp_), |
| compartment(base->compartment()) |
| {} |
| |
| inline StackBaseShape(ExclusiveContext* cx, const Class* clasp, uint32_t objectFlags); |
| explicit inline StackBaseShape(Shape* shape); |
| |
| struct Lookup |
| { |
| uint32_t flags; |
| const Class* 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()); |
| } |
| }; |
| |
| static inline HashNumber hash(const Lookup& lookup); |
| static inline bool match(ReadBarriered<UnownedBaseShape*> key, const Lookup& lookup); |
| }; |
| |
| typedef HashSet<ReadBarriered<UnownedBaseShape*>, |
| StackBaseShape, |
| SystemAllocPolicy> BaseShapeSet; |
| |
| |
| class Shape : public gc::TenuredCell |
| { |
| friend class ::JSObject; |
| friend class ::JSFunction; |
| friend class Bindings; |
| friend class NativeObject; |
| friend class PropertyTree; |
| friend class StaticBlockObject; |
| friend class TenuringTracer; |
| friend struct StackBaseShape; |
| friend struct StackShape; |
| friend struct JS::ubi::Concrete<Shape>; |
| |
| protected: |
| HeapPtrBaseShape base_; |
| PreBarrieredId propid_; |
| |
| enum SlotInfo : uint32_t |
| { |
| /* 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 = 27, |
| FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT), |
| |
| /* |
| * numLinearSearches starts at zero and is incremented initially on |
| * search() calls. Once numLinearSearches reaches LINEAR_SEARCHES_MAX, |
| * the table is created on the next search() call. The table can also |
| * be created when hashifying for dictionary mode. |
| */ |
| LINEAR_SEARCHES_MAX = 0x7, |
| LINEAR_SEARCHES_SHIFT = 24, |
| LINEAR_SEARCHES_MASK = LINEAR_SEARCHES_MAX << LINEAR_SEARCHES_SHIFT, |
| |
| /* |
| * Mask to get the index in object slots for shapes which hasSlot(). |
| * For !hasSlot() 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 = JS_BIT(24) - 1 |
| }; |
| |
| uint32_t slotInfo; /* mask of above info */ |
| uint8_t attrs; /* attributes, see jsapi.h JSPROP_* */ |
| uint8_t flags; /* flags, see below for defines */ |
| |
| HeapPtrShape parent; /* parent node, reverse for..in order */ |
| /* kids is valid when !inDictionary(), listp is valid when inDictionary(). */ |
| union { |
| KidsPointer kids; /* null, single child, or a tagged ptr |
| to many-kids data structure */ |
| HeapPtrShape* listp; /* dictionary list starting at shape_ |
| has a double-indirect back pointer, |
| either to the next shape's parent if not |
| last, else to obj->shape_ */ |
| }; |
| |
| template <bool adding = false> |
| static inline Shape* search(ExclusiveContext* cx, Shape* start, jsid id, |
| ShapeTable::Entry** pentry); |
| static inline Shape* searchNoHashify(Shape* start, jsid id); |
| |
| void removeFromDictionary(NativeObject* obj); |
| void insertIntoDictionary(HeapPtrShape* dictp); |
| |
| inline void initDictionaryShape(const StackShape& child, uint32_t nfixed, HeapPtrShape* dictp); |
| |
| /* Replace the base shape of the last shape in a non-dictionary lineage with base. */ |
| static Shape* replaceLastProperty(ExclusiveContext* 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(ExclusiveContext* 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(), |
| hasSlot() == (p->maybeSlot() != maybeSlot())); |
| parent = p; |
| } |
| |
| bool ensureOwnBaseShape(ExclusiveContext* cx) { |
| if (base()->isOwned()) |
| return true; |
| return makeOwnBaseShape(cx); |
| } |
| |
| bool makeOwnBaseShape(ExclusiveContext* cx); |
| |
| public: |
| bool hasTable() const { return base()->hasTable(); } |
| ShapeTable& table() const { return base()->table(); } |
| |
| void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, |
| JS::ClassInfo* info) const |
| { |
| if (hasTable()) { |
| if (inDictionary()) |
| info->shapesMallocHeapDictTables += table().sizeOfIncludingThis(mallocSizeOf); |
| else |
| info->shapesMallocHeapTreeTables += table().sizeOfIncludingThis(mallocSizeOf); |
| } |
| |
| if (!inDictionary() && kids.isHash()) |
| info->shapesMallocHeapTreeKids += kids.toHash()->sizeOfIncludingThis(mallocSizeOf); |
| } |
| |
| bool isNative() const { |
| MOZ_ASSERT(!(flags & NON_NATIVE) == getObjectClass()->isNative()); |
| return !(flags & NON_NATIVE); |
| } |
| |
| bool isAccessorShape() const { |
| MOZ_ASSERT_IF(flags & ACCESSOR_SHAPE, getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE); |
| return flags & ACCESSOR_SHAPE; |
| } |
| AccessorShape& asAccessorShape() const { |
| MOZ_ASSERT(isAccessorShape()); |
| return *(AccessorShape*)this; |
| } |
| |
| const HeapPtrShape& previous() const { return parent; } |
| JSCompartment* compartment() const { return base()->compartment(); } |
| JSCompartment* maybeCompartment() const { return compartment(); } |
| |
| template <AllowGC allowGC> |
| class Range { |
| protected: |
| friend class Shape; |
| |
| typename MaybeRooted<Shape*, allowGC>::RootType cursor; |
| |
| public: |
| Range(ExclusiveContext* cx, Shape* shape) : cursor(cx, shape) { |
| JS_STATIC_ASSERT(allowGC == CanGC); |
| } |
| |
| explicit Range(Shape* shape) : cursor((ExclusiveContext*) nullptr, shape) { |
| JS_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 Class* getObjectClass() const { |
| return base()->clasp_; |
| } |
| |
| static Shape* setObjectFlags(ExclusiveContext* 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: |
| /* |
| * Implementation-private bits stored in shape->flags. See public: enum {} |
| * flags further below, which were allocated FCFS over time, so interleave |
| * with these bits. |
| */ |
| enum { |
| /* Property is placeholder for a non-native class. */ |
| NON_NATIVE = 0x01, |
| |
| /* Property stored in per-object dictionary, not shared property tree. */ |
| IN_DICTIONARY = 0x02, |
| |
| /* |
| * Slotful property was stored to more than once. This is used as a |
| * hint for type inference. |
| */ |
| OVERWRITTEN = 0x04, |
| |
| /* |
| * This shape is an AccessorShape, a fat Shape that can store |
| * getter/setter information. |
| */ |
| ACCESSOR_SHAPE = 0x08, |
| |
| UNUSED_BITS = 0x3C |
| }; |
| |
| /* Get a shape identical to this one, without parent/kids 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_(ExclusiveContext* cx, Handle<StackShape> other, uint32_t nfixed); |
| |
| /* |
| * Whether this shape has a valid slot value. This may be true even if |
| * !hasSlot() (see SlotInfo comment above), and may be false even if |
| * hasSlot() if the shape is being constructed and has not had a slot |
| * assigned yet. After construction, hasSlot() implies !hasMissingSlot(). |
| */ |
| bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; } |
| |
| public: |
| bool inDictionary() const { |
| return (flags & IN_DICTIONARY) != 0; |
| } |
| |
| 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(); |
| } |
| |
| void setOverwritten() { |
| flags |= OVERWRITTEN; |
| } |
| bool hadOverwrite() const { |
| return flags & OVERWRITTEN; |
| } |
| |
| void update(GetterOp getter, SetterOp setter, uint8_t attrs); |
| |
| bool matches(const Shape* other) const { |
| return propid_.get() == other->propid_.get() && |
| matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags, |
| other->getter(), other->setter()); |
| } |
| |
| inline bool matches(const StackShape& other) const; |
| |
| bool matchesParamsAfterId(BaseShape* base, uint32_t aslot, unsigned aattrs, unsigned aflags, |
| GetterOp rawGetter, SetterOp rawSetter) const |
| { |
| return base->unowned() == this->base()->unowned() && |
| maybeSlot() == aslot && |
| attrs == aattrs && |
| getter() == rawGetter && |
| setter() == rawSetter; |
| } |
| |
| bool set(JSContext* cx, HandleNativeObject obj, HandleObject receiver, MutableHandleValue vp, |
| ObjectOpResult& result); |
| |
| BaseShape* base() const { return base_.get(); } |
| |
| bool hasSlot() const { |
| return (attrs & JSPROP_SHARED) == 0; |
| } |
| uint32_t slot() const { MOZ_ASSERT(hasSlot() && !hasMissingSlot()); return maybeSlot(); } |
| uint32_t maybeSlot() const { |
| return slotInfo & SLOT_MASK; |
| } |
| |
| bool isEmptyShape() const { |
| MOZ_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot()); |
| return JSID_IS_EMPTY(propid_); |
| } |
| |
| uint32_t slotSpan(const Class* clasp) const { |
| MOZ_ASSERT(!inDictionary()); |
| uint32_t free = JSSLOT_FREE(clasp); |
| return hasMissingSlot() ? free : Max(free, maybeSlot() + 1); |
| } |
| |
| uint32_t slotSpan() const { |
| return slotSpan(getObjectClass()); |
| } |
| |
| void setSlot(uint32_t slot) { |
| MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); |
| slotInfo = slotInfo & ~Shape::SLOT_MASK; |
| slotInfo = slotInfo | slot; |
| } |
| |
| uint32_t numFixedSlots() const { |
| return slotInfo >> FIXED_SLOTS_SHIFT; |
| } |
| |
| void setNumFixedSlots(uint32_t nfixed) { |
| MOZ_ASSERT(nfixed < FIXED_SLOTS_MAX); |
| slotInfo = slotInfo & ~FIXED_SLOTS_MASK; |
| slotInfo = slotInfo | (nfixed << FIXED_SLOTS_SHIFT); |
| } |
| |
| uint32_t numLinearSearches() const { |
| return (slotInfo & LINEAR_SEARCHES_MASK) >> LINEAR_SEARCHES_SHIFT; |
| } |
| |
| void incrementNumLinearSearches() { |
| uint32_t count = numLinearSearches(); |
| MOZ_ASSERT(count < LINEAR_SEARCHES_MAX); |
| slotInfo = slotInfo & ~LINEAR_SEARCHES_MASK; |
| slotInfo = slotInfo | ((count + 1) << LINEAR_SEARCHES_SHIFT); |
| } |
| |
| const PreBarrieredId& propid() const { |
| MOZ_ASSERT(!isEmptyShape()); |
| MOZ_ASSERT(!JSID_IS_VOID(propid_)); |
| return propid_; |
| } |
| PreBarrieredId& 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; |
| } |
| |
| bool hasShadowable() const { return attrs & JSPROP_SHADOWABLE; } |
| |
| uint32_t entryCount() { |
| if (hasTable()) |
| return table().entryCount(); |
| uint32_t count = 0; |
| for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) |
| ++count; |
| return count; |
| } |
| |
| bool isBigEnoughForAShapeTable() { |
| MOZ_ASSERT(!hasTable()); |
| Shape* shape = this; |
| uint32_t count = 0; |
| for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) { |
| ++count; |
| if (count >= ShapeTable::MIN_ENTRIES) |
| return true; |
| } |
| return false; |
| } |
| |
| #ifdef DEBUG |
| void dump(JSContext* cx, FILE* fp) const; |
| void dumpSubtree(JSContext* cx, int level, FILE* fp) const; |
| #endif |
| |
| void sweep(); |
| void finalize(FreeOp* fop); |
| void removeChild(Shape* child); |
| |
| static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; } |
| |
| void traceChildren(JSTracer* trc); |
| |
| inline Shape* search(ExclusiveContext* cx, jsid id); |
| inline Shape* searchLinear(jsid id); |
| |
| void fixupAfterMovingGC(); |
| void fixupGetterSetterForBarrier(JSTracer* trc); |
| |
| /* For JIT usage */ |
| static inline size_t offsetOfBase() { return offsetof(Shape, base_); } |
| static inline size_t offsetOfSlotInfo() { return offsetof(Shape, slotInfo); } |
| static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; } |
| |
| private: |
| void fixupDictionaryShapeAfterMovingGC(); |
| void fixupShapeTreeAfterMovingGC(); |
| |
| static void staticAsserts() { |
| JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base)); |
| JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo)); |
| JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT); |
| } |
| }; |
| |
| /* 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/kids information. */ |
| inline AccessorShape(const StackShape& other, uint32_t nfixed); |
| }; |
| |
| inline |
| StackBaseShape::StackBaseShape(Shape* shape) |
| : flags(shape->getObjectFlags()), |
| clasp(shape->getObjectClass()), |
| compartment(shape->compartment()) |
| {} |
| |
| class MOZ_RAII AutoRooterGetterSetter |
| { |
| class Inner : private JS::CustomAutoRooter |
| { |
| public: |
| inline Inner(ExclusiveContext* cx, uint8_t attrs, GetterOp* pgetter_, SetterOp* psetter_); |
| |
| private: |
| virtual void trace(JSTracer* trc); |
| |
| uint8_t attrs; |
| GetterOp* pgetter; |
| SetterOp* psetter; |
| }; |
| |
| public: |
| inline AutoRooterGetterSetter(ExclusiveContext* cx, uint8_t attrs, |
| GetterOp* pgetter, SetterOp* psetter |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM); |
| inline AutoRooterGetterSetter(ExclusiveContext* cx, uint8_t attrs, |
| JSNative* pgetter, JSNative* psetter |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM); |
| |
| private: |
| mozilla::Maybe<Inner> inner; |
| MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
| }; |
| |
| struct EmptyShape : public js::Shape |
| { |
| EmptyShape(UnownedBaseShape* base, uint32_t nfixed) |
| : js::Shape(base, nfixed) |
| { |
| // Only empty shapes can be NON_NATIVE. |
| if (!getObjectClass()->isNative()) |
| flags |= NON_NATIVE; |
| } |
| |
| static Shape* new_(ExclusiveContext* 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(ExclusiveContext* cx, const Class* clasp, |
| TaggedProto proto, size_t nfixed, uint32_t objectFlags = 0); |
| static Shape* getInitialShape(ExclusiveContext* cx, const Class* 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(ExclusiveContext* 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(ExclusiveContext* cx, Handle<ObjectSubclass*> obj); |
| }; |
| |
| /* |
| * Entries for the per-compartment initialShapes set indexing initial shapes |
| * for objects in the compartment 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. |
| */ |
| ReadBarrieredShape shape; |
| |
| /* |
| * Matching prototype for the entry. The shape of an object determines its |
| * prototype, but the prototype cannot be determined from the shape itself. |
| */ |
| TaggedProto proto; |
| |
| /* State used to determine a match on an initial shape. */ |
| struct Lookup { |
| const Class* clasp; |
| TaggedProto hashProto; |
| TaggedProto matchProto; |
| uint32_t nfixed; |
| uint32_t baseFlags; |
| |
| Lookup(const Class* clasp, TaggedProto proto, uint32_t nfixed, uint32_t baseFlags) |
| : clasp(clasp), |
| hashProto(proto), matchProto(proto), |
| nfixed(nfixed), baseFlags(baseFlags) |
| {} |
| }; |
| |
| inline InitialShapeEntry(); |
| inline InitialShapeEntry(const ReadBarrieredShape& shape, TaggedProto proto); |
| |
| inline Lookup getLookup() const; |
| |
| static inline HashNumber hash(const Lookup& lookup); |
| static inline bool match(const InitialShapeEntry& key, const Lookup& lookup); |
| static void rekey(InitialShapeEntry& k, const InitialShapeEntry& newKey) { k = newKey; } |
| }; |
| |
| typedef HashSet<InitialShapeEntry, InitialShapeEntry, SystemAllocPolicy> InitialShapeSet; |
| |
| struct StackShape : public JS::Traceable |
| { |
| /* For performance, StackShape only roots when absolutely necessary. */ |
| UnownedBaseShape* base; |
| jsid propid; |
| GetterOp rawGetter; |
| SetterOp rawSetter; |
| uint32_t slot_; |
| uint8_t attrs; |
| uint8_t flags; |
| |
| explicit StackShape(UnownedBaseShape* base, jsid propid, uint32_t slot, |
| unsigned attrs, unsigned flags) |
| : base(base), |
| propid(propid), |
| rawGetter(nullptr), |
| rawSetter(nullptr), |
| slot_(slot), |
| attrs(uint8_t(attrs)), |
| flags(uint8_t(flags)) |
| { |
| MOZ_ASSERT(base); |
| MOZ_ASSERT(!JSID_IS_VOID(propid)); |
| MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); |
| MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); |
| } |
| |
| explicit StackShape(Shape* shape) |
| : base(shape->base()->unowned()), |
| propid(shape->propidRef()), |
| rawGetter(shape->getter()), |
| rawSetter(shape->setter()), |
| slot_(shape->maybeSlot()), |
| attrs(shape->attrs), |
| flags(shape->flags) |
| {} |
| |
| void updateGetterSetter(GetterOp rawGetter, SetterOp rawSetter) { |
| if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER|JSPROP_SETTER))) |
| flags |= Shape::ACCESSOR_SHAPE; |
| else |
| flags &= ~Shape::ACCESSOR_SHAPE; |
| |
| this->rawGetter = rawGetter; |
| this->rawSetter = rawSetter; |
| } |
| |
| bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; } |
| bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; } |
| |
| uint32_t slot() const { MOZ_ASSERT(hasSlot() && !hasMissingSlot()); return slot_; } |
| uint32_t maybeSlot() const { return slot_; } |
| |
| uint32_t slotSpan() const { |
| uint32_t free = JSSLOT_FREE(base->clasp_); |
| return hasMissingSlot() ? free : (maybeSlot() + 1); |
| } |
| |
| void setSlot(uint32_t slot) { |
| MOZ_ASSERT(slot <= SHAPE_INVALID_SLOT); |
| slot_ = slot; |
| } |
| |
| bool isAccessorShape() const { |
| return flags & Shape::ACCESSOR_SHAPE; |
| } |
| |
| HashNumber hash() const { |
| HashNumber hash = uintptr_t(base); |
| |
| /* Accumulate from least to most random so the low bits are most random. */ |
| hash = mozilla::RotateLeft(hash, 4) ^ attrs; |
| hash = mozilla::RotateLeft(hash, 4) ^ slot_; |
| hash = mozilla::RotateLeft(hash, 4) ^ JSID_BITS(propid); |
| hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawGetter); |
| hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawSetter); |
| return hash; |
| } |
| |
| // Traceable implementation. |
| static void trace(StackShape* stackShape, JSTracer* trc) { stackShape->trace(trc); } |
| void trace(JSTracer* trc); |
| }; |
| |
| template <typename Outer> |
| class StackShapeOperations { |
| const StackShape& ss() const { return static_cast<const Outer*>(this)->get(); } |
| |
| public: |
| bool hasSlot() const { return ss().hasSlot(); } |
| 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 Outer> |
| class MutableStackShapeOperations : public StackShapeOperations<Outer> { |
| StackShape& ss() { return static_cast<Outer*>(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; } |
| }; |
| |
| template <> |
| class RootedBase<StackShape> : public MutableStackShapeOperations<JS::Rooted<StackShape>> |
| {}; |
| |
| template <> |
| class HandleBase<StackShape> : public StackShapeOperations<JS::Handle<StackShape>> |
| {}; |
| |
| template <> |
| class MutableHandleBase<StackShape> |
| : public MutableStackShapeOperations<JS::MutableHandle<StackShape>> |
| {}; |
| |
| inline |
| Shape::Shape(const StackShape& other, uint32_t nfixed) |
| : base_(other.base), |
| propid_(other.propid), |
| slotInfo(other.maybeSlot() | (nfixed << FIXED_SLOTS_SHIFT)), |
| attrs(other.attrs), |
| flags(other.flags), |
| parent(nullptr) |
| { |
| #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(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); |
| kids.setNull(); |
| } |
| |
| // This class is used to add a post barrier on the AccessorShape's getter/setter |
| // objects. It updates the pointers and the shape's entry in the parent's |
| // KidsHash table. |
| class ShapeGetterSetterRef : public gc::BufferableRef |
| { |
| AccessorShape* shape_; |
| |
| public: |
| explicit ShapeGetterSetterRef(AccessorShape* shape) : shape_(shape) {} |
| void trace(JSTracer* trc) override { shape_->fixupGetterSetterForBarrier(trc); } |
| }; |
| |
| static inline void |
| GetterSetterWriteBarrierPost(AccessorShape* shape) |
| { |
| MOZ_ASSERT(shape); |
| if (shape->hasGetterObject()) { |
| gc::StoreBuffer* sb = reinterpret_cast<gc::Cell*>(shape->getterObject())->storeBuffer(); |
| if (sb) { |
| sb->putGeneric(ShapeGetterSetterRef(shape)); |
| return; |
| } |
| } |
| if (shape->hasSetterObject()) { |
| gc::StoreBuffer* sb = reinterpret_cast<gc::Cell*>(shape->setterObject())->storeBuffer(); |
| if (sb) { |
| sb->putGeneric(ShapeGetterSetterRef(shape)); |
| return; |
| } |
| } |
| } |
| |
| inline |
| AccessorShape::AccessorShape(const StackShape& other, uint32_t nfixed) |
| : Shape(other, nfixed), |
| rawGetter(other.rawGetter), |
| rawSetter(other.rawSetter) |
| { |
| MOZ_ASSERT(getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE); |
| GetterSetterWriteBarrierPost(this); |
| } |
| |
| inline |
| Shape::Shape(UnownedBaseShape* base, uint32_t nfixed) |
| : base_(base), |
| propid_(JSID_EMPTY), |
| slotInfo(SHAPE_INVALID_SLOT | (nfixed << FIXED_SLOTS_SHIFT)), |
| attrs(JSPROP_SHARED), |
| flags(0), |
| parent(nullptr) |
| { |
| MOZ_ASSERT(base); |
| kids.setNull(); |
| } |
| |
| 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 void |
| Shape::initDictionaryShape(const StackShape& child, uint32_t nfixed, HeapPtrShape* dictp) |
| { |
| if (child.isAccessorShape()) |
| new (this) AccessorShape(child, nfixed); |
| else |
| new (this) Shape(child, nfixed); |
| this->flags |= IN_DICTIONARY; |
| |
| this->listp = nullptr; |
| if (dictp) |
| insertIntoDictionary(dictp); |
| } |
| |
| inline Shape* |
| Shape::searchLinear(jsid id) |
| { |
| /* |
| * Non-dictionary shapes can acquire a table at any point the main thread |
| * is operating on it, so other threads inspecting such shapes can't use |
| * their table without racing. This function can be called from any thread |
| * on any non-dictionary shape. |
| */ |
| MOZ_ASSERT(!inDictionary()); |
| |
| for (Shape* shape = this; shape; ) { |
| if (shape->propidRef() == id) |
| return shape; |
| shape = shape->parent; |
| } |
| |
| return nullptr; |
| } |
| |
| /* |
| * Keep this function in sync with search. It neither hashifies the start |
| * shape nor increments linear search count. |
| */ |
| inline Shape* |
| Shape::searchNoHashify(Shape* start, jsid id) |
| { |
| /* |
| * If we have a table, search in the shape table, else do a linear |
| * search. We never hashify into a table in parallel. |
| */ |
| if (start->hasTable()) { |
| ShapeTable::Entry& entry = start->table().search(id); |
| return entry.shape(); |
| } |
| |
| return start->searchLinear(id); |
| } |
| |
| inline bool |
| Shape::matches(const StackShape& other) const |
| { |
| return propid_.get() == other.propid && |
| matchesParamsAfterId(other.base, other.slot_, other.attrs, other.flags, |
| other.rawGetter, other.rawSetter); |
| } |
| |
| template<> struct RootKind<Shape*> : SpecificRootKind<Shape*, THING_ROOT_SHAPE> {}; |
| template<> struct RootKind<BaseShape*> : SpecificRootKind<BaseShape*, THING_ROOT_BASE_SHAPE> {}; |
| |
| // Property lookup hooks on objects are required to return a non-nullptr shape |
| // to signify that the property has been found. For cases where the property is |
| // not actually represented by a Shape, use a dummy value. This includes all |
| // properties of non-native objects, and dense elements for native objects. |
| // Use separate APIs for these two cases. |
| |
| template <AllowGC allowGC> |
| static inline void |
| MarkNonNativePropertyFound(typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) |
| { |
| propp.set(reinterpret_cast<Shape*>(1)); |
| } |
| |
| template <AllowGC allowGC> |
| static inline void |
| MarkDenseOrTypedArrayElementFound(typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) |
| { |
| propp.set(reinterpret_cast<Shape*>(1)); |
| } |
| |
| static inline bool |
| IsImplicitDenseOrTypedArrayElement(Shape* prop) |
| { |
| return prop == reinterpret_cast<Shape*>(1); |
| } |
| |
| static inline bool |
| IsImplicitNonNativeProperty(Shape* prop) |
| { |
| return prop == reinterpret_cast<Shape*>(1); |
| } |
| |
| Shape* |
| ReshapeForAllocKind(JSContext* cx, Shape* shape, TaggedProto proto, |
| gc::AllocKind allocKind); |
| |
| } // namespace js |
| |
| #ifdef _MSC_VER |
| #pragma warning(pop) |
| #pragma warning(pop) |
| #endif |
| |
| // JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell |
| // instances that occupy a compartment. |
| namespace JS { |
| namespace ubi { |
| |
| template<> struct Concrete<js::Shape> : TracerConcreteWithCompartment<js::Shape> { |
| Size size(mozilla::MallocSizeOf mallocSizeOf) const override; |
| |
| protected: |
| explicit Concrete(js::Shape *ptr) : TracerConcreteWithCompartment<js::Shape>(ptr) { } |
| |
| public: |
| static void construct(void *storage, js::Shape *ptr) { new (storage) Concrete(ptr); } |
| }; |
| |
| template<> struct Concrete<js::BaseShape> : TracerConcreteWithCompartment<js::BaseShape> { |
| Size size(mozilla::MallocSizeOf mallocSizeOf) const override; |
| |
| protected: |
| explicit Concrete(js::BaseShape *ptr) : TracerConcreteWithCompartment<js::BaseShape>(ptr) { } |
| |
| public: |
| static void construct(void *storage, js::BaseShape *ptr) { new (storage) Concrete(ptr); } |
| }; |
| |
| } // namespace ubi |
| } // namespace JS |
| |
| #endif /* vm_Shape_h */ |