LibJS+LibWeb: Return Vector<PropertyKey> from internal_own_property_keys

By doing that we avoid lots of `PropertyKey` -> `Value` -> `PropertyKey`
transforms, which are quite expensive because of underlying
`FlyString` -> `PrimitiveString` -> `FlyString` conversions.

10% improvement on MicroBench/object-keys.js
This commit is contained in:
Aliaksandr Kalenik 2025-05-14 15:39:48 +03:00 committed by Alexander Kalenik
parent 47569c1714
commit 5ee810f772
Notes: github-actions[bot] 2025-05-15 18:13:23 +00:00
24 changed files with 134 additions and 155 deletions

View file

@ -279,8 +279,6 @@ ThrowCompletionOr<bool> Object::has_own_property(PropertyKey const& property_key
// 7.3.16 SetIntegrityLevel ( O, level ), https://tc39.es/ecma262/#sec-setintegritylevel
ThrowCompletionOr<bool> Object::set_integrity_level(IntegrityLevel level)
{
auto& vm = this->vm();
// 1. Let status be ? O.[[PreventExtensions]]().
auto status = TRY(internal_prevent_extensions());
@ -294,9 +292,7 @@ ThrowCompletionOr<bool> Object::set_integrity_level(IntegrityLevel level)
// 4. If level is sealed, then
if (level == IntegrityLevel::Sealed) {
// a. For each element k of keys, do
for (auto& key : keys) {
auto property_key = MUST(PropertyKey::from_value(vm, key));
for (auto& property_key : keys) {
// i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
TRY(define_property_or_throw(property_key, { .configurable = false }));
}
@ -306,9 +302,7 @@ ThrowCompletionOr<bool> Object::set_integrity_level(IntegrityLevel level)
// a. Assert: level is frozen.
// b. For each element k of keys, do
for (auto& key : keys) {
auto property_key = MUST(PropertyKey::from_value(vm, key));
for (auto& property_key : keys) {
// i. Let currentDesc be ? O.[[GetOwnProperty]](k).
auto current_descriptor = TRY(internal_get_own_property(property_key));
@ -341,8 +335,6 @@ ThrowCompletionOr<bool> Object::set_integrity_level(IntegrityLevel level)
// 7.3.17 TestIntegrityLevel ( O, level ), https://tc39.es/ecma262/#sec-testintegritylevel
ThrowCompletionOr<bool> Object::test_integrity_level(IntegrityLevel level) const
{
auto& vm = this->vm();
// 1. Let extensible be ? IsExtensible(O).
auto extensible = TRY(is_extensible());
@ -355,9 +347,7 @@ ThrowCompletionOr<bool> Object::test_integrity_level(IntegrityLevel level) const
auto keys = TRY(internal_own_property_keys());
// 5. For each element k of keys, do
for (auto& key : keys) {
auto property_key = MUST(PropertyKey::from_value(vm, key));
for (auto& property_key : keys) {
// a. Let currentDesc be ? O.[[GetOwnProperty]](k).
auto current_descriptor = TRY(internal_get_own_property(property_key));
@ -396,11 +386,11 @@ ThrowCompletionOr<GC::RootVector<Value>> Object::enumerable_own_property_names(P
auto properties = GC::RootVector<Value> { heap() };
// 3. For each element key of ownKeys, do
for (auto& key : own_keys) {
for (auto& property_key : own_keys) {
// a. If Type(key) is String, then
if (!key.is_string())
if (!property_key.is_string() && !property_key.is_number()) {
continue;
auto property_key = MUST(PropertyKey::from_value(vm, key));
}
// i. Let desc be ? O.[[GetOwnProperty]](key).
auto descriptor = TRY(internal_get_own_property(property_key));
@ -409,7 +399,7 @@ ThrowCompletionOr<GC::RootVector<Value>> Object::enumerable_own_property_names(P
if (descriptor.has_value() && *descriptor->enumerable) {
// 1. If kind is key, append key to properties.
if (kind == PropertyKind::Key) {
properties.append(key);
properties.append(property_key.to_value(vm));
continue;
}
// 2. Else,
@ -428,7 +418,7 @@ ThrowCompletionOr<GC::RootVector<Value>> Object::enumerable_own_property_names(P
VERIFY(kind == PropertyKind::KeyAndValue);
// ii. Let entry be CreateArrayFromList(« key, value »).
auto entry = Array::create_from(realm, { key, value });
auto entry = Array::create_from(realm, { property_key.to_value(vm), value });
// iii. Append entry to properties.
properties.append(entry);
@ -454,25 +444,23 @@ ThrowCompletionOr<void> Object::copy_data_properties(VM& vm, Value source, HashT
auto keys = TRY(from->internal_own_property_keys());
// 4. For each element nextKey of keys, do
for (auto& next_key_value : keys) {
auto next_key = MUST(PropertyKey::from_value(vm, next_key_value));
for (auto& next_property_key : keys) {
// a. Let excluded be false.
// b. For each element e of excludedKeys, do
// i. If SameValue(e, nextKey) is true, then
// 1. Set excluded to true.
if (excluded_keys.contains(next_key))
if (excluded_keys.contains(next_property_key))
continue;
// c. If excluded is false, then
// i. Let desc be ? from.[[GetOwnProperty]](nextKey).
auto desc = TRY(from->internal_get_own_property(next_key));
auto desc = TRY(from->internal_get_own_property(next_property_key));
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
if (desc.has_value() && desc->attributes().is_enumerable()) {
// 1. Let propValue be ? Get(from, nextKey).
auto prop_value = TRY(from->get(next_key));
auto prop_value = TRY(from->get(next_property_key));
// 2. If excludedValues is present, then
// a. For each element e of excludedValues, do
@ -481,7 +469,7 @@ ThrowCompletionOr<void> Object::copy_data_properties(VM& vm, Value source, HashT
// 3. If excluded is false, Perform ! CreateDataPropertyOrThrow(target, nextKey, propValue).
// NOTE: HashTable traits for JS::Value uses SameValue.
if (!excluded_values.contains(prop_value))
MUST(create_data_property_or_throw(next_key, prop_value));
MUST(create_data_property_or_throw(next_property_key, prop_value));
}
}
@ -1120,33 +1108,30 @@ ThrowCompletionOr<bool> Object::internal_delete(PropertyKey const& property_key)
return false;
}
// 10.1.11 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
ThrowCompletionOr<GC::RootVector<Value>> Object::internal_own_property_keys() const
ThrowCompletionOr<Vector<PropertyKey>> Object::internal_own_property_keys() const
{
auto& vm = this->vm();
// 1. Let keys be a new empty List.
GC::RootVector<Value> keys { heap() };
Vector<PropertyKey> keys;
// 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
for (auto& entry : m_indexed_properties) {
for (auto const& entry : m_indexed_properties) {
// a. Add P as the last element of keys.
keys.append(PrimitiveString::create(vm, String::number(entry.index())));
keys.append(String::number(entry.index()));
}
// 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
for (auto& it : shape().property_table()) {
for (auto const& it : shape().property_table()) {
if (it.key.is_string()) {
// a. Add P as the last element of keys.
keys.append(it.key.to_value(vm));
keys.append(it.key);
}
}
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
for (auto& it : shape().property_table()) {
for (auto const& it : shape().property_table()) {
if (it.key.is_symbol()) {
// a. Add P as the last element of keys.
keys.append(it.key.to_value(vm));
keys.append(it.key);
}
}
@ -1376,22 +1361,20 @@ ThrowCompletionOr<Object*> Object::define_properties(Value properties)
Vector<NameAndDescriptor> descriptors;
// 4. For each element nextKey of keys, do
for (auto& next_key : keys) {
auto property_key = MUST(PropertyKey::from_value(vm, next_key));
for (auto& next_property_key : keys) {
// a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
auto property_descriptor = TRY(props->internal_get_own_property(property_key));
auto property_descriptor = TRY(props->internal_get_own_property(next_property_key));
// b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
if (property_descriptor.has_value() && *property_descriptor->enumerable) {
// i. Let descObj be ? Get(props, nextKey).
auto descriptor_object = TRY(props->get(property_key));
auto descriptor_object = TRY(props->get(next_property_key));
// ii. Let desc be ? ToPropertyDescriptor(descObj).
auto descriptor = TRY(to_property_descriptor(vm, descriptor_object));
// iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
descriptors.append({ property_key, descriptor });
descriptors.append({ next_property_key, descriptor });
}
}
@ -1420,24 +1403,24 @@ Optional<Completion> Object::enumerate_object_properties(Function<Optional<Compl
// * Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively.
// * A property of a prototype is not processed if it has the same name as a property that has already been processed.
auto& vm = this->vm();
HashTable<FlyString> visited;
auto const* target = this;
while (target) {
auto own_keys = TRY(target->internal_own_property_keys());
for (auto& key : own_keys) {
if (!key.is_string())
for (auto& property_key : own_keys) {
if (!property_key.is_string())
continue;
FlyString property_key = key.as_string().utf8_string();
if (visited.contains(property_key))
if (visited.contains(property_key.as_string()))
continue;
auto descriptor = TRY(target->internal_get_own_property(property_key));
if (!descriptor.has_value())
continue;
visited.set(property_key);
visited.set(property_key.as_string());
if (!*descriptor->enumerable)
continue;
if (auto completion = callback(key); completion.has_value())
if (auto completion = callback(property_key.to_value(vm)); completion.has_value())
return completion.release_value();
}