LibJS: Reduce inline cache memory usage

Store bytecode property lookup caches as tiered handles instead of
eagerly allocating four entries for every cache slot. Each slot starts
empty, grows to one monomorphic entry after the first cacheable lookup,
and promotes to the existing four-entry table when another cache key
appears.

Global variable caches keep one inline property entry because they
usually warm up as global object property accesses. This keeps common
global access allocation-free while still avoiding the inherited
polymorphic cache cost.

Generated asm offsets now point at the lazy property-cache storage and
the inline global entry, and asm fast paths bail out for empty property
cache slots.
This commit is contained in:
Andreas Kling 2026-05-18 13:57:57 +02:00 committed by Andreas Kling
parent 75b2bcd7e4
commit 0a49dd1c28
7 changed files with 336 additions and 94 deletions

View file

@ -915,9 +915,8 @@ i64 asm_try_put_by_id_cache(VM* vm, u32 pc)
auto value = vm->get(insn.src());
auto& cache = vm->current_executable().property_lookup_caches[insn.cache()];
for (size_t i = 0; i < cache.entries.size(); ++i) {
auto& entry = cache.entries[i];
switch (cache.types[i]) {
for (auto& entry : cache.entries()) {
switch (entry.type) {
case PropertyLookupCache::Entry::Type::ChangeOwnProperty: {
auto cached_shape = entry.shape.ptr();
if (cached_shape != &object.shape()) [[unlikely]]
@ -970,7 +969,12 @@ i64 asm_try_get_by_id_cache(VM* vm, u32 pc)
auto& shape = object.shape();
auto& cache = vm->current_executable().property_lookup_caches[insn.cache()];
for (auto& entry : cache.entries) {
for (auto& entry : cache.entries()) {
if (entry.type != PropertyLookupCache::Entry::Type::GetOwnProperty
&& entry.type != PropertyLookupCache::Entry::Type::GetPropertyInPrototypeChain) {
continue;
}
auto cached_prototype = entry.prototype.ptr();
if (cached_prototype) {
if (&shape != entry.shape) [[unlikely]]

View file

@ -499,13 +499,18 @@ macro pop_inline_frame_and_resume(caller_frame, value_reg)
dispatch_current
end
macro load_property_lookup_cache(cache)
macro load_property_lookup_cache(cache, fail_label)
temp exe, caches
load32 cache, [pb, pc, m_cache]
mul cache, cache, PROPERTY_LOOKUP_CACHE_SIZE
load64 exe, [exec_ctx, EXECUTION_CONTEXT_EXECUTABLE]
load64 caches, [exe, EXECUTABLE_PROPERTY_LOOKUP_CACHES_DATA]
add cache, caches
load64 cache, [cache, PROPERTY_LOOKUP_CACHE_DATA]
branch_zero cache, fail_label
branch_bits_clear cache, PROPERTY_LOOKUP_CACHE_POLYMORPHIC_DATA_TAG, .data_loaded
sub cache, PROPERTY_LOOKUP_CACHE_POLYMORPHIC_DATA_TAG
.data_loaded:
end
macro load_global_variable_cache(cache)
@ -1685,13 +1690,13 @@ handler GetById
unbox_object obj, base
load64 shape, [obj, OBJECT_SHAPE]
assert_nonzero shape
load_property_lookup_cache plc
load_property_lookup_cache plc, .try_cache
assert_nonzero plc
load_pair64 cache_shape, cache_proto, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE]
load_pair64 cache_shape, cache_proto, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_SHAPE], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE]
branch_ne cache_shape, shape, .try_cache
branch_nonzero cache_proto, .proto
# Check dictionary generation matches
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_DICTIONARY_GENERATION]
load32 cur_dict_gen, [shape, SHAPE_DICTIONARY_GENERATION]
branch_ne dict_gen, cur_dict_gen, .try_cache
# IC hit! Load property value via get_direct (own property)
@ -1705,11 +1710,11 @@ handler GetById
dispatch_next
.proto:
# cache_proto = prototype Object*, shape = object's shape, plc = PLC base
load64 prop_offset, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE_CHAIN_VALIDITY]
load64 prop_offset, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE_CHAIN_VALIDITY]
branch_zero prop_offset, .try_cache
load8 tag, [prop_offset, PROTOTYPE_CHAIN_VALIDITY_VALID]
branch_zero tag, .try_cache
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_DICTIONARY_GENERATION]
load32 cur_dict_gen, [shape, SHAPE_DICTIONARY_GENERATION]
branch_ne dict_gen, cur_dict_gen, .try_cache
load64 props, [cache_proto, OBJECT_NAMED_PROPERTIES]
@ -1738,12 +1743,12 @@ handler PutById
unbox_object obj, base
load64 shape, [obj, OBJECT_SHAPE]
assert_nonzero shape
load_property_lookup_cache plc
load_property_lookup_cache plc, .try_cache
assert_nonzero plc
load_pair64 cache_shape, cache_proto, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE]
load_pair64 cache_shape, cache_proto, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_SHAPE], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE]
branch_ne cache_shape, shape, .try_cache
branch_nonzero cache_proto, .try_cache
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_DICTIONARY_GENERATION]
load32 cur_dict_gen, [shape, SHAPE_DICTIONARY_GENERATION]
branch_ne dict_gen, cur_dict_gen, .try_cache
# Check current value at prop_offset is not an accessor
@ -1916,12 +1921,12 @@ handler GetLength
# Non-magical length: IC fast path (same as GetById)
load64 shape, [obj, OBJECT_SHAPE]
assert_nonzero shape
load_property_lookup_cache plc
load_property_lookup_cache plc, .slow
assert_nonzero plc
load_pair64 cache_shape, cache_proto, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE]
load_pair64 cache_shape, cache_proto, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_SHAPE], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE]
branch_ne cache_shape, shape, .slow
branch_nonzero cache_proto, .slow
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [plc, PROPERTY_LOOKUP_CACHE_ENTRY_PROPERTY_OFFSET], [plc, PROPERTY_LOOKUP_CACHE_ENTRY_DICTIONARY_GENERATION]
load32 cur_dict_gen, [shape, SHAPE_DICTIONARY_GENERATION]
branch_ne dict_gen, cur_dict_gen, .slow
load64 props, [obj, OBJECT_NAMED_PROPERTIES]
@ -1967,10 +1972,10 @@ handler GetGlobal
# (falls through to env binding path on shape mismatch)
load64 shape, [global_object, OBJECT_SHAPE]
assert_nonzero shape
load64 cache_shape, [gvc, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE]
load64 cache_shape, [gvc, GLOBAL_VARIABLE_CACHE_ENTRY_SHAPE]
branch_ne cache_shape, shape, .try_env_binding
load32 cur_dict_gen, [shape, SHAPE_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [gvc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [gvc, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [gvc, GLOBAL_VARIABLE_CACHE_ENTRY_PROPERTY_OFFSET], [gvc, GLOBAL_VARIABLE_CACHE_ENTRY_DICTIONARY_GENERATION]
branch_ne dict_gen, cur_dict_gen, .try_env_binding
# IC hit! Load property value via get_direct
load64 props, [global_object, OBJECT_NAMED_PROPERTIES]
@ -2013,10 +2018,10 @@ handler SetGlobal
branch_ne cache_serial, env_serial, .slow
load64 shape, [global_object, OBJECT_SHAPE]
assert_nonzero shape
load64 cache_shape, [gvc, PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE]
load64 cache_shape, [gvc, GLOBAL_VARIABLE_CACHE_ENTRY_SHAPE]
branch_ne cache_shape, shape, .try_env_binding
load32 cur_dict_gen, [shape, SHAPE_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [gvc, PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET], [gvc, PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION]
load_pair32 prop_offset, dict_gen, [gvc, GLOBAL_VARIABLE_CACHE_ENTRY_PROPERTY_OFFSET], [gvc, GLOBAL_VARIABLE_CACHE_ENTRY_DICTIONARY_GENERATION]
branch_ne dict_gen, cur_dict_gen, .try_env_binding
# IC hit! Load current value to check it's not an accessor.
load64 props, [global_object, OBJECT_NAMED_PROPERTIES]

View file

@ -76,7 +76,8 @@ int main()
// PropertyLookupCache layout
outln("\n# PropertyLookupCache layout");
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRIES, PropertyLookupCache, entries);
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_DATA, PropertyLookupCache, m_data);
outln("const PROPERTY_LOOKUP_CACHE_POLYMORPHIC_DATA_TAG = {}", PropertyLookupCache::polymorphic_data_tag);
EMIT_SIZEOF(PROPERTY_LOOKUP_CACHE_SIZE, PropertyLookupCache);
// PropertyLookupCache::Entry layout
@ -89,15 +90,6 @@ int main()
EMIT_OFFSET(PROPERTY_LOOKUP_CACHE_ENTRY_PROTOTYPE_CHAIN_VALIDITY, PropertyLookupCache::Entry, prototype_chain_validity);
EMIT_SIZEOF(PROPERTY_LOOKUP_CACHE_ENTRY_SIZE, PropertyLookupCache::Entry);
// Composite offsets for entry[0] within a PropertyLookupCache
outln("\n# Entry[0] offsets within PropertyLookupCache");
auto plc_entries = offsetof(PropertyLookupCache, entries);
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_PROPERTY_OFFSET = {}", plc_entries + offsetof(PropertyLookupCache::Entry, property_offset));
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_DICTIONARY_GENERATION = {}", plc_entries + offsetof(PropertyLookupCache::Entry, shape_dictionary_generation));
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_SHAPE = {}", plc_entries + offsetof(PropertyLookupCache::Entry, shape));
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE = {}", plc_entries + offsetof(PropertyLookupCache::Entry, prototype));
outln("const PROPERTY_LOOKUP_CACHE_ENTRY0_PROTOTYPE_CHAIN_VALIDITY = {}", plc_entries + offsetof(PropertyLookupCache::Entry, prototype_chain_validity));
// ObjectPropertyIteratorCacheData layout
outln("\n# ObjectPropertyIteratorCacheData layout");
EMIT_OFFSET(OBJECT_PROPERTY_ITERATOR_CACHE_DATA_PROPERTIES, ObjectPropertyIteratorCacheData, m_properties);
@ -240,11 +232,15 @@ int main()
// GlobalVariableCache layout
outln("\n# GlobalVariableCache layout");
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_ENTRY, GlobalVariableCache, entry);
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_ENVIRONMENT_SERIAL, GlobalVariableCache, environment_serial_number);
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_ENVIRONMENT_BINDING_INDEX, GlobalVariableCache, environment_binding_index);
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_HAS_ENVIRONMENT_BINDING, GlobalVariableCache, has_environment_binding_index);
EMIT_OFFSET(GLOBAL_VARIABLE_CACHE_IN_MODULE_ENVIRONMENT, GlobalVariableCache, in_module_environment);
EMIT_SIZEOF(GLOBAL_VARIABLE_CACHE_SIZE, GlobalVariableCache);
outln("const GLOBAL_VARIABLE_CACHE_ENTRY_PROPERTY_OFFSET = {}", offsetof(GlobalVariableCache, entry) + offsetof(PropertyLookupCache::Entry, property_offset));
outln("const GLOBAL_VARIABLE_CACHE_ENTRY_DICTIONARY_GENERATION = {}", offsetof(GlobalVariableCache, entry) + offsetof(PropertyLookupCache::Entry, shape_dictionary_generation));
outln("const GLOBAL_VARIABLE_CACHE_ENTRY_SHAPE = {}", offsetof(GlobalVariableCache, entry) + offsetof(PropertyLookupCache::Entry, shape));
// Builtin enum values
outln("\n# Builtin enum values");

View file

@ -7,6 +7,7 @@
#include <AK/BinarySearch.h>
#include <AK/NumericLimits.h>
#include <AK/QuickSort.h>
#include <AK/StdLibExtras.h>
#include <LibGC/Heap.h>
#include <LibGC/HeapBlock.h>
#include <LibJS/Bytecode/BasicBlock.h>
@ -67,6 +68,152 @@ size_t InstructionStream::external_memory_size() const
});
}
static_assert(alignof(PropertyLookupCache::MonomorphicData) > PropertyLookupCache::polymorphic_data_tag);
static_assert(alignof(PropertyLookupCache::PolymorphicData) > PropertyLookupCache::polymorphic_data_tag);
static_assert(offsetof(PropertyLookupCache::MonomorphicData, entry) == 0);
static_assert(offsetof(PropertyLookupCache::PolymorphicData, entries) == 0);
PropertyLookupCache::PropertyLookupCache(PropertyLookupCache&& other)
: m_data(exchange(other.m_data, 0))
{
}
PropertyLookupCache& PropertyLookupCache::operator=(PropertyLookupCache&& other)
{
if (this != &other) {
clear();
m_data = exchange(other.m_data, 0);
}
return *this;
}
PropertyLookupCache::~PropertyLookupCache()
{
clear();
}
PropertyLookupCache::MonomorphicData* PropertyLookupCache::monomorphic_data()
{
if (!m_data || (m_data & polymorphic_data_tag))
return nullptr;
return reinterpret_cast<MonomorphicData*>(m_data);
}
PropertyLookupCache::MonomorphicData const* PropertyLookupCache::monomorphic_data() const
{
if (!m_data || (m_data & polymorphic_data_tag))
return nullptr;
return reinterpret_cast<MonomorphicData const*>(m_data);
}
PropertyLookupCache::PolymorphicData* PropertyLookupCache::polymorphic_data()
{
if (!(m_data & polymorphic_data_tag))
return nullptr;
return reinterpret_cast<PolymorphicData*>(m_data & ~polymorphic_data_tag);
}
PropertyLookupCache::PolymorphicData const* PropertyLookupCache::polymorphic_data() const
{
if (!(m_data & polymorphic_data_tag))
return nullptr;
return reinterpret_cast<PolymorphicData const*>(m_data & ~polymorphic_data_tag);
}
void PropertyLookupCache::set_monomorphic_data(MonomorphicData* data)
{
VERIFY(data);
VERIFY(!(reinterpret_cast<FlatPtr>(data) & polymorphic_data_tag));
m_data = reinterpret_cast<FlatPtr>(data);
}
void PropertyLookupCache::set_polymorphic_data(PolymorphicData* data)
{
VERIFY(data);
VERIFY(!(reinterpret_cast<FlatPtr>(data) & polymorphic_data_tag));
m_data = reinterpret_cast<FlatPtr>(data) | polymorphic_data_tag;
}
PropertyLookupCache::Entry* PropertyLookupCache::first_entry()
{
if (auto* data = monomorphic_data())
return &data->entry;
if (auto* data = polymorphic_data())
return &data->entries[0];
return nullptr;
}
PropertyLookupCache::Entry const* PropertyLookupCache::first_entry() const
{
if (auto* data = monomorphic_data())
return &data->entry;
if (auto* data = polymorphic_data())
return &data->entries[0];
return nullptr;
}
Span<PropertyLookupCache::Entry> PropertyLookupCache::entries()
{
if (auto* data = monomorphic_data())
return { &data->entry, 1 };
if (auto* data = polymorphic_data())
return data->entries.span();
return {};
}
ReadonlySpan<PropertyLookupCache::Entry> PropertyLookupCache::entries() const
{
if (auto* data = monomorphic_data())
return { &data->entry, 1 };
if (auto* data = polymorphic_data())
return data->entries.span();
return {};
}
size_t PropertyLookupCache::external_memory_size() const
{
if (monomorphic_data())
return sizeof(MonomorphicData);
if (polymorphic_data())
return sizeof(PolymorphicData);
return 0;
}
void PropertyLookupCache::clear()
{
if (auto* data = monomorphic_data()) {
delete data;
m_data = 0;
return;
}
if (auto* data = polymorphic_data()) {
delete data;
m_data = 0;
}
}
bool PropertyLookupCache::entries_have_same_cache_key(Entry const& a, Entry const& b)
{
if (a.type == Entry::Type::Empty || b.type == Entry::Type::Empty)
return false;
if (a.type != b.type)
return false;
switch (a.type) {
case Entry::Type::AddOwnProperty:
return a.from_shape == b.from_shape && a.shape == b.shape;
case Entry::Type::ChangeOwnProperty:
case Entry::Type::GetOwnProperty:
return a.shape == b.shape;
case Entry::Type::ChangePropertyInPrototypeChain:
case Entry::Type::GetPropertyInPrototypeChain:
return a.shape == b.shape && a.prototype == b.prototype;
case Entry::Type::Empty:
VERIFY_NOT_REACHED();
}
VERIFY_NOT_REACHED();
}
ObjectPropertyIteratorCacheData::ObjectPropertyIteratorCacheData(VM& vm, Vector<PropertyKey> properties, ObjectPropertyIteratorFastPath fast_path, u32 indexed_property_count, bool receiver_has_magical_length_property, GC::Ref<Shape> shape, GC::Ptr<PrototypeChainValidity> prototype_chain_validity)
: m_properties(move(properties))
, m_shape(shape)
@ -413,6 +560,8 @@ size_t Executable::external_memory_size() const
{
size_t size = bytecode.external_memory_size();
size = saturating_add_external_memory_size(size, vector_external_memory_size(property_lookup_caches));
for (auto const& cache : property_lookup_caches)
size = saturating_add_external_memory_size(size, cache.external_memory_size());
size = saturating_add_external_memory_size(size, vector_external_memory_size(global_variable_caches));
size = saturating_add_external_memory_size(size, vector_external_memory_size(template_object_caches));
size = saturating_add_external_memory_size(size, vector_external_memory_size(object_shape_caches));
@ -469,7 +618,7 @@ static void clear_cache_entry_if_dead(PropertyLookupCache::Entry& entry)
void StaticPropertyLookupCache::sweep_all()
{
for (auto* cache : static_property_lookup_caches()) {
for (auto& entry : cache->entries)
for (auto& entry : cache->entries())
clear_cache_entry_if_dead(entry);
}
}
@ -477,13 +626,11 @@ void StaticPropertyLookupCache::sweep_all()
void Executable::remove_dead_cells(Badge<GC::Heap>)
{
for (auto& cache : property_lookup_caches) {
for (auto& entry : cache.entries)
clear_cache_entry_if_dead(entry);
}
for (auto& cache : global_variable_caches) {
for (auto& entry : cache.entries)
for (auto& entry : cache.entries())
clear_cache_entry_if_dead(entry);
}
for (auto& cache : global_variable_caches)
clear_cache_entry_if_dead(cache.entry);
for (auto& cache : object_shape_caches) {
auto* shape = cache.shape.ptr();
if (shape && cell_is_dead(shape))

View file

@ -9,7 +9,9 @@
#include <AK/NonnullOwnPtr.h>
#include <AK/Optional.h>
#include <AK/OwnPtr.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <AK/Utf16FlyString.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
@ -55,7 +57,7 @@ private:
size_t m_size { 0 };
};
// Represents one polymorphic inline cache used for property lookups.
// Represents one tiered inline cache used for property lookups.
struct PropertyLookupCache {
static constexpr size_t max_number_of_shapes_to_remember = 4;
struct Entry {
@ -67,6 +69,7 @@ struct PropertyLookupCache {
ChangePropertyInPrototypeChain,
GetPropertyInPrototypeChain,
};
Type type { Type::Empty };
u32 property_offset { 0 };
u32 shape_dictionary_generation { 0 };
GC::RawPtr<Shape> from_shape;
@ -75,20 +78,82 @@ struct PropertyLookupCache {
GC::RawPtr<PrototypeChainValidity> prototype_chain_validity;
};
struct MonomorphicData {
Entry entry;
};
struct PolymorphicData {
AK::Array<Entry, max_number_of_shapes_to_remember> entries;
};
PropertyLookupCache() = default;
PropertyLookupCache(PropertyLookupCache const&) = delete;
PropertyLookupCache& operator=(PropertyLookupCache const&) = delete;
PropertyLookupCache(PropertyLookupCache&&);
PropertyLookupCache& operator=(PropertyLookupCache&&);
~PropertyLookupCache();
[[nodiscard]] Entry* first_entry();
[[nodiscard]] Entry const* first_entry() const;
[[nodiscard]] Span<Entry> entries();
[[nodiscard]] ReadonlySpan<Entry> entries() const;
[[nodiscard]] size_t external_memory_size() const;
void update(Entry::Type type, auto callback)
{
// First, move all entries one step back.
for (size_t i = entries.size() - 1; i >= 1; --i) {
types[i] = types[i - 1];
entries[i] = entries[i - 1];
Entry new_entry;
new_entry.type = type;
callback(new_entry);
if (!m_data) {
auto data = make<MonomorphicData>();
data->entry = new_entry;
set_monomorphic_data(data.leak_ptr());
return;
}
types[0] = type;
entries[0] = {};
callback(entries[0]);
if (auto* data = monomorphic_data()) {
if (entries_have_same_cache_key(data->entry, new_entry)) {
data->entry = new_entry;
return;
}
auto old_entry = data->entry;
auto new_data = make<PolymorphicData>();
new_data->entries[0] = new_entry;
new_data->entries[1] = old_entry;
clear();
set_polymorphic_data(new_data.leak_ptr());
return;
}
auto& entries = polymorphic_data()->entries;
size_t insertion_index = entries.size() - 1;
for (size_t i = 0; i < entries.size(); ++i) {
if (entries_have_same_cache_key(entries[i], new_entry)) {
insertion_index = i;
break;
}
}
for (size_t i = insertion_index; i > 0; --i)
entries[i] = entries[i - 1];
entries[0] = new_entry;
}
AK::Array<Entry::Type, max_number_of_shapes_to_remember> types;
AK::Array<Entry, max_number_of_shapes_to_remember> entries;
void clear();
static constexpr FlatPtr polymorphic_data_tag = 1;
FlatPtr m_data { 0 };
private:
[[nodiscard]] MonomorphicData* monomorphic_data();
[[nodiscard]] MonomorphicData const* monomorphic_data() const;
[[nodiscard]] PolymorphicData* polymorphic_data();
[[nodiscard]] PolymorphicData const* polymorphic_data() const;
void set_monomorphic_data(MonomorphicData*);
void set_polymorphic_data(PolymorphicData*);
static bool entries_have_same_cache_key(Entry const&, Entry const&);
};
// A PropertyLookupCache for use as a static local variable.
@ -98,7 +163,29 @@ struct StaticPropertyLookupCache : public PropertyLookupCache {
static void sweep_all();
};
struct GlobalVariableCache : public PropertyLookupCache {
struct GlobalVariableCache {
PropertyLookupCache::Entry* first_entry()
{
if (entry.type == PropertyLookupCache::Entry::Type::Empty)
return nullptr;
return &entry;
}
PropertyLookupCache::Entry const* first_entry() const
{
if (entry.type == PropertyLookupCache::Entry::Type::Empty)
return nullptr;
return &entry;
}
void update(PropertyLookupCache::Entry::Type type, auto callback)
{
entry = {};
entry.type = type;
callback(entry);
}
PropertyLookupCache::Entry entry;
u64 environment_serial_number { 0 };
u32 environment_binding_index { 0 };
bool has_environment_binding_index { false };

View file

@ -1046,8 +1046,9 @@ inline ThrowCompletionOr<Value> get_global(VM& vm, IdentifierTableIndex identifi
// OPTIMIZATION: For global var bindings, if the shape of the global object hasn't changed,
// we can use the cached property offset.
if (&shape == cache.entries[0].shape && (!shape.is_dictionary() || shape.dictionary_generation() == cache.entries[0].shape_dictionary_generation)) {
auto value = binding_object.get_direct(cache.entries[0].property_offset);
auto* entry = cache.first_entry();
if (entry && &shape == entry->shape && (!shape.is_dictionary() || shape.dictionary_generation() == entry->shape_dictionary_generation)) {
auto value = binding_object.get_direct(entry->property_offset);
if (value.is_accessor())
return TRY(call(vm, value.as_accessor().getter(), &binding_object));
return value;
@ -1096,12 +1097,14 @@ inline ThrowCompletionOr<Value> get_global(VM& vm, IdentifierTableIndex identifi
CacheableGetPropertyMetadata cacheable_metadata;
auto value = TRY(binding_object.internal_get(identifier, &binding_object, &cacheable_metadata));
if (cacheable_metadata.type == CacheableGetPropertyMetadata::Type::GetOwnProperty) {
cache.entries[0].shape = shape;
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
cache.update(PropertyLookupCache::Entry::Type::GetOwnProperty, [&](auto& entry) {
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
if (shape.is_dictionary()) {
cache.entries[0].shape_dictionary_generation = shape.dictionary_generation();
}
if (shape.is_dictionary()) {
entry.shape_dictionary_generation = shape.dictionary_generation();
}
});
}
return value;
}
@ -2396,12 +2399,13 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(VM& vm) const
if (cache.environment_serial_number == declarative_record.environment_serial_number()) {
// OPTIMIZATION: For global var bindings, if the shape of the global object hasn't changed,
// we can use the cached property offset.
if (&shape == cache.entries[0].shape && (!shape.is_dictionary() || shape.dictionary_generation() == cache.entries[0].shape_dictionary_generation)) {
auto value = binding_object.get_direct(cache.entries[0].property_offset);
auto* entry = cache.first_entry();
if (entry && &shape == entry->shape && (!shape.is_dictionary() || shape.dictionary_generation() == entry->shape_dictionary_generation)) {
auto value = binding_object.get_direct(entry->property_offset);
if (value.is_accessor())
TRY(call(vm, value.as_accessor().setter(), &binding_object, src));
else
binding_object.put_direct(cache.entries[0].property_offset, src);
binding_object.put_direct(entry->property_offset, src);
return {};
}
@ -2463,12 +2467,14 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(VM& vm) const
return vm.throw_completion<TypeError>(ErrorType::ObjectSetReturnedFalse);
}
if (cacheable_metadata.type == CacheableSetPropertyMetadata::Type::ChangeOwnProperty) {
cache.entries[0].shape = shape;
cache.entries[0].property_offset = cacheable_metadata.property_offset.value();
cache.update(PropertyLookupCache::Entry::Type::ChangeOwnProperty, [&](auto& entry) {
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
if (shape.is_dictionary()) {
cache.entries[0].shape_dictionary_generation = shape.dictionary_generation();
}
if (shape.is_dictionary()) {
entry.shape_dictionary_generation = shape.dictionary_generation();
}
});
}
return {};
}

View file

@ -89,7 +89,12 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
auto& shape = base_obj->shape();
for (auto& cache_entry : cache.entries) {
for (auto& cache_entry : cache.entries()) {
if (cache_entry.type != PropertyLookupCache::Entry::Type::GetOwnProperty
&& cache_entry.type != PropertyLookupCache::Entry::Type::GetPropertyInPrototypeChain) {
continue;
}
auto cached_prototype = cache_entry.prototype.ptr();
if (cached_prototype) {
// OPTIMIZATION: If the prototype chain hasn't been mutated in a way that would invalidate the cache, we can use it.
@ -145,31 +150,26 @@ ALWAYS_INLINE ThrowCompletionOr<Value> get_by_id(VM& vm, GetBaseIdentifier get_b
// 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();
cache.update(PropertyLookupCache::Entry::Type::GetOwnProperty, [&](auto& entry) {
entry.shape = shape;
entry.property_offset = cacheable_metadata.property_offset.value();
if (shape.is_dictionary()) {
entry.shape_dictionary_generation = shape.dictionary_generation();
}
if (shape.is_dictionary()) {
entry.shape_dictionary_generation = shape.dictionary_generation();
}
});
} 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 = const_cast<Object*>(cacheable_metadata.prototype.ptr());
entry.prototype_chain_validity = prototype_chain_validity;
cache.update(PropertyLookupCache::Entry::Type::GetPropertyInPrototypeChain, [&](auto& entry) {
entry.shape = &base_obj->shape();
entry.property_offset = cacheable_metadata.property_offset.value();
entry.prototype = const_cast<Object*>(cacheable_metadata.prototype.ptr());
entry.prototype_chain_validity = prototype_chain_validity;
if (shape.is_dictionary()) {
entry.shape_dictionary_generation = shape.dictionary_generation();
}
if (shape.is_dictionary()) {
entry.shape_dictionary_generation = shape.dictionary_generation();
}
});
}
}
@ -233,12 +233,11 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
auto this_value_object = MUST(this_value.to_object(vm));
auto& from_shape = this_value_object->shape();
if (caches) [[likely]] {
for (size_t i = 0; i < caches->entries.size(); ++i) {
switch (caches->types[i]) {
for (auto& cache : caches->entries()) {
switch (cache.type) {
case PropertyLookupCache::Entry::Type::Empty:
break;
case PropertyLookupCache::Entry::Type::ChangePropertyInPrototypeChain: {
auto& cache = caches->entries[i];
auto cached_prototype = cache.prototype.ptr();
if (!cached_prototype) [[unlikely]]
break;
@ -270,7 +269,6 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
break;
}
case PropertyLookupCache::Entry::Type::ChangeOwnProperty: {
auto& cache = caches->entries[i];
auto cached_shape = cache.shape.ptr();
if (cached_shape != &object->shape()) [[unlikely]]
break;
@ -289,7 +287,6 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
return {};
}
case PropertyLookupCache::Entry::Type::AddOwnProperty: {
auto& cache = caches->entries[i];
// OPTIMIZATION: If the object's shape is the same as the one cached before adding the new property, we can
// reuse the resulting shape from the cache.
if (cache.from_shape != &object->shape()) [[unlikely]]
@ -315,8 +312,9 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
object->put_direct(cache.property_offset, value);
return {};
}
default:
VERIFY_NOT_REACHED();
case PropertyLookupCache::Entry::Type::GetOwnProperty:
case PropertyLookupCache::Entry::Type::GetPropertyInPrototypeChain:
break;
}
}
}
@ -385,9 +383,8 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
}
case PutKind::Own: {
if (caches) [[likely]] {
for (size_t i = 0; i < caches->entries.size(); ++i) {
if (caches->types[i] == PropertyLookupCache::Entry::Type::AddOwnProperty) {
auto& cache = caches->entries[i];
for (auto& cache : caches->entries()) {
if (cache.type == PropertyLookupCache::Entry::Type::AddOwnProperty) {
if (cache.from_shape != &object->shape()) [[unlikely]]
continue;
auto cached_shape = cache.shape.ptr();