LibJS: Cache access to properties found in prototype chain

We already had fast access to own properties via shape-based IC.
This patch extends the mechanism to properties on the prototype chain,
using the "validity cell" technique from V8.

- Prototype objects now have unique shape
- Each prototype has an associated PrototypeChainValidity
- When a prototype shape is mutated, every prototype shape "below" it
  in any prototype chain is invalidated.
- Invalidation happens by marking the validity object as invalid,
  and then replacing it with a new validity object.
- Property caches keep a pointer to the last seen valid validity.
  If there is no validity, or the validity is invalid, the cache
  misses and gets repopulated.

This is very helpful when using JavaScript to access DOM objects,
as we frequently have to traverse 4+ prototype objects before finding
the property we're interested in on e.g EventTarget or Node.
This commit is contained in:
Andreas Kling 2024-05-04 15:48:23 +02:00
commit 8ff16c1b57
Notes: sideshowbarker 2024-07-17 05:02:42 +09:00
12 changed files with 232 additions and 52 deletions

View file

@ -333,7 +333,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> ClassExpression::create_class_const
}
}
auto prototype = Object::create(realm, proto_parent);
auto prototype = Object::create_prototype(realm, proto_parent);
VERIFY(prototype);
vm.running_execution_context().lexical_environment = class_environment;

View file

@ -118,9 +118,23 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<DeprecatedFlyString c
return Value { base_obj->indexed_properties().array_like_size() };
}
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
auto& shape = base_obj->shape();
if (&shape == cache.shape) {
if (cache.prototype) {
// OPTIMIZATION: If the prototype chain hasn't been mutated in a way that would invalidate the cache, we can use it.
bool can_use_cache = [&]() -> bool {
if (&shape != cache.shape)
return false;
if (!cache.prototype_chain_validity)
return false;
if (!cache.prototype_chain_validity->is_valid())
return false;
return true;
}();
if (can_use_cache)
return cache.prototype->get_direct(cache.property_offset.value());
} else if (&shape == cache.shape) {
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
return base_obj->get_direct(cache.property_offset.value());
}
@ -128,8 +142,15 @@ inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<DeprecatedFlyString c
auto value = TRY(base_obj->internal_get(property, this_value, &cacheable_metadata));
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
cache = {};
cache.shape = shape;
cache.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
cache = {};
cache.shape = &base_obj->shape();
cache.property_offset = cacheable_metadata.property_offset.value();
cache.prototype = *cacheable_metadata.prototype;
cache.prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
}
return value;

View file

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

View file

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

View file

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

View file

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

View file

@ -181,15 +181,13 @@ ThrowCompletionOr<void> Intrinsics::initialize_intrinsics(Realm& realm)
// These are done first since other prototypes depend on their presence.
m_empty_object_shape = heap().allocate_without_realm<Shape>(realm);
m_object_prototype = heap().allocate_without_realm<ObjectPrototype>(realm);
m_object_prototype->convert_to_prototype_if_needed();
m_function_prototype = heap().allocate_without_realm<FunctionPrototype>(realm);
m_function_prototype->convert_to_prototype_if_needed();
m_new_object_shape = heap().allocate_without_realm<Shape>(realm);
m_new_object_shape->set_prototype_without_transition(m_object_prototype);
m_new_ordinary_function_prototype_object_shape = heap().allocate_without_realm<Shape>(realm);
m_new_ordinary_function_prototype_object_shape->set_prototype_without_transition(m_object_prototype);
m_new_ordinary_function_prototype_object_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable);
// OPTIMIZATION: A lot of runtime algorithms create an "iterator result" object.
// We pre-bake a shape for these objects and remember the property offsets.
// This allows us to construct them very quickly.
@ -367,7 +365,6 @@ void Intrinsics::visit_edges(Visitor& visitor)
visitor.visit(m_realm);
visitor.visit(m_empty_object_shape);
visitor.visit(m_new_object_shape);
visitor.visit(m_new_ordinary_function_prototype_object_shape);
visitor.visit(m_iterator_result_object_shape);
visitor.visit(m_proxy_constructor);
visitor.visit(m_async_from_sync_iterator_prototype);

View file

@ -21,7 +21,6 @@ public:
NonnullGCPtr<Shape> empty_object_shape() { return *m_empty_object_shape; }
NonnullGCPtr<Shape> new_object_shape() { return *m_new_object_shape; }
NonnullGCPtr<Shape> new_ordinary_function_prototype_object_shape() { return *m_new_ordinary_function_prototype_object_shape; }
[[nodiscard]] NonnullGCPtr<Shape> iterator_result_object_shape() { return *m_iterator_result_object_shape; }
[[nodiscard]] u32 iterator_result_object_value_offset() { return m_iterator_result_object_value_offset; }
@ -125,7 +124,6 @@ private:
GCPtr<Shape> m_empty_object_shape;
GCPtr<Shape> m_new_object_shape;
GCPtr<Shape> m_new_ordinary_function_prototype_object_shape;
GCPtr<Shape> m_iterator_result_object_shape;
u32 m_iterator_result_object_value_offset { 0 };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -37,6 +37,14 @@ NonnullGCPtr<Object> Object::create(Realm& realm, Object* prototype)
return realm.heap().allocate<Object>(realm, ConstructWithPrototypeTag::Tag, *prototype);
}
NonnullGCPtr<Object> Object::create_prototype(Realm& realm, Object* prototype)
{
auto shape = realm.heap().allocate_without_realm<Shape>(realm);
if (prototype)
shape->set_prototype_without_transition(prototype);
return realm.heap().allocate<Object>(realm, shape);
}
NonnullGCPtr<Object> Object::create_with_premade_shape(Shape& shape)
{
return shape.heap().allocate<Object>(shape.realm(), shape);
@ -893,7 +901,7 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
return js_undefined();
// c. Return ? parent.[[Get]](P, Receiver).
return parent->internal_get(property_key, receiver, nullptr, PropertyLookupPhase::PrototypeChain);
return parent->internal_get(property_key, receiver, cacheable_metadata, PropertyLookupPhase::PrototypeChain);
}
// 3. If IsDataDescriptor(desc) is true, return desc.[[Value]].
@ -904,6 +912,15 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::OwnProperty,
.property_offset = descriptor->property_offset.value(),
.prototype = nullptr,
};
} else if (phase == PropertyLookupPhase::PrototypeChain) {
VERIFY(shape().is_prototype_shape());
VERIFY(shape().prototype_chain_validity()->is_valid());
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::InPrototypeChain,
.property_offset = descriptor->property_offset.value(),
.prototype = this,
};
}
}
@ -999,6 +1016,7 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
*cacheable_metadata = CacheablePropertyMetadata {
.type = CacheablePropertyMetadata::Type::OwnProperty,
.property_offset = own_descriptor->property_offset.value(),
.prototype = nullptr,
};
}
@ -1448,4 +1466,11 @@ ThrowCompletionOr<Value> Object::ordinary_to_primitive(Value::PreferredType pref
return vm.throw_completion<TypeError>(ErrorType::Convert, "object", preferred_type == Value::PreferredType::String ? "string" : "number");
}
void Object::convert_to_prototype_if_needed()
{
if (shape().is_prototype_shape())
return;
set_shape(shape().clone_for_prototype());
}
}

View file

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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -11,6 +11,15 @@
namespace JS {
JS_DEFINE_ALLOCATOR(Shape);
JS_DEFINE_ALLOCATOR(PrototypeChainValidity);
static HashTable<JS::GCPtr<Shape>> s_all_prototype_shapes;
Shape::~Shape()
{
if (m_is_prototype_shape)
s_all_prototype_shapes.remove(this);
}
NonnullGCPtr<Shape> Shape::create_cacheable_dictionary_transition()
{
@ -18,6 +27,7 @@ NonnullGCPtr<Shape> Shape::create_cacheable_dictionary_transition()
new_shape->m_dictionary = true;
new_shape->m_cacheable = true;
new_shape->m_prototype = m_prototype;
invalidate_prototype_if_needed_for_new_prototype(new_shape);
ensure_property_table();
new_shape->ensure_property_table();
(*new_shape->m_property_table) = *m_property_table;
@ -31,6 +41,7 @@ NonnullGCPtr<Shape> Shape::create_uncacheable_dictionary_transition()
new_shape->m_dictionary = true;
new_shape->m_cacheable = true;
new_shape->m_prototype = m_prototype;
invalidate_prototype_if_needed_for_new_prototype(new_shape);
ensure_property_table();
new_shape->ensure_property_table();
(*new_shape->m_property_table) = *m_property_table;
@ -38,8 +49,10 @@ NonnullGCPtr<Shape> Shape::create_uncacheable_dictionary_transition()
return new_shape;
}
Shape* Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
GCPtr<Shape> Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
{
if (m_is_prototype_shape)
return nullptr;
if (!m_forward_transitions)
return nullptr;
auto it = m_forward_transitions->find(key);
@ -50,11 +63,13 @@ Shape* Shape::get_or_prune_cached_forward_transition(TransitionKey const& key)
m_forward_transitions->remove(it);
return nullptr;
}
return it->value;
return it->value.ptr();
}
GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const& key)
{
if (m_is_prototype_shape)
return nullptr;
if (!m_delete_transitions)
return nullptr;
auto it = m_delete_transitions->find(key);
@ -68,8 +83,10 @@ GCPtr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const&
return it->value.ptr();
}
Shape* Shape::get_or_prune_cached_prototype_transition(Object* prototype)
GCPtr<Shape> Shape::get_or_prune_cached_prototype_transition(Object* prototype)
{
if (m_is_prototype_shape)
return nullptr;
if (!m_prototype_transitions)
return nullptr;
auto it = m_prototype_transitions->find(prototype);
@ -80,41 +97,52 @@ Shape* Shape::get_or_prune_cached_prototype_transition(Object* prototype)
m_prototype_transitions->remove(it);
return nullptr;
}
return it->value;
return it->value.ptr();
}
Shape* Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
NonnullGCPtr<Shape> Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
{
TransitionKey key { property_key, attributes };
if (auto* existing_shape = get_or_prune_cached_forward_transition(key))
return existing_shape;
if (auto existing_shape = get_or_prune_cached_forward_transition(key))
return *existing_shape;
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Put);
invalidate_prototype_if_needed_for_new_prototype(new_shape);
if (!m_is_prototype_shape) {
if (!m_forward_transitions)
m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
m_forward_transitions->set(key, new_shape.ptr());
}
return new_shape;
}
Shape* Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
NonnullGCPtr<Shape> Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
{
TransitionKey key { property_key, attributes };
if (auto* existing_shape = get_or_prune_cached_forward_transition(key))
return existing_shape;
if (auto existing_shape = get_or_prune_cached_forward_transition(key))
return *existing_shape;
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, attributes, TransitionType::Configure);
invalidate_prototype_if_needed_for_new_prototype(new_shape);
if (!m_is_prototype_shape) {
if (!m_forward_transitions)
m_forward_transitions = make<HashMap<TransitionKey, WeakPtr<Shape>>>();
m_forward_transitions->set(key, new_shape.ptr());
}
return new_shape;
}
Shape* Shape::create_prototype_transition(Object* new_prototype)
NonnullGCPtr<Shape> Shape::create_prototype_transition(Object* new_prototype)
{
if (auto* existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
return existing_shape;
if (new_prototype)
new_prototype->convert_to_prototype_if_needed();
if (auto existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
return *existing_shape;
auto new_shape = heap().allocate_without_realm<Shape>(*this, new_prototype);
invalidate_prototype_if_needed_for_new_prototype(new_shape);
if (!m_is_prototype_shape) {
if (!m_prototype_transitions)
m_prototype_transitions = make<HashMap<GCPtr<Object>, WeakPtr<Shape>>>();
m_prototype_transitions->set(new_prototype, new_shape.ptr());
}
return new_shape;
}
@ -178,6 +206,8 @@ void Shape::visit_edges(Cell::Visitor& visitor)
for (auto& it : *m_delete_transitions)
it.key.visit_edges(visitor);
}
visitor.visit(m_prototype_chain_validity);
}
Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) const
@ -245,6 +275,7 @@ NonnullGCPtr<Shape> Shape::create_delete_transition(StringOrSymbol const& proper
if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
return *existing_shape;
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, TransitionType::Delete);
invalidate_prototype_if_needed_for_new_prototype(new_shape);
if (!m_delete_transitions)
m_delete_transitions = make<HashMap<StringOrSymbol, WeakPtr<Shape>>>();
m_delete_transitions->set(property_key, new_shape.ptr());
@ -290,4 +321,77 @@ void Shape::remove_property_without_transition(StringOrSymbol const& property_ke
}
}
NonnullGCPtr<Shape> Shape::create_for_prototype(NonnullGCPtr<Realm> realm, GCPtr<Object> prototype)
{
auto new_shape = realm->heap().allocate_without_realm<Shape>(realm);
s_all_prototype_shapes.set(new_shape);
new_shape->m_is_prototype_shape = true;
new_shape->m_prototype = prototype;
new_shape->m_prototype_chain_validity = realm->heap().allocate_without_realm<PrototypeChainValidity>();
return new_shape;
}
NonnullGCPtr<Shape> Shape::clone_for_prototype()
{
VERIFY(!m_is_prototype_shape);
VERIFY(!m_prototype_chain_validity);
auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
s_all_prototype_shapes.set(new_shape);
new_shape->m_is_prototype_shape = true;
new_shape->m_prototype = m_prototype;
ensure_property_table();
new_shape->ensure_property_table();
(*new_shape->m_property_table) = *m_property_table;
new_shape->m_property_count = new_shape->m_property_table->size();
new_shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
return new_shape;
}
void Shape::set_prototype_without_transition(Object* new_prototype)
{
VERIFY(new_prototype);
new_prototype->convert_to_prototype_if_needed();
m_prototype = new_prototype;
}
void Shape::set_prototype_shape()
{
VERIFY(!m_is_prototype_shape);
s_all_prototype_shapes.set(this);
m_is_prototype_shape = true;
m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
}
void Shape::invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape)
{
if (!m_is_prototype_shape)
return;
new_prototype_shape->set_prototype_shape();
m_prototype_chain_validity->set_valid(false);
invalidate_all_prototype_chains_leading_to_this();
}
void Shape::invalidate_all_prototype_chains_leading_to_this()
{
HashTable<Shape*> shapes_to_invalidate;
for (auto& candidate : s_all_prototype_shapes) {
if (!candidate->m_prototype)
continue;
for (auto* current_prototype_shape = &candidate->m_prototype->shape(); current_prototype_shape; current_prototype_shape = current_prototype_shape->prototype() ? &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>();
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -34,12 +34,25 @@ struct TransitionKey {
}
};
class PrototypeChainValidity final : public Cell {
JS_CELL(PrototypeChainValidity, Cell);
JS_DECLARE_ALLOCATOR(PrototypeChainValidity);
public:
[[nodiscard]] bool is_valid() const { return m_valid; }
void set_valid(bool valid) { m_valid = valid; }
private:
bool m_valid { true };
size_t padding { 0 };
};
class Shape final : public Cell {
JS_CELL(Shape, Cell);
JS_DECLARE_ALLOCATOR(Shape);
public:
virtual ~Shape() override = default;
virtual ~Shape() override;
enum class TransitionType : u8 {
Invalid,
@ -51,12 +64,14 @@ public:
UncacheableDictionary,
};
Shape* create_put_transition(StringOrSymbol const&, PropertyAttributes attributes);
Shape* create_configure_transition(StringOrSymbol const&, PropertyAttributes attributes);
Shape* create_prototype_transition(Object* new_prototype);
[[nodiscard]] NonnullGCPtr<Shape> create_put_transition(StringOrSymbol const&, PropertyAttributes attributes);
[[nodiscard]] NonnullGCPtr<Shape> create_configure_transition(StringOrSymbol const&, PropertyAttributes attributes);
[[nodiscard]] NonnullGCPtr<Shape> create_prototype_transition(Object* new_prototype);
[[nodiscard]] NonnullGCPtr<Shape> create_delete_transition(StringOrSymbol const&);
[[nodiscard]] NonnullGCPtr<Shape> create_cacheable_dictionary_transition();
[[nodiscard]] NonnullGCPtr<Shape> create_uncacheable_dictionary_transition();
[[nodiscard]] NonnullGCPtr<Shape> clone_for_prototype();
[[nodiscard]] static NonnullGCPtr<Shape> create_for_prototype(NonnullGCPtr<Realm>, GCPtr<Object> prototype);
void add_property_without_transition(StringOrSymbol const&, PropertyAttributes);
void add_property_without_transition(PropertyKey const&, PropertyAttributes);
@ -69,6 +84,11 @@ public:
[[nodiscard]] bool is_cacheable_dictionary() const { return m_dictionary && m_cacheable; }
[[nodiscard]] bool is_uncacheable_dictionary() const { return m_dictionary && !m_cacheable; }
[[nodiscard]] bool is_prototype_shape() const { return m_is_prototype_shape; }
void set_prototype_shape();
GCPtr<PrototypeChainValidity> prototype_chain_validity() const { return m_prototype_chain_validity; }
Realm& realm() const { return m_realm; }
Object* prototype() { return m_prototype; }
@ -83,7 +103,7 @@ public:
PropertyMetadata value;
};
void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
void set_prototype_without_transition(Object* new_prototype);
private:
explicit Shape(Realm&);
@ -91,10 +111,13 @@ private:
Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType);
Shape(Shape& previous_shape, Object* new_prototype);
void invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape);
void invalidate_all_prototype_chains_leading_to_this();
virtual void visit_edges(Visitor&) override;
Shape* get_or_prune_cached_forward_transition(TransitionKey const&);
Shape* get_or_prune_cached_prototype_transition(Object* prototype);
[[nodiscard]] GCPtr<Shape> get_or_prune_cached_forward_transition(TransitionKey const&);
[[nodiscard]] GCPtr<Shape> get_or_prune_cached_prototype_transition(Object* prototype);
[[nodiscard]] GCPtr<Shape> get_or_prune_cached_delete_transition(StringOrSymbol const&);
void ensure_property_table() const;
@ -109,13 +132,17 @@ private:
GCPtr<Shape> m_previous;
StringOrSymbol m_property_key;
GCPtr<Object> m_prototype;
GCPtr<PrototypeChainValidity> m_prototype_chain_validity;
u32 m_property_count { 0 };
PropertyAttributes m_attributes { 0 };
TransitionType m_transition_type { TransitionType::Invalid };
bool m_dictionary { false };
bool m_cacheable { true };
bool m_dictionary : 1 { false };
bool m_cacheable : 1 { true };
bool m_is_prototype_shape : 1 { false };
};
}