mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +00:00
LibJS: Track current shape dictionary generation in PropertyLookupCache
When an object becomes too big (currently 64 properties or more), we change its shape to a dictionary and don't do any further transitions. However, this means the Shape of the object no longer changes, so the cache invalidation check of `current_shape != cache.shape` is no longer a valid check. This fixes that by keeping track of a generation number for the Shape both on the Shape object and in the cache, allowing that to be checked instead of the Shape identity. The generation is incremented whenever the dictionary is mutated. Fixes stale cache lookups on Gmail preventing emails from being displayed. I was not able to produce a reproduction for this, plus the generation count was over the 20k mark on Gmail.
This commit is contained in:
parent
a21d247b0e
commit
d4deafe5fe
Notes:
github-actions[bot]
2025-10-24 13:36:11 +00:00
Author: https://github.com/Lubrsi
Commit: d4deafe5fe
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6115
Reviewed-by: https://github.com/kalenikaliaksandr ✅
5 changed files with 79 additions and 6 deletions
|
|
@ -1033,7 +1033,7 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
|
|||
|
||||
// OPTIMIZATION: For global var bindings, if the shape of the global object hasn't changed,
|
||||
// we can use the cached property offset.
|
||||
if (&shape == cache.entries[0].shape) {
|
||||
if (&shape == cache.entries[0].shape && (!shape.is_dictionary() || shape.dictionary_generation() == cache.entries[0].shape_dictionary_generation.value())) {
|
||||
auto value = binding_object.get_direct(cache.entries[0].property_offset.value());
|
||||
if (value.is_accessor())
|
||||
return TRY(call(vm, value.as_accessor().getter(), js_undefined()));
|
||||
|
|
@ -1085,6 +1085,10 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
|
|||
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
|
||||
cache.entries[0].shape = shape;
|
||||
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
|
||||
|
||||
if (shape.is_dictionary()) {
|
||||
cache.entries[0].shape_dictionary_generation = shape.dictionary_generation();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
@ -1140,6 +1144,13 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
|
|||
bool can_use_cache = [&]() -> bool {
|
||||
if (&object->shape() != cache.shape) [[unlikely]]
|
||||
return false;
|
||||
|
||||
if (cache.shape->is_dictionary()) {
|
||||
VERIFY(cache.shape_dictionary_generation.has_value());
|
||||
if (object->shape().dictionary_generation() != cache.shape_dictionary_generation.value()) [[unlikely]]
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cached_prototype_chain_validity = cache.prototype_chain_validity.ptr();
|
||||
if (!cached_prototype_chain_validity) [[unlikely]]
|
||||
return false;
|
||||
|
|
@ -1159,6 +1170,13 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
|
|||
case PropertyLookupCache::Entry::Type::ChangeOwnProperty: {
|
||||
if (cache.shape != &object->shape()) [[unlikely]]
|
||||
break;
|
||||
|
||||
if (cache.shape->is_dictionary()) {
|
||||
VERIFY(cache.shape_dictionary_generation.has_value());
|
||||
if (cache.shape->dictionary_generation() != cache.shape_dictionary_generation.value())
|
||||
break;
|
||||
}
|
||||
|
||||
auto value_in_object = object->get_direct(cache.property_offset.value());
|
||||
if (value_in_object.is_accessor()) [[unlikely]] {
|
||||
(void)TRY(call(vm, value_in_object.as_accessor().setter(), this_value, value));
|
||||
|
|
@ -1175,6 +1193,13 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
|
|||
auto cached_shape = cache.shape.ptr();
|
||||
if (!cached_shape) [[unlikely]]
|
||||
break;
|
||||
|
||||
if (cache.shape->is_dictionary()) {
|
||||
VERIFY(cache.shape_dictionary_generation.has_value());
|
||||
if (object->shape().dictionary_generation() != cache.shape_dictionary_generation.value())
|
||||
break;
|
||||
}
|
||||
|
||||
// The cache is invalid if the prototype chain has been mutated, since such a mutation could have added a setter for the property.
|
||||
auto cached_prototype_chain_validity = cache.prototype_chain_validity.ptr();
|
||||
if (cached_prototype_chain_validity && !cached_prototype_chain_validity->is_valid()) [[unlikely]]
|
||||
|
|
@ -1209,6 +1234,9 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
|
|||
if (cacheable_metadata.prototype) {
|
||||
cache.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
|
||||
}
|
||||
if (cache.shape->is_dictionary()) {
|
||||
cache.shape_dictionary_generation = cache.shape->dictionary_generation();
|
||||
}
|
||||
}
|
||||
|
||||
// If internal_set() caused object's shape change, we can no longer be sure
|
||||
|
|
@ -1225,6 +1253,10 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
|
|||
cache.type = PropertyLookupCache::Entry::Type::ChangeOwnProperty;
|
||||
cache.shape = object->shape();
|
||||
cache.property_offset = cacheable_metadata.property_offset.value();
|
||||
|
||||
if (cache.shape->is_dictionary()) {
|
||||
cache.shape_dictionary_generation = cache.shape->dictionary_generation();
|
||||
}
|
||||
break;
|
||||
case CacheableSetPropertyMetadata::Type::ChangePropertyInPrototypeChain:
|
||||
cache.type = PropertyLookupCache::Entry::Type::ChangePropertyInPrototypeChain;
|
||||
|
|
@ -1232,6 +1264,10 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
|
|||
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 (cache.shape->is_dictionary()) {
|
||||
cache.shape_dictionary_generation = cache.shape->dictionary_generation();
|
||||
}
|
||||
break;
|
||||
case CacheableSetPropertyMetadata::Type::NotCacheable:
|
||||
break;
|
||||
|
|
@ -2294,7 +2330,7 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
|
|||
if (cache.environment_serial_number == declarative_record.environment_serial_number()) {
|
||||
// OPTIMIZATION: For global var bindings, if the shape of the global object hasn't changed,
|
||||
// we can use the cached property offset.
|
||||
if (&shape == cache.entries[0].shape) {
|
||||
if (&shape == cache.entries[0].shape && (!shape.is_dictionary() || shape.dictionary_generation() == cache.entries[0].shape_dictionary_generation.value())) {
|
||||
auto value = binding_object.get_direct(cache.entries[0].property_offset.value());
|
||||
if (value.is_accessor())
|
||||
TRY(call(vm, value.as_accessor().setter(), &binding_object, src));
|
||||
|
|
@ -2363,6 +2399,10 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
|
|||
if (cacheable_metadata.type == CacheableSetPropertyMetadata::Type::ChangeOwnProperty) {
|
||||
cache.entries[0].shape = shape;
|
||||
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
|
||||
|
||||
if (shape.is_dictionary()) {
|
||||
cache.entries[0].shape_dictionary_generation = shape.dictionary_generation();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue