Bug 1674108 part 1 - Store dense and TypedArray index in PropertyResult. r=jonco

This refactors PropertyResult so that we can also store indexes. This will help
us support typed arrays larger than 2 GB because it lets us replace JSID_TO_INT
(limited to INT32_MAX) with the size_t we stored.

Differential Revision: https://phabricator.services.mozilla.com/D95141
This commit is contained in:
Jan de Mooij 2020-10-30 14:43:54 +00:00
parent 3b35332b4f
commit 2a0dab8062
6 changed files with 174 additions and 130 deletions

View File

@ -227,54 +227,67 @@ class ObjectOpResult {
};
class PropertyResult {
union {
js::Shape* shape_;
uintptr_t bits_;
enum class Kind : uint8_t {
NotFound,
NativeProperty,
NonNativeProperty,
DenseElement,
TypedArrayElement,
};
static const uintptr_t NotFound = 0;
static const uintptr_t NonNativeProperty = 1;
static const uintptr_t DenseOrTypedArrayElement = 1;
union {
// Set if kind is NativeProperty.
js::Shape* shape_;
// Set if kind is DenseElement.
uint32_t denseIndex_;
// Set if kind is TypedArrayElement.
size_t typedArrayIndex_;
};
Kind kind_ = Kind::NotFound;
public:
PropertyResult() : bits_(NotFound) {}
explicit PropertyResult(js::Shape* propertyShape) : shape_(propertyShape) {
MOZ_ASSERT(!isFound() || isNativeProperty());
}
PropertyResult() = default;
explicit operator bool() const { return isFound(); }
bool isFound() const { return bits_ != NotFound; }
bool isNonNativeProperty() const { return bits_ == NonNativeProperty; }
bool isDenseOrTypedArrayElement() const {
return bits_ == DenseOrTypedArrayElement;
}
bool isNativeProperty() const { return isFound() && !isNonNativeProperty(); }
js::Shape* maybeShape() const {
MOZ_ASSERT(!isNonNativeProperty());
return isFound() ? shape_ : nullptr;
}
bool isFound() const { return kind_ != Kind::NotFound; }
bool isNonNativeProperty() const { return kind_ == Kind::NonNativeProperty; }
bool isDenseElement() const { return kind_ == Kind::DenseElement; }
bool isTypedArrayElement() const { return kind_ == Kind::TypedArrayElement; }
bool isNativeProperty() const { return kind_ == Kind::NativeProperty; }
js::Shape* shape() const {
MOZ_ASSERT(isNativeProperty());
return shape_;
}
void setNotFound() { bits_ = NotFound; }
void setNativeProperty(js::Shape* propertyShape) {
shape_ = propertyShape;
MOZ_ASSERT(isNativeProperty());
uint32_t denseElementIndex() const {
MOZ_ASSERT(isDenseElement());
return denseIndex_;
}
void setNonNativeProperty() { bits_ = NonNativeProperty; }
size_t typedArrayElementIndex() const {
MOZ_ASSERT(isTypedArrayElement());
return typedArrayIndex_;
}
void setDenseOrTypedArrayElement() { bits_ = DenseOrTypedArrayElement; }
void setNotFound() { kind_ = Kind::NotFound; }
void setNativeProperty(js::Shape* propertyShape) {
kind_ = Kind::NativeProperty;
shape_ = propertyShape;
}
void setNonNativeProperty() { kind_ = Kind::NonNativeProperty; }
void setDenseElement(uint32_t index) {
kind_ = Kind::DenseElement;
denseIndex_ = index;
}
void setTypedArrayElement(size_t index) {
kind_ = Kind::TypedArrayElement;
typedArrayIndex_ = index;
}
void trace(JSTracer* trc);
};
@ -292,14 +305,15 @@ class WrappedPtrOperations<JS::PropertyResult, Wrapper> {
public:
bool isFound() const { return value().isFound(); }
explicit operator bool() const { return bool(value()); }
js::Shape* maybeShape() const { return value().maybeShape(); }
js::Shape* shape() const { return value().shape(); }
uint32_t denseElementIndex() const { return value().denseElementIndex(); }
size_t typedArrayElementIndex() const {
return value().typedArrayElementIndex();
}
bool isNativeProperty() const { return value().isNativeProperty(); }
bool isNonNativeProperty() const { return value().isNonNativeProperty(); }
bool isDenseOrTypedArrayElement() const {
return value().isDenseOrTypedArrayElement();
}
js::Shape* asTaggedShape() const { return value().asTaggedShape(); }
bool isDenseElement() const { return value().isDenseElement(); }
bool isTypedArrayElement() const { return value().isTypedArrayElement(); }
};
template <class Wrapper>
@ -311,7 +325,10 @@ class MutableWrappedPtrOperations<JS::PropertyResult, Wrapper>
void setNotFound() { value().setNotFound(); }
void setNativeProperty(js::Shape* shape) { value().setNativeProperty(shape); }
void setNonNativeProperty() { value().setNonNativeProperty(); }
void setDenseOrTypedArrayElement() { value().setDenseOrTypedArrayElement(); }
void setDenseElement(uint32_t index) { value().setDenseElement(index); }
void setTypedArrayElement(size_t index) {
value().setTypedArrayElement(index);
}
};
} // namespace js

View File

@ -485,13 +485,12 @@ static bool IsCacheableProtoChain(JSObject* obj, JSObject* holder) {
}
static bool IsCacheableGetPropReadSlot(JSObject* obj, JSObject* holder,
PropertyResult prop) {
if (!prop || !IsCacheableProtoChain(obj, holder)) {
Shape* shape) {
if (!shape->isDataProperty()) {
return false;
}
Shape* shape = prop.shape();
if (!shape->isDataProperty()) {
if (!IsCacheableProtoChain(obj, holder)) {
return false;
}
@ -508,7 +507,9 @@ enum NativeGetPropCacheability {
static NativeGetPropCacheability IsCacheableGetPropCall(JSObject* obj,
JSObject* holder,
Shape* shape) {
if (!shape || !IsCacheableProtoChain(obj, holder)) {
MOZ_ASSERT(shape);
if (!IsCacheableProtoChain(obj, holder)) {
return CanAttachNone;
}
@ -606,10 +607,7 @@ static bool IsCacheableNoProperty(JSContext* cx, JSObject* obj,
JSObject* holder, Shape* shape, jsid id,
jsbytecode* pc,
GetPropertyResultFlags resultFlags) {
if (shape) {
return false;
}
MOZ_ASSERT(!shape);
MOZ_ASSERT(!holder);
// Idempotent ICs may only attach missing-property stubs if undefined
@ -645,25 +643,28 @@ static NativeGetPropCacheability CanAttachNativeGetProp(
}
MOZ_ASSERT(!holder);
if (baseHolder) {
if (!baseHolder->isNative()) {
return CanAttachNone;
}
if (prop.isNativeProperty()) {
MOZ_ASSERT(baseHolder);
holder.set(&baseHolder->as<NativeObject>());
}
shape.set(prop.maybeShape());
shape.set(prop.shape());
if (IsCacheableGetPropReadSlot(obj, holder, prop)) {
return CanAttachReadSlot;
if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
return CanAttachReadSlot;
}
// Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
return IsCacheableGetPropCall(obj, holder, shape);
}
return CanAttachNone;
}
if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags)) {
return CanAttachReadSlot;
}
// Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
return IsCacheableGetPropCall(obj, holder, shape);
if (!prop.isFound()) {
if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags)) {
return CanAttachReadSlot;
}
}
return CanAttachNone;
@ -2749,8 +2750,7 @@ AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
// prototype. Ignore the global lexical scope as it doesn't figure
// into the prototype chain. We guard on the global lexical
// scope's shape independently.
if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder,
PropertyResult(shape))) {
if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder, shape)) {
return AttachDecision::NoAction;
}
@ -2889,7 +2889,7 @@ AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
}
holder = &env->as<NativeObject>();
if (!IsCacheableGetPropReadSlot(holder, holder, PropertyResult(shape))) {
if (!IsCacheableGetPropReadSlot(holder, holder, shape)) {
return AttachDecision::NoAction;
}
if (holder->getSlot(shape->slot()).isMagic()) {
@ -3767,7 +3767,9 @@ void SetPropIRGenerator::trackAttached(const char* name) {
static bool IsCacheableSetPropCallNative(JSObject* obj, JSObject* holder,
Shape* shape) {
if (!shape || !IsCacheableProtoChain(obj, holder)) {
MOZ_ASSERT(shape);
if (!IsCacheableProtoChain(obj, holder)) {
return false;
}
@ -3797,7 +3799,9 @@ static bool IsCacheableSetPropCallNative(JSObject* obj, JSObject* holder,
static bool IsCacheableSetPropCallScripted(JSObject* obj, JSObject* holder,
Shape* shape) {
if (!shape || !IsCacheableProtoChain(obj, holder)) {
MOZ_ASSERT(shape);
if (!IsCacheableProtoChain(obj, holder)) {
return false;
}
@ -3833,11 +3837,11 @@ static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, HandleObject obj,
return false;
}
if (prop.isNonNativeProperty()) {
if (!prop.isNativeProperty()) {
return false;
}
propShape.set(prop.maybeShape());
propShape.set(prop.shape());
if (!IsCacheableSetPropCallScripted(obj, holder, propShape) &&
!IsCacheableSetPropCallNative(obj, holder, propShape)) {
return false;
@ -4756,7 +4760,7 @@ AttachDecision InstanceOfIRGenerator::tryAttachStub() {
jsid hasInstanceID = SYMBOL_TO_JSID(cx_->wellKnownSymbols().hasInstance);
if (!LookupPropertyPure(cx_, fun, hasInstanceID, &hasInstanceHolder,
&hasInstanceProp) ||
!hasInstanceProp.isFound() || hasInstanceProp.isNonNativeProperty()) {
!hasInstanceProp.isNativeProperty()) {
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}

View File

@ -2369,10 +2369,12 @@ bool js::LookupOwnPropertyPure(JSContext* cx, JSObject* obj, jsid id,
if (obj->isNative()) {
// Search for a native dense element, typed array element, or property.
if (JSID_IS_INT(id) &&
obj->as<NativeObject>().containsDenseElement(JSID_TO_INT(id))) {
propp->setDenseOrTypedArrayElement();
return true;
if (JSID_IS_INT(id)) {
uint32_t index = JSID_TO_INT(id);
if (obj->as<NativeObject>().containsDenseElement(index)) {
propp->setDenseElement(index);
return true;
}
}
if (obj->is<TypedArrayObject>()) {
@ -2384,7 +2386,7 @@ bool js::LookupOwnPropertyPure(JSContext* cx, JSObject* obj, jsid id,
if (index.inspect()) {
if (index.inspect().value() < obj->as<TypedArrayObject>().length()) {
propp->setDenseOrTypedArrayElement();
propp->setTypedArrayElement(index.inspect().value());
} else {
propp->setNotFound();
if (isTypedArrayOutOfRange) {
@ -2421,13 +2423,13 @@ bool js::LookupOwnPropertyPure(JSContext* cx, JSObject* obj, jsid id,
static inline bool NativeGetPureInline(NativeObject* pobj, jsid id,
PropertyResult prop, Value* vp,
JSContext* cx) {
if (prop.isDenseOrTypedArrayElement()) {
// For simplicity we ignore the TypedArray with string index case.
if (!JSID_IS_INT(id)) {
return false;
}
return pobj->getDenseOrTypedArrayElement<NoGC>(cx, JSID_TO_INT(id), vp);
if (prop.isDenseElement()) {
*vp = pobj->getDenseElement(prop.denseElementIndex());
return true;
}
if (prop.isTypedArrayElement()) {
size_t idx = prop.typedArrayElementIndex();
return pobj->as<TypedArrayObject>().getElement<NoGC>(cx, idx, vp);
}
// Fail if we have a custom getter.
@ -2477,7 +2479,9 @@ bool js::GetOwnPropertyPure(JSContext* cx, JSObject* obj, jsid id, Value* vp,
static inline bool NativeGetGetterPureInline(PropertyResult prop,
JSFunction** fp) {
if (!prop.isDenseOrTypedArrayElement() && prop.shape()->hasGetterObject()) {
MOZ_ASSERT(prop.isNativeProperty());
if (prop.shape()->hasGetterObject()) {
Shape* shape = prop.shape();
if (shape->getterObject()->is<JSFunction>()) {
*fp = &shape->getterObject()->as<JSFunction>();
@ -2531,8 +2535,7 @@ bool js::GetOwnNativeGetterPure(JSContext* cx, JSObject* obj, jsid id,
return false;
}
if (!prop || prop.isDenseOrTypedArrayElement() ||
!prop.shape()->hasGetterObject()) {
if (!prop || !prop.isNativeProperty() || !prop.shape()->hasGetterObject()) {
return true;
}
@ -2557,8 +2560,7 @@ bool js::HasOwnDataPropertyPure(JSContext* cx, JSObject* obj, jsid id,
return false;
}
*result = prop && !prop.isDenseOrTypedArrayElement() &&
prop.shape()->isDataProperty();
*result = prop && prop.isNativeProperty() && prop.shape()->isDataProperty();
return true;
}

View File

@ -741,9 +741,12 @@ static MOZ_ALWAYS_INLINE bool CallResolveOp(JSContext* cx,
MOZ_ASSERT_IF(obj->getClass()->getMayResolve(),
obj->getClass()->getMayResolve()(cx->names(), id, obj));
if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) {
propp.setDenseOrTypedArrayElement();
return true;
if (JSID_IS_INT(id)) {
uint32_t index = JSID_TO_INT(id);
if (obj->containsDenseElement(index)) {
propp.setDenseElement(index);
return true;
}
}
MOZ_ASSERT(!obj->is<TypedArrayObject>());
@ -765,10 +768,13 @@ static MOZ_ALWAYS_INLINE bool LookupOwnPropertyInline(
typename MaybeRooted<PropertyResult, allowGC>::MutableHandleType propp,
bool* donep) {
// Check for a native dense element.
if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) {
propp.setDenseOrTypedArrayElement();
*donep = true;
return true;
if (JSID_IS_INT(id)) {
uint32_t index = JSID_TO_INT(id);
if (obj->containsDenseElement(index)) {
propp.setDenseElement(index);
*donep = true;
return true;
}
}
// Check for a typed array element. Integer lookups always finish here
@ -784,9 +790,9 @@ static MOZ_ALWAYS_INLINE bool LookupOwnPropertyInline(
}
if (index.inspect()) {
if (index.inspect().value() <
obj->template as<TypedArrayObject>().length()) {
propp.setDenseOrTypedArrayElement();
uint64_t idx = index.inspect().value();
if (idx < obj->template as<TypedArrayObject>().length()) {
propp.setTypedArrayElement(idx);
} else {
propp.setNotFound();
}
@ -840,9 +846,12 @@ static inline MOZ_MUST_USE bool NativeLookupOwnPropertyNoResolve(
JSContext* cx, HandleNativeObject obj, HandleId id,
MutableHandle<PropertyResult> result) {
// Check for a native dense element.
if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) {
result.setDenseOrTypedArrayElement();
return true;
if (JSID_IS_INT(id)) {
uint32_t index = JSID_TO_INT(id);
if (obj->containsDenseElement(index)) {
result.setDenseElement(index);
return true;
}
}
// Check for a typed array element.
@ -852,7 +861,7 @@ static inline MOZ_MUST_USE bool NativeLookupOwnPropertyNoResolve(
if (index) {
if (index.value() < obj->as<TypedArrayObject>().length()) {
result.setDenseOrTypedArrayElement();
result.setTypedArrayElement(index.value());
} else {
result.setNotFound();
}

View File

@ -1672,9 +1672,15 @@ static MOZ_ALWAYS_INLINE bool GetExistingProperty(
static bool GetExistingPropertyValue(JSContext* cx, HandleNativeObject obj,
HandleId id, Handle<PropertyResult> prop,
MutableHandleValue vp) {
if (prop.isDenseOrTypedArrayElement()) {
return obj->getDenseOrTypedArrayElement<CanGC>(cx, JSID_TO_INT(id), vp);
if (prop.isDenseElement()) {
vp.set(obj->getDenseElement(prop.denseElementIndex()));
return true;
}
if (prop.isTypedArrayElement()) {
size_t idx = prop.typedArrayElementIndex();
return obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx, vp);
}
MOZ_ASSERT(!cx->isHelperThreadContext());
MOZ_ASSERT(prop.shape()->propid() == id);
@ -1713,8 +1719,7 @@ static bool DefinePropertyIsRedundant(JSContext* cx, HandleNativeObject obj,
if (desc.hasValue()) {
// Get the current value of the existing property.
RootedValue currentValue(cx);
if (!prop.isDenseOrTypedArrayElement() &&
prop.shape()->isDataProperty()) {
if (prop.isNativeProperty() && prop.shape()->isDataProperty()) {
// Inline GetExistingPropertyValue in order to omit a type
// correctness assertion that's too strict for this particular
// call site. For details, see bug 1125624 comments 13-16.
@ -1733,13 +1738,13 @@ static bool DefinePropertyIsRedundant(JSContext* cx, HandleNativeObject obj,
}
GetterOp existingGetterOp =
prop.isDenseOrTypedArrayElement() ? nullptr : prop.shape()->getter();
prop.isNativeProperty() ? prop.shape()->getter() : nullptr;
if (desc.getter() != existingGetterOp) {
return true;
}
SetterOp existingSetterOp =
prop.isDenseOrTypedArrayElement() ? nullptr : prop.shape()->setter();
prop.isNativeProperty() ? prop.shape()->setter() : nullptr;
if (desc.setter() != existingSetterOp) {
return true;
}
@ -1887,7 +1892,7 @@ bool js::NativeDefineProperty(JSContext* cx, HandleNativeObject obj,
// type for this property that doesn't match the value in the slot.
// Update the type here, even though this DefineProperty call is
// otherwise a no-op. (See bug 1125624 comment 13.)
if (!prop.isDenseOrTypedArrayElement() && desc.hasValue()) {
if (prop.isNativeProperty() && desc.hasValue()) {
UpdateShapeTypeAndValue(cx, obj, prop.shape(), id, desc.value());
}
return result.succeed();
@ -2344,9 +2349,12 @@ bool js::NativeGetOwnPropertyDescriptor(
desc.setGetter(nullptr);
desc.setSetter(nullptr);
if (prop.isDenseOrTypedArrayElement()) {
if (!obj->getDenseOrTypedArrayElement<CanGC>(cx, JSID_TO_INT(id),
desc.value())) {
if (prop.isDenseElement()) {
desc.value().set(obj->getDenseElement(prop.denseElementIndex()));
} else if (prop.isTypedArrayElement()) {
size_t idx = prop.typedArrayElementIndex();
if (!obj->as<TypedArrayObject>().getElement<CanGC>(cx, idx,
desc.value())) {
return false;
}
} else {
@ -2557,9 +2565,14 @@ static MOZ_ALWAYS_INLINE bool NativeGetPropertyInline(
if (prop) {
// Steps 5-8. Special case for dense elements because
// GetExistingProperty doesn't support those.
if (prop.isDenseOrTypedArrayElement()) {
return pobj->template getDenseOrTypedArrayElement<allowGC>(
cx, JSID_TO_INT(id), vp);
if (prop.isDenseElement()) {
vp.set(pobj->getDenseElement(prop.denseElementIndex()));
return true;
}
if (prop.isTypedArrayElement()) {
size_t idx = prop.typedArrayElementIndex();
auto* tarr = &pobj->template as<TypedArrayObject>();
return tarr->template getElement<allowGC>(cx, idx, vp);
}
typename MaybeRooted<Shape*, allowGC>::RootType shape(cx, prop.shape());
@ -2866,7 +2879,7 @@ static bool SetExistingProperty(JSContext* cx, HandleId id, HandleValue v,
Handle<PropertyResult> prop,
ObjectOpResult& result) {
// Step 5 for dense elements.
if (prop.isDenseOrTypedArrayElement()) {
if (prop.isDenseElement() || prop.isTypedArrayElement()) {
// Step 5.a.
if (pobj->denseElementsAreFrozen()) {
return result.fail(JSMSG_READ_ONLY);
@ -2874,14 +2887,13 @@ static bool SetExistingProperty(JSContext* cx, HandleId id, HandleValue v,
// Pure optimization for the common case:
if (receiver.isObject() && pobj == &receiver.toObject()) {
uint32_t index = JSID_TO_INT(id);
if (pobj->is<TypedArrayObject>()) {
if (prop.isTypedArrayElement()) {
Rooted<TypedArrayObject*> tobj(cx, &pobj->as<TypedArrayObject>());
return SetTypedArrayElement(cx, tobj, index, v, result);
size_t idx = prop.typedArrayElementIndex();
return SetTypedArrayElement(cx, tobj, idx, v, result);
}
return SetDenseElement(cx, pobj, index, v, result);
return SetDenseElement(cx, pobj, prop.denseElementIndex(), v, result);
}
// Steps 5.b-f.
@ -3047,16 +3059,16 @@ bool js::NativeDeleteProperty(JSContext* cx, HandleNativeObject obj,
return true;
}
// Step 5.
if (prop.isDenseOrTypedArrayElement()) {
// Typed array elements are non-configurable.
MOZ_ASSERT(!obj->is<TypedArrayObject>());
// Typed array elements are non-configurable.
MOZ_ASSERT(!prop.isTypedArrayElement());
// Step 5.
if (prop.isDenseElement()) {
if (!obj->maybeCopyElementsForWrite(cx)) {
return false;
}
obj->setDenseElementHole(cx, JSID_TO_INT(id));
obj->setDenseElementHole(cx, prop.denseElementIndex());
} else {
if (!NativeObject::removeProperty(cx, obj, id)) {
return false;

View File

@ -277,12 +277,12 @@ 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;
}
if (prop.isDenseElement()) {
return obj->as<NativeObject>().getElementsHeader()->elementAttributes();
}
if (prop.isTypedArrayElement()) {
return JSPROP_ENUMERATE | JSPROP_PERMANENT;
}
return prop.shape()->attributes();
}