|
@@ -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);
|
|
|
- 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;
|
|
|
}
|
|
|
|
|
|
-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);
|
|
|
- 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;
|
|
|
}
|
|
|
|
|
|
-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);
|
|
|
- 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;
|
|
|
}
|
|
|
|
|
@@ -178,6 +206,8 @@ void Shape::visit_edges(Cell::Visitor& visitor)
|
|
|
for (auto& it : *m_delete_transitions)
|
|
|
it.key.visit_edges(visitor);
|
|
|
}
|
|
|
+
|
|
|
+ visitor.visit(m_prototype_chain_validity);
|
|
|
}
|
|
|
|
|
|
Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) const
|
|
@@ -245,6 +275,7 @@ NonnullGCPtr<Shape> Shape::create_delete_transition(StringOrSymbol const& proper
|
|
|
if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
|
|
|
return *existing_shape;
|
|
|
auto new_shape = heap().allocate_without_realm<Shape>(*this, property_key, TransitionType::Delete);
|
|
|
+ invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
|
|
if (!m_delete_transitions)
|
|
|
m_delete_transitions = make<HashMap<StringOrSymbol, WeakPtr<Shape>>>();
|
|
|
m_delete_transitions->set(property_key, new_shape.ptr());
|
|
@@ -290,4 +321,77 @@ void Shape::remove_property_without_transition(StringOrSymbol const& property_ke
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+NonnullGCPtr<Shape> Shape::create_for_prototype(NonnullGCPtr<Realm> realm, GCPtr<Object> prototype)
|
|
|
+{
|
|
|
+ auto new_shape = realm->heap().allocate_without_realm<Shape>(realm);
|
|
|
+ s_all_prototype_shapes.set(new_shape);
|
|
|
+ new_shape->m_is_prototype_shape = true;
|
|
|
+ new_shape->m_prototype = prototype;
|
|
|
+ new_shape->m_prototype_chain_validity = realm->heap().allocate_without_realm<PrototypeChainValidity>();
|
|
|
+ return new_shape;
|
|
|
+}
|
|
|
+
|
|
|
+NonnullGCPtr<Shape> Shape::clone_for_prototype()
|
|
|
+{
|
|
|
+ VERIFY(!m_is_prototype_shape);
|
|
|
+ VERIFY(!m_prototype_chain_validity);
|
|
|
+ auto new_shape = heap().allocate_without_realm<Shape>(m_realm);
|
|
|
+ s_all_prototype_shapes.set(new_shape);
|
|
|
+ new_shape->m_is_prototype_shape = true;
|
|
|
+ new_shape->m_prototype = m_prototype;
|
|
|
+ ensure_property_table();
|
|
|
+ new_shape->ensure_property_table();
|
|
|
+ (*new_shape->m_property_table) = *m_property_table;
|
|
|
+ new_shape->m_property_count = new_shape->m_property_table->size();
|
|
|
+ new_shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
|
|
|
+ return new_shape;
|
|
|
+}
|
|
|
+
|
|
|
+void Shape::set_prototype_without_transition(Object* new_prototype)
|
|
|
+{
|
|
|
+ VERIFY(new_prototype);
|
|
|
+ new_prototype->convert_to_prototype_if_needed();
|
|
|
+ m_prototype = new_prototype;
|
|
|
+}
|
|
|
+
|
|
|
+void Shape::set_prototype_shape()
|
|
|
+{
|
|
|
+ VERIFY(!m_is_prototype_shape);
|
|
|
+ s_all_prototype_shapes.set(this);
|
|
|
+ m_is_prototype_shape = true;
|
|
|
+ m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
|
|
|
+}
|
|
|
+
|
|
|
+void Shape::invalidate_prototype_if_needed_for_new_prototype(NonnullGCPtr<Shape> new_prototype_shape)
|
|
|
+{
|
|
|
+ if (!m_is_prototype_shape)
|
|
|
+ return;
|
|
|
+ new_prototype_shape->set_prototype_shape();
|
|
|
+ m_prototype_chain_validity->set_valid(false);
|
|
|
+
|
|
|
+ invalidate_all_prototype_chains_leading_to_this();
|
|
|
+}
|
|
|
+
|
|
|
+void Shape::invalidate_all_prototype_chains_leading_to_this()
|
|
|
+{
|
|
|
+ HashTable<Shape*> shapes_to_invalidate;
|
|
|
+ for (auto& candidate : s_all_prototype_shapes) {
|
|
|
+ if (!candidate->m_prototype)
|
|
|
+ continue;
|
|
|
+ for (auto* current_prototype_shape = &candidate->m_prototype->shape(); current_prototype_shape; current_prototype_shape = current_prototype_shape->prototype() ? ¤t_prototype_shape->prototype()->shape() : nullptr) {
|
|
|
+ if (current_prototype_shape == this) {
|
|
|
+ VERIFY(candidate->m_is_prototype_shape);
|
|
|
+ shapes_to_invalidate.set(candidate);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (shapes_to_invalidate.is_empty())
|
|
|
+ return;
|
|
|
+ for (auto* shape : shapes_to_invalidate) {
|
|
|
+ shape->m_prototype_chain_validity->set_valid(false);
|
|
|
+ shape->m_prototype_chain_validity = heap().allocate_without_realm<PrototypeChainValidity>();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
}
|