/* * Copyright (c) 2021-2025, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace JS::Bytecode { enum class GetByIdMode { Normal, Length, }; ALWAYS_INLINE GC::Ptr 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 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(ErrorType::ToObjectNullOrUndefinedWithPropertyAndName, get_property_name(), base_value, base_identifier); return vm.throw_completion(ErrorType::ToObjectNullOrUndefinedWithProperty, get_property_name(), base_value); } template ALWAYS_INLINE ThrowCompletionOr> 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 ALWAYS_INLINE ThrowCompletionOr 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 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; } }