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.
This commit is contained in:
Andreas Kling 2023-07-10 20:45:31 +02:00 committed by Linus Groh
parent 17d23e76e5
commit cf6792ec40
Notes: sideshowbarker 2024-07-16 22:16:50 +09:00
6 changed files with 38 additions and 5 deletions

View file

@ -18,6 +18,7 @@ namespace JS::Bytecode {
struct PropertyLookupCache {
WeakPtr<Shape> shape;
Optional<u32> property_offset;
u64 unique_shape_serial_number { 0 };
};
struct Executable {

View file

@ -546,7 +546,10 @@ static ThrowCompletionOr<void> get_by_id(Bytecode::Interpreter& interpreter, Ide
}
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
if (&base_obj->shape() == cache.shape) {
// NOTE: Unique shapes don't change identity, so we compare their serial numbers instead.
auto& shape = base_obj->shape();
if (&shape == cache.shape
&& (!shape.is_unique() || shape.unique_shape_serial_number() == cache.unique_shape_serial_number)) {
interpreter.accumulator() = base_obj->get_direct(cache.property_offset.value());
return {};
}
@ -555,11 +558,9 @@ static ThrowCompletionOr<void> get_by_id(Bytecode::Interpreter& interpreter, Ide
interpreter.accumulator() = TRY(base_obj->internal_get(name, this_value, &cacheable_metadata));
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
cache.shape = &base_obj->shape();
cache.shape = shape;
cache.property_offset = cacheable_metadata.property_offset.value();
} else {
cache.shape = nullptr;
cache.property_offset = {};
cache.unique_shape_serial_number = shape.unique_shape_serial_number();
}
return {};

View file

@ -48,6 +48,7 @@ struct CacheablePropertyMetadata {
};
Type type { Type::NotCacheable };
Optional<u32> property_offset;
u64 unique_shape_serial_number { 0 };
};
class Object : public Cell {

View file

@ -197,6 +197,8 @@ void Shape::add_property_to_unique_shape(StringOrSymbol const& property_key, Pro
VERIFY(m_property_count < NumericLimits<u32>::max());
++m_property_count;
++m_unique_shape_serial_number;
}
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_
VERIFY(it != m_property_table->end());
it->value.attributes = attributes;
m_property_table->set(property_key, it->value);
++m_unique_shape_serial_number;
}
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
if (it.value.offset > offset)
--it.value.offset;
}
++m_unique_shape_serial_number;
}
void Shape::add_property_without_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)

View file

@ -81,6 +81,8 @@ public:
void add_property_to_unique_shape(StringOrSymbol const&, PropertyAttributes attributes);
void reconfigure_property_in_unique_shape(StringOrSymbol const& property_key, PropertyAttributes attributes);
[[nodiscard]] u64 unique_shape_serial_number() const { return m_unique_shape_serial_number; }
private:
explicit Shape(Realm&);
Shape(Shape& previous_shape, StringOrSymbol const& property_key, PropertyAttributes attributes, TransitionType);
@ -107,6 +109,10 @@ private:
PropertyAttributes m_attributes { 0 };
TransitionType m_transition_type : 6 { TransitionType::Invalid };
bool m_unique : 1 { false };
// Since unique shapes never change identity, inline caches use this incrementing serial number
// to know whether its property table has been modified since last time we checked.
u64 m_unique_shape_serial_number { 0 };
};
}

View file

@ -0,0 +1,18 @@
test("Inline cache invalidated by deleting property from unique shape", () => {
// Create an object with an unique shape by adding a huge amount of properties.
let o = {};
for (let x = 0; x < 1000; ++x) {
o["prop" + x] = x;
}
function ic(o) {
return o.prop2;
}
let first = ic(o);
delete o.prop2;
let second = ic(o);
expect(first).toBe(2);
expect(second).toBeUndefined();
});