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 
16 namespace cppgc {
17 
18 class Visitor;
19 
20 namespace internal {
21 
22 class CrossThreadPersistentRegion;
23 class 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.
28 class PersistentNode final {
29  public:
30   PersistentNode() = default;
31 
32   PersistentNode(const PersistentNode&) = delete;
33   PersistentNode& operator=(const PersistentNode&) = delete;
34 
InitializeAsUsedNode(void* owner, TraceCallback trace)35   void InitializeAsUsedNode(void* owner, TraceCallback trace) {
36     CPPGC_DCHECK(trace);
37     owner_ = owner;
38     trace_ = trace;
39   }
40 
InitializeAsFreeNode(PersistentNode* next)41   void InitializeAsFreeNode(PersistentNode* next) {
42     next_ = next;
43     trace_ = nullptr;
44   }
45 
UpdateOwner(void* owner)46   void UpdateOwner(void* owner) {
47     CPPGC_DCHECK(IsUsed());
48     owner_ = owner;
49   }
50 
FreeListNext() const51   PersistentNode* FreeListNext() const {
52     CPPGC_DCHECK(!IsUsed());
53     return next_;
54   }
55 
Trace(Visitor* visitor) const56   void Trace(Visitor* visitor) const {
57     CPPGC_DCHECK(IsUsed());
58     trace_(visitor, owner_);
59   }
60 
IsUsed() const61   bool IsUsed() const { return trace_; }
62 
owner() const63   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 
79 class 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 
TryAllocateNodeFromFreeList(void* owner, TraceCallback trace)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 
FreeNode(PersistentNode* node)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.
139 class 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 
AllocateNode(void* owner, TraceCallback trace)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 
FreeNode(PersistentNode* node)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.
171 class 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.
181 class 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 
AllocateNode(void* owner, TraceCallback trace)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 
FreeNode(PersistentNode* node)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