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_INTERNAL_PERSISTENT_NODE_H_
6#define INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_
7
8#include <array>
9#include <memory>
10#include <vector>
11
12#include "cppgc/internal/logging.h"
13#include "cppgc/trace-trait.h"
14#include "v8config.h"  // NOLINT(build/include_directory)
15
16namespace cppgc {
17
18class Visitor;
19
20namespace internal {
21
22class CrossThreadPersistentRegion;
23class FatalOutOfMemoryHandler;
24
25// PersistentNode represents a variant of two states:
26// 1) traceable node with a back pointer to the Persistent object;
27// 2) freelist entry.
28class PersistentNode final {
29 public:
30  PersistentNode() = default;
31
32  PersistentNode(const PersistentNode&) = delete;
33  PersistentNode& operator=(const PersistentNode&) = delete;
34
35  void InitializeAsUsedNode(void* owner, TraceCallback trace) {
36    CPPGC_DCHECK(trace);
37    owner_ = owner;
38    trace_ = trace;
39  }
40
41  void InitializeAsFreeNode(PersistentNode* next) {
42    next_ = next;
43    trace_ = nullptr;
44  }
45
46  void UpdateOwner(void* owner) {
47    CPPGC_DCHECK(IsUsed());
48    owner_ = owner;
49  }
50
51  PersistentNode* FreeListNext() const {
52    CPPGC_DCHECK(!IsUsed());
53    return next_;
54  }
55
56  void Trace(Visitor* visitor) const {
57    CPPGC_DCHECK(IsUsed());
58    trace_(visitor, owner_);
59  }
60
61  bool IsUsed() const { return trace_; }
62
63  void* owner() const {
64    CPPGC_DCHECK(IsUsed());
65    return owner_;
66  }
67
68 private:
69  // PersistentNode acts as a designated union:
70  // If trace_ != nullptr, owner_ points to the corresponding Persistent handle.
71  // Otherwise, next_ points to the next freed PersistentNode.
72  union {
73    void* owner_ = nullptr;
74    PersistentNode* next_;
75  };
76  TraceCallback trace_ = nullptr;
77};
78
79class V8_EXPORT PersistentRegionBase {
80  using PersistentNodeSlots = std::array<PersistentNode, 256u>;
81
82 public:
83  // Clears Persistent fields to avoid stale pointers after heap teardown.
84  ~PersistentRegionBase();
85
86  PersistentRegionBase(const PersistentRegionBase&) = delete;
87  PersistentRegionBase& operator=(const PersistentRegionBase&) = delete;
88
89  void Trace(Visitor*);
90
91  size_t NodesInUse() const;
92
93  void ClearAllUsedNodes();
94
95 protected:
96  explicit PersistentRegionBase(const FatalOutOfMemoryHandler& oom_handler);
97
98  PersistentNode* TryAllocateNodeFromFreeList(void* owner,
99                                              TraceCallback trace) {
100    PersistentNode* node = nullptr;
101    if (V8_LIKELY(free_list_head_)) {
102      node = free_list_head_;
103      free_list_head_ = free_list_head_->FreeListNext();
104      CPPGC_DCHECK(!node->IsUsed());
105      node->InitializeAsUsedNode(owner, trace);
106      nodes_in_use_++;
107    }
108    return node;
109  }
110
111  void FreeNode(PersistentNode* node) {
112    CPPGC_DCHECK(node);
113    CPPGC_DCHECK(node->IsUsed());
114    node->InitializeAsFreeNode(free_list_head_);
115    free_list_head_ = node;
116    CPPGC_DCHECK(nodes_in_use_ > 0);
117    nodes_in_use_--;
118  }
119
120  PersistentNode* RefillFreeListAndAllocateNode(void* owner,
121                                                TraceCallback trace);
122
123 private:
124  template <typename PersistentBaseClass>
125  void ClearAllUsedNodes();
126
127  void RefillFreeList();
128
129  std::vector<std::unique_ptr<PersistentNodeSlots>> nodes_;
130  PersistentNode* free_list_head_ = nullptr;
131  size_t nodes_in_use_ = 0;
132  const FatalOutOfMemoryHandler& oom_handler_;
133
134  friend class CrossThreadPersistentRegion;
135};
136
137// Variant of PersistentRegionBase that checks whether the allocation and
138// freeing happens only on the thread that created the region.
139class V8_EXPORT PersistentRegion final : public PersistentRegionBase {
140 public:
141  explicit PersistentRegion(const FatalOutOfMemoryHandler&);
142  // Clears Persistent fields to avoid stale pointers after heap teardown.
143  ~PersistentRegion() = default;
144
145  PersistentRegion(const PersistentRegion&) = delete;
146  PersistentRegion& operator=(const PersistentRegion&) = delete;
147
148  V8_INLINE PersistentNode* AllocateNode(void* owner, TraceCallback trace) {
149    CPPGC_DCHECK(IsCreationThread());
150    auto* node = TryAllocateNodeFromFreeList(owner, trace);
151    if (V8_LIKELY(node)) return node;
152
153    // Slow path allocation allows for checking thread correspondence.
154    CPPGC_CHECK(IsCreationThread());
155    return RefillFreeListAndAllocateNode(owner, trace);
156  }
157
158  V8_INLINE void FreeNode(PersistentNode* node) {
159    CPPGC_DCHECK(IsCreationThread());
160    PersistentRegionBase::FreeNode(node);
161  }
162
163 private:
164  bool IsCreationThread();
165
166  int creation_thread_id_;
167};
168
169// CrossThreadPersistent uses PersistentRegionBase but protects it using this
170// lock when needed.
171class V8_EXPORT PersistentRegionLock final {
172 public:
173  PersistentRegionLock();
174  ~PersistentRegionLock();
175
176  static void AssertLocked();
177};
178
179// Variant of PersistentRegionBase that checks whether the PersistentRegionLock
180// is locked.
181class V8_EXPORT CrossThreadPersistentRegion final
182    : protected PersistentRegionBase {
183 public:
184  explicit CrossThreadPersistentRegion(const FatalOutOfMemoryHandler&);
185  // Clears Persistent fields to avoid stale pointers after heap teardown.
186  ~CrossThreadPersistentRegion();
187
188  CrossThreadPersistentRegion(const CrossThreadPersistentRegion&) = delete;
189  CrossThreadPersistentRegion& operator=(const CrossThreadPersistentRegion&) =
190      delete;
191
192  V8_INLINE PersistentNode* AllocateNode(void* owner, TraceCallback trace) {
193    PersistentRegionLock::AssertLocked();
194    auto* node = TryAllocateNodeFromFreeList(owner, trace);
195    if (V8_LIKELY(node)) return node;
196
197    return RefillFreeListAndAllocateNode(owner, trace);
198  }
199
200  V8_INLINE void FreeNode(PersistentNode* node) {
201    PersistentRegionLock::AssertLocked();
202    PersistentRegionBase::FreeNode(node);
203  }
204
205  void Trace(Visitor*);
206
207  size_t NodesInUse() const;
208
209  void ClearAllUsedNodes();
210};
211
212}  // namespace internal
213
214}  // namespace cppgc
215
216#endif  // INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_
217