// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ #define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ #include #include "cppgc/internal/persistent-node.h" #include "cppgc/internal/pointer-policies.h" #include "cppgc/persistent.h" #include "cppgc/visitor.h" namespace cppgc { namespace internal { template class BasicCrossThreadPersistent final : public PersistentBase, public LocationPolicy, private WeaknessPolicy, private CheckingPolicy { public: using typename WeaknessPolicy::IsStrongPersistent; using PointeeType = T; ~BasicCrossThreadPersistent() { Clear(); } BasicCrossThreadPersistent( const SourceLocation& loc = SourceLocation::Current()) : LocationPolicy(loc) {} BasicCrossThreadPersistent( std::nullptr_t, const SourceLocation& loc = SourceLocation::Current()) : LocationPolicy(loc) {} BasicCrossThreadPersistent( SentinelPointer s, const SourceLocation& loc = SourceLocation::Current()) : PersistentBase(s), LocationPolicy(loc) {} BasicCrossThreadPersistent( T* raw, const SourceLocation& loc = SourceLocation::Current()) : PersistentBase(raw), LocationPolicy(loc) { if (!IsValid(raw)) return; PersistentRegionLock guard; CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); SetNode(region.AllocateNode(this, &Trace)); this->CheckPointer(raw); } class UnsafeCtorTag { private: UnsafeCtorTag() = default; template friend class BasicCrossThreadPersistent; }; BasicCrossThreadPersistent( UnsafeCtorTag, T* raw, const SourceLocation& loc = SourceLocation::Current()) : PersistentBase(raw), LocationPolicy(loc) { if (!IsValid(raw)) return; CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); SetNode(region.AllocateNode(this, &Trace)); this->CheckPointer(raw); } BasicCrossThreadPersistent( T& raw, const SourceLocation& loc = SourceLocation::Current()) : BasicCrossThreadPersistent(&raw, loc) {} template ::value>> BasicCrossThreadPersistent( internal::BasicMember member, const SourceLocation& loc = SourceLocation::Current()) : BasicCrossThreadPersistent(member.Get(), loc) {} BasicCrossThreadPersistent( const BasicCrossThreadPersistent& other, const SourceLocation& loc = SourceLocation::Current()) : BasicCrossThreadPersistent(loc) { // Invoke operator=. *this = other; } // Heterogeneous ctor. template ::value>> BasicCrossThreadPersistent( const BasicCrossThreadPersistent& other, const SourceLocation& loc = SourceLocation::Current()) : BasicCrossThreadPersistent(loc) { *this = other; } BasicCrossThreadPersistent( BasicCrossThreadPersistent&& other, const SourceLocation& loc = SourceLocation::Current()) noexcept { // Invoke operator=. *this = std::move(other); } BasicCrossThreadPersistent& operator=( const BasicCrossThreadPersistent& other) { PersistentRegionLock guard; AssignUnsafe(other.Get()); return *this; } template ::value>> BasicCrossThreadPersistent& operator=( const BasicCrossThreadPersistent& other) { PersistentRegionLock guard; AssignUnsafe(other.Get()); return *this; } BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) { if (this == &other) return *this; Clear(); PersistentRegionLock guard; PersistentBase::operator=(std::move(other)); LocationPolicy::operator=(std::move(other)); if (!IsValid(GetValue())) return *this; GetNode()->UpdateOwner(this); other.SetValue(nullptr); other.SetNode(nullptr); this->CheckPointer(Get()); return *this; } BasicCrossThreadPersistent& operator=(T* other) { Assign(other); return *this; } // Assignment from member. template ::value>> BasicCrossThreadPersistent& operator=( internal::BasicMember member) { return operator=(member.Get()); } BasicCrossThreadPersistent& operator=(std::nullptr_t) { Clear(); return *this; } BasicCrossThreadPersistent& operator=(SentinelPointer s) { Assign(s); return *this; } /** * Returns a pointer to the stored object. * * Note: **Not thread-safe.** * * \returns a pointer to the stored object. */ // CFI cast exemption to allow passing SentinelPointer through T* and support // heterogeneous assignments between different Member and Persistent handles // based on their actual types. V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { return static_cast(const_cast(GetValue())); } /** * Clears the stored object. */ void Clear() { // Simplified version of `Assign()` to allow calling without a complete type // `T`. const void* old_value = GetValue(); if (IsValid(old_value)) { PersistentRegionLock guard; old_value = GetValue(); // The fast path check (IsValid()) does not acquire the lock. Reload // the value to ensure the reference has not been cleared. if (IsValid(old_value)) { CrossThreadPersistentRegion& region = this->GetPersistentRegion(old_value); region.FreeNode(GetNode()); SetNode(nullptr); } else { CPPGC_DCHECK(!GetNode()); } } SetValue(nullptr); } /** * Returns a pointer to the stored object and releases it. * * Note: **Not thread-safe.** * * \returns a pointer to the stored object. */ T* Release() { T* result = Get(); Clear(); return result; } /** * Conversio to boolean. * * Note: **Not thread-safe.** * * \returns true if an actual object has been stored and false otherwise. */ explicit operator bool() const { return Get(); } /** * Conversion to object of type T. * * Note: **Not thread-safe.** * * \returns the object. */ operator T*() const { return Get(); } /** * Dereferences the stored object. * * Note: **Not thread-safe.** */ T* operator->() const { return Get(); } T& operator*() const { return *Get(); } template BasicCrossThreadPersistent To() const { using OtherBasicCrossThreadPersistent = BasicCrossThreadPersistent; PersistentRegionLock guard; return OtherBasicCrossThreadPersistent( typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(), static_cast(Get())); } template ::IsStrongPersistent::value>::type> BasicCrossThreadPersistent Lock() const { return BasicCrossThreadPersistent< U, internal::StrongCrossThreadPersistentPolicy>(*this); } private: static bool IsValid(const void* ptr) { return ptr && ptr != kSentinelPointer; } static void Trace(Visitor* v, const void* ptr) { const auto* handle = static_cast(ptr); v->TraceRoot(*handle, handle->Location()); } void Assign(T* ptr) { const void* old_value = GetValue(); if (IsValid(old_value)) { PersistentRegionLock guard; old_value = GetValue(); // The fast path check (IsValid()) does not acquire the lock. Reload // the value to ensure the reference has not been cleared. if (IsValid(old_value)) { CrossThreadPersistentRegion& region = this->GetPersistentRegion(old_value); if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { SetValue(ptr); this->CheckPointer(ptr); return; } region.FreeNode(GetNode()); SetNode(nullptr); } else { CPPGC_DCHECK(!GetNode()); } } SetValue(ptr); if (!IsValid(ptr)) return; PersistentRegionLock guard; SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace)); this->CheckPointer(ptr); } void AssignUnsafe(T* ptr) { PersistentRegionLock::AssertLocked(); const void* old_value = GetValue(); if (IsValid(old_value)) { CrossThreadPersistentRegion& region = this->GetPersistentRegion(old_value); if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { SetValue(ptr); this->CheckPointer(ptr); return; } region.FreeNode(GetNode()); SetNode(nullptr); } SetValue(ptr); if (!IsValid(ptr)) return; SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace)); this->CheckPointer(ptr); } void ClearFromGC() const { if (IsValid(GetValue())) { WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); PersistentBase::ClearFromGC(); } } friend class cppgc::Visitor; }; template struct IsWeak< BasicCrossThreadPersistent> : std::true_type {}; } // namespace internal namespace subtle { /** * **DO NOT USE: Has known caveats, see below.** * * CrossThreadPersistent allows retaining objects from threads other than the * thread the owning heap is operating on. * * Known caveats: * - Does not protect the heap owning an object from terminating. * - Reaching transitively through the graph is unsupported as objects may be * moved concurrently on the thread owning the object. */ template using CrossThreadPersistent = internal::BasicCrossThreadPersistent< T, internal::StrongCrossThreadPersistentPolicy>; /** * **DO NOT USE: Has known caveats, see below.** * * CrossThreadPersistent allows weakly retaining objects from threads other than * the thread the owning heap is operating on. * * Known caveats: * - Does not protect the heap owning an object from terminating. * - Reaching transitively through the graph is unsupported as objects may be * moved concurrently on the thread owning the object. */ template using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent< T, internal::WeakCrossThreadPersistentPolicy>; } // namespace subtle } // namespace cppgc #endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_