LibJS+LibWeb: Replace StringOrSymbol usage with PropertyKey
Some checks are pending
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

- 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
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 copy_initializer = m_initializer;
auto name = property_key_or_private_name.visit( auto name = property_key_or_private_name.visit(
[&](PropertyKey const& property_key) -> String { [&](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 { [&](PrivateName const& private_name) -> String {
return private_name.description.to_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); seen.set(&o);
for (auto& it : o.shape().property_table()) { for (auto& it : o.shape().property_table()) {
auto value = o.get_direct(it.value.offset); 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()) { if (value.is_object()) {
dump_object(value.as_object(), seen, indent + 2); 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; size_t index = 0;
for (auto& it : object.shape().property_table()) { 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(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)); TRY(value_to_html(object.get_direct(it.value.offset), html_output, seen_objects));
if (index != object.shape().property_count() - 1) 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->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.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_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_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.to_string_or_symbol()).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 = heap().allocate<Shape>(realm);
m_normal_function_prototype_shape->set_prototype_without_transition(m_object_prototype); 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_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 = heap().allocate<Shape>(realm);
m_normal_function_shape->set_prototype_without_transition(m_function_prototype); 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.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.name, Attribute::Configurable);
m_normal_function_shape->add_property_without_transition(vm.names.prototype, Attribute::Writable); 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_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.to_string_or_symbol()).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.to_string_or_symbol()).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 = heap().allocate<Shape>(realm);
m_native_function_shape->set_prototype_without_transition(m_function_prototype); 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.length, Attribute::Configurable);
m_native_function_shape->add_property_without_transition(vm.names.name, 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_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.to_string_or_symbol()).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 = heap().allocate<Shape>(realm);
m_unmapped_arguments_object_shape->set_prototype_without_transition(m_object_prototype); 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.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.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_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_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(StringOrSymbol(vm.well_known_symbol_iterator())).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.to_string_or_symbol()).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 = heap().allocate<Shape>(realm);
m_mapped_arguments_object_shape->set_prototype_without_transition(m_object_prototype); 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.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.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_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_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(StringOrSymbol(vm.well_known_symbol_iterator())).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.to_string_or_symbol()).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(). // Normally Realm::create() takes care of this, but these are allocated via Heap::allocate().
m_function_prototype->initialize(realm); 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; value = value_and_attributes->value;
attributes = value_and_attributes->attributes; attributes = value_and_attributes->attributes;
} else { } else {
auto metadata = shape().lookup(property_key.to_string_or_symbol()); auto metadata = shape().lookup(property_key);
if (!metadata.has_value()) if (!metadata.has_value())
return {}; return {};
@ -1220,7 +1220,7 @@ bool Object::storage_has(PropertyKey const& property_key) const
{ {
if (property_key.is_number()) if (property_key.is_number())
return m_indexed_properties.has_index(property_key.as_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) 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()); 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);
auto metadata = shape().lookup(property_key_string_or_symbol);
if (!metadata.has_value()) { if (!metadata.has_value()) {
static constexpr size_t max_transitions_before_converting_to_dictionary = 64; 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()); set_shape(m_shape->create_cacheable_dictionary_transition());
if (m_shape->is_dictionary()) 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 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); m_storage.append(value);
return; return;
} }
if (attributes != metadata->attributes) { if (attributes != metadata->attributes) {
if (m_shape->is_dictionary()) 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 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; m_storage[metadata->offset] = value;
@ -1276,18 +1275,18 @@ void Object::storage_delete(PropertyKey const& property_key)
intrinsics->value.remove(property_key.as_string()); 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()); VERIFY(metadata.has_value());
if (m_shape->is_cacheable_dictionary()) { if (m_shape->is_cacheable_dictionary()) {
m_shape = m_shape->create_uncacheable_dictionary_transition(); m_shape = m_shape->create_uncacheable_dictionary_transition();
} }
if (m_shape->is_uncacheable_dictionary()) { 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); m_storage.remove(metadata->offset);
return; 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); m_storage.remove(metadata->offset);
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -7,16 +8,13 @@
#pragma once #pragma once
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <LibGC/Root.h>
#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/StringOrSymbol.h> #include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Symbol.h>
namespace JS { namespace JS {
class PropertyKey { class PropertyKey {
AK_MAKE_DEFAULT_COPYABLE(PropertyKey);
AK_MAKE_DEFAULT_MOVABLE(PropertyKey);
public: public:
enum class StringMayBeNumber { enum class StringMayBeNumber {
Yes, Yes,
@ -33,26 +31,61 @@ public:
return TRY(value.to_string(vm)); 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() = 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> template<Integral T>
PropertyKey(T index) PropertyKey(T index)
: m_data(index)
{ {
// FIXME: Replace this with requires(IsUnsigned<T>)? // FIXME: Replace this with requires(IsUnsigned<T>)?
// Needs changes in various places using `int` (but not actually being in the negative range) // Needs changes in various places using `int` (but not actually being in the negative range)
VERIFY(index >= 0); VERIFY(index >= 0);
if constexpr (NumericLimits<T>::max() >= NumericLimits<u32>::max()) { if constexpr (NumericLimits<T>::max() >= NumericLimits<u32>::max()) {
if (index >= NumericLimits<u32>::max()) { if (index >= NumericLimits<u32>::max()) {
m_data = FlyString { String::number(index) }; new (&m_string) FlyString { String::number(index) };
return; return;
} }
} }
m_number = static_cast<u64>(index) << 2 | NUMBER_FLAG;
} }
PropertyKey(FlyString string, StringMayBeNumber string_may_be_number = StringMayBeNumber::Yes) 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) PropertyKey(String const& string)
@ -61,66 +94,102 @@ public:
} }
PropertyKey(GC::Ref<Symbol> symbol) PropertyKey(GC::Ref<Symbol> symbol)
: m_data { symbol }
{ {
m_bits = reinterpret_cast<uintptr_t>(symbol.ptr()) | SYMBOL_FLAG;
} }
PropertyKey(StringOrSymbol const& string_or_symbol) PropertyKey& operator=(PropertyKey const& other)
: m_data { {
string_or_symbol.is_string() if (this != &other) {
? Variant<FlyString, GC::Root<Symbol>, u32> { string_or_symbol.as_string() } if (is_string())
: Variant<FlyString, GC::Root<Symbol>, u32> { const_cast<Symbol*>(string_or_symbol.as_symbol()) } m_string.~FlyString();
new (this) PropertyKey(other);
} }
{ return *this;
} }
bool is_number() const { return m_data.has<u32>(); } PropertyKey& operator=(PropertyKey&& other) noexcept
bool is_string() const { return m_data.has<FlyString>(); } {
bool is_symbol() const { return m_data.has<GC::Root<Symbol>>(); } 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>(); } ~PropertyKey()
FlyString const& as_string() const { return m_data.get<FlyString>(); } {
Symbol const* as_symbol() const { return m_data.get<GC::Root<Symbol>>(); } 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 String to_string() const
{ {
VERIFY(!is_symbol());
if (is_string()) if (is_string())
return as_string().to_string(); return as_string().to_string();
if (is_symbol())
return MUST(as_symbol()->descriptive_string());
return String::number(as_number()); return String::number(as_number());
} }
StringOrSymbol to_string_or_symbol() const void visit_edges(Cell::Visitor& visitor) const
{ {
VERIFY(!is_number()); if (is_symbol())
if (is_string()) visitor.visit(const_cast<Symbol*>(as_symbol()));
return StringOrSymbol(as_string());
return StringOrSymbol(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: private:
friend Traits<JS::PropertyKey>; friend Traits<PropertyKey>;
static Variant<FlyString, u32> try_coerce_into_number(FlyString string, StringMayBeNumber string_may_be_number) union {
{ FlyString m_string;
if (string_may_be_number != StringMayBeNumber::Yes) u64 m_number;
return string; Symbol const* m_symbol;
auto view = string.bytes_as_string_view(); uintptr_t m_bits;
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;
}; };
static_assert(sizeof(PropertyKey) == sizeof(uintptr_t));
} }
namespace AK { namespace AK {
@ -129,15 +198,24 @@ template<>
struct Traits<JS::PropertyKey> : public DefaultTraits<JS::PropertyKey> { struct Traits<JS::PropertyKey> : public DefaultTraits<JS::PropertyKey> {
static unsigned hash(JS::PropertyKey const& name) static unsigned hash(JS::PropertyKey const& name)
{ {
return name.m_data.visit( if (name.is_string())
[](FlyString const& string) { return string.hash(); }, return name.as_string().hash();
[](GC::Root<JS::Symbol> const& symbol) { return ptr_hash(symbol.ptr()); }, if (name.is_symbol())
[](u32 const& number) { return int_hash(number); }); 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) 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()) if (property_key.is_number())
return builder.put_u64(property_key.as_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()) if (is_private_reference())
return vm.throw_completion<ReferenceError>(ErrorType::ReferenceUnresolvable); return vm.throw_completion<ReferenceError>(ErrorType::ReferenceUnresolvable);
else 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 // 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(); 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) if (m_is_prototype_shape)
return nullptr; return nullptr;
@ -100,7 +100,7 @@ GC::Ptr<Shape> Shape::get_or_prune_cached_prototype_transition(Object* prototype
return it->value.ptr(); 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 }; TransitionKey key { property_key, attributes };
if (auto existing_shape = get_or_prune_cached_forward_transition(key)) 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; 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 }; TransitionKey key { property_key, attributes };
if (auto existing_shape = get_or_prune_cached_forward_transition(key)) 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_realm(previous_shape.m_realm)
, m_previous(&previous_shape) , m_previous(&previous_shape)
, m_property_key(property_key) , 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_realm(previous_shape.m_realm)
, m_previous(&previous_shape) , m_previous(&previous_shape)
, m_property_key(property_key) , m_property_key(property_key)
@ -188,7 +188,8 @@ void Shape::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_realm); visitor.visit(m_realm);
visitor.visit(m_prototype); visitor.visit(m_prototype);
visitor.visit(m_previous); 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 // 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. // 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); 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) if (m_property_count == 0)
return {}; return {};
@ -220,7 +221,7 @@ Optional<PropertyMetadata> Shape::lookup(StringOrSymbol const& property_key) con
return property; return property;
} }
FLATTEN OrderedHashMap<StringOrSymbol, PropertyMetadata> const& Shape::property_table() const FLATTEN OrderedHashMap<PropertyKey, PropertyMetadata> const& Shape::property_table() const
{ {
ensure_property_table(); ensure_property_table();
return *m_property_table; return *m_property_table;
@ -230,7 +231,7 @@ void Shape::ensure_property_table() const
{ {
if (m_property_table) if (m_property_table)
return; return;
m_property_table = make<OrderedHashMap<StringOrSymbol, PropertyMetadata>>(); m_property_table = make<OrderedHashMap<PropertyKey, PropertyMetadata>>();
u32 next_offset = 0; u32 next_offset = 0;
@ -246,18 +247,18 @@ void Shape::ensure_property_table() const
} }
for (auto const& shape : transition_chain.in_reverse()) { 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. // Ignore prototype transitions as they don't affect the key map.
continue; continue;
} }
if (shape.m_transition_type == TransitionType::Put) { 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) { } 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()); VERIFY(it != m_property_table->end());
it->value.attributes = shape.m_attributes; it->value.attributes = shape.m_attributes;
} else if (shape.m_transition_type == TransitionType::Delete) { } 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()); VERIFY(remove_it != m_property_table->end());
auto removed_offset = remove_it->value.offset; auto removed_offset = remove_it->value.offset;
m_property_table->remove(remove_it); 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)) if (auto existing_shape = get_or_prune_cached_delete_transition(property_key))
return *existing_shape; return *existing_shape;
auto new_shape = heap().allocate<Shape>(*this, property_key, TransitionType::Delete); auto new_shape = heap().allocate<Shape>(*this, property_key, TransitionType::Delete);
invalidate_prototype_if_needed_for_new_prototype(new_shape); invalidate_prototype_if_needed_for_new_prototype(new_shape);
if (!m_delete_transitions) 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()); m_delete_transitions->set(property_key, new_shape.ptr());
return new_shape; 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(); ensure_property_table();
if (m_property_table->set(property_key, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) { 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) void Shape::set_property_attributes_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)
{ {
VERIFY(is_dictionary()); VERIFY(is_dictionary());
VERIFY(m_property_table); 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); 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(is_uncacheable_dictionary());
VERIFY(m_property_table); VERIFY(m_property_table);

View file

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

View file

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