LibJS: Add inline caching for adding new own properties to objects

We already had IC support in PutById for the following cases:
- Changing an existing own property
- Calling a setter located in the prototype chain

This was enough to speed up code where structurally identical objects
(same shape) are processed in a loop:
```js
const arr = [{ a: 1 }, { a: 2 }, { a: 3 }];
for (let obj of arr) {
    obj.a += 1;
}
```

However, creating structurally identical objects in a loop was still
slow:
```js
for (let i = 0; i < 10_000_000; i++) {
    const o = {};
    o.a = 1;
    o.b = 2;
    o.c = 3;
}
```

This change addresses that by adding a new IC type that caches both the
source and target shapes, allowing property additions to be fast-pathed
by directly jumping to the shape that already includes the new property.
This commit is contained in:
Aliaksandr Kalenik 2025-09-15 17:23:39 +02:00 committed by Andreas Kling
parent a54215c07d
commit d45f8a3081
Notes: github-actions[bot] 2025-09-17 10:45:37 +00:00
21 changed files with 162 additions and 75 deletions

View file

@ -28,6 +28,16 @@ namespace JS::Bytecode {
struct PropertyLookupCache {
static constexpr size_t max_number_of_shapes_to_remember = 4;
struct Entry {
enum class Type {
Empty,
AddOwnProperty,
ChangeOwnProperty,
GetOwnProperty,
ChangePropertyInPrototypeChain,
GetPropertyInPrototypeChain,
};
Type type { Type::Empty };
WeakPtr<Shape> from_shape;
WeakPtr<Shape> shape;
Optional<u32> property_offset;
WeakPtr<Object> prototype;

View file

@ -1023,7 +1023,7 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<IdentifierTableIndex>
}
}
CacheablePropertyMetadata cacheable_metadata;
CacheableGetPropertyMetadata cacheable_metadata;
auto value = TRY(base_obj->internal_get(executable.get_identifier(property), this_value, &cacheable_metadata));
// If internal_get() caused object's shape change, we can no longer be sure
@ -1037,11 +1037,11 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<IdentifierTableIndex>
cache.entries[0] = {};
return cache.entries[0];
};
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
auto& entry = get_cache_slot();
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
} else if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetPropertyInPrototypeChain) {
auto& entry = get_cache_slot();
entry.shape = &base_obj->shape();
entry.property_offset = cacheable_metadata.property_offset.value();
@ -1192,9 +1192,9 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
}
if (TRY(binding_object.has_property(identifier))) {
CacheablePropertyMetadata cacheable_metadata;
CacheableGetPropertyMetadata cacheable_metadata;
auto value = TRY(binding_object.internal_get(identifier, js_undefined(), &cacheable_metadata));
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
cache.entries[0].shape = shape;
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
}
@ -1236,10 +1236,16 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
break;
}
case Op::PropertyKind::KeyValue: {
auto& shape = object->shape();
auto this_value_object = MUST(this_value.to_object(vm));
auto& from_shape = this_value_object->shape();
if (caches) {
for (auto& cache : caches->entries) {
if (cache.prototype) {
switch (cache.type) {
case PropertyLookupCache::Entry::Type::Empty:
break;
case PropertyLookupCache::Entry::Type::ChangePropertyInPrototypeChain: {
if (!cache.prototype)
break;
// OPTIMIZATION: If the prototype chain hasn't been mutated in a way that would invalidate the cache, we can use it.
bool can_use_cache = [&]() -> bool {
if (&object->shape() != cache.shape)
@ -1257,7 +1263,11 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
return {};
}
}
} else if (cache.shape == &object->shape()) {
break;
}
case PropertyLookupCache::Entry::Type::ChangeOwnProperty: {
if (cache.shape != &object->shape())
break;
auto value_in_object = object->get_direct(cache.property_offset.value());
if (value_in_object.is_accessor()) {
TRY(call(vm, value_in_object.as_accessor().setter(), this_value, value));
@ -1266,32 +1276,74 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
}
return {};
}
case PropertyLookupCache::Entry::Type::AddOwnProperty: {
// OPTIMIZATION: If the object's shape is the same as the one cached before adding the new property, we can
// reuse the resulting shape from the cache.
if (cache.from_shape != &object->shape())
break;
if (!cache.shape)
break;
// The cache is invalid if the prototype chain has been mutated, since such a mutation could have added a setter for the property.
if (cache.prototype_chain_validity && !cache.prototype_chain_validity->is_valid())
break;
object->unsafe_set_shape(*cache.shape);
object->put_direct(*cache.property_offset, value);
return {};
}
default:
VERIFY_NOT_REACHED();
}
}
}
CacheablePropertyMetadata cacheable_metadata;
CacheableSetPropertyMetadata cacheable_metadata;
bool succeeded = TRY(object->internal_set(name, value, this_value, &cacheable_metadata));
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = caches->entries.size() - 1; i >= 1; --i) {
caches->entries[i] = caches->entries[i - 1];
}
caches->entries[0] = {};
return caches->entries[0];
};
if (succeeded && caches && cacheable_metadata.type == CacheableSetPropertyMetadata::Type::AddOwnProperty) {
auto& cache = get_cache_slot();
cache.type = PropertyLookupCache::Entry::Type::AddOwnProperty;
cache.from_shape = from_shape;
cache.property_offset = cacheable_metadata.property_offset.value();
cache.shape = &object->shape();
if (cacheable_metadata.prototype) {
cache.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
}
}
// If internal_set() caused object's shape change, we can no longer be sure
// that collected metadata is valid, e.g. if setter in prototype chain added
// property with the same name into the object itself.
if (succeeded && caches && &shape == &object->shape()) {
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = caches->entries.size() - 1; i >= 1; --i) {
caches->entries[i] = caches->entries[i - 1];
}
caches->entries[0] = {};
return caches->entries[0];
};
if (succeeded && caches && &from_shape == &object->shape()) {
auto& cache = get_cache_slot();
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
switch (cacheable_metadata.type) {
case CacheableSetPropertyMetadata::Type::AddOwnProperty:
// Something went wrong if we ended up here, because cacheable addition of a new property should've changed the shape.
VERIFY_NOT_REACHED();
break;
case CacheableSetPropertyMetadata::Type::ChangeOwnProperty:
cache.type = PropertyLookupCache::Entry::Type::ChangeOwnProperty;
cache.shape = object->shape();
cache.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
break;
case CacheableSetPropertyMetadata::Type::ChangePropertyInPrototypeChain:
cache.type = PropertyLookupCache::Entry::Type::ChangePropertyInPrototypeChain;
cache.shape = object->shape();
cache.property_offset = cacheable_metadata.property_offset.value();
cache.prototype = *cacheable_metadata.prototype;
cache.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
break;
case CacheableSetPropertyMetadata::Type::NotCacheable:
break;
default:
VERIFY_NOT_REACHED();
}
}
@ -2366,7 +2418,7 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
}
if (TRY(binding_object.has_property(identifier))) {
CacheablePropertyMetadata cacheable_metadata;
CacheableSetPropertyMetadata cacheable_metadata;
auto success = TRY(binding_object.internal_set(identifier, src, &binding_object, &cacheable_metadata));
if (!success && vm.in_strict_mode()) {
// Note: Nothing like this in the spec, this is here to produce nicer errors instead of the generic one thrown by Object::set().
@ -2380,7 +2432,7 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
}
return vm.throw_completion<TypeError>(ErrorType::ObjectSetReturnedFalse);
}
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
if (cacheable_metadata.type == CacheableSetPropertyMetadata::Type::ChangeOwnProperty) {
cache.entries[0].shape = shape;
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
}

View file

@ -38,7 +38,7 @@ bool ArgumentsObject::parameter_map_has(PropertyKey const& key) const
}
// 10.4.4.3 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver
ThrowCompletionOr<Value> ArgumentsObject::internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
ThrowCompletionOr<Value> ArgumentsObject::internal_get(PropertyKey const& property_key, Value receiver, CacheableGetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
{
// 1. Let map be args.[[ParameterMap]].
// 2. Let isMapped be ! HasOwnProperty(map, P).
@ -56,7 +56,7 @@ ThrowCompletionOr<Value> ArgumentsObject::internal_get(PropertyKey const& proper
}
// 10.4.4.4 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver
ThrowCompletionOr<bool> ArgumentsObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase)
ThrowCompletionOr<bool> ArgumentsObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase)
{
bool is_mapped = false;

View file

@ -22,8 +22,8 @@ public:
virtual ThrowCompletionOr<Optional<PropertyDescriptor>> internal_get_own_property(PropertyKey const&) const override;
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) const override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheableGetPropertyMetadata*, PropertyLookupPhase) const override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
void set_mapped_names(Vector<Utf16FlyString> mapped_names) { m_mapped_names = move(mapped_names); }

View file

@ -326,7 +326,7 @@ bool Array::default_prototype_chain_intact() const
return true;
}
ThrowCompletionOr<bool> Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
ThrowCompletionOr<bool> Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheableSetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
{
auto& vm = this->vm();

View file

@ -49,7 +49,7 @@ public:
virtual ~Array() override = default;
virtual ThrowCompletionOr<Optional<PropertyDescriptor>> internal_get_own_property(PropertyKey const&) const override final;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override final;
virtual ThrowCompletionOr<bool> internal_has_property(PropertyKey const&) const override final;
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;

View file

@ -136,7 +136,7 @@ ThrowCompletionOr<bool> ModuleNamespaceObject::internal_has_property(PropertyKey
}
// 10.4.6.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver
ThrowCompletionOr<Value> ModuleNamespaceObject::internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
ThrowCompletionOr<Value> ModuleNamespaceObject::internal_get(PropertyKey const& property_key, Value receiver, CacheableGetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
{
auto& vm = this->vm();
@ -183,7 +183,7 @@ ThrowCompletionOr<Value> ModuleNamespaceObject::internal_get(PropertyKey const&
}
// 10.4.6.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-set-p-v-receiver
ThrowCompletionOr<bool> ModuleNamespaceObject::internal_set(PropertyKey const&, Value, Value, CacheablePropertyMetadata*, PropertyLookupPhase)
ThrowCompletionOr<bool> ModuleNamespaceObject::internal_set(PropertyKey const&, Value, Value, CacheableSetPropertyMetadata*, PropertyLookupPhase)
{
// 1. Return false.
return false;

View file

@ -26,8 +26,8 @@ public:
virtual ThrowCompletionOr<Optional<PropertyDescriptor>> internal_get_own_property(PropertyKey const&) const override;
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual ThrowCompletionOr<bool> internal_has_property(PropertyKey const&) const override;
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) const override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheableGetPropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) const override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override;
virtual void initialize(Realm&) override;

View file

@ -148,7 +148,7 @@ ThrowCompletionOr<void> Object::set(PropertyKey const& property_key, Value value
}
// 7.3.5 CreateDataProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createdataproperty
ThrowCompletionOr<bool> Object::create_data_property(PropertyKey const& property_key, Value value)
ThrowCompletionOr<bool> Object::create_data_property(PropertyKey const& property_key, Value value, Optional<u32>* new_property_offset)
{
// 1. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
auto new_descriptor = PropertyDescriptor {
@ -159,7 +159,10 @@ ThrowCompletionOr<bool> Object::create_data_property(PropertyKey const& property
};
// 2. Return ? O.[[DefineOwnProperty]](P, newDesc).
return internal_define_own_property(property_key, new_descriptor);
auto result = internal_define_own_property(property_key, new_descriptor);
if (new_property_offset && new_descriptor.property_offset.has_value())
*new_property_offset = new_descriptor.property_offset.value();
return result;
}
// 7.3.6 CreateMethodProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createmethodproperty
@ -890,7 +893,7 @@ ThrowCompletionOr<bool> Object::internal_has_property(PropertyKey const& propert
// 10.1.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver
// 10.1.8.1 OrdinaryGet ( O, P, Receiver ), https://tc39.es/ecma262/#sec-ordinaryget
ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, Value receiver, CacheableGetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
{
VERIFY(!receiver.is_special_empty_value());
@ -917,16 +920,16 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
if (!cacheable_metadata || !descriptor->property_offset.has_value() || !shape().is_cacheable())
return;
if (phase == PropertyLookupPhase::OwnProperty) {
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::OwnProperty,
*cacheable_metadata = CacheableGetPropertyMetadata {
.type = CacheableGetPropertyMetadata::Type::GetOwnProperty,
.property_offset = descriptor->property_offset.value(),
.prototype = nullptr,
};
} else if (phase == PropertyLookupPhase::PrototypeChain) {
VERIFY(shape().is_prototype_shape());
VERIFY(shape().prototype_chain_validity()->is_valid());
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::InPrototypeChain,
*cacheable_metadata = CacheableGetPropertyMetadata {
.type = CacheableGetPropertyMetadata::Type::GetPropertyInPrototypeChain,
.property_offset = descriptor->property_offset.value(),
.prototype = this,
};
@ -957,7 +960,7 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
// 10.1.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
// 10.1.9.1 OrdinarySet ( O, P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinaryset
ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheableSetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
{
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
@ -970,7 +973,7 @@ ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Va
}
// 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ), https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor
ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey const& property_key, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey const& property_key, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor, CacheableSetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
{
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
@ -999,21 +1002,21 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
}
}
auto update_inline_cache = [&] {
auto update_inline_cache_for_property_change = [&] {
// Non-standard: If the caller has requested cacheable metadata and the property is an own property, fill it in.
if (!cacheable_metadata || !own_descriptor->property_offset.has_value() || !shape().is_cacheable())
return;
if (phase == PropertyLookupPhase::OwnProperty) {
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::OwnProperty,
*cacheable_metadata = CacheableSetPropertyMetadata {
.type = CacheableSetPropertyMetadata::Type::ChangeOwnProperty,
.property_offset = own_descriptor->property_offset.value(),
.prototype = nullptr,
};
} else if (phase == PropertyLookupPhase::PrototypeChain) {
VERIFY(shape().is_prototype_shape());
VERIFY(shape().prototype_chain_validity()->is_valid());
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::InPrototypeChain,
*cacheable_metadata = CacheableSetPropertyMetadata {
.type = CacheableSetPropertyMetadata::Type::ChangePropertyInPrototypeChain,
.property_offset = own_descriptor->property_offset.value(),
.prototype = this,
};
@ -1051,7 +1054,7 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
// NOTE: We don't cache non-setter properties in the prototype chain, as that's a weird
// use-case, and doesn't seem like something in need of optimization.
if (phase == PropertyLookupPhase::OwnProperty)
update_inline_cache();
update_inline_cache_for_property_change();
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
return TRY(receiver_object.internal_define_own_property(property_key, value_descriptor, &existing_descriptor));
@ -1062,7 +1065,18 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
VERIFY(!receiver_object.storage_has(property_key));
// ii. Return ? CreateDataProperty(Receiver, P, V).
return TRY(receiver_object.create_data_property(property_key, value));
Optional<u32> new_property_offset;
auto result = TRY(receiver_object.create_data_property(property_key, value, &new_property_offset));
auto& receiver_shape = receiver_object.shape();
if (cacheable_metadata && new_property_offset.has_value() && !receiver_shape.is_dictionary() && receiver_shape.is_cacheable()) {
VERIFY(!property_key.is_number());
*cacheable_metadata = CacheableSetPropertyMetadata {
.type = CacheableSetPropertyMetadata::Type::AddOwnProperty,
.property_offset = *new_property_offset,
.prototype = receiver_object.prototype(),
};
}
return result;
}
}
@ -1076,7 +1090,7 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
if (!setter)
return false;
update_inline_cache();
update_inline_cache_for_property_change();
// 6. Perform ? Call(setter, Receiver, « V »).
(void)TRY(call(vm, *setter, receiver, value));

View file

@ -41,11 +41,23 @@ struct PrivateElement {
// Non-standard: This is information optionally returned by object property access functions.
// It can be used to implement inline caches for property lookup.
struct CacheablePropertyMetadata {
struct CacheableGetPropertyMetadata {
enum class Type {
NotCacheable,
OwnProperty,
InPrototypeChain,
GetOwnProperty,
GetPropertyInPrototypeChain,
};
Type type { Type::NotCacheable };
Optional<u32> property_offset;
GC::Ptr<Object const> prototype;
};
struct CacheableSetPropertyMetadata {
enum class Type {
NotCacheable,
AddOwnProperty,
ChangeOwnProperty,
ChangePropertyInPrototypeChain,
};
Type type { Type::NotCacheable };
Optional<u32> property_offset;
@ -110,7 +122,7 @@ public:
ThrowCompletionOr<Value> get(PropertyKey const&) const;
ThrowCompletionOr<void> set(PropertyKey const&, Value, ShouldThrowExceptions);
ThrowCompletionOr<bool> create_data_property(PropertyKey const&, Value);
ThrowCompletionOr<bool> create_data_property(PropertyKey const&, Value, Optional<u32>* new_property_offset = nullptr);
void create_method_property(PropertyKey const&, Value);
ThrowCompletionOr<bool> create_data_property_or_throw(PropertyKey const&, Value);
void create_non_enumerable_data_property_or_throw(PropertyKey const&, Value);
@ -145,8 +157,8 @@ public:
OwnProperty,
PrototypeChain,
};
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) const;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty);
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheableGetPropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) const;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheableSetPropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty);
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&);
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const;
@ -156,7 +168,7 @@ public:
// might not hold when property access behaves differently.
bool may_interfere_with_indexed_property_access() const { return m_may_interfere_with_indexed_property_access; }
ThrowCompletionOr<bool> ordinary_set_with_own_descriptor(PropertyKey const&, Value, Value, Optional<PropertyDescriptor>, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty);
ThrowCompletionOr<bool> ordinary_set_with_own_descriptor(PropertyKey const&, Value, Value, Optional<PropertyDescriptor>, CacheableSetPropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty);
// 10.4.7 Immutable Prototype Exotic Objects, https://tc39.es/ecma262/#sec-immutable-prototype-exotic-objects
@ -237,6 +249,7 @@ public:
Shape& shape() { return *m_shape; }
Shape const& shape() const { return *m_shape; }
void unsafe_set_shape(Shape&);
void convert_to_prototype_if_needed();
@ -263,8 +276,6 @@ protected:
Object(ConstructWithPrototypeTag, Object& prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);
explicit Object(Shape&, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No);
void unsafe_set_shape(Shape&);
// [[Extensible]]
bool m_is_extensible { true };

View file

@ -470,7 +470,7 @@ ThrowCompletionOr<bool> ProxyObject::internal_has_property(PropertyKey const& pr
}
// 10.5.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
ThrowCompletionOr<Value> ProxyObject::internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) const
ThrowCompletionOr<Value> ProxyObject::internal_get(PropertyKey const& property_key, Value receiver, CacheableGetPropertyMetadata*, PropertyLookupPhase) const
{
LIMIT_PROXY_RECURSION_DEPTH();
@ -539,7 +539,7 @@ ThrowCompletionOr<Value> ProxyObject::internal_get(PropertyKey const& property_k
}
// 10.5.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
ThrowCompletionOr<bool> ProxyObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase)
ThrowCompletionOr<bool> ProxyObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase)
{
LIMIT_PROXY_RECURSION_DEPTH();

View file

@ -38,8 +38,8 @@ public:
virtual ThrowCompletionOr<Optional<PropertyDescriptor>> internal_get_own_property(PropertyKey const&) const override;
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual ThrowCompletionOr<bool> internal_has_property(PropertyKey const&) const override;
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) const override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheableGetPropertyMetadata*, PropertyLookupPhase) const override;
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase) override;
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override;
virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;

View file

@ -325,7 +325,7 @@ public:
}
// 10.4.5.5 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-typedarray-get
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const& property_key, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const override
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const& property_key, Value receiver, CacheableGetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const override
{
VERIFY(!receiver.is_special_empty_value());
@ -350,7 +350,7 @@ public:
}
// 10.4.5.6 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheableSetPropertyMetadata*, PropertyLookupPhase) override
{
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());

View file

@ -215,7 +215,7 @@ JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> PlatformObject::internal
}
// https://webidl.spec.whatwg.org/#legacy-platform-object-set
JS::ThrowCompletionOr<bool> PlatformObject::internal_set(JS::PropertyKey const& property_name, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* metadata, PropertyLookupPhase phase)
JS::ThrowCompletionOr<bool> PlatformObject::internal_set(JS::PropertyKey const& property_name, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata* metadata, PropertyLookupPhase phase)
{
if (!m_legacy_platform_object_flags.has_value() || m_legacy_platform_object_flags->has_global_interface_extended_attribute)
return Base::internal_set(property_name, value, receiver, metadata, phase);

View file

@ -37,7 +37,7 @@ public:
// ^JS::Object
virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyKey const&) const override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value, JS::Value, JS::CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value, JS::Value, JS::CacheableSetPropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) override;
virtual JS::ThrowCompletionOr<bool> internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional<JS::PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override;
virtual JS::ThrowCompletionOr<bool> internal_prevent_extensions() override;

View file

@ -659,7 +659,7 @@ JS::ThrowCompletionOr<bool> Location::internal_define_own_property(JS::PropertyK
}
// 7.10.5.7 [[Get]] ( P, Receiver ), https://html.spec.whatwg.org/multipage/history.html#location-get
JS::ThrowCompletionOr<JS::Value> Location::internal_get(JS::PropertyKey const& property_key, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
JS::ThrowCompletionOr<JS::Value> Location::internal_get(JS::PropertyKey const& property_key, JS::Value receiver, JS::CacheableGetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
{
auto& vm = this->vm();
@ -672,7 +672,7 @@ JS::ThrowCompletionOr<JS::Value> Location::internal_get(JS::PropertyKey const& p
}
// 7.10.5.8 [[Set]] ( P, V, Receiver ), https://html.spec.whatwg.org/multipage/history.html#location-set
JS::ThrowCompletionOr<bool> Location::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
JS::ThrowCompletionOr<bool> Location::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
{
auto& vm = this->vm();

View file

@ -60,8 +60,8 @@ public:
virtual JS::ThrowCompletionOr<bool> internal_prevent_extensions() override;
virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyKey const&) const override;
virtual JS::ThrowCompletionOr<bool> internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional<JS::PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual JS::ThrowCompletionOr<JS::Value> internal_get(JS::PropertyKey const&, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase) const override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase) override;
virtual JS::ThrowCompletionOr<JS::Value> internal_get(JS::PropertyKey const&, JS::Value receiver, JS::CacheableGetPropertyMetadata*, PropertyLookupPhase) const override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata*, PropertyLookupPhase) override;
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override;
virtual JS::ThrowCompletionOr<GC::RootVector<JS::Value>> internal_own_property_keys() const override;

View file

@ -154,7 +154,7 @@ JS::ThrowCompletionOr<bool> WindowProxy::internal_define_own_property(JS::Proper
// 7.4.7 [[Get]] ( P, Receiver ), https://html.spec.whatwg.org/multipage/nav-history-apis.html#windowproxy-get
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#windowproxy-get
JS::ThrowCompletionOr<JS::Value> WindowProxy::internal_get(JS::PropertyKey const& property_key, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase) const
JS::ThrowCompletionOr<JS::Value> WindowProxy::internal_get(JS::PropertyKey const& property_key, JS::Value receiver, JS::CacheableGetPropertyMetadata*, PropertyLookupPhase) const
{
auto& vm = this->vm();
@ -175,7 +175,7 @@ JS::ThrowCompletionOr<JS::Value> WindowProxy::internal_get(JS::PropertyKey const
// 7.4.8 [[Set]] ( P, V, Receiver ), https://html.spec.whatwg.org/multipage/nav-history-apis.html#windowproxy-set
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#windowproxy-set
JS::ThrowCompletionOr<bool> WindowProxy::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase)
JS::ThrowCompletionOr<bool> WindowProxy::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata*, PropertyLookupPhase)
{
auto& vm = this->vm();

View file

@ -28,8 +28,8 @@ public:
virtual JS::ThrowCompletionOr<bool> internal_prevent_extensions() override;
virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyKey const&) const override;
virtual JS::ThrowCompletionOr<bool> internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional<JS::PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual JS::ThrowCompletionOr<JS::Value> internal_get(JS::PropertyKey const&, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase) const override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase) override;
virtual JS::ThrowCompletionOr<JS::Value> internal_get(JS::PropertyKey const&, JS::Value receiver, JS::CacheableGetPropertyMetadata*, PropertyLookupPhase) const override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata*, PropertyLookupPhase) override;
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override;
virtual JS::ThrowCompletionOr<GC::RootVector<JS::Value>> internal_own_property_keys() const override;

View file

@ -39,7 +39,7 @@ void ObservableArray::set_on_delete_an_indexed_value_callback(DeleteAnIndexedVal
m_on_delete_an_indexed_value = GC::create_function(heap(), move(callback));
}
JS::ThrowCompletionOr<bool> ObservableArray::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* metadata, PropertyLookupPhase phase)
JS::ThrowCompletionOr<bool> ObservableArray::internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata* metadata, PropertyLookupPhase phase)
{
if (property_key.is_number() && m_on_set_an_indexed_value)
TRY(Bindings::throw_dom_exception_if_needed(vm(), [&] { return m_on_set_an_indexed_value->function()(value); }));

View file

@ -20,7 +20,7 @@ class WEB_API ObservableArray final : public JS::Array {
public:
static GC::Ref<ObservableArray> create(JS::Realm& realm);
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* metadata = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const& property_key, JS::Value value, JS::Value receiver, JS::CacheableSetPropertyMetadata* metadata = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) override;
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const& property_key) override;
using SetAnIndexedValueCallbackFunction = Function<ExceptionOr<void>(JS::Value&)>;