Bug 1700052 part 11 - Store getter/setter objects in slots instead of in the shape tree. r=jonco,evilpie

There will be additional cleanup of the Shape code in follow-up patches.

Differential Revision: https://phabricator.services.mozilla.com/D110258
This commit is contained in:
Jan de Mooij 2021-04-07 07:16:07 +00:00
Родитель 1001dc3ad6
Коммит b4ce5b51de
17 изменённых файлов: 241 добавлений и 401 удалений

Просмотреть файл

@ -1291,15 +1291,7 @@ void Shape::traceChildren(JSTracer* trc) {
dictNext.setObject(obj);
}
}
cache_.trace(trc);
if (hasGetterObject()) {
TraceManuallyBarrieredEdge(trc, &asAccessorShape().getter_, "getter");
}
if (hasSetterObject()) {
TraceManuallyBarrieredEdge(trc, &asAccessorShape().setter_, "setter");
}
}
inline void js::GCMarker::eagerlyMarkChildren(Shape* shape) {
MOZ_ASSERT(shape->isMarked(markColor()));
@ -1325,16 +1317,6 @@ inline void js::GCMarker::eagerlyMarkChildren(Shape* shape) {
// be traced by this loop they do not need to be traced here as well.
MOZ_ASSERT(shape->canSkipMarkingShapeCache());
// When triggered between slices on behalf of a barrier, these
// objects may reside in the nursery, so require an extra check.
// FIXME: Bug 1157967 - remove the isTenured checks.
if (shape->hasGetterObject() && shape->getterObject()->isTenured()) {
markAndTraverseEdge(shape, shape->getterObject());
}
if (shape->hasSetterObject() && shape->setterObject()->isTenured()) {
markAndTraverseEdge(shape, shape->setterObject());
}
shape = shape->previous();
} while (shape && mark(shape));
}

Просмотреть файл

@ -236,14 +236,6 @@ void StackShape::trace(JSTracer* trc) {
}
TraceRoot(trc, (jsid*)&propid, "StackShape id");
if (getter) {
TraceRoot(trc, &getter, "StackShape getter");
}
if (setter) {
TraceRoot(trc, &setter, "StackShape setter");
}
}
void StackBaseShape::trace(JSTracer* trc) { proto.trace(trc); }

Просмотреть файл

@ -101,18 +101,6 @@ void gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, Shape* shape) {
// Don't trace the propid because the CC doesn't care about jsid.
if (shape->hasGetterObject()) {
JSObject* tmp = shape->getterObject();
TraceEdgeInternal(trc, &tmp, "getter");
MOZ_ASSERT(tmp == shape->getterObject());
}
if (shape->hasSetterObject()) {
JSObject* tmp = shape->setterObject();
TraceEdgeInternal(trc, &tmp, "setter");
MOZ_ASSERT(tmp == shape->setterObject());
}
shape = shape->previous();
} while (shape);
}

Просмотреть файл

@ -877,12 +877,39 @@ static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
}
}
// See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter
// properties
static void EmitGuardGetterSetterSlot(CacheIRWriter& writer,
NativeObject* holder, Shape* shape,
ObjOperandId holderId,
bool holderIsConstant = false) {
// If the holder is guaranteed to be the same object, and it never had a
// slot holding a GetterSetter mutated or deleted, its Shape will change when
// that does happen so we don't need to guard on the GetterSetter.
if (holderIsConstant && !holder->hadGetterSetterChange()) {
return;
}
size_t slot = shape->slot();
Value slotVal = holder->getSlot(slot);
MOZ_ASSERT(slotVal.isPrivateGCThing());
if (holder->isFixedSlot(slot)) {
size_t offset = NativeObject::getFixedSlotOffset(slot);
writer.guardFixedSlotValue(holderId, offset, slotVal);
} else {
size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value);
writer.guardDynamicSlotValue(holderId, offset, slotVal);
}
}
static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
NativeObject* holder, Shape* shape,
ObjOperandId objId, ICState::Mode mode) {
// Use the megamorphic guard if we're in megamorphic mode, except if |obj|
// is a Window as GuardHasGetterSetter doesn't support this yet (Window may
// require outerizing).
if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
TestMatchingNativeReceiver(writer, obj, objId);
@ -892,9 +919,15 @@ static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
// Guard on the holder's shape.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
EmitGuardGetterSetterSlot(writer, holder, shape, holderId,
/* holderIsConstant = */ true);
} else {
EmitGuardGetterSetterSlot(writer, holder, shape, objId);
}
} else {
writer.guardHasGetterSetter(objId, shape->propid(), shape);
GetterSetter* gs = holder->getGetterSetter(shape);
writer.guardHasGetterSetter(objId, shape->propid(), gs);
}
}
@ -1495,6 +1528,8 @@ AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
// and not the expando object.
MOZ_ASSERT(canCache == CanAttachNativeGetter ||
canCache == CanAttachScriptedGetter);
EmitGuardGetterSetterSlot(writer, nativeExpandoObj, propShape,
expandoObjId);
EmitCallGetterResultNoGuards(cx_, writer, nativeExpandoObj,
nativeExpandoObj, propShape, receiverId);
}
@ -1598,6 +1633,8 @@ AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
MOZ_ASSERT(canCache == CanAttachNativeGetter ||
canCache == CanAttachScriptedGetter);
MOZ_ASSERT(!isSuper());
EmitGuardGetterSetterSlot(writer, holder, shape, holderId,
/* holderIsConstant = */ true);
EmitCallGetterResultNoGuards(cx_, writer, nativeCheckObj, holder, shape,
receiverId);
}
@ -2822,6 +2859,13 @@ AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
// Shape guard holder.
ObjOperandId holderId = writer.loadObject(holder);
writer.guardShape(holderId, holder->lastProperty());
EmitGuardGetterSetterSlot(writer, holder, shape, holderId,
/* holderIsConstant = */ true);
} else {
// Note: pass true for |holderIsConstant| because the holder must be the
// current global object.
EmitGuardGetterSetterSlot(writer, holder, shape, globalId,
/* holderIsConstant = */ true);
}
if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, shape,
@ -3842,9 +3886,15 @@ AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
// Guard on the holder's shape.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
EmitGuardGetterSetterSlot(writer, holder, propShape, holderId,
/* holderIsConstant = */ true);
} else {
EmitGuardGetterSetterSlot(writer, holder, propShape, objId);
}
} else {
writer.guardHasGetterSetter(objId, id, propShape);
GetterSetter* gs = holder->getGetterSetter(propShape);
writer.guardHasGetterSetter(objId, id, gs);
}
if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, propShape,
@ -4233,6 +4283,9 @@ AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
EmitGuardGetterSetterSlot(writer, holder, propShape, holderId,
/* holderIsConstant = */ true);
// EmitCallSetterNoGuards expects |obj| to be the object the property is
// on to do some checks. Since we actually looked at proto, and no extra
// guards will be generated, we can just pass that instead.
@ -4278,14 +4331,15 @@ AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &propShape)) {
auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
// Note that we don't actually use the expandoObjId here after the
// shape guard. The DOM proxy (objId) is passed to the setter as
// |this|.
// Call the setter. Note that we pass objId, the DOM proxy, as |this|
// and not the expando object.
maybeEmitIdGuard(id);
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal,
nativeExpandoObj);
ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
obj, objId, expandoVal, nativeExpandoObj);
MOZ_ASSERT(holder == nativeExpandoObj);
EmitGuardGetterSetterSlot(writer, nativeExpandoObj, propShape,
expandoObjId);
EmitCallSetterNoGuards(cx_, writer, nativeExpandoObj, nativeExpandoObj,
propShape, objId, rhsId);
trackAttached("DOMProxyExpandoSetter");

Просмотреть файл

@ -7042,13 +7042,14 @@ bool CacheIRCompiler::emitMegamorphicStoreSlot(ObjOperandId objId,
bool CacheIRCompiler::emitGuardHasGetterSetter(ObjOperandId objId,
uint32_t idOffset,
uint32_t shapeOffset) {
uint32_t getterSetterOffset) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
Register obj = allocator.useRegister(masm, objId);
StubFieldOffset id(idOffset, StubField::Type::Id);
StubFieldOffset shape(shapeOffset, StubField::Type::Shape);
StubFieldOffset getterSetter(getterSetterOffset,
StubField::Type::GetterSetter);
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
@ -7065,15 +7066,15 @@ bool CacheIRCompiler::emitGuardHasGetterSetter(ObjOperandId objId,
volatileRegs.takeUnchecked(scratch2);
masm.PushRegsInMask(volatileRegs);
using Fn =
bool (*)(JSContext * cx, JSObject * obj, jsid id, Shape * propShape);
using Fn = bool (*)(JSContext * cx, JSObject * obj, jsid id,
GetterSetter * getterSetter);
masm.setupUnalignedABICall(scratch1);
masm.loadJSContext(scratch1);
masm.passABIArg(scratch1);
masm.passABIArg(obj);
emitLoadStubField(id, scratch2);
masm.passABIArg(scratch2);
emitLoadStubField(shape, scratch3);
emitLoadStubField(getterSetter, scratch3);
masm.passABIArg(scratch3);
masm.callWithABI<Fn, ObjectHasGetterSetterPure>();
masm.mov(ReturnReg, scratch1);

Просмотреть файл

@ -530,7 +530,7 @@
args:
obj: ObjId
id: IdField
shape: ShapeField
getterSetter: GetterSetterField
- name: GuardInt32IsNonNegative
shared: true

Просмотреть файл

@ -14894,10 +14894,10 @@ void CodeGenerator::visitGuardHasGetterSetter(LGuardHasGetterSetter* lir) {
Register temp3 = ToRegister(lir->temp3());
masm.movePropertyKey(lir->mir()->propId(), temp2);
masm.movePtr(ImmGCPtr(lir->mir()->shape()), temp3);
masm.movePtr(ImmGCPtr(lir->mir()->getterSetter()), temp3);
using Fn =
bool (*)(JSContext * cx, JSObject * obj, jsid id, Shape * propShape);
using Fn = bool (*)(JSContext * cx, JSObject * obj, jsid id,
GetterSetter * getterSetter);
masm.setupUnalignedABICall(temp1);
masm.loadJSContext(temp1);
masm.passABIArg(temp1);

Просмотреть файл

@ -11948,10 +11948,10 @@ class MLoadWrapperTarget : public MUnaryInstruction,
class MGuardHasGetterSetter : public MUnaryInstruction,
public SingleObjectPolicy::Data {
jsid propId_;
CompilerShape shape_;
CompilerGetterSetter getterSetter_;
MGuardHasGetterSetter(MDefinition* obj, jsid id, Shape* shape)
: MUnaryInstruction(classOpcode, obj), propId_(id), shape_(shape) {
MGuardHasGetterSetter(MDefinition* obj, jsid id, GetterSetter* gs)
: MUnaryInstruction(classOpcode, obj), propId_(id), getterSetter_(gs) {
setResultType(MIRType::Object);
setMovable();
setGuard();
@ -11963,7 +11963,7 @@ class MGuardHasGetterSetter : public MUnaryInstruction,
NAMED_OPERANDS((0, object))
jsid propId() const { return propId_; }
Shape* shape() const { return shape_; }
GetterSetter* getterSetter() const { return getterSetter_; }
AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::ObjectFields);
@ -11976,7 +11976,7 @@ class MGuardHasGetterSetter : public MUnaryInstruction,
if (ins->toGuardHasGetterSetter()->propId() != propId()) {
return false;
}
if (ins->toGuardHasGetterSetter()->shape() != shape()) {
if (ins->toGuardHasGetterSetter()->getterSetter() != getterSetter()) {
return false;
}
return congruentIfOperandsEqual(ins);

Просмотреть файл

@ -1926,13 +1926,9 @@ bool SetNativeDataPropertyPure(JSContext* cx, JSObject* obj, PropertyName* name,
}
bool ObjectHasGetterSetterPure(JSContext* cx, JSObject* objArg, jsid id,
Shape* propShape) {
GetterSetter* getterSetter) {
AutoUnsafeCallWithABI unsafe;
MOZ_ASSERT(propShape->propid() == id);
MOZ_ASSERT(propShape->hasGetterObject() || propShape->hasSetterObject());
// Window objects may require outerizing (passing the WindowProxy to the
// getter/setter), so we don't support them here.
if (MOZ_UNLIKELY(!objArg->is<NativeObject>() || IsWindow(objArg))) {
@ -1943,14 +1939,15 @@ bool ObjectHasGetterSetterPure(JSContext* cx, JSObject* objArg, jsid id,
while (true) {
if (Shape* shape = nobj->lastProperty()->search(cx, id)) {
if (shape == propShape) {
if (!shape->isAccessorDescriptor()) {
return false;
}
GetterSetter* actualGetterSetter = nobj->getGetterSetter(shape);
if (actualGetterSetter == getterSetter) {
return true;
}
if (shape->getterOrUndefined() == propShape->getterOrUndefined() &&
shape->setterOrUndefined() == propShape->setterOrUndefined()) {
return true;
}
return false;
return (actualGetterSetter->getter() == getterSetter->getter() &&
actualGetterSetter->setter() == getterSetter->setter());
}
// Property not found. Watch out for Class hooks.

Просмотреть файл

@ -560,8 +560,8 @@ bool HasNativeElementPure(JSContext* cx, NativeObject* obj, int32_t index,
bool SetNativeDataPropertyPure(JSContext* cx, JSObject* obj, PropertyName* name,
Value* val);
bool ObjectHasGetterSetterPure(JSContext* cx, JSObject* obj, jsid id,
Shape* propShape);
bool ObjectHasGetterSetterPure(JSContext* cx, JSObject* objArg, jsid id,
GetterSetter* getterSetter);
JSString* TypeOfObject(JSObject* obj, JSRuntime* rt);

Просмотреть файл

@ -449,14 +449,13 @@ bool WarpCacheIRTranspiler::emitGuardIsNotDOMProxy(ObjOperandId objId) {
return true;
}
bool WarpCacheIRTranspiler::emitGuardHasGetterSetter(ObjOperandId objId,
uint32_t idOffset,
uint32_t shapeOffset) {
bool WarpCacheIRTranspiler::emitGuardHasGetterSetter(
ObjOperandId objId, uint32_t idOffset, uint32_t getterSetterOffset) {
MDefinition* obj = getOperand(objId);
jsid id = idStubField(idOffset);
Shape* shape = shapeStubField(shapeOffset);
GetterSetter* gs = getterSetterStubField(getterSetterOffset);
auto* ins = MGuardHasGetterSetter::New(alloc(), obj, id, shape);
auto* ins = MGuardHasGetterSetter::New(alloc(), obj, id, gs);
add(ins);
setOperand(objId, ins);

Просмотреть файл

@ -3273,6 +3273,9 @@ void JSObject::dump(js::GenericPrinter& out) const {
if (nobj->hadElementsAccess()) {
out.put(" had_elements_access");
}
if (nobj->hadGetterSetterChange()) {
out.put(" had_getter_setter_change");
}
if (nobj->isIndexed()) {
out.put(" indexed");
}

Просмотреть файл

@ -25,6 +25,7 @@
#include "js/Value.h"
#include "util/Memory.h"
#include "vm/EqualityOperations.h" // js::SameValue
#include "vm/GetterSetter.h" // js::GetterSetter
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/TypedArrayObject.h"
@ -1280,6 +1281,41 @@ bool NativeObject::reshapeForShadowedProp(JSContext* cx,
return generateOwnShape(cx, obj);
}
static bool ChangeProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
HandleObject getter, HandleObject setter,
unsigned attrs, PropertyResult* existing) {
MOZ_ASSERT(existing);
Rooted<GetterSetter*> gs(cx);
// If we're redefining a getter/setter property but the getter and setter
// objects are still the same, use the existing GetterSetter.
if (existing->isNativeProperty()) {
Shape* prop = existing->shape();
if (prop->isAccessorDescriptor()) {
GetterSetter* current = obj->getGetterSetter(prop);
if (current->getter() == getter && current->setter() == setter) {
gs = current;
}
}
}
if (!gs) {
gs = GetterSetter::create(cx, getter, setter);
if (!gs) {
return false;
}
}
Shape* shape = NativeObject::putDataProperty(cx, obj, id, attrs);
if (!shape) {
return false;
}
obj->setSlot(shape->slot(), PrivateGCThingValue(gs));
return true;
}
// Whether we're adding a new property or changing an existing property (this
// can be either a property stored in the shape tree or a dense element).
enum class IsAddOrChange { Add, Change };
@ -1330,11 +1366,17 @@ static MOZ_ALWAYS_INLINE bool AddOrChangeProperty(
// the slower putProperty.
if constexpr (AddOrChange == IsAddOrChange::Add) {
if (desc.isAccessorDescriptor()) {
if (!NativeObject::addAccessorProperty(cx, obj, id, desc.getterObject(),
desc.setterObject(),
desc.attributes())) {
Rooted<GetterSetter*> gs(cx, GetterSetter::create(cx, desc.getterObject(),
desc.setterObject()));
if (!gs) {
return false;
}
Shape* shape = NativeObject::addDataProperty(
cx, obj, id, SHAPE_INVALID_SLOT, desc.attributes());
if (!shape) {
return false;
}
obj->initSlot(shape->slot(), PrivateGCThingValue(gs));
} else {
Shape* shape = NativeObject::addDataProperty(
cx, obj, id, SHAPE_INVALID_SLOT, desc.attributes());
@ -1345,9 +1387,8 @@ static MOZ_ALWAYS_INLINE bool AddOrChangeProperty(
}
} else {
if (desc.isAccessorDescriptor()) {
if (!NativeObject::putAccessorProperty(cx, obj, id, desc.getterObject(),
desc.setterObject(),
desc.attributes())) {
if (!ChangeProperty(cx, obj, id, desc.getterObject(), desc.setterObject(),
desc.attributes(), existing)) {
return false;
}
} else {

Просмотреть файл

@ -23,6 +23,7 @@
#include "js/shadow/Object.h" // JS::shadow::Object
#include "js/shadow/Zone.h" // JS::shadow::Zone
#include "js/Value.h"
#include "vm/GetterSetter.h"
#include "vm/JSObject.h"
#include "vm/Shape.h"
#include "vm/StringType.h"
@ -865,6 +866,13 @@ class NativeObject : public JSObject {
return hasFlag(ObjectFlag::HasInterestingSymbol);
}
static bool setHadGetterSetterChange(JSContext* cx, HandleNativeObject obj) {
return setFlag(cx, obj, ObjectFlag::HadGetterSetterChange);
}
bool hadGetterSetterChange() const {
return hasFlag(ObjectFlag::HadGetterSetterChange);
}
/*
* Grow or shrink slots immediately before changing the slot span.
* The number of allocated slots is not stored explicitly, and changes to
@ -1088,20 +1096,46 @@ class NativeObject : public JSObject {
getSlotAddressUnchecked(slot)->init(this, HeapSlot::Slot, slot, value);
}
// Returns the GetterSetter for an accessor property.
GetterSetter* getGetterSetter(Shape* shape) const {
MOZ_ASSERT(shape->isAccessorDescriptor());
return getSlot(shape->slot()).toGCThing()->as<GetterSetter>();
}
// Returns the (possibly nullptr) getter or setter object. The shape must be
// for an accessor property.
JSObject* getGetter(Shape* shape) const { return shape->getterObject(); }
JSObject* getSetter(Shape* shape) const { return shape->setterObject(); }
JSObject* getGetter(Shape* shape) const {
return getGetterSetter(shape)->getter();
}
JSObject* getSetter(Shape* shape) const {
return getGetterSetter(shape)->setter();
}
// Returns true if the property has a non-nullptr getter or setter object. The
// shape can be any property shape.
bool hasGetter(Shape* shape) const { return shape->hasGetterObject(); }
bool hasSetter(Shape* shape) const { return shape->hasSetterObject(); }
bool hasGetter(Shape* shape) const {
return shape->hasGetterValue() && getGetter(shape);
}
bool hasSetter(Shape* shape) const {
return shape->hasSetterValue() && getSetter(shape);
}
// If the property has a non-nullptr getter/setter, return it as ObjectValue.
// Else return |undefined|. The shape must be for an accessor property.
Value getGetterValue(Shape* shape) const { return shape->getterValue(); }
Value getSetterValue(Shape* shape) const { return shape->setterValue(); }
Value getGetterValue(Shape* shape) const {
MOZ_ASSERT(shape->hasGetterValue());
if (JSObject* getterObj = getGetter(shape)) {
return ObjectValue(*getterObj);
}
return UndefinedValue();
}
Value getSetterValue(Shape* shape) const {
MOZ_ASSERT(shape->hasSetterValue());
if (JSObject* setterObj = getSetter(shape)) {
return ObjectValue(*setterObj);
}
return UndefinedValue();
}
// MAX_FIXED_SLOTS is the biggest number of fixed slots our GC
// size classes will give an object.

Просмотреть файл

@ -108,20 +108,13 @@ template <MaybeAdding Adding>
inline Shape* Shape::new_(JSContext* cx, Handle<StackShape> other,
uint32_t nfixed) {
Shape* shape = other.isAccessorShape() ? js::Allocate<AccessorShape>(cx)
: js::Allocate<Shape>(cx);
Shape* shape = 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;
return new (shape) Shape(other, nfixed);
}
inline void Shape::updateBaseShapeAfterMovingGC() {
@ -131,70 +124,9 @@ inline void Shape::updateBaseShapeAfterMovingGC() {
}
}
static inline void GetterSetterPreWriteBarrier(AccessorShape* shape) {
if (shape->hasGetterObject()) {
PreWriteBarrier(shape->getterObject());
}
if (shape->hasSetterObject()) {
PreWriteBarrier(shape->setterObject());
}
}
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), getter_(other.getter), setter_(other.setter) {
MOZ_ASSERT(getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE);
GetterSetterPostWriteBarrier(this);
}
inline AccessorShape::AccessorShape(BaseShape* base, ObjectFlags objectFlags,
uint32_t nfixed)
: Shape(base, objectFlags, nfixed), getter_(nullptr), setter_(nullptr) {
MOZ_ASSERT(getAllocKind() == gc::AllocKind::ACCESSOR_SHAPE);
}
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);
}
new (this) Shape(child, nfixed);
this->immutableFlags |= IN_DICTIONARY;
MOZ_ASSERT(dictNext.isNone());

Просмотреть файл

@ -358,7 +358,7 @@ Shape* Shape::replaceLastProperty(JSContext* cx, ObjectFlags objectFlags,
/* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::getChildDataProperty(
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child) {
MOZ_ASSERT(child.isDataProperty());
MOZ_ASSERT(!child.isCustomDataProperty());
if (child.hasMissingSlot()) {
uint32_t slot;
@ -427,14 +427,14 @@ Shape* Shape::replaceLastProperty(JSContext* cx, ObjectFlags objectFlags,
/* static */ MOZ_ALWAYS_INLINE Shape* NativeObject::getChildAccessorProperty(
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child) {
MOZ_ASSERT(!child.isDataProperty());
MOZ_ASSERT(child.isCustomDataProperty());
// Accessor properties have no slot, but slot_ will reflect that of parent.
child.setSlot(parent->maybeSlot());
if (obj->inDictionaryMode()) {
MOZ_ASSERT(parent == obj->lastProperty());
Shape* shape = Allocate<AccessorShape>(cx);
Shape* shape = Allocate<Shape>(cx);
if (!shape) {
return nullptr;
}
@ -475,8 +475,7 @@ bool js::NativeObject::toDictionaryMode(JSContext* cx, HandleNativeObject obj) {
while (shape) {
MOZ_ASSERT(!shape->inDictionary());
Shape* dprop = shape->isAccessorShape() ? Allocate<AccessorShape>(cx)
: Allocate<Shape>(cx);
Shape* dprop = Allocate<Shape>(cx);
if (!dprop) {
ReportOutOfMemory(cx);
return false;
@ -635,7 +634,6 @@ Shape* NativeObject::addAccessorPropertyInternal(
Rooted<StackShape> child(cx, StackShape(last->base(), objectFlags, id,
SHAPE_INVALID_SLOT, attrs));
child.updateGetterSetter(getter, setter);
shape = getChildAccessorProperty(cx, obj, last, &child);
if (!shape) {
return nullptr;
@ -750,8 +748,7 @@ Shape* NativeObject::addEnumerableDataProperty(JSContext* cx,
MOZ_ASSERT(!child->inDictionary());
if (child->propidRaw() != id || child->objectFlags() != objectFlags ||
child->isAccessorShape() || child->attributes() != attrs ||
child->base() != lastProperty->base()) {
child->attributes() != attrs || child->base() != lastProperty->base()) {
break;
}
@ -942,17 +939,20 @@ Shape* NativeObject::putDataProperty(JSContext* cx, HandleNativeObject obj,
// If the caller wants to allocate a slot, but doesn't care which slot,
// copy the existing shape's slot into slot so we can match shape, if all
// other members match.
bool hadSlot = shape->isDataProperty();
uint32_t oldSlot = shape->maybeSlot();
uint32_t slot = hadSlot ? oldSlot : SHAPE_INVALID_SLOT;
uint32_t slot =
shape->isCustomDataProperty() ? SHAPE_INVALID_SLOT : shape->slot();
ObjectFlags objectFlags =
GetObjectFlagsForNewProperty(obj->lastProperty(), id, attrs, cx);
if (shape->isAccessorDescriptor()) {
objectFlags.setFlag(ObjectFlag::HadGetterSetterChange);
}
// Now that we've possibly preserved slot, check whether the property info and
// object flags match. If so, this is a redundant "put" and we can return
// without more work.
if (shape->matchesPropertyParamsAfterId(slot, attrs, nullptr, nullptr) &&
if (shape->matchesPropertyParamsAfterId(slot, attrs) &&
obj->lastProperty()->objectFlags() == objectFlags) {
return shape;
}
@ -961,7 +961,7 @@ Shape* NativeObject::putDataProperty(JSContext* cx, HandleNativeObject obj,
return nullptr;
}
MOZ_ASSERT_IF(shape->isDataProperty(), shape->slot() == slot);
MOZ_ASSERT_IF(!shape->isCustomDataProperty(), shape->slot() == slot);
if (obj->inDictionaryMode()) {
// Updating some property in a dictionary-mode object. Create a new
@ -993,7 +993,6 @@ Shape* NativeObject::putDataProperty(JSContext* cx, HandleNativeObject obj,
shape->setBase(obj->lastProperty()->base());
shape->setSlot(slot);
shape->attrs = uint8_t(attrs);
shape->immutableFlags &= ~Shape::ACCESSOR_SHAPE;
shape->immutableFlags |= Shape::IN_DICTIONARY;
} else {
// Updating the last property in a non-dictionary-mode object. Find an
@ -1012,8 +1011,6 @@ Shape* NativeObject::putDataProperty(JSContext* cx, HandleNativeObject obj,
}
MOZ_ASSERT(obj->lastProperty()->objectFlags() == objectFlags);
MOZ_ASSERT(shape->isDataProperty());
return shape;
}
@ -1053,7 +1050,7 @@ Shape* NativeObject::putAccessorProperty(JSContext* cx, HandleNativeObject obj,
AssertCanChangeAttrs(shape, attrs);
bool hadSlot = shape->isDataProperty();
bool hadSlot = !shape->isCustomDataProperty();
uint32_t oldSlot = shape->maybeSlot();
ObjectFlags objectFlags =
@ -1061,8 +1058,7 @@ Shape* NativeObject::putAccessorProperty(JSContext* cx, HandleNativeObject obj,
// Check whether the property info and object flags match. If so, this is a
// redundant "put" and we can return without more work.
if (shape->matchesPropertyParamsAfterId(SHAPE_INVALID_SLOT, attrs, getter,
setter) &&
if (shape->matchesPropertyParamsAfterId(SHAPE_INVALID_SLOT, attrs) &&
obj->lastProperty()->objectFlags() == objectFlags) {
return shape;
}
@ -1095,13 +1091,7 @@ Shape* NativeObject::putAccessorProperty(JSContext* cx, HandleNativeObject obj,
shape->setBase(obj->lastProperty()->base());
shape->setSlot(SHAPE_INVALID_SLOT);
shape->attrs = uint8_t(attrs);
shape->immutableFlags |= Shape::IN_DICTIONARY | Shape::ACCESSOR_SHAPE;
AccessorShape& accShape = shape->asAccessorShape();
GetterSetterPreWriteBarrier(&accShape);
accShape.getter_ = getter;
accShape.setter_ = setter;
GetterSetterPostWriteBarrier(&accShape);
shape->immutableFlags |= Shape::IN_DICTIONARY;
} else {
// Updating the last property in a non-dictionary-mode object. Find an
// alternate shared child of the last property's previous shape.
@ -1112,7 +1102,6 @@ Shape* NativeObject::putAccessorProperty(JSContext* cx, HandleNativeObject obj,
Rooted<StackShape> child(
cx, StackShape(obj->lastProperty()->base(), objectFlags, id,
SHAPE_INVALID_SLOT, attrs));
child.updateGetterSetter(getter, setter);
RootedShape parent(cx, shape->parent);
shape = getChildAccessorProperty(cx, obj, parent, &child);
if (!shape) {
@ -1152,6 +1141,21 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
return true;
}
// If we're removing an accessor property, ensure the HadGetterSetterChange
// object flag is set. This is necessary because the slot holding the
// GetterSetter can be changed indirectly by removing the property and then
// adding it back with a different GetterSetter value but the same shape.
if (shape->isAccessorDescriptor() && !obj->hadGetterSetterChange()) {
if (!NativeObject::setHadGetterSetterChange(cx, obj)) {
return false;
}
// Relookup shape/table/entry in case setHadGetterSetterChange changed them.
if (!Shape::search(cx, obj->lastProperty(), id, keep, shape.address(),
&table, &entry)) {
return false;
}
}
const bool removingLastProperty = (shape == obj->lastProperty());
/*
@ -1178,8 +1182,7 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
*/
RootedShape spare(cx);
if (obj->inDictionaryMode()) {
/* For simplicity, always allocate an accessor shape for now. */
spare = Allocate<AccessorShape>(cx);
spare = Allocate<Shape>(cx);
if (!spare) {
return false;
}
@ -1187,7 +1190,7 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
}
/* If shape has a slot, free its slot number. */
if (shape->isDataProperty()) {
if (!shape->isCustomDataProperty()) {
obj->freeSlot(cx, shape->slot());
}
@ -1283,22 +1286,13 @@ Shape* NativeObject::replaceWithNewEquivalentShape(JSContext* cx,
if (!newShape) {
RootedShape oldRoot(cx, oldShape);
bool allocAccessorShape = accessorShape || oldShape->isAccessorShape();
if (allocAccessorShape) {
newShape = Allocate<AccessorShape>(cx);
} else {
newShape = Allocate<Shape>(cx);
}
newShape = Allocate<Shape>(cx);
if (!newShape) {
return nullptr;
}
if (allocAccessorShape) {
new (newShape) AccessorShape(oldRoot->base(), ObjectFlags(), 0);
} else {
new (newShape) Shape(oldRoot->base(), ObjectFlags(), 0);
}
new (newShape) Shape(oldRoot->base(), ObjectFlags(), 0);
oldShape = oldRoot;
}
@ -1781,19 +1775,8 @@ void Shape::fixupShapeTreeAfterMovingGC() {
Shape* key = MaybeForwarded(e.front());
BaseShape* base = MaybeForwarded(key->base());
JSObject* getter = key->maybeGetterObject();
if (getter) {
getter = MaybeForwarded(getter);
}
JSObject* setter = key->maybeSetterObject();
if (setter) {
setter = MaybeForwarded(setter);
}
StackShape lookup(base, key->objectFlags(), key->propidRef(),
key->immutableFlags & Shape::SLOT_MASK, key->attrs);
lookup.updateGetterSetter(getter, setter);
e.rekeyFront(lookup, key);
}
}
@ -1806,60 +1789,6 @@ void Shape::fixupAfterMovingGC() {
}
}
void NurseryShapesRef::trace(JSTracer* trc) {
auto& shapes = zone_->nurseryShapes();
for (auto shape : shapes) {
shape->fixupGetterSetterForBarrier(trc);
}
shapes.clearAndFree();
}
void Shape::fixupGetterSetterForBarrier(JSTracer* trc) {
if (!hasGetterValue() && !hasSetterValue()) {
return;
}
JSObject* priorGetter = asAccessorShape().getterObject();
JSObject* priorSetter = asAccessorShape().setterObject();
if (!priorGetter && !priorSetter) {
return;
}
JSObject* postGetter = priorGetter;
JSObject* postSetter = priorSetter;
if (priorGetter) {
TraceManuallyBarrieredEdge(trc, &postGetter, "getterObj");
}
if (priorSetter) {
TraceManuallyBarrieredEdge(trc, &postSetter, "setterObj");
}
if (priorGetter == postGetter && priorSetter == postSetter) {
return;
}
if (parent && !parent->inDictionary() && parent->children.isShapeSet()) {
// Relocating the getterObj or setterObj will have changed our location in
// our parent's ShapeSet, so take care to update it. We must do this before
// we update the shape itself, since the shape is used to match the original
// entry in the hash set.
StackShape original(this);
StackShape updated(this);
updated.getter = postGetter;
updated.setter = postSetter;
ShapeSet* set = parent->children.toShapeSet();
MOZ_ALWAYS_TRUE(set->rekeyAs(original, updated, this));
}
asAccessorShape().getter_ = postGetter;
asAccessorShape().setter_ = postSetter;
MOZ_ASSERT_IF(
parent && !parent->inDictionary() && parent->children.isShapeSet(),
parent->children.toShapeSet()->has(StackShape(this)));
}
#ifdef DEBUG
void ShapeChildren::checkHasChild(Shape* child) const {
@ -1891,9 +1820,8 @@ void Shape::dump(js::GenericPrinter& out) const {
JSID_TO_SYMBOL(propid)->dump(out);
}
out.printf(" g/s %p/%p slot %d attrs %x ", maybeGetterObject(),
maybeSetterObject(), isDataProperty() ? int32_t(slot()) : -1,
attrs);
out.printf(" slot %d attrs %x ",
isCustomDataProperty() ? -1 : int32_t(slot()), attrs);
if (attrs) {
int first = 1;

Просмотреть файл

@ -666,6 +666,11 @@ enum class ObjectFlag : uint16_t {
// most proto chains. Code using this flag must check for "__proto__"
// property names separately.
HasNonWritableOrAccessorPropExclProto = 1 << 9,
// If set, the object either mutated or deleted an accessor property. This is
// used to invalidate IC/Warp code specializing on specific getter/setter
// objects. See also the SMDOC comment in vm/GetterSetter.h.
HadGetterSetterChange = 1 << 10,
};
using ObjectFlags = EnumFlags<ObjectFlag>;
@ -930,7 +935,7 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
MOZ_ASSERT_IF(p && !p->hasMissingSlot() && !inDictionary(),
p->maybeSlot() <= maybeSlot());
MOZ_ASSERT_IF(p && !inDictionary(),
isDataProperty() == (p->maybeSlot() != maybeSlot()));
!isCustomDataProperty() == (p->maybeSlot() != maybeSlot()));
parent = p;
}
@ -1100,63 +1105,21 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
public:
bool inDictionary() const { return immutableFlags & IN_DICTIONARY; }
inline JSObject* maybeGetterObject() const;
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 JSObject* maybeSetterObject() const;
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->objectFlags(),
other->maybeSlot(), other->attrs,
other->maybeGetterObject(),
other->maybeSetterObject());
other->maybeSlot(), other->attrs);
}
inline bool matches(const StackShape& other) const;
bool matchesParamsAfterId(BaseShape* base, ObjectFlags aobjectFlags,
uint32_t aslot, unsigned aattrs, JSObject* getter,
JSObject* setter) const {
uint32_t aslot, unsigned aattrs) const {
return base == this->base() && objectFlags() == aobjectFlags &&
matchesPropertyParamsAfterId(aslot, aattrs, getter, setter);
matchesPropertyParamsAfterId(aslot, aattrs);
}
bool matchesPropertyParamsAfterId(uint32_t aslot, unsigned aattrs,
JSObject* getter, JSObject* setter) const {
return maybeSlot() == aslot && attrs == aattrs &&
maybeGetterObject() == getter && maybeSetterObject() == setter;
bool matchesPropertyParamsAfterId(uint32_t aslot, unsigned aattrs) const {
return maybeSlot() == aslot && attrs == aattrs;
}
// Note: this returns true only for plain data properties with a slot. Returns
@ -1170,7 +1133,8 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
return isDataProperty(attrs);
}
uint32_t slot() const {
MOZ_ASSERT(isDataProperty() && !hasMissingSlot());
MOZ_ASSERT(!isCustomDataProperty());
MOZ_ASSERT(!hasMissingSlot());
return maybeSlot();
}
uint32_t maybeSlot() const { return immutableFlags & SLOT_MASK; }
@ -1326,7 +1290,6 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
void fixupAfterMovingGC();
void fixupGetterSetterForBarrier(JSTracer* trc);
void updateBaseShapeAfterMovingGC();
// For JIT usage.
@ -1363,23 +1326,7 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
};
/* Fat Shape used for accessor properties. */
class AccessorShape : public Shape {
friend class Shape;
friend class NativeObject;
// If hasGetterValue(), the getter object or null.
JSObject* getter_;
// If hasSetterValue(), the setter object or null.
JSObject* setter_;
public:
/* Get a shape identical to this one, without parent/children information. */
inline AccessorShape(const StackShape& other, uint32_t nfixed);
inline AccessorShape(BaseShape* base, ObjectFlags objectFlags,
uint32_t nfixed);
};
class AccessorShape : public Shape {};
struct EmptyShape : public js::Shape {
EmptyShape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed)
@ -1488,8 +1435,6 @@ struct StackShape {
/* For performance, StackShape only roots when absolutely necessary. */
BaseShape* base;
jsid propid;
JSObject* getter;
JSObject* setter;
uint32_t immutableFlags;
ObjectFlags objectFlags;
uint8_t attrs;
@ -1499,8 +1444,6 @@ struct StackShape {
uint32_t slot, unsigned attrs)
: base(base),
propid(propid),
getter(nullptr),
setter(nullptr),
immutableFlags(slot),
objectFlags(objectFlags),
attrs(uint8_t(attrs)),
@ -1513,32 +1456,21 @@ struct StackShape {
explicit StackShape(Shape* shape)
: base(shape->base()),
propid(shape->propidRef()),
getter(shape->maybeGetterObject()),
setter(shape->maybeSetterObject()),
immutableFlags(shape->immutableFlags),
objectFlags(shape->objectFlags()),
attrs(shape->attrs),
mutableFlags(shape->mutableFlags) {}
void updateGetterSetter(JSObject* getter, JSObject* setter) {
if (getter || setter || (attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
immutableFlags |= Shape::ACCESSOR_SHAPE;
} else {
immutableFlags &= ~Shape::ACCESSOR_SHAPE;
}
this->getter = getter;
this->setter = setter;
}
bool isDataProperty() const {
MOZ_ASSERT(!JSID_IS_EMPTY(propid));
return Shape::isDataProperty(attrs);
}
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
bool isCustomDataProperty() const { return attrs & JSPROP_CUSTOM_DATA_PROP; }
uint32_t slot() const {
MOZ_ASSERT(isDataProperty() && !hasMissingSlot());
MOZ_ASSERT(!hasMissingSlot());
return maybeSlot();
}
uint32_t maybeSlot() const { return immutableFlags & Shape::SLOT_MASK; }
@ -1548,15 +1480,11 @@ struct StackShape {
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, objectFlags.toRaw(), attrs,
maybeSlot(), getter, setter));
hash,
mozilla::HashGeneric(base, objectFlags.toRaw(), attrs, maybeSlot()));
}
// StructGCPolicy implementation.
@ -1571,11 +1499,11 @@ class WrappedPtrOperations<StackShape, Wrapper> {
public:
bool isDataProperty() const { return ss().isDataProperty(); }
bool isCustomDataProperty() const { return ss().isCustomDataProperty(); }
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; }
ObjectFlags objectFlags() const { return ss().objectFlags; }
jsid propid() const { return ss().propid; }
@ -1587,9 +1515,6 @@ class MutableWrappedPtrOperations<StackShape, Wrapper>
StackShape& ss() { return static_cast<Wrapper*>(this)->get(); }
public:
void updateGetterSetter(JSObject* getter, JSObject* setter) {
ss().updateGetterSetter(getter, setter);
}
void setSlot(uint32_t slot) { ss().setSlot(slot); }
void setBase(BaseShape* base) { ss().base = base; }
void setAttrs(uint8_t attrs) { ss().attrs = attrs; }
@ -1608,29 +1533,11 @@ inline Shape::Shape(const StackShape& other, uint32_t nfixed)
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(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed)
: CellWithTenuredGCPointer(base),
propid_(JSID_EMPTY),
@ -1643,24 +1550,6 @@ inline Shape::Shape(BaseShape* base, ObjectFlags objectFlags, uint32_t nfixed)
children.setNone();
}
inline JSObject* Shape::maybeGetterObject() const {
return isAccessorShape() ? asAccessorShape().getter_ : nullptr;
}
inline JSObject* Shape::maybeSetterObject() const {
return isAccessorShape() ? asAccessorShape().setter_ : nullptr;
}
inline JSObject* Shape::getterObject() const {
MOZ_ASSERT(hasGetterValue());
return asAccessorShape().getter_;
}
inline JSObject* Shape::setterObject() const {
MOZ_ASSERT(hasSetterValue());
return asAccessorShape().setter_;
}
inline Shape* Shape::searchLinear(jsid id) {
for (Shape* shape = this; shape;) {
if (shape->propidRef() == id) {
@ -1675,7 +1564,7 @@ inline Shape* Shape::searchLinear(jsid id) {
inline bool Shape::matches(const StackShape& other) const {
return propid_.get() == other.propid &&
matchesParamsAfterId(other.base, other.objectFlags, other.maybeSlot(),
other.attrs, other.getter, other.setter);
other.attrs);
}
template <MaybeAdding Adding>