Compare commits

...

3 commits

Author SHA1 Message Date
Andreas Kling
d065171791 LibJS: Use property lookup caches for some of our hot C++ gets
We can use caching in a million more places. This is just me running JS
benchmarks and looking at which get() call sites were hot and putting
caches there.

Lots of nice speedups all over the place, some examples:

1.19x speedup on Octane/raytrace.js
1.13x speedup on Octane/earley-boyer.js
1.12x speedup on Kraken/ai-astar.js
1.10x speedup on Octane/box2d.js
1.08x speedup on Octane/gbemu.js
1.05x speedup on Octane/regexp.js
2025-10-14 15:47:38 +02:00
Andreas Kling
0fb9ba1e3a LibJS: Add Value::get() and Object::get() overloads with lookup cache
To speed up property access, callers of get() can now provide a lookup
cache like so:

    static Bytecode::PropertyLookupCache cache;
    auto value = TRY(object.get(property, cache));

Note that the cache has to be `static` or it won't make sense!

This basically brings the inline caches from our bytecode VM straight
into C++ land, allowing us to gain serious performance improvements.
The implementation shares code with the GetById bytecode instruction.
2025-10-14 15:47:38 +02:00
Andreas Kling
26c1dea22a LibJS: Move GetById logic to a separate header to prepare for reuse
We also make the code a bit more generic by making callers provide
(templated) callbacks that produce the property name and base expression
string if any.
2025-10-14 15:47:38 +02:00
15 changed files with 300 additions and 161 deletions

View file

@ -16,6 +16,7 @@
#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Label.h> #include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Op.h> #include <LibJS/Bytecode/Op.h>
#include <LibJS/Bytecode/PropertyAccess.h>
#include <LibJS/Export.h> #include <LibJS/Export.h>
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h> #include <LibJS/Runtime/Accessor.h>
@ -922,27 +923,6 @@ ALWAYS_INLINE Completion throw_null_or_undefined_property_access(VM& vm, Value b
return vm.throw_completion<TypeError>(ErrorType::ToObjectNullOrUndefined); return vm.throw_completion<TypeError>(ErrorType::ToObjectNullOrUndefined);
} }
ALWAYS_INLINE GC::Ptr<Object> base_object_for_get_impl(VM& vm, Value base_value)
{
if (base_value.is_object()) [[likely]]
return base_value.as_object();
// OPTIMIZATION: For various primitives we can avoid actually creating a new object for them.
auto& realm = *vm.current_realm();
if (base_value.is_string())
return realm.intrinsics().string_prototype();
if (base_value.is_number())
return realm.intrinsics().number_prototype();
if (base_value.is_boolean())
return realm.intrinsics().boolean_prototype();
if (base_value.is_bigint())
return realm.intrinsics().bigint_prototype();
if (base_value.is_symbol())
return realm.intrinsics().symbol_prototype();
return nullptr;
}
ALWAYS_INLINE ThrowCompletionOr<GC::Ref<Object>> base_object_for_get(VM& vm, Value base_value, Optional<IdentifierTableIndex> base_identifier, IdentifierTableIndex property_identifier, Executable const& executable) ALWAYS_INLINE ThrowCompletionOr<GC::Ref<Object>> base_object_for_get(VM& vm, Value base_value, Optional<IdentifierTableIndex> base_identifier, IdentifierTableIndex property_identifier, Executable const& executable)
{ {
if (auto base_object = base_object_for_get_impl(vm, base_value)) if (auto base_object = base_object_for_get_impl(vm, base_value))
@ -961,92 +941,6 @@ ALWAYS_INLINE ThrowCompletionOr<GC::Ref<Object>> base_object_for_get(VM& vm, Val
return throw_null_or_undefined_property_get(vm, base_value, base_identifier, property, executable); return throw_null_or_undefined_property_get(vm, base_value, base_identifier, property, executable);
} }
enum class GetByIdMode {
Normal,
Length,
};
template<GetByIdMode mode = GetByIdMode::Normal>
inline ThrowCompletionOr<Value> get_by_id(VM& vm, Optional<IdentifierTableIndex> base_identifier, IdentifierTableIndex property, Value base_value, Value this_value, PropertyLookupCache& cache, Executable const& executable)
{
if constexpr (mode == GetByIdMode::Length) {
if (base_value.is_string()) {
return Value(base_value.as_string().length_in_utf16_code_units());
}
}
auto base_obj = TRY(base_object_for_get(vm, base_value, base_identifier, property, executable));
if constexpr (mode == GetByIdMode::Length) {
// OPTIMIZATION: Fast path for the magical "length" property on Array objects.
if (base_obj->has_magical_length_property()) {
return Value { base_obj->indexed_properties().array_like_size() };
}
}
auto& shape = base_obj->shape();
GC::Ptr<PrototypeChainValidity> prototype_chain_validity;
if (shape.prototype())
prototype_chain_validity = shape.prototype()->shape().prototype_chain_validity();
for (auto& cache_entry : cache.entries) {
if (cache_entry.prototype) {
// OPTIMIZATION: If the prototype chain hasn't been mutated in a way that would invalidate the cache, we can use it.
bool can_use_cache = [&]() -> bool {
if (&shape != cache_entry.shape)
return false;
if (!cache_entry.prototype_chain_validity)
return false;
if (!cache_entry.prototype_chain_validity->is_valid())
return false;
return true;
}();
if (can_use_cache) {
auto value = cache_entry.prototype->get_direct(cache_entry.property_offset.value());
if (value.is_accessor())
return TRY(call(vm, value.as_accessor().getter(), this_value));
return value;
}
} else if (&shape == cache_entry.shape) {
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
auto value = base_obj->get_direct(cache_entry.property_offset.value());
if (value.is_accessor())
return TRY(call(vm, value.as_accessor().getter(), this_value));
return value;
}
}
CacheableGetPropertyMetadata cacheable_metadata;
auto value = TRY(base_obj->internal_get(executable.get_identifier(property), this_value, &cacheable_metadata));
// If internal_get() caused object's shape change, we can no longer be sure
// that collected metadata is valid, e.g. if getter in prototype chain added
// property with the same name into the object itself.
if (&shape == &base_obj->shape()) {
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = cache.entries.size() - 1; i >= 1; --i) {
cache.entries[i] = cache.entries[i - 1];
}
cache.entries[0] = {};
return cache.entries[0];
};
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
auto& entry = get_cache_slot();
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetPropertyInPrototypeChain) {
auto& entry = get_cache_slot();
entry.shape = &base_obj->shape();
entry.property_offset = cacheable_metadata.property_offset.value();
entry.prototype = *cacheable_metadata.prototype;
entry.prototype_chain_validity = *prototype_chain_validity;
}
}
return value;
}
inline ThrowCompletionOr<Value> get_by_value(VM& vm, Optional<IdentifierTableIndex> base_identifier, Value base_value, Value property_key_value, Executable const& executable) inline ThrowCompletionOr<Value> get_by_value(VM& vm, Optional<IdentifierTableIndex> base_identifier, Value base_value, Value property_key_value, Executable const& executable)
{ {
// OPTIMIZATION: Fast path for simple Int32 indexes in array-like objects. // OPTIMIZATION: Fast path for simple Int32 indexes in array-like objects.
@ -2637,7 +2531,7 @@ ThrowCompletionOr<void> GetById::execute_impl(Bytecode::Interpreter& interpreter
auto base_value = interpreter.get(base()); auto base_value = interpreter.get(base());
auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index]; auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index];
interpreter.set(dst(), TRY(get_by_id(interpreter.vm(), m_base_identifier, m_property, base_value, base_value, cache, interpreter.current_executable()))); interpreter.set(dst(), TRY(get_by_id<GetByIdMode::Normal>(interpreter.vm(), [&] { return interpreter.get_identifier(m_base_identifier); }, [&] { return interpreter.get_identifier(m_property); }, base_value, base_value, cache)));
return {}; return {};
} }
@ -2646,7 +2540,7 @@ ThrowCompletionOr<void> GetByIdWithThis::execute_impl(Bytecode::Interpreter& int
auto base_value = interpreter.get(m_base); auto base_value = interpreter.get(m_base);
auto this_value = interpreter.get(m_this_value); auto this_value = interpreter.get(m_this_value);
auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index]; auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index];
interpreter.set(dst(), TRY(get_by_id(interpreter.vm(), {}, m_property, base_value, this_value, cache, interpreter.current_executable()))); interpreter.set(dst(), TRY(get_by_id<GetByIdMode::Normal>(interpreter.vm(), [] { return Optional<Utf16FlyString const&> {}; }, [&] { return interpreter.get_identifier(m_property); }, base_value, this_value, cache)));
return {}; return {};
} }
@ -2656,7 +2550,7 @@ ThrowCompletionOr<void> GetLength::execute_impl(Bytecode::Interpreter& interpret
auto& executable = interpreter.current_executable(); auto& executable = interpreter.current_executable();
auto& cache = executable.property_lookup_caches[m_cache_index]; auto& cache = executable.property_lookup_caches[m_cache_index];
interpreter.set(dst(), TRY(get_by_id<GetByIdMode::Length>(interpreter.vm(), m_base_identifier, *executable.length_identifier, base_value, base_value, cache, executable))); interpreter.set(dst(), TRY(get_by_id<GetByIdMode::Length>(interpreter.vm(), [&] { return interpreter.get_identifier(m_base_identifier); }, [&] { return executable.get_identifier(*executable.length_identifier); }, base_value, base_value, cache)));
return {}; return {};
} }
@ -2666,7 +2560,7 @@ ThrowCompletionOr<void> GetLengthWithThis::execute_impl(Bytecode::Interpreter& i
auto this_value = interpreter.get(m_this_value); auto this_value = interpreter.get(m_this_value);
auto& executable = interpreter.current_executable(); auto& executable = interpreter.current_executable();
auto& cache = executable.property_lookup_caches[m_cache_index]; auto& cache = executable.property_lookup_caches[m_cache_index];
interpreter.set(dst(), TRY(get_by_id<GetByIdMode::Length>(interpreter.vm(), {}, *executable.length_identifier, base_value, this_value, cache, executable))); interpreter.set(dst(), TRY(get_by_id<GetByIdMode::Length>(interpreter.vm(), [] { return Optional<Utf16FlyString const&> {}; }, [&] { return executable.get_identifier(*executable.length_identifier); }, base_value, this_value, cache)));
return {}; return {};
} }

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/IdentifierTable.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibWasm/Opcode.h>
namespace JS::Bytecode {
enum class GetByIdMode {
Normal,
Length,
};
ALWAYS_INLINE GC::Ptr<Object> base_object_for_get_impl(VM& vm, Value base_value)
{
if (base_value.is_object()) [[likely]]
return base_value.as_object();
// OPTIMIZATION: For various primitives we can avoid actually creating a new object for them.
auto& realm = *vm.current_realm();
if (base_value.is_string())
return realm.intrinsics().string_prototype();
if (base_value.is_number())
return realm.intrinsics().number_prototype();
if (base_value.is_boolean())
return realm.intrinsics().boolean_prototype();
if (base_value.is_bigint())
return realm.intrinsics().bigint_prototype();
if (base_value.is_symbol())
return realm.intrinsics().symbol_prototype();
return nullptr;
}
template<typename GetBaseIdentifier, typename GetPropertyName>
ALWAYS_INLINE Completion throw_null_or_undefined_property_get(VM& vm, Value base_value, GetBaseIdentifier get_base_identifier, GetPropertyName get_property_name)
{
VERIFY(base_value.is_nullish());
auto base_identifier = get_base_identifier();
if (base_identifier.has_value())
return vm.throw_completion<TypeError>(ErrorType::ToObjectNullOrUndefinedWithPropertyAndName, get_property_name(), base_value, base_identifier);
return vm.throw_completion<TypeError>(ErrorType::ToObjectNullOrUndefinedWithProperty, get_property_name(), base_value);
}
template<typename GetBaseIdentifier, typename GetPropertyName>
ALWAYS_INLINE ThrowCompletionOr<GC::Ref<Object>> base_object_for_get(VM& vm, Value base_value, GetBaseIdentifier get_base_identifier, GetPropertyName get_property_name)
{
if (auto base_object = base_object_for_get_impl(vm, base_value))
return GC::Ref { *base_object };
// NOTE: At this point this is guaranteed to throw (null or undefined).
return throw_null_or_undefined_property_get(vm, base_value, get_base_identifier, get_property_name);
}
template<GetByIdMode mode, typename GetBaseIdentifier, typename GetPropertyName>
ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_base_identifier, GetPropertyName get_property_name, Value base_value, Value this_value, PropertyLookupCache& cache)
{
if constexpr (mode == GetByIdMode::Length) {
if (base_value.is_string()) {
return Value(base_value.as_string().length_in_utf16_code_units());
}
}
auto base_obj = TRY(base_object_for_get(vm, base_value, get_base_identifier, get_property_name));
if constexpr (mode == GetByIdMode::Length) {
// OPTIMIZATION: Fast path for the magical "length" property on Array objects.
if (base_obj->has_magical_length_property()) {
return Value { base_obj->indexed_properties().array_like_size() };
}
}
auto& shape = base_obj->shape();
GC::Ptr<PrototypeChainValidity> prototype_chain_validity;
if (shape.prototype())
prototype_chain_validity = shape.prototype()->shape().prototype_chain_validity();
for (auto& cache_entry : cache.entries) {
if (cache_entry.prototype) {
// OPTIMIZATION: If the prototype chain hasn't been mutated in a way that would invalidate the cache, we can use it.
bool can_use_cache = [&]() -> bool {
if (&shape != cache_entry.shape) [[unlikely]]
return false;
if (!cache_entry.prototype_chain_validity) [[unlikely]]
return false;
if (!cache_entry.prototype_chain_validity->is_valid()) [[unlikely]]
return false;
return true;
}();
if (can_use_cache) [[likely]] {
auto value = cache_entry.prototype->get_direct(cache_entry.property_offset.value());
if (value.is_accessor())
return TRY(call(vm, value.as_accessor().getter(), this_value));
return value;
}
} else if (&shape == cache_entry.shape) {
// OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset.
auto value = base_obj->get_direct(cache_entry.property_offset.value());
if (value.is_accessor()) {
return TRY(call(vm, value.as_accessor().getter(), this_value));
}
return value;
}
}
CacheableGetPropertyMetadata cacheable_metadata;
auto value = TRY(base_obj->internal_get(get_property_name(), this_value, &cacheable_metadata));
// If internal_get() caused object's shape change, we can no longer be sure
// that collected metadata is valid, e.g. if getter in prototype chain added
// property with the same name into the object itself.
if (&shape == &base_obj->shape()) {
auto get_cache_slot = [&] -> PropertyLookupCache::Entry& {
for (size_t i = cache.entries.size() - 1; i >= 1; --i) {
cache.entries[i] = cache.entries[i - 1];
}
cache.entries[0] = {};
return cache.entries[0];
};
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
auto& entry = get_cache_slot();
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
} else if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetPropertyInPrototypeChain) {
auto& entry = get_cache_slot();
entry.shape = &base_obj->shape();
entry.property_offset = cacheable_metadata.property_offset.value();
entry.prototype = *cacheable_metadata.prototype;
entry.prototype_chain_validity = *prototype_chain_validity;
}
}
return value;
}
}

View file

@ -327,6 +327,7 @@ class Generator;
class Instruction; class Instruction;
class Interpreter; class Interpreter;
class Operand; class Operand;
struct PropertyLookupCache;
class RegexTable; class RegexTable;
class Register; class Register;

View file

@ -127,7 +127,8 @@ ThrowCompletionOr<size_t> length_of_array_like(VM& vm, Object const& object)
return object.indexed_properties().array_like_size(); return object.indexed_properties().array_like_size();
// 1. Return ℝ(? ToLength(? Get(obj, "length"))). // 1. Return ℝ(? ToLength(? Get(obj, "length"))).
return TRY(object.get(vm.names.length)).to_length(vm); static Bytecode::PropertyLookupCache cache;
return TRY(object.get(vm.names.length, cache)).to_length(vm);
} }
// 7.3.20 CreateListFromArrayLike ( obj [ , elementTypes ] ), https://tc39.es/ecma262/#sec-createlistfromarraylike // 7.3.20 CreateListFromArrayLike ( obj [ , elementTypes ] ), https://tc39.es/ecma262/#sec-createlistfromarraylike
@ -173,7 +174,8 @@ ThrowCompletionOr<GC::RootVector<Value>> create_list_from_array_like(VM& vm, Val
ThrowCompletionOr<FunctionObject*> species_constructor(VM& vm, Object const& object, FunctionObject& default_constructor) ThrowCompletionOr<FunctionObject*> species_constructor(VM& vm, Object const& object, FunctionObject& default_constructor)
{ {
// 1. Let C be ? Get(O, "constructor"). // 1. Let C be ? Get(O, "constructor").
auto constructor = TRY(object.get(vm.names.constructor)); static Bytecode::PropertyLookupCache cache;
auto constructor = TRY(object.get(vm.names.constructor, cache));
// 2. If C is undefined, return defaultConstructor. // 2. If C is undefined, return defaultConstructor.
if (constructor.is_undefined()) if (constructor.is_undefined())
@ -184,7 +186,8 @@ ThrowCompletionOr<FunctionObject*> species_constructor(VM& vm, Object const& obj
return vm.throw_completion<TypeError>(ErrorType::NotAConstructor, constructor.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NotAConstructor, constructor.to_string_without_side_effects());
// 4. Let S be ? Get(C, @@species). // 4. Let S be ? Get(C, @@species).
auto species = TRY(constructor.as_object().get(vm.well_known_symbol_species())); static Bytecode::PropertyLookupCache cache2;
auto species = TRY(constructor.as_object().get(vm.well_known_symbol_species(), cache2));
// 5. If S is either undefined or null, return defaultConstructor. // 5. If S is either undefined or null, return defaultConstructor.
if (species.is_nullish()) if (species.is_nullish())
@ -407,7 +410,8 @@ ThrowCompletionOr<Object*> get_prototype_from_constructor(VM& vm, FunctionObject
// 1. Assert: intrinsicDefaultProto is this specification's name of an intrinsic object. The corresponding object must be an intrinsic that is intended to be used as the [[Prototype]] value of an object. // 1. Assert: intrinsicDefaultProto is this specification's name of an intrinsic object. The corresponding object must be an intrinsic that is intended to be used as the [[Prototype]] value of an object.
// 2. Let proto be ? Get(constructor, "prototype"). // 2. Let proto be ? Get(constructor, "prototype").
auto prototype = TRY(constructor.get(vm.names.prototype)); static Bytecode::PropertyLookupCache cache;
auto prototype = TRY(constructor.get(vm.names.prototype, cache));
// 3. If Type(proto) is not Object, then // 3. If Type(proto) is not Object, then
if (!prototype.is_object()) { if (!prototype.is_object()) {

View file

@ -119,7 +119,8 @@ static ThrowCompletionOr<Object*> array_species_create(VM& vm, Object& original_
if (!is_array) if (!is_array)
return TRY(Array::create(realm, length)).ptr(); return TRY(Array::create(realm, length)).ptr();
auto constructor = TRY(original_array.get(vm.names.constructor)); static Bytecode::PropertyLookupCache cache;
auto constructor = TRY(original_array.get(vm.names.constructor, cache));
if (constructor.is_constructor()) { if (constructor.is_constructor()) {
auto& constructor_function = constructor.as_function(); auto& constructor_function = constructor.as_function();
auto* this_realm = vm.current_realm(); auto* this_realm = vm.current_realm();
@ -131,7 +132,8 @@ static ThrowCompletionOr<Object*> array_species_create(VM& vm, Object& original_
} }
if (constructor.is_object()) { if (constructor.is_object()) {
constructor = TRY(constructor.as_object().get(vm.well_known_symbol_species())); static Bytecode::PropertyLookupCache cache2;
constructor = TRY(constructor.as_object().get(vm.well_known_symbol_species(), cache2));
if (constructor.is_null()) if (constructor.is_null())
constructor = js_undefined(); constructor = js_undefined();
} }

View file

@ -22,7 +22,8 @@ ThrowCompletionOr<GC::Ref<AsyncGenerator>> AsyncGenerator::create(Realm& realm,
{ {
auto& vm = realm.vm(); auto& vm = realm.vm();
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
auto generating_function_prototype = TRY(generating_function->get(vm.names.prototype)); static Bytecode::PropertyLookupCache cache;
auto generating_function_prototype = TRY(generating_function->get(vm.names.prototype, cache));
auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm)); auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
auto object = realm.create<AsyncGenerator>(realm, generating_function_prototype_object, move(execution_context)); auto object = realm.create<AsyncGenerator>(realm, generating_function_prototype_object, move(execution_context));
object->m_generating_function = generating_function; object->m_generating_function = generating_function;

View file

@ -29,7 +29,8 @@ ThrowCompletionOr<GC::Ref<GeneratorObject>> GeneratorObject::create(Realm& realm
// changed thus we hardcode the prototype. // changed thus we hardcode the prototype.
generating_function_prototype = realm.intrinsics().generator_prototype(); generating_function_prototype = realm.intrinsics().generator_prototype();
} else { } else {
generating_function_prototype = TRY(generating_function->get(vm.names.prototype)); static Bytecode::PropertyLookupCache cache;
generating_function_prototype = TRY(generating_function->get(vm.names.prototype, cache));
} }
auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm)); auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
auto object = realm.create<GeneratorObject>(realm, generating_function_prototype_object, move(execution_context)); auto object = realm.create<GeneratorObject>(realm, generating_function_prototype_object, move(execution_context));

View file

@ -39,7 +39,8 @@ Iterator::Iterator(Object& prototype)
ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator_direct(VM& vm, Object& object) ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator_direct(VM& vm, Object& object)
{ {
// 1. Let nextMethod be ? Get(obj, "next"). // 1. Let nextMethod be ? Get(obj, "next").
auto next_method = TRY(object.get(vm.names.next)); static Bytecode::PropertyLookupCache cache;
auto next_method = TRY(object.get(vm.names.next, cache));
// 2. Let iteratorRecord be Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }. // 2. Let iteratorRecord be Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 3. Return iteratorRecord. // 3. Return iteratorRecord.
@ -57,7 +58,8 @@ ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator_from_method(VM& vm, Valu
return vm.throw_completion<TypeError>(ErrorType::NotIterable, object.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NotIterable, object.to_string_without_side_effects());
// 3. Let nextMethod be ? Get(iterator, "next"). // 3. Let nextMethod be ? Get(iterator, "next").
auto next_method = TRY(iterator.get(vm, vm.names.next)); static Bytecode::PropertyLookupCache cache;
auto next_method = TRY(iterator.get(vm, vm.names.next, cache));
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
auto iterator_record = vm.heap().allocate<IteratorRecord>(iterator.as_object(), next_method, false); auto iterator_record = vm.heap().allocate<IteratorRecord>(iterator.as_object(), next_method, false);
@ -79,7 +81,8 @@ ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator(VM& vm, Value object, It
// b. If method is undefined, then // b. If method is undefined, then
if (!method) { if (!method) {
// i. Let syncMethod be ? GetMethod(obj, @@iterator). // i. Let syncMethod be ? GetMethod(obj, @@iterator).
auto sync_method = TRY(object.get_method(vm, vm.well_known_symbol_iterator())); static Bytecode::PropertyLookupCache cache;
auto sync_method = TRY(object.get_method(vm, vm.well_known_symbol_iterator(), cache));
// ii. If syncMethod is undefined, throw a TypeError exception. // ii. If syncMethod is undefined, throw a TypeError exception.
if (!sync_method) if (!sync_method)
@ -95,7 +98,8 @@ ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator(VM& vm, Value object, It
// 2. Else, // 2. Else,
else { else {
// a. Let method be ? GetMethod(obj, @@iterator). // a. Let method be ? GetMethod(obj, @@iterator).
method = TRY(object.get_method(vm, vm.well_known_symbol_iterator())); static Bytecode::PropertyLookupCache cache;
method = TRY(object.get_method(vm, vm.well_known_symbol_iterator(), cache));
} }
// 3. If method is undefined, throw a TypeError exception. // 3. If method is undefined, throw a TypeError exception.
@ -124,7 +128,8 @@ ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator_flattenable(VM& vm, Valu
} }
// 2. Let method be ? GetMethod(obj, %Symbol.iterator%). // 2. Let method be ? GetMethod(obj, %Symbol.iterator%).
auto method = TRY(object.get_method(vm, vm.well_known_symbol_iterator())); static Bytecode::PropertyLookupCache cache;
auto method = TRY(object.get_method(vm, vm.well_known_symbol_iterator(), cache));
Value iterator; Value iterator;
@ -192,14 +197,16 @@ ThrowCompletionOr<GC::Ref<Object>> iterator_next(VM& vm, IteratorRecord& iterato
ThrowCompletionOr<bool> iterator_complete(VM& vm, Object& iterator_result) ThrowCompletionOr<bool> iterator_complete(VM& vm, Object& iterator_result)
{ {
// 1. Return ToBoolean(? Get(iterResult, "done")). // 1. Return ToBoolean(? Get(iterResult, "done")).
return TRY(iterator_result.get(vm.names.done)).to_boolean(); static Bytecode::PropertyLookupCache cache;
return TRY(iterator_result.get(vm.names.done, cache)).to_boolean();
} }
// 7.4.8 IteratorValue ( iteratorResult ), https://tc39.es/ecma262/#sec-iteratorvalue // 7.4.8 IteratorValue ( iteratorResult ), https://tc39.es/ecma262/#sec-iteratorvalue
ThrowCompletionOr<Value> iterator_value(VM& vm, Object& iterator_result) ThrowCompletionOr<Value> iterator_value(VM& vm, Object& iterator_result)
{ {
// 1. Return ? Get(iterResult, "value"). // 1. Return ? Get(iterResult, "value").
return TRY(iterator_result.get(vm.names.value)); static Bytecode::PropertyLookupCache cache;
return TRY(iterator_result.get(vm.names.value, cache));
} }
// 7.4.9 IteratorStep ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstep // 7.4.9 IteratorStep ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstep
@ -220,7 +227,8 @@ ThrowCompletionOr<IterationResultOrDone> iterator_step(VM& vm, IteratorRecord& i
auto result = TRY(iterator_next(vm, iterator_record)); auto result = TRY(iterator_next(vm, iterator_record));
// 2. Let done be Completion(IteratorComplete(result)). // 2. Let done be Completion(IteratorComplete(result)).
auto done = result->get(vm.names.done); static Bytecode::PropertyLookupCache cache;
auto done = result->get(vm.names.done, cache);
// 3. If done is a throw completion, then // 3. If done is a throw completion, then
if (done.is_throw_completion()) { if (done.is_throw_completion()) {
@ -244,7 +252,8 @@ ThrowCompletionOr<IterationResultOrDone> iterator_step(VM& vm, IteratorRecord& i
} }
// 6. Return result. // 6. Return result.
return ThrowCompletionOr<IterationResultOrDone> { IterationResult { done_value, result->get(vm.names.value) } }; static Bytecode::PropertyLookupCache cache2;
return ThrowCompletionOr<IterationResultOrDone> { IterationResult { done_value, result->get(vm.names.value, cache2) } };
} }
// 7.4.10 IteratorStepValue ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstepvalue // 7.4.10 IteratorStepValue ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstepvalue

View file

@ -125,6 +125,13 @@ ThrowCompletionOr<Value> Object::get(PropertyKey const& property_key) const
return TRY(internal_get(property_key, this)); return TRY(internal_get(property_key, this));
} }
// 7.3.2 Get ( O, P ), https://tc39.es/ecma262/#sec-get-o-p
ThrowCompletionOr<Value> Object::get(PropertyKey const& property_key, Bytecode::PropertyLookupCache& cache) const
{
// 1. Return ? O.[[Get]](P, O).
return TRY(Value(this).get(vm(), property_key, cache));
}
// NOTE: 7.3.3 GetV ( V, P ) is implemented as Value::get(). // NOTE: 7.3.3 GetV ( V, P ) is implemented as Value::get().
// 7.3.4 Set ( O, P, V, Throw ), https://tc39.es/ecma262/#sec-set-o-p-v-throw // 7.3.4 Set ( O, P, V, Throw ), https://tc39.es/ecma262/#sec-set-o-p-v-throw
@ -1540,7 +1547,15 @@ ThrowCompletionOr<Value> Object::ordinary_to_primitive(Value::PreferredType pref
// 3. For each element name of methodNames, do // 3. For each element name of methodNames, do
for (auto& method_name : method_names) { for (auto& method_name : method_names) {
// a. Let method be ? Get(O, name). // a. Let method be ? Get(O, name).
auto method = TRY(get(method_name)); Value method;
if (method_name == vm.names.toString) {
static Bytecode::PropertyLookupCache cache;
method = TRY(get(method_name, cache));
} else {
ASSERT(method_name == vm.names.valueOf);
static Bytecode::PropertyLookupCache cache;
method = TRY(get(method_name, cache));
}
// b. If IsCallable(method) is true, then // b. If IsCallable(method) is true, then
if (method.is_function()) { if (method.is_function()) {

View file

@ -121,6 +121,7 @@ public:
// 7.3 Operations on Objects, https://tc39.es/ecma262/#sec-operations-on-objects // 7.3 Operations on Objects, https://tc39.es/ecma262/#sec-operations-on-objects
ThrowCompletionOr<Value> get(PropertyKey const&) const; ThrowCompletionOr<Value> get(PropertyKey const&) const;
ThrowCompletionOr<Value> get(PropertyKey const&, Bytecode::PropertyLookupCache&) const;
ThrowCompletionOr<void> set(PropertyKey const&, Value, ShouldThrowExceptions); ThrowCompletionOr<void> set(PropertyKey const&, Value, ShouldThrowExceptions);
ThrowCompletionOr<bool> create_data_property(PropertyKey const&, Value, Optional<u32>* new_property_offset = nullptr); ThrowCompletionOr<bool> create_data_property(PropertyKey const&, Value, Optional<u32>* new_property_offset = nullptr);
void create_method_property(PropertyKey const&, Value); void create_method_property(PropertyKey const&, Value);

View file

@ -182,7 +182,8 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_string)
builtin_tag = "Object"sv; builtin_tag = "Object"sv;
// 15. Let tag be ? Get(O, @@toStringTag). // 15. Let tag be ? Get(O, @@toStringTag).
auto to_string_tag = TRY(object->get(vm.well_known_symbol_to_string_tag())); static Bytecode::PropertyLookupCache cache;
auto to_string_tag = TRY(object->get(vm.well_known_symbol_to_string_tag(), cache));
// Optimization: Instead of creating another PrimitiveString from builtin_tag, we separate tag and to_string_tag and add an additional branch to step 16. // Optimization: Instead of creating another PrimitiveString from builtin_tag, we separate tag and to_string_tag and add an additional branch to step 16.
StringView tag; StringView tag;

View file

@ -61,7 +61,8 @@ void RegExpPrototype::initialize(Realm& realm)
static ThrowCompletionOr<void> increment_last_index(VM& vm, Object& regexp_object, Utf16View const& string, bool unicode) static ThrowCompletionOr<void> increment_last_index(VM& vm, Object& regexp_object, Utf16View const& string, bool unicode)
{ {
// Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))). // Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))).
auto last_index_value = TRY(regexp_object.get(vm.names.lastIndex)); static Bytecode::PropertyLookupCache cache;
auto last_index_value = TRY(regexp_object.get(vm.names.lastIndex, cache));
auto last_index = TRY(last_index_value.to_length(vm)); auto last_index = TRY(last_index_value.to_length(vm));
// Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode). // Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
@ -176,7 +177,8 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
// 1. Let length be the length of S. // 1. Let length be the length of S.
// 2. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))). // 2. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
auto last_index_value = TRY(regexp_object.get(vm.names.lastIndex)); static Bytecode::PropertyLookupCache cache;
auto last_index_value = TRY(regexp_object.get(vm.names.lastIndex, cache));
auto last_index = TRY(last_index_value.to_length(vm)); auto last_index = TRY(last_index_value.to_length(vm));
auto const& regex = regexp_object.regex(); auto const& regex = regexp_object.regex();
@ -393,7 +395,8 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
ThrowCompletionOr<Value> regexp_exec(VM& vm, Object& regexp_object, GC::Ref<PrimitiveString> string) ThrowCompletionOr<Value> regexp_exec(VM& vm, Object& regexp_object, GC::Ref<PrimitiveString> string)
{ {
// 1. Let exec be ? Get(R, "exec"). // 1. Let exec be ? Get(R, "exec").
auto exec = TRY(regexp_object.get(vm.names.exec)); static Bytecode::PropertyLookupCache cache;
auto exec = TRY(regexp_object.get(vm.names.exec, cache));
// 2. If IsCallable(exec) is true, then // 2. If IsCallable(exec) is true, then
if (exec.is_function()) { if (exec.is_function()) {
@ -508,10 +511,13 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::flags)
// 17. If unicodeSets is true, append the code unit 0x0076 (LATIN SMALL LETTER V) as the last code unit of result. // 17. If unicodeSets is true, append the code unit 0x0076 (LATIN SMALL LETTER V) as the last code unit of result.
// 18. Let sticky be ToBoolean(? Get(R, "sticky")). // 18. Let sticky be ToBoolean(? Get(R, "sticky")).
// 19. If sticky is true, append the code unit 0x0079 (LATIN SMALL LETTER Y) as the last code unit of result. // 19. If sticky is true, append the code unit 0x0079 (LATIN SMALL LETTER Y) as the last code unit of result.
#define __JS_ENUMERATE(FlagName, flagName, flag_name, flag_char) \ #define __JS_ENUMERATE(FlagName, flagName, flag_name, flag_char) \
auto flag_##flag_name = TRY(regexp_object->get(vm.names.flagName)); \ { \
if (flag_##flag_name.to_boolean()) \ static Bytecode::PropertyLookupCache cache; \
builder.append(#flag_char##sv); auto flag_##flag_name = TRY(regexp_object->get(vm.names.flagName, cache)); \
if (flag_##flag_name.to_boolean()) \
builder.append(#flag_char##sv); \
}
JS_ENUMERATE_REGEXP_FLAGS JS_ENUMERATE_REGEXP_FLAGS
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
@ -532,7 +538,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_match)
auto string = TRY(vm.argument(0).to_primitive_string(vm)); auto string = TRY(vm.argument(0).to_primitive_string(vm));
// 4. Let flags be ? ToString(? Get(rx, "flags")). // 4. Let flags be ? ToString(? Get(rx, "flags")).
auto flags_value = TRY(regexp_object->get(vm.names.flags)); static Bytecode::PropertyLookupCache cache;
auto flags_value = TRY(regexp_object->get(vm.names.flags, cache));
auto flags = TRY(flags_value.to_string(vm)); auto flags = TRY(flags_value.to_string(vm));
// 5. If flags does not contain "g", then // 5. If flags does not contain "g", then
@ -608,7 +615,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_match_all)
auto* constructor = TRY(species_constructor(vm, regexp_object, realm.intrinsics().regexp_constructor())); auto* constructor = TRY(species_constructor(vm, regexp_object, realm.intrinsics().regexp_constructor()));
// 5. Let flags be ? ToString(? Get(R, "flags")). // 5. Let flags be ? ToString(? Get(R, "flags")).
auto flags_value = TRY(regexp_object->get(vm.names.flags)); static Bytecode::PropertyLookupCache cache;
auto flags_value = TRY(regexp_object->get(vm.names.flags, cache));
auto flags = TRY(flags_value.to_string(vm)); auto flags = TRY(flags_value.to_string(vm));
// Steps 9-12 are performed early so that flags can be moved. // Steps 9-12 are performed early so that flags can be moved.
@ -625,7 +633,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_match_all)
auto matcher = TRY(construct(vm, *constructor, regexp_object, PrimitiveString::create(vm, move(flags)))); auto matcher = TRY(construct(vm, *constructor, regexp_object, PrimitiveString::create(vm, move(flags))));
// 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
auto last_index_value = TRY(regexp_object->get(vm.names.lastIndex)); static Bytecode::PropertyLookupCache cache2;
auto last_index_value = TRY(regexp_object->get(vm.names.lastIndex, cache2));
auto last_index = TRY(last_index_value.to_length(vm)); auto last_index = TRY(last_index_value.to_length(vm));
// 8. Perform ? Set(matcher, "lastIndex", lastIndex, true). // 8. Perform ? Set(matcher, "lastIndex", lastIndex, true).
@ -659,7 +668,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace)
} }
// 7. Let flags be ? ToString(? Get(rx, "flags")). // 7. Let flags be ? ToString(? Get(rx, "flags")).
auto flags_value = TRY(regexp_object->get(vm.names.flags)); static Bytecode::PropertyLookupCache cache;
auto flags_value = TRY(regexp_object->get(vm.names.flags, cache));
auto flags = TRY(flags_value.to_string(vm)); auto flags = TRY(flags_value.to_string(vm));
// 8. If flags contains "g", let global be true. Otherwise, let global be false. // 8. If flags contains "g", let global be true. Otherwise, let global be false.
@ -731,7 +741,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace)
auto matched_length = matched->length_in_utf16_code_units(); auto matched_length = matched->length_in_utf16_code_units();
// e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")). // e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")).
auto position_value = TRY(result->get(vm.names.index)); static Bytecode::PropertyLookupCache cache2;
auto position_value = TRY(result->get(vm.names.index, cache2));
double position = TRY(position_value.to_integer_or_infinity(vm)); double position = TRY(position_value.to_integer_or_infinity(vm));
// f. Set position to the result of clamping position between 0 and lengthS. // f. Set position to the result of clamping position between 0 and lengthS.
@ -760,7 +771,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_replace)
} }
// j. Let namedCaptures be ? Get(result, "groups"). // j. Let namedCaptures be ? Get(result, "groups").
auto named_captures = TRY(result->get(vm.names.groups)); static Bytecode::PropertyLookupCache cache3;
auto named_captures = TRY(result->get(vm.names.groups, cache3));
String replacement; String replacement;
@ -833,7 +845,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_search)
auto string = TRY(vm.argument(0).to_primitive_string(vm)); auto string = TRY(vm.argument(0).to_primitive_string(vm));
// 4. Let previousLastIndex be ? Get(rx, "lastIndex"). // 4. Let previousLastIndex be ? Get(rx, "lastIndex").
auto previous_last_index = TRY(regexp_object->get(vm.names.lastIndex)); static Bytecode::PropertyLookupCache cache;
auto previous_last_index = TRY(regexp_object->get(vm.names.lastIndex, cache));
// 5. If SameValue(previousLastIndex, +0đť”˝) is false, then // 5. If SameValue(previousLastIndex, +0đť”˝) is false, then
if (!same_value(previous_last_index, Value(0))) { if (!same_value(previous_last_index, Value(0))) {
@ -845,7 +858,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_search)
auto result = TRY(regexp_exec(vm, regexp_object, string)); auto result = TRY(regexp_exec(vm, regexp_object, string));
// 7. Let currentLastIndex be ? Get(rx, "lastIndex"). // 7. Let currentLastIndex be ? Get(rx, "lastIndex").
auto current_last_index = TRY(regexp_object->get(vm.names.lastIndex)); static Bytecode::PropertyLookupCache cache2;
auto current_last_index = TRY(regexp_object->get(vm.names.lastIndex, cache2));
// 8. If SameValue(currentLastIndex, previousLastIndex) is false, then // 8. If SameValue(currentLastIndex, previousLastIndex) is false, then
if (!same_value(current_last_index, previous_last_index)) { if (!same_value(current_last_index, previous_last_index)) {
@ -858,7 +872,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_search)
return Value(-1); return Value(-1);
// 10. Return ? Get(result, "index"). // 10. Return ? Get(result, "index").
return TRY(result.get(vm, vm.names.index)); static Bytecode::PropertyLookupCache cache3;
return TRY(result.get(vm, vm.names.index, cache3));
} }
// 22.2.6.13 get RegExp.prototype.source, https://tc39.es/ecma262/#sec-get-regexp.prototype.source // 22.2.6.13 get RegExp.prototype.source, https://tc39.es/ecma262/#sec-get-regexp.prototype.source
@ -903,7 +918,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_split)
auto* constructor = TRY(species_constructor(vm, regexp_object, realm.intrinsics().regexp_constructor())); auto* constructor = TRY(species_constructor(vm, regexp_object, realm.intrinsics().regexp_constructor()));
// 5. Let flags be ? ToString(? Get(rx, "flags")). // 5. Let flags be ? ToString(? Get(rx, "flags")).
auto flags_value = TRY(regexp_object->get(vm.names.flags)); static Bytecode::PropertyLookupCache cache;
auto flags_value = TRY(regexp_object->get(vm.names.flags, cache));
auto flags = TRY(flags_value.to_string(vm)); auto flags = TRY(flags_value.to_string(vm));
// 6. If flags contains "u" or flags contains "v", let unicodeMatching be true. // 6. If flags contains "u" or flags contains "v", let unicodeMatching be true.
@ -973,7 +989,8 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::symbol_split)
// d. Else, // d. Else,
// i. Let e be ℝ(? ToLength(? Get(splitter, "lastIndex"))). // i. Let e be ℝ(? ToLength(? Get(splitter, "lastIndex"))).
auto last_index_value = TRY(splitter->get(vm.names.lastIndex)); static Bytecode::PropertyLookupCache cache2;
auto last_index_value = TRY(splitter->get(vm.names.lastIndex, cache2));
auto last_index = TRY(last_index_value.to_length(vm)); auto last_index = TRY(last_index_value.to_length(vm));
// ii. Set e to min(e, size). // ii. Set e to min(e, size).
@ -1068,11 +1085,13 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::to_string)
auto regexp_object = TRY(this_object(vm)); auto regexp_object = TRY(this_object(vm));
// 3. Let pattern be ? ToString(? Get(R, "source")). // 3. Let pattern be ? ToString(? Get(R, "source")).
auto source_attr = TRY(regexp_object->get(vm.names.source)); static Bytecode::PropertyLookupCache cache;
auto source_attr = TRY(regexp_object->get(vm.names.source, cache));
auto pattern = TRY(source_attr.to_string(vm)); auto pattern = TRY(source_attr.to_string(vm));
// 4. Let flags be ? ToString(? Get(R, "flags")). // 4. Let flags be ? ToString(? Get(R, "flags")).
auto flags_attr = TRY(regexp_object->get(vm.names.flags)); static Bytecode::PropertyLookupCache cache2;
auto flags_attr = TRY(regexp_object->get(vm.names.flags, cache2));
auto flags = TRY(flags_attr.to_string(vm)); auto flags = TRY(flags_attr.to_string(vm));
// 5. Let result be the string-concatenation of "/", pattern, "/", and flags. // 5. Let result be the string-concatenation of "/", pattern, "/", and flags.

View file

@ -587,7 +587,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match)
auto regexp = vm.argument(0); auto regexp = vm.argument(0);
if (regexp.is_object()) { if (regexp.is_object()) {
// a. Let matcher be ? GetMethod(regexp, @@match). // a. Let matcher be ? GetMethod(regexp, @@match).
auto matcher = TRY(regexp.get_method(vm, vm.well_known_symbol_match())); static Bytecode::PropertyLookupCache cache;
auto matcher = TRY(regexp.get_method(vm, vm.well_known_symbol_match(), cache));
// b. If matcher is not undefined, then // b. If matcher is not undefined, then
if (matcher) { if (matcher) {
@ -634,7 +635,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match_all)
} }
// c. Let matcher be ? GetMethod(regexp, @@matchAll). // c. Let matcher be ? GetMethod(regexp, @@matchAll).
auto matcher = TRY(regexp.get_method(vm, vm.well_known_symbol_match_all())); static Bytecode::PropertyLookupCache cache;
auto matcher = TRY(regexp.get_method(vm, vm.well_known_symbol_match_all(), cache));
// d. If matcher is not undefined, then // d. If matcher is not undefined, then
if (matcher) { if (matcher) {
@ -804,7 +806,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
// 2. If searchValue is an Object, then // 2. If searchValue is an Object, then
if (search_value.is_object()) { if (search_value.is_object()) {
// a. Let replacer be ? GetMethod(searchValue, @@replace). // a. Let replacer be ? GetMethod(searchValue, @@replace).
auto replacer = TRY(search_value.get_method(vm, vm.well_known_symbol_replace())); static Bytecode::PropertyLookupCache cache;
auto replacer = TRY(search_value.get_method(vm, vm.well_known_symbol_replace(), cache));
// b. If replacer is not undefined, then // b. If replacer is not undefined, then
if (replacer) { if (replacer) {
@ -899,7 +902,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace_all)
} }
// c. Let replacer be ? GetMethod(searchValue, @@replace). // c. Let replacer be ? GetMethod(searchValue, @@replace).
auto replacer = TRY(search_value.get_method(vm, vm.well_known_symbol_replace())); static Bytecode::PropertyLookupCache cache;
auto replacer = TRY(search_value.get_method(vm, vm.well_known_symbol_replace(), cache));
// d. If replacer is not undefined, then // d. If replacer is not undefined, then
if (replacer) { if (replacer) {
@ -999,7 +1003,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::search)
// 2. If regexp is an Object, then // 2. If regexp is an Object, then
if (regexp.is_object()) { if (regexp.is_object()) {
// a. Let searcher be ? GetMethod(regexp, @@search). // a. Let searcher be ? GetMethod(regexp, @@search).
auto searcher = TRY(regexp.get_method(vm, vm.well_known_symbol_search())); static Bytecode::PropertyLookupCache cache;
auto searcher = TRY(regexp.get_method(vm, vm.well_known_symbol_search(), cache));
// b. If searcher is not undefined, then // b. If searcher is not undefined, then
if (searcher) { if (searcher) {
@ -1080,7 +1085,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
// 2. If separator is an Object, then // 2. If separator is an Object, then
if (separator_argument.is_object()) { if (separator_argument.is_object()) {
// a. Let splitter be ? GetMethod(separator, @@split). // a. Let splitter be ? GetMethod(separator, @@split).
auto splitter = TRY(separator_argument.get_method(vm, vm.well_known_symbol_split())); static Bytecode::PropertyLookupCache cache;
auto splitter = TRY(separator_argument.get_method(vm, vm.well_known_symbol_split(), cache));
// b. If splitter is not undefined, then // b. If splitter is not undefined, then
if (splitter) { if (splitter) {
// i. Return ? Call(splitter, separator, « O, limit »). // i. Return ? Call(splitter, separator, « O, limit »).

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2020-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, David Tuin <davidot@serenityos.org> * Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* *
@ -15,6 +15,7 @@
#include <AK/Utf16String.h> #include <AK/Utf16String.h>
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <LibCrypto/BigInt/SignedBigInteger.h> #include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Bytecode/PropertyAccess.h>
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h> #include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
@ -304,7 +305,8 @@ ThrowCompletionOr<bool> Value::is_regexp(VM& vm) const
return false; return false;
// 2. Let matcher be ? Get(argument, @@match). // 2. Let matcher be ? Get(argument, @@match).
auto matcher = TRY(as_object().get(vm.well_known_symbol_match())); static Bytecode::PropertyLookupCache cache;
auto matcher = TRY(as_object().get(vm.well_known_symbol_match(), cache));
// 3. If matcher is not undefined, return ToBoolean(matcher). // 3. If matcher is not undefined, return ToBoolean(matcher).
if (!matcher.is_undefined()) if (!matcher.is_undefined())
@ -568,7 +570,8 @@ ThrowCompletionOr<Value> Value::to_primitive_slow_case(VM& vm, PreferredType pre
// 1. If input is an Object, then // 1. If input is an Object, then
if (is_object()) { if (is_object()) {
// a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
auto exotic_to_primitive = TRY(get_method(vm, vm.well_known_symbol_to_primitive())); static Bytecode::PropertyLookupCache cache;
auto exotic_to_primitive = TRY(get_method(vm, vm.well_known_symbol_to_primitive(), cache));
// b. If exoticToPrim is not undefined, then // b. If exoticToPrim is not undefined, then
if (exotic_to_primitive) { if (exotic_to_primitive) {
@ -1306,6 +1309,13 @@ ThrowCompletionOr<Value> Value::get(VM& vm, PropertyKey const& property_key) con
return TRY(object->internal_get(property_key, *this)); return TRY(object->internal_get(property_key, *this));
} }
ThrowCompletionOr<Value> Value::get(VM& vm, PropertyKey const& property, Bytecode::PropertyLookupCache& cache) const
{
if (is_nullish())
return vm.throw_completion<TypeError>(ErrorType::ToObjectNullOrUndefined);
return Bytecode::get_by_id<Bytecode::GetByIdMode::Normal>(vm, [&]() { return Optional<Utf16FlyString const&> {}; }, [&]() { return property; }, *this, *this, cache);
}
// 7.3.11 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod // 7.3.11 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod
ThrowCompletionOr<GC::Ptr<FunctionObject>> Value::get_method(VM& vm, PropertyKey const& property_key) const ThrowCompletionOr<GC::Ptr<FunctionObject>> Value::get_method(VM& vm, PropertyKey const& property_key) const
{ {
@ -1324,6 +1334,24 @@ ThrowCompletionOr<GC::Ptr<FunctionObject>> Value::get_method(VM& vm, PropertyKey
return function.as_function(); return function.as_function();
} }
// 7.3.11 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod
ThrowCompletionOr<GC::Ptr<FunctionObject>> Value::get_method(VM& vm, PropertyKey const& property_key, Bytecode::PropertyLookupCache& cache) const
{
// 1. Let func be ? GetV(V, P).
auto function = TRY(get(vm, property_key, cache));
// 2. If func is either undefined or null, return undefined.
if (function.is_nullish())
return nullptr;
// 3. If IsCallable(func) is false, throw a TypeError exception.
if (!function.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, function.to_string_without_side_effects());
// 4. Return func.
return function.as_function();
}
// 13.10 Relational Operators, https://tc39.es/ecma262/#sec-relational-operators // 13.10 Relational Operators, https://tc39.es/ecma262/#sec-relational-operators
// RelationalExpression : RelationalExpression > ShiftExpression // RelationalExpression : RelationalExpression > ShiftExpression
ThrowCompletionOr<bool> greater_than(VM& vm, Value lhs, Value rhs) ThrowCompletionOr<bool> greater_than(VM& vm, Value lhs, Value rhs)
@ -2168,7 +2196,8 @@ ThrowCompletionOr<Value> instance_of(VM& vm, Value value, Value target)
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, target.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NotAnObject, target.to_string_without_side_effects());
// 2. Let instOfHandler be ? GetMethod(target, @@hasInstance). // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
auto instance_of_handler = TRY(target.get_method(vm, vm.well_known_symbol_has_instance())); static Bytecode::PropertyLookupCache cache;
auto instance_of_handler = TRY(target.get_method(vm, vm.well_known_symbol_has_instance(), cache));
// 3. If instOfHandler is not undefined, then // 3. If instOfHandler is not undefined, then
if (instance_of_handler) { if (instance_of_handler) {
@ -2215,7 +2244,8 @@ ThrowCompletionOr<Value> ordinary_has_instance(VM& vm, Value lhs, Value rhs)
auto* lhs_object = &lhs.as_object(); auto* lhs_object = &lhs.as_object();
// 4. Let P be ? Get(C, "prototype"). // 4. Let P be ? Get(C, "prototype").
auto rhs_prototype = TRY(rhs_function.get(vm.names.prototype)); static Bytecode::PropertyLookupCache cache;
auto rhs_prototype = TRY(rhs.get(vm, vm.names.prototype, cache));
// 5. If P is not an Object, throw a TypeError exception. // 5. If P is not an Object, throw a TypeError exception.
if (!rhs_prototype.is_object()) if (!rhs_prototype.is_object())

View file

@ -382,7 +382,10 @@ public:
bool to_boolean() const; bool to_boolean() const;
ThrowCompletionOr<Value> get(VM&, PropertyKey const&) const; ThrowCompletionOr<Value> get(VM&, PropertyKey const&) const;
ThrowCompletionOr<Value> get(VM&, PropertyKey const&, Bytecode::PropertyLookupCache&) const;
ThrowCompletionOr<GC::Ptr<FunctionObject>> get_method(VM&, PropertyKey const&) const; ThrowCompletionOr<GC::Ptr<FunctionObject>> get_method(VM&, PropertyKey const&) const;
ThrowCompletionOr<GC::Ptr<FunctionObject>> get_method(VM&, PropertyKey const&, Bytecode::PropertyLookupCache&) const;
[[nodiscard]] String to_string_without_side_effects() const; [[nodiscard]] String to_string_without_side_effects() const;
[[nodiscard]] Utf16String to_utf16_string_without_side_effects() const; [[nodiscard]] Utf16String to_utf16_string_without_side_effects() const;