// 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