1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibJS+LibWeb: Replace StringOrSymbol usage with PropertyKey

- Avoids unnecessary conversions between StringOrSymbol and PropertyKey
  on the hot path of property access.
- Simplifies the code by removing StringOrSymbol and using PropertyKey
  directly. There was no reason to have a separate StringOrSymbol type
  representing the same data as PropertyKey, just with the index key
  stored as a string.

PropertyKey has been updated to use a tagged pointer instead of a
Variant, so it still occupies 8 bytes, same as StringOrSymbol.

12% improvement on JetStream/gcc-loops.cpp.js
12% improvement on MicroBench/object-assign.js
7% improvement on MicroBench/object-keys.js
This commit is contained in:
Aliaksandr Kalenik 2025-05-15 17:14:00 +03:00 committed by Alexander Kalenik
parent 5495531118
commit b559965448
Notes: github-actions[bot] 2025-05-17 14:09:31 +00:00
12 changed files with 195 additions and 275 deletions

View file

@ -243,7 +243,7 @@ ThrowCompletionOr<ClassElement::ClassValue> ClassField::class_element_evaluation
auto copy_initializer = m_initializer;
auto name = property_key_or_private_name.visit(
[&](PropertyKey const& property_key) -> String {
return property_key.is_number() ? property_key.to_string() : property_key.to_string_or_symbol().to_display_string();
return property_key.to_string();
},
[&](PrivateName const& private_name) -> String {
return private_name.description.to_string();

View file

@ -1921,7 +1921,7 @@ static void dump_object(Object& o, HashTable<Object const*>& seen, int indent =
seen.set(&o);
for (auto& it : o.shape().property_table()) {
auto value = o.get_direct(it.value.offset);
dbgln("{} {} -> {}", String::repeated(' ', indent).release_value(), it.key.to_display_string(), value);
dbgln("{} {} -> {}", String::repeated(' ', indent).release_value(), it.key.to_string(), value);
if (value.is_object()) {
dump_object(value.as_object(), seen, indent + 2);
}

View file

@ -124,7 +124,7 @@ ErrorOr<void> MarkupGenerator::object_to_html(Object const& object, StringBuilde
size_t index = 0;
for (auto& it : object.shape().property_table()) {
TRY(html_output.try_append(TRY(wrap_string_in_style(TRY(String::formatted("\"{}\"", escape_html_entities(it.key.to_display_string()))), StyleType::String))));
TRY(html_output.try_append(TRY(wrap_string_in_style(TRY(String::formatted("\"{}\"", escape_html_entities(it.key.to_string()))), StyleType::String))));
TRY(html_output.try_append(TRY(wrap_string_in_style(": "sv, StyleType::Punctuation))));
TRY(value_to_html(object.get_direct(it.value.offset), html_output, seen_objects));
if (index != object.shape().property_count() - 1)

View file

@ -196,47 +196,47 @@ void Intrinsics::initialize_intrinsics(Realm& realm)
m_iterator_result_object_shape->set_prototype_without_transition(m_object_prototype);
m_iterator_result_object_shape->add_property_without_transition(vm.names.value, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
m_iterator_result_object_shape->add_property_without_transition(vm.names.done, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable);
m_iterator_result_object_value_offset = m_iterator_result_object_shape->lookup(vm.names.value.to_string_or_symbol()).value().offset;
m_iterator_result_object_done_offset = m_iterator_result_object_shape->lookup(vm.names.done.to_string_or_symbol()).value().offset;
m_iterator_result_object_value_offset = m_iterator_result_object_shape->lookup(vm.names.value).value().offset;
m_iterator_result_object_done_offset = m_iterator_result_object_shape->lookup(vm.names.done).value().offset;
m_normal_function_prototype_shape = heap().allocate<Shape>(realm);
m_normal_function_prototype_shape->set_prototype_without_transition(m_object_prototype);
m_normal_function_prototype_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable);
m_normal_function_prototype_constructor_offset = m_normal_function_prototype_shape->lookup(vm.names.constructor.to_string_or_symbol()).value().offset;
m_normal_function_prototype_constructor_offset = m_normal_function_prototype_shape->lookup(vm.names.constructor).value().offset;
m_normal_function_shape = heap().allocate<Shape>(realm);
m_normal_function_shape->set_prototype_without_transition(m_function_prototype);
m_normal_function_shape->add_property_without_transition(vm.names.length, Attribute::Configurable);
m_normal_function_shape->add_property_without_transition(vm.names.name, Attribute::Configurable);
m_normal_function_shape->add_property_without_transition(vm.names.prototype, Attribute::Writable);
m_normal_function_length_offset = m_normal_function_shape->lookup(vm.names.length.to_string_or_symbol()).value().offset;
m_normal_function_name_offset = m_normal_function_shape->lookup(vm.names.name.to_string_or_symbol()).value().offset;
m_normal_function_prototype_offset = m_normal_function_shape->lookup(vm.names.prototype.to_string_or_symbol()).value().offset;
m_normal_function_length_offset = m_normal_function_shape->lookup(vm.names.length).value().offset;
m_normal_function_name_offset = m_normal_function_shape->lookup(vm.names.name).value().offset;
m_normal_function_prototype_offset = m_normal_function_shape->lookup(vm.names.prototype).value().offset;
m_native_function_shape = heap().allocate<Shape>(realm);
m_native_function_shape->set_prototype_without_transition(m_function_prototype);
m_native_function_shape->add_property_without_transition(vm.names.length, Attribute::Configurable);
m_native_function_shape->add_property_without_transition(vm.names.name, Attribute::Configurable);
m_native_function_length_offset = m_native_function_shape->lookup(vm.names.length.to_string_or_symbol()).value().offset;
m_native_function_name_offset = m_native_function_shape->lookup(vm.names.name.to_string_or_symbol()).value().offset;
m_native_function_length_offset = m_native_function_shape->lookup(vm.names.length).value().offset;
m_native_function_name_offset = m_native_function_shape->lookup(vm.names.name).value().offset;
m_unmapped_arguments_object_shape = heap().allocate<Shape>(realm);
m_unmapped_arguments_object_shape->set_prototype_without_transition(m_object_prototype);
m_unmapped_arguments_object_shape->add_property_without_transition(vm.names.length, Attribute::Writable | Attribute::Configurable);
m_unmapped_arguments_object_shape->add_property_without_transition(vm.well_known_symbol_iterator(), Attribute::Writable | Attribute::Configurable);
m_unmapped_arguments_object_shape->add_property_without_transition(vm.names.callee, 0);
m_unmapped_arguments_object_length_offset = m_unmapped_arguments_object_shape->lookup(vm.names.length.to_string_or_symbol()).value().offset;
m_unmapped_arguments_object_well_known_symbol_iterator_offset = m_unmapped_arguments_object_shape->lookup(StringOrSymbol(vm.well_known_symbol_iterator())).value().offset;
m_unmapped_arguments_object_callee_offset = m_unmapped_arguments_object_shape->lookup(vm.names.callee.to_string_or_symbol()).value().offset;
m_unmapped_arguments_object_length_offset = m_unmapped_arguments_object_shape->lookup(vm.names.length).value().offset;
m_unmapped_arguments_object_well_known_symbol_iterator_offset = m_unmapped_arguments_object_shape->lookup(vm.well_known_symbol_iterator()).value().offset;
m_unmapped_arguments_object_callee_offset = m_unmapped_arguments_object_shape->lookup(vm.names.callee).value().offset;
m_mapped_arguments_object_shape = heap().allocate<Shape>(realm);
m_mapped_arguments_object_shape->set_prototype_without_transition(m_object_prototype);
m_mapped_arguments_object_shape->add_property_without_transition(vm.names.length, Attribute::Writable | Attribute::Configurable);
m_mapped_arguments_object_shape->add_property_without_transition(vm.well_known_symbol_iterator(), Attribute::Writable | Attribute::Configurable);
m_mapped_arguments_object_shape->add_property_without_transition(vm.names.callee, Attribute::Writable | Attribute::Configurable);
m_mapped_arguments_object_length_offset = m_mapped_arguments_object_shape->lookup(vm.names.length.to_string_or_symbol()).value().offset;
m_mapped_arguments_object_well_known_symbol_iterator_offset = m_mapped_arguments_object_shape->lookup(StringOrSymbol(vm.well_known_symbol_iterator())).value().offset;
m_mapped_arguments_object_callee_offset = m_mapped_arguments_object_shape->lookup(vm.names.callee.to_string_or_symbol()).value().offset;
m_mapped_arguments_object_length_offset = m_mapped_arguments_object_shape->lookup(vm.names.length).value().offset;
m_mapped_arguments_object_well_known_symbol_iterator_offset = m_mapped_arguments_object_shape->lookup(vm.well_known_symbol_iterator()).value().offset;
m_mapped_arguments_object_callee_offset = m_mapped_arguments_object_shape->lookup(vm.names.callee).value().offset;
// Normally Realm::create() takes care of this, but these are allocated via Heap::allocate().
m_function_prototype->initialize(realm);

View file

@ -1199,7 +1199,7 @@ Optional<ValueAndAttributes> Object::storage_get(PropertyKey const& property_key
value = value_and_attributes->value;
attributes = value_and_attributes->attributes;
} else {
auto metadata = shape().lookup(property_key.to_string_or_symbol());
auto metadata = shape().lookup(property_key);
if (!metadata.has_value())
return {};
@ -1220,7 +1220,7 @@ bool Object::storage_has(PropertyKey const& property_key) const
{
if (property_key.is_number())
return m_indexed_properties.has_index(property_key.as_number());
return shape().lookup(property_key.to_string_or_symbol()).has_value();
return shape().lookup(property_key).has_value();
}
void Object::storage_set(PropertyKey const& property_key, ValueAndAttributes const& value_and_attributes)
@ -1238,8 +1238,7 @@ void Object::storage_set(PropertyKey const& property_key, ValueAndAttributes con
intrinsics->value.remove(property_key.as_string());
}
auto property_key_string_or_symbol = property_key.to_string_or_symbol();
auto metadata = shape().lookup(property_key_string_or_symbol);
auto metadata = shape().lookup(property_key);
if (!metadata.has_value()) {
static constexpr size_t max_transitions_before_converting_to_dictionary = 64;
@ -1247,18 +1246,18 @@ void Object::storage_set(PropertyKey const& property_key, ValueAndAttributes con
set_shape(m_shape->create_cacheable_dictionary_transition());
if (m_shape->is_dictionary())
m_shape->add_property_without_transition(property_key_string_or_symbol, attributes);
m_shape->add_property_without_transition(property_key, attributes);
else
set_shape(*m_shape->create_put_transition(property_key_string_or_symbol, attributes));
set_shape(*m_shape->create_put_transition(property_key, attributes));
m_storage.append(value);
return;
}
if (attributes != metadata->attributes) {
if (m_shape->is_dictionary())
m_shape->set_property_attributes_without_transition(property_key_string_or_symbol, attributes);
m_shape->set_property_attributes_without_transition(property_key, attributes);
else
set_shape(*m_shape->create_configure_transition(property_key_string_or_symbol, attributes));
set_shape(*m_shape->create_configure_transition(property_key, attributes));
}
m_storage[metadata->offset] = value;
@ -1276,18 +1275,18 @@ void Object::storage_delete(PropertyKey const& property_key)
intrinsics->value.remove(property_key.as_string());
}
auto metadata = shape().lookup(property_key.to_string_or_symbol());
auto metadata = shape().lookup(property_key);
VERIFY(metadata.has_value());
if (m_shape->is_cacheable_dictionary()) {
m_shape = m_shape->create_uncacheable_dictionary_transition();
}
if (m_shape->is_uncacheable_dictionary()) {
m_shape->remove_property_without_transition(property_key.to_string_or_symbol(), metadata->offset);
m_shape->remove_property_without_transition(property_key, metadata->offset);
m_storage.remove(metadata->offset);
return;
}
m_shape = m_shape->create_delete_transition(property_key.to_string_or_symbol());
m_shape = m_shape->create_delete_transition(property_key);
m_storage.remove(metadata->offset);
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,16 +8,13 @@
#pragma once
#include <AK/FlyString.h>
#include <LibGC/Root.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/StringOrSymbol.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Symbol.h>
namespace JS {
class PropertyKey {
AK_MAKE_DEFAULT_COPYABLE(PropertyKey);
AK_MAKE_DEFAULT_MOVABLE(PropertyKey);
public:
enum class StringMayBeNumber {
Yes,
@ -33,26 +31,61 @@ public:
return TRY(value.to_string(vm));
}
static constexpr uintptr_t NORMAL_STRING_FLAG = 0;
static constexpr uintptr_t SHORT_STRING_FLAG = 1;
static constexpr uintptr_t SYMBOL_FLAG = 2;
static constexpr uintptr_t NUMBER_FLAG = 3;
bool is_string() const { return (m_bits & 3) == NORMAL_STRING_FLAG || (m_bits & 3) == SHORT_STRING_FLAG; }
bool is_number() const { return (m_bits & 3) == NUMBER_FLAG; }
bool is_symbol() const { return (m_bits & 3) == SYMBOL_FLAG; }
PropertyKey() = delete;
PropertyKey(PropertyKey const& other)
{
if (other.is_string())
new (&m_string) FlyString(other.m_string);
else
m_bits = other.m_bits;
}
PropertyKey(PropertyKey&& other) noexcept
{
if (other.is_string())
new (&m_string) FlyString(move(other.m_string));
else
m_bits = exchange(other.m_bits, 0);
}
template<Integral T>
PropertyKey(T index)
: m_data(index)
{
// FIXME: Replace this with requires(IsUnsigned<T>)?
// Needs changes in various places using `int` (but not actually being in the negative range)
VERIFY(index >= 0);
if constexpr (NumericLimits<T>::max() >= NumericLimits<u32>::max()) {
if (index >= NumericLimits<u32>::max()) {
m_data = FlyString { String::number(index) };
new (&m_string) FlyString { String::number(index) };
return;
}
}
m_number = static_cast<u64>(index) << 2 | NUMBER_FLAG;
}
PropertyKey(FlyString string, StringMayBeNumber string_may_be_number = StringMayBeNumber::Yes)
: m_data { try_coerce_into_number(move(string), string_may_be_number) }
{
if (string_may_be_number == StringMayBeNumber::Yes) {
auto view = string.bytes_as_string_view();
if (!view.is_empty() && !(view[0] == '0' && view.length() > 1)) {
auto property_index = view.to_number<u32>(TrimWhitespace::No);
if (property_index.has_value() && property_index.value() < NumericLimits<u32>::max()) {
m_number = static_cast<u64>(property_index.release_value()) << 2 | NUMBER_FLAG;
return;
}
}
}
new (&m_string) FlyString(move(string));
}
PropertyKey(String const& string)
@ -61,66 +94,102 @@ public:
}
PropertyKey(GC::Ref<Symbol> symbol)
: m_data { symbol }
{
m_bits = reinterpret_cast<uintptr_t>(symbol.ptr()) | SYMBOL_FLAG;
}
PropertyKey(StringOrSymbol const& string_or_symbol)
: m_data {
string_or_symbol.is_string()
? Variant<FlyString, GC::Root<Symbol>, u32> { string_or_symbol.as_string() }
: Variant<FlyString, GC::Root<Symbol>, u32> { const_cast<Symbol*>(string_or_symbol.as_symbol()) }
PropertyKey& operator=(PropertyKey const& other)
{
if (this != &other) {
if (is_string())
m_string.~FlyString();
new (this) PropertyKey(other);
}
{
return *this;
}
bool is_number() const { return m_data.has<u32>(); }
bool is_string() const { return m_data.has<FlyString>(); }
bool is_symbol() const { return m_data.has<GC::Root<Symbol>>(); }
PropertyKey& operator=(PropertyKey&& other) noexcept
{
if (this != &other) {
if (is_string())
m_string.~FlyString();
new (this) PropertyKey(move(other));
}
return *this;
}
u32 as_number() const { return m_data.get<u32>(); }
FlyString const& as_string() const { return m_data.get<FlyString>(); }
Symbol const* as_symbol() const { return m_data.get<GC::Root<Symbol>>(); }
~PropertyKey()
{
if (is_string())
m_string.~FlyString();
}
u32 as_number() const
{
VERIFY(is_number());
return m_number >> 2;
}
FlyString const& as_string() const
{
VERIFY(is_string());
return m_string;
}
Symbol const* as_symbol() const
{
VERIFY(is_symbol());
return reinterpret_cast<Symbol const*>(m_bits & ~3ULL);
}
Value to_value(VM& vm) const
{
if (is_string())
return Value { PrimitiveString::create(vm, as_string()) };
if (is_symbol())
return Value { as_symbol() };
return Value { PrimitiveString::create(vm, String::number(as_number())) };
}
String to_string() const
{
VERIFY(!is_symbol());
if (is_string())
return as_string().to_string();
if (is_symbol())
return MUST(as_symbol()->descriptive_string());
return String::number(as_number());
}
StringOrSymbol to_string_or_symbol() const
void visit_edges(Cell::Visitor& visitor) const
{
VERIFY(!is_number());
if (is_string())
return StringOrSymbol(as_string());
return StringOrSymbol(as_symbol());
if (is_symbol())
visitor.visit(const_cast<Symbol*>(as_symbol()));
}
bool operator==(PropertyKey const&) const = default;
bool operator==(PropertyKey const& other) const
{
if (is_string())
return other.is_string() && m_string == other.m_string;
if (is_symbol())
return other.is_symbol() && as_symbol() == other.as_symbol();
if (other.is_number())
return as_number() == other.as_number();
return false;
}
private:
friend Traits<JS::PropertyKey>;
friend Traits<PropertyKey>;
static Variant<FlyString, u32> try_coerce_into_number(FlyString string, StringMayBeNumber string_may_be_number)
{
if (string_may_be_number != StringMayBeNumber::Yes)
return string;
auto view = string.bytes_as_string_view();
if (view.is_empty())
return string;
if (view[0] == '0' && view.length() > 1)
return string;
auto property_index = view.to_number<u32>(TrimWhitespace::No);
if (!property_index.has_value() || property_index.value() >= NumericLimits<u32>::max())
return string;
return property_index.release_value();
}
Variant<FlyString, u32, GC::Root<Symbol>> m_data;
union {
FlyString m_string;
u64 m_number;
Symbol const* m_symbol;
uintptr_t m_bits;
};
};
static_assert(sizeof(PropertyKey) == sizeof(uintptr_t));
}
namespace AK {
@ -129,15 +198,24 @@ template<>
struct Traits<JS::PropertyKey> : public DefaultTraits<JS::PropertyKey> {
static unsigned hash(JS::PropertyKey const& name)
{
return name.m_data.visit(
[](FlyString const& string) { return string.hash(); },
[](GC::Root<JS::Symbol> const& symbol) { return ptr_hash(symbol.ptr()); },
[](u32 const& number) { return int_hash(number); });
if (name.is_string())
return name.as_string().hash();
if (name.is_symbol())
return ptr_hash(name.as_symbol());
if (name.is_number())
return int_hash(name.as_number());
VERIFY_NOT_REACHED();
}
static bool equals(JS::PropertyKey const& a, JS::PropertyKey const& b)
{
return a.m_data == b.m_data;
if (a.is_string())
return b.is_string() && a.as_string() == b.as_string();
if (a.is_symbol())
return b.is_symbol() && a.as_symbol() == b.as_symbol();
if (a.is_number())
return b.is_number() && a.as_number() == b.as_number();
VERIFY_NOT_REACHED();
}
};
@ -147,7 +225,7 @@ struct Formatter<JS::PropertyKey> : Formatter<StringView> {
{
if (property_key.is_number())
return builder.put_u64(property_key.as_number());
return builder.put_string(property_key.to_string_or_symbol().to_display_string());
return builder.put_string(property_key.to_string());
}
};

View file

@ -78,7 +78,7 @@ Completion Reference::throw_reference_error(VM& vm) const
if (is_private_reference())
return vm.throw_completion<ReferenceError>(ErrorType::ReferenceUnresolvable);
else
return vm.throw_completion<ReferenceError>(ErrorType::UnknownIdentifier, name().to_string_or_symbol().to_display_string());
return vm.throw_completion<ReferenceError>(ErrorType::UnknownIdentifier, name().to_string());
}
// 6.2.4.5 GetValue ( V ), https://tc39.es/ecma262/#sec-getvalue

View file

@ -66,7 +66,7 @@ GC::Ptr<Shape> Shape::get_or_prune_cached_forward_transition(TransitionKey const
return it->value.ptr();
}
GC::Ptr<Shape> Shape::get_or_prune_cached_delete_transition(StringOrSymbol const& key)
GC::Ptr<Shape> Shape::get_or_prune_cached_delete_transition(PropertyKey const& key)
{
if (m_is_prototype_shape)
return nullptr;
@ -100,7 +100,7 @@ GC::Ptr<Shape> Shape::get_or_prune_cached_prototype_transition(Object* prototype
return it->value.ptr();
}
GC::Ref<Shape> Shape::create_put_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
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))
@ -115,7 +115,7 @@ GC::Ref<Shape> Shape::create_put_transition(StringOrSymbol const& property_key,
return new_shape;
}
GC::Ref<Shape> Shape::create_configure_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
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))
@ -151,7 +151,7 @@ Shape::Shape(Realm& realm)
{
}
Shape::Shape(Shape& previous_shape, StringOrSymbol const& property_key, PropertyAttributes attributes, TransitionType transition_type)
Shape::Shape(Shape& previous_shape, PropertyKey const& property_key, PropertyAttributes attributes, TransitionType transition_type)
: m_realm(previous_shape.m_realm)
, m_previous(&previous_shape)
, m_property_key(property_key)
@ -162,7 +162,7 @@ Shape::Shape(Shape& previous_shape, StringOrSymbol const& property_key, Property
{
}
Shape::Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType transition_type)
Shape::Shape(Shape& previous_shape, PropertyKey const& property_key, TransitionType transition_type)
: m_realm(previous_shape.m_realm)
, m_previous(&previous_shape)
, m_property_key(property_key)
@ -188,7 +188,8 @@ void Shape::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_realm);
visitor.visit(m_prototype);
visitor.visit(m_previous);
m_property_key.visit_edges(visitor);
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.
@ -210,7 +211,7 @@ void Shape::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_prototype_chain_validity);
}
Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) const
Optional<PropertyMetadata> Shape::lookup(PropertyKey const& property_key) const
{
if (m_property_count == 0)
return {};
@ -220,7 +221,7 @@ Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) con
return property;
}
FLATTEN OrderedHashMap<StringOrSymbol, PropertyMetadata> const& Shape::property_table() const
FLATTEN OrderedHashMap<PropertyKey, PropertyMetadata> const& Shape::property_table() const
{
ensure_property_table();
return *m_property_table;
@ -230,7 +231,7 @@ void Shape::ensure_property_table() const
{
if (m_property_table)
return;
m_property_table = make<OrderedHashMap<StringOrSymbol, PropertyMetadata>>();
m_property_table = make<OrderedHashMap<PropertyKey, PropertyMetadata>>();
u32 next_offset = 0;
@ -246,18 +247,18 @@ void Shape::ensure_property_table() const
}
for (auto const& shape : transition_chain.in_reverse()) {
if (!shape.m_property_key.is_valid()) {
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 });
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);
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);
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);
@ -270,19 +271,19 @@ void Shape::ensure_property_table() const
}
}
GC::Ref<Shape> Shape::create_delete_transition(StringOrSymbol const& property_key)
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<StringOrSymbol, WeakPtr<Shape>>>();
m_delete_transitions = make<HashMap<PropertyKey, WeakPtr<Shape>>>();
m_delete_transitions->set(property_key, new_shape.ptr());
return new_shape;
}
void Shape::add_property_without_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
void Shape::add_property_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
{
ensure_property_table();
if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) {
@ -291,12 +292,7 @@ void Shape::add_property_without_transition(StringOrSymbol const& property_key,
}
}
FLATTEN void Shape::add_property_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
{
add_property_without_transition(property_key.to_string_or_symbol(), attributes);
}
void Shape::set_property_attributes_without_transition(StringOrSymbol const& property_key, PropertyAttributes attributes)
void Shape::set_property_attributes_without_transition(PropertyKey const& property_key, PropertyAttributes attributes)
{
VERIFY(is_dictionary());
VERIFY(m_property_table);
@ -306,7 +302,7 @@ void Shape::set_property_attributes_without_transition(StringOrSymbol const& pro
m_property_table->set(property_key, it->value);
}
void Shape::remove_property_without_transition(StringOrSymbol const& property_key, u32 offset)
void Shape::remove_property_without_transition(PropertyKey const& property_key, u32 offset)
{
VERIFY(is_uncacheable_dictionary());
VERIFY(m_property_table);

View file

@ -14,7 +14,7 @@
#include <LibJS/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibJS/Runtime/PropertyAttributes.h>
#include <LibJS/Runtime/StringOrSymbol.h>
#include <LibJS/Runtime/PropertyKey.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
@ -25,7 +25,7 @@ struct PropertyMetadata {
};
struct TransitionKey {
StringOrSymbol property_key;
PropertyKey property_key;
PropertyAttributes attributes { 0 };
bool operator==(TransitionKey const& other) const
@ -66,20 +66,19 @@ public:
UncacheableDictionary,
};
[[nodiscard]] GC::Ref<Shape> create_put_transition(StringOrSymbol const&, PropertyAttributes attributes);
[[nodiscard]] GC::Ref<Shape> create_configure_transition(StringOrSymbol const&, PropertyAttributes attributes);
[[nodiscard]] GC::Ref<Shape> create_put_transition(PropertyKey const&, PropertyAttributes attributes);
[[nodiscard]] GC::Ref<Shape> create_configure_transition(PropertyKey const&, PropertyAttributes attributes);
[[nodiscard]] GC::Ref<Shape> create_prototype_transition(Object* new_prototype);
[[nodiscard]] GC::Ref<Shape> create_delete_transition(StringOrSymbol const&);
[[nodiscard]] GC::Ref<Shape> create_delete_transition(PropertyKey const&);
[[nodiscard]] GC::Ref<Shape> create_cacheable_dictionary_transition();
[[nodiscard]] GC::Ref<Shape> create_uncacheable_dictionary_transition();
[[nodiscard]] GC::Ref<Shape> clone_for_prototype();
[[nodiscard]] static GC::Ref<Shape> create_for_prototype(GC::Ref<Realm>, GC::Ptr<Object> prototype);
void add_property_without_transition(StringOrSymbol const&, PropertyAttributes);
void add_property_without_transition(PropertyKey const&, PropertyAttributes);
void remove_property_without_transition(StringOrSymbol const&, u32 offset);
void set_property_attributes_without_transition(StringOrSymbol const&, PropertyAttributes);
void remove_property_without_transition(PropertyKey const&, u32 offset);
void set_property_attributes_without_transition(PropertyKey const&, PropertyAttributes);
[[nodiscard]] bool is_cacheable() const { return m_cacheable; }
[[nodiscard]] bool is_dictionary() const { return m_dictionary; }
@ -96,12 +95,12 @@ public:
Object* prototype() { return m_prototype; }
Object const* prototype() const { return m_prototype; }
Optional<PropertyMetadata> lookup(StringOrSymbol const&) const;
OrderedHashMap<StringOrSymbol, PropertyMetadata> const& property_table() const;
Optional<PropertyMetadata> lookup(PropertyKey const&) const;
OrderedHashMap<PropertyKey, PropertyMetadata> const& property_table() const;
u32 property_count() const { return m_property_count; }
struct Property {
StringOrSymbol key;
PropertyKey key;
PropertyMetadata value;
};
@ -109,8 +108,8 @@ public:
private:
explicit Shape(Realm&);
Shape(Shape& previous_shape, StringOrSymbol const& property_key, PropertyAttributes attributes, TransitionType);
Shape(Shape& previous_shape, StringOrSymbol const& property_key, TransitionType);
Shape(Shape& previous_shape, PropertyKey const& property_key, PropertyAttributes attributes, TransitionType);
Shape(Shape& previous_shape, PropertyKey const& property_key, TransitionType);
Shape(Shape& previous_shape, Object* new_prototype);
void invalidate_prototype_if_needed_for_new_prototype(GC::Ref<Shape> new_prototype_shape);
@ -120,19 +119,19 @@ private:
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_forward_transition(TransitionKey const&);
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_prototype_transition(Object* prototype);
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_delete_transition(StringOrSymbol const&);
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_delete_transition(PropertyKey const&);
void ensure_property_table() const;
GC::Ref<Realm> m_realm;
mutable OwnPtr<OrderedHashMap<StringOrSymbol, PropertyMetadata>> m_property_table;
mutable OwnPtr<OrderedHashMap<PropertyKey, PropertyMetadata>> m_property_table;
OwnPtr<HashMap<TransitionKey, WeakPtr<Shape>>> m_forward_transitions;
OwnPtr<HashMap<GC::Ptr<Object>, WeakPtr<Shape>>> m_prototype_transitions;
OwnPtr<HashMap<StringOrSymbol, WeakPtr<Shape>>> m_delete_transitions;
OwnPtr<HashMap<PropertyKey, WeakPtr<Shape>>> m_delete_transitions;
GC::Ptr<Shape> m_previous;
StringOrSymbol m_property_key;
Optional<PropertyKey> m_property_key;
GC::Ptr<Object> m_prototype;
GC::Ptr<PrototypeChainValidity> m_prototype_chain_validity;
@ -153,6 +152,6 @@ template<>
struct AK::Traits<JS::TransitionKey> : public DefaultTraits<JS::TransitionKey> {
static unsigned hash(const JS::TransitionKey& key)
{
return pair_int_hash(key.attributes.bits(), Traits<JS::StringOrSymbol>::hash(key.property_key));
return pair_int_hash(key.attributes.bits(), Traits<JS::PropertyKey>::hash(key.property_key));
}
};

View file

@ -1,153 +0,0 @@
/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Symbol.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
class StringOrSymbol {
public:
StringOrSymbol()
: m_bits(0)
{
}
StringOrSymbol(FlyString const& string)
: m_string(string)
{
}
~StringOrSymbol()
{
if (is_string())
m_string.~FlyString();
}
StringOrSymbol(Symbol const* symbol)
: m_symbol_with_tag(symbol)
{
set_symbol_flag();
}
StringOrSymbol(StringOrSymbol const& other)
{
if (other.is_string())
new (&m_string) FlyString(other.m_string);
else
m_bits = other.m_bits;
}
StringOrSymbol(StringOrSymbol&& other)
{
if (other.is_string())
new (&m_string) FlyString(move(other.m_string));
else
m_bits = exchange(other.m_bits, 0);
}
ALWAYS_INLINE bool is_valid() const { return m_bits != 0; }
ALWAYS_INLINE bool is_symbol() const { return is_valid() && (m_bits & 2); }
ALWAYS_INLINE bool is_string() const { return is_valid() && !(m_bits & 2); }
ALWAYS_INLINE FlyString const& as_string() const
{
VERIFY(is_string());
return m_string;
}
ALWAYS_INLINE Symbol const* as_symbol() const
{
VERIFY(is_symbol());
return reinterpret_cast<Symbol const*>(m_bits & ~2ULL);
}
String to_display_string() const
{
if (is_string())
return as_string().to_string();
if (is_symbol())
return MUST(as_symbol()->descriptive_string());
VERIFY_NOT_REACHED();
}
Value to_value(VM& vm) const
{
if (is_string())
return PrimitiveString::create(vm, as_string());
if (is_symbol())
return const_cast<Symbol*>(as_symbol());
return {};
}
void visit_edges(Cell::Visitor& visitor)
{
if (is_symbol())
visitor.visit(const_cast<Symbol*>(as_symbol()));
}
ALWAYS_INLINE bool operator==(StringOrSymbol const& other) const
{
if (is_string())
return other.is_string() && m_string == other.m_string;
if (is_symbol())
return other.is_symbol() && as_symbol() == other.as_symbol();
return true;
}
StringOrSymbol& operator=(StringOrSymbol const& other)
{
if (this != &other) {
this->~StringOrSymbol();
new (this) StringOrSymbol(other);
}
return *this;
}
StringOrSymbol& operator=(StringOrSymbol&& other)
{
if (this != &other) {
this->~StringOrSymbol();
new (this) StringOrSymbol(move(other));
}
return *this;
}
unsigned hash() const
{
if (is_string())
return m_string.hash();
return ptr_hash(as_symbol());
}
private:
ALWAYS_INLINE void set_symbol_flag()
{
m_bits |= 2;
}
union {
FlyString m_string;
Symbol const* m_symbol_with_tag;
uintptr_t m_bits;
};
};
static_assert(sizeof(StringOrSymbol) == sizeof(uintptr_t));
}
template<>
struct AK::Traits<JS::StringOrSymbol> : public DefaultTraits<JS::StringOrSymbol> {
static unsigned hash(JS::StringOrSymbol const& key)
{
return key.hash();
}
};

View file

@ -8,6 +8,7 @@
#pragma once
#include <AK/String.h>
#include <LibGC/CellAllocator.h>
#include <LibJS/Heap/Cell.h>
namespace JS {

View file

@ -917,7 +917,7 @@ ThrowCompletionOr<PropertyKey> Value::to_property_key(VM& vm) const
// 2. If key is a Symbol, then
if (key.is_symbol()) {
// a. Return key.
return &key.as_symbol();
return key.as_symbol();
}
// 3. Return ! ToString(key).