1 // Copyright 2020 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ 6 #define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ 7 8 #include <atomic> 9 10 #include "cppgc/internal/persistent-node.h" 11 #include "cppgc/internal/pointer-policies.h" 12 #include "cppgc/persistent.h" 13 #include "cppgc/visitor.h" 14 15 namespace cppgc { 16 namespace internal { 17 18 // Wrapper around PersistentBase that allows accessing poisoned memory when 19 // using ASAN. This is needed as the GC of the heap that owns the value 20 // of a CTP, may clear it (heap termination, weakness) while the object 21 // holding the CTP may be poisoned as itself may be deemed dead. 22 class CrossThreadPersistentBase : public PersistentBase { 23 public: 24 CrossThreadPersistentBase() = default; CrossThreadPersistentBase(const void * raw)25 explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {} 26 GetValueFromGC()27 V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const { 28 return raw_; 29 } 30 31 V8_CLANG_NO_SANITIZE("address") GetNodeFromGC()32 PersistentNode* GetNodeFromGC() const { return node_; } 33 34 V8_CLANG_NO_SANITIZE("address") ClearFromGC()35 void ClearFromGC() const { 36 raw_ = nullptr; 37 SetNodeSafe(nullptr); 38 } 39 40 // GetNodeSafe() can be used for a thread-safe IsValid() check in a 41 // double-checked locking pattern. See ~BasicCrossThreadPersistent. GetNodeSafe()42 PersistentNode* GetNodeSafe() const { 43 return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load( 44 std::memory_order_acquire); 45 } 46 47 // The GC writes using SetNodeSafe() while holding the lock. 48 V8_CLANG_NO_SANITIZE("address") SetNodeSafe(PersistentNode * value)49 void SetNodeSafe(PersistentNode* value) const { 50 #if defined(__has_feature) 51 #if __has_feature(address_sanitizer) 52 #define V8_IS_ASAN 1 53 #endif 54 #endif 55 56 #ifdef V8_IS_ASAN 57 __atomic_store(&node_, &value, __ATOMIC_RELEASE); 58 #else // !V8_IS_ASAN 59 // Non-ASAN builds can use atomics. This also covers MSVC which does not 60 // have the __atomic_store intrinsic. 61 reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store( 62 value, std::memory_order_release); 63 #endif // !V8_IS_ASAN 64 65 #undef V8_IS_ASAN 66 } 67 }; 68 69 template <typename T, typename WeaknessPolicy, typename LocationPolicy, 70 typename CheckingPolicy> 71 class BasicCrossThreadPersistent final : public CrossThreadPersistentBase, 72 public LocationPolicy, 73 private WeaknessPolicy, 74 private CheckingPolicy { 75 public: 76 using typename WeaknessPolicy::IsStrongPersistent; 77 using PointeeType = T; 78 ~BasicCrossThreadPersistent()79 ~BasicCrossThreadPersistent() { 80 // This implements fast path for destroying empty/sentinel. 81 // 82 // Simplified version of `AssignUnsafe()` to allow calling without a 83 // complete type `T`. Uses double-checked locking with a simple thread-safe 84 // check for a valid handle based on a node. 85 if (GetNodeSafe()) { 86 PersistentRegionLock guard; 87 const void* old_value = GetValue(); 88 // The fast path check (GetNodeSafe()) does not acquire the lock. Recheck 89 // validity while holding the lock to ensure the reference has not been 90 // cleared. 91 if (IsValid(old_value)) { 92 CrossThreadPersistentRegion& region = 93 this->GetPersistentRegion(old_value); 94 region.FreeNode(GetNode()); 95 SetNode(nullptr); 96 } else { 97 CPPGC_DCHECK(!GetNode()); 98 } 99 } 100 // No need to call SetValue() as the handle is not used anymore. This can 101 // leave behind stale sentinel values but will always destroy the underlying 102 // node. 103 } 104 105 BasicCrossThreadPersistent( 106 const SourceLocation& loc = SourceLocation::Current()) LocationPolicy(loc)107 : LocationPolicy(loc) {} 108 109 BasicCrossThreadPersistent( 110 std::nullptr_t, const SourceLocation& loc = SourceLocation::Current()) LocationPolicy(loc)111 : LocationPolicy(loc) {} 112 113 BasicCrossThreadPersistent( 114 SentinelPointer s, const SourceLocation& loc = SourceLocation::Current()) CrossThreadPersistentBase(s)115 : CrossThreadPersistentBase(s), LocationPolicy(loc) {} 116 117 BasicCrossThreadPersistent( 118 T* raw, const SourceLocation& loc = SourceLocation::Current()) CrossThreadPersistentBase(raw)119 : CrossThreadPersistentBase(raw), LocationPolicy(loc) { 120 if (!IsValid(raw)) return; 121 PersistentRegionLock guard; 122 CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); 123 SetNode(region.AllocateNode(this, &Trace)); 124 this->CheckPointer(raw); 125 } 126 127 class UnsafeCtorTag { 128 private: 129 UnsafeCtorTag() = default; 130 template <typename U, typename OtherWeaknessPolicy, 131 typename OtherLocationPolicy, typename OtherCheckingPolicy> 132 friend class BasicCrossThreadPersistent; 133 }; 134 135 BasicCrossThreadPersistent( 136 UnsafeCtorTag, T* raw, 137 const SourceLocation& loc = SourceLocation::Current()) CrossThreadPersistentBase(raw)138 : CrossThreadPersistentBase(raw), LocationPolicy(loc) { 139 if (!IsValid(raw)) return; 140 CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); 141 SetNode(region.AllocateNode(this, &Trace)); 142 this->CheckPointer(raw); 143 } 144 145 BasicCrossThreadPersistent( 146 T& raw, const SourceLocation& loc = SourceLocation::Current()) 147 : BasicCrossThreadPersistent(&raw, loc) {} 148 149 template <typename U, typename MemberBarrierPolicy, 150 typename MemberWeaknessTag, typename MemberCheckingPolicy, 151 typename = std::enable_if_t<std::is_base_of<T, U>::value>> 152 BasicCrossThreadPersistent( 153 internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag, 154 MemberCheckingPolicy> 155 member, 156 const SourceLocation& loc = SourceLocation::Current()) 157 : BasicCrossThreadPersistent(member.Get(), loc) {} 158 159 BasicCrossThreadPersistent( 160 const BasicCrossThreadPersistent& other, 161 const SourceLocation& loc = SourceLocation::Current()) BasicCrossThreadPersistent(loc)162 : BasicCrossThreadPersistent(loc) { 163 // Invoke operator=. 164 *this = other; 165 } 166 167 // Heterogeneous ctor. 168 template <typename U, typename OtherWeaknessPolicy, 169 typename OtherLocationPolicy, typename OtherCheckingPolicy, 170 typename = std::enable_if_t<std::is_base_of<T, U>::value>> 171 BasicCrossThreadPersistent( 172 const BasicCrossThreadPersistent<U, OtherWeaknessPolicy, 173 OtherLocationPolicy, 174 OtherCheckingPolicy>& other, 175 const SourceLocation& loc = SourceLocation::Current()) BasicCrossThreadPersistent(loc)176 : BasicCrossThreadPersistent(loc) { 177 *this = other; 178 } 179 180 BasicCrossThreadPersistent( 181 BasicCrossThreadPersistent&& other, 182 const SourceLocation& loc = SourceLocation::Current()) noexcept { 183 // Invoke operator=. 184 *this = std::move(other); 185 } 186 187 BasicCrossThreadPersistent& operator=( 188 const BasicCrossThreadPersistent& other) { 189 PersistentRegionLock guard; 190 AssignSafe(guard, other.Get()); 191 return *this; 192 } 193 194 template <typename U, typename OtherWeaknessPolicy, 195 typename OtherLocationPolicy, typename OtherCheckingPolicy, 196 typename = std::enable_if_t<std::is_base_of<T, U>::value>> 197 BasicCrossThreadPersistent& operator=( 198 const BasicCrossThreadPersistent<U, OtherWeaknessPolicy, 199 OtherLocationPolicy, 200 OtherCheckingPolicy>& other) { 201 PersistentRegionLock guard; 202 AssignSafe(guard, other.Get()); 203 return *this; 204 } 205 206 BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) { 207 if (this == &other) return *this; 208 Clear(); 209 PersistentRegionLock guard; 210 PersistentBase::operator=(std::move(other)); 211 LocationPolicy::operator=(std::move(other)); 212 if (!IsValid(GetValue())) return *this; 213 GetNode()->UpdateOwner(this); 214 other.SetValue(nullptr); 215 other.SetNode(nullptr); 216 this->CheckPointer(Get()); 217 return *this; 218 } 219 220 /** 221 * Assigns a raw pointer. 222 * 223 * Note: **Not thread-safe.** 224 */ 225 BasicCrossThreadPersistent& operator=(T* other) { 226 AssignUnsafe(other); 227 return *this; 228 } 229 230 // Assignment from member. 231 template <typename U, typename MemberBarrierPolicy, 232 typename MemberWeaknessTag, typename MemberCheckingPolicy, 233 typename = std::enable_if_t<std::is_base_of<T, U>::value>> 234 BasicCrossThreadPersistent& operator=( 235 internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag, 236 MemberCheckingPolicy> 237 member) { 238 return operator=(member.Get()); 239 } 240 241 /** 242 * Assigns a nullptr. 243 * 244 * \returns the handle. 245 */ 246 BasicCrossThreadPersistent& operator=(std::nullptr_t) { 247 Clear(); 248 return *this; 249 } 250 251 /** 252 * Assigns the sentinel pointer. 253 * 254 * \returns the handle. 255 */ 256 BasicCrossThreadPersistent& operator=(SentinelPointer s) { 257 PersistentRegionLock guard; 258 AssignSafe(guard, s); 259 return *this; 260 } 261 262 /** 263 * Returns a pointer to the stored object. 264 * 265 * Note: **Not thread-safe.** 266 * 267 * \returns a pointer to the stored object. 268 */ 269 // CFI cast exemption to allow passing SentinelPointer through T* and support 270 // heterogeneous assignments between different Member and Persistent handles 271 // based on their actual types. Get()272 V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { 273 return static_cast<T*>(const_cast<void*>(GetValue())); 274 } 275 276 /** 277 * Clears the stored object. 278 */ Clear()279 void Clear() { 280 PersistentRegionLock guard; 281 AssignSafe(guard, nullptr); 282 } 283 284 /** 285 * Returns a pointer to the stored object and releases it. 286 * 287 * Note: **Not thread-safe.** 288 * 289 * \returns a pointer to the stored object. 290 */ Release()291 T* Release() { 292 T* result = Get(); 293 Clear(); 294 return result; 295 } 296 297 /** 298 * Conversio to boolean. 299 * 300 * Note: **Not thread-safe.** 301 * 302 * \returns true if an actual object has been stored and false otherwise. 303 */ 304 explicit operator bool() const { return Get(); } 305 306 /** 307 * Conversion to object of type T. 308 * 309 * Note: **Not thread-safe.** 310 * 311 * \returns the object. 312 */ 313 operator T*() const { return Get(); } 314 315 /** 316 * Dereferences the stored object. 317 * 318 * Note: **Not thread-safe.** 319 */ 320 T* operator->() const { return Get(); } 321 T& operator*() const { return *Get(); } 322 323 template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy, 324 typename OtherLocationPolicy = LocationPolicy, 325 typename OtherCheckingPolicy = CheckingPolicy> 326 BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy, 327 OtherCheckingPolicy> To()328 To() const { 329 using OtherBasicCrossThreadPersistent = 330 BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy, 331 OtherCheckingPolicy>; 332 PersistentRegionLock guard; 333 return OtherBasicCrossThreadPersistent( 334 typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(), 335 static_cast<U*>(Get())); 336 } 337 338 template <typename U = T, 339 typename = typename std::enable_if<!BasicCrossThreadPersistent< 340 U, WeaknessPolicy>::IsStrongPersistent::value>::type> 341 BasicCrossThreadPersistent<U, internal::StrongCrossThreadPersistentPolicy> Lock()342 Lock() const { 343 return BasicCrossThreadPersistent< 344 U, internal::StrongCrossThreadPersistentPolicy>(*this); 345 } 346 347 private: IsValid(const void * ptr)348 static bool IsValid(const void* ptr) { 349 return ptr && ptr != kSentinelPointer; 350 } 351 Trace(Visitor * v,const void * ptr)352 static void Trace(Visitor* v, const void* ptr) { 353 const auto* handle = static_cast<const BasicCrossThreadPersistent*>(ptr); 354 v->TraceRoot(*handle, handle->Location()); 355 } 356 AssignUnsafe(T * ptr)357 void AssignUnsafe(T* ptr) { 358 const void* old_value = GetValue(); 359 if (IsValid(old_value)) { 360 PersistentRegionLock guard; 361 old_value = GetValue(); 362 // The fast path check (IsValid()) does not acquire the lock. Reload 363 // the value to ensure the reference has not been cleared. 364 if (IsValid(old_value)) { 365 CrossThreadPersistentRegion& region = 366 this->GetPersistentRegion(old_value); 367 if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { 368 SetValue(ptr); 369 this->CheckPointer(ptr); 370 return; 371 } 372 region.FreeNode(GetNode()); 373 SetNode(nullptr); 374 } else { 375 CPPGC_DCHECK(!GetNode()); 376 } 377 } 378 SetValue(ptr); 379 if (!IsValid(ptr)) return; 380 PersistentRegionLock guard; 381 SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace)); 382 this->CheckPointer(ptr); 383 } 384 AssignSafe(PersistentRegionLock &,T * ptr)385 void AssignSafe(PersistentRegionLock&, T* ptr) { 386 PersistentRegionLock::AssertLocked(); 387 const void* old_value = GetValue(); 388 if (IsValid(old_value)) { 389 CrossThreadPersistentRegion& region = 390 this->GetPersistentRegion(old_value); 391 if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { 392 SetValue(ptr); 393 this->CheckPointer(ptr); 394 return; 395 } 396 region.FreeNode(GetNode()); 397 SetNode(nullptr); 398 } 399 SetValue(ptr); 400 if (!IsValid(ptr)) return; 401 SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace)); 402 this->CheckPointer(ptr); 403 } 404 ClearFromGC()405 void ClearFromGC() const { 406 if (IsValid(GetValueFromGC())) { 407 WeaknessPolicy::GetPersistentRegion(GetValueFromGC()) 408 .FreeNode(GetNodeFromGC()); 409 CrossThreadPersistentBase::ClearFromGC(); 410 } 411 } 412 413 // See Get() for details. 414 V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") GetFromGC()415 T* GetFromGC() const { 416 return static_cast<T*>(const_cast<void*>(GetValueFromGC())); 417 } 418 419 friend class cppgc::Visitor; 420 }; 421 422 template <typename T, typename LocationPolicy, typename CheckingPolicy> 423 struct IsWeak< 424 BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy, 425 LocationPolicy, CheckingPolicy>> 426 : std::true_type {}; 427 428 } // namespace internal 429 430 namespace subtle { 431 432 /** 433 * **DO NOT USE: Has known caveats, see below.** 434 * 435 * CrossThreadPersistent allows retaining objects from threads other than the 436 * thread the owning heap is operating on. 437 * 438 * Known caveats: 439 * - Does not protect the heap owning an object from terminating. 440 * - Reaching transitively through the graph is unsupported as objects may be 441 * moved concurrently on the thread owning the object. 442 */ 443 template <typename T> 444 using CrossThreadPersistent = internal::BasicCrossThreadPersistent< 445 T, internal::StrongCrossThreadPersistentPolicy>; 446 447 /** 448 * **DO NOT USE: Has known caveats, see below.** 449 * 450 * CrossThreadPersistent allows weakly retaining objects from threads other than 451 * the thread the owning heap is operating on. 452 * 453 * Known caveats: 454 * - Does not protect the heap owning an object from terminating. 455 * - Reaching transitively through the graph is unsupported as objects may be 456 * moved concurrently on the thread owning the object. 457 */ 458 template <typename T> 459 using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent< 460 T, internal::WeakCrossThreadPersistentPolicy>; 461 462 } // namespace subtle 463 } // namespace cppgc 464 465 #endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ 466