| // Copyright 2025 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef V8_OBJECTS_NUMBER_STRING_CACHE_INL_H_ |
| #define V8_OBJECTS_NUMBER_STRING_CACHE_INL_H_ |
| |
| #include "src/objects/number-string-cache.h" |
| // Include the non-inl header before the rest of the headers. |
| |
| #include "src/base/bits.h" |
| #include "src/common/globals.h" |
| #include "src/handles/handles-inl.h" |
| #include "src/heap/factory-inl.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/objects/fixed-array-inl.h" |
| |
| // Has to be the last include (doesn't have include guards): |
| #include "src/objects/object-macros.h" |
| |
| namespace v8::internal { |
| |
| uint32_t SmiStringCache::capacity() const { |
| return Super::capacity().value() / kEntrySize; |
| } |
| |
| InternalIndex SmiStringCache::GetEntryFor(Tagged<Smi> number) const { |
| uint32_t hash = static_cast<uint32_t>(number.value()); |
| uint32_t mask = capacity() - 1; |
| return InternalIndex{hash & mask}; |
| } |
| |
| // static |
| InternalIndex SmiStringCache::GetEntryFor(Isolate* isolate, |
| Tagged<Smi> number) { |
| return isolate->factory()->smi_string_cache()->GetEntryFor(number); |
| } |
| |
| // static |
| Handle<Object> SmiStringCache::Get(Isolate* isolate, InternalIndex entry, |
| Tagged<Smi> number) { |
| DisallowGarbageCollection no_gc; |
| Factory* factory = isolate->factory(); |
| Tagged<SmiStringCache> cache = *factory->smi_string_cache(); |
| uint32_t entry_index = entry.as_uint32() * kEntrySize; |
| |
| Tagged<Object> key = cache->get(entry_index + kEntryKeyIndex); |
| const bool match = (key == number); |
| |
| if (V8_UNLIKELY(v8_flags.native_code_counters)) { |
| Counters* counters = isolate->counters(); |
| counters->number_string_cache_smi_probes()->Increment(); |
| if (!match) { |
| counters->number_string_cache_smi_misses()->Increment(); |
| } |
| } |
| |
| if (match) { |
| auto string = Cast<String>(cache->get(entry_index + kEntryValueIndex)); |
| return handle(string, isolate); |
| } |
| return factory->undefined_value(); |
| } |
| |
| // static |
| void SmiStringCache::Set(Isolate* isolate, InternalIndex entry, |
| Tagged<Smi> number, DirectHandle<String> string) { |
| // Smi::zero() must never be put into this cache, one must read the value |
| // from preallocated_number_string_table instead. |
| DCHECK_NE(number, kEmptySentinel); |
| |
| DirectHandle<SmiStringCache> cache = isolate->factory()->smi_string_cache(); |
| DCHECK_EQ(entry, cache->GetEntryFor(number)); |
| uint32_t entry_index = entry.as_uint32() * kEntrySize; |
| |
| // Switch to full-size mode if the entry is already occupied. |
| if (cache->capacity() == kInitialSize && |
| cache->get(entry_index + kEntryKeyIndex) != kEmptySentinel && |
| !isolate->MemorySaverModeEnabled()) { |
| // Allocate full-size cache, recompute the entry and proceed with adding |
| // the entry. |
| uint32_t full_size = |
| base::bits::RoundUpToPowerOfTwo32(v8_flags.smi_string_cache_size); |
| CHECK_LT(kInitialSize, full_size); |
| CHECK_LE(full_size, kMaxCapacity); |
| // TODO(ishell): consider copying entries from previous table. |
| cache = SmiStringCache::New(isolate, full_size); |
| isolate->heap()->SetSmiStringCache(*cache); |
| |
| entry = cache->GetEntryFor(number); |
| entry_index = entry.as_uint32() * kEntrySize; |
| } |
| DisallowGarbageCollection no_gc; |
| cache->set(entry_index + kEntryKeyIndex, number); |
| cache->set(entry_index + kEntryValueIndex, *string); |
| } |
| |
| // static |
| template <class IsolateT> |
| DirectHandle<SmiStringCache> SmiStringCache::New(IsolateT* isolate, |
| uint32_t capacity) { |
| std::optional<DisallowGarbageCollection> no_gc; |
| DirectHandle<SmiStringCache> result = Cast<SmiStringCache>( |
| Allocate(isolate, capacity * kEntrySize, &no_gc, AllocationType::kOld)); |
| result->Clear(); |
| return result; |
| } |
| |
| // Clears all entried in the table. |
| void SmiStringCache::Clear() { |
| Relaxed_MemsetTagged(RawFieldOfFirstElement(), kEmptySentinel, |
| Super::capacity().value()); |
| } |
| |
| // static |
| InternalIndex DoubleStringCache::GetEntryFor(uint64_t number_bits, |
| uint32_t capacity) { |
| uint32_t hash = static_cast<uint32_t>(number_bits) ^ |
| static_cast<uint32_t>(number_bits >> 32); |
| uint32_t mask = capacity - 1; |
| return InternalIndex{hash & mask}; |
| } |
| |
| InternalIndex DoubleStringCache::GetEntryFor(uint64_t number_bits) const { |
| return GetEntryFor(number_bits, capacity()); |
| } |
| |
| // static |
| InternalIndex DoubleStringCache::GetEntryFor(Isolate* isolate, |
| uint64_t number_bits) { |
| return isolate->factory()->double_string_cache()->GetEntryFor(number_bits); |
| } |
| |
| // static |
| Handle<Object> DoubleStringCache::Get(Isolate* isolate, |
| InternalIndex entry_index, |
| uint64_t number_bits) { |
| DisallowGarbageCollection no_gc; |
| Factory* factory = isolate->factory(); |
| DirectHandle<DoubleStringCache> cache = factory->double_string_cache(); |
| auto& entry = cache->entries()[entry_index.as_uint32()]; |
| |
| const bool match = entry.key_.value_as_bits() == number_bits; |
| |
| if (V8_UNLIKELY(v8_flags.native_code_counters)) { |
| Counters* counters = isolate->counters(); |
| counters->number_string_cache_double_probes()->Increment(); |
| if (!match) { |
| counters->number_string_cache_double_misses()->Increment(); |
| } |
| } |
| |
| if (match) { |
| auto string = Cast<String>(entry.value_.load()); |
| return handle(string, isolate); |
| } |
| return factory->undefined_value(); |
| } |
| |
| // static |
| void DoubleStringCache::Set(Isolate* isolate, InternalIndex entry_index, |
| uint64_t number_bits, DirectHandle<String> string) { |
| DirectHandle<DoubleStringCache> cache = |
| isolate->factory()->double_string_cache(); |
| DCHECK_EQ(entry_index, cache->GetEntryFor(number_bits)); |
| |
| auto* entry = &cache->entries()[entry_index.as_uint32()]; |
| |
| // Switch to full-size mode if the entry is already occupied. |
| if (cache->capacity() == kInitialSize && |
| entry->value_.load() != kEmptySentinel && |
| !isolate->MemorySaverModeEnabled()) { |
| // Allocate full-size cache, recompute the entry and proceed with adding |
| // the entry. |
| uint32_t full_size = |
| base::bits::RoundUpToPowerOfTwo32(v8_flags.double_string_cache_size); |
| CHECK_LT(kInitialSize, full_size); |
| CHECK_LE(full_size, kMaxCapacity); |
| cache = DoubleStringCache::New(isolate, full_size); |
| isolate->heap()->SetDoubleStringCache(*cache); |
| |
| entry_index = cache->GetEntryFor(number_bits); |
| entry = &cache->entries()[entry_index.as_uint32()]; |
| } |
| DisallowGarbageCollection no_gc; |
| entry->key_.set_value_as_bits(number_bits); |
| entry->value_.store(&**cache, *string); |
| } |
| |
| // static |
| template <class IsolateT> |
| DirectHandle<DoubleStringCache> DoubleStringCache::New(IsolateT* isolate, |
| int capacity) { |
| DCHECK_GE(capacity, kInitialSize); |
| DCHECK_LE(capacity, kMaxCapacity); |
| |
| Tagged<DoubleStringCache> xs = |
| UncheckedCast<DoubleStringCache>(isolate->factory()->AllocateRawArray( |
| SizeFor(capacity), AllocationType::kOld)); |
| |
| ReadOnlyRoots roots{isolate}; |
| xs->set_map_after_allocation(isolate, roots.double_string_cache_map(), |
| SKIP_WRITE_BARRIER); |
| xs->capacity_ = capacity; |
| #if TAGGED_SIZE_8_BYTES |
| xs->optional_padding_ = 0; |
| #endif |
| xs->Clear(); |
| |
| return handle(xs, isolate); |
| } |
| |
| void DoubleStringCache::Clear() { |
| static_assert(sizeof(Entry) % kTaggedSize == 0); |
| auto* data = reinterpret_cast<Tagged_t*>(begin()); |
| Relaxed_MemsetTagged(data, kEmptySentinel, |
| capacity() * (sizeof(Entry) / kTaggedSize)); |
| } |
| |
| } // namespace v8::internal |
| |
| #include "src/objects/object-macros-undef.h" |
| |
| #endif // V8_OBJECTS_NUMBER_STRING_CACHE_INL_H_ |