diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 7b118a350e..6de37f0573 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 5bec2c919f..672ea0b660 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 e593ccd851..064ac0b50b 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 06e425b3c5..6e3d9c6a8b 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 a54097bba7..3b10104768 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 40cbad3bbf..bf53edc1a0 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 3a327924e0..c74fbbd4f6 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 6a5ba36c80..5b2ff4f493 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 54d3ea9c1f..406a323218 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 7178fa7b63..b0d95fd3bf 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 919105676a..edb9130861 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 dbbb66076c..fbd861c52a 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 }; }; }