Commit 7649960c authored by Patrick Thier's avatar Patrick Thier Committed by V8 LUCI CQ

Reland "[strings] Support shared external strings"

This is a reland of commit d00c0405

Changes since revert: Use AsAtomicTagged instead of
base::AsAtomicPointer to store a hash value in the forwarding table.

Original change's description:
> [strings] Support shared external strings>
>
> With this CL shared strings can be externalized and external strings can
> be shared.
> The StringForwardingTable is used to delay the real transition to the
> next full GC. On the API side strings marked for externalization will
> look like externalized strings.
>
> Bug: v8:12957
> Change-Id: I53b6509129bc5679c06bdf99421bdb41ea5d9082
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3849643
> Reviewed-by: Shu-yu Guo <syg@chromium.org>
> Reviewed-by: Camillo Bruni <cbruni@chromium.org>
> Commit-Queue: Patrick Thier <pthier@chromium.org>
> Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
> Reviewed-by: Victor Gomes <victorgomes@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#82966}

Bug: v8:12957
Change-Id: I17715e927e4339240a6aa12a3c4a3c2ea50eb567
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3871211
Commit-Queue: Patrick Thier <pthier@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarVictor Gomes <victorgomes@chromium.org>
Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83018}
parent 23feac78
......@@ -20,6 +20,7 @@ class String;
namespace internal {
class ExternalString;
class ScopedExternalStringLock;
class StringForwardingTable;
} // namespace internal
/**
......@@ -269,6 +270,7 @@ class V8_EXPORT String : public Name {
private:
friend class internal::ExternalString;
friend class v8::String;
friend class internal::StringForwardingTable;
friend class internal::ScopedExternalStringLock;
};
......
......@@ -5739,19 +5739,53 @@ int String::Write(Isolate* v8_isolate, uint16_t* buffer, int start, int length,
start, length, options);
}
namespace {
bool HasExternalStringResource(i::String string) {
return i::StringShape(string).IsExternal() ||
string.HasExternalForwardingIndex(kAcquireLoad);
}
v8::String::ExternalStringResourceBase* GetExternalResourceFromForwardingTable(
i::String string, uint32_t raw_hash, bool* is_one_byte) {
DCHECK(i::String::IsExternalForwardingIndex(raw_hash));
const int index = i::String::ForwardingIndexValueBits::decode(raw_hash);
i::Isolate* isolate = i::GetIsolateFromWritableObject(string);
auto resource = isolate->string_forwarding_table()->GetExternalResource(
index, is_one_byte);
DCHECK_NOT_NULL(resource);
return resource;
}
} // namespace
bool v8::String::IsExternal() const {
i::Handle<i::String> str = Utils::OpenHandle(this);
return i::StringShape(*str).IsExternal();
return HasExternalStringResource(*str);
}
bool v8::String::IsExternalTwoByte() const {
i::Handle<i::String> str = Utils::OpenHandle(this);
return i::StringShape(*str).IsExternalTwoByte();
if (i::StringShape(*str).IsExternalTwoByte()) return true;
uint32_t raw_hash_field = str->raw_hash_field(kAcquireLoad);
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
GetExternalResourceFromForwardingTable(*str, raw_hash_field, &is_one_byte);
return !is_one_byte;
}
return false;
}
bool v8::String::IsExternalOneByte() const {
i::Handle<i::String> str = Utils::OpenHandle(this);
return i::StringShape(*str).IsExternalOneByte();
if (i::StringShape(*str).IsExternalOneByte()) return true;
uint32_t raw_hash_field = str->raw_hash_field(kAcquireLoad);
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
GetExternalResourceFromForwardingTable(*str, raw_hash_field, &is_one_byte);
return is_one_byte;
}
return false;
}
void v8::String::VerifyExternalStringResource(
......@@ -5768,7 +5802,17 @@ void v8::String::VerifyExternalStringResource(
const void* resource = i::ExternalTwoByteString::cast(str).resource();
expected = reinterpret_cast<const ExternalStringResource*>(resource);
} else {
expected = nullptr;
uint32_t raw_hash_field = str.raw_hash_field(kAcquireLoad);
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
auto resource = GetExternalResourceFromForwardingTable(
str, raw_hash_field, &is_one_byte);
if (!is_one_byte) {
expected = reinterpret_cast<const ExternalStringResource*>(resource);
}
} else {
expected = nullptr;
}
}
CHECK_EQ(expected, value);
}
......@@ -5793,9 +5837,17 @@ void v8::String::VerifyExternalStringResourceBase(
expected = reinterpret_cast<const ExternalStringResourceBase*>(resource);
expectedEncoding = TWO_BYTE_ENCODING;
} else {
expected = nullptr;
expectedEncoding =
str.IsOneByteRepresentation() ? ONE_BYTE_ENCODING : TWO_BYTE_ENCODING;
uint32_t raw_hash_field = str.raw_hash_field(kAcquireLoad);
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
expected = GetExternalResourceFromForwardingTable(str, raw_hash_field,
&is_one_byte);
expectedEncoding = is_one_byte ? ONE_BYTE_ENCODING : TWO_BYTE_ENCODING;
} else {
expected = nullptr;
expectedEncoding =
str.IsOneByteRepresentation() ? ONE_BYTE_ENCODING : TWO_BYTE_ENCODING;
}
}
CHECK_EQ(expected, value);
CHECK_EQ(expectedEncoding, encoding);
......@@ -5815,6 +5867,16 @@ String::ExternalStringResource* String::GetExternalStringResourceSlow() const {
i::Internals::ReadExternalPointerField<i::kExternalStringResourceTag>(
isolate, str.ptr(), i::Internals::kStringResourceOffset);
return reinterpret_cast<String::ExternalStringResource*>(value);
} else {
uint32_t raw_hash_field = str.raw_hash_field(kAcquireLoad);
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
auto resource = GetExternalResourceFromForwardingTable(
str, raw_hash_field, &is_one_byte);
if (!is_one_byte) {
return reinterpret_cast<ExternalStringResource*>(resource);
}
}
}
return nullptr;
}
......@@ -5859,6 +5921,15 @@ String::ExternalStringResourceBase* String::GetExternalStringResourceBaseSlow(
i::Internals::ReadExternalPointerField<i::kExternalStringResourceTag>(
isolate, string, i::Internals::kStringResourceOffset);
resource = reinterpret_cast<ExternalStringResourceBase*>(value);
} else {
uint32_t raw_hash_field = str.raw_hash_field();
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
resource = GetExternalResourceFromForwardingTable(str, raw_hash_field,
&is_one_byte);
*encoding_out = is_one_byte ? Encoding::ONE_BYTE_ENCODING
: Encoding::TWO_BYTE_ENCODING;
}
}
return resource;
}
......@@ -5875,6 +5946,15 @@ v8::String::GetExternalOneByteStringResource() const {
return i::ExternalOneByteString::cast(str).resource();
}
}
uint32_t raw_hash_field = str.raw_hash_field(kAcquireLoad);
if (i::String::IsExternalForwardingIndex(raw_hash_field)) {
bool is_one_byte;
auto resource = GetExternalResourceFromForwardingTable(str, raw_hash_field,
&is_one_byte);
if (is_one_byte) {
return reinterpret_cast<ExternalOneByteStringResource*>(resource);
}
}
return nullptr;
}
......@@ -6948,8 +7028,7 @@ bool v8::String::MakeExternal(v8::String::ExternalStringResource* resource) {
CHECK(resource && resource->data());
bool result = obj.MakeExternal(resource);
DCHECK(result);
DCHECK(obj.IsExternalString());
DCHECK_IMPLIES(result, HasExternalStringResource(obj));
return result;
}
......@@ -6975,7 +7054,7 @@ bool v8::String::MakeExternal(
CHECK(resource && resource->data());
bool result = obj.MakeExternal(resource);
DCHECK_IMPLIES(result, obj.IsExternalString());
DCHECK_IMPLIES(result, HasExternalStringResource(obj));
return result;
}
......
......@@ -280,6 +280,10 @@ void HeapObject::HeapObjectPrint(std::ostream& os) {
case UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE:
case SHARED_STRING_TYPE:
case SHARED_ONE_BYTE_STRING_TYPE:
case SHARED_EXTERNAL_STRING_TYPE:
case SHARED_EXTERNAL_ONE_BYTE_STRING_TYPE:
case SHARED_UNCACHED_EXTERNAL_STRING_TYPE:
case SHARED_UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE:
case SHARED_THIN_STRING_TYPE:
case SHARED_THIN_ONE_BYTE_STRING_TYPE:
case JS_LAST_DUMMY_API_OBJECT_TYPE:
......
......@@ -3698,6 +3698,10 @@ void Isolate::Deinit() {
ClearSerializerData();
if (OwnsStringTables()) {
string_forwarding_table()->TearDown();
}
#ifdef V8_COMPRESS_POINTERS
external_pointer_table().TearDown();
if (owns_shareable_data()) {
......
......@@ -1207,9 +1207,11 @@ MaybeHandle<Map> FactoryBase<Impl>::GetInPlaceInternalizedStringMap(
case SHARED_ONE_BYTE_STRING_TYPE:
map = read_only_roots().one_byte_internalized_string_map_handle();
break;
case SHARED_EXTERNAL_STRING_TYPE:
case EXTERNAL_STRING_TYPE:
map = read_only_roots().external_internalized_string_map_handle();
break;
case SHARED_EXTERNAL_ONE_BYTE_STRING_TYPE:
case EXTERNAL_ONE_BYTE_STRING_TYPE:
map =
read_only_roots().external_one_byte_internalized_string_map_handle();
......
......@@ -1039,6 +1039,21 @@ StringTransitionStrategy Factory::ComputeSharingStrategyForString(
case ONE_BYTE_STRING_TYPE:
*shared_map = read_only_roots().shared_one_byte_string_map_handle();
return StringTransitionStrategy::kInPlace;
case EXTERNAL_STRING_TYPE:
*shared_map = read_only_roots().shared_external_string_map_handle();
return StringTransitionStrategy::kInPlace;
case EXTERNAL_ONE_BYTE_STRING_TYPE:
*shared_map =
read_only_roots().shared_external_one_byte_string_map_handle();
return StringTransitionStrategy::kInPlace;
case UNCACHED_EXTERNAL_STRING_TYPE:
*shared_map =
read_only_roots().shared_uncached_external_string_map_handle();
return StringTransitionStrategy::kInPlace;
case UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE:
*shared_map = read_only_roots()
.shared_uncached_external_one_byte_string_map_handle();
return StringTransitionStrategy::kInPlace;
default:
return StringTransitionStrategy::kCopy;
}
......
......@@ -2924,7 +2924,7 @@ class StringForwardingTableCleaner final {
StringForwardingTable* forwarding_table =
isolate_->string_forwarding_table();
forwarding_table->IterateElements(
isolate_, [&](StringForwardingTable::Record* record) {
[&](StringForwardingTable::Record* record) {
TransitionStrings(record);
});
forwarding_table->Reset();
......@@ -2942,15 +2942,48 @@ class StringForwardingTableCleaner final {
}
if (marking_state_->IsBlack(HeapObject::cast(original))) {
String original_string = String::cast(original);
if (original_string.IsThinString()) {
original_string = ThinString::cast(original_string).actual();
}
TryExternalize(original_string, record);
TryInternalize(original_string, record);
original_string.set_raw_hash_field(record->raw_hash(isolate_));
} else {
record->DisposeExternalResource();
}
}
void TryExternalize(String original_string,
StringForwardingTable::Record* record) {
// If the string is already external, dispose the resource.
if (original_string.IsExternalString()) {
record->DisposeUnusedExternalResource(original_string);
return;
}
bool is_one_byte;
v8::String::ExternalStringResourceBase* external_resource =
record->external_resource(&is_one_byte);
if (external_resource == nullptr) return;
if (is_one_byte) {
original_string.MakeExternalDuringGC(
isolate_,
reinterpret_cast<v8::String::ExternalOneByteStringResource*>(
external_resource));
} else {
original_string.MakeExternalDuringGC(
isolate_, reinterpret_cast<v8::String::ExternalStringResource*>(
external_resource));
}
}
void TryInternalize(String original_string,
StringForwardingTable::Record* record) {
if (original_string.IsThinString()) return;
String forward_string = record->forward_string(isolate_);
if (original_string.IsInternalizedString()) return;
Object forward = record->ForwardStringObjectOrHash(isolate_);
if (!forward.IsHeapObject()) return;
String forward_string = String::cast(forward);
// Mark the forwarded string to keep it alive.
marking_state_->WhiteToBlack(forward_string);
......@@ -2978,7 +3011,7 @@ void MarkCompactCollector::ClearNonLiveReferences() {
TRACE_GC(heap()->tracer(),
GCTracer::Scope::MC_CLEAR_STRING_FORWARDING_TABLE);
// Clear string forwarding table. Live strings are transitioned to
// ThinStrings in the cleanup process.
// ThinStrings/ExternalStrings in the cleanup process.
// Clearing the string forwarding table must happen before clearing the
// string table, as entries in the forwarding table can keep internalized
// strings alive.
......
......@@ -148,6 +148,13 @@ enum InstanceType : uint16_t {
kOneByteStringTag | kThinStringTag | kNotInternalizedTag,
SHARED_STRING_TYPE = STRING_TYPE | kSharedStringTag,
SHARED_ONE_BYTE_STRING_TYPE = ONE_BYTE_STRING_TYPE | kSharedStringTag,
SHARED_EXTERNAL_STRING_TYPE = EXTERNAL_STRING_TYPE | kSharedStringTag,
SHARED_EXTERNAL_ONE_BYTE_STRING_TYPE =
EXTERNAL_ONE_BYTE_STRING_TYPE | kSharedStringTag,
SHARED_UNCACHED_EXTERNAL_STRING_TYPE =
UNCACHED_EXTERNAL_STRING_TYPE | kSharedStringTag,
SHARED_UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE =
UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE | kSharedStringTag,
SHARED_THIN_STRING_TYPE = THIN_STRING_TYPE | kSharedStringTag,
SHARED_THIN_ONE_BYTE_STRING_TYPE =
THIN_ONE_BYTE_STRING_TYPE | kSharedStringTag,
......
......@@ -51,9 +51,13 @@ namespace internal {
V(UNCACHED_EXTERNAL_STRING_TYPE) \
V(UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE) \
V(SHARED_STRING_TYPE) \
V(SHARED_EXTERNAL_STRING_TYPE) \
V(SHARED_THIN_STRING_TYPE) \
V(SHARED_ONE_BYTE_STRING_TYPE) \
V(SHARED_THIN_ONE_BYTE_STRING_TYPE)
V(SHARED_EXTERNAL_ONE_BYTE_STRING_TYPE) \
V(SHARED_THIN_ONE_BYTE_STRING_TYPE) \
V(SHARED_UNCACHED_EXTERNAL_STRING_TYPE) \
V(SHARED_UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE)
#define INSTANCE_TYPE_LIST(V) \
INSTANCE_TYPE_LIST_BASE(V) \
......@@ -103,6 +107,17 @@ namespace internal {
V(SHARED_STRING_TYPE, kVariableSizeSentinel, shared_string, SharedString) \
V(SHARED_ONE_BYTE_STRING_TYPE, kVariableSizeSentinel, \
shared_one_byte_string, SharedOneByteString) \
V(SHARED_EXTERNAL_STRING_TYPE, ExternalTwoByteString::kSize, \
shared_external_string, SharedExternalString) \
V(SHARED_EXTERNAL_ONE_BYTE_STRING_TYPE, ExternalOneByteString::kSize, \
shared_external_one_byte_string, SharedExternalOneByteString) \
V(SHARED_UNCACHED_EXTERNAL_STRING_TYPE, \
ExternalTwoByteString::kUncachedSize, shared_uncached_external_string, \
SharedUncachedExternalString) \
V(SHARED_UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE, \
ExternalOneByteString::kUncachedSize, \
shared_uncached_external_one_byte_string, \
SharedUncachedExternalOneByteString) \
V(SHARED_THIN_STRING_TYPE, ThinString::kSize, shared_thin_string, \
SharedThinString) \
V(SHARED_THIN_ONE_BYTE_STRING_TYPE, ThinString::kSize, \
......
......@@ -7,6 +7,7 @@
#include "src/base/atomicops.h"
#include "src/common/globals.h"
#include "src/heap/safepoint.h"
#include "src/objects/name-inl.h"
#include "src/objects/slots-inl.h"
#include "src/objects/slots.h"
......@@ -25,17 +26,23 @@ class StringForwardingTable::Record final {
}
String forward_string(PtrComprCageBase cage_base) const {
return String::cast(ForwardStringObject(cage_base));
return String::cast(ForwardStringObjectOrHash(cage_base));
}
inline uint32_t raw_hash(PtrComprCageBase cage_base) const;
inline v8::String::ExternalStringResourceBase* external_resource(
bool* is_one_byte) const;
Object OriginalStringObject(PtrComprCageBase cage_base) const {
return OriginalStringSlot().Acquire_Load(cage_base);
}
Object ForwardStringObject(PtrComprCageBase cage_base) const {
return ForwardStringSlot().Acquire_Load(cage_base);
Object ForwardStringObjectOrHash(PtrComprCageBase cage_base) const {
return ForwardStringOrHashSlot().Acquire_Load(cage_base);
}
Address ExternalResourceAddress() const {
return base::AsAtomicPointer::Acquire_Load(&external_resource_);
}
void set_original_string(Object object) {
......@@ -43,38 +50,183 @@ class StringForwardingTable::Record final {
}
void set_forward_string(Object object) {
ForwardStringSlot().Release_Store(object);
ForwardStringOrHashSlot().Release_Store(object);
}
inline void set_raw_hash_if_empty(uint32_t raw_hash);
inline void set_external_resource(
v8::String::ExternalStringResourceBase* resource, bool is_one_byte);
void set_external_resource(Address address) {
base::AsAtomicPointer::Release_Store(&external_resource_, address);
}
inline void SetInternalized(String string, String forward_to);
inline void SetExternal(String string,
v8::String::ExternalStringResourceBase*,
bool is_one_byte, uint32_t raw_hash);
inline bool TryUpdateExternalResource(
v8::String::ExternalStringResourceBase* resource, bool is_one_byte);
inline bool TryUpdateExternalResource(Address address);
inline void DisposeExternalResource();
// Dispose the external resource if the original string has transitioned
// to an external string and the resource used for the transition is different
// than the one in the record.
inline void DisposeUnusedExternalResource(String original_string);
private:
OffHeapObjectSlot OriginalStringSlot() const {
return OffHeapObjectSlot(&original_string_);
}
OffHeapObjectSlot ForwardStringSlot() const {
return OffHeapObjectSlot(&forward_string_);
OffHeapObjectSlot ForwardStringOrHashSlot() const {
return OffHeapObjectSlot(&forward_string_or_hash_);
}
static constexpr intptr_t kExternalResourceIsOneByteTag = 1;
static constexpr intptr_t kExternalResourceEncodingMask = 1;
static constexpr intptr_t kExternalResourceAddressMask =
~kExternalResourceEncodingMask;
// Always a pointer to the string that needs to be transitioned.
Tagged_t original_string_;
Tagged_t forward_string_;
// The field either stores the forward string object, or a raw hash.
// For strings forwarded to an internalized string (to be converted to a
// ThinString during GC), this field always contrains the internalized string
// object.
// It is guaranteed that only computed hash values (LSB = 0) are stored,
// therefore a raw hash is distinguishable from a string object by the
// heap object tag.
// Raw hashes can be overwritten by forward string objects, whereas
// forward string objects will never be overwritten once set.
Tagged_t forward_string_or_hash_;
// Although this is an external pointer, we are using Address instead of
// ExternalPointer_t to not have to deal with the ExternalPointerTable.
// This is OK, as the StringForwardingTable is outside of the V8 sandbox.
// The LSB is used to indicate whether the external resource is a one-byte
// (LSB = 1) or two-byte (LSB = 0) external string resource.
Address external_resource_;
// Possible string transitions and how they affect the fields of the record:
// Shared string (not in the table) --> Interalized
// forward_string_or_hash_ is set to the internalized string object.
// external_resource_ is nullptr.
// Shared string (not in the table) --> External
// forward_string_or_hash_ is set to the computed hash value of the string.
// external_resource_ is set to the address of the external resource.
// Shared string (in the table to be internalized) --> External
// forward_string_or_hash_ will not be overwritten. It will still contain
// the internalized string object from the previous transition.
// external_resource_ is set to the address of the external resource.
// Shared string (in the table to be made external) --> Internalized
// forward_string_or_hash_ (previously contained the computed hash value) is
// overwritten with the internalized string object.
// external_resource_ is not overwritten (still the external resource).
friend class StringForwardingTable::Block;
};
uint32_t StringForwardingTable::Record::raw_hash(
PtrComprCageBase cage_base) const {
String internalized = forward_string(cage_base);
uint32_t raw_hash = internalized.raw_hash_field();
Object hash_or_string = ForwardStringObjectOrHash(cage_base);
uint32_t raw_hash;
if (hash_or_string.IsHeapObject()) {
raw_hash = String::cast(hash_or_string).raw_hash_field();
} else {
raw_hash = static_cast<uint32_t>(hash_or_string.ptr());
}
DCHECK(Name::IsHashFieldComputed(raw_hash));
return raw_hash;
}
v8::String::ExternalStringResourceBase*
StringForwardingTable::Record::external_resource(bool* is_one_byte) const {
Address address = ExternalResourceAddress();
*is_one_byte = (address & kExternalResourceEncodingMask) ==
kExternalResourceIsOneByteTag;
address &= kExternalResourceAddressMask;
return reinterpret_cast<v8::String::ExternalStringResourceBase*>(address);
}
void StringForwardingTable::Record::set_raw_hash_if_empty(uint32_t raw_hash) {
// Assert that computed hash values don't overlap with heap object tag.
static_assert((kHeapObjectTag & Name::kHashNotComputedMask) != 0);
DCHECK(Name::IsHashFieldComputed(raw_hash));
DCHECK_NE(raw_hash & kHeapObjectTagMask, kHeapObjectTag);
AsAtomicTagged::Release_CompareAndSwap(&forward_string_or_hash_,
unused_element().value(), raw_hash);
}
void StringForwardingTable::Record::set_external_resource(
v8::String::ExternalStringResourceBase* resource, bool is_one_byte) {
DCHECK_NOT_NULL(resource);
Address address = reinterpret_cast<Address>(resource);
if (is_one_byte && address != kNullAddress) {
address |= kExternalResourceIsOneByteTag;
}
set_external_resource(address);
}
void StringForwardingTable::Record::SetInternalized(String string,
String forward_to) {
set_original_string(string);
set_forward_string(forward_to);
set_external_resource(kNullExternalPointer);
}
void StringForwardingTable::Record::SetExternal(
String string, v8::String::ExternalStringResourceBase* resource,
bool is_one_byte, uint32_t raw_hash) {
set_original_string(string);
set_raw_hash_if_empty(raw_hash);
set_external_resource(resource, is_one_byte);
}
bool StringForwardingTable::Record::TryUpdateExternalResource(
v8::String::ExternalStringResourceBase* resource, bool is_one_byte) {
DCHECK_NOT_NULL(resource);
Address address = reinterpret_cast<Address>(resource);
if (is_one_byte && address != kNullAddress) {
address |= kExternalResourceIsOneByteTag;
}
return TryUpdateExternalResource(address);
}
bool StringForwardingTable::Record::TryUpdateExternalResource(Address address) {
static_assert(kNullAddress == kNullExternalPointer);
// Don't set the external resource if another one is already stored. If we
// would simply overwrite the resource, the previously stored one would be
// leaked.
return base::AsAtomicPointer::AcquireRelease_CompareAndSwap(
&external_resource_, kNullAddress, address) == kNullAddress;
}
void StringForwardingTable::Record::DisposeExternalResource() {
bool is_one_byte;
auto resource = external_resource(&is_one_byte);
if (resource != nullptr) {
resource->Dispose();
}
}
void StringForwardingTable::Record::DisposeUnusedExternalResource(
String original) {
#ifdef DEBUG
String stored_original =
original_string(GetIsolateFromWritableObject(original));
if (stored_original.IsThinString()) {
stored_original = ThinString::cast(stored_original).actual();
}
DCHECK_EQ(original, stored_original);
#endif
if (!original.IsExternalString()) return;
Address original_resource =
ExternalString::cast(original).resource_as_address();
bool is_one_byte;
auto resource = external_resource(&is_one_byte);
if (resource != nullptr &&
reinterpret_cast<Address>(resource) != original_resource) {
resource->Dispose();
}
}
class StringForwardingTable::Block {
......@@ -175,10 +327,7 @@ uint32_t StringForwardingTable::CapacityForBlock(uint32_t block_index) {
}
template <typename Func>
void StringForwardingTable::IterateElements(Isolate* isolate, Func&& callback) {
isolate->heap()->safepoint()->AssertActive();
DCHECK_NE(isolate->heap()->gc_state(), Heap::NOT_IN_GC);
void StringForwardingTable::IterateElements(Func&& callback) {
if (empty()) return;
BlockVector* blocks = blocks_.load(std::memory_order_relaxed);
const uint32_t last_block_index = static_cast<uint32_t>(blocks->size() - 1);
......
......@@ -174,6 +174,65 @@ int StringForwardingTable::AddForwardString(String string, 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 <typename T>
int StringForwardingTable::AddExternalResourceAndHash(String string,
T* resource,
uint32_t raw_hash) {
constexpr bool is_one_byte =
std::is_base_of_v<v8::String::ExternalOneByteStringResource, T>;
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 <typename T>
bool StringForwardingTable::TryUpdateExternalResource(int index, T* resource) {
constexpr bool is_one_byte =
std::is_base_of_v<v8::String::ExternalOneByteStringResource, T>;
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());
......@@ -202,6 +261,21 @@ uint32_t StringForwardingTable::GetRawHash(PtrComprCageBase cage_base,
return block->record(index_in_block)->raw_hash(cage_base);
}
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);
......
......@@ -14,7 +14,10 @@ namespace v8 {
namespace internal {
// Mapping from forwarding indices (stored in a string's hash field) to
// internalized strings.
// internalized strings/external resources.
// The table is used to handle string transitions (temporarily until the next
// full GC, during which actual string transitions happen) that overwrite the
// string buffer. In particular these are Internalization and Externalization.
// The table is organised in "blocks". As writes only append new entries, the
// organisation in blocks allows lock-free writes. We need a lock only for
// growing the table (adding more blocks). When the vector holding the blocks
......@@ -39,12 +42,27 @@ class StringForwardingTable {
inline bool empty() const;
// Returns the index of the added record.
int AddForwardString(String string, String forward_to);
template <typename T>
EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
int AddExternalResourceAndHash(String string, T* resource, uint32_t raw_hash);
void UpdateForwardString(int index, String forward_to);
// Returns true when the resource was set. When an external resource is
// already set for the record, false is returned and the resource not stored.
// The caller is responsible for disposing the resource.
template <typename T>
EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
bool TryUpdateExternalResource(int index, T* resource);
String GetForwardString(PtrComprCageBase cage_base, int index) const;
static Address GetForwardStringAddress(Isolate* isolate, int index);
V8_EXPORT_PRIVATE uint32_t GetRawHash(PtrComprCageBase cage_base,
int index) const;
v8::String::ExternalStringResourceBase* GetExternalResource(
int index, bool* is_one_byte) const;
template <typename Func>
V8_INLINE void IterateElements(Isolate* isolate, Func&& callback);
V8_INLINE void IterateElements(Func&& callback);
// Dispose all external resources stored in the table.
void TearDown();
void Reset();
void UpdateAfterEvacuation();
......
......@@ -1490,6 +1490,8 @@ bool String::IsInPlaceInternalizable(InstanceType instance_type) {
case SHARED_ONE_BYTE_STRING_TYPE:
case EXTERNAL_STRING_TYPE:
case EXTERNAL_ONE_BYTE_STRING_TYPE:
case SHARED_EXTERNAL_STRING_TYPE:
case SHARED_EXTERNAL_ONE_BYTE_STRING_TYPE:
return true;
default:
return false;
......
......@@ -390,17 +390,29 @@ class InternalizedStringKey final : public StringTableKey {
const bool can_avoid_copy =
!v8_flags.shared_string_table && !shape.IsUncachedExternal();
if (can_avoid_copy && shape.IsExternalOneByte()) {
// Shared external strings are always in-place internalizable.
// If this assumption is invalidated in the future, make sure that we
// fully initialize (copy contents) for shared external strings, as the
// original string is not transitioned to a ThinString (setting the
// resource) immediately.
DCHECK(!shape.IsShared());
string_ =
isolate->factory()->InternalizeExternalString<ExternalOneByteString>(
string_);
} else if (can_avoid_copy && shape.IsExternalTwoByte()) {
// Shared external strings are always in-place internalizable.
// If this assumption is invalidated in the future, make sure that we
// fully initialize (copy contents) for shared external strings, as the
// original string is not transitioned to a ThinString (setting the
// resource) immediately.
DCHECK(!shape.IsShared());
string_ =
isolate->factory()->InternalizeExternalString<ExternalTwoByteString>(
string_);
} else {
// Otherwise allocate a new internalized string.
string_ = isolate->factory()->NewInternalizedStringImpl(
string_, string_->length(), string_->raw_hash_field());
string_ = isolate->factory()->NewInternalizedStringImpl(string_, length(),
raw_hash_field());
}
}
......@@ -435,13 +447,12 @@ namespace {
void SetInternalizedReference(Isolate* isolate, String string,
String internalized) {
// TODO(v8:12007): Support external strings.
DCHECK(!string.IsThinString());
DCHECK(!string.IsInternalizedString());
DCHECK(internalized.IsInternalizedString());
DCHECK(!internalized.HasForwardingIndex(kAcquireLoad));
if ((string.IsShared() || v8_flags.always_use_string_forwarding_table) &&
!string.IsExternalString()) {
uint32_t field = string.raw_hash_field();
DCHECK(!internalized.HasInternalizedForwardingIndex(kAcquireLoad));
if (string.IsShared() || v8_flags.always_use_string_forwarding_table) {
uint32_t field = string.raw_hash_field(kAcquireLoad);
// Don't use the forwarding table for strings that have an integer index.
// Using the hash field for the integer index is more beneficial than
// using it to store the forwarding index to the internalized string.
......@@ -450,20 +461,27 @@ void SetInternalizedReference(Isolate* isolate, String string,
// to prevent too many copies of the string in the forwarding table.
if (Name::IsInternalizedForwardingIndex(field)) return;
const int forwarding_index =
isolate->string_forwarding_table()->AddForwardString(string,
internalized);
string.set_raw_hash_field(
String::CreateInternalizedForwardingIndex(forwarding_index),
kReleaseStore);
} else {
if (V8_UNLIKELY(v8_flags.always_use_string_forwarding_table)) {
// It is possible that the string has a forwarding index (the string was
// externalized after it had its forwarding index set). Overwrite the
// hash field to avoid having a ThinString with a forwarding index.
DCHECK(string.IsExternalString());
string.set_raw_hash_field(internalized.raw_hash_field());
// If we already have an entry for an external resource in the table, update
// the entry instead of creating a new one. There is no guarantee that we
// will always update existing records instead of creating new ones, but
// races should be rare.
if (Name::IsForwardingIndex(field)) {
const int forwarding_index =
Name::ForwardingIndexValueBits::decode(field);
isolate->string_forwarding_table()->UpdateForwardString(forwarding_index,
internalized);
// Update the forwarding index type to include 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);
}
......
This diff is collapsed.
......@@ -410,6 +410,11 @@ class String : public TorqueGeneratedString<String, Name> {
int* length_output = nullptr);
// Externalization.
template <typename T>
bool MarkForExternalizationDuringGC(Isolate* isolate, T* resource);
template <typename T>
EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
void MakeExternalDuringGC(Isolate* isolate, T* resource);
V8_EXPORT_PRIVATE bool MakeExternal(
v8::String::ExternalStringResource* resource);
V8_EXPORT_PRIVATE bool MakeExternal(
......
......@@ -154,6 +154,11 @@ class Symbol;
UncachedExternalOneByteStringMap) \
V(Map, shared_one_byte_string_map, SharedOneByteStringMap) \
V(Map, shared_string_map, SharedStringMap) \
V(Map, shared_external_one_byte_string_map, SharedExternalOneByteStringMap) \
V(Map, shared_external_string_map, SharedExternalStringMap) \
V(Map, shared_uncached_external_one_byte_string_map, \
SharedUncachedExternalOneByteStringMap) \
V(Map, shared_uncached_external_string_map, SharedUncachedExternalStringMap) \
V(Map, shared_thin_one_byte_string_map, SharedThinOneByteStringMap) \
V(Map, shared_thin_string_map, SharedThinStringMap) \
/* Oddball maps */ \
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment