// Copyright 2022 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. #include "src/objects/string-forwarding-table.h" #include "src/base/atomicops.h" #include "src/common/globals.h" #include "src/objects/objects-inl.h" #include "src/objects/slots-inl.h" #include "src/objects/slots.h" #include "src/objects/string-forwarding-table-inl.h" #include "src/utils/allocation.h" namespace v8 { namespace internal { StringForwardingTable::Block::Block(int capacity) : capacity_(capacity) { static_assert(unused_element().ptr() == 0); static_assert(kNullAddress == 0); static_assert(sizeof(Record) % sizeof(Address) == 0); static_assert(offsetof(Record, original_string_) == 0); constexpr int kRecordPointerSize = sizeof(Record) / sizeof(Address); MemsetPointer(reinterpret_cast(&elements_[0]), 0, capacity_ * kRecordPointerSize); } void* StringForwardingTable::Block::operator new(size_t size, int capacity) { // Make sure the size given is the size of the Block structure. DCHECK_EQ(size, sizeof(StringForwardingTable::Block)); // Make sure the Record class is trivial and has standard layout. static_assert(std::is_trivial_v); static_assert(std::is_standard_layout_v); // Make sure that the elements_ array is at the end of Block, with no padding, // so that subsequent elements can be accessed as offsets from elements_. static_assert(offsetof(StringForwardingTable::Block, elements_) == sizeof(StringForwardingTable::Block) - sizeof(Record)); // Make sure that elements_ is aligned when StringTable::Block is aligned. static_assert((alignof(StringForwardingTable::Block) + offsetof(StringForwardingTable::Block, elements_)) % kTaggedSize == 0); const size_t elements_size = capacity * sizeof(Record); // Storage for the first element is already supplied by elements_, so subtract // sizeof(Record). const size_t new_size = size + elements_size - sizeof(Record); DCHECK_LE(alignof(StringForwardingTable::Block), kSystemPointerSize); return AlignedAllocWithRetry(new_size, kSystemPointerSize); } void StringForwardingTable::Block::operator delete(void* block) { AlignedFree(block); } std::unique_ptr StringForwardingTable::Block::New( int capacity) { return std::unique_ptr(new (capacity) Block(capacity)); } void StringForwardingTable::Block::UpdateAfterEvacuation( PtrComprCageBase cage_base) { UpdateAfterEvacuation(cage_base, capacity_); } void StringForwardingTable::Block::UpdateAfterEvacuation( PtrComprCageBase cage_base, int up_to_index) { // This is only used for Scavenger. DCHECK(!v8_flags.minor_mc); DCHECK(v8_flags.always_use_string_forwarding_table); for (int index = 0; index < up_to_index; ++index) { Object original = record(index)->OriginalStringObject(cage_base); if (!original.IsHeapObject()) continue; HeapObject object = HeapObject::cast(original); if (Heap::InFromPage(object)) { DCHECK(!object.InSharedWritableHeap()); MapWord map_word = object.map_word(kRelaxedLoad); if (map_word.IsForwardingAddress()) { HeapObject forwarded_object = map_word.ToForwardingAddress(); record(index)->set_original_string(forwarded_object); } else { record(index)->set_original_string(deleted_element()); } } else { DCHECK(!object.map_word(kRelaxedLoad).IsForwardingAddress()); } } } StringForwardingTable::BlockVector::BlockVector(size_t capacity) : allocator_(Allocator()), capacity_(capacity), size_(0) { begin_ = allocator_.allocate(capacity); } StringForwardingTable::BlockVector::~BlockVector() { allocator_.deallocate(begin_, capacity()); } // static std::unique_ptr StringForwardingTable::BlockVector::Grow( StringForwardingTable::BlockVector* data, size_t capacity, const base::Mutex& mutex) { mutex.AssertHeld(); std::unique_ptr new_data = std::make_unique(capacity); // Copy pointers to blocks from the old to the new vector. for (size_t i = 0; i < data->size(); i++) { new_data->begin_[i] = data->LoadBlock(i); } new_data->size_ = data->size(); return new_data; } StringForwardingTable::StringForwardingTable(Isolate* isolate) : isolate_(isolate), next_free_index_(0) { InitializeBlockVector(); } StringForwardingTable::~StringForwardingTable() { BlockVector* blocks = blocks_.load(std::memory_order_relaxed); for (uint32_t block_index = 0; block_index < blocks->size(); block_index++) { delete blocks->LoadBlock(block_index); } } void StringForwardingTable::InitializeBlockVector() { BlockVector* blocks = block_vector_storage_ .emplace_back(std::make_unique( kInitialBlockVectorCapacity)) .get(); blocks->AddBlock(Block::New(kInitialBlockSize)); blocks_.store(blocks, std::memory_order_relaxed); } StringForwardingTable::BlockVector* StringForwardingTable::EnsureCapacity( uint32_t block_index) { BlockVector* blocks = blocks_.load(std::memory_order_acquire); if (V8_UNLIKELY(block_index >= blocks->size())) { base::MutexGuard table_grow_guard(&grow_mutex_); // Reload the vector, as another thread could have grown it. blocks = blocks_.load(std::memory_order_relaxed); // Check again if we need to grow under lock. if (block_index >= blocks->size()) { // Grow the vector if the block to insert is greater than the vectors // capacity. if (block_index >= blocks->capacity()) { std::unique_ptr new_blocks = BlockVector::Grow(blocks, blocks->capacity() * 2, grow_mutex_); block_vector_storage_.push_back(std::move(new_blocks)); blocks = block_vector_storage_.back().get(); blocks_.store(blocks, std::memory_order_release); } const uint32_t capacity = CapacityForBlock(block_index); std::unique_ptr new_block = Block::New(capacity); blocks->AddBlock(std::move(new_block)); } } return blocks; } int StringForwardingTable::AddForwardString(String string, String forward_to) { DCHECK_IMPLIES(!v8_flags.always_use_string_forwarding_table, string.InSharedHeap()); DCHECK_IMPLIES(!v8_flags.always_use_string_forwarding_table, forward_to.InSharedHeap()); int index = next_free_index_++; uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); BlockVector* blocks = EnsureCapacity(block_index); Block* block = blocks->LoadBlock(block_index, kAcquireLoad); block->record(index_in_block)->SetInternalized(string, forward_to); return index; } void StringForwardingTable::UpdateForwardString(int index, String forward_to) { CHECK_LT(index, size()); uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); Block* block = blocks_.load(std::memory_order_acquire) ->LoadBlock(block_index, kAcquireLoad); block->record(index_in_block)->set_forward_string(forward_to); } template int StringForwardingTable::AddExternalResourceAndHash(String string, T* resource, uint32_t raw_hash) { constexpr bool is_one_byte = std::is_base_of_v; DCHECK_IMPLIES(!FLAG_always_use_string_forwarding_table, string.InSharedHeap()); int index = next_free_index_++; uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); BlockVector* blocks = EnsureCapacity(block_index); Block* block = blocks->LoadBlock(block_index, kAcquireLoad); block->record(index_in_block) ->SetExternal(string, resource, is_one_byte, raw_hash); return index; } template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) int StringForwardingTable:: AddExternalResourceAndHash(String string, v8::String::ExternalOneByteStringResource*, uint32_t raw_hash); template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) int StringForwardingTable:: AddExternalResourceAndHash(String string, v8::String::ExternalStringResource*, uint32_t raw_hash); template bool StringForwardingTable::TryUpdateExternalResource(int index, T* resource) { constexpr bool is_one_byte = std::is_base_of_v; CHECK_LT(index, size()); uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); Block* block = blocks_.load(std::memory_order_acquire) ->LoadBlock(block_index, kAcquireLoad); return block->record(index_in_block) ->TryUpdateExternalResource(resource, is_one_byte); } template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) bool StringForwardingTable:: TryUpdateExternalResource( int index, v8::String::ExternalOneByteStringResource* resource); template EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) bool StringForwardingTable:: TryUpdateExternalResource(int index, v8::String::ExternalStringResource* resource); String StringForwardingTable::GetForwardString(PtrComprCageBase cage_base, int index) const { CHECK_LT(index, size()); uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); Block* block = blocks_.load(std::memory_order_acquire) ->LoadBlock(block_index, kAcquireLoad); return block->record(index_in_block)->forward_string(cage_base); } // static Address StringForwardingTable::GetForwardStringAddress(Isolate* isolate, int index) { return isolate->string_forwarding_table() ->GetForwardString(isolate, index) .ptr(); } uint32_t StringForwardingTable::GetRawHash(PtrComprCageBase cage_base, int index) const { CHECK_LT(index, size()); uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); Block* block = blocks_.load(std::memory_order_acquire) ->LoadBlock(block_index, kAcquireLoad); return block->record(index_in_block)->raw_hash(cage_base); } // static uint32_t StringForwardingTable::GetRawHashStatic(Isolate* isolate, int index) { return isolate->string_forwarding_table()->GetRawHash(isolate, index); } v8::String::ExternalStringResourceBase* StringForwardingTable::GetExternalResource(int index, bool* is_one_byte) const { CHECK_LT(index, size()); uint32_t index_in_block; const uint32_t block_index = BlockForIndex(index, &index_in_block); Block* block = blocks_.load(std::memory_order_acquire) ->LoadBlock(block_index, kAcquireLoad); return block->record(index_in_block)->external_resource(is_one_byte); } void StringForwardingTable::TearDown() { IterateElements([](Record* record) { record->DisposeExternalResource(); }); Reset(); } void StringForwardingTable::Reset() { isolate_->heap()->safepoint()->AssertActive(); DCHECK_NE(isolate_->heap()->gc_state(), Heap::NOT_IN_GC); BlockVector* blocks = blocks_.load(std::memory_order_relaxed); for (uint32_t block_index = 0; block_index < blocks->size(); ++block_index) { delete blocks->LoadBlock(block_index); } block_vector_storage_.clear(); InitializeBlockVector(); next_free_index_ = 0; } void StringForwardingTable::UpdateAfterEvacuation() { DCHECK(v8_flags.always_use_string_forwarding_table); if (empty()) return; BlockVector* blocks = blocks_.load(std::memory_order_relaxed); const unsigned int last_block_index = static_cast(blocks->size() - 1); for (unsigned int block_index = 0; block_index < last_block_index; ++block_index) { Block* block = blocks->LoadBlock(block_index, kAcquireLoad); block->UpdateAfterEvacuation(isolate_); } // Handle last block separately, as it is not filled to capacity. const int max_index = IndexInBlock(size() - 1, last_block_index) + 1; blocks->LoadBlock(last_block_index, kAcquireLoad) ->UpdateAfterEvacuation(isolate_, max_index); } } // namespace internal } // namespace v8