1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibJS: Cache PutById to setters in the prototype chain

This is *extremely* common on the web, but barely shows up at all in
JavaScript benchmarks.

A typical example is setting Element.innerHTML on a HTMLDivElement.
HTMLDivElement doesn't have innerHTML, so it has to travel up the
prototype chain until it finds it.

Before this change, we didn't cache this at all, so we had to travel
the prototype chain every time a setter like this was used.

We now use the same mechanism we already had for GetBydId and cache
PutById setter accesses in the prototype chain as well.

1.74x speedup on MicroBench/setter-in-prototype-chain.js
This commit is contained in:
Andreas Kling 2025-05-04 16:00:13 +02:00 committed by Andreas Kling
parent 71665fa504
commit 183c847c80
Notes: github-actions[bot] 2025-05-05 13:22:41 +00:00
18 changed files with 93 additions and 39 deletions

View file

@ -1224,20 +1224,54 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
break;
}
case Op::PropertyKind::KeyValue: {
if (cache && cache->shape == &object->shape()) {
object->put_direct(*cache->property_offset, value);
return {};
if (cache) {
if (cache->prototype) {
// 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)
return false;
if (!cache->prototype_chain_validity)
return false;
if (!cache->prototype_chain_validity->is_valid())
return false;
return true;
}();
if (can_use_cache) {
auto value_in_prototype = cache->prototype->get_direct(cache->property_offset.value());
if (value_in_prototype.is_accessor()) {
TRY(call(vm, value_in_prototype.as_accessor().setter(), this_value, value));
return {};
}
}
} else if (cache->shape == &object->shape()) {
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));
} else {
object->put_direct(*cache->property_offset, value);
}
return {};
}
}
CacheablePropertyMetadata cacheable_metadata;
bool succeeded = TRY(object->internal_set(name, value, this_value, &cacheable_metadata));
if (succeeded && cache && cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
cache->shape = object->shape();
cache->property_offset = cacheable_metadata.property_offset.value();
if (succeeded && cache) {
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
*cache = {};
cache->shape = object->shape();
cache->property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
*cache = {};
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();
}
}
if (!succeeded && vm.in_strict_mode()) {
if (!succeeded && vm.in_strict_mode()) [[unlikely]] {
if (base.is_object())
return vm.throw_completion<TypeError>(ErrorType::ReferenceNullishSetProperty, name, base.to_string_without_side_effects());
return vm.throw_completion<TypeError>(ErrorType::ReferencePrimitiveSetProperty, name, base.typeof_(vm)->utf8_string(), base.to_string_without_side_effects());
@ -2318,7 +2352,7 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
if (&shape == cache.shape) {
auto value = binding_object.get_direct(cache.property_offset.value());
if (value.is_accessor())
TRY(call(vm, value.as_accessor().setter(), js_undefined(), value));
TRY(call(vm, value.as_accessor().setter(), &binding_object, src));
else
binding_object.put_direct(cache.property_offset.value(), src);
return {};