Skip to content

Commit cf6792e

Browse files
awesomeklinglinusg
authored andcommitted
LibJS/Bytecode: Invalidate inline caches on unique shape mutation
Since we can't rely on shape identity (i.e its pointer address) for unique shapes, give them a serial number that increments whenever a mutation occurs. Inline caches can then compare this serial number against what they have seen before.
1 parent 17d23e7 commit cf6792e

File tree

6 files changed

+38
-5
lines changed

6 files changed

+38
-5
lines changed

Userland/Libraries/LibJS/Bytecode/Executable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace JS::Bytecode {
1818
struct PropertyLookupCache {
1919
WeakPtr<Shape> shape;
2020
Optional<u32> property_offset;
21+
u64 unique_shape_serial_number { 0 };
2122
};
2223

2324
struct Executable {

Userland/Libraries/LibJS/Bytecode/Op.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,10 @@ static ThrowCompletionOr<void> get_by_id(Bytecode::Interpreter& interpreter, Ide
546546
}
547547

548548
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
549-
if (&base_obj->shape() == cache.shape) {
549+
// NOTE: Unique shapes don't change identity, so we compare their serial numbers instead.
550+
auto& shape = base_obj->shape();
551+
if (&shape == cache.shape
552+
&& (!shape.is_unique() || shape.unique_shape_serial_number() == cache.unique_shape_serial_number)) {
550553
interpreter.accumulator() = base_obj->get_direct(cache.property_offset.value());
551554
return {};
552555
}
@@ -555,11 +558,9 @@ static ThrowCompletionOr<void> get_by_id(Bytecode::Interpreter& interpreter, Ide
555558
interpreter.accumulator() = TRY(base_obj->internal_get(name, this_value, &cacheable_metadata));
556559

557560
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
558-
cache.shape = &base_obj->shape();
561+
cache.shape = shape;
559562
cache.property_offset = cacheable_metadata.property_offset.value();
560-
} else {
561-
cache.shape = nullptr;
562-
cache.property_offset = {};
563+
cache.unique_shape_serial_number = shape.unique_shape_serial_number();
563564
}
564565

565566
return {};

Userland/Libraries/LibJS/Runtime/Object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ struct CacheablePropertyMetadata {
4848
};
4949
Type type { Type::NotCacheable };
5050
Optional<u32> property_offset;
51+
u64 unique_shape_serial_number { 0 };
5152
};
5253

5354
class Object : public Cell {

Userland/Libraries/LibJS/Runtime/Shape.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ void Shape::add_property_to_unique_shape(StringOrSymbol const& property_key, Pro
197197

198198
VERIFY(m_property_count < NumericLimits<u32>::max());
199199
++m_property_count;
200+
201+
++m_unique_shape_serial_number;
200202
}
201203

202204
void Shape::reconfigure_property_in_unique_shape(StringOrSymbol const& property_key, PropertyAttributes attributes)
@@ -207,6 +209,8 @@ void Shape::reconfigure_property_in_unique_shape(StringOrSymbol const& property_
207209
VERIFY(it != m_property_table->end());
208210
it->value.attributes = attributes;
209211
m_property_table->set(property_key, it->value);
212+
213+
++m_unique_shape_serial_number;
210214
}
211215

212216
void Shape::remove_property_from_unique_shape(StringOrSymbol const& property_key, size_t offset)
@@ -220,6 +224,8 @@ void Shape::remove_property_from_unique_shape(StringOrSymbol const& property_key
220224
if (it.value.offset > offset)
221225
--it.value.offset;
222226
}
227+
228+
++m_unique_shape_serial_number;
223229
}
224230

225231
void Shape::add_property_without_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)

Userland/Libraries/LibJS/Runtime/Shape.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ class Shape final
8181
void add_property_to_unique_shape(StringOrSymbol const&, PropertyAttributes attributes);
8282
void reconfigure_property_in_unique_shape(StringOrSymbol const& property_key, PropertyAttributes attributes);
8383

84+
[[nodiscard]] u64 unique_shape_serial_number() const { return m_unique_shape_serial_number; }
85+
8486
private:
8587
explicit Shape(Realm&);
8688
Shape(Shape& previous_shape, StringOrSymbol const& property_key, PropertyAttributes attributes, TransitionType);
@@ -107,6 +109,10 @@ class Shape final
107109
PropertyAttributes m_attributes { 0 };
108110
TransitionType m_transition_type : 6 { TransitionType::Invalid };
109111
bool m_unique : 1 { false };
112+
113+
// Since unique shapes never change identity, inline caches use this incrementing serial number
114+
// to know whether its property table has been modified since last time we checked.
115+
u64 m_unique_shape_serial_number { 0 };
110116
};
111117

112118
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
test("Inline cache invalidated by deleting property from unique shape", () => {
2+
// Create an object with an unique shape by adding a huge amount of properties.
3+
let o = {};
4+
for (let x = 0; x < 1000; ++x) {
5+
o["prop" + x] = x;
6+
}
7+
8+
function ic(o) {
9+
return o.prop2;
10+
}
11+
12+
let first = ic(o);
13+
delete o.prop2;
14+
let second = ic(o);
15+
16+
expect(first).toBe(2);
17+
expect(second).toBeUndefined();
18+
});

0 commit comments

Comments
 (0)