mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 20:29:18 +00:00
LibJS: Cache access to properties found in prototype chain
We already had fast access to own properties via shape-based IC. This patch extends the mechanism to properties on the prototype chain, using the "validity cell" technique from V8. - Prototype objects now have unique shape - Each prototype has an associated PrototypeChainValidity - When a prototype shape is mutated, every prototype shape "below" it in any prototype chain is invalidated. - Invalidation happens by marking the validity object as invalid, and then replacing it with a new validity object. - Property caches keep a pointer to the last seen valid validity. If there is no validity, or the validity is invalid, the cache misses and gets repopulated. This is very helpful when using JavaScript to access DOM objects, as we frequently have to traverse 4+ prototype objects before finding the property we're interested in on e.g EventTarget or Node.
This commit is contained in:
parent
493a04d5fe
commit
8ff16c1b57
Notes:
sideshowbarker
2024-07-17 05:02:42 +09:00
Author: https://github.com/awesomekling
Commit: 8ff16c1b57
Pull-request: https://github.com/SerenityOS/serenity/pull/24206
Reviewed-by: https://github.com/kennethmyhra
12 changed files with 232 additions and 52 deletions
|
@ -333,7 +333,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> ClassExpression::create_class_const
|
|||
}
|
||||
}
|
||||
|
||||
auto prototype = Object::create(realm, proto_parent);
|
||||
auto prototype = Object::create_prototype(realm, proto_parent);
|
||||
VERIFY(prototype);
|
||||
|
||||
vm.running_execution_context().lexical_environment = class_environment;
|
||||
|
|
|
@ -118,9 +118,23 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<DeprecatedFlyString c
|
|||
return Value { base_obj->indexed_properties().array_like_size() };
|
||||
}
|
||||
|
||||
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
|
||||
auto& shape = base_obj->shape();
|
||||
if (&shape == cache.shape) {
|
||||
|
||||
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 (&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)
|
||||
return cache.prototype->get_direct(cache.property_offset.value());
|
||||
} else if (&shape == cache.shape) {
|
||||
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
|
||||
return base_obj->get_direct(cache.property_offset.value());
|
||||
}
|
||||
|
||||
|
@ -128,8 +142,15 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<DeprecatedFlyString c
|
|||
auto value = TRY(base_obj->internal_get(property, this_value, &cacheable_metadata));
|
||||
|
||||
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
|
||||
cache = {};
|
||||
cache.shape = shape;
|
||||
cache.property_offset = cacheable_metadata.property_offset.value();
|
||||
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
|
||||
cache = {};
|
||||
cache.shape = &base_obj->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();
|
||||
}
|
||||
|
||||
return value;
|
||||
|
|
|
@ -24,6 +24,8 @@ namespace JS::Bytecode {
|
|||
struct PropertyLookupCache {
|
||||
WeakPtr<Shape> shape;
|
||||
Optional<u32> property_offset;
|
||||
WeakPtr<Object> prototype;
|
||||
WeakPtr<PrototypeChainValidity> prototype_chain_validity;
|
||||
};
|
||||
|
||||
struct GlobalVariableCache : public PropertyLookupCache {
|
||||
|
|
|
@ -217,6 +217,7 @@ class Symbol;
|
|||
class Token;
|
||||
class Utf16String;
|
||||
class VM;
|
||||
class PrototypeChainValidity;
|
||||
class Value;
|
||||
class WeakContainer;
|
||||
class WrappedFunction;
|
||||
|
|
|
@ -350,17 +350,17 @@ void ECMAScriptFunctionObject::initialize(Realm& realm)
|
|||
Object* prototype = nullptr;
|
||||
switch (m_kind) {
|
||||
case FunctionKind::Normal:
|
||||
prototype = vm.heap().allocate<Object>(realm, realm.intrinsics().new_ordinary_function_prototype_object_shape());
|
||||
prototype = Object::create_prototype(realm, realm.intrinsics().object_prototype());
|
||||
MUST(prototype->define_property_or_throw(vm.names.constructor, { .value = this, .writable = true, .enumerable = false, .configurable = true }));
|
||||
break;
|
||||
case FunctionKind::Generator:
|
||||
// prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
|
||||
prototype = Object::create(realm, realm.intrinsics().generator_function_prototype_prototype());
|
||||
prototype = Object::create_prototype(realm, realm.intrinsics().generator_function_prototype_prototype());
|
||||
break;
|
||||
case FunctionKind::Async:
|
||||
break;
|
||||
case FunctionKind::AsyncGenerator:
|
||||
prototype = Object::create(realm, realm.intrinsics().async_generator_function_prototype_prototype());
|
||||
prototype = Object::create_prototype(realm, realm.intrinsics().async_generator_function_prototype_prototype());
|
||||
break;
|
||||
}
|
||||
// 27.7.4 AsyncFunction Instances, https://tc39.es/ecma262/#sec-async-function-instances
|
||||
|
|
|
@ -227,7 +227,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> FunctionConstructor::create_dynamic
|
|||
// 30. If kind is generator, then
|
||||
if (kind == FunctionKind::Generator) {
|
||||
// a. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%).
|
||||
prototype = Object::create(realm, realm.intrinsics().generator_function_prototype_prototype());
|
||||
prototype = Object::create_prototype(realm, realm.intrinsics().generator_function_prototype_prototype());
|
||||
|
||||
// b. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
|
||||
function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
|
||||
|
@ -235,7 +235,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> FunctionConstructor::create_dynamic
|
|||
// 31. Else if kind is asyncGenerator, then
|
||||
else if (kind == FunctionKind::AsyncGenerator) {
|
||||
// a. Let prototype be OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
|
||||
prototype = Object::create(realm, realm.intrinsics().async_generator_function_prototype_prototype());
|
||||
prototype = Object::create_prototype(realm, realm.intrinsics().async_generator_function_prototype_prototype());
|
||||
|
||||
// b. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
|
||||
function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
|
||||
|
@ -243,7 +243,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> FunctionConstructor::create_dynamic
|
|||
// 32. Else if kind is normal, perform MakeConstructor(F).
|
||||
else if (kind == FunctionKind::Normal) {
|
||||
// FIXME: Implement MakeConstructor
|
||||
prototype = Object::create(realm, realm.intrinsics().object_prototype());
|
||||
prototype = Object::create_prototype(realm, realm.intrinsics().object_prototype());
|
||||
prototype->define_direct_property(vm.names.constructor, function, Attribute::Writable | Attribute::Configurable);
|
||||
function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
|
||||
}
|
||||
|
|
|
@ -181,15 +181,13 @@ ThrowCompletionOr<void> Intrinsics::initialize_intrinsics(Realm& realm)
|
|||
// These are done first since other prototypes depend on their presence.
|
||||
m_empty_object_shape = heap().allocate_without_realm<Shape>(realm);
|
||||
m_object_prototype = heap().allocate_without_realm<ObjectPrototype>(realm);
|
||||
m_object_prototype->convert_to_prototype_if_needed();
|
||||
m_function_prototype = heap().allocate_without_realm<FunctionPrototype>(realm);
|
||||
m_function_prototype->convert_to_prototype_if_needed();
|
||||
|
||||
m_new_object_shape = heap().allocate_without_realm<Shape>(realm);
|
||||
m_new_object_shape->set_prototype_without_transition(m_object_prototype);
|
||||
|
||||
m_new_ordinary_function_prototype_object_shape = heap().allocate_without_realm<Shape>(realm);
|
||||
m_new_ordinary_function_prototype_object_shape->set_prototype_without_transition(m_object_prototype);
|
||||
m_new_ordinary_function_prototype_object_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable);
|
||||
|
||||
// OPTIMIZATION: A lot of runtime algorithms create an "iterator result" object.
|
||||
// We pre-bake a shape for these objects and remember the property offsets.
|
||||
// This allows us to construct them very quickly.
|
||||
|
@ -367,7 +365,6 @@ void Intrinsics::visit_edges(Visitor& visitor)
|
|||
visitor.visit(m_realm);
|
||||
visitor.visit(m_empty_object_shape);
|
||||
visitor.visit(m_new_object_shape);
|
||||
visitor.visit(m_new_ordinary_function_prototype_object_shape);
|
||||
visitor.visit(m_iterator_result_object_shape);
|
||||
visitor.visit(m_proxy_constructor);
|
||||
visitor.visit(m_async_from_sync_iterator_prototype);
|
||||
|
|
|
@ -21,7 +21,6 @@ public:
|
|||
NonnullGCPtr<Shape> empty_object_shape() { return *m_empty_object_shape; }
|
||||
|
||||
NonnullGCPtr<Shape> new_object_shape() { return *m_new_object_shape; }
|
||||
NonnullGCPtr<Shape> new_ordinary_function_prototype_object_shape() { return *m_new_ordinary_function_prototype_object_shape; }
|
||||
|
||||
[[nodiscard]] NonnullGCPtr<Shape> iterator_result_object_shape() { return *m_iterator_result_object_shape; }
|
||||
[[nodiscard]] u32 iterator_result_object_value_offset() { return m_iterator_result_object_value_offset; }
|
||||
|
@ -125,7 +124,6 @@ private:
|
|||
|
||||
GCPtr<Shape> m_empty_object_shape;
|
||||
GCPtr<Shape> m_new_object_shape;
|
||||
GCPtr<Shape> m_new_ordinary_function_prototype_object_shape;
|
||||
|
||||
GCPtr<Shape> m_iterator_result_object_shape;
|
||||
u32 m_iterator_result_object_value_offset { 0 };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -37,6 +37,14 @@ NonnullGCPtr<Object> Object::create(Realm& realm, Object* prototype)
|
|||
return realm.heap().allocate<Object>(realm, ConstructWithPrototypeTag::Tag, *prototype);
|
||||
}
|
||||
|
||||
NonnullGCPtr<Object> Object::create_prototype(Realm& realm, Object* prototype)
|
||||
{
|
||||
auto shape = realm.heap().allocate_without_realm<Shape>(realm);
|
||||
if (prototype)
|
||||
shape->set_prototype_without_transition(prototype);
|
||||
return realm.heap().allocate<Object>(realm, shape);
|
||||
}
|
||||
|
||||
NonnullGCPtr<Object> Object::create_with_premade_shape(Shape& shape)
|
||||
{
|
||||
return shape.heap().allocate<Object>(shape.realm(), shape);
|
||||
|
@ -893,7 +901,7 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
|
|||
return js_undefined();
|
||||
|
||||
// c. Return ? parent.[[Get]](P, Receiver).
|
||||
return parent->internal_get(property_key, receiver, nullptr, PropertyLookupPhase::PrototypeChain);
|
||||
return parent->internal_get(property_key, receiver, cacheable_metadata, PropertyLookupPhase::PrototypeChain);
|
||||
}
|
||||
|
||||
// 3. If IsDataDescriptor(desc) is true, return desc.[[Value]].
|
||||
|
@ -904,6 +912,15 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
|
|||
*cacheable_metadata = CacheablePropertyMetadata {
|
||||
.type = CacheablePropertyMetadata::Type::OwnProperty,
|
||||
.property_offset = descriptor->property_offset.value(),
|
||||
.prototype = nullptr,
|
||||
};
|
||||
} else if (phase == PropertyLookupPhase::PrototypeChain) {
|
||||
VERIFY(shape().is_prototype_shape());
|
||||
VERIFY(shape().prototype_chain_validity()->is_valid());
|
||||
*cacheable_metadata = CacheablePropertyMetadata {
|
||||
.type = CacheablePropertyMetadata::Type::InPrototypeChain,
|
||||
.property_offset = descriptor->property_offset.value(),
|
||||
.prototype = this,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -999,6 +1016,7 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
|
|||
*cacheable_metadata = CacheablePropertyMetadata {
|
||||
.type = CacheablePropertyMetadata::Type::OwnProperty,
|
||||
.property_offset = own_descriptor->property_offset.value(),
|
||||
.prototype = nullptr,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1448,4 +1466,11 @@ ThrowCompletionOr<Value> Object::ordinary_to_primitive(Value::PreferredType pref
|
|||
return vm.throw_completion<TypeError>(ErrorType::Convert, "object", preferred_type == Value::PreferredType::String ? "string" : "number");
|
||||
}
|
||||
|
||||
void Object::convert_to_prototype_if_needed()
|
||||
{
|
||||
if (shape().is_prototype_shape())
|
||||
return;
|
||||
set_shape(shape().clone_for_prototype());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -46,9 +46,11 @@ struct CacheablePropertyMetadata {
|
|||
enum class Type {
|
||||
NotCacheable,
|
||||
OwnProperty,
|
||||
InPrototypeChain,
|
||||
};
|
||||
Type type { Type::NotCacheable };
|
||||
Optional<u32> property_offset;
|
||||
GCPtr<Object const> prototype;
|
||||
};
|
||||
|
||||
class Object : public Cell {
|
||||
|
@ -56,6 +58,7 @@ class Object : public Cell {
|
|||
JS_DECLARE_ALLOCATOR(Object);
|
||||
|
||||
public:
|
||||
static NonnullGCPtr<Object> create_prototype(Realm&, Object* prototype);
|
||||
static NonnullGCPtr<Object> create(Realm&, Object* prototype);
|
||||
static NonnullGCPtr<Object> create_with_premade_shape(Shape&);
|
||||
|
||||
|
@ -215,6 +218,8 @@ public:
|
|||
Shape& shape() { return *m_shape; }
|
||||
Shape const& shape() const { return *m_shape; }
|
||||
|
||||
void convert_to_prototype_if_needed();
|
||||
|
||||
template<typename T>
|
||||
bool fast_is() const = delete;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -11,6 +11,15 @@
|
|||
namespace JS {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(Shape);
|
||||
JS_DEFINE_ALLOCATOR(PrototypeChainValidity);
|
||||
|
||||
static HashTable<JS::GCPtr<Shape>> s_all_prototype_shapes;
|
||||
|
||||
Shape::~Shape()
|
||||
{
|
||||
if (m_is_prototype_shape)
|
||||
s_all_prototype_shapes.remove(this);
|
||||
}
|
||||
|
||||
NonnullGCPtr<Shape> Shape::create_cacheable_dictionary_transition()
|
||||
{
|
||||
|
@ -18,6 +27,7 @@ NonnullGCPtr<Shape> Shape::create_cacheable_dictionary_transition()
|
|||
new_shape->m_dictionary = true;
|
||||
new_shape->m_cacheable = true;
|
||||
new_shape->m_prototype = m_prototype;
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
ensure_property_table();
|
||||
new_shape->ensure_property_table();
|
||||
(*new_shape->m_property_table) = *m_property_table;
|
||||
|
@ -31,6 +41,7 @@ NonnullGCPtr<Shape> Shape::create_uncacheable_dictionary_transition()
|
|||
new_shape->m_dictionary = true;
|
||||
new_shape->m_cacheable = true;
|
||||
new_shape->m_prototype = m_prototype;
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
ensure_property_table();
|
||||
new_shape->ensure_property_table();
|
||||
(*new_shape->m_property_table) = *m_property_table;
|
||||
|
@ -38,8 +49,10 @@ NonnullGCPtr<Shape> Shape::create_uncacheable_dictionary_transition()
|
|||
return new_shape;
|
||||
}
|
||||
|
||||
Shape* Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
|
||||
GCPtr<Shape> Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
|
||||
{
|
||||
if (m_is_prototype_shape)
|
||||
return nullptr;
|
||||
if (!m_forward_transitions)
|
||||
return nullptr;
|
||||
auto it = m_forward_transitions->find(key);
|
||||
|
@ -50,11 +63,13 @@ Shape* Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
|
|||
m_forward_transitions->remove(it);
|
||||
return nullptr;
|
||||
}
|
||||
return it->value;
|
||||
return it->value.ptr();
|
||||
}
|
||||
|
||||
GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const& key)
|
||||
{
|
||||
if (m_is_prototype_shape)
|
||||
return nullptr;
|
||||
if (!m_delete_transitions)
|
||||
return nullptr;
|
||||
auto it = m_delete_transitions->find(key);
|
||||
|
@ -68,8 +83,10 @@ GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const&
|
|||
return it->value.ptr();
|
||||
}
|
||||
|
||||
Shape* Shape::get_or_prune_cached_prototype_transition(Object* prototype)
|
||||
GCPtr<Shape> Shape::get_or_prune_cached_prototype_transition(Object* prototype)
|
||||
{
|
||||
if (m_is_prototype_shape)
|
||||
return nullptr;
|
||||
if (!m_prototype_transitions)
|
||||
return nullptr;
|
||||
auto it = m_prototype_transitions->find(prototype);
|
||||
|
@ -80,41 +97,52 @@ Shape* Shape::get_or_prune_cached_prototype_transition(Object* prototype)
|
|||
m_prototype_transitions->remove(it);
|
||||
return nullptr;
|
||||
}
|
||||
return it->value;
|
||||
return it->value.ptr();
|
||||
}
|
||||
|
||||
Shape* Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
|
||||
NonnullGCPtr<Shape> Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
|
||||
{
|
||||
TransitionKey key { property_key, attributes };
|
||||
if (auto* existing_shape = get_or_prune_cached_forward_transition(key))
|
||||
return existing_shape;
|
||||
if (auto existing_shape = get_or_prune_cached_forward_transition(key))
|
||||
return *existing_shape;
|
||||
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Put);
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_is_prototype_shape) {
|
||||
if (!m_forward_transitions)
|
||||
m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
|
||||
m_forward_transitions->set(key, new_shape.ptr());
|
||||
}
|
||||
return new_shape;
|
||||
}
|
||||
|
||||
Shape* Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
|
||||
NonnullGCPtr<Shape> Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
|
||||
{
|
||||
TransitionKey key { property_key, attributes };
|
||||
if (auto* existing_shape = get_or_prune_cached_forward_transition(key))
|
||||
return existing_shape;
|
||||
if (auto existing_shape = get_or_prune_cached_forward_transition(key))
|
||||
return *existing_shape;
|
||||
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Configure);
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_is_prototype_shape) {
|
||||
if (!m_forward_transitions)
|
||||
m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
|
||||
m_forward_transitions->set(key, new_shape.ptr());
|
||||
}
|
||||
return new_shape;
|
||||
}
|
||||
|
||||
Shape* Shape::create_prototype_transition(Object* new_prototype)
|
||||
NonnullGCPtr<Shape> Shape::create_prototype_transition(Object* new_prototype)
|
||||
{
|
||||
if (auto* existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
|
||||
return existing_shape;
|
||||
if (new_prototype)
|
||||
new_prototype->convert_to_prototype_if_needed();
|
||||
if (auto existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
|
||||
return *existing_shape;
|
||||
auto new_shape = heap().allocate_without_realm<Shape>(*this, new_prototype);
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_is_prototype_shape) {
|
||||
if (!m_prototype_transitions)
|
||||
m_prototype_transitions = make<HashMap<GCPtr<Object>, WeakPtr<Shape>>>();
|
||||
m_prototype_transitions->set(new_prototype, new_shape.ptr());
|
||||
}
|
||||
return new_shape;
|
||||
}
|
||||
|
||||
|
@ -178,6 +206,8 @@ void Shape::visit_edges(Cell::Visitor& visitor)
|
|||
for (auto& it : *m_delete_transitions)
|
||||
it.key.visit_edges(visitor);
|
||||
}
|
||||
|
||||
visitor.visit(m_prototype_chain_validity);
|
||||
}
|
||||
|
||||
Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) const
|
||||
|
@ -245,6 +275,7 @@ NonnullGCPtr<Shape> Shape::create_delete_transition(StringOrSymbol const& proper
|
|||
if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
|
||||
return *existing_shape;
|
||||
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, TransitionType::Delete);
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_delete_transitions)
|
||||
m_delete_transitions = make<HashMap<StringOrSymbol, WeakPtr<Shape>>>();
|
||||
m_delete_transitions->set(property_key, new_shape.ptr());
|
||||
|
@ -290,4 +321,77 @@ void Shape::remove_property_without_transition(StringOrSymbol const& property_ke
|
|||
}
|
||||
}
|
||||
|
||||
NonnullGCPtr<Shape> Shape::create_for_prototype(NonnullGCPtr<Realm> realm, GCPtr<Object> prototype)
|
||||
{
|
||||
auto new_shape = realm->heap().allocate_without_realm<Shape>(realm);
|
||||
s_all_prototype_shapes.set(new_shape);
|
||||
new_shape->m_is_prototype_shape = true;
|
||||
new_shape->m_prototype = prototype;
|
||||
new_shape->m_prototype_chain_validity = realm->heap().allocate_without_realm<PrototypeChainValidity>();
|
||||
return new_shape;
|
||||
}
|
||||
|
||||
NonnullGCPtr<Shape> Shape::clone_for_prototype()
|
||||
{
|
||||
VERIFY(!m_is_prototype_shape);
|
||||
VERIFY(!m_prototype_chain_validity);
|
||||
auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
|
||||
s_all_prototype_shapes.set(new_shape);
|
||||
new_shape->m_is_prototype_shape = true;
|
||||
new_shape->m_prototype = m_prototype;
|
||||
ensure_property_table();
|
||||
new_shape->ensure_property_table();
|
||||
(*new_shape->m_property_table) = *m_property_table;
|
||||
new_shape->m_property_count = new_shape->m_property_table->size();
|
||||
new_shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
|
||||
return new_shape;
|
||||
}
|
||||
|
||||
void Shape::set_prototype_without_transition(Object* new_prototype)
|
||||
{
|
||||
VERIFY(new_prototype);
|
||||
new_prototype->convert_to_prototype_if_needed();
|
||||
m_prototype = new_prototype;
|
||||
}
|
||||
|
||||
void Shape::set_prototype_shape()
|
||||
{
|
||||
VERIFY(!m_is_prototype_shape);
|
||||
s_all_prototype_shapes.set(this);
|
||||
m_is_prototype_shape = true;
|
||||
m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
|
||||
}
|
||||
|
||||
void Shape::invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape)
|
||||
{
|
||||
if (!m_is_prototype_shape)
|
||||
return;
|
||||
new_prototype_shape->set_prototype_shape();
|
||||
m_prototype_chain_validity->set_valid(false);
|
||||
|
||||
invalidate_all_prototype_chains_leading_to_this();
|
||||
}
|
||||
|
||||
void Shape::invalidate_all_prototype_chains_leading_to_this()
|
||||
{
|
||||
HashTable<Shape*> shapes_to_invalidate;
|
||||
for (auto& candidate : s_all_prototype_shapes) {
|
||||
if (!candidate->m_prototype)
|
||||
continue;
|
||||
for (auto* current_prototype_shape = &candidate->m_prototype->shape(); current_prototype_shape; current_prototype_shape = current_prototype_shape->prototype() ? ¤t_prototype_shape->prototype()->shape() : nullptr) {
|
||||
if (current_prototype_shape == this) {
|
||||
VERIFY(candidate->m_is_prototype_shape);
|
||||
shapes_to_invalidate.set(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shapes_to_invalidate.is_empty())
|
||||
return;
|
||||
for (auto* shape : shapes_to_invalidate) {
|
||||
shape->m_prototype_chain_validity->set_valid(false);
|
||||
shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -34,12 +34,25 @@ struct TransitionKey {
|
|||
}
|
||||
};
|
||||
|
||||
class PrototypeChainValidity final : public Cell {
|
||||
JS_CELL(PrototypeChainValidity, Cell);
|
||||
JS_DECLARE_ALLOCATOR(PrototypeChainValidity);
|
||||
|
||||
public:
|
||||
[[nodiscard]] bool is_valid() const { return m_valid; }
|
||||
void set_valid(bool valid) { m_valid = valid; }
|
||||
|
||||
private:
|
||||
bool m_valid { true };
|
||||
size_t padding { 0 };
|
||||
};
|
||||
|
||||
class Shape final : public Cell {
|
||||
JS_CELL(Shape, Cell);
|
||||
JS_DECLARE_ALLOCATOR(Shape);
|
||||
|
||||
public:
|
||||
virtual ~Shape() override = default;
|
||||
virtual ~Shape() override;
|
||||
|
||||
enum class TransitionType : u8 {
|
||||
Invalid,
|
||||
|
@ -51,12 +64,14 @@ public:
|
|||
UncacheableDictionary,
|
||||
};
|
||||
|
||||
Shape* create_put_transition(StringOrSymbol const&, PropertyAttributes attributes);
|
||||
Shape* create_configure_transition(StringOrSymbol const&, PropertyAttributes attributes);
|
||||
Shape* create_prototype_transition(Object* new_prototype);
|
||||
[[nodiscard]] NonnullGCPtr<Shape> create_put_transition(StringOrSymbol const&, PropertyAttributes attributes);
|
||||
[[nodiscard]] NonnullGCPtr<Shape> create_configure_transition(StringOrSymbol const&, PropertyAttributes attributes);
|
||||
[[nodiscard]] NonnullGCPtr<Shape> create_prototype_transition(Object* new_prototype);
|
||||
[[nodiscard]] NonnullGCPtr<Shape> create_delete_transition(StringOrSymbol const&);
|
||||
[[nodiscard]] NonnullGCPtr<Shape> create_cacheable_dictionary_transition();
|
||||
[[nodiscard]] NonnullGCPtr<Shape> create_uncacheable_dictionary_transition();
|
||||
[[nodiscard]] NonnullGCPtr<Shape> clone_for_prototype();
|
||||
[[nodiscard]] static NonnullGCPtr<Shape> create_for_prototype(NonnullGCPtr<Realm>, GCPtr<Object> prototype);
|
||||
|
||||
void add_property_without_transition(StringOrSymbol const&, PropertyAttributes);
|
||||
void add_property_without_transition(PropertyKey const&, PropertyAttributes);
|
||||
|
@ -69,6 +84,11 @@ public:
|
|||
[[nodiscard]] bool is_cacheable_dictionary() const { return m_dictionary && m_cacheable; }
|
||||
[[nodiscard]] bool is_uncacheable_dictionary() const { return m_dictionary && !m_cacheable; }
|
||||
|
||||
[[nodiscard]] bool is_prototype_shape() const { return m_is_prototype_shape; }
|
||||
void set_prototype_shape();
|
||||
|
||||
GCPtr<PrototypeChainValidity> prototype_chain_validity() const { return m_prototype_chain_validity; }
|
||||
|
||||
Realm& realm() const { return m_realm; }
|
||||
|
||||
Object* prototype() { return m_prototype; }
|
||||
|
@ -83,7 +103,7 @@ public:
|
|||
PropertyMetadata value;
|
||||
};
|
||||
|
||||
void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
|
||||
void set_prototype_without_transition(Object* new_prototype);
|
||||
|
||||
private:
|
||||
explicit Shape(Realm&);
|
||||
|
@ -91,10 +111,13 @@ private:
|
|||
Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType);
|
||||
Shape(Shape& previous_shape, Object* new_prototype);
|
||||
|
||||
void invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape);
|
||||
void invalidate_all_prototype_chains_leading_to_this();
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
Shape* get_or_prune_cached_forward_transition(TransitionKey const&);
|
||||
Shape* get_or_prune_cached_prototype_transition(Object* prototype);
|
||||
[[nodiscard]] GCPtr<Shape> get_or_prune_cached_forward_transition(TransitionKey const&);
|
||||
[[nodiscard]] GCPtr<Shape> get_or_prune_cached_prototype_transition(Object* prototype);
|
||||
[[nodiscard]] GCPtr<Shape> get_or_prune_cached_delete_transition(StringOrSymbol const&);
|
||||
|
||||
void ensure_property_table() const;
|
||||
|
@ -109,13 +132,17 @@ private:
|
|||
GCPtr<Shape> m_previous;
|
||||
StringOrSymbol m_property_key;
|
||||
GCPtr<Object> m_prototype;
|
||||
|
||||
GCPtr<PrototypeChainValidity> m_prototype_chain_validity;
|
||||
|
||||
u32 m_property_count { 0 };
|
||||
|
||||
PropertyAttributes m_attributes { 0 };
|
||||
TransitionType m_transition_type { TransitionType::Invalid };
|
||||
|
||||
bool m_dictionary { false };
|
||||
bool m_cacheable { true };
|
||||
bool m_dictionary : 1 { false };
|
||||
bool m_cacheable : 1 { true };
|
||||
bool m_is_prototype_shape : 1 { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue