Commit cff06313 authored by Albert J. Wong's avatar Albert J. Wong Committed by Commit Bot

Make StringImpl flags atomic and threadsafe.

This leaves just the ref_count_ variable left as thread hostile.

CL is broken out of
https://chromium-review.googlesource.com/c/chromium/src/+/1557854

See this design doc for more details on the larger motivation for
this change:
https://docs.google.com/document/d/1hp7h6zfD5S6mnMI4cHe1PpacRnb2rNpRRkn1gBBJHuQ/edit#heading=h.erv1bv18t616

Bug: 1083392
Change-Id: I9ab3b1d86c8e27d887b6dbb34ac4ae4fd720af0b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2301589
Commit-Queue: Albert J. Wong <ajwong@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790158}
parent fc2e17b9
...@@ -37,7 +37,7 @@ struct UCharBufferTranslator { ...@@ -37,7 +37,7 @@ struct UCharBufferTranslator {
string->AddRef(); string->AddRef();
location = string.get(); location = string.get();
location->SetHash(hash); location->SetHash(hash);
location->SetIsAtomic(true); location->SetIsAtomic();
} }
}; };
...@@ -118,7 +118,7 @@ struct HashAndUTF8CharactersTranslator { ...@@ -118,7 +118,7 @@ struct HashAndUTF8CharactersTranslator {
new_string->AddRef(); new_string->AddRef();
location = new_string.get(); location = new_string.get();
location->SetHash(hash); location->SetHash(hash);
location->SetIsAtomic(true); location->SetIsAtomic();
} }
}; };
...@@ -181,7 +181,7 @@ AtomicStringTable::~AtomicStringTable() { ...@@ -181,7 +181,7 @@ AtomicStringTable::~AtomicStringTable() {
for (StringImpl* string : table_) { for (StringImpl* string : table_) {
if (!string->IsStatic()) { if (!string->IsStatic()) {
DCHECK(string->IsAtomic()); DCHECK(string->IsAtomic());
string->SetIsAtomic(false); string->UnsetIsAtomic();
} }
} }
} }
...@@ -230,7 +230,7 @@ struct LCharBufferTranslator { ...@@ -230,7 +230,7 @@ struct LCharBufferTranslator {
string->AddRef(); string->AddRef();
location = string.get(); location = string.get();
location->SetHash(hash); location->SetHash(hash);
location->SetIsAtomic(true); location->SetIsAtomic();
} }
}; };
...@@ -253,7 +253,7 @@ StringImpl* AtomicStringTable::Add(StringImpl* string) { ...@@ -253,7 +253,7 @@ StringImpl* AtomicStringTable::Add(StringImpl* string) {
StringImpl* result = *table_.insert(string).stored_value; StringImpl* result = *table_.insert(string).stored_value;
if (!result->IsAtomic()) if (!result->IsAtomic())
result->SetIsAtomic(true); result->SetIsAtomic();
DCHECK(!string->IsStatic() || result->IsStatic()); DCHECK(!string->IsStatic() || result->IsStatic());
return result; return result;
......
...@@ -55,7 +55,7 @@ struct SameSizeAsStringImpl { ...@@ -55,7 +55,7 @@ struct SameSizeAsStringImpl {
ASSERT_SIZE(StringImpl, SameSizeAsStringImpl); ASSERT_SIZE(StringImpl, SameSizeAsStringImpl);
} // anonymous namespace } // namespace
void* StringImpl::operator new(size_t size) { void* StringImpl::operator new(size_t size) {
DCHECK_EQ(size, sizeof(StringImpl)); DCHECK_EQ(size, sizeof(StringImpl));
...@@ -78,11 +78,18 @@ void StringImpl::DestroyIfNotStatic() const { ...@@ -78,11 +78,18 @@ void StringImpl::DestroyIfNotStatic() const {
delete this; delete this;
} }
void StringImpl::UpdateContainsOnlyASCIIOrEmpty() const { bool StringImpl::ContainsOnlyASCIIOrEmptySlowCase() const {
contains_only_ascii_ = Is8Bit() bool contains_only_ascii =
? CharactersAreAllASCII(Characters8(), length()) Is8Bit() ? CharactersAreAllASCII(Characters8(), length())
: CharactersAreAllASCII(Characters16(), length()); : CharactersAreAllASCII(Characters16(), length());
needs_ascii_check_ = false; uint32_t new_flags = kAsciiCheckDone;
if (contains_only_ascii)
new_flags |= kContainsOnlyAscii;
const uint32_t previous_flags =
hash_and_flags_.fetch_or(new_flags, std::memory_order_relaxed);
static constexpr uint32_t mask = kAsciiCheckDone | kContainsOnlyAscii;
DCHECK((previous_flags & mask) == 0 || (previous_flags & mask) == new_flags);
return contains_only_ascii;
} }
bool StringImpl::IsSafeToSendToAnotherThread() const { bool StringImpl::IsSafeToSendToAnotherThread() const {
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include <limits.h> #include <limits.h>
#include <string.h> #include <string.h>
#include <atomic>
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -92,14 +93,9 @@ class WTF_EXPORT StringImpl { ...@@ -92,14 +93,9 @@ class WTF_EXPORT StringImpl {
// ref-counted in a non-threadsafe manner. // ref-counted in a non-threadsafe manner.
enum ConstructEmptyStringTag { kConstructEmptyString }; enum ConstructEmptyStringTag { kConstructEmptyString };
explicit StringImpl(ConstructEmptyStringTag) explicit StringImpl(ConstructEmptyStringTag)
: ref_count_(1), : length_(0),
length_(0), hash_and_flags_(kAsciiCheckDone | kContainsOnlyAscii | kIs8Bit |
hash_(0), kIsStatic) {
contains_only_ascii_(true),
needs_ascii_check_(false),
is_atomic_(false),
is_8bit_(true),
is_static_(true) {
// Ensure that the hash is computed so that AtomicStringHash can call // Ensure that the hash is computed so that AtomicStringHash can call
// existingHash() with impunity. The empty string is special because it // existingHash() with impunity. The empty string is special because it
// is never entered into AtomicString's HashKey, but still needs to // is never entered into AtomicString's HashKey, but still needs to
...@@ -109,53 +105,28 @@ class WTF_EXPORT StringImpl { ...@@ -109,53 +105,28 @@ class WTF_EXPORT StringImpl {
enum ConstructEmptyString16BitTag { kConstructEmptyString16Bit }; enum ConstructEmptyString16BitTag { kConstructEmptyString16Bit };
explicit StringImpl(ConstructEmptyString16BitTag) explicit StringImpl(ConstructEmptyString16BitTag)
: ref_count_(1), : length_(0),
length_(0), hash_and_flags_(kAsciiCheckDone | kContainsOnlyAscii | kIsStatic) {
hash_(0),
contains_only_ascii_(true),
needs_ascii_check_(false),
is_atomic_(false),
is_8bit_(false),
is_static_(true) {
GetHash(); GetHash();
} }
// FIXME: there has to be a less hacky way to do this. // FIXME: there has to be a less hacky way to do this.
enum Force8Bit { kForce8BitConstructor }; enum Force8Bit { kForce8BitConstructor };
StringImpl(wtf_size_t length, Force8Bit) StringImpl(wtf_size_t length, Force8Bit)
: ref_count_(1), : length_(length), hash_and_flags_(LengthToAsciiFlags(length) | kIs8Bit) {
length_(length),
hash_(0),
contains_only_ascii_(!length),
needs_ascii_check_(static_cast<bool>(length)),
is_atomic_(false),
is_8bit_(true),
is_static_(false) {
DCHECK(length_); DCHECK(length_);
} }
StringImpl(wtf_size_t length) StringImpl(wtf_size_t length)
: ref_count_(1), : length_(length), hash_and_flags_(LengthToAsciiFlags(length)) {
length_(length),
hash_(0),
contains_only_ascii_(!length),
needs_ascii_check_(static_cast<bool>(length)),
is_atomic_(false),
is_8bit_(false),
is_static_(false) {
DCHECK(length_); DCHECK(length_);
} }
enum StaticStringTag { kStaticString }; enum StaticStringTag { kStaticString };
StringImpl(wtf_size_t length, wtf_size_t hash, StaticStringTag) StringImpl(wtf_size_t length, wtf_size_t hash, StaticStringTag)
: ref_count_(1), : length_(length),
length_(length), hash_and_flags_(hash << kHashShift | LengthToAsciiFlags(length) |
hash_(hash), kIs8Bit | kIsStatic) {}
contains_only_ascii_(!length),
needs_ascii_check_(static_cast<bool>(length)),
is_atomic_(false),
is_8bit_(true),
is_static_(true) {}
public: public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE(); REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
...@@ -201,7 +172,9 @@ class WTF_EXPORT StringImpl { ...@@ -201,7 +172,9 @@ class WTF_EXPORT StringImpl {
UChar*& data); UChar*& data);
wtf_size_t length() const { return length_; } wtf_size_t length() const { return length_; }
bool Is8Bit() const { return is_8bit_; } bool Is8Bit() const {
return hash_and_flags_.load(std::memory_order_relaxed) & kIs8Bit;
}
ALWAYS_INLINE const LChar* Characters8() const { ALWAYS_INLINE const LChar* Characters8() const {
DCHECK(Is8Bit()); DCHECK(Is8Bit());
...@@ -230,10 +203,21 @@ class WTF_EXPORT StringImpl { ...@@ -230,10 +203,21 @@ class WTF_EXPORT StringImpl {
return length() * (Is8Bit() ? sizeof(LChar) : sizeof(UChar)); return length() * (Is8Bit() ? sizeof(LChar) : sizeof(UChar));
} }
bool IsAtomic() const { return is_atomic_; } bool IsAtomic() const {
void SetIsAtomic(bool is_atomic) { is_atomic_ = is_atomic; } return hash_and_flags_.load(std::memory_order_acquire) & kIsAtomic;
}
void SetIsAtomic() {
hash_and_flags_.fetch_or(kIsAtomic, std::memory_order_release);
}
void UnsetIsAtomic() {
hash_and_flags_.fetch_and(~kIsAtomic, std::memory_order_release);
}
bool IsStatic() const { return is_static_; } bool IsStatic() const {
return hash_and_flags_.load(std::memory_order_relaxed) & kIsStatic;
}
bool ContainsOnlyASCIIOrEmpty() const; bool ContainsOnlyASCIIOrEmpty() const;
...@@ -244,27 +228,26 @@ class WTF_EXPORT StringImpl { ...@@ -244,27 +228,26 @@ class WTF_EXPORT StringImpl {
// access. So, we shift left and right when setting and getting our hash // access. So, we shift left and right when setting and getting our hash
// code. // code.
void SetHash(wtf_size_t hash) const { void SetHash(wtf_size_t hash) const {
DCHECK(!HasHash());
// Multiple clients assume that StringHasher is the canonical string // Multiple clients assume that StringHasher is the canonical string
// hash function. // hash function.
DCHECK(hash == (Is8Bit() ? StringHasher::ComputeHashAndMaskTop8Bits( DCHECK(hash == (Is8Bit() ? StringHasher::ComputeHashAndMaskTop8Bits(
Characters8(), length_) Characters8(), length_)
: StringHasher::ComputeHashAndMaskTop8Bits( : StringHasher::ComputeHashAndMaskTop8Bits(
Characters16(), length_))); Characters16(), length_)));
hash_ = hash;
DCHECK(hash); // Verify that 0 is a valid sentinel hash value. DCHECK(hash); // Verify that 0 is a valid sentinel hash value.
SetHashRaw(hash);
} }
bool HasHash() const { return hash_ != 0; } bool HasHash() const { return GetHashRaw() != 0; }
wtf_size_t ExistingHash() const { wtf_size_t ExistingHash() const {
DCHECK(HasHash()); DCHECK(HasHash());
return hash_; return GetHashRaw();
} }
wtf_size_t GetHash() const { wtf_size_t GetHash() const {
if (HasHash()) if (wtf_size_t hash = GetHashRaw())
return ExistingHash(); return hash;
return HashSlowCase(); return HashSlowCase();
} }
...@@ -459,6 +442,57 @@ class WTF_EXPORT StringImpl { ...@@ -459,6 +442,57 @@ class WTF_EXPORT StringImpl {
static const UChar kLatin1CaseFoldTable[256]; static const UChar kLatin1CaseFoldTable[256];
private: private:
enum Flags {
// These two fields are never modified for the lifetime of the StringImpl.
// It is therefore safe to read them with a relaxed operation.
kIs8Bit = 1 << 0,
kIsStatic = 1 << 1,
// This is the only flag that can be both set and unset. It is safe to do
// so because all accesses are mediated by the same atomic string table and
// so protected by thread locality (pre-unification) or a mutex
// (post-unification). Thus these accesses can also be relaxed.
kIsAtomic = 1 << 2,
// These two bits are set atomically together. They are initially both
// zero, and like the hash computation below, become non-zero only as part
// of a single atomic bitwise or. Thus concurrent loads will always observe
// either a state where the ASCII check has not been completed and both
// bits are zero, or a state where the state is fully populated.
kAsciiCheckDone = 1 << 3,
kContainsOnlyAscii = 1 << 4,
// The last 24 bits (past kHashShift) are reserved for the hash.
// These bits are all zero if the hash is uncomputed, and the hash is
// atomically stored with bitwise or.
//
// Therefore a relaxed read can be used, and will either observe an
// uncomputed hash (if the fetch_or is not yet visible on this thread)
// or the correct hash (if it is). It is possible for a thread to compute
// the hash for a second time if there is a race. This is safe, since
// storing the same bits again with a bitwise or is idempotent.
};
// Hash value is 24 bits.
constexpr static int kHashShift = (sizeof(unsigned) * 8) - 24;
static inline constexpr uint32_t LengthToAsciiFlags(int length) {
return length ? 0 : kAsciiCheckDone | kContainsOnlyAscii;
}
void SetHashRaw(unsigned hash_val) const {
// Setting the hash is idempotent so fetch_or() is sufficient. DCHECK()
// as a sanity check.
unsigned previous_value = hash_and_flags_.fetch_or(
hash_val << kHashShift, std::memory_order_relaxed);
DCHECK(((previous_value >> kHashShift) == 0) ||
((previous_value >> kHashShift) == hash_val));
}
unsigned GetHashRaw() const {
return hash_and_flags_.load(std::memory_order_relaxed) >> kHashShift;
}
template <typename CharType> template <typename CharType>
static size_t AllocationSize(wtf_size_t length) { static size_t AllocationSize(wtf_size_t length) {
static_assert( static_assert(
...@@ -485,7 +519,7 @@ class WTF_EXPORT StringImpl { ...@@ -485,7 +519,7 @@ class WTF_EXPORT StringImpl {
NOINLINE wtf_size_t HashSlowCase() const; NOINLINE wtf_size_t HashSlowCase() const;
void DestroyIfNotStatic() const; void DestroyIfNotStatic() const;
void UpdateContainsOnlyASCIIOrEmpty() const; bool ContainsOnlyASCIIOrEmptySlowCase() const;
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
std::string AsciiForDebugging() const; std::string AsciiForDebugging() const;
...@@ -501,18 +535,12 @@ class WTF_EXPORT StringImpl { ...@@ -501,18 +535,12 @@ class WTF_EXPORT StringImpl {
} }
#endif #endif
private:
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
mutable ThreadRestrictionVerifier verifier_; mutable ThreadRestrictionVerifier verifier_;
#endif #endif
mutable unsigned ref_count_; mutable unsigned ref_count_{1};
const unsigned length_; const unsigned length_;
mutable unsigned hash_ : 24; mutable std::atomic_uint32_t hash_and_flags_;
mutable unsigned contains_only_ascii_ : 1;
mutable unsigned needs_ascii_check_ : 1;
unsigned is_atomic_ : 1;
const unsigned is_8bit_ : 1;
const unsigned is_static_ : 1;
DISALLOW_COPY_AND_ASSIGN(StringImpl); DISALLOW_COPY_AND_ASSIGN(StringImpl);
}; };
...@@ -554,9 +582,10 @@ inline bool Equal(const char* a, StringImpl* b) { ...@@ -554,9 +582,10 @@ inline bool Equal(const char* a, StringImpl* b) {
WTF_EXPORT bool EqualNonNull(const StringImpl* a, const StringImpl* b); WTF_EXPORT bool EqualNonNull(const StringImpl* a, const StringImpl* b);
ALWAYS_INLINE bool StringImpl::ContainsOnlyASCIIOrEmpty() const { ALWAYS_INLINE bool StringImpl::ContainsOnlyASCIIOrEmpty() const {
if (needs_ascii_check_) uint32_t flags = hash_and_flags_.load(std::memory_order_relaxed);
UpdateContainsOnlyASCIIOrEmpty(); if (flags & kAsciiCheckDone)
return contains_only_ascii_; return flags & kContainsOnlyAscii;
return ContainsOnlyASCIIOrEmptySlowCase();
} }
template <typename CharType> template <typename CharType>
......
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