cross-thread-persistent.h 14.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 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 <atomic>

#include "cppgc/internal/persistent-node.h"
#include "cppgc/internal/pointer-policies.h"
#include "cppgc/persistent.h"
#include "cppgc/visitor.h"

namespace cppgc {
namespace internal {

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
// Wrapper around PersistentBase that allows accessing poisoned memory when
// using ASAN. This is needed as the GC of the heap that owns the value
// of a CTP, may clear it (heap termination, weakness) while the object
// holding the CTP may be poisoned as itself may be deemed dead.
class CrossThreadPersistentBase : public PersistentBase {
 public:
  CrossThreadPersistentBase() = default;
  explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {}

  V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const {
    return raw_;
  }

  V8_CLANG_NO_SANITIZE("address")
  PersistentNode* GetNodeFromGC() const { return node_; }

  V8_CLANG_NO_SANITIZE("address")
  void ClearFromGC() const {
    raw_ = nullptr;
37 38 39
    SetNodeSafe(nullptr);
  }

40 41
  // GetNodeSafe() can be used for a thread-safe IsValid() check in a
  // double-checked locking pattern. See ~BasicCrossThreadPersistent.
42 43
  PersistentNode* GetNodeSafe() const {
    return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load(
44
        std::memory_order_acquire);
45 46 47 48 49 50 51 52 53 54 55 56
  }

  // The GC writes using SetNodeSafe() while holding the lock.
  V8_CLANG_NO_SANITIZE("address")
  void SetNodeSafe(PersistentNode* value) const {
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define V8_IS_ASAN 1
#endif
#endif

#ifdef V8_IS_ASAN
57
    __atomic_store(&node_, &value, __ATOMIC_RELEASE);
58 59 60 61
#else   // !V8_IS_ASAN
    // Non-ASAN builds can use atomics. This also covers MSVC which does not
    // have the __atomic_store intrinsic.
    reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store(
62
        value, std::memory_order_release);
63 64 65
#endif  // !V8_IS_ASAN

#undef V8_IS_ASAN
66 67 68
  }
};

69 70
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
          typename CheckingPolicy>
71
class BasicCrossThreadPersistent final : public CrossThreadPersistentBase,
72 73 74 75 76 77 78
                                         public LocationPolicy,
                                         private WeaknessPolicy,
                                         private CheckingPolicy {
 public:
  using typename WeaknessPolicy::IsStrongPersistent;
  using PointeeType = T;

79
  ~BasicCrossThreadPersistent() {
80 81
    //  This implements fast path for destroying empty/sentinel.
    //
82
    // Simplified version of `AssignUnsafe()` to allow calling without a
83 84
    // complete type `T`. Uses double-checked locking with a simple thread-safe
    // check for a valid handle based on a node.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    if (GetNodeSafe()) {
      PersistentRegionLock guard;
      const void* old_value = GetValue();
      // The fast path check (GetNodeSafe()) does not acquire the lock. Recheck
      // validity while holding the lock 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());
      }
    }
    // No need to call SetValue() as the handle is not used anymore. This can
    // leave behind stale sentinel values but will always destroy the underlying
    // node.
  }
104

105
  BasicCrossThreadPersistent(
106 107 108
      const SourceLocation& loc = SourceLocation::Current())
      : LocationPolicy(loc) {}

109
  BasicCrossThreadPersistent(
110 111 112
      std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
      : LocationPolicy(loc) {}

113
  BasicCrossThreadPersistent(
114
      SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
115
      : CrossThreadPersistentBase(s), LocationPolicy(loc) {}
116

117
  BasicCrossThreadPersistent(
118
      T* raw, const SourceLocation& loc = SourceLocation::Current())
119
      : CrossThreadPersistentBase(raw), LocationPolicy(loc) {
120
    if (!IsValid(raw)) return;
121
    PersistentRegionLock guard;
122
    CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
123 124 125 126 127 128 129 130 131 132 133 134
    SetNode(region.AllocateNode(this, &Trace));
    this->CheckPointer(raw);
  }

  class UnsafeCtorTag {
   private:
    UnsafeCtorTag() = default;
    template <typename U, typename OtherWeaknessPolicy,
              typename OtherLocationPolicy, typename OtherCheckingPolicy>
    friend class BasicCrossThreadPersistent;
  };

135
  BasicCrossThreadPersistent(
136 137
      UnsafeCtorTag, T* raw,
      const SourceLocation& loc = SourceLocation::Current())
138
      : CrossThreadPersistentBase(raw), LocationPolicy(loc) {
139
    if (!IsValid(raw)) return;
140
    CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
141 142 143 144
    SetNode(region.AllocateNode(this, &Trace));
    this->CheckPointer(raw);
  }

145
  BasicCrossThreadPersistent(
146 147 148 149 150 151
      T& raw, const SourceLocation& loc = SourceLocation::Current())
      : BasicCrossThreadPersistent(&raw, loc) {}

  template <typename U, typename MemberBarrierPolicy,
            typename MemberWeaknessTag, typename MemberCheckingPolicy,
            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
152
  BasicCrossThreadPersistent(
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
      internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
                            MemberCheckingPolicy>
          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 <typename U, typename OtherWeaknessPolicy,
            typename OtherLocationPolicy, typename OtherCheckingPolicy,
            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
171
  BasicCrossThreadPersistent(
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
      const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
                                       OtherLocationPolicy,
                                       OtherCheckingPolicy>& 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;
190
    AssignSafe(guard, other.Get());
191 192 193 194 195 196 197 198 199 200 201
    return *this;
  }

  template <typename U, typename OtherWeaknessPolicy,
            typename OtherLocationPolicy, typename OtherCheckingPolicy,
            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
  BasicCrossThreadPersistent& operator=(
      const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
                                       OtherLocationPolicy,
                                       OtherCheckingPolicy>& other) {
    PersistentRegionLock guard;
202
    AssignSafe(guard, other.Get());
203 204 205 206 207 208 209 210 211 212 213 214 215
    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);
216
    this->CheckPointer(Get());
217 218 219
    return *this;
  }

220 221 222 223 224
  /**
   * Assigns a raw pointer.
   *
   * Note: **Not thread-safe.**
   */
225
  BasicCrossThreadPersistent& operator=(T* other) {
226
    AssignUnsafe(other);
227 228 229 230 231 232 233 234 235 236 237 238 239 240
    return *this;
  }

  // Assignment from member.
  template <typename U, typename MemberBarrierPolicy,
            typename MemberWeaknessTag, typename MemberCheckingPolicy,
            typename = std::enable_if_t<std::is_base_of<T, U>::value>>
  BasicCrossThreadPersistent& operator=(
      internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
                            MemberCheckingPolicy>
          member) {
    return operator=(member.Get());
  }

241 242 243 244 245
  /**
   * Assigns a nullptr.
   *
   * \returns the handle.
   */
246 247 248 249 250
  BasicCrossThreadPersistent& operator=(std::nullptr_t) {
    Clear();
    return *this;
  }

251 252 253 254 255
  /**
   * Assigns the sentinel pointer.
   *
   * \returns the handle.
   */
256
  BasicCrossThreadPersistent& operator=(SentinelPointer s) {
257 258
    PersistentRegionLock guard;
    AssignSafe(guard, s);
259 260 261 262 263 264 265 266 267 268 269 270 271 272
    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 {
273
    return static_cast<T*>(const_cast<void*>(GetValue()));
274 275 276 277 278
  }

  /**
   * Clears the stored object.
   */
279
  void Clear() {
280 281
    PersistentRegionLock guard;
    AssignSafe(guard, nullptr);
282
  }
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

  /**
   * 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.
   */
313
  operator T*() const { return Get(); }
314 315 316 317 318 319 320 321 322

  /**
   * Dereferences the stored object.
   *
   * Note: **Not thread-safe.**
   */
  T* operator->() const { return Get(); }
  T& operator*() const { return *Get(); }

323 324 325 326 327 328
  template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
            typename OtherLocationPolicy = LocationPolicy,
            typename OtherCheckingPolicy = CheckingPolicy>
  BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
                             OtherCheckingPolicy>
  To() const {
329 330 331
    using OtherBasicCrossThreadPersistent =
        BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
                                   OtherCheckingPolicy>;
332
    PersistentRegionLock guard;
333 334
    return OtherBasicCrossThreadPersistent(
        typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(),
335 336 337 338 339 340 341 342 343 344 345 346
        static_cast<U*>(Get()));
  }

  template <typename U = T,
            typename = typename std::enable_if<!BasicCrossThreadPersistent<
                U, WeaknessPolicy>::IsStrongPersistent::value>::type>
  BasicCrossThreadPersistent<U, internal::StrongCrossThreadPersistentPolicy>
  Lock() const {
    return BasicCrossThreadPersistent<
        U, internal::StrongCrossThreadPersistentPolicy>(*this);
  }

347
 private:
348 349 350
  static bool IsValid(const void* ptr) {
    return ptr && ptr != kSentinelPointer;
  }
351 352 353 354 355 356

  static void Trace(Visitor* v, const void* ptr) {
    const auto* handle = static_cast<const BasicCrossThreadPersistent*>(ptr);
    v->TraceRoot(*handle, handle->Location());
  }

357
  void AssignUnsafe(T* ptr) {
358
    const void* old_value = GetValue();
359 360
    if (IsValid(old_value)) {
      PersistentRegionLock guard;
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
      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) && (&region == &this->GetPersistentRegion(ptr))) {
          SetValue(ptr);
          this->CheckPointer(ptr);
          return;
        }
        region.FreeNode(GetNode());
        SetNode(nullptr);
      } else {
        CPPGC_DCHECK(!GetNode());
376 377 378 379 380 381 382 383 384
      }
    }
    SetValue(ptr);
    if (!IsValid(ptr)) return;
    PersistentRegionLock guard;
    SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &Trace));
    this->CheckPointer(ptr);
  }

385
  void AssignSafe(PersistentRegionLock&, T* ptr) {
386
    PersistentRegionLock::AssertLocked();
387
    const void* old_value = GetValue();
388
    if (IsValid(old_value)) {
389 390
      CrossThreadPersistentRegion& region =
          this->GetPersistentRegion(old_value);
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
      if (IsValid(ptr) && (&region == &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 {
406 407 408 409
    if (IsValid(GetValueFromGC())) {
      WeaknessPolicy::GetPersistentRegion(GetValueFromGC())
          .FreeNode(GetNodeFromGC());
      CrossThreadPersistentBase::ClearFromGC();
410 411 412
    }
  }

413 414 415 416 417 418
  // See Get() for details.
  V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
  T* GetFromGC() const {
    return static_cast<T*>(const_cast<void*>(GetValueFromGC()));
  }

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
  friend class cppgc::Visitor;
};

template <typename T, typename LocationPolicy, typename CheckingPolicy>
struct IsWeak<
    BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
                               LocationPolicy, CheckingPolicy>>
    : 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 <typename T>
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 <typename T>
using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
    T, internal::WeakCrossThreadPersistentPolicy>;

}  // namespace subtle
}  // namespace cppgc

#endif  // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_