#ifndef vm_Shape_inl_h
#define vm_Shape_inl_h
#include "vm/Shape.h"
#include "mozilla/TypeTraits.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 Class* 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>
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>
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 {
cx->recoverFromOutOfMemory();
}
foundShape = start->searchLinear(id);
if (start->hasIC()) {
JS::AutoCheckCannotGC nogc;
if (!start->appendShapeToIC(id, foundShape, nogc)) {
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 = base_;
if (IsForwarded(base)) {
base_.unsafeSet(Forwarded(base));
}
}
static inline void GetterSetterWriteBarrierPost(AccessorShape* shape) {
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("GetterSetterWriteBarrierPost");
}
}
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);
GetterSetterWriteBarrierPost(this);
}
inline void Shape::initDictionaryShape(const StackShape& child, uint32_t nfixed,
GCPtrShape* dictp) {
if (child.isAccessorShape()) {
new (this) AccessorShape(child, nfixed);
} else {
new (this) Shape(child, nfixed);
}
this->immutableFlags |= IN_DICTIONARY;
this->listp = nullptr;
if (dictp) {
insertIntoDictionary(dictp);
}
}
template <class ObjectSubclass>
inline bool EmptyShape::ensureInitialCustomShape(
JSContext* cx, Handle<ObjectSubclass*> obj) {
static_assert(mozilla::IsBaseOf<JSObject, ObjectSubclass>::value,
"ObjectSubclass must be a subclass of JSObject");
if (!obj->empty()) {
return true;
}
RootedShape shape(cx, ObjectSubclass::assignInitialShape(cx, obj));
if (!shape) {
return false;
}
MOZ_ASSERT(!obj->empty());
if (obj->isDelegate()) {
return true;
}
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 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) {
if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
inner.emplace(cx, Inner(attrs, pgetter, psetter));
}
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
static inline uint8_t GetPropertyAttributes(JSObject* obj,
PropertyResult prop) {
MOZ_ASSERT(obj->isNative());
if (prop.isDenseOrTypedArrayElement()) {
if (obj->is<TypedArrayObject>()) {
return JSPROP_ENUMERATE | JSPROP_PERMANENT;
}
return obj->as<NativeObject>().getElementsHeader()->elementAttributes();
}
return prop.shape()->attributes();
}
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));
HashNumber hash0 = HashId(id);
HashNumber hash1 = Hash1(hash0, hashShift_);
Entry* entry = &getEntry(hash1);
if (entry->isFree()) {
return *entry;
}
Shape* shape = entry->shape();
if (shape && shape->propidRaw() == id) {
return *entry;
}
uint32_t sizeLog2 = HASH_BITS - hashShift_;
HashNumber hash2 = Hash2(hash0, sizeLog2, hashShift_);
uint32_t sizeMask = JS_BITMASK(sizeLog2);
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);
}
MOZ_ALWAYS_INLINE Shape* Shape::searchNoHashify(Shape* start, jsid id) {
Shape* foundShape;
JS::AutoCheckCannotGC nogc;
ShapeCachePtr cache = start->getCache(nogc);
if (!cache.search<MaybeAdding::NotAdding>(id, start, &foundShape)) {
foundShape = start->searchLinear(id);
}
return foundShape;
}
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(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);
}
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(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);
}
}
#endif