cppgc: Provide operator==(Raw, Member) to avoid Member decompression

The operator with raw pointer allows us to avoid Member decompression,
which is more expensive than compression. It's also quite frequently
called (e.g. in HeapHashSet::find()).

The existing operator
  template <...>
  bool operator==(const Member<T1>&, const Member<T2>&);
was not called for
  GCed* raw = ...;
  member == raw;
because the compiler wouldn't deduce `T2` in `const Member<T2>` as
`GCed` when the initializer expression `raw` is of different type
(`GCed*`).

Bug: chromium:1325007
Change-Id: Ie1ee12bad28081c66f4e08a146467fd7c040bb70
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3757344
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Anton Bikineev <bikineev@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81702}
This commit is contained in:
Anton Bikineev 2022-07-13 20:56:06 +02:00 committed by V8 LUCI CQ
parent 7ccbd7bed8
commit 4dee3fbd37
3 changed files with 201 additions and 33 deletions

View File

@ -67,7 +67,7 @@ class CageBaseGlobal final {
class CompressedPointer final { class CompressedPointer final {
public: public:
using Storage = uint32_t; using IntegralType = uint32_t;
V8_INLINE CompressedPointer() : value_(0u) {} V8_INLINE CompressedPointer() : value_(0u) {}
V8_INLINE explicit CompressedPointer(const void* ptr) V8_INLINE explicit CompressedPointer(const void* ptr)
@ -79,24 +79,26 @@ class CompressedPointer final {
V8_INLINE const void* Load() const { return Decompress(value_); } V8_INLINE const void* Load() const { return Decompress(value_); }
V8_INLINE const void* LoadAtomic() const { V8_INLINE const void* LoadAtomic() const {
return Decompress( return Decompress(
reinterpret_cast<const std::atomic<Storage>&>(value_).load( reinterpret_cast<const std::atomic<IntegralType>&>(value_).load(
std::memory_order_relaxed)); std::memory_order_relaxed));
} }
V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); } V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); }
V8_INLINE void StoreAtomic(const void* value) { V8_INLINE void StoreAtomic(const void* value) {
reinterpret_cast<std::atomic<Storage>&>(value_).store( reinterpret_cast<std::atomic<IntegralType>&>(value_).store(
Compress(value), std::memory_order_relaxed); Compress(value), std::memory_order_relaxed);
} }
V8_INLINE void Clear() { value_ = 0u; } V8_INLINE void Clear() { value_ = 0u; }
V8_INLINE bool IsCleared() const { return !value_; } V8_INLINE bool IsCleared() const { return !value_; }
V8_INLINE uint32_t GetAsInteger() const { return value_; }
V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) { V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) {
return a.value_ == b.value_; return a.value_ == b.value_;
} }
static V8_INLINE Storage Compress(const void* ptr) { static V8_INLINE IntegralType Compress(const void* ptr) {
static_assert( static_assert(
SentinelPointer::kSentinelValue == 0b10, SentinelPointer::kSentinelValue == 0b10,
"The compression scheme relies on the sentinel encoded as 0b10"); "The compression scheme relies on the sentinel encoded as 0b10");
@ -111,14 +113,14 @@ class CompressedPointer final {
const auto uptr = reinterpret_cast<uintptr_t>(ptr); const auto uptr = reinterpret_cast<uintptr_t>(ptr);
// Shift the pointer by one and truncate. // Shift the pointer by one and truncate.
auto compressed = static_cast<Storage>(uptr >> 1); auto compressed = static_cast<IntegralType>(uptr >> 1);
// Normal compressed pointers must have the MSB set. // Normal compressed pointers must have the MSB set.
CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) || CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) ||
(compressed & 0x80000000)); (compressed & 0x80000000));
return compressed; return compressed;
} }
static V8_INLINE void* Decompress(Storage ptr) { static V8_INLINE void* Decompress(IntegralType ptr) {
CPPGC_DCHECK(CageBaseGlobal::IsSet()); CPPGC_DCHECK(CageBaseGlobal::IsSet());
const uintptr_t base = CageBaseGlobal::Get(); const uintptr_t base = CageBaseGlobal::Get();
// Treat compressed pointer as signed and cast it to uint64_t, which will // Treat compressed pointer as signed and cast it to uint64_t, which will
@ -129,19 +131,19 @@ class CompressedPointer final {
} }
private: private:
static constexpr Storage kCompressedSentinel = static constexpr IntegralType kCompressedSentinel =
SentinelPointer::kSentinelValue >> 1; SentinelPointer::kSentinelValue >> 1;
// All constructors initialize `value_`. Do not add a default value here as it // All constructors initialize `value_`. Do not add a default value here as it
// results in a non-atomic write on some builds, even when the atomic version // results in a non-atomic write on some builds, even when the atomic version
// of the constructor is used. // of the constructor is used.
Storage value_; IntegralType value_;
}; };
#endif // defined(CPPGC_POINTER_COMPRESSION) #endif // defined(CPPGC_POINTER_COMPRESSION)
class RawPointer final { class RawPointer final {
public: public:
using Storage = uintptr_t; using IntegralType = uintptr_t;
V8_INLINE RawPointer() : ptr_(nullptr) {} V8_INLINE RawPointer() : ptr_(nullptr) {}
V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {} V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {}
@ -161,6 +163,10 @@ class RawPointer final {
V8_INLINE void Clear() { ptr_ = nullptr; } V8_INLINE void Clear() { ptr_ = nullptr; }
V8_INLINE bool IsCleared() const { return !ptr_; } V8_INLINE bool IsCleared() const { return !ptr_; }
V8_INLINE uintptr_t GetAsInteger() const {
return reinterpret_cast<uintptr_t>(ptr_);
}
V8_INLINE friend bool operator==(RawPointer a, RawPointer b) { V8_INLINE friend bool operator==(RawPointer a, RawPointer b) {
return a.ptr_ == b.ptr_; return a.ptr_ == b.ptr_;
} }
@ -175,13 +181,13 @@ class RawPointer final {
// MemberBase always refers to the object as const object and defers to // MemberBase always refers to the object as const object and defers to
// BasicMember on casting to the right type as needed. // BasicMember on casting to the right type as needed.
class MemberBase { class MemberBase {
protected: public:
#if defined(CPPGC_POINTER_COMPRESSION) #if defined(CPPGC_POINTER_COMPRESSION)
using RawStorage = CompressedPointer; using RawStorage = CompressedPointer;
#else // !defined(CPPGC_POINTER_COMPRESSION) #else // !defined(CPPGC_POINTER_COMPRESSION)
using RawStorage = RawPointer; using RawStorage = RawPointer;
#endif // !defined(CPPGC_POINTER_COMPRESSION) #endif // !defined(CPPGC_POINTER_COMPRESSION)
protected:
struct AtomicInitializerTag {}; struct AtomicInitializerTag {};
V8_INLINE MemberBase() = default; V8_INLINE MemberBase() = default;
@ -253,30 +259,53 @@ class BasicMember final : private MemberBase, private CheckingPolicy {
// Copy ctor. // Copy ctor.
V8_INLINE BasicMember(const BasicMember& other) V8_INLINE BasicMember(const BasicMember& other)
: BasicMember(other.GetRawStorage()) {} : BasicMember(other.GetRawStorage()) {}
// Allow heterogeneous construction.
// Heterogeneous copy constructors. When the source pointer have a different
// type, perform a compress-decompress round, because the source pointer may
// need to be adjusted.
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag, template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
typename OtherCheckingPolicy, typename OtherCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>> std::enable_if_t<internal::IsDecayedSameV<T, U>>* = nullptr>
V8_INLINE BasicMember( // NOLINT V8_INLINE BasicMember( // NOLINT
const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
OtherCheckingPolicy>& other) OtherCheckingPolicy>& other)
: BasicMember(other.GetRawStorage()) {} : BasicMember(other.GetRawStorage()) {}
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
typename OtherCheckingPolicy,
std::enable_if_t<internal::IsStrictlyBaseOfV<T, U>>* = nullptr>
V8_INLINE BasicMember( // NOLINT
const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
OtherCheckingPolicy>& other)
: BasicMember(other.Get()) {}
// Move ctor. // Move ctor.
V8_INLINE BasicMember(BasicMember&& other) noexcept V8_INLINE BasicMember(BasicMember&& other) noexcept
: BasicMember(other.GetRawStorage()) { : BasicMember(other.GetRawStorage()) {
other.Clear(); other.Clear();
} }
// Allow heterogeneous move construction.
// Heterogeneous move constructors. When the source pointer have a different
// type, perform a compress-decompress round, because the source pointer may
// need to be adjusted.
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag, template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
typename OtherCheckingPolicy, typename OtherCheckingPolicy,
typename = std::enable_if_t<std::is_base_of<T, U>::value>> std::enable_if_t<internal::IsDecayedSameV<T, U>>* = nullptr>
V8_INLINE BasicMember(BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, V8_INLINE BasicMember(BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
OtherCheckingPolicy>&& other) noexcept OtherCheckingPolicy>&& other) noexcept
: BasicMember(other.GetRawStorage()) { : BasicMember(other.GetRawStorage()) {
other.Clear(); other.Clear();
} }
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
typename OtherCheckingPolicy,
std::enable_if_t<internal::IsStrictlyBaseOfV<T, U>>* = nullptr>
V8_INLINE BasicMember(BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
OtherCheckingPolicy>&& other) noexcept
: BasicMember(other.Get()) {
other.Clear();
}
// Construction from Persistent. // Construction from Persistent.
template <typename U, typename PersistentWeaknessPolicy, template <typename U, typename PersistentWeaknessPolicy,
typename PersistentLocationPolicy, typename PersistentLocationPolicy,
@ -291,14 +320,21 @@ class BasicMember final : private MemberBase, private CheckingPolicy {
V8_INLINE BasicMember& operator=(const BasicMember& other) { V8_INLINE BasicMember& operator=(const BasicMember& other) {
return operator=(other.GetRawStorage()); return operator=(other.GetRawStorage());
} }
// Allow heterogeneous copy assignment.
// Heterogeneous copy assignment. When the source pointer have a different
// type, perform a compress-decompress round, because the source pointer may
// need to be adjusted.
template <typename U, typename OtherWeaknessTag, typename OtherBarrierPolicy, template <typename U, typename OtherWeaknessTag, typename OtherBarrierPolicy,
typename OtherCheckingPolicy, typename OtherCheckingPolicy>
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
V8_INLINE BasicMember& operator=( V8_INLINE BasicMember& operator=(
const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
OtherCheckingPolicy>& other) { OtherCheckingPolicy>& other) {
if constexpr (internal::IsDecayedSameV<T, U>) {
return operator=(other.GetRawStorage()); return operator=(other.GetRawStorage());
} else {
static_assert(internal::IsStrictlyBaseOfV<T, U>);
return operator=(other.Get());
}
} }
// Move assignment. // Move assignment.
@ -307,14 +343,21 @@ class BasicMember final : private MemberBase, private CheckingPolicy {
other.Clear(); other.Clear();
return *this; return *this;
} }
// Heterogeneous move assignment.
// Heterogeneous move assignment. When the source pointer have a different
// type, perform a compress-decompress round, because the source pointer may
// need to be adjusted.
template <typename U, typename OtherWeaknessTag, typename OtherBarrierPolicy, template <typename U, typename OtherWeaknessTag, typename OtherBarrierPolicy,
typename OtherCheckingPolicy, typename OtherCheckingPolicy>
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
V8_INLINE BasicMember& operator=( V8_INLINE BasicMember& operator=(
BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
OtherCheckingPolicy>&& other) noexcept { OtherCheckingPolicy>&& other) noexcept {
if constexpr (internal::IsDecayedSameV<T, U>) {
operator=(other.GetRawStorage()); operator=(other.GetRawStorage());
} else {
static_assert(internal::IsStrictlyBaseOfV<T, U>);
operator=(other.Get());
}
other.Clear(); other.Clear();
return *this; return *this;
} }
@ -385,6 +428,10 @@ class BasicMember final : private MemberBase, private CheckingPolicy {
return reinterpret_cast<const T**>(GetRawSlot()); return reinterpret_cast<const T**>(GetRawSlot());
} }
V8_INLINE RawStorage GetRawStorage() const {
return MemberBase::GetRawStorage();
}
private: private:
V8_INLINE explicit BasicMember(RawStorage raw) : MemberBase(raw) { V8_INLINE explicit BasicMember(RawStorage raw) : MemberBase(raw) {
InitializingWriteBarrier(); InitializingWriteBarrier();
@ -419,14 +466,6 @@ class BasicMember final : private MemberBase, private CheckingPolicy {
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1, template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
typename CheckingPolicy1> typename CheckingPolicy1>
friend class BasicMember; friend class BasicMember;
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
typename WriteBarrierPolicy2, typename CheckingPolicy2>
friend bool operator==(
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1>&
member1,
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2>&
member2);
}; };
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1, template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
@ -437,7 +476,15 @@ V8_INLINE bool operator==(
member1, member1,
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2>& const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2>&
member2) { member2) {
if constexpr (internal::IsDecayedSameV<T1, T2>) {
// Check compressed pointers if types are the same.
return member1.GetRawStorage() == member2.GetRawStorage(); return member1.GetRawStorage() == member2.GetRawStorage();
} else {
static_assert(internal::IsStrictlyBaseOfV<T1, T2> ||
internal::IsStrictlyBaseOfV<T2, T1>);
// Otherwise, check decompressed pointers.
return member1.Get() == member2.Get();
}
} }
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1, template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
@ -451,6 +498,51 @@ V8_INLINE bool operator!=(
return !(member1 == member2); return !(member1 == member2);
} }
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
typename CheckingPolicy, typename U>
V8_INLINE bool operator==(const BasicMember<T, WeaknessTag, WriteBarrierPolicy,
CheckingPolicy>& member,
U* raw) {
// Never allow comparison with erased pointers.
static_assert(!internal::IsDecayedSameV<void, U>);
if constexpr (internal::IsDecayedSameV<T, U>) {
// Check compressed pointers if types are the same.
return member.GetRawStorage() == MemberBase::RawStorage(raw);
} else if constexpr (internal::IsStrictlyBaseOfV<T, U>) {
// Cast the raw pointer to T, which may adjust the pointer.
return member.GetRawStorage() ==
MemberBase::RawStorage(static_cast<T*>(raw));
} else {
// Otherwise, decompressed the member.
return member.Get() == raw;
}
}
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
typename CheckingPolicy, typename U>
V8_INLINE bool operator!=(const BasicMember<T, WeaknessTag, WriteBarrierPolicy,
CheckingPolicy>& member,
U* raw) {
return !(member == raw);
}
template <typename T, typename U, typename WeaknessTag,
typename WriteBarrierPolicy, typename CheckingPolicy>
V8_INLINE bool operator==(T* raw,
const BasicMember<U, WeaknessTag, WriteBarrierPolicy,
CheckingPolicy>& member) {
return member == raw;
}
template <typename T, typename U, typename WeaknessTag,
typename WriteBarrierPolicy, typename CheckingPolicy>
V8_INLINE bool operator!=(T* raw,
const BasicMember<U, WeaknessTag, WriteBarrierPolicy,
CheckingPolicy>& member) {
return !(raw == member);
}
template <typename T, typename WriteBarrierPolicy, typename CheckingPolicy> template <typename T, typename WriteBarrierPolicy, typename CheckingPolicy>
struct IsWeak< struct IsWeak<
internal::BasicMember<T, WeakMemberTag, WriteBarrierPolicy, CheckingPolicy>> internal::BasicMember<T, WeakMemberTag, WriteBarrierPolicy, CheckingPolicy>>

View File

@ -170,6 +170,15 @@ struct IsComplete {
decltype(IsSizeOfKnown(std::declval<T*>()))::value; decltype(IsSizeOfKnown(std::declval<T*>()))::value;
}; };
template <typename T, typename U>
constexpr bool IsDecayedSameV =
std::is_same_v<std::decay_t<T>, std::decay_t<U>>;
template <typename B, typename D>
constexpr bool IsStrictlyBaseOfV =
std::is_base_of_v<std::decay_t<B>, std::decay_t<D>> &&
!IsDecayedSameV<B, D>;
} // namespace internal } // namespace internal
/** /**

View File

@ -21,9 +21,15 @@ namespace internal {
namespace { namespace {
struct GCed : GarbageCollected<GCed> { struct GCed : GarbageCollected<GCed> {
double d;
virtual void Trace(cppgc::Visitor*) const {} virtual void Trace(cppgc::Visitor*) const {}
}; };
struct DerivedGCed : GCed {
struct DerivedMixin : GarbageCollectedMixin {
void Trace(cppgc::Visitor* v) const override {}
};
struct DerivedGCed : GCed, DerivedMixin {
void Trace(cppgc::Visitor* v) const override { GCed::Trace(v); } void Trace(cppgc::Visitor* v) const override { GCed::Trace(v); }
}; };
@ -335,10 +341,19 @@ void EqualityTest(cppgc::Heap* heap) {
MemberType1<GCed> member1 = gced; MemberType1<GCed> member1 = gced;
MemberType2<GCed> member2 = gced; MemberType2<GCed> member2 = gced;
EXPECT_TRUE(member1 == member2); EXPECT_TRUE(member1 == member2);
EXPECT_TRUE(member1 == gced);
EXPECT_TRUE(member2 == gced);
EXPECT_FALSE(member1 != member2); EXPECT_FALSE(member1 != member2);
EXPECT_FALSE(member1 != gced);
EXPECT_FALSE(member2 != gced);
member2 = member1; member2 = member1;
EXPECT_TRUE(member1 == member2); EXPECT_TRUE(member1 == member2);
EXPECT_TRUE(member1 == gced);
EXPECT_TRUE(member2 == gced);
EXPECT_FALSE(member1 != member2); EXPECT_FALSE(member1 != member2);
EXPECT_FALSE(member1 != gced);
EXPECT_FALSE(member2 != gced);
} }
{ {
MemberType1<GCed> member1 = MemberType1<GCed> member1 =
@ -346,7 +361,9 @@ void EqualityTest(cppgc::Heap* heap) {
MemberType2<GCed> member2 = MemberType2<GCed> member2 =
MakeGarbageCollected<GCed>(heap->GetAllocationHandle()); MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
EXPECT_TRUE(member1 != member2); EXPECT_TRUE(member1 != member2);
EXPECT_TRUE(member1 != member2.Get());
EXPECT_FALSE(member1 == member2); EXPECT_FALSE(member1 == member2);
EXPECT_FALSE(member1 == member2.Get());
} }
} }
@ -363,6 +380,56 @@ TEST_F(MemberTest, EqualityTest) {
EqualityTest<UntracedMember, UntracedMember>(heap); EqualityTest<UntracedMember, UntracedMember>(heap);
} }
TEST_F(MemberTest, HeterogeneousEqualityTest) {
cppgc::Heap* heap = GetHeap();
{
auto* gced = MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
auto* derived = static_cast<DerivedMixin*>(gced);
ASSERT_NE(reinterpret_cast<void*>(gced), reinterpret_cast<void*>(derived));
}
{
auto* gced = MakeGarbageCollected<DerivedGCed>(heap->GetAllocationHandle());
Member<DerivedGCed> member = gced;
#define EXPECT_MIXIN_EQUAL(Mixin) \
EXPECT_TRUE(member == mixin); \
EXPECT_TRUE(member == gced); \
EXPECT_TRUE(mixin == gced); \
EXPECT_FALSE(member != mixin); \
EXPECT_FALSE(member != gced); \
EXPECT_FALSE(mixin != gced);
{
// Construct from raw.
Member<DerivedMixin> mixin = gced;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Copy construct from member.
Member<DerivedMixin> mixin = member;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Move construct from member.
Member<DerivedMixin> mixin = std::move(member);
member = gced;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Copy assign from member.
Member<DerivedMixin> mixin;
mixin = member;
EXPECT_MIXIN_EQUAL(mixin);
}
{
// Move assign from member.
Member<DerivedMixin> mixin;
mixin = std::move(member);
member = gced;
EXPECT_MIXIN_EQUAL(mixin);
}
#undef EXPECT_MIXIN_EQUAL
}
}
TEST_F(MemberTest, WriteBarrierTriggered) { TEST_F(MemberTest, WriteBarrierTriggered) {
CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered = 0; CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered = 0;
CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered = 0; CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered = 0;