summaryrefslogtreecommitdiffstats
path: root/js/src/vm/Shape-inl.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/vm/Shape-inl.h463
1 files changed, 463 insertions, 0 deletions
diff --git a/js/src/vm/Shape-inl.h b/js/src/vm/Shape-inl.h
new file mode 100644
index 0000000000..01ed0391b9
--- /dev/null
+++ b/js/src/vm/Shape-inl.h
@@ -0,0 +1,463 @@
+/* -*- 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_inl_h
+#define vm_Shape_inl_h
+
+#include "vm/Shape.h"
+
+#include "gc/Allocator.h"
+#include "vm/Interpreter.h"
+#include "vm/JSObject.h"
+#include "vm/TypedArrayObject.h"
+
+#include "gc/Marking-inl.h"
+#include "vm/JSAtom-inl.h"
+#include "vm/JSContext-inl.h"
+
+namespace js {
+
+inline AutoKeepShapeCaches::AutoKeepShapeCaches(JSContext* cx)
+ : cx_(cx), prev_(cx->zone()->keepShapeCaches()) {
+ cx->zone()->setKeepShapeCaches(true);
+}
+
+inline AutoKeepShapeCaches::~AutoKeepShapeCaches() {
+ cx_->zone()->setKeepShapeCaches(prev_);
+}
+
+inline StackBaseShape::StackBaseShape(const JSClass* clasp,
+ uint32_t objectFlags)
+ : flags(objectFlags), clasp(clasp) {}
+
+MOZ_ALWAYS_INLINE Shape* Shape::search(JSContext* cx, jsid id) {
+ return search(cx, this, id);
+}
+
+MOZ_ALWAYS_INLINE bool Shape::maybeCreateCacheForLookup(JSContext* cx) {
+ if (hasTable() || hasIC()) {
+ return true;
+ }
+
+ if (!inDictionary() && numLinearSearches() < LINEAR_SEARCHES_MAX) {
+ incrementNumLinearSearches();
+ return true;
+ }
+
+ if (!isBigEnoughForAShapeTable()) {
+ return true;
+ }
+
+ return Shape::cachify(cx, this);
+}
+
+template <MaybeAdding Adding>
+/* static */ inline bool Shape::search(JSContext* cx, Shape* start, jsid id,
+ const AutoKeepShapeCaches& keep,
+ Shape** pshape, ShapeTable** ptable,
+ ShapeTable::Entry** pentry) {
+ if (start->inDictionary()) {
+ ShapeTable* table = start->ensureTableForDictionary(cx, keep);
+ if (!table) {
+ return false;
+ }
+ *ptable = table;
+ *pentry = &table->search<Adding>(id, keep);
+ *pshape = (*pentry)->shape();
+ return true;
+ }
+
+ *ptable = nullptr;
+ *pentry = nullptr;
+ *pshape = Shape::search<Adding>(cx, start, id);
+ return true;
+}
+
+template <MaybeAdding Adding>
+/* static */ MOZ_ALWAYS_INLINE Shape* Shape::search(JSContext* cx, Shape* start,
+ jsid id) {
+ Shape* foundShape = nullptr;
+ if (start->maybeCreateCacheForLookup(cx)) {
+ JS::AutoCheckCannotGC nogc;
+ ShapeCachePtr cache = start->getCache(nogc);
+ if (cache.search<Adding>(id, start, &foundShape)) {
+ return foundShape;
+ }
+ } else {
+ // Just do a linear search.
+ cx->recoverFromOutOfMemory();
+ }
+
+ foundShape = start->searchLinear(id);
+ if (start->hasIC()) {
+ JS::AutoCheckCannotGC nogc;
+ if (!start->appendShapeToIC(id, foundShape, nogc)) {
+ // Failure indicates that the cache is full, which means we missed
+ // the cache ShapeIC::MAX_SIZE times. This indicates the cache is no
+ // longer useful, so convert it into a ShapeTable.
+ if (!Shape::hashify(cx, start)) {
+ cx->recoverFromOutOfMemory();
+ }
+ }
+ }
+ return foundShape;
+}
+
+inline Shape* Shape::new_(JSContext* cx, Handle<StackShape> other,
+ uint32_t nfixed) {
+ Shape* shape = other.isAccessorShape() ? js::Allocate<AccessorShape>(cx)
+ : js::Allocate<Shape>(cx);
+ if (!shape) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ if (other.isAccessorShape()) {
+ new (shape) AccessorShape(other, nfixed);
+ } else {
+ new (shape) Shape(other, nfixed);
+ }
+
+ return shape;
+}
+
+inline void Shape::updateBaseShapeAfterMovingGC() {
+ BaseShape* base = this->base();
+ if (IsForwarded(base)) {
+ unbarrieredSetHeaderPtr(Forwarded(base));
+ }
+}
+
+static inline void GetterSetterPostWriteBarrier(AccessorShape* shape) {
+ // If the shape contains any nursery pointers then add it to a vector on the
+ // zone that we fixup on minor GC. Prevent this vector growing too large
+ // since we don't tolerate OOM here.
+
+ static const size_t MaxShapeVectorLength = 5000;
+
+ MOZ_ASSERT(shape);
+
+ gc::StoreBuffer* sb = nullptr;
+ if (shape->hasGetterObject()) {
+ sb = shape->getterObject()->storeBuffer();
+ }
+ if (!sb && shape->hasSetterObject()) {
+ sb = shape->setterObject()->storeBuffer();
+ }
+ if (!sb) {
+ return;
+ }
+
+ auto& nurseryShapes = shape->zone()->nurseryShapes();
+
+ {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!nurseryShapes.append(shape)) {
+ oomUnsafe.crash("GetterSetterPostWriteBarrier");
+ }
+ }
+
+ if (nurseryShapes.length() == 1) {
+ sb->putGeneric(NurseryShapesRef(shape->zone()));
+ } else if (nurseryShapes.length() == MaxShapeVectorLength) {
+ sb->setAboutToOverflow(JS::GCReason::FULL_SHAPE_BUFFER);
+ }
+}
+
+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);
+ GetterSetterPostWriteBarrier(this);
+}
+
+inline void Shape::initDictionaryShape(const StackShape& child, uint32_t nfixed,
+ DictionaryShapeLink next) {
+ if (child.isAccessorShape()) {
+ new (this) AccessorShape(child, nfixed);
+ } else {
+ new (this) Shape(child, nfixed);
+ }
+ this->immutableFlags |= IN_DICTIONARY;
+
+ MOZ_ASSERT(dictNext.isNone());
+ if (!next.isNone()) {
+ insertIntoDictionaryBefore(next);
+ }
+}
+
+inline void Shape::setNextDictionaryShape(Shape* shape) {
+ setDictionaryNextPtr(DictionaryShapeLink(shape));
+}
+
+inline void Shape::setDictionaryObject(JSObject* obj) {
+ setDictionaryNextPtr(DictionaryShapeLink(obj));
+}
+
+inline void Shape::clearDictionaryNextPtr() {
+ setDictionaryNextPtr(DictionaryShapeLink());
+}
+
+inline void Shape::setDictionaryNextPtr(DictionaryShapeLink next) {
+ MOZ_ASSERT(inDictionary());
+ dictNextPreWriteBarrier();
+ dictNext = next;
+}
+
+inline void Shape::dictNextPreWriteBarrier() {
+ // Only object pointers are traced, so we only need to barrier those.
+ if (dictNext.isObject()) {
+ gc::PreWriteBarrier(dictNext.toObject());
+ }
+}
+
+inline GCPtrShape* DictionaryShapeLink::prevPtr() {
+ MOZ_ASSERT(!isNone());
+
+ if (isShape()) {
+ return &toShape()->parent;
+ }
+
+ return toObject()->as<NativeObject>().shapePtr();
+}
+
+template <class ObjectSubclass>
+/* static */ inline bool EmptyShape::ensureInitialCustomShape(
+ JSContext* cx, Handle<ObjectSubclass*> obj) {
+ static_assert(std::is_base_of_v<JSObject, ObjectSubclass>,
+ "ObjectSubclass must be a subclass of JSObject");
+
+ // If the provided object has a non-empty shape, it was given the cached
+ // initial shape when created: nothing to do.
+ if (!obj->empty()) {
+ return true;
+ }
+
+ // If no initial shape was assigned, do so.
+ RootedShape shape(cx, ObjectSubclass::assignInitialShape(cx, obj));
+ if (!shape) {
+ return false;
+ }
+ MOZ_ASSERT(!obj->empty());
+
+ // If the object is a standard prototype -- |RegExp.prototype|,
+ // |String.prototype|, |RangeError.prototype|, &c. -- GlobalObject.cpp's
+ // |CreateBlankProto| marked it as a delegate. These are the only objects
+ // of this class that won't use the standard prototype, and there's no
+ // reason to pollute the initial shape cache with entries for them.
+ if (obj->isDelegate()) {
+ return true;
+ }
+
+ // Cache the initial shape for non-prototype objects, however, so that
+ // future instances will begin life with that shape.
+ RootedObject proto(cx, obj->staticPrototype());
+ EmptyShape::insertInitialShape(cx, shape, proto);
+ return true;
+}
+
+inline AutoRooterGetterSetter::Inner::Inner(uint8_t attrs, GetterOp* pgetter_,
+ SetterOp* psetter_)
+ : attrs(attrs), pgetter(pgetter_), psetter(psetter_) {}
+
+inline AutoRooterGetterSetter::AutoRooterGetterSetter(JSContext* cx,
+ uint8_t attrs,
+ GetterOp* pgetter,
+ SetterOp* psetter) {
+ if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
+ inner.emplace(cx, Inner(attrs, pgetter, psetter));
+ }
+}
+
+static inline uint8_t GetPropertyAttributes(JSObject* obj,
+ PropertyResult prop) {
+ MOZ_ASSERT(obj->isNative());
+
+ if (prop.isDenseElement()) {
+ return obj->as<NativeObject>().getElementsHeader()->elementAttributes();
+ }
+ if (prop.isTypedArrayElement()) {
+ return JSPROP_ENUMERATE;
+ }
+
+ return prop.shape()->attributes();
+}
+
+/*
+ * Double hashing needs the second hash code to be relatively prime to table
+ * size, so we simply make hash2 odd.
+ */
+MOZ_ALWAYS_INLINE HashNumber Hash1(HashNumber hash0, uint32_t shift) {
+ return hash0 >> shift;
+}
+
+MOZ_ALWAYS_INLINE HashNumber Hash2(HashNumber hash0, uint32_t log2,
+ uint32_t shift) {
+ return ((hash0 << log2) >> shift) | 1;
+}
+
+template <MaybeAdding Adding>
+MOZ_ALWAYS_INLINE ShapeTable::Entry& ShapeTable::searchUnchecked(jsid id) {
+ MOZ_ASSERT(entries_);
+ MOZ_ASSERT(!JSID_IS_EMPTY(id));
+
+ /* Compute the primary hash address. */
+ HashNumber hash0 = HashId(id);
+ HashNumber hash1 = Hash1(hash0, hashShift_);
+ Entry* entry = &getEntry(hash1);
+
+ /* Miss: return space for a new entry. */
+ if (entry->isFree()) {
+ return *entry;
+ }
+
+ /* Hit: return entry. */
+ Shape* shape = entry->shape();
+ if (shape && shape->propidRaw() == id) {
+ return *entry;
+ }
+
+ /* Collision: double hash. */
+ uint32_t sizeLog2 = HASH_BITS - hashShift_;
+ HashNumber hash2 = Hash2(hash0, sizeLog2, hashShift_);
+ uint32_t sizeMask = BitMask(sizeLog2);
+
+ /* Save the first removed entry pointer so we can recycle it if adding. */
+ Entry* firstRemoved;
+ if (Adding == MaybeAdding::Adding) {
+ if (entry->isRemoved()) {
+ firstRemoved = entry;
+ } else {
+ firstRemoved = nullptr;
+ if (!entry->hadCollision()) {
+ entry->flagCollision();
+ }
+ }
+ }
+
+#ifdef DEBUG
+ bool collisionFlag = true;
+ if (!entry->isRemoved()) {
+ collisionFlag = entry->hadCollision();
+ }
+#endif
+
+ while (true) {
+ hash1 -= hash2;
+ hash1 &= sizeMask;
+ entry = &getEntry(hash1);
+
+ if (entry->isFree()) {
+ return (Adding == MaybeAdding::Adding && firstRemoved) ? *firstRemoved
+ : *entry;
+ }
+
+ shape = entry->shape();
+ if (shape && shape->propidRaw() == id) {
+ MOZ_ASSERT(collisionFlag);
+ return *entry;
+ }
+
+ if (Adding == MaybeAdding::Adding) {
+ if (entry->isRemoved()) {
+ if (!firstRemoved) {
+ firstRemoved = entry;
+ }
+ } else {
+ if (!entry->hadCollision()) {
+ entry->flagCollision();
+ }
+ }
+ }
+
+#ifdef DEBUG
+ if (!entry->isRemoved()) {
+ collisionFlag &= entry->hadCollision();
+ }
+#endif
+ }
+
+ MOZ_CRASH("Shape::search failed to find an expected entry.");
+}
+
+template <MaybeAdding Adding>
+MOZ_ALWAYS_INLINE ShapeTable::Entry& ShapeTable::search(
+ jsid id, const AutoKeepShapeCaches&) {
+ return searchUnchecked<Adding>(id);
+}
+
+template <MaybeAdding Adding>
+MOZ_ALWAYS_INLINE ShapeTable::Entry& ShapeTable::search(
+ jsid id, const JS::AutoCheckCannotGC&) {
+ return searchUnchecked<Adding>(id);
+}
+
+/*
+ * Keep this function in sync with search. It neither hashifies the start
+ * shape nor increments linear search count.
+ */
+MOZ_ALWAYS_INLINE Shape* Shape::searchNoHashify(Shape* start, jsid id) {
+ /*
+ * If we have a cache, search in the shape cache, else do a linear
+ * search. We never hashify or cachify into a table in parallel.
+ */
+ Shape* foundShape;
+ JS::AutoCheckCannotGC nogc;
+ ShapeCachePtr cache = start->getCache(nogc);
+ if (!cache.search<MaybeAdding::NotAdding>(id, start, &foundShape)) {
+ foundShape = start->searchLinear(id);
+ }
+
+ return foundShape;
+}
+
+/* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::addDataProperty(
+ JSContext* cx, HandleNativeObject obj, HandleId id, uint32_t slot,
+ unsigned attrs) {
+ MOZ_ASSERT(!JSID_IS_VOID(id));
+ MOZ_ASSERT_IF(!id.isPrivateName(), obj->uninlinedNonProxyIsExtensible());
+ MOZ_ASSERT(!obj->containsPure(id));
+
+ AutoKeepShapeCaches keep(cx);
+ ShapeTable* table = nullptr;
+ ShapeTable::Entry* entry = nullptr;
+ if (obj->inDictionaryMode()) {
+ table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
+ if (!table) {
+ return nullptr;
+ }
+ entry = &table->search<MaybeAdding::Adding>(id, keep);
+ }
+
+ return addDataPropertyInternal(cx, obj, id, slot, attrs, table, entry, keep);
+}
+
+/* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::addAccessorProperty(
+ JSContext* cx, HandleNativeObject obj, HandleId id, GetterOp getter,
+ SetterOp setter, unsigned attrs) {
+ MOZ_ASSERT(!JSID_IS_VOID(id));
+ MOZ_ASSERT_IF(!id.isPrivateName(), obj->uninlinedNonProxyIsExtensible());
+ MOZ_ASSERT(!obj->containsPure(id));
+
+ AutoKeepShapeCaches keep(cx);
+ ShapeTable* table = nullptr;
+ ShapeTable::Entry* entry = nullptr;
+ if (obj->inDictionaryMode()) {
+ table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
+ if (!table) {
+ return nullptr;
+ }
+ entry = &table->search<MaybeAdding::Adding>(id, keep);
+ }
+
+ return addAccessorPropertyInternal(cx, obj, id, getter, setter, attrs, table,
+ entry, keep);
+}
+
+} /* namespace js */
+
+#endif /* vm_Shape_inl_h */