mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-19 15:43:20 +00:00
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.
This commit is contained in:
parent
3716db1c61
commit
26c1dea22a
Notes:
github-actions[bot]
2025-10-14 13:49:04 +00:00
Author: https://github.com/awesomekling
Commit: 26c1dea22a
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6474
3 changed files with 158 additions and 111 deletions
|
@ -16,6 +16,7 @@
|
|||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibJS/Bytecode/Label.h>
|
||||
#include <LibJS/Bytecode/Op.h>
|
||||
#include <LibJS/Bytecode/PropertyAccess.h>
|
||||
#include <LibJS/Export.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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& 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 {};
|
||||
}
|
||||
|
||||
|
@ -2646,7 +2540,7 @@ ThrowCompletionOr<void> GetByIdWithThis::execute_impl(Bytecode::Interpreter& int
|
|||
auto base_value = interpreter.get(m_base);
|
||||
auto this_value = interpreter.get(m_this_value);
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
@ -2656,7 +2550,7 @@ ThrowCompletionOr<void> GetLength::execute_impl(Bytecode::Interpreter& interpret
|
|||
auto& executable = interpreter.current_executable();
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
@ -2666,7 +2560,7 @@ ThrowCompletionOr<void> GetLengthWithThis::execute_impl(Bytecode::Interpreter& i
|
|||
auto this_value = interpreter.get(m_this_value);
|
||||
auto& executable = interpreter.current_executable();
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue