From 8ff16c1b5703a380a3ddbd945d1dbebd8532ad89 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 4 May 2024 15:48:23 +0200 Subject: [PATCH] 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. --- Userland/Libraries/LibJS/AST.cpp | 2 +- .../LibJS/Bytecode/CommonImplementations.h | 25 ++- .../Libraries/LibJS/Bytecode/Executable.h | 2 + Userland/Libraries/LibJS/Forward.h | 1 + .../Runtime/ECMAScriptFunctionObject.cpp | 6 +- .../LibJS/Runtime/FunctionConstructor.cpp | 6 +- .../Libraries/LibJS/Runtime/Intrinsics.cpp | 7 +- Userland/Libraries/LibJS/Runtime/Intrinsics.h | 2 - Userland/Libraries/LibJS/Runtime/Object.cpp | 29 +++- Userland/Libraries/LibJS/Runtime/Object.h | 7 +- Userland/Libraries/LibJS/Runtime/Shape.cpp | 150 +++++++++++++++--- Userland/Libraries/LibJS/Runtime/Shape.h | 47 ++++-- 12 files changed, 232 insertions(+), 52 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 7b118a350ef..6de37f0573c 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -333,7 +333,7 @@ ThrowCompletionOr 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; diff --git a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h index 5bec2c919ff..672ea0b660a 100644 --- a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h +++ b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h @@ -118,9 +118,23 @@ inline ThrowCompletionOr get_by_id(VM& vm, Optionalindexed_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 get_by_id(VM& vm, Optionalinternal_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; diff --git a/Userland/Libraries/LibJS/Bytecode/Executable.h b/Userland/Libraries/LibJS/Bytecode/Executable.h index e593ccd851c..064ac0b50bc 100644 --- a/Userland/Libraries/LibJS/Bytecode/Executable.h +++ b/Userland/Libraries/LibJS/Bytecode/Executable.h @@ -24,6 +24,8 @@ namespace JS::Bytecode { struct PropertyLookupCache { WeakPtr shape; Optional property_offset; + WeakPtr prototype; + WeakPtr prototype_chain_validity; }; struct GlobalVariableCache : public PropertyLookupCache { diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 06e425b3c52..6e3d9c6a8b5 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -217,6 +217,7 @@ class Symbol; class Token; class Utf16String; class VM; +class PrototypeChainValidity; class Value; class WeakContainer; class WrappedFunction; diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index a54097bba7c..3b101047688 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -350,17 +350,17 @@ void ECMAScriptFunctionObject::initialize(Realm& realm) Object* prototype = nullptr; switch (m_kind) { case FunctionKind::Normal: - prototype = vm.heap().allocate(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 diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp index 40cbad3bbf6..bf53edc1a03 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp @@ -227,7 +227,7 @@ ThrowCompletionOr 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 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 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); } diff --git a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp index 3a327924e07..c74fbbd4f6f 100644 --- a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -181,15 +181,13 @@ ThrowCompletionOr Intrinsics::initialize_intrinsics(Realm& realm) // These are done first since other prototypes depend on their presence. m_empty_object_shape = heap().allocate_without_realm(realm); m_object_prototype = heap().allocate_without_realm(realm); + m_object_prototype->convert_to_prototype_if_needed(); m_function_prototype = heap().allocate_without_realm(realm); + m_function_prototype->convert_to_prototype_if_needed(); m_new_object_shape = heap().allocate_without_realm(realm); m_new_object_shape->set_prototype_without_transition(m_object_prototype); - m_new_ordinary_function_prototype_object_shape = heap().allocate_without_realm(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); diff --git a/Userland/Libraries/LibJS/Runtime/Intrinsics.h b/Userland/Libraries/LibJS/Runtime/Intrinsics.h index 6a5ba36c803..5b2ff4f4937 100644 --- a/Userland/Libraries/LibJS/Runtime/Intrinsics.h +++ b/Userland/Libraries/LibJS/Runtime/Intrinsics.h @@ -21,7 +21,6 @@ public: NonnullGCPtr empty_object_shape() { return *m_empty_object_shape; } NonnullGCPtr new_object_shape() { return *m_new_object_shape; } - NonnullGCPtr new_ordinary_function_prototype_object_shape() { return *m_new_ordinary_function_prototype_object_shape; } [[nodiscard]] NonnullGCPtr 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 m_empty_object_shape; GCPtr m_new_object_shape; - GCPtr m_new_ordinary_function_prototype_object_shape; GCPtr m_iterator_result_object_shape; u32 m_iterator_result_object_value_offset { 0 }; diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 54d3ea9c1f2..406a3232181 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020-2024, Andreas Kling * Copyright (c) 2020-2023, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause @@ -37,6 +37,14 @@ NonnullGCPtr Object::create(Realm& realm, Object* prototype) return realm.heap().allocate(realm, ConstructWithPrototypeTag::Tag, *prototype); } +NonnullGCPtr Object::create_prototype(Realm& realm, Object* prototype) +{ + auto shape = realm.heap().allocate_without_realm(realm); + if (prototype) + shape->set_prototype_without_transition(prototype); + return realm.heap().allocate(realm, shape); +} + NonnullGCPtr Object::create_with_premade_shape(Shape& shape) { return shape.heap().allocate(shape.realm(), shape); @@ -893,7 +901,7 @@ ThrowCompletionOr 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 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 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 Object::ordinary_to_primitive(Value::PreferredType pref return vm.throw_completion(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()); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 7178fa7b633..b0d95fd3bf6 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020-2024, Andreas Kling * Copyright (c) 2020-2023, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause @@ -46,9 +46,11 @@ struct CacheablePropertyMetadata { enum class Type { NotCacheable, OwnProperty, + InPrototypeChain, }; Type type { Type::NotCacheable }; Optional property_offset; + GCPtr prototype; }; class Object : public Cell { @@ -56,6 +58,7 @@ class Object : public Cell { JS_DECLARE_ALLOCATOR(Object); public: + static NonnullGCPtr create_prototype(Realm&, Object* prototype); static NonnullGCPtr create(Realm&, Object* prototype); static NonnullGCPtr 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 bool fast_is() const = delete; diff --git a/Userland/Libraries/LibJS/Runtime/Shape.cpp b/Userland/Libraries/LibJS/Runtime/Shape.cpp index 919105676a9..edb91308618 100644 --- a/Userland/Libraries/LibJS/Runtime/Shape.cpp +++ b/Userland/Libraries/LibJS/Runtime/Shape.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021, Andreas Kling + * Copyright (c) 2020-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,6 +11,15 @@ namespace JS { JS_DEFINE_ALLOCATOR(Shape); +JS_DEFINE_ALLOCATOR(PrototypeChainValidity); + +static HashTable> s_all_prototype_shapes; + +Shape::~Shape() +{ + if (m_is_prototype_shape) + s_all_prototype_shapes.remove(this); +} NonnullGCPtr Shape::create_cacheable_dictionary_transition() { @@ -18,6 +27,7 @@ NonnullGCPtr 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::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::create_uncacheable_dictionary_transition() return new_shape; } -Shape* Shape::get_or_prune_cached_forward_transition(TransitionKey const& key) +GCPtr 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::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::get_or_prune_cached_delete_transition(StringOrSymbol const& return it->value.ptr(); } -Shape* Shape::get_or_prune_cached_prototype_transition(Object* prototype) +GCPtr 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::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(*this, property_key, attributes, TransitionType::Put); - if (!m_forward_transitions) - m_forward_transitions = make>>(); - m_forward_transitions->set(key, new_shape.ptr()); + invalidate_prototype_if_needed_for_new_prototype(new_shape); + if (!m_is_prototype_shape) { + if (!m_forward_transitions) + m_forward_transitions = make>>(); + m_forward_transitions->set(key, new_shape.ptr()); + } return new_shape; } -Shape* Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes) +NonnullGCPtr 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(*this, property_key, attributes, TransitionType::Configure); - if (!m_forward_transitions) - m_forward_transitions = make>>(); - m_forward_transitions->set(key, new_shape.ptr()); + invalidate_prototype_if_needed_for_new_prototype(new_shape); + if (!m_is_prototype_shape) { + if (!m_forward_transitions) + m_forward_transitions = make>>(); + m_forward_transitions->set(key, new_shape.ptr()); + } return new_shape; } -Shape* Shape::create_prototype_transition(Object* new_prototype) +NonnullGCPtr 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(*this, new_prototype); - if (!m_prototype_transitions) - m_prototype_transitions = make, WeakPtr>>(); - m_prototype_transitions->set(new_prototype, new_shape.ptr()); + invalidate_prototype_if_needed_for_new_prototype(new_shape); + if (!m_is_prototype_shape) { + if (!m_prototype_transitions) + m_prototype_transitions = make, WeakPtr>>(); + 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 Shape::lookup(StringOrSymbol const& property_key) const @@ -245,6 +275,7 @@ NonnullGCPtr 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(*this, property_key, TransitionType::Delete); + invalidate_prototype_if_needed_for_new_prototype(new_shape); if (!m_delete_transitions) m_delete_transitions = make>>(); 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::create_for_prototype(NonnullGCPtr realm, GCPtr prototype) +{ + auto new_shape = realm->heap().allocate_without_realm(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(); + return new_shape; +} + +NonnullGCPtr Shape::clone_for_prototype() +{ + VERIFY(!m_is_prototype_shape); + VERIFY(!m_prototype_chain_validity); + auto new_shape = heap().allocate_without_realm(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(); + 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(); +} + +void Shape::invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr 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 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(); + } +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Shape.h b/Userland/Libraries/LibJS/Runtime/Shape.h index dbbb66076cf..fbd861c52a8 100644 --- a/Userland/Libraries/LibJS/Runtime/Shape.h +++ b/Userland/Libraries/LibJS/Runtime/Shape.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021, Andreas Kling + * Copyright (c) 2020-2024, Andreas Kling * * 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 create_put_transition(StringOrSymbol const&, PropertyAttributes attributes); + [[nodiscard]] NonnullGCPtr create_configure_transition(StringOrSymbol const&, PropertyAttributes attributes); + [[nodiscard]] NonnullGCPtr create_prototype_transition(Object* new_prototype); [[nodiscard]] NonnullGCPtr create_delete_transition(StringOrSymbol const&); [[nodiscard]] NonnullGCPtr create_cacheable_dictionary_transition(); [[nodiscard]] NonnullGCPtr create_uncacheable_dictionary_transition(); + [[nodiscard]] NonnullGCPtr clone_for_prototype(); + [[nodiscard]] static NonnullGCPtr create_for_prototype(NonnullGCPtr, GCPtr 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 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 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 get_or_prune_cached_forward_transition(TransitionKey const&); + [[nodiscard]] GCPtr get_or_prune_cached_prototype_transition(Object* prototype); [[nodiscard]] GCPtr get_or_prune_cached_delete_transition(StringOrSymbol const&); void ensure_property_table() const; @@ -109,13 +132,17 @@ private: GCPtr m_previous; StringOrSymbol m_property_key; GCPtr m_prototype; + + GCPtr 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 }; }; }