소스 검색

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.
Andreas Kling 1 년 전
부모
커밋
8ff16c1b57

+ 1 - 1
Userland/Libraries/LibJS/AST.cpp

@@ -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);
     VERIFY(prototype);
 
 
     vm.running_execution_context().lexical_environment = class_environment;
     vm.running_execution_context().lexical_environment = class_environment;

+ 23 - 2
Userland/Libraries/LibJS/Bytecode/CommonImplementations.h

@@ -118,9 +118,23 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<DeprecatedFlyString c
         return Value { base_obj->indexed_properties().array_like_size() };
         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();
     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());
         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));
     auto value = TRY(base_obj->internal_get(property, this_value, &cacheable_metadata));
 
 
     if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
     if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
+        cache = {};
         cache.shape = shape;
         cache.shape = shape;
         cache.property_offset = cacheable_metadata.property_offset.value();
         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;
     return value;

+ 2 - 0
Userland/Libraries/LibJS/Bytecode/Executable.h

@@ -24,6 +24,8 @@ namespace JS::Bytecode {
 struct PropertyLookupCache {
 struct PropertyLookupCache {
     WeakPtr<Shape> shape;
     WeakPtr<Shape> shape;
     Optional<u32> property_offset;
     Optional<u32> property_offset;
+    WeakPtr<Object> prototype;
+    WeakPtr<PrototypeChainValidity> prototype_chain_validity;
 };
 };
 
 
 struct GlobalVariableCache : public PropertyLookupCache {
 struct GlobalVariableCache : public PropertyLookupCache {

+ 1 - 0
Userland/Libraries/LibJS/Forward.h

@@ -217,6 +217,7 @@ class Symbol;
 class Token;
 class Token;
 class Utf16String;
 class Utf16String;
 class VM;
 class VM;
+class PrototypeChainValidity;
 class Value;
 class Value;
 class WeakContainer;
 class WeakContainer;
 class WrappedFunction;
 class WrappedFunction;

+ 3 - 3
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp

@@ -350,17 +350,17 @@ void ECMAScriptFunctionObject::initialize(Realm& realm)
         Object* prototype = nullptr;
         Object* prototype = nullptr;
         switch (m_kind) {
         switch (m_kind) {
         case FunctionKind::Normal:
         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 }));
             MUST(prototype->define_property_or_throw(vm.names.constructor, { .value = this, .writable = true, .enumerable = false, .configurable = true }));
             break;
             break;
         case FunctionKind::Generator:
         case FunctionKind::Generator:
             // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
             // 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;
             break;
         case FunctionKind::Async:
         case FunctionKind::Async:
             break;
             break;
         case FunctionKind::AsyncGenerator:
         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;
             break;
         }
         }
         // 27.7.4 AsyncFunction Instances, https://tc39.es/ecma262/#sec-async-function-instances
         // 27.7.4 AsyncFunction Instances, https://tc39.es/ecma262/#sec-async-function-instances

+ 3 - 3
Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp

@@ -227,7 +227,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> FunctionConstructor::create_dynamic
     // 30. If kind is generator, then
     // 30. If kind is generator, then
     if (kind == FunctionKind::Generator) {
     if (kind == FunctionKind::Generator) {
         // a. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%).
         // 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 }).
         // b. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
         function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
         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
     // 31. Else if kind is asyncGenerator, then
     else if (kind == FunctionKind::AsyncGenerator) {
     else if (kind == FunctionKind::AsyncGenerator) {
         // a. Let prototype be OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
         // 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 }).
         // b. Perform ! DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
         function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
         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).
     // 32. Else if kind is normal, perform MakeConstructor(F).
     else if (kind == FunctionKind::Normal) {
     else if (kind == FunctionKind::Normal) {
         // FIXME: Implement MakeConstructor
         // 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);
         prototype->define_direct_property(vm.names.constructor, function, Attribute::Writable | Attribute::Configurable);
         function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
         function->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
     }
     }

+ 2 - 5
Userland/Libraries/LibJS/Runtime/Intrinsics.cpp

@@ -181,15 +181,13 @@ ThrowCompletionOr<void> Intrinsics::initialize_intrinsics(Realm& realm)
     // These are done first since other prototypes depend on their presence.
     // These are done first since other prototypes depend on their presence.
     m_empty_object_shape = heap().allocate_without_realm<Shape>(realm);
     m_empty_object_shape = heap().allocate_without_realm<Shape>(realm);
     m_object_prototype = heap().allocate_without_realm<ObjectPrototype>(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 = 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 = heap().allocate_without_realm<Shape>(realm);
     m_new_object_shape->set_prototype_without_transition(m_object_prototype);
     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.
     // OPTIMIZATION: A lot of runtime algorithms create an "iterator result" object.
     //               We pre-bake a shape for these objects and remember the property offsets.
     //               We pre-bake a shape for these objects and remember the property offsets.
     //               This allows us to construct them very quickly.
     //               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_realm);
     visitor.visit(m_empty_object_shape);
     visitor.visit(m_empty_object_shape);
     visitor.visit(m_new_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_iterator_result_object_shape);
     visitor.visit(m_proxy_constructor);
     visitor.visit(m_proxy_constructor);
     visitor.visit(m_async_from_sync_iterator_prototype);
     visitor.visit(m_async_from_sync_iterator_prototype);

+ 0 - 2
Userland/Libraries/LibJS/Runtime/Intrinsics.h

@@ -21,7 +21,6 @@ public:
     NonnullGCPtr<Shape> empty_object_shape() { return *m_empty_object_shape; }
     NonnullGCPtr<Shape> empty_object_shape() { return *m_empty_object_shape; }
 
 
     NonnullGCPtr<Shape> new_object_shape() { return *m_new_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]] 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; }
     [[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_empty_object_shape;
     GCPtr<Shape> m_new_object_shape;
     GCPtr<Shape> m_new_object_shape;
-    GCPtr<Shape> m_new_ordinary_function_prototype_object_shape;
 
 
     GCPtr<Shape> m_iterator_result_object_shape;
     GCPtr<Shape> m_iterator_result_object_shape;
     u32 m_iterator_result_object_value_offset { 0 };
     u32 m_iterator_result_object_value_offset { 0 };

+ 27 - 2
Userland/Libraries/LibJS/Runtime/Object.cpp

@@ -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>
  * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * 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);
     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)
 NonnullGCPtr<Object> Object::create_with_premade_shape(Shape& shape)
 {
 {
     return shape.heap().allocate<Object>(shape.realm(), 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();
             return js_undefined();
 
 
         // c. Return ? parent.[[Get]](P, Receiver).
         // 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]].
     // 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 {
                 *cacheable_metadata = CacheablePropertyMetadata {
                     .type = CacheablePropertyMetadata::Type::OwnProperty,
                     .type = CacheablePropertyMetadata::Type::OwnProperty,
                     .property_offset = descriptor->property_offset.value(),
                     .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 {
                 *cacheable_metadata = CacheablePropertyMetadata {
                     .type = CacheablePropertyMetadata::Type::OwnProperty,
                     .type = CacheablePropertyMetadata::Type::OwnProperty,
                     .property_offset = own_descriptor->property_offset.value(),
                     .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");
     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());
+}
+
 }
 }

+ 6 - 1
Userland/Libraries/LibJS/Runtime/Object.h

@@ -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>
  * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
@@ -46,9 +46,11 @@ struct CacheablePropertyMetadata {
     enum class Type {
     enum class Type {
         NotCacheable,
         NotCacheable,
         OwnProperty,
         OwnProperty,
+        InPrototypeChain,
     };
     };
     Type type { Type::NotCacheable };
     Type type { Type::NotCacheable };
     Optional<u32> property_offset;
     Optional<u32> property_offset;
+    GCPtr<Object const> prototype;
 };
 };
 
 
 class Object : public Cell {
 class Object : public Cell {
@@ -56,6 +58,7 @@ class Object : public Cell {
     JS_DECLARE_ALLOCATOR(Object);
     JS_DECLARE_ALLOCATOR(Object);
 
 
 public:
 public:
+    static NonnullGCPtr<Object> create_prototype(Realm&, Object* prototype);
     static NonnullGCPtr<Object> create(Realm&, Object* prototype);
     static NonnullGCPtr<Object> create(Realm&, Object* prototype);
     static NonnullGCPtr<Object> create_with_premade_shape(Shape&);
     static NonnullGCPtr<Object> create_with_premade_shape(Shape&);
 
 
@@ -215,6 +218,8 @@ public:
     Shape& shape() { return *m_shape; }
     Shape& shape() { return *m_shape; }
     Shape const& shape() const { return *m_shape; }
     Shape const& shape() const { return *m_shape; }
 
 
+    void convert_to_prototype_if_needed();
+
     template<typename T>
     template<typename T>
     bool fast_is() const = delete;
     bool fast_is() const = delete;
 
 

+ 127 - 23
Userland/Libraries/LibJS/Runtime/Shape.cpp

@@ -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
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -11,6 +11,15 @@
 namespace JS {
 namespace JS {
 
 
 JS_DEFINE_ALLOCATOR(Shape);
 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()
 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_dictionary = true;
     new_shape->m_cacheable = true;
     new_shape->m_cacheable = true;
     new_shape->m_prototype = m_prototype;
     new_shape->m_prototype = m_prototype;
+    invalidate_prototype_if_needed_for_new_prototype(new_shape);
     ensure_property_table();
     ensure_property_table();
     new_shape->ensure_property_table();
     new_shape->ensure_property_table();
     (*new_shape->m_property_table) = *m_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_dictionary = true;
     new_shape->m_cacheable = true;
     new_shape->m_cacheable = true;
     new_shape->m_prototype = m_prototype;
     new_shape->m_prototype = m_prototype;
+    invalidate_prototype_if_needed_for_new_prototype(new_shape);
     ensure_property_table();
     ensure_property_table();
     new_shape->ensure_property_table();
     new_shape->ensure_property_table();
     (*new_shape->m_property_table) = *m_property_table;
     (*new_shape->m_property_table) = *m_property_table;
@@ -38,8 +49,10 @@ NonnullGCPtr<Shape> Shape::create_uncacheable_dictionary_transition()
     return new_shape;
     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)
     if (!m_forward_transitions)
         return nullptr;
         return nullptr;
     auto it = m_forward_transitions->find(key);
     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);
         m_forward_transitions->remove(it);
         return nullptr;
         return nullptr;
     }
     }
-    return it->value;
+    return it->value.ptr();
 }
 }
 
 
 GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const& key)
 GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const& key)
 {
 {
+    if (m_is_prototype_shape)
+        return nullptr;
     if (!m_delete_transitions)
     if (!m_delete_transitions)
         return nullptr;
         return nullptr;
     auto it = m_delete_transitions->find(key);
     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();
     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)
     if (!m_prototype_transitions)
         return nullptr;
         return nullptr;
     auto it = m_prototype_transitions->find(prototype);
     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);
         m_prototype_transitions->remove(it);
         return nullptr;
         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 };
     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);
     auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Put);
-    if (!m_forward_transitions)
-        m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
-    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<HashMap<TransitionKey, WeakPtr<Shape>>>();
+        m_forward_transitions->set(key, new_shape.ptr());
+    }
     return new_shape;
     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 };
     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);
     auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Configure);
-    if (!m_forward_transitions)
-        m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
-    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<HashMap<TransitionKey, WeakPtr<Shape>>>();
+        m_forward_transitions->set(key, new_shape.ptr());
+    }
     return new_shape;
     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);
     auto new_shape = heap().allocate_without_realm<Shape>(*this, new_prototype);
-    if (!m_prototype_transitions)
-        m_prototype_transitions = make<HashMap<GCPtr<Object>, WeakPtr<Shape>>>();
-    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<HashMap<GCPtr<Object>, WeakPtr<Shape>>>();
+        m_prototype_transitions->set(new_prototype, new_shape.ptr());
+    }
     return new_shape;
     return new_shape;
 }
 }
 
 
@@ -178,6 +206,8 @@ void Shape::visit_edges(Cell::Visitor& visitor)
         for (auto& it : *m_delete_transitions)
         for (auto& it : *m_delete_transitions)
             it.key.visit_edges(visitor);
             it.key.visit_edges(visitor);
     }
     }
+
+    visitor.visit(m_prototype_chain_validity);
 }
 }
 
 
 Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) const
 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))
     if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
         return *existing_shape;
         return *existing_shape;
     auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, TransitionType::Delete);
     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)
     if (!m_delete_transitions)
         m_delete_transitions = make<HashMap<StringOrSymbol, WeakPtr<Shape>>>();
         m_delete_transitions = make<HashMap<StringOrSymbol, WeakPtr<Shape>>>();
     m_delete_transitions->set(property_key, new_shape.ptr());
     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() ? &current_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>();
+    }
+}
+
 }
 }

+ 37 - 10
Userland/Libraries/LibJS/Runtime/Shape.h

@@ -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
  * 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 {
 class Shape final : public Cell {
     JS_CELL(Shape, Cell);
     JS_CELL(Shape, Cell);
     JS_DECLARE_ALLOCATOR(Shape);
     JS_DECLARE_ALLOCATOR(Shape);
 
 
 public:
 public:
-    virtual ~Shape() override = default;
+    virtual ~Shape() override;
 
 
     enum class TransitionType : u8 {
     enum class TransitionType : u8 {
         Invalid,
         Invalid,
@@ -51,12 +64,14 @@ public:
         UncacheableDictionary,
         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_delete_transition(StringOrSymbol const&);
     [[nodiscard]] NonnullGCPtr<Shape> create_cacheable_dictionary_transition();
     [[nodiscard]] NonnullGCPtr<Shape> create_cacheable_dictionary_transition();
     [[nodiscard]] NonnullGCPtr<Shape> create_uncacheable_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(StringOrSymbol const&, PropertyAttributes);
     void add_property_without_transition(PropertyKey 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_cacheable_dictionary() const { return m_dictionary && m_cacheable; }
     [[nodiscard]] bool is_uncacheable_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; }
     Realm& realm() const { return m_realm; }
 
 
     Object* prototype() { return m_prototype; }
     Object* prototype() { return m_prototype; }
@@ -83,7 +103,7 @@ public:
         PropertyMetadata value;
         PropertyMetadata value;
     };
     };
 
 
-    void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
+    void set_prototype_without_transition(Object* new_prototype);
 
 
 private:
 private:
     explicit Shape(Realm&);
     explicit Shape(Realm&);
@@ -91,10 +111,13 @@ private:
     Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType);
     Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType);
     Shape(Shape& previous_shape, Object* new_prototype);
     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;
     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&);
     [[nodiscard]] GCPtr<Shape> get_or_prune_cached_delete_transition(StringOrSymbol const&);
 
 
     void ensure_property_table() const;
     void ensure_property_table() const;
@@ -109,13 +132,17 @@ private:
     GCPtr<Shape> m_previous;
     GCPtr<Shape> m_previous;
     StringOrSymbol m_property_key;
     StringOrSymbol m_property_key;
     GCPtr<Object> m_prototype;
     GCPtr<Object> m_prototype;
+
+    GCPtr<PrototypeChainValidity> m_prototype_chain_validity;
+
     u32 m_property_count { 0 };
     u32 m_property_count { 0 };
 
 
     PropertyAttributes m_attributes { 0 };
     PropertyAttributes m_attributes { 0 };
     TransitionType m_transition_type { TransitionType::Invalid };
     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 };
 };
 };
 
 
 }
 }