LibJS: Skip allocation of temp object for primitive types in Value::get

Previously, `String.prototype.split()` caused the construction of a
temporary StringObject when a string primitive was passed as an
argument, solely to perform a Symbol.split lookup. This change allows
skipping that allocation by looking directly into the prototype of
primitive values.

As a result, we can avoid ~200000 StringObject allocations in a single
test from the Speedometer 2 benchmark.

Co-Authored-By: Andreas Kling <andreas@ladybird.org>
This commit is contained in:
Aliaksandr Kalenik 2025-03-24 16:25:33 +00:00 committed by Alexander Kalenik
commit a8285f255b
Notes: github-actions[bot] 2025-03-24 19:39:12 +00:00
2 changed files with 17 additions and 1 deletions

View file

@ -1220,7 +1220,21 @@ double to_integer_or_infinity(double number)
ThrowCompletionOr<Value> Value::get(VM& vm, PropertyKey const& property_key) const
{
// 1. Let O be ? ToObject(V).
auto object = TRY(to_object(vm));
auto object = TRY([&]() -> ThrowCompletionOr<GC::Ref<Object>> {
// OPTIMIZATION: For primitive values, we can skip allocation of temporary object and look
// directly into prototype where requested property is located.
if (is_string() && property_key != vm.names.length)
return vm.current_realm()->intrinsics().string_prototype();
if (is_boolean())
return vm.current_realm()->intrinsics().boolean_prototype();
if (is_number())
return vm.current_realm()->intrinsics().number_prototype();
if (is_bigint())
return vm.current_realm()->intrinsics().bigint_prototype();
if (is_symbol())
return vm.current_realm()->intrinsics().symbol_prototype();
return to_object(vm);
}());
// 2. Return ? O.[[Get]](P, V).
return TRY(object->internal_get(property_key, *this));