Skip to content

Commit d4deafe

Browse files
Lubrsikalenikaliaksandr
authored andcommitted
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.
1 parent a21d247 commit d4deafe

File tree

5 files changed

+79
-6
lines changed

5 files changed

+79
-6
lines changed

Libraries/LibJS/Bytecode/Executable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct PropertyLookupCache {
4343
Optional<u32> property_offset;
4444
GC::Weak<Object> prototype;
4545
GC::Weak<PrototypeChainValidity> prototype_chain_validity;
46+
Optional<u32> shape_dictionary_generation;
4647
};
4748
AK::Array<Entry, max_number_of_shapes_to_remember> entries;
4849
};

Libraries/LibJS/Bytecode/Interpreter.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
10331033

10341034
// OPTIMIZATION: For global var bindings, if the shape of the global object hasn't changed,
10351035
// we can use the cached property offset.
1036-
if (&shape == cache.entries[0].shape) {
1036+
if (&shape == cache.entries[0].shape && (!shape.is_dictionary() || shape.dictionary_generation() == cache.entries[0].shape_dictionary_generation.value())) {
10371037
auto value = binding_object.get_direct(cache.entries[0].property_offset.value());
10381038
if (value.is_accessor())
10391039
return TRY(call(vm, value.as_accessor().getter(), js_undefined()));
@@ -1085,6 +1085,10 @@ inline ThrowCompletionOr<Value> get_global(Interpreter& interpreter, IdentifierT
10851085
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
10861086
cache.entries[0].shape = shape;
10871087
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
1088+
1089+
if (shape.is_dictionary()) {
1090+
cache.entries[0].shape_dictionary_generation = shape.dictionary_generation();
1091+
}
10881092
}
10891093
return value;
10901094
}
@@ -1140,6 +1144,13 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
11401144
bool can_use_cache = [&]() -> bool {
11411145
if (&object->shape() != cache.shape) [[unlikely]]
11421146
return false;
1147+
1148+
if (cache.shape->is_dictionary()) {
1149+
VERIFY(cache.shape_dictionary_generation.has_value());
1150+
if (object->shape().dictionary_generation() != cache.shape_dictionary_generation.value()) [[unlikely]]
1151+
return false;
1152+
}
1153+
11431154
auto cached_prototype_chain_validity = cache.prototype_chain_validity.ptr();
11441155
if (!cached_prototype_chain_validity) [[unlikely]]
11451156
return false;
@@ -1159,6 +1170,13 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
11591170
case PropertyLookupCache::Entry::Type::ChangeOwnProperty: {
11601171
if (cache.shape != &object->shape()) [[unlikely]]
11611172
break;
1173+
1174+
if (cache.shape->is_dictionary()) {
1175+
VERIFY(cache.shape_dictionary_generation.has_value());
1176+
if (cache.shape->dictionary_generation() != cache.shape_dictionary_generation.value())
1177+
break;
1178+
}
1179+
11621180
auto value_in_object = object->get_direct(cache.property_offset.value());
11631181
if (value_in_object.is_accessor()) [[unlikely]] {
11641182
(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
11751193
auto cached_shape = cache.shape.ptr();
11761194
if (!cached_shape) [[unlikely]]
11771195
break;
1196+
1197+
if (cache.shape->is_dictionary()) {
1198+
VERIFY(cache.shape_dictionary_generation.has_value());
1199+
if (object->shape().dictionary_generation() != cache.shape_dictionary_generation.value())
1200+
break;
1201+
}
1202+
11781203
// The cache is invalid if the prototype chain has been mutated, since such a mutation could have added a setter for the property.
11791204
auto cached_prototype_chain_validity = cache.prototype_chain_validity.ptr();
11801205
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
12091234
if (cacheable_metadata.prototype) {
12101235
cache.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
12111236
}
1237+
if (cache.shape->is_dictionary()) {
1238+
cache.shape_dictionary_generation = cache.shape->dictionary_generation();
1239+
}
12121240
}
12131241

12141242
// If internal_set() caused object's shape change, we can no longer be sure
@@ -1225,13 +1253,21 @@ ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value this_value
12251253
cache.type = PropertyLookupCache::Entry::Type::ChangeOwnProperty;
12261254
cache.shape = object->shape();
12271255
cache.property_offset = cacheable_metadata.property_offset.value();
1256+
1257+
if (cache.shape->is_dictionary()) {
1258+
cache.shape_dictionary_generation = cache.shape->dictionary_generation();
1259+
}
12281260
break;
12291261
case CacheableSetPropertyMetadata::Type::ChangePropertyInPrototypeChain:
12301262
cache.type = PropertyLookupCache::Entry::Type::ChangePropertyInPrototypeChain;
12311263
cache.shape = object->shape();
12321264
cache.property_offset = cacheable_metadata.property_offset.value();
12331265
cache.prototype = *cacheable_metadata.prototype;
12341266
cache.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
1267+
1268+
if (cache.shape->is_dictionary()) {
1269+
cache.shape_dictionary_generation = cache.shape->dictionary_generation();
1270+
}
12351271
break;
12361272
case CacheableSetPropertyMetadata::Type::NotCacheable:
12371273
break;
@@ -2294,7 +2330,7 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
22942330
if (cache.environment_serial_number == declarative_record.environment_serial_number()) {
22952331
// OPTIMIZATION: For global var bindings, if the shape of the global object hasn't changed,
22962332
// we can use the cached property offset.
2297-
if (&shape == cache.entries[0].shape) {
2333+
if (&shape == cache.entries[0].shape && (!shape.is_dictionary() || shape.dictionary_generation() == cache.entries[0].shape_dictionary_generation.value())) {
22982334
auto value = binding_object.get_direct(cache.entries[0].property_offset.value());
22992335
if (value.is_accessor())
23002336
TRY(call(vm, value.as_accessor().setter(), &binding_object, src));
@@ -2363,6 +2399,10 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
23632399
if (cacheable_metadata.type == CacheableSetPropertyMetadata::Type::ChangeOwnProperty) {
23642400
cache.entries[0].shape = shape;
23652401
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
2402+
2403+
if (shape.is_dictionary()) {
2404+
cache.entries[0].shape_dictionary_generation = shape.dictionary_generation();
2405+
}
23662406
}
23672407
return {};
23682408
}

Libraries/LibJS/Bytecode/PropertyAccess.h

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
9898
bool can_use_cache = [&]() -> bool {
9999
if (&shape != cache_entry.shape) [[unlikely]]
100100
return false;
101+
102+
if (shape.is_dictionary()) {
103+
VERIFY(cache_entry.shape_dictionary_generation.has_value());
104+
if (shape.dictionary_generation() != cache_entry.shape_dictionary_generation.value()) [[unlikely]] {
105+
return false;
106+
}
107+
}
108+
101109
auto cached_prototype_chain_validity = cache_entry.prototype_chain_validity.ptr();
102110
if (!cached_prototype_chain_validity) [[unlikely]]
103111
return false;
@@ -113,11 +121,21 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
113121
}
114122
} else if (&shape == cache_entry.shape) {
115123
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
116-
auto value = base_obj->get_direct(cache_entry.property_offset.value());
117-
if (value.is_accessor()) {
118-
return TRY(call(vm, value.as_accessor().getter(), this_value));
124+
bool can_use_cache = true;
125+
if (shape.is_dictionary()) {
126+
VERIFY(cache_entry.shape_dictionary_generation.has_value());
127+
if (shape.dictionary_generation() != cache_entry.shape_dictionary_generation.value()) [[unlikely]] {
128+
can_use_cache = false;
129+
}
130+
}
131+
132+
if (can_use_cache) [[likely]] {
133+
auto value = base_obj->get_direct(cache_entry.property_offset.value());
134+
if (value.is_accessor()) {
135+
return TRY(call(vm, value.as_accessor().getter(), this_value));
136+
}
137+
return value;
119138
}
120-
return value;
121139
}
122140
}
123141

@@ -139,12 +157,20 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
139157
auto& entry = get_cache_slot();
140158
entry.shape = shape;
141159
entry.property_offset = cacheable_metadata.property_offset.value();
160+
161+
if (shape.is_dictionary()) {
162+
entry.shape_dictionary_generation = shape.dictionary_generation();
163+
}
142164
} else if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetPropertyInPrototypeChain) {
143165
auto& entry = get_cache_slot();
144166
entry.shape = &base_obj->shape();
145167
entry.property_offset = cacheable_metadata.property_offset.value();
146168
entry.prototype = *cacheable_metadata.prototype;
147169
entry.prototype_chain_validity = *prototype_chain_validity;
170+
171+
if (shape.is_dictionary()) {
172+
entry.shape_dictionary_generation = shape.dictionary_generation();
173+
}
148174
}
149175
}
150176

Libraries/LibJS/Runtime/Shape.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ void Shape::add_property_without_transition(PropertyKey const& property_key, Pro
291291
if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) {
292292
VERIFY(m_property_count < NumericLimits<u32>::max());
293293
++m_property_count;
294+
++m_dictionary_generation;
294295
}
295296
}
296297

@@ -303,6 +304,7 @@ void Shape::set_property_attributes_without_transition(PropertyKey const& proper
303304
VERIFY(it != m_property_table->end());
304305
it->value.attributes = attributes;
305306
m_property_table->set(property_key, it->value);
307+
++m_dictionary_generation;
306308
}
307309

308310
void Shape::remove_property_without_transition(PropertyKey const& property_key, u32 offset)
@@ -317,6 +319,7 @@ void Shape::remove_property_without_transition(PropertyKey const& property_key,
317319
if (it.value.offset > offset)
318320
--it.value.offset;
319321
}
322+
++m_dictionary_generation;
320323
}
321324

322325
GC::Ref<Shape> Shape::clone_for_prototype()

Libraries/LibJS/Runtime/Shape.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class JS_API Shape final : public Cell {
8484
[[nodiscard]] bool is_cacheable_dictionary() const { return m_dictionary && m_cacheable; }
8585
[[nodiscard]] bool is_uncacheable_dictionary() const { return m_dictionary && !m_cacheable; }
8686

87+
[[nodiscard]] u32 dictionary_generation() const { return m_dictionary_generation; }
88+
8789
[[nodiscard]] bool is_prototype_shape() const { return m_is_prototype_shape; }
8890
void set_prototype_shape();
8991

@@ -137,6 +139,7 @@ class JS_API Shape final : public Cell {
137139
GC::Ptr<PrototypeChainValidity> m_prototype_chain_validity;
138140

139141
u32 m_property_count { 0 };
142+
u32 m_dictionary_generation { 0 };
140143

141144
PropertyAttributes m_attributes { 0 };
142145
TransitionType m_transition_type { TransitionType::Invalid };

0 commit comments

Comments
 (0)