#include "src/objects/string-table.h"
#include <atomic>
#include "src/base/atomicops.h"
#include "src/base/macros.h"
#include "src/common/assert-scope.h"
#include "src/common/globals.h"
#include "src/common/ptr-compr-inl.h"
#include "src/execution/isolate-utils-inl.h"
#include "src/heap/heap-layout-inl.h"
#include "src/heap/safepoint.h"
#include "src/objects/internal-index.h"
#include "src/objects/object-list-macros.h"
#include "src/objects/off-heap-hash-table-inl.h"
#include "src/objects/slots-inl.h"
#include "src/objects/slots.h"
#include "src/objects/string-inl.h"
#include "src/objects/string-table-inl.h"
#include "src/snapshot/deserializer.h"
#include "src/utils/allocation.h"
#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
class StringTable::OffHeapStringHashSet
: public OffHeapHashTableBase<OffHeapStringHashSet> {
public:
static constexpr int kEntrySize = 1;
static constexpr int kMaxEmptyFactor = 4;
static constexpr int kMinCapacity = 2048;
explicit OffHeapStringHashSet(int capacity)
: OffHeapHashTableBase<OffHeapStringHashSet>(capacity) {}
static uint32_t Hash(PtrComprCageBase, Tagged<Object> key) {
return Cast<String>(key)->hash();
}
template <typename IsolateT, typename StringTableKey>
static bool KeyIsMatch(IsolateT* isolate, StringTableKey* key,
Tagged<Object> obj) {
auto string = Cast<String>(obj);
if (string->hash() != key->hash()) return false;
if (string->length() != key->length()) return false;
return key->IsMatch(isolate, string);
}
Tagged<Object> GetKey(PtrComprCageBase cage_base, InternalIndex index) const {
return slot(index).Acquire_Load(cage_base);
}
void SetKey(InternalIndex index, Tagged<Object> key) {
DCHECK(IsString(key));
slot(index).Release_Store(key);
}
void Set(InternalIndex index, Tagged<String> key) { SetKey(index, key); }
void CopyEntryExcludingKeyInto(PtrComprCageBase, InternalIndex,
OffHeapStringHashSet*, InternalIndex) {
}
private:
friend class StringTable::Data;
};
class StringTable::Data {
public:
static std::unique_ptr<Data> New(int capacity);
static std::unique_ptr<Data> Resize(PtrComprCageBase cage_base,
std::unique_ptr<Data> data, int capacity);
void* operator new(size_t size, int capacity);
void* operator new(size_t size) = delete;
void operator delete(void* description);
OffHeapStringHashSet& table() { return table_; }
const OffHeapStringHashSet& table() const { return table_; }
template <typename Char>
static Address TryStringToIndexOrLookupExisting(Isolate* isolate,
Tagged<String> string,
Tagged<String> source,
size_t start);
void IterateElements(RootVisitor* visitor) {
table_.IterateElements(Root::kStringTable, visitor);
}
Data* PreviousData() { return previous_data_.get(); }
void DropPreviousData() { previous_data_.reset(); }
void Print(PtrComprCageBase cage_base) const;
size_t GetCurrentMemoryUsage() const;
private:
explicit Data(int capacity) : table_(capacity) {}
std::unique_ptr<Data> previous_data_;
OffHeapStringHashSet table_;
};
void* StringTable::Data::operator new(size_t size, int capacity) {
DCHECK_EQ(size, sizeof(StringTable::Data));
return OffHeapStringHashSet::Allocate<Data, offsetof(Data, table_.elements_)>(
capacity);
}
void StringTable::Data::operator delete(void* table) {
OffHeapStringHashSet::Free(table);
}
size_t StringTable::Data::GetCurrentMemoryUsage() const {
size_t usage = sizeof(*this) + table_.GetSizeExcludingHeader();
if (previous_data_) {
usage += previous_data_->GetCurrentMemoryUsage();
}
return usage;
}
std::unique_ptr<StringTable::Data> StringTable::Data::New(int capacity) {
return std::unique_ptr<Data>(new (capacity) Data(capacity));
}
std::unique_ptr<StringTable::Data> StringTable::Data::Resize(
PtrComprCageBase cage_base, std::unique_ptr<Data> data, int capacity) {
std::unique_ptr<Data> new_data(new (capacity) Data(capacity));
data->table_.RehashInto(cage_base, &new_data->table_);
new_data->previous_data_ = std::move(data);
return new_data;
}
void StringTable::Data::Print(PtrComprCageBase cage_base) const {
OFStream os(stdout);
os << "StringTable {" << std::endl;
for (InternalIndex i : InternalIndex::Range(table_.capacity())) {
os << " " << i.as_uint32() << ": " << Brief(table_.GetKey(cage_base, i))
<< std::endl;
}
os << "}" << std::endl;
}
StringTable::StringTable(Isolate* isolate)
: data_(Data::New(OffHeapStringHashSet::kMinCapacity).release()),
isolate_(isolate) {
DCHECK_EQ(empty_element(), OffHeapStringHashSet::empty_element());
DCHECK_EQ(deleted_element(), OffHeapStringHashSet::deleted_element());
}
StringTable::~StringTable() { delete data_; }
int StringTable::Capacity() const {
return data_.load(std::memory_order_acquire)->table().capacity();
}
int StringTable::NumberOfElements() const {
{
base::MutexGuard table_write_guard(&write_mutex_);
return data_.load(std::memory_order_relaxed)->table().number_of_elements();
}
}
class InternalizedStringKey final : public StringTableKey {
public:
explicit InternalizedStringKey(DirectHandle<String> string, uint32_t hash)
: StringTableKey(hash, string->length()), string_(string) {
DCHECK_NE(0, length());
DCHECK_IMPLIES(!v8_flags.shared_string_table,
!IsInternalizedString(*string));
DCHECK(string->IsFlat());
DCHECK(String::IsHashFieldComputed(hash));
}
bool IsMatch(Isolate* isolate, Tagged<String> string) {
DCHECK(!SharedStringAccessGuardIfNeeded::IsNeeded(string));
return string_->SlowEqualsNonThinSameLength(length(), string);
}
void PrepareForInsertion(Isolate* isolate) {
StringTransitionStrategy strategy =
isolate->factory()->ComputeInternalizationStrategyForString(
string_, &maybe_internalized_map_);
switch (strategy) {
case StringTransitionStrategy::kCopy:
break;
case StringTransitionStrategy::kInPlace:
return;
case StringTransitionStrategy::kAlreadyTransitioned:
DCHECK(v8_flags.shared_string_table);
internalized_string_ = string_;
return;
}
StringShape shape(*string_);
const bool can_move_resource =
!v8_flags.shared_string_table && !shape.IsUncachedExternal();
if (can_move_resource && shape.IsExternalOneByte()) {
DCHECK(!shape.IsShared());
internalized_string_ =
isolate->factory()->InternalizeExternalString<ExternalOneByteString>(
string_);
} else if (can_move_resource && shape.IsExternalTwoByte()) {
DCHECK(!shape.IsShared());
internalized_string_ =
isolate->factory()->InternalizeExternalString<ExternalTwoByteString>(
string_);
} else {
internalized_string_ = isolate->factory()->NewInternalizedStringImpl(
string_, length(), raw_hash_field());
}
}
DirectHandle<String> GetHandleForInsertion(Isolate* isolate) {
DirectHandle<Map> internalized_map;
if (maybe_internalized_map_.ToHandle(&internalized_map)) {
string_->set_map_safe_transition_no_write_barrier(isolate,
*internalized_map);
DCHECK(IsInternalizedString(*string_));
return string_;
}
return internalized_string_.ToHandleChecked();
}
bool IsThinString() override { return Is<ThinString>(*string_); }
Tagged<String> UnwrapThinString() override {
return Cast<ThinString>(*string_)->actual();
}
private:
DirectHandle<String> string_;
MaybeDirectHandle<String> internalized_string_;
MaybeDirectHandle<Map> maybe_internalized_map_;
};
namespace {
void SetInternalizedReference(Isolate* isolate, Tagged<String> string,
Tagged<String> internalized) {
DCHECK(!IsThinString(string));
DCHECK(!IsInternalizedString(string));
DCHECK(IsInternalizedString(internalized));
DCHECK(!internalized->HasInternalizedForwardingIndex(kAcquireLoad));
if (string->IsShared() || v8_flags.always_use_string_forwarding_table) {
if (!v8_flags.shared_string_table) {
return;
}
uint32_t field = string->raw_hash_field(kAcquireLoad);
if (Name::IsIntegerIndex(field)) return;
if (Name::IsInternalizedForwardingIndex(field)) return;
if (Name::IsForwardingIndex(field)) {
const int forwarding_index =
Name::ForwardingIndexValueBits::decode(field);
isolate->string_forwarding_table()->UpdateForwardString(forwarding_index,
internalized);
field = Name::IsInternalizedForwardingIndexBit::update(field, true);
string->set_raw_hash_field(field, kReleaseStore);
} else {
const int forwarding_index =
isolate->string_forwarding_table()->AddForwardString(string,
internalized);
string->set_raw_hash_field(
String::CreateInternalizedForwardingIndex(forwarding_index),
kReleaseStore);
}
} else {
DCHECK(!string->HasForwardingIndex(kAcquireLoad));
string->MakeThin(isolate, internalized);
}
}
}
DirectHandle<String> StringTable::LookupString(Isolate* isolate,
DirectHandle<String> string) {
DirectHandle<String> result =
String::Flatten(isolate, indirect_handle(string, isolate));
if (!IsInternalizedString(*result)) {
uint32_t raw_hash_field = result->raw_hash_field(kAcquireLoad);
if (String::IsInternalizedForwardingIndex(raw_hash_field)) {
const int index =
String::ForwardingIndexValueBits::decode(raw_hash_field);
result = direct_handle(
isolate->string_forwarding_table()->GetForwardString(isolate, index),
isolate);
} else {
if (!Name::IsHashFieldComputed(raw_hash_field)) {
raw_hash_field = result->EnsureRawHash();
}
InternalizedStringKey key(result, raw_hash_field);
result = LookupKey(isolate, &key);
}
}
if (*string != *result && !IsThinString(*string)) {
SetInternalizedReference(isolate, *string, *result);
}
return result;
}
template <typename StringTableKey, typename IsolateT>
DirectHandle<String> StringTable::LookupKey(IsolateT* isolate,
StringTableKey* key) {
Data* const current_data = data_.load(std::memory_order_acquire);
OffHeapStringHashSet& current_table = current_data->table();
InternalIndex entry = current_table.FindEntry(isolate, key, key->hash());
if (entry.is_found()) {
DirectHandle<String> result(
Cast<String>(current_table.GetKey(isolate, entry)), isolate);
DCHECK_IMPLIES(v8_flags.shared_string_table,
HeapLayout::InAnySharedSpace(*result));
return result;
}
key->PrepareForInsertion(isolate);
{
base::MutexGuard table_write_guard(&write_mutex_);
Data* data = EnsureCapacity(isolate, 1);
OffHeapStringHashSet& table = data->table();
DisallowGarbageCollection no_gc;
if (key->IsThinString()) {
return DirectHandle<String>(key->UnwrapThinString(), isolate);
}
entry = table.FindEntryOrInsertionEntry(isolate, key, key->hash());
Tagged<Object> element = table.GetKey(isolate, entry);
if (element == OffHeapStringHashSet::empty_element()) {
DirectHandle<String> new_string = key->GetHandleForInsertion(isolate_);
DCHECK_IMPLIES(v8_flags.shared_string_table, new_string->IsShared());
table.AddAt(isolate, entry, *new_string);
return new_string;
} else if (element == OffHeapStringHashSet::deleted_element()) {
DirectHandle<String> new_string = key->GetHandleForInsertion(isolate_);
DCHECK_IMPLIES(v8_flags.shared_string_table, new_string->IsShared());
table.OverwriteDeletedAt(isolate, entry, *new_string);
return new_string;
} else {
return direct_handle(Cast<String>(element), isolate);
}
}
}
template DirectHandle<String> StringTable::LookupKey(Isolate* isolate,
OneByteStringKey* key);
template DirectHandle<String> StringTable::LookupKey(Isolate* isolate,
TwoByteStringKey* key);
template DirectHandle<String> StringTable::LookupKey(
Isolate* isolate, SeqOneByteSubStringKey* key);
template DirectHandle<String> StringTable::LookupKey(
Isolate* isolate, SeqTwoByteSubStringKey* key);
template DirectHandle<String> StringTable::LookupKey(LocalIsolate* isolate,
OneByteStringKey* key);
template DirectHandle<String> StringTable::LookupKey(LocalIsolate* isolate,
TwoByteStringKey* key);
template DirectHandle<String> StringTable::LookupKey(
Isolate* isolate, StringTableInsertionKey* key);
template DirectHandle<String> StringTable::LookupKey(
LocalIsolate* isolate, StringTableInsertionKey* key);
StringTable::Data* StringTable::EnsureCapacity(PtrComprCageBase cage_base,
int additional_elements) {
write_mutex_.AssertHeld();
Data* data = data_.load(std::memory_order_relaxed);
int new_capacity;
if (data->table().ShouldResizeToAdd(additional_elements, &new_capacity)) {
std::unique_ptr<Data> new_data =
Data::Resize(cage_base, std::unique_ptr<Data>(data), new_capacity);
DCHECK_EQ(new_data->PreviousData(), data);
data = new_data.release();
data_.store(data, std::memory_order_release);
}
return data;
}
namespace {
template <typename Char>
class CharBuffer {
public:
void Reset(size_t length) {
if (length >= kInlinedBufferSize)
outofline_ = std::make_unique<Char[]>(length);
}
Char* Data() {
if (outofline_)
return outofline_.get();
else
return inlined_;
}
private:
static constexpr size_t kInlinedBufferSize = 256;
Char inlined_[kInlinedBufferSize];
std::unique_ptr<Char[]> outofline_;
};
}
template <typename Char>
Address StringTable::Data::TryStringToIndexOrLookupExisting(
Isolate* isolate, Tagged<String> string, Tagged<String> source,
size_t start) {
DisallowGarbageCollection no_gc;
uint32_t length = string->length();
const bool is_source_hash_usable = start == 0 && length == source->length();
uint32_t raw_hash_field = source->raw_hash_field(kAcquireLoad);
if (Name::IsInternalizedForwardingIndex(raw_hash_field) &&
is_source_hash_usable) {
const int index = Name::ForwardingIndexValueBits::decode(raw_hash_field);
Tagged<String> internalized =
isolate->string_forwarding_table()->GetForwardString(isolate, index);
return internalized.ptr();
}
const HashSeed seed = HashSeed(isolate);
CharBuffer<Char> buffer;
const Char* chars;
SharedStringAccessGuardIfNeeded access_guard(isolate);
if (IsConsString(source, isolate)) {
DCHECK(!source->IsFlat());
buffer.Reset(length);
String::WriteToFlat(source, buffer.Data(), 0, length, access_guard);
chars = buffer.Data();
} else {
chars = source->GetDirectStringChars<Char>(no_gc, access_guard) + start;
}
if (!Name::IsHashFieldComputed(raw_hash_field) || !is_source_hash_usable) {
raw_hash_field =
StringHasher::HashSequentialString<Char>(chars, length, seed);
}
SequentialStringKey<Char> key(raw_hash_field,
base::Vector<const Char>(chars, length));
if (Name::ContainsCachedArrayIndex(raw_hash_field)) {
return Smi::FromInt(String::ArrayIndexValueBits::decode(raw_hash_field))
.ptr();
}
if (Name::IsIntegerIndex(raw_hash_field)) {
return Smi::FromInt(ResultSentinel::kUnsupported).ptr();
}
Data* string_table_data =
isolate->string_table()->data_.load(std::memory_order_acquire);
InternalIndex entry =
string_table_data->table().FindEntry(isolate, &key, key.hash());
if (entry.is_not_found()) {
return Smi::FromInt(ResultSentinel::kNotFound).ptr();
}
Tagged<String> internalized =
Cast<String>(string_table_data->table().GetKey(isolate, entry));
if (!IsInternalizedString(string)) {
SetInternalizedReference(isolate, string, internalized);
} else {
DCHECK(v8_flags.shared_string_table);
}
return internalized.ptr();
}
Address StringTable::TryStringToIndexOrLookupExisting(Isolate* isolate,
Address raw_string) {
Tagged<String> string = Cast<String>(Tagged<Object>(raw_string));
if (IsInternalizedString(string)) {
DCHECK(v8_flags.shared_string_table);
return raw_string;
}
static_assert(
!String::ArrayIndexValueBits::is_valid(ResultSentinel::kUnsupported));
static_assert(
!String::ArrayIndexValueBits::is_valid(ResultSentinel::kNotFound));
size_t start = 0;
Tagged<String> source = string;
if (IsSlicedString(source)) {
Tagged<SlicedString> sliced = Cast<SlicedString>(source);
start = sliced->offset();
source = sliced->parent();
} else if (IsConsString(source) && source->IsFlat()) {
source = Cast<ConsString>(source)->first();
}
if (IsThinString(source)) {
source = Cast<ThinString>(source)->actual();
if (string->length() == source->length()) {
return source.ptr();
}
}
if (source->IsOneByteRepresentation()) {
return StringTable::Data::TryStringToIndexOrLookupExisting<uint8_t>(
isolate, string, source, start);
}
return StringTable::Data::TryStringToIndexOrLookupExisting<uint16_t>(
isolate, string, source, start);
}
void StringTable::InsertForIsolateDeserialization(
Isolate* isolate, const base::Vector<DirectHandle<String>>& strings) {
DCHECK_EQ(NumberOfElements(), 0);
const int length = static_cast<int>(strings.size());
{
base::MutexGuard table_write_guard(&write_mutex_);
Data* const data = EnsureCapacity(isolate, length);
for (const DirectHandle<String>& s : strings) {
StringTableInsertionKey key(
isolate, s, DeserializingUserCodeOption::kNotDeserializingUserCode);
InternalIndex entry =
data->table().FindEntryOrInsertionEntry(isolate, &key, key.hash());
DirectHandle<String> inserted_string = key.GetHandleForInsertion(isolate);
DCHECK_IMPLIES(v8_flags.shared_string_table, inserted_string->IsShared());
data->table().AddAt(isolate, entry, *inserted_string);
}
}
DCHECK_EQ(NumberOfElements(), length);
}
void StringTable::InsertEmptyStringForBootstrapping(Isolate* isolate) {
DCHECK_EQ(NumberOfElements(), 0);
{
base::MutexGuard table_write_guard(&write_mutex_);
Data* const data = EnsureCapacity(isolate, 1);
DirectHandle<String> empty_string = isolate->factory()->empty_string();
uint32_t hash = empty_string->EnsureHash();
InternalIndex entry = data->table().FindInsertionEntry(isolate, hash);
DCHECK_IMPLIES(v8_flags.shared_string_table, empty_string->IsShared());
data->table().AddAt(isolate, entry, *empty_string);
}
DCHECK_EQ(NumberOfElements(), 1);
}
void StringTable::Print(PtrComprCageBase cage_base) const {
data_.load(std::memory_order_acquire)->Print(cage_base);
}
size_t StringTable::GetCurrentMemoryUsage() const {
return sizeof(*this) +
data_.load(std::memory_order_acquire)->GetCurrentMemoryUsage();
}
void StringTable::IterateElements(RootVisitor* visitor) {
isolate_->heap()->safepoint()->AssertActive();
data_.load(std::memory_order_relaxed)->IterateElements(visitor);
}
void StringTable::DropOldData() {
isolate_->heap()->safepoint()->AssertActive();
DCHECK_NE(isolate_->heap()->gc_state(), Heap::NOT_IN_GC);
data_.load(std::memory_order_relaxed)->DropPreviousData();
}
void StringTable::NotifyElementsRemoved(int count) {
isolate_->heap()->safepoint()->AssertActive();
DCHECK_NE(isolate_->heap()->gc_state(), Heap::NOT_IN_GC);
data_.load(std::memory_order_relaxed)->table().ElementsRemoved(count);
}
}
}