mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-18 18:00:31 +00:00
Mapped and unmapped arguments objects already use dedicated premade shapes. Track their [[ParameterMap]] internal slot there instead of setting an Object flag after construction. This keeps the information on the shared shape, preserves it through shape transitions, and still lets Object.prototype.toString() recognize arguments objects without per-instance bookkeeping.
395 lines
14 KiB
C++
395 lines
14 KiB
C++
/*
|
|
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
|
|
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibGC/DeferGC.h>
|
|
#include <LibJS/Runtime/Realm.h>
|
|
#include <LibJS/Runtime/Shape.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
|
|
namespace JS {
|
|
|
|
GC_DEFINE_ALLOCATOR(Shape);
|
|
GC_DEFINE_ALLOCATOR(PrototypeChainValidity);
|
|
|
|
Shape::~Shape() = default;
|
|
|
|
void Shape::finalize()
|
|
{
|
|
Base::finalize();
|
|
if (m_is_prototype_shape)
|
|
m_realm->all_prototype_shapes().remove(this);
|
|
}
|
|
|
|
GC::Ref<Shape> Shape::create_dictionary_transition()
|
|
{
|
|
auto new_shape = heap().allocate<Shape>(m_realm);
|
|
new_shape->m_dictionary = true;
|
|
new_shape->m_has_parameter_map = m_has_parameter_map;
|
|
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;
|
|
new_shape->m_property_count = new_shape->m_property_table->size();
|
|
return new_shape;
|
|
}
|
|
|
|
GC::Ptr<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);
|
|
if (it == m_forward_transitions->end())
|
|
return nullptr;
|
|
if (!it->value) {
|
|
// The cached forward transition has gone stale (from garbage collection). Prune it.
|
|
m_forward_transitions->remove(it);
|
|
return nullptr;
|
|
}
|
|
return it->value.ptr();
|
|
}
|
|
|
|
GC::Ptr<Shape> Shape::get_or_prune_cached_delete_transition(PropertyKey const& key)
|
|
{
|
|
if (m_is_prototype_shape)
|
|
return nullptr;
|
|
if (!m_delete_transitions)
|
|
return nullptr;
|
|
auto it = m_delete_transitions->find(key);
|
|
if (it == m_delete_transitions->end())
|
|
return nullptr;
|
|
if (!it->value) {
|
|
// The cached delete transition has gone stale (from garbage collection). Prune it.
|
|
m_delete_transitions->remove(it);
|
|
return nullptr;
|
|
}
|
|
return it->value.ptr();
|
|
}
|
|
|
|
GC::Ptr<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);
|
|
if (it == m_prototype_transitions->end())
|
|
return nullptr;
|
|
if (!it->value) {
|
|
// The cached prototype transition has gone stale (from garbage collection). Prune it.
|
|
m_prototype_transitions->remove(it);
|
|
return nullptr;
|
|
}
|
|
return it->value.ptr();
|
|
}
|
|
|
|
GC::Ref<Shape> Shape::create_put_transition(PropertyKey const& property_key, PropertyAttributes attributes)
|
|
{
|
|
TransitionKey key { property_key, attributes };
|
|
if (auto existing_shape = get_or_prune_cached_forward_transition(key))
|
|
return *existing_shape;
|
|
auto new_shape = heap().allocate<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, GC::Weak<Shape>>>();
|
|
m_forward_transitions->set(key, new_shape);
|
|
}
|
|
return new_shape;
|
|
}
|
|
|
|
GC::Ref<Shape> Shape::create_configure_transition(PropertyKey const& property_key, PropertyAttributes attributes)
|
|
{
|
|
TransitionKey key { property_key, attributes };
|
|
if (auto existing_shape = get_or_prune_cached_forward_transition(key))
|
|
return *existing_shape;
|
|
auto new_shape = heap().allocate<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, GC::Weak<Shape>>>();
|
|
m_forward_transitions->set(key, new_shape.ptr());
|
|
}
|
|
return new_shape;
|
|
}
|
|
|
|
GC::Ref<Shape> Shape::create_prototype_transition(Object* new_prototype)
|
|
{
|
|
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<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<GC::Ptr<Object>, GC::Weak<Shape>>>();
|
|
m_prototype_transitions->set(new_prototype, new_shape.ptr());
|
|
}
|
|
return new_shape;
|
|
}
|
|
|
|
Shape::Shape(Realm& realm)
|
|
: m_realm(realm)
|
|
{
|
|
}
|
|
|
|
Shape::Shape(Shape& previous_shape, PropertyKey const& property_key, PropertyAttributes attributes, TransitionType transition_type)
|
|
: m_attributes(attributes)
|
|
, m_transition_type(transition_type)
|
|
, m_has_parameter_map(previous_shape.m_has_parameter_map)
|
|
, m_realm(previous_shape.m_realm)
|
|
, m_previous(&previous_shape)
|
|
, m_property_key(property_key)
|
|
, m_prototype(previous_shape.m_prototype)
|
|
, m_property_count(transition_type == TransitionType::Put ? previous_shape.m_property_count + 1 : previous_shape.m_property_count)
|
|
{
|
|
}
|
|
|
|
Shape::Shape(Shape& previous_shape, PropertyKey const& property_key, TransitionType transition_type)
|
|
: m_transition_type(transition_type)
|
|
, m_has_parameter_map(previous_shape.m_has_parameter_map)
|
|
, m_realm(previous_shape.m_realm)
|
|
, m_previous(&previous_shape)
|
|
, m_property_key(property_key)
|
|
, m_prototype(previous_shape.m_prototype)
|
|
, m_property_count(previous_shape.m_property_count - 1)
|
|
{
|
|
VERIFY(transition_type == TransitionType::Delete);
|
|
}
|
|
|
|
Shape::Shape(Shape& previous_shape, Object* new_prototype)
|
|
: m_transition_type(TransitionType::Prototype)
|
|
, m_has_parameter_map(previous_shape.m_has_parameter_map)
|
|
, m_realm(previous_shape.m_realm)
|
|
, m_previous(&previous_shape)
|
|
, m_prototype(new_prototype)
|
|
, m_property_count(previous_shape.m_property_count)
|
|
{
|
|
}
|
|
|
|
void Shape::visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_realm);
|
|
visitor.visit(m_prototype);
|
|
visitor.visit(m_previous);
|
|
if (m_property_key.has_value())
|
|
m_property_key->visit_edges(visitor);
|
|
|
|
// NOTE: We don't need to mark the keys in the property table, since they are guaranteed
|
|
// to also be marked by the chain of shapes leading up to this one.
|
|
|
|
visitor.ignore(m_prototype_transitions);
|
|
|
|
// FIXME: The forward transition keys should be weak, but we have to mark them for now in case they go stale.
|
|
if (m_forward_transitions) {
|
|
for (auto& it : *m_forward_transitions)
|
|
it.key.property_key.visit_edges(visitor);
|
|
}
|
|
|
|
// FIXME: The delete transition keys should be weak, but we have to mark them for now in case they go stale.
|
|
if (m_delete_transitions) {
|
|
for (auto& it : *m_delete_transitions)
|
|
it.key.visit_edges(visitor);
|
|
}
|
|
|
|
visitor.visit(m_prototype_chain_validity);
|
|
|
|
if (m_property_table) {
|
|
for (auto& it : *m_property_table)
|
|
it.key.visit_edges(visitor);
|
|
}
|
|
}
|
|
|
|
Optional<PropertyMetadata> Shape::lookup(PropertyKey const& property_key) const
|
|
{
|
|
if (m_property_count == 0)
|
|
return {};
|
|
auto property = property_table().get(property_key);
|
|
if (!property.has_value())
|
|
return {};
|
|
return property;
|
|
}
|
|
|
|
FLATTEN OrderedHashMap<PropertyKey, PropertyMetadata> const& Shape::property_table() const
|
|
{
|
|
ensure_property_table();
|
|
return *m_property_table;
|
|
}
|
|
|
|
void Shape::ensure_property_table() const
|
|
{
|
|
if (m_property_table)
|
|
return;
|
|
m_property_table = make<OrderedHashMap<PropertyKey, PropertyMetadata>>();
|
|
|
|
u32 next_offset = 0;
|
|
|
|
Vector<Shape const&, 64> transition_chain;
|
|
transition_chain.append(*this);
|
|
for (auto shape = m_previous; shape; shape = shape->m_previous) {
|
|
if (shape->m_property_table) {
|
|
*m_property_table = *shape->m_property_table;
|
|
next_offset = shape->m_property_count;
|
|
break;
|
|
}
|
|
transition_chain.append(*shape);
|
|
}
|
|
|
|
for (auto const& shape : transition_chain.in_reverse()) {
|
|
if (!shape.m_property_key.has_value()) {
|
|
// Ignore prototype transitions as they don't affect the key map.
|
|
continue;
|
|
}
|
|
if (shape.m_transition_type == TransitionType::Put) {
|
|
m_property_table->set(*shape.m_property_key, { next_offset++, shape.m_attributes });
|
|
} else if (shape.m_transition_type == TransitionType::Configure) {
|
|
auto it = m_property_table->find(*shape.m_property_key);
|
|
VERIFY(it != m_property_table->end());
|
|
it->value.attributes = shape.m_attributes;
|
|
} else if (shape.m_transition_type == TransitionType::Delete) {
|
|
auto remove_it = m_property_table->find(*shape.m_property_key);
|
|
VERIFY(remove_it != m_property_table->end());
|
|
auto removed_offset = remove_it->value.offset;
|
|
m_property_table->remove(remove_it);
|
|
for (auto& it : *m_property_table) {
|
|
if (it.value.offset > removed_offset)
|
|
--it.value.offset;
|
|
}
|
|
--next_offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
GC::Ref<Shape> Shape::create_delete_transition(PropertyKey const& property_key)
|
|
{
|
|
if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
|
|
return *existing_shape;
|
|
auto new_shape = heap().allocate<Shape>(*this, property_key, TransitionType::Delete);
|
|
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
|
if (!m_delete_transitions)
|
|
m_delete_transitions = make<HashMap<PropertyKey, GC::Weak<Shape>>>();
|
|
m_delete_transitions->set(property_key, new_shape.ptr());
|
|
return new_shape;
|
|
}
|
|
|
|
void Shape::add_property_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
|
|
{
|
|
invalidate_prototype_if_needed_for_change_without_transition();
|
|
ensure_property_table();
|
|
if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) {
|
|
VERIFY(m_property_count < NumericLimits<u32>::max());
|
|
++m_property_count;
|
|
++m_dictionary_generation;
|
|
}
|
|
}
|
|
|
|
void Shape::set_property_attributes_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
|
|
{
|
|
invalidate_prototype_if_needed_for_change_without_transition();
|
|
VERIFY(is_dictionary());
|
|
VERIFY(m_property_table);
|
|
auto it = m_property_table->find(property_key);
|
|
VERIFY(it != m_property_table->end());
|
|
it->value.attributes = attributes;
|
|
m_property_table->set(property_key, it->value);
|
|
++m_dictionary_generation;
|
|
}
|
|
|
|
void Shape::remove_property_without_transition(PropertyKey const& property_key, u32 offset)
|
|
{
|
|
invalidate_prototype_if_needed_for_change_without_transition();
|
|
VERIFY(is_dictionary());
|
|
VERIFY(m_property_table);
|
|
if (m_property_table->remove(property_key))
|
|
--m_property_count;
|
|
for (auto& it : *m_property_table) {
|
|
VERIFY(it.value.offset != offset);
|
|
if (it.value.offset > offset)
|
|
--it.value.offset;
|
|
}
|
|
++m_dictionary_generation;
|
|
}
|
|
|
|
GC::Ref<Shape> Shape::clone_for_prototype()
|
|
{
|
|
VERIFY(!m_is_prototype_shape);
|
|
VERIFY(!m_prototype_chain_validity);
|
|
auto new_shape = heap().allocate<Shape>(m_realm);
|
|
m_realm->all_prototype_shapes().set(new_shape);
|
|
new_shape->m_is_prototype_shape = true;
|
|
new_shape->m_has_parameter_map = m_has_parameter_map;
|
|
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<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);
|
|
m_realm->all_prototype_shapes().set(this);
|
|
m_is_prototype_shape = true;
|
|
m_prototype_chain_validity = heap().allocate<PrototypeChainValidity>();
|
|
}
|
|
|
|
void Shape::invalidate_prototype_if_needed_for_new_prototype(GC::Ref<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_prototype_if_needed_for_change_without_transition()
|
|
{
|
|
if (!m_is_prototype_shape)
|
|
return;
|
|
m_prototype_chain_validity->set_valid(false);
|
|
m_prototype_chain_validity = heap().allocate<PrototypeChainValidity>();
|
|
|
|
invalidate_all_prototype_chains_leading_to_this();
|
|
}
|
|
|
|
void Shape::invalidate_all_prototype_chains_leading_to_this()
|
|
{
|
|
HashTable<Shape*> shapes_to_invalidate;
|
|
for (auto& candidate : m_realm->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<PrototypeChainValidity>();
|
|
}
|
|
}
|
|
|
|
}
|