LibJS: Compact Shape property table to a sorted flat array
Replace the lazy per-shape OrderedHashMap cache for non-dictionary shapes with a GC-allocated descriptor array. Store descriptors in hash order for lookup while keeping an enum index so callers can still walk properties in insertion order. Keep dictionary shapes on the mutable OrderedHashMap path, and migrate callers that enumerated Shape::property_table() to the new insertion order iterator. Cap descriptor arrays to their compact u16 index range and keep larger dictionary shapes on the mutable table path across prototype transitions and prototype clones. Add coverage for setting the prototype of a dictionary object with more than 65536 named properties.
This commit is contained in:
parent
30b8f46084
commit
e3841a7392
14 changed files with 423 additions and 122 deletions
|
|
@ -1447,11 +1447,12 @@ struct FastPropertyNameIteratorData {
|
|||
|
||||
static bool shape_has_enumerable_string_property(Shape const& shape)
|
||||
{
|
||||
for (auto const& [property_key, metadata] : shape.property_table()) {
|
||||
bool has_enumerable_string_property = false;
|
||||
shape.for_each_property_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
if (property_key.is_string() && metadata.attributes.is_enumerable())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
has_enumerable_string_property = true;
|
||||
});
|
||||
return has_enumerable_string_property;
|
||||
}
|
||||
|
||||
static bool property_name_iterator_fast_path_is_still_eligible(Object& object, PropertyNameIterator::FastPath fast_path, u32 indexed_property_count)
|
||||
|
|
@ -1550,10 +1551,10 @@ static ThrowCompletionOr<Optional<FastPropertyNameIteratorData>> try_get_fast_pr
|
|||
// Common case: only the receiver contributes enumerable string keys, so
|
||||
// we can copy them straight from the shape without any shadowing work.
|
||||
result.properties.ensure_capacity(object.shape().property_count());
|
||||
for (auto const& [property_key, metadata] : object.shape().property_table()) {
|
||||
object.shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
if (property_key.is_string() && metadata.attributes.is_enumerable())
|
||||
result.properties.append(property_key);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1583,25 +1584,25 @@ static ThrowCompletionOr<Optional<FastPropertyNameIteratorData>> try_get_fast_pr
|
|||
if (object_to_check->has_magical_length_property())
|
||||
seen_non_enumerable_properties.set(vm.names.length);
|
||||
|
||||
for (auto const& [property_key, metadata] : object_to_check->shape().property_table()) {
|
||||
object_to_check->shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
if (!property_key.is_string())
|
||||
continue;
|
||||
return;
|
||||
|
||||
bool enumerable = metadata.attributes.is_enumerable();
|
||||
if (!enumerable)
|
||||
seen_non_enumerable_properties.set(property_key);
|
||||
if (in_prototype_chain && enumerable) {
|
||||
if (seen_non_enumerable_properties.contains(property_key))
|
||||
continue;
|
||||
return;
|
||||
ensure_seen_properties();
|
||||
if (seen_properties->contains(property_key))
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (enumerable)
|
||||
result.properties.append(property_key);
|
||||
if (seen_properties.has_value())
|
||||
seen_properties->set(property_key);
|
||||
}
|
||||
});
|
||||
in_prototype_chain = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ set(SOURCES
|
|||
Runtime/DataView.cpp
|
||||
Runtime/DataViewConstructor.cpp
|
||||
Runtime/DataViewPrototype.cpp
|
||||
Runtime/DescriptorArray.cpp
|
||||
Runtime/Date.cpp
|
||||
Runtime/DateConstructor.cpp
|
||||
Runtime/DatePrototype.cpp
|
||||
|
|
|
|||
155
Libraries/LibJS/Runtime/DescriptorArray.cpp
Normal file
155
Libraries/LibJS/Runtime/DescriptorArray.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibJS/Runtime/DescriptorArray.h>
|
||||
#include <LibJS/Runtime/ExternalMemory.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(DescriptorArray);
|
||||
|
||||
static u32 property_key_hash(PropertyKey const& property_key)
|
||||
{
|
||||
return AK::Traits<PropertyKey>::hash(property_key);
|
||||
}
|
||||
|
||||
DescriptorArray::DescriptorArray(DescriptorArray const& other, u32 descriptor_count)
|
||||
{
|
||||
VERIFY(descriptor_count <= max_descriptor_count);
|
||||
m_entries.ensure_capacity(descriptor_count);
|
||||
other.for_each_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
set(property_key, metadata, m_entries.size());
|
||||
},
|
||||
descriptor_count);
|
||||
}
|
||||
|
||||
Optional<PropertyMetadata> DescriptorArray::lookup(PropertyKey const& property_key, u32 descriptor_count) const
|
||||
{
|
||||
auto index = find(property_key, descriptor_count);
|
||||
if (!index.has_value())
|
||||
return {};
|
||||
return m_entries[*index].metadata();
|
||||
}
|
||||
|
||||
void DescriptorArray::set(PropertyKey const& property_key, PropertyMetadata metadata, u32 enum_index)
|
||||
{
|
||||
VERIFY(enum_index < max_descriptor_count);
|
||||
if (auto existing_index = find(property_key, enum_index + 1); existing_index.has_value()) {
|
||||
auto& entry = m_entries[*existing_index];
|
||||
entry.offset = metadata.offset;
|
||||
entry.attributes = metadata.attributes.bits();
|
||||
return;
|
||||
}
|
||||
|
||||
auto insertion_index = find_insertion_index(property_key);
|
||||
m_entries.insert(insertion_index, { property_key, metadata.offset, metadata.attributes.bits(), static_cast<u16>(enum_index) });
|
||||
}
|
||||
|
||||
void DescriptorArray::set_attributes(PropertyKey const& property_key, PropertyAttributes attributes, u32 descriptor_count)
|
||||
{
|
||||
auto index = find(property_key, descriptor_count);
|
||||
VERIFY(index.has_value());
|
||||
m_entries[*index].attributes = attributes.bits();
|
||||
}
|
||||
|
||||
void DescriptorArray::remove(PropertyKey const& property_key, u32 descriptor_count)
|
||||
{
|
||||
auto index = find(property_key, descriptor_count);
|
||||
VERIFY(index.has_value());
|
||||
|
||||
auto removed_offset = m_entries[*index].offset;
|
||||
auto removed_enum_index = m_entries[*index].enum_index;
|
||||
m_entries.remove(*index);
|
||||
|
||||
for (auto& entry : m_entries) {
|
||||
if (entry.enum_index >= descriptor_count)
|
||||
continue;
|
||||
if (entry.offset > removed_offset)
|
||||
--entry.offset;
|
||||
if (entry.enum_index > removed_enum_index)
|
||||
--entry.enum_index;
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorArray::for_each_in_insertion_order(Function<void(PropertyKey const&, PropertyMetadata const&)> const& callback, u32 descriptor_count) const
|
||||
{
|
||||
Vector<Entry const*, 32> entries;
|
||||
entries.ensure_capacity(descriptor_count);
|
||||
for (auto const& entry : m_entries) {
|
||||
if (entry.enum_index < descriptor_count)
|
||||
entries.unchecked_append(&entry);
|
||||
}
|
||||
|
||||
quick_sort(entries, [](auto const* lhs, auto const* rhs) {
|
||||
return lhs->enum_index < rhs->enum_index;
|
||||
});
|
||||
|
||||
for (auto const* entry : entries) {
|
||||
auto metadata = entry->metadata();
|
||||
callback(entry->property_key, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorArray::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
for (auto const& entry : m_entries)
|
||||
entry.property_key.visit_edges(visitor);
|
||||
}
|
||||
|
||||
size_t DescriptorArray::external_memory_size() const
|
||||
{
|
||||
return vector_external_memory_size(m_entries);
|
||||
}
|
||||
|
||||
Optional<size_t> DescriptorArray::find(PropertyKey const& property_key, u32 descriptor_count) const
|
||||
{
|
||||
if (m_entries.size() <= 16) {
|
||||
for (size_t i = 0; i < m_entries.size(); ++i) {
|
||||
auto const& entry = m_entries[i];
|
||||
if (entry.enum_index < descriptor_count && entry.property_key == property_key)
|
||||
return i;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto hash = property_key_hash(property_key);
|
||||
size_t low = 0;
|
||||
size_t high = m_entries.size();
|
||||
while (low < high) {
|
||||
auto middle = low + (high - low) / 2;
|
||||
if (property_key_hash(m_entries[middle].property_key) < hash)
|
||||
low = middle + 1;
|
||||
else
|
||||
high = middle;
|
||||
}
|
||||
|
||||
for (auto i = low; i < m_entries.size() && property_key_hash(m_entries[i].property_key) == hash; ++i) {
|
||||
auto const& entry = m_entries[i];
|
||||
if (entry.enum_index < descriptor_count && entry.property_key == property_key)
|
||||
return i;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t DescriptorArray::find_insertion_index(PropertyKey const& property_key) const
|
||||
{
|
||||
auto hash = property_key_hash(property_key);
|
||||
size_t low = 0;
|
||||
size_t high = m_entries.size();
|
||||
while (low < high) {
|
||||
auto middle = low + (high - low) / 2;
|
||||
if (property_key_hash(m_entries[middle].property_key) <= hash)
|
||||
low = middle + 1;
|
||||
else
|
||||
high = middle;
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
}
|
||||
68
Libraries/LibJS/Runtime/DescriptorArray.h
Normal file
68
Libraries/LibJS/Runtime/DescriptorArray.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Export.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Runtime/PropertyAttributes.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
struct PropertyMetadata {
|
||||
u32 offset { 0 };
|
||||
PropertyAttributes attributes { 0 };
|
||||
};
|
||||
|
||||
class JS_API DescriptorArray final : public Cell {
|
||||
GC_CELL(DescriptorArray, Cell);
|
||||
GC_DECLARE_ALLOCATOR(DescriptorArray);
|
||||
|
||||
public:
|
||||
struct Entry {
|
||||
PropertyKey property_key;
|
||||
u32 offset { 0 };
|
||||
u16 attributes { 0 };
|
||||
u16 enum_index { 0 };
|
||||
|
||||
PropertyMetadata metadata() const
|
||||
{
|
||||
return { offset, static_cast<u8>(attributes) };
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr u32 max_descriptor_count = NumericLimits<u16>::max() + 1;
|
||||
|
||||
DescriptorArray() = default;
|
||||
explicit DescriptorArray(DescriptorArray const&, u32 descriptor_count);
|
||||
virtual ~DescriptorArray() override = default;
|
||||
|
||||
[[nodiscard]] u32 size() const { return m_entries.size(); }
|
||||
[[nodiscard]] Optional<PropertyMetadata> lookup(PropertyKey const&, u32 descriptor_count) const;
|
||||
|
||||
void set(PropertyKey const&, PropertyMetadata, u32 enum_index);
|
||||
void set_attributes(PropertyKey const&, PropertyAttributes, u32 descriptor_count);
|
||||
void remove(PropertyKey const&, u32 descriptor_count);
|
||||
|
||||
void for_each_in_insertion_order(Function<void(PropertyKey const&, PropertyMetadata const&)> const&, u32 descriptor_count) const;
|
||||
|
||||
private:
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
virtual size_t external_memory_size() const override;
|
||||
|
||||
[[nodiscard]] Optional<size_t> find(PropertyKey const&, u32 descriptor_count) const;
|
||||
[[nodiscard]] size_t find_insertion_index(PropertyKey const&) const;
|
||||
|
||||
Vector<Entry> m_entries;
|
||||
};
|
||||
|
||||
static_assert(sizeof(DescriptorArray::Entry) == 16);
|
||||
|
||||
}
|
||||
|
|
@ -1242,20 +1242,20 @@ ThrowCompletionOr<GC::RootVector<Value>> Object::internal_own_property_keys() co
|
|||
}
|
||||
|
||||
// 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_string()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_string()) {
|
||||
// a. Add P as the last element of keys.
|
||||
keys.append(it.key.to_value(vm));
|
||||
keys.append(property_key.to_value(vm));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_symbol()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_symbol()) {
|
||||
// a. Add P as the last element of keys.
|
||||
keys.append(it.key.to_value(vm));
|
||||
keys.append(property_key.to_value(vm));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 5. Return keys.
|
||||
return { move(keys) };
|
||||
|
|
@ -1486,11 +1486,11 @@ ThrowCompletionOr<void> Object::for_each_own_property_with_enumerability(Functio
|
|||
if (has_magical_length_property())
|
||||
keys.unchecked_append({ PropertyKey(vm.names.length), false });
|
||||
|
||||
for (auto const& [property_key, metadata] : shape().property_table()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
if (!property_key.is_string())
|
||||
continue;
|
||||
return;
|
||||
keys.unchecked_append({ property_key, metadata.attributes.is_enumerable() });
|
||||
}
|
||||
});
|
||||
|
||||
for (auto& key : keys)
|
||||
TRY(callback(key.property_key, key.enumerable));
|
||||
|
|
@ -1512,7 +1512,7 @@ ThrowCompletionOr<void> Object::for_each_own_property_with_enumerability(Functio
|
|||
|
||||
size_t Object::own_properties_count() const
|
||||
{
|
||||
return indexed_real_size() + shape().property_table().size() + (has_magical_length_property() ? 1 : 0);
|
||||
return indexed_real_size() + shape().property_count() + (has_magical_length_property() ? 1 : 0);
|
||||
}
|
||||
|
||||
// Simple side-effect free property lookup, following the prototype chain. Non-standard.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <LibGC/DeferGC.h>
|
||||
#include <LibJS/Runtime/DescriptorArray.h>
|
||||
#include <LibJS/Runtime/ExternalMemory.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Shape.h>
|
||||
|
|
@ -21,7 +22,7 @@ Shape::~Shape() = default;
|
|||
size_t Shape::external_memory_size() const
|
||||
{
|
||||
size_t size = 0;
|
||||
if (m_property_table)
|
||||
if (m_dictionary && m_property_table)
|
||||
size += hash_map_external_memory_size(*m_property_table);
|
||||
if (m_forward_transitions)
|
||||
size += hash_map_external_memory_size(*m_forward_transitions);
|
||||
|
|
@ -41,10 +42,7 @@ GC::Ref<Shape> Shape::create_dictionary_transition()
|
|||
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();
|
||||
copy_properties_to_dictionary_shape(*new_shape);
|
||||
return new_shape;
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +103,9 @@ GC::Ref<Shape> Shape::create_put_transition(PropertyKey const& property_key, Pro
|
|||
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);
|
||||
new_shape->m_descriptors = copy_descriptors();
|
||||
new_shape->m_descriptors->set(property_key, { m_property_count, attributes }, m_property_count);
|
||||
new_shape->m_own_descriptor_count = new_shape->m_property_count;
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_is_prototype_shape) {
|
||||
if (!m_forward_transitions)
|
||||
|
|
@ -120,6 +121,9 @@ GC::Ref<Shape> Shape::create_configure_transition(PropertyKey const& property_ke
|
|||
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);
|
||||
new_shape->m_descriptors = copy_descriptors();
|
||||
new_shape->m_descriptors->set_attributes(property_key, attributes, m_own_descriptor_count);
|
||||
new_shape->m_own_descriptor_count = new_shape->m_property_count;
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_is_prototype_shape) {
|
||||
if (!m_forward_transitions)
|
||||
|
|
@ -136,6 +140,13 @@ GC::Ref<Shape> Shape::create_prototype_transition(Object* new_prototype)
|
|||
if (auto existing_shape = get_or_prune_cached_prototype_transition(new_prototype))
|
||||
return *existing_shape;
|
||||
auto new_shape = heap().allocate<Shape>(*this, new_prototype);
|
||||
if (m_dictionary && m_property_count > DescriptorArray::max_descriptor_count) {
|
||||
new_shape->m_dictionary = true;
|
||||
copy_properties_to_dictionary_shape(*new_shape);
|
||||
} else {
|
||||
new_shape->m_descriptors = copy_descriptors();
|
||||
new_shape->m_own_descriptor_count = new_shape->m_property_count;
|
||||
}
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_is_prototype_shape) {
|
||||
if (!m_prototype_transitions)
|
||||
|
|
@ -159,6 +170,7 @@ Shape::Shape(Shape& previous_shape, PropertyKey const& property_key, PropertyAtt
|
|||
, 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)
|
||||
, m_own_descriptor_count(m_property_count)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -170,6 +182,7 @@ Shape::Shape(Shape& previous_shape, PropertyKey const& property_key, TransitionT
|
|||
, m_property_key(property_key)
|
||||
, m_prototype(previous_shape.m_prototype)
|
||||
, m_property_count(previous_shape.m_property_count - 1)
|
||||
, m_own_descriptor_count(m_property_count)
|
||||
{
|
||||
VERIFY(transition_type == TransitionType::Delete);
|
||||
}
|
||||
|
|
@ -181,6 +194,7 @@ Shape::Shape(Shape& previous_shape, Object* new_prototype)
|
|||
, m_previous(&previous_shape)
|
||||
, m_prototype(new_prototype)
|
||||
, m_property_count(previous_shape.m_property_count)
|
||||
, m_own_descriptor_count(m_property_count)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +202,7 @@ void Shape::visit_edges(Cell::Visitor& visitor)
|
|||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_realm);
|
||||
visitor.visit(m_descriptors);
|
||||
visitor.visit(m_prototype);
|
||||
visitor.visit(m_previous);
|
||||
if (m_property_key.has_value())
|
||||
|
|
@ -237,60 +252,68 @@ 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())
|
||||
if (m_dictionary) {
|
||||
ensure_property_table();
|
||||
auto property = m_property_table->get(property_key);
|
||||
if (!property.has_value())
|
||||
return {};
|
||||
return property;
|
||||
}
|
||||
if (!m_descriptors)
|
||||
return {};
|
||||
return property;
|
||||
return m_descriptors->lookup(property_key, m_own_descriptor_count);
|
||||
}
|
||||
|
||||
FLATTEN OrderedHashMap<PropertyKey, PropertyMetadata> const& Shape::property_table() const
|
||||
void Shape::for_each_property_in_insertion_order(Function<void(PropertyKey const&, PropertyMetadata const&)> const& callback) const
|
||||
{
|
||||
ensure_property_table();
|
||||
return *m_property_table;
|
||||
if (m_dictionary) {
|
||||
ensure_property_table();
|
||||
for (auto const& [property_key, metadata] : *m_property_table)
|
||||
callback(property_key, metadata);
|
||||
return;
|
||||
}
|
||||
if (!m_descriptors)
|
||||
return;
|
||||
m_descriptors->for_each_in_insertion_order(callback, m_own_descriptor_count);
|
||||
}
|
||||
|
||||
void Shape::ensure_property_table() const
|
||||
{
|
||||
VERIFY(m_dictionary);
|
||||
if (m_property_table)
|
||||
return;
|
||||
m_property_table = make<OrderedHashMap<PropertyKey, PropertyMetadata>>();
|
||||
}
|
||||
|
||||
u32 next_offset = 0;
|
||||
void Shape::ensure_descriptor_array()
|
||||
{
|
||||
VERIFY(!m_dictionary);
|
||||
if (m_descriptors)
|
||||
return;
|
||||
m_descriptors = heap().allocate<DescriptorArray>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
GC::Ref<DescriptorArray> Shape::copy_descriptors() const
|
||||
{
|
||||
VERIFY(m_property_count <= DescriptorArray::max_descriptor_count);
|
||||
if (!m_dictionary && m_descriptors)
|
||||
return heap().allocate<DescriptorArray>(*m_descriptors, m_own_descriptor_count);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
auto descriptors = heap().allocate<DescriptorArray>();
|
||||
for_each_property_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
descriptors->set(property_key, metadata, descriptors->size());
|
||||
});
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
void Shape::copy_properties_to_dictionary_shape(Shape& shape) const
|
||||
{
|
||||
VERIFY(shape.m_dictionary);
|
||||
shape.ensure_property_table();
|
||||
for_each_property_in_insertion_order([&](auto const& property_key, auto const& metadata) {
|
||||
shape.m_property_table->set(property_key, metadata);
|
||||
});
|
||||
shape.m_property_count = shape.m_property_table->size();
|
||||
}
|
||||
|
||||
GC::Ref<Shape> Shape::create_delete_transition(PropertyKey const& property_key)
|
||||
|
|
@ -298,6 +321,9 @@ 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);
|
||||
new_shape->m_descriptors = copy_descriptors();
|
||||
new_shape->m_descriptors->remove(property_key, m_own_descriptor_count);
|
||||
new_shape->m_own_descriptor_count = new_shape->m_property_count;
|
||||
invalidate_prototype_if_needed_for_new_prototype(new_shape);
|
||||
if (!m_delete_transitions)
|
||||
m_delete_transitions = make<HashMap<PropertyKey, GC::Weak<Shape>>>();
|
||||
|
|
@ -308,12 +334,26 @@ GC::Ref<Shape> Shape::create_delete_transition(PropertyKey const& property_key)
|
|||
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;
|
||||
if (m_dictionary) {
|
||||
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;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ensure_descriptor_array();
|
||||
if (!m_descriptors->lookup(property_key, m_own_descriptor_count).has_value()) {
|
||||
VERIFY(m_property_count < NumericLimits<u32>::max());
|
||||
m_descriptors->set(property_key, { m_property_count, attributes }, m_own_descriptor_count);
|
||||
++m_property_count;
|
||||
++m_own_descriptor_count;
|
||||
++m_dictionary_generation;
|
||||
return;
|
||||
}
|
||||
m_descriptors->set(property_key, { m_property_count, attributes }, m_own_descriptor_count);
|
||||
}
|
||||
|
||||
void Shape::set_property_attributes_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
|
||||
|
|
@ -351,10 +391,14 @@ GC::Ref<Shape> Shape::clone_for_prototype()
|
|||
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();
|
||||
if (m_dictionary && m_property_count > DescriptorArray::max_descriptor_count) {
|
||||
new_shape->m_dictionary = true;
|
||||
copy_properties_to_dictionary_shape(*new_shape);
|
||||
} else {
|
||||
new_shape->m_descriptors = copy_descriptors();
|
||||
new_shape->m_property_count = m_property_count;
|
||||
new_shape->m_own_descriptor_count = m_property_count;
|
||||
}
|
||||
new_shape->m_prototype_chain_validity = heap().allocate<PrototypeChainValidity>();
|
||||
if (new_shape->m_prototype)
|
||||
new_shape->m_prototype->shape().add_child_prototype_shape(*new_shape);
|
||||
|
|
|
|||
|
|
@ -17,17 +17,13 @@
|
|||
#include <LibJS/Export.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Runtime/DescriptorArray.h>
|
||||
#include <LibJS/Runtime/PropertyAttributes.h>
|
||||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
struct PropertyMetadata {
|
||||
u32 offset { 0 };
|
||||
PropertyAttributes attributes { 0 };
|
||||
};
|
||||
|
||||
struct TransitionKey {
|
||||
PropertyKey property_key;
|
||||
PropertyAttributes attributes { 0 };
|
||||
|
|
@ -102,7 +98,7 @@ public:
|
|||
Object const* prototype() const { return m_prototype; }
|
||||
|
||||
Optional<PropertyMetadata> lookup(PropertyKey const&) const;
|
||||
OrderedHashMap<PropertyKey, PropertyMetadata> const& property_table() const;
|
||||
void for_each_property_in_insertion_order(Function<void(PropertyKey const&, PropertyMetadata const&)> const&) const;
|
||||
u32 property_count() const { return m_property_count; }
|
||||
|
||||
void set_prototype_without_transition(Object* new_prototype);
|
||||
|
|
@ -127,6 +123,9 @@ private:
|
|||
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_delete_transition(PropertyKey const&);
|
||||
|
||||
void ensure_property_table() const;
|
||||
void ensure_descriptor_array();
|
||||
[[nodiscard]] GC::Ref<DescriptorArray> copy_descriptors() const;
|
||||
void copy_properties_to_dictionary_shape(Shape&) const;
|
||||
|
||||
PropertyAttributes m_attributes { 0 };
|
||||
TransitionType m_transition_type { TransitionType::Invalid };
|
||||
|
|
@ -138,6 +137,7 @@ private:
|
|||
GC::Ref<Realm> m_realm;
|
||||
|
||||
mutable OwnPtr<OrderedHashMap<PropertyKey, PropertyMetadata>> m_property_table;
|
||||
GC::Ptr<DescriptorArray> m_descriptors;
|
||||
|
||||
OwnPtr<HashMap<TransitionKey, GC::Weak<Shape>>> m_forward_transitions;
|
||||
OwnPtr<HashMap<GC::Ptr<Object>, GC::Weak<Shape>>> m_prototype_transitions;
|
||||
|
|
@ -152,11 +152,12 @@ private:
|
|||
OwnPtr<Vector<GC::Weak<Shape>>> m_child_prototype_shapes;
|
||||
|
||||
u32 m_property_count { 0 };
|
||||
u32 m_own_descriptor_count { 0 };
|
||||
u32 m_dictionary_generation { 0 };
|
||||
};
|
||||
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
static_assert(sizeof(Shape) == 104, "Keep the size of JS::Shape down!");
|
||||
static_assert(sizeof(Shape) == 120, "Keep the size of JS::Shape down!");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,20 +159,20 @@ ThrowCompletionOr<GC::RootVector<Value>> StringObject::internal_own_property_key
|
|||
}
|
||||
|
||||
// 7. For each own property key P of O such that P is a String and P is not an array index, in ascending chronological order of property creation, do
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_string()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_string()) {
|
||||
// a. Add P as the last element of keys.
|
||||
keys.append(it.key.to_value(vm));
|
||||
keys.append(property_key.to_value(vm));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 8. For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_symbol()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_symbol()) {
|
||||
// a. Add P as the last element of keys.
|
||||
keys.append(it.key.to_value(vm));
|
||||
keys.append(property_key.to_value(vm));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 9. Return keys.
|
||||
return { move(keys) };
|
||||
|
|
|
|||
|
|
@ -477,20 +477,20 @@ public:
|
|||
}
|
||||
|
||||
// 4. For each own property key P of O such that P is a String and P is not an integer index, in ascending chronological order of property creation, do
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_string()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_string()) {
|
||||
// a. Append P to keys.
|
||||
keys.append(it.key.to_value(vm));
|
||||
keys.append(property_key.to_value(vm));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 5. For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_symbol()) {
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_symbol()) {
|
||||
// a. Append P to keys.
|
||||
keys.append(it.key.to_value(vm));
|
||||
keys.append(property_key.to_value(vm));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Return keys.
|
||||
return { move(keys) };
|
||||
|
|
|
|||
|
|
@ -432,16 +432,16 @@ JS::ThrowCompletionOr<GC::RootVector<JS::Value>> PlatformObject::internal_own_pr
|
|||
|
||||
// 4. For each P of O’s own property keys that is a String, in ascending chronological order of property creation, append P to keys.
|
||||
// NB: A PropertyKey containing a number is a String (it can only be a String or a Symbol, the number representation is an optimization).
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_string() || it.key.is_number())
|
||||
keys.append(it.key.to_value(vm));
|
||||
}
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_string() || property_key.is_number())
|
||||
keys.append(property_key.to_value(vm));
|
||||
});
|
||||
|
||||
// 5. For each P of O’s own property keys that is a Symbol, in ascending chronological order of property creation, append P to keys.
|
||||
for (auto& it : shape().property_table()) {
|
||||
if (it.key.is_symbol())
|
||||
keys.append(it.key.to_value(vm));
|
||||
}
|
||||
shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (property_key.is_symbol())
|
||||
keys.append(property_key.to_value(vm));
|
||||
});
|
||||
|
||||
// FIXME: 6. Assert: keys has no duplicate items.
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,11 @@ WebIDL::ExceptionOr<ImportMap> parse_import_map_string(JS::Realm& realm, ByteStr
|
|||
}
|
||||
|
||||
// 9. If parsed's keys contains any items besides "imports", "scopes", or "integrity", then the user agent should report a warning to the console indicating that an invalid top-level key was present in the import map.
|
||||
for (auto& key : parsed_object.shape().property_table().keys()) {
|
||||
Vector<JS::PropertyKey> parsed_keys;
|
||||
parsed_object.shape().for_each_property_in_insertion_order([&](auto const& key, auto const&) {
|
||||
parsed_keys.append(key);
|
||||
});
|
||||
for (auto& key : parsed_keys) {
|
||||
if (key.as_string().is_one_of("imports"sv, "scopes"sv, "integrity"sv))
|
||||
continue;
|
||||
|
||||
|
|
@ -132,7 +136,11 @@ WebIDL::ExceptionOr<ModuleSpecifierMap> sort_and_normalise_module_specifier_map(
|
|||
ModuleSpecifierMap normalized;
|
||||
|
||||
// 2. For each specifierKey → value of originalMap:
|
||||
for (auto& specifier_key : original_map.shape().property_table().keys()) {
|
||||
Vector<JS::PropertyKey> specifier_keys;
|
||||
original_map.shape().for_each_property_in_insertion_order([&](auto const& specifier_key, auto const&) {
|
||||
specifier_keys.append(specifier_key);
|
||||
});
|
||||
for (auto& specifier_key : specifier_keys) {
|
||||
auto value = TRY(original_map.get(specifier_key.as_string()));
|
||||
|
||||
// 1. Let normalizedSpecifierKey be the result of normalizing a specifier key given specifierKey and baseURL.
|
||||
|
|
@ -200,7 +208,11 @@ WebIDL::ExceptionOr<HashMap<URL::URL, ModuleSpecifierMap>> sort_and_normalise_sc
|
|||
HashMap<URL::URL, ModuleSpecifierMap> normalized;
|
||||
|
||||
// 2. For each scopePrefix → potentialSpecifierMap of originalMap:
|
||||
for (auto& scope_prefix : original_map.shape().property_table().keys()) {
|
||||
Vector<JS::PropertyKey> scope_prefixes;
|
||||
original_map.shape().for_each_property_in_insertion_order([&](auto const& scope_prefix, auto const&) {
|
||||
scope_prefixes.append(scope_prefix);
|
||||
});
|
||||
for (auto& scope_prefix : scope_prefixes) {
|
||||
auto potential_specifier_map = TRY(original_map.get(scope_prefix.as_string()));
|
||||
|
||||
// 1. If potentialSpecifierMap is not an ordered map, then throw a TypeError indicating that the value of the scope with prefix scopePrefix needs to be a JSON object.
|
||||
|
|
@ -237,7 +249,11 @@ WebIDL::ExceptionOr<ModuleIntegrityMap> normalize_module_integrity_map(JS::Realm
|
|||
ModuleIntegrityMap normalized;
|
||||
|
||||
// 2. For each key → value of originalMap:
|
||||
for (auto& key : original_map.shape().property_table().keys()) {
|
||||
Vector<JS::PropertyKey> keys;
|
||||
original_map.shape().for_each_property_in_insertion_order([&](auto const& key, auto const&) {
|
||||
keys.append(key);
|
||||
});
|
||||
for (auto& key : keys) {
|
||||
auto value = TRY(original_map.get(key.as_string()));
|
||||
|
||||
// 1. Let resolvedURL be the result of resolving a URL-like module specifier given key and baseURL.
|
||||
|
|
|
|||
|
|
@ -387,6 +387,21 @@ describe("Object.defineProperty on dictionaries", () => {
|
|||
});
|
||||
|
||||
describe("dictionary objects with prototype chain", () => {
|
||||
test("setting prototype of a very large dictionary object", () => {
|
||||
const obj = {};
|
||||
const propertyCount = 65537;
|
||||
for (let i = 0; i < propertyCount; ++i) {
|
||||
obj["p" + i] = i;
|
||||
}
|
||||
|
||||
const proto = { inherited: "value" };
|
||||
Object.setPrototypeOf(obj, proto);
|
||||
|
||||
expect(obj.p0).toBe(0);
|
||||
expect(obj.p65536).toBe(65536);
|
||||
expect(obj.inherited).toBe("value");
|
||||
});
|
||||
|
||||
test("dictionary object inheriting from prototype", () => {
|
||||
const proto = { inherited: 42 };
|
||||
const obj = Object.create(proto);
|
||||
|
|
|
|||
|
|
@ -174,15 +174,15 @@ TESTJS_GLOBAL_FUNCTION(parse_webassembly_module, parseWebAssemblyModule)
|
|||
HashMap<Wasm::Linker::Name, Wasm::ExternValue> imports;
|
||||
auto import_value = vm.argument(1);
|
||||
if (auto import_object = import_value.template as_if<JS::Object>()) {
|
||||
for (auto const& property : import_object->shape().property_table()) {
|
||||
auto module_object = import_object->get_without_side_effects(property.key).as_if<WebAssemblyModule>();
|
||||
import_object->shape().for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
auto module_object = import_object->get_without_side_effects(property_key).template as_if<WebAssemblyModule>();
|
||||
if (!module_object)
|
||||
continue;
|
||||
return;
|
||||
for (auto& entry : module_object->module_instance().exports()) {
|
||||
// FIXME: Don't pretend that everything is a function
|
||||
imports.set({ property.key.as_string().to_utf16_string().to_byte_string(), entry.name(), Wasm::TypeIndex(0) }, entry.value());
|
||||
imports.set({ property_key.as_string().to_utf16_string().to_byte_string(), entry.name(), Wasm::TypeIndex(0) }, entry.value());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return JS::Value(TRY(WebAssemblyModule::create(realm, result.release_value(), imports)));
|
||||
|
|
|
|||
|
|
@ -789,11 +789,11 @@ static ErrorOr<int> run_repl(bool gc_on_every_allocation, bool syntax_highlight)
|
|||
Vector<Line::CompletionSuggestion> results;
|
||||
|
||||
Function<void(JS::Shape const&, Utf16FlyString const&)> list_all_properties = [&results, &list_all_properties](JS::Shape const& shape, Utf16FlyString const& property_pattern) {
|
||||
for (auto const& descriptor : shape.property_table()) {
|
||||
if (!descriptor.key.is_string())
|
||||
continue;
|
||||
shape.for_each_property_in_insertion_order([&](auto const& property_key, auto const&) {
|
||||
if (!property_key.is_string())
|
||||
return;
|
||||
|
||||
auto key = descriptor.key.as_string().to_utf16_string();
|
||||
auto key = property_key.as_string().to_utf16_string();
|
||||
|
||||
if (key.starts_with(property_pattern.view())) {
|
||||
Line::CompletionSuggestion completion { key.to_utf8_but_should_be_ported_to_utf16(), Line::CompletionSuggestion::ForSearch };
|
||||
|
|
@ -802,7 +802,7 @@ static ErrorOr<int> run_repl(bool gc_on_every_allocation, bool syntax_highlight)
|
|||
results.last().invariant_offset = property_pattern.length_in_code_units();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (auto const* prototype = shape.prototype()) {
|
||||
list_all_properties(prototype->shape(), property_pattern);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue