Commit 821c2c17 authored by Shu-yu Guo's avatar Shu-yu Guo Committed by V8 LUCI CQ

[string] Add a is_shared bit to strings and String::Share

The is_shared bit bumps the number of reserved bits for Strings'
InstanceType from 6 to 7. This has the side effect of shuffling the
InstanceType enum values.

There are no users of this bit yet. This is steps 1-2 from the following
design doc [1], in preparation for sharing internalized and
in-place-internalizable strings.

[1] https://docs.google.com/document/d/1c5i8f2EfKIQygGZ23hNiGxouvRISjUMnJjNsOodj6z0/edit?usp=sharing

Bug: v8:12007
Change-Id: Idf11a6035305f0375b4f824ffd32a64f6b5b043b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3266017
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77831}
parent 2b01c828
......@@ -268,9 +268,9 @@ class Internals {
static const int kNodeStateIsWeakValue = 2;
static const int kNodeStateIsPendingValue = 3;
static const int kFirstNonstringType = 0x40;
static const int kOddballType = 0x43;
static const int kForeignType = 0x46;
static const int kFirstNonstringType = 0x80;
static const int kOddballType = 0x83;
static const int kForeignType = 0xcc;
static const int kJSSpecialApiObjectType = 0x410;
static const int kJSObjectType = 0x421;
static const int kFirstJSApiObjectType = 0x422;
......
......@@ -1883,13 +1883,13 @@ enum PropertiesEnumerationMode {
kPropertyAdditionOrder,
};
enum class StringInternalizationStrategy {
// The string must be internalized by first copying.
enum class StringTransitionStrategy {
// The string must be transitioned to a new representation by first copying.
kCopy,
// The string can be internalized in-place by changing its map.
// The string can be transitioned in-place by changing its map.
kInPlace,
// The string is already internalized.
kAlreadyInternalized
// The string is already transitioned to the desired representation.
kAlreadyTransitioned
};
} // namespace internal
......
......@@ -264,6 +264,8 @@ void HeapObject::HeapObjectPrint(std::ostream& os) {
case THIN_ONE_BYTE_STRING_TYPE:
case UNCACHED_EXTERNAL_STRING_TYPE:
case UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE:
case SHARED_STRING_TYPE:
case SHARED_ONE_BYTE_STRING_TYPE:
case JS_LAST_DUMMY_API_OBJECT_TYPE:
// TODO(all): Handle these types too.
os << "UNKNOWN TYPE " << map().instance_type();
......
......@@ -593,19 +593,22 @@ Handle<SeqTwoByteString> FactoryBase<Impl>::NewTwoByteInternalizedString(
}
template <typename Impl>
MaybeHandle<SeqOneByteString> FactoryBase<Impl>::NewRawOneByteString(
int length, AllocationType allocation) {
template <typename SeqStringT>
MaybeHandle<SeqStringT> FactoryBase<Impl>::NewRawStringWithMap(
int length, Map map, AllocationType allocation) {
DCHECK(SeqStringT::IsCompatibleMap(map, read_only_roots()));
DCHECK_IMPLIES(!StringShape(map).IsShared(),
RefineAllocationTypeForInPlaceInternalizableString(
allocation, map) == allocation);
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), SeqOneByteString);
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), SeqStringT);
}
DCHECK_GT(length, 0); // Use Factory::empty_string() instead.
int size = SeqOneByteString::SizeFor(length);
DCHECK_GE(SeqOneByteString::kMaxSize, size);
int size = SeqStringT::SizeFor(length);
DCHECK_GE(SeqStringT::kMaxSize, size);
Map map = read_only_roots().one_byte_string_map();
SeqOneByteString string = SeqOneByteString::cast(AllocateRawWithImmortalMap(
size, RefineAllocationTypeForInPlaceInternalizableString(allocation, map),
map));
SeqStringT string =
SeqStringT::cast(AllocateRawWithImmortalMap(size, allocation, map));
DisallowGarbageCollection no_gc;
string.set_length(length);
string.set_raw_hash_field(String::kEmptyHashField);
......@@ -614,24 +617,37 @@ MaybeHandle<SeqOneByteString> FactoryBase<Impl>::NewRawOneByteString(
}
template <typename Impl>
MaybeHandle<SeqTwoByteString> FactoryBase<Impl>::NewRawTwoByteString(
MaybeHandle<SeqOneByteString> FactoryBase<Impl>::NewRawOneByteString(
int length, AllocationType allocation) {
if (length > String::kMaxLength || length < 0) {
THROW_NEW_ERROR(isolate(), NewInvalidStringLengthError(), SeqTwoByteString);
}
DCHECK_GT(length, 0); // Use Factory::empty_string() instead.
int size = SeqTwoByteString::SizeFor(length);
DCHECK_GE(SeqTwoByteString::kMaxSize, size);
Map map = read_only_roots().one_byte_string_map();
return NewRawStringWithMap<SeqOneByteString>(
length, map,
RefineAllocationTypeForInPlaceInternalizableString(allocation, map));
}
template <typename Impl>
MaybeHandle<SeqTwoByteString> FactoryBase<Impl>::NewRawTwoByteString(
int length, AllocationType allocation) {
Map map = read_only_roots().string_map();
SeqTwoByteString string = SeqTwoByteString::cast(AllocateRawWithImmortalMap(
size, RefineAllocationTypeForInPlaceInternalizableString(allocation, map),
map));
DisallowGarbageCollection no_gc;
string.set_length(length);
string.set_raw_hash_field(String::kEmptyHashField);
DCHECK_EQ(size, string.Size());
return handle(string, isolate());
return NewRawStringWithMap<SeqTwoByteString>(
length, map,
RefineAllocationTypeForInPlaceInternalizableString(allocation, map));
}
template <typename Impl>
MaybeHandle<SeqOneByteString> FactoryBase<Impl>::NewRawSharedOneByteString(
int length) {
return NewRawStringWithMap<SeqOneByteString>(
length, read_only_roots().shared_one_byte_string_map(),
AllocationType::kSharedOld);
}
template <typename Impl>
MaybeHandle<SeqTwoByteString> FactoryBase<Impl>::NewRawSharedTwoByteString(
int length) {
return NewRawStringWithMap<SeqTwoByteString>(
length, read_only_roots().shared_string_map(),
AllocationType::kSharedOld);
}
template <typename Impl>
......
......@@ -219,6 +219,11 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) FactoryBase
Handle<String> left, Handle<String> right, int length, bool one_byte,
AllocationType allocation = AllocationType::kYoung);
V8_WARN_UNUSED_RESULT MaybeHandle<SeqOneByteString> NewRawSharedOneByteString(
int length);
V8_WARN_UNUSED_RESULT MaybeHandle<SeqTwoByteString> NewRawSharedTwoByteString(
int length);
// Allocates a new BigInt with {length} digits. Only to be used by
// MutableBigInt::New*.
Handle<FreshlyAllocatedBigInt> NewBigInt(
......@@ -279,6 +284,10 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) FactoryBase
Handle<String> MakeOrFindTwoCharacterString(uint16_t c1, uint16_t c2);
template <typename SeqStringT>
MaybeHandle<SeqStringT> NewRawStringWithMap(int length, Map map,
AllocationType allocation);
private:
friend class WebSnapshotDeserializer;
Impl* impl() { return static_cast<Impl*>(this); }
......
......@@ -877,12 +877,12 @@ namespace {
} // namespace
StringInternalizationStrategy Factory::ComputeInternalizationStrategyForString(
StringTransitionStrategy Factory::ComputeInternalizationStrategyForString(
Handle<String> string, MaybeHandle<Map>* internalized_map) {
// Do not internalize young strings in-place: This allows us to ignore both
// string table and stub cache on scavenges.
if (Heap::InYoungGeneration(*string)) {
return StringInternalizationStrategy::kCopy;
return StringTransitionStrategy::kCopy;
}
DCHECK_NOT_NULL(internalized_map);
DisallowGarbageCollection no_gc;
......@@ -892,12 +892,12 @@ StringInternalizationStrategy Factory::ComputeInternalizationStrategyForString(
Map map = string->map();
*internalized_map = GetInPlaceInternalizedStringMap(map);
if (!internalized_map->is_null()) {
return StringInternalizationStrategy::kInPlace;
return StringTransitionStrategy::kInPlace;
}
if (InstanceTypeChecker::IsInternalizedString(map.instance_type())) {
return StringInternalizationStrategy::kAlreadyInternalized;
return StringTransitionStrategy::kAlreadyTransitioned;
}
return StringInternalizationStrategy::kCopy;
return StringTransitionStrategy::kCopy;
}
template <class StringClass>
......@@ -921,6 +921,31 @@ template Handle<ExternalOneByteString>
template Handle<ExternalTwoByteString>
Factory::InternalizeExternalString<ExternalTwoByteString>(Handle<String>);
StringTransitionStrategy Factory::ComputeSharingStrategyForString(
Handle<String> string, MaybeHandle<Map>* shared_map) {
DCHECK(FLAG_shared_string_table);
// Do not share young strings in-place: there is no shared young space.
if (Heap::InYoungGeneration(*string)) {
return StringTransitionStrategy::kCopy;
}
DCHECK_NOT_NULL(shared_map);
DisallowGarbageCollection no_gc;
InstanceType instance_type = string->map().instance_type();
if (StringShape(instance_type).IsShared()) {
return StringTransitionStrategy::kAlreadyTransitioned;
}
switch (instance_type) {
case STRING_TYPE:
*shared_map = read_only_roots().shared_string_map_handle();
return StringTransitionStrategy::kInPlace;
case ONE_BYTE_STRING_TYPE:
*shared_map = read_only_roots().shared_one_byte_string_map_handle();
return StringTransitionStrategy::kInPlace;
default:
return StringTransitionStrategy::kCopy;
}
}
Handle<String> Factory::LookupSingleCharacterStringFromCode(uint16_t code) {
if (code <= unibrow::Latin1::kMaxChar) {
{
......
......@@ -278,15 +278,15 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
// Compute the internalization strategy for the input string.
//
// Old-generation flat strings can be internalized by mutating their map
// return kInPlace, along with the matching internalized string map for string
// is stored in internalized_map.
// Old-generation sequential strings can be internalized by mutating their map
// and return kInPlace, along with the matching internalized string map for
// string stored in internalized_map.
//
// Internalized strings return kAlreadyInternalized.
// Internalized strings return kAlreadyTransitioned.
//
// All other strings are internalized by flattening and copying and return
// kCopy.
V8_WARN_UNUSED_RESULT StringInternalizationStrategy
V8_WARN_UNUSED_RESULT StringTransitionStrategy
ComputeInternalizationStrategyForString(Handle<String> string,
MaybeHandle<Map>* internalized_map);
......@@ -295,6 +295,20 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
template <class StringClass>
Handle<StringClass> InternalizeExternalString(Handle<String> string);
// Compute the sharing strategy for the input string.
//
// Old-generation sequential and thin strings can be shared by mutating their
// map and return kInPlace, along with the matching shared string map for the
// string stored in shared_map.
//
// Already-shared strings return kAlreadyTransitioned.
//
// All other strings are shared by flattening and copying into a sequential
// string then sharing that sequential string, and return kCopy.
V8_WARN_UNUSED_RESULT StringTransitionStrategy
ComputeSharingStrategyForString(Handle<String> string,
MaybeHandle<Map>* shared_map);
// Creates a single character string where the character has given code.
// A cache is used for Latin1 codes.
Handle<String> LookupSingleCharacterStringFromCode(uint16_t code);
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@apiExposedInstanceTypeValue(0x46)
@apiExposedInstanceTypeValue(0xcc)
extern class Foreign extends HeapObject {
foreign_address: ExternalPointer;
}
......
......@@ -16,9 +16,9 @@ namespace v8 {
namespace internal {
// We use the full 16 bits of the instance_type field to encode heap object
// instance types. All the high-order bits (bits 6-15) are cleared if the object
// instance types. All the high-order bits (bits 7-15) are cleared if the object
// is a string, and contain set bits if it is not a string.
const uint32_t kIsNotStringMask = ~((1 << 6) - 1);
const uint32_t kIsNotStringMask = ~((1 << 7) - 1);
const uint32_t kStringTag = 0x0;
// For strings, bits 0-2 indicate the representation of the string. In
......@@ -68,6 +68,23 @@ const uint32_t kIsNotInternalizedMask = 1 << 5;
const uint32_t kNotInternalizedTag = 1 << 5;
const uint32_t kInternalizedTag = 0;
// For strings, bit 6 indicates that the string is accessible by more than one
// thread. Note that a string that is allocated in the shared heap is not
// accessible by more than one thread until it is explicitly shared (e.g. by
// postMessage).
//
// Runtime code that shares strings with other threads directly need to manually
// set this bit.
//
// TODO(v8:12007): External strings cannot be shared yet.
//
// TODO(v8:12007): This bit is currently ignored on internalized strings, which
// are either always shared or always not shared depending on
// FLAG_shared_string_table. This will be hardcoded once
// FLAG_shared_string_table is removed.
const uint32_t kSharedStringMask = 1 << 6;
const uint32_t kSharedStringTag = 1 << 6;
// A ConsString with an empty string as the right side is a candidate
// for being shortcut by the garbage collector. We don't allocate any
// non-flat internalized strings, so we do not shortcut them thereby
......@@ -119,6 +136,8 @@ enum InstanceType : uint16_t {
THIN_STRING_TYPE = kTwoByteStringTag | kThinStringTag | kNotInternalizedTag,
THIN_ONE_BYTE_STRING_TYPE =
kOneByteStringTag | kThinStringTag | kNotInternalizedTag,
SHARED_STRING_TYPE = STRING_TYPE | kSharedStringTag,
SHARED_ONE_BYTE_STRING_TYPE = ONE_BYTE_STRING_TYPE | kSharedStringTag,
// Most instance types are defined in Torque, with the exception of the string
// types above. They are ordered by inheritance hierarchy so that we can easily
......
......@@ -49,7 +49,9 @@ namespace internal {
V(SLICED_ONE_BYTE_STRING_TYPE) \
V(THIN_ONE_BYTE_STRING_TYPE) \
V(UNCACHED_EXTERNAL_STRING_TYPE) \
V(UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE)
V(UNCACHED_EXTERNAL_ONE_BYTE_STRING_TYPE) \
V(SHARED_STRING_TYPE) \
V(SHARED_ONE_BYTE_STRING_TYPE)
#define INSTANCE_TYPE_LIST(V) \
INSTANCE_TYPE_LIST_BASE(V) \
......@@ -94,7 +96,11 @@ namespace internal {
UncachedExternalOneByteInternalizedString) \
V(THIN_STRING_TYPE, ThinString::kSize, thin_string, ThinString) \
V(THIN_ONE_BYTE_STRING_TYPE, ThinString::kSize, thin_one_byte_string, \
ThinOneByteString)
ThinOneByteString) \
\
V(SHARED_STRING_TYPE, kVariableSizeSentinel, shared_string, SharedString) \
V(SHARED_ONE_BYTE_STRING_TYPE, kVariableSizeSentinel, \
shared_one_byte_string, SharedOneByteString)
// A struct is a simple object a set of object-valued fields. Including an
// object type in this causes the compiler to generate most of the boilerplate
......
......@@ -2197,7 +2197,8 @@ int HeapObject::SizeFromMap(Map map) const {
return Context::SizeFor(Context::unchecked_cast(*this).length());
}
if (instance_type == ONE_BYTE_STRING_TYPE ||
instance_type == ONE_BYTE_INTERNALIZED_STRING_TYPE) {
instance_type == ONE_BYTE_INTERNALIZED_STRING_TYPE ||
instance_type == SHARED_ONE_BYTE_STRING_TYPE) {
// Strings may get concurrently truncated, hence we have to access its
// length synchronized.
return SeqOneByteString::SizeFor(
......@@ -2215,7 +2216,8 @@ int HeapObject::SizeFromMap(Map map) const {
return FreeSpace::unchecked_cast(*this).size(kRelaxedLoad);
}
if (instance_type == STRING_TYPE ||
instance_type == INTERNALIZED_STRING_TYPE) {
instance_type == INTERNALIZED_STRING_TYPE ||
instance_type == SHARED_STRING_TYPE) {
// Strings may get concurrently truncated, hence we have to access its
// length synchronized.
return SeqTwoByteString::SizeFor(
......
......@@ -3,7 +3,7 @@
// found in the LICENSE file.
@generateBodyDescriptor
@apiExposedInstanceTypeValue(0x43)
@apiExposedInstanceTypeValue(0x83)
@highestInstanceTypeWithinParentClassRange
extern class Oddball extends PrimitiveHeapObject {
to_number_raw: float64;
......
......@@ -172,6 +172,13 @@ bool StringShape::IsUncachedExternal() const {
return (type_ & kUncachedExternalStringMask) == kUncachedExternalStringTag;
}
bool StringShape::IsShared() const {
// TODO(v8:12007): Set is_shared to true on internalized string when
// FLAG_shared_string_table is removed.
return (type_ & kSharedStringMask) == kSharedStringTag ||
(FLAG_shared_string_table && IsInternalized());
}
StringRepresentationTag StringShape::representation_tag() const {
uint32_t tag = (type_ & kStringRepresentationMask);
return static_cast<StringRepresentationTag>(tag);
......@@ -696,6 +703,24 @@ String::FlatContent String::GetFlatContent(
return SlowGetFlatContent(no_gc, access_guard);
}
Handle<String> String::Share(Isolate* isolate, Handle<String> string) {
DCHECK(FLAG_shared_string_table);
MaybeHandle<Map> new_map;
switch (
isolate->factory()->ComputeSharingStrategyForString(string, &new_map)) {
case StringTransitionStrategy::kCopy:
return SlowShare(isolate, string);
case StringTransitionStrategy::kInPlace:
// A relaxed write is sufficient here, because at this point the string
// has not yet escaped the current thread.
DCHECK(string->InSharedHeap());
string->set_map_no_write_barrier(*new_map.ToHandleChecked());
return string;
case StringTransitionStrategy::kAlreadyTransitioned:
return string;
}
}
uint16_t String::Get(int index) const {
DCHECK(!SharedStringAccessGuardIfNeeded::IsNeeded(*this));
return GetImpl(index, GetPtrComprCageBase(*this),
......@@ -761,6 +786,14 @@ bool String::IsFlat(PtrComprCageBase cage_base) const {
return ConsString::cast(*this).IsFlat(cage_base);
}
bool String::IsShared() const { return IsShared(GetPtrComprCageBase(*this)); }
bool String::IsShared(PtrComprCageBase cage_base) const {
const bool result = StringShape(*this, cage_base).IsShared();
DCHECK_IMPLIES(result, InSharedHeap());
return result;
}
String String::GetUnderlying() const {
// Giving direct access to underlying string only makes sense if the
// wrapping string is already flattened.
......@@ -952,6 +985,17 @@ inline int SeqTwoByteString::AllocatedSize() {
return SizeFor(length(kAcquireLoad));
}
// static
bool SeqOneByteString::IsCompatibleMap(Map map, ReadOnlyRoots roots) {
return map == roots.one_byte_string_map() ||
map == roots.shared_one_byte_string_map();
}
// static
bool SeqTwoByteString::IsCompatibleMap(Map map, ReadOnlyRoots roots) {
return map == roots.string_map() || map == roots.shared_string_map();
}
void SlicedString::set_parent(String parent, WriteBarrierMode mode) {
DCHECK(parent.IsSeqString() || parent.IsExternalString());
TorqueGeneratedSlicedString<SlicedString, Super>::set_parent(parent, mode);
......
......@@ -364,13 +364,13 @@ class InternalizedStringKey final : public StringTableKey {
Handle<String> AsHandle(Isolate* isolate) {
// Internalize the string in-place if possible.
MaybeHandle<Map> maybe_internalized_map;
StringInternalizationStrategy strategy =
StringTransitionStrategy strategy =
isolate->factory()->ComputeInternalizationStrategyForString(
string_, &maybe_internalized_map);
switch (strategy) {
case StringInternalizationStrategy::kCopy:
case StringTransitionStrategy::kCopy:
break;
case StringInternalizationStrategy::kInPlace:
case StringTransitionStrategy::kInPlace:
// A relaxed write is sufficient here even with concurrent
// internalization. Though it is not synchronizing, a thread that does
// not see the relaxed write will wait on the string table write
......@@ -381,7 +381,7 @@ class InternalizedStringKey final : public StringTableKey {
*maybe_internalized_map.ToHandleChecked());
DCHECK(string_->IsInternalizedString());
return string_;
case StringInternalizationStrategy::kAlreadyInternalized:
case StringTransitionStrategy::kAlreadyTransitioned:
// We can see already internalized strings here only when sharing the
// string table and allowing concurrent internalization.
DCHECK(FLAG_shared_string_table);
......@@ -505,7 +505,7 @@ Handle<String> StringTable::LookupKey(IsolateT* isolate, StringTableKey* key) {
InternalIndex entry = data->FindEntry(isolate, key, key->hash());
if (entry.is_found()) {
Handle<String> result(String::cast(data->Get(isolate, entry)), isolate);
DCHECK_IMPLIES(FLAG_shared_string_table, result->InSharedHeap());
DCHECK_IMPLIES(FLAG_shared_string_table, result->IsShared());
return result;
}
......@@ -516,7 +516,7 @@ Handle<String> StringTable::LookupKey(IsolateT* isolate, StringTableKey* key) {
// allocates the same string, the insert will fail, the lookup above will
// succeed, and this string will be discarded.
Handle<String> new_string = key->AsHandle(isolate);
DCHECK_IMPLIES(FLAG_shared_string_table, new_string->InSharedHeap());
DCHECK_IMPLIES(FLAG_shared_string_table, new_string->IsShared());
{
base::MutexGuard table_write_guard(&write_mutex_);
......
......@@ -104,6 +104,42 @@ Handle<String> String::SlowCopy(Isolate* isolate, Handle<SeqString> source,
return copy;
}
Handle<String> String::SlowShare(Isolate* isolate, Handle<String> source) {
DCHECK(FLAG_shared_string_table);
Handle<String> flat = Flatten(isolate, source, AllocationType::kSharedOld);
// Do not recursively call Share, so directly compute the sharing strategy for
// the flat string, which could already be a copy or an existing string from
// e.g. a shortcut ConsString.
MaybeHandle<Map> new_map;
switch (isolate->factory()->ComputeSharingStrategyForString(flat, &new_map)) {
case StringTransitionStrategy::kCopy:
break;
case StringTransitionStrategy::kInPlace:
// A relaxed write is sufficient here, because at this point the string
// has not yet escaped the current thread.
DCHECK(flat->InSharedHeap());
flat->set_map_no_write_barrier(*new_map.ToHandleChecked());
return flat;
case StringTransitionStrategy::kAlreadyTransitioned:
return flat;
}
int length = flat->length();
if (flat->IsOneByteRepresentation()) {
Handle<SeqOneByteString> copy =
isolate->factory()->NewRawSharedOneByteString(length).ToHandleChecked();
DisallowGarbageCollection no_gc;
WriteToFlat(*flat, copy->GetChars(no_gc), 0, length);
return copy;
}
Handle<SeqTwoByteString> copy =
isolate->factory()->NewRawSharedTwoByteString(length).ToHandleChecked();
DisallowGarbageCollection no_gc;
WriteToFlat(*flat, copy->GetChars(no_gc), 0, length);
return copy;
}
namespace {
template <class StringClass>
......@@ -149,6 +185,7 @@ void String::MakeThin(IsolateT* isolate, String internalized) {
DCHECK_NE(*this, internalized);
DCHECK(internalized.IsInternalizedString());
// TODO(v8:12007): Make this method threadsafe.
DCHECK(!IsShared());
DCHECK_IMPLIES(
InSharedWritableHeap(),
ThreadId::Current() == GetIsolateFromWritableObject(*this)->thread_id());
......
......@@ -59,6 +59,7 @@ class StringShape {
V8_INLINE bool IsSequentialOneByte() const;
V8_INLINE bool IsSequentialTwoByte() const;
V8_INLINE bool IsInternalized() const;
V8_INLINE bool IsShared() const;
V8_INLINE StringRepresentationTag representation_tag() const;
V8_INLINE uint32_t encoding_tag() const;
V8_INLINE uint32_t full_representation_tag() const;
......@@ -275,6 +276,11 @@ class String : public TorqueGeneratedString<String, Name> {
// Requires: StringShape(this).IsIndirect() && this->IsFlat()
inline String GetUnderlying() const;
// Shares the string. Checks inline if the string is already shared or can be
// shared by transitioning its map in-place. If neither is possible, flattens
// and copies into a new shared sequential string.
static inline Handle<String> Share(Isolate* isolate, Handle<String> string);
// String relational comparison, implemented according to ES6 section 7.2.11
// Abstract Relational Comparison (step 5): The comparison of Strings uses a
// simple lexicographic ordering on sequences of code unit values. There is no
......@@ -443,6 +449,9 @@ class String : public TorqueGeneratedString<String, Name> {
inline bool IsFlat() const;
inline bool IsFlat(PtrComprCageBase cage_base) const;
inline bool IsShared() const;
inline bool IsShared(PtrComprCageBase cage_base) const;
// Max char codes.
static const int32_t kMaxOneByteCharCode = unibrow::Latin1::kMaxChar;
static const uint32_t kMaxOneByteCharCodeU = unibrow::Latin1::kMaxChar;
......@@ -603,6 +612,9 @@ class String : public TorqueGeneratedString<String, Name> {
static Handle<String> SlowCopy(Isolate* isolate, Handle<SeqString> source,
AllocationType allocation);
V8_EXPORT_PRIVATE static Handle<String> SlowShare(Isolate* isolate,
Handle<String> source);
// Slow case of String::Equals. This implementation works on any strings
// but it is most efficient on strings that are almost flat.
V8_EXPORT_PRIVATE bool SlowEquals(String other) const;
......@@ -709,11 +721,6 @@ class SeqOneByteString
// is deterministic.
void clear_padding();
// Garbage collection support. This method is called by the
// garbage collector to compute the actual size of an OneByteString
// instance.
inline int SeqOneByteStringSize(InstanceType instance_type);
// Maximal memory usage for a single sequential one-byte string.
static const int kMaxCharsSize = kMaxLength;
static const int kMaxSize = OBJECT_POINTER_ALIGN(kMaxCharsSize + kHeaderSize);
......@@ -721,6 +728,9 @@ class SeqOneByteString
int AllocatedSize();
// A SeqOneByteString have different maps depending on whether it is shared.
static inline bool IsCompatibleMap(Map map, ReadOnlyRoots roots);
class BodyDescriptor;
TQ_OBJECT_CONSTRUCTORS(SeqOneByteString)
......@@ -757,11 +767,6 @@ class SeqTwoByteString
// is deterministic.
void clear_padding();
// Garbage collection support. This method is called by the
// garbage collector to compute the actual size of a TwoByteString
// instance.
inline int SeqTwoByteStringSize(InstanceType instance_type);
// Maximal memory usage for a single sequential two-byte string.
static const int kMaxCharsSize = kMaxLength * 2;
static const int kMaxSize = OBJECT_POINTER_ALIGN(kMaxCharsSize + kHeaderSize);
......@@ -770,6 +775,9 @@ class SeqTwoByteString
int AllocatedSize();
// A SeqTwoByteString have different maps depending on whether it is shared.
static inline bool IsCompatibleMap(Map map, ReadOnlyRoots roots);
class BodyDescriptor;
TQ_OBJECT_CONSTRUCTORS(SeqTwoByteString)
......
......@@ -5,7 +5,7 @@
#include 'src/builtins/builtins-string-gen.h'
@abstract
@reserveBitsInInstanceType(6)
@reserveBitsInInstanceType(7)
extern class String extends Name {
macro StringInstanceType(): StringInstanceType {
return %RawDownCast<StringInstanceType>(
......@@ -32,6 +32,7 @@ bitfield struct StringInstanceType extends uint16 {
is_one_byte: bool: 1 bit;
is_uncached: bool: 1 bit;
is_not_internalized: bool: 1 bit;
is_shared: bool: 1 bit;
}
@generateBodyDescriptor
......
......@@ -142,6 +142,8 @@ class Symbol;
UncachedExternalOneByteInternalizedStringMap) \
V(Map, uncached_external_one_byte_string_map, \
UncachedExternalOneByteStringMap) \
V(Map, shared_one_byte_string_map, SharedOneByteStringMap) \
V(Map, shared_string_map, SharedStringMap) \
/* Oddball maps */ \
V(Map, undefined_map, UndefinedMap) \
V(Map, the_hole_map, TheHoleMap) \
......
......@@ -219,9 +219,9 @@ UNINITIALIZED_TEST(YoungInternalization) {
// Allocate two young strings with the same contents in isolate2 then intern
// them. They should be the same as the interned strings from isolate1.
Handle<String> young_one_byte_seq2 =
factory2->NewStringFromAsciiChecked(raw_one_byte, AllocationType::kOld);
factory2->NewStringFromAsciiChecked(raw_one_byte, AllocationType::kYoung);
Handle<String> young_two_byte_seq2 =
factory2->NewStringFromTwoByte(two_byte, AllocationType::kOld)
factory2->NewStringFromTwoByte(two_byte, AllocationType::kYoung)
.ToHandleChecked();
Handle<String> one_byte_intern2 =
factory2->InternalizeString(young_one_byte_seq2);
......@@ -330,6 +330,146 @@ UNINITIALIZED_TEST(ConcurrentInternalization) {
}
}
namespace {
void CheckSharedStringIsEqualCopy(Handle<String> shared,
Handle<String> original) {
CHECK(shared->IsShared());
CHECK(shared->Equals(*original));
CHECK_NE(*shared, *original);
}
Handle<String> ShareAndVerify(Isolate* isolate, Handle<String> string) {
Handle<String> shared = String::Share(isolate, string);
CHECK(shared->IsShared());
#ifdef VERIFY_HEAP
shared->ObjectVerify(isolate);
string->ObjectVerify(isolate);
#endif // VERIFY_HEAP
return shared;
}
} // namespace
UNINITIALIZED_TEST(StringShare) {
if (!ReadOnlyHeap::IsReadOnlySpaceShared()) return;
if (!COMPRESS_POINTERS_IN_SHARED_CAGE_BOOL) return;
FLAG_shared_string_table = true;
MultiClientIsolateTest test;
v8::Isolate* isolate = test.NewClientIsolate();
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
Factory* factory = i_isolate->factory();
HandleScope scope(i_isolate);
// A longer string so that concatenated to itself, the result is >
// ConsString::kMinLength.
const char raw_one_byte[] =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit";
base::uc16 raw_two_byte[] = {2001, 2002, 2003};
base::Vector<const base::uc16> two_byte(raw_two_byte, 3);
{
// Old-generation sequential strings are shared in-place.
Handle<String> one_byte_seq =
factory->NewStringFromAsciiChecked(raw_one_byte, AllocationType::kOld);
Handle<String> two_byte_seq =
factory->NewStringFromTwoByte(two_byte, AllocationType::kOld)
.ToHandleChecked();
CHECK(!one_byte_seq->IsShared());
CHECK(!two_byte_seq->IsShared());
Handle<String> shared_one_byte = ShareAndVerify(i_isolate, one_byte_seq);
Handle<String> shared_two_byte = ShareAndVerify(i_isolate, two_byte_seq);
CHECK_EQ(*one_byte_seq, *shared_one_byte);
CHECK_EQ(*two_byte_seq, *shared_two_byte);
}
{
// Internalized strings are always shared.
Handle<String> one_byte_seq =
factory->NewStringFromAsciiChecked(raw_one_byte, AllocationType::kOld);
Handle<String> two_byte_seq =
factory->NewStringFromTwoByte(two_byte, AllocationType::kOld)
.ToHandleChecked();
CHECK(!one_byte_seq->IsShared());
CHECK(!two_byte_seq->IsShared());
Handle<String> one_byte_intern = factory->InternalizeString(one_byte_seq);
Handle<String> two_byte_intern = factory->InternalizeString(two_byte_seq);
CHECK(one_byte_intern->IsShared());
CHECK(two_byte_intern->IsShared());
Handle<String> shared_one_byte_intern =
ShareAndVerify(i_isolate, one_byte_intern);
Handle<String> shared_two_byte_intern =
ShareAndVerify(i_isolate, two_byte_intern);
CHECK_EQ(*one_byte_intern, *shared_one_byte_intern);
CHECK_EQ(*two_byte_intern, *shared_two_byte_intern);
}
// All other strings are flattened then copied if the flatten didn't already
// create a new copy.
{
// Young strings
Handle<String> young_one_byte_seq = factory->NewStringFromAsciiChecked(
raw_one_byte, AllocationType::kYoung);
Handle<String> young_two_byte_seq =
factory->NewStringFromTwoByte(two_byte, AllocationType::kYoung)
.ToHandleChecked();
CHECK(!young_one_byte_seq->IsShared());
CHECK(!young_two_byte_seq->IsShared());
Handle<String> shared_one_byte =
ShareAndVerify(i_isolate, young_one_byte_seq);
Handle<String> shared_two_byte =
ShareAndVerify(i_isolate, young_two_byte_seq);
CheckSharedStringIsEqualCopy(shared_one_byte, young_one_byte_seq);
CheckSharedStringIsEqualCopy(shared_two_byte, young_two_byte_seq);
}
{
// Thin strings
Handle<String> one_byte_seq1 =
factory->NewStringFromAsciiChecked(raw_one_byte);
Handle<String> one_byte_seq2 =
factory->NewStringFromAsciiChecked(raw_one_byte);
CHECK(!one_byte_seq1->IsShared());
CHECK(!one_byte_seq2->IsShared());
factory->InternalizeString(one_byte_seq1);
factory->InternalizeString(one_byte_seq2);
CHECK(StringShape(*one_byte_seq2).IsThin());
Handle<String> shared = ShareAndVerify(i_isolate, one_byte_seq2);
CheckSharedStringIsEqualCopy(shared, one_byte_seq2);
}
{
// Cons strings
Handle<String> one_byte_seq1 =
factory->NewStringFromAsciiChecked(raw_one_byte);
Handle<String> one_byte_seq2 =
factory->NewStringFromAsciiChecked(raw_one_byte);
CHECK(!one_byte_seq1->IsShared());
CHECK(!one_byte_seq2->IsShared());
Handle<String> cons =
factory->NewConsString(one_byte_seq1, one_byte_seq2).ToHandleChecked();
CHECK(!cons->IsShared());
CHECK(cons->IsConsString());
Handle<String> shared = ShareAndVerify(i_isolate, cons);
CheckSharedStringIsEqualCopy(shared, cons);
}
{
// Sliced strings
Handle<String> one_byte_seq =
factory->NewStringFromAsciiChecked(raw_one_byte);
CHECK(!one_byte_seq->IsShared());
Handle<String> sliced =
factory->NewSubString(one_byte_seq, 1, one_byte_seq->length());
CHECK(!sliced->IsShared());
CHECK(sliced->IsSlicedString());
Handle<String> shared = ShareAndVerify(i_isolate, sliced);
CheckSharedStringIsEqualCopy(shared, sliced);
}
}
UNINITIALIZED_TEST(PromotionMarkCompact) {
if (FLAG_single_generation) return;
if (!ReadOnlyHeap::IsReadOnlySpaceShared()) return;
......
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