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:
parent
5495531118
commit
b559965448
Notes:
github-actions[bot]
2025-05-17 14:09:31 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: b559965448
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4761
12 changed files with 195 additions and 275 deletions
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -8,6 +8,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
|
||||
namespace JS {
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue