diff --git a/include/cppgc/member.h b/include/cppgc/member.h index 3733e25e60..1781bc3c87 100644 --- a/include/cppgc/member.h +++ b/include/cppgc/member.h @@ -67,7 +67,7 @@ class CageBaseGlobal final { class CompressedPointer final { public: - using Storage = uint32_t; + using IntegralType = uint32_t; V8_INLINE CompressedPointer() : value_(0u) {} 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* LoadAtomic() const { return Decompress( - reinterpret_cast&>(value_).load( + reinterpret_cast&>(value_).load( std::memory_order_relaxed)); } V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); } V8_INLINE void StoreAtomic(const void* value) { - reinterpret_cast&>(value_).store( + reinterpret_cast&>(value_).store( Compress(value), std::memory_order_relaxed); } V8_INLINE void Clear() { value_ = 0u; } V8_INLINE bool IsCleared() const { return !value_; } + V8_INLINE uint32_t GetAsInteger() const { return value_; } + V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) { return a.value_ == b.value_; } - static V8_INLINE Storage Compress(const void* ptr) { + static V8_INLINE IntegralType Compress(const void* ptr) { static_assert( SentinelPointer::kSentinelValue == 0b10, "The compression scheme relies on the sentinel encoded as 0b10"); @@ -111,14 +113,14 @@ class CompressedPointer final { const auto uptr = reinterpret_cast(ptr); // Shift the pointer by one and truncate. - auto compressed = static_cast(uptr >> 1); + auto compressed = static_cast(uptr >> 1); // Normal compressed pointers must have the MSB set. CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) || (compressed & 0x80000000)); return compressed; } - static V8_INLINE void* Decompress(Storage ptr) { + static V8_INLINE void* Decompress(IntegralType ptr) { CPPGC_DCHECK(CageBaseGlobal::IsSet()); const uintptr_t base = CageBaseGlobal::Get(); // Treat compressed pointer as signed and cast it to uint64_t, which will @@ -129,19 +131,19 @@ class CompressedPointer final { } private: - static constexpr Storage kCompressedSentinel = + static constexpr IntegralType kCompressedSentinel = SentinelPointer::kSentinelValue >> 1; // 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 // of the constructor is used. - Storage value_; + IntegralType value_; }; #endif // defined(CPPGC_POINTER_COMPRESSION) class RawPointer final { public: - using Storage = uintptr_t; + using IntegralType = uintptr_t; V8_INLINE RawPointer() : ptr_(nullptr) {} V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {} @@ -161,6 +163,10 @@ class RawPointer final { V8_INLINE void Clear() { ptr_ = nullptr; } V8_INLINE bool IsCleared() const { return !ptr_; } + V8_INLINE uintptr_t GetAsInteger() const { + return reinterpret_cast(ptr_); + } + V8_INLINE friend bool operator==(RawPointer a, RawPointer b) { return a.ptr_ == b.ptr_; } @@ -175,13 +181,13 @@ class RawPointer final { // MemberBase always refers to the object as const object and defers to // BasicMember on casting to the right type as needed. class MemberBase { - protected: + public: #if defined(CPPGC_POINTER_COMPRESSION) using RawStorage = CompressedPointer; #else // !defined(CPPGC_POINTER_COMPRESSION) using RawStorage = RawPointer; #endif // !defined(CPPGC_POINTER_COMPRESSION) - + protected: struct AtomicInitializerTag {}; V8_INLINE MemberBase() = default; @@ -253,30 +259,53 @@ class BasicMember final : private MemberBase, private CheckingPolicy { // Copy ctor. V8_INLINE BasicMember(const BasicMember& other) : 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 ::value>> + std::enable_if_t>* = nullptr> V8_INLINE BasicMember( // NOLINT const BasicMember& other) : BasicMember(other.GetRawStorage()) {} + template >* = nullptr> + V8_INLINE BasicMember( // NOLINT + const BasicMember& other) + : BasicMember(other.Get()) {} + // Move ctor. V8_INLINE BasicMember(BasicMember&& other) noexcept : BasicMember(other.GetRawStorage()) { 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 ::value>> + std::enable_if_t>* = nullptr> V8_INLINE BasicMember(BasicMember&& other) noexcept : BasicMember(other.GetRawStorage()) { other.Clear(); } + template >* = nullptr> + V8_INLINE BasicMember(BasicMember&& other) noexcept + : BasicMember(other.Get()) { + other.Clear(); + } + // Construction from Persistent. template ::value>> + typename OtherCheckingPolicy> V8_INLINE BasicMember& operator=( const BasicMember& other) { - return operator=(other.GetRawStorage()); + if constexpr (internal::IsDecayedSameV) { + return operator=(other.GetRawStorage()); + } else { + static_assert(internal::IsStrictlyBaseOfV); + return operator=(other.Get()); + } } // Move assignment. @@ -307,14 +343,21 @@ class BasicMember final : private MemberBase, private CheckingPolicy { other.Clear(); 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 ::value>> + typename OtherCheckingPolicy> V8_INLINE BasicMember& operator=( BasicMember&& other) noexcept { - operator=(other.GetRawStorage()); + if constexpr (internal::IsDecayedSameV) { + operator=(other.GetRawStorage()); + } else { + static_assert(internal::IsStrictlyBaseOfV); + operator=(other.Get()); + } other.Clear(); return *this; } @@ -385,6 +428,10 @@ class BasicMember final : private MemberBase, private CheckingPolicy { return reinterpret_cast(GetRawSlot()); } + V8_INLINE RawStorage GetRawStorage() const { + return MemberBase::GetRawStorage(); + } + private: V8_INLINE explicit BasicMember(RawStorage raw) : MemberBase(raw) { InitializingWriteBarrier(); @@ -419,14 +466,6 @@ class BasicMember final : private MemberBase, private CheckingPolicy { template friend class BasicMember; - template - friend bool operator==( - const BasicMember& - member1, - const BasicMember& - member2); }; template & member2) { - return member1.GetRawStorage() == member2.GetRawStorage(); + if constexpr (internal::IsDecayedSameV) { + // Check compressed pointers if types are the same. + return member1.GetRawStorage() == member2.GetRawStorage(); + } else { + static_assert(internal::IsStrictlyBaseOfV || + internal::IsStrictlyBaseOfV); + // Otherwise, check decompressed pointers. + return member1.Get() == member2.Get(); + } } template +V8_INLINE bool operator==(const BasicMember& member, + U* raw) { + // Never allow comparison with erased pointers. + static_assert(!internal::IsDecayedSameV); + + if constexpr (internal::IsDecayedSameV) { + // Check compressed pointers if types are the same. + return member.GetRawStorage() == MemberBase::RawStorage(raw); + } else if constexpr (internal::IsStrictlyBaseOfV) { + // Cast the raw pointer to T, which may adjust the pointer. + return member.GetRawStorage() == + MemberBase::RawStorage(static_cast(raw)); + } else { + // Otherwise, decompressed the member. + return member.Get() == raw; + } +} + +template +V8_INLINE bool operator!=(const BasicMember& member, + U* raw) { + return !(member == raw); +} + +template +V8_INLINE bool operator==(T* raw, + const BasicMember& member) { + return member == raw; +} + +template +V8_INLINE bool operator!=(T* raw, + const BasicMember& member) { + return !(raw == member); +} + template struct IsWeak< internal::BasicMember> diff --git a/include/cppgc/type-traits.h b/include/cppgc/type-traits.h index 970ffd4841..2f499e6886 100644 --- a/include/cppgc/type-traits.h +++ b/include/cppgc/type-traits.h @@ -170,6 +170,15 @@ struct IsComplete { decltype(IsSizeOfKnown(std::declval()))::value; }; +template +constexpr bool IsDecayedSameV = + std::is_same_v, std::decay_t>; + +template +constexpr bool IsStrictlyBaseOfV = + std::is_base_of_v, std::decay_t> && + !IsDecayedSameV; + } // namespace internal /** diff --git a/test/unittests/heap/cppgc/member-unittest.cc b/test/unittests/heap/cppgc/member-unittest.cc index ea4f9c0e82..180ef65348 100644 --- a/test/unittests/heap/cppgc/member-unittest.cc +++ b/test/unittests/heap/cppgc/member-unittest.cc @@ -21,9 +21,15 @@ namespace internal { namespace { struct GCed : GarbageCollected { + double d; 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); } }; @@ -335,10 +341,19 @@ void EqualityTest(cppgc::Heap* heap) { MemberType1 member1 = gced; MemberType2 member2 = gced; EXPECT_TRUE(member1 == member2); + EXPECT_TRUE(member1 == gced); + EXPECT_TRUE(member2 == gced); EXPECT_FALSE(member1 != member2); + EXPECT_FALSE(member1 != gced); + EXPECT_FALSE(member2 != gced); + member2 = member1; EXPECT_TRUE(member1 == member2); + EXPECT_TRUE(member1 == gced); + EXPECT_TRUE(member2 == gced); EXPECT_FALSE(member1 != member2); + EXPECT_FALSE(member1 != gced); + EXPECT_FALSE(member2 != gced); } { MemberType1 member1 = @@ -346,7 +361,9 @@ void EqualityTest(cppgc::Heap* heap) { MemberType2 member2 = MakeGarbageCollected(heap->GetAllocationHandle()); EXPECT_TRUE(member1 != member2); + EXPECT_TRUE(member1 != member2.Get()); EXPECT_FALSE(member1 == member2); + EXPECT_FALSE(member1 == member2.Get()); } } @@ -363,6 +380,56 @@ TEST_F(MemberTest, EqualityTest) { EqualityTest(heap); } +TEST_F(MemberTest, HeterogeneousEqualityTest) { + cppgc::Heap* heap = GetHeap(); + { + auto* gced = MakeGarbageCollected(heap->GetAllocationHandle()); + auto* derived = static_cast(gced); + ASSERT_NE(reinterpret_cast(gced), reinterpret_cast(derived)); + } + { + auto* gced = MakeGarbageCollected(heap->GetAllocationHandle()); + Member 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 mixin = gced; + EXPECT_MIXIN_EQUAL(mixin); + } + { + // Copy construct from member. + Member mixin = member; + EXPECT_MIXIN_EQUAL(mixin); + } + { + // Move construct from member. + Member mixin = std::move(member); + member = gced; + EXPECT_MIXIN_EQUAL(mixin); + } + { + // Copy assign from member. + Member mixin; + mixin = member; + EXPECT_MIXIN_EQUAL(mixin); + } + { + // Move assign from member. + Member mixin; + mixin = std::move(member); + member = gced; + EXPECT_MIXIN_EQUAL(mixin); + } +#undef EXPECT_MIXIN_EQUAL + } +} + TEST_F(MemberTest, WriteBarrierTriggered) { CustomWriteBarrierPolicy::InitializingWriteBarriersTriggered = 0; CustomWriteBarrierPolicy::AssigningWriteBarriersTriggered = 0;