/src/node/deps/v8/include/cppgc/visitor.h
Line | Count | Source (jump to first uncovered line) |
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_VISITOR_H_ |
6 | | #define INCLUDE_CPPGC_VISITOR_H_ |
7 | | |
8 | | #include <type_traits> |
9 | | |
10 | | #include "cppgc/custom-space.h" |
11 | | #include "cppgc/ephemeron-pair.h" |
12 | | #include "cppgc/garbage-collected.h" |
13 | | #include "cppgc/internal/logging.h" |
14 | | #include "cppgc/internal/member-storage.h" |
15 | | #include "cppgc/internal/pointer-policies.h" |
16 | | #include "cppgc/liveness-broker.h" |
17 | | #include "cppgc/member.h" |
18 | | #include "cppgc/sentinel-pointer.h" |
19 | | #include "cppgc/source-location.h" |
20 | | #include "cppgc/trace-trait.h" |
21 | | #include "cppgc/type-traits.h" |
22 | | |
23 | | namespace cppgc { |
24 | | |
25 | | namespace internal { |
26 | | template <typename T, typename WeaknessPolicy, typename LocationPolicy, |
27 | | typename CheckingPolicy> |
28 | | class BasicCrossThreadPersistent; |
29 | | template <typename T, typename WeaknessPolicy, typename LocationPolicy, |
30 | | typename CheckingPolicy> |
31 | | class BasicPersistent; |
32 | | class ConservativeTracingVisitor; |
33 | | class VisitorBase; |
34 | | class VisitorFactory; |
35 | | } // namespace internal |
36 | | |
37 | | using WeakCallback = void (*)(const LivenessBroker&, const void*); |
38 | | |
39 | | /** |
40 | | * Visitor passed to trace methods. All managed pointers must have called the |
41 | | * Visitor's trace method on them. |
42 | | * |
43 | | * \code |
44 | | * class Foo final : public GarbageCollected<Foo> { |
45 | | * public: |
46 | | * void Trace(Visitor* visitor) const { |
47 | | * visitor->Trace(foo_); |
48 | | * visitor->Trace(weak_foo_); |
49 | | * } |
50 | | * private: |
51 | | * Member<Foo> foo_; |
52 | | * WeakMember<Foo> weak_foo_; |
53 | | * }; |
54 | | * \endcode |
55 | | */ |
56 | | class V8_EXPORT Visitor { |
57 | | public: |
58 | | class Key { |
59 | | private: |
60 | | Key() = default; |
61 | | friend class internal::VisitorFactory; |
62 | | }; |
63 | | |
64 | 0 | explicit Visitor(Key) {} |
65 | | |
66 | | virtual ~Visitor() = default; |
67 | | |
68 | | /** |
69 | | * Trace method for Member. |
70 | | * |
71 | | * \param member Member reference retaining an object. |
72 | | */ |
73 | | template <typename T> |
74 | | void Trace(const Member<T>& member) { |
75 | | const T* value = member.GetRawAtomic(); |
76 | | CPPGC_DCHECK(value != kSentinelPointer); |
77 | | TraceImpl(value); |
78 | | } |
79 | | |
80 | | /** |
81 | | * Trace method for WeakMember. |
82 | | * |
83 | | * \param weak_member WeakMember reference weakly retaining an object. |
84 | | */ |
85 | | template <typename T> |
86 | | void Trace(const WeakMember<T>& weak_member) { |
87 | | static_assert(sizeof(T), "Pointee type must be fully defined."); |
88 | | static_assert(internal::IsGarbageCollectedOrMixinType<T>::value, |
89 | | "T must be GarbageCollected or GarbageCollectedMixin type"); |
90 | | static_assert(!internal::IsAllocatedOnCompactableSpace<T>::value, |
91 | | "Weak references to compactable objects are not allowed"); |
92 | | |
93 | | const T* value = weak_member.GetRawAtomic(); |
94 | | |
95 | | // Bailout assumes that WeakMember emits write barrier. |
96 | | if (!value) { |
97 | | return; |
98 | | } |
99 | | |
100 | | CPPGC_DCHECK(value != kSentinelPointer); |
101 | | VisitWeak(value, TraceTrait<T>::GetTraceDescriptor(value), |
102 | | &HandleWeak<WeakMember<T>>, &weak_member); |
103 | | } |
104 | | |
105 | | #if defined(CPPGC_POINTER_COMPRESSION) |
106 | | /** |
107 | | * Trace method for UncompressedMember. |
108 | | * |
109 | | * \param member UncompressedMember reference retaining an object. |
110 | | */ |
111 | | template <typename T> |
112 | | void Trace(const subtle::UncompressedMember<T>& member) { |
113 | | const T* value = member.GetRawAtomic(); |
114 | | CPPGC_DCHECK(value != kSentinelPointer); |
115 | | TraceImpl(value); |
116 | | } |
117 | | #endif // defined(CPPGC_POINTER_COMPRESSION) |
118 | | |
119 | | template <typename T> |
120 | | void TraceMultiple(const subtle::UncompressedMember<T>* start, size_t len) { |
121 | | static_assert(sizeof(T), "Pointee type must be fully defined."); |
122 | | static_assert(internal::IsGarbageCollectedOrMixinType<T>::value, |
123 | | "T must be GarbageCollected or GarbageCollectedMixin type"); |
124 | | VisitMultipleUncompressedMember(start, len, |
125 | | &TraceTrait<T>::GetTraceDescriptor); |
126 | | } |
127 | | |
128 | | template <typename T, |
129 | | std::enable_if_t<!std::is_same_v< |
130 | | Member<T>, subtle::UncompressedMember<T>>>* = nullptr> |
131 | | void TraceMultiple(const Member<T>* start, size_t len) { |
132 | | static_assert(sizeof(T), "Pointee type must be fully defined."); |
133 | | static_assert(internal::IsGarbageCollectedOrMixinType<T>::value, |
134 | | "T must be GarbageCollected or GarbageCollectedMixin type"); |
135 | | #if defined(CPPGC_POINTER_COMPRESSION) |
136 | | static_assert(std::is_same_v<Member<T>, subtle::CompressedMember<T>>, |
137 | | "Member and CompressedMember must be the same."); |
138 | | VisitMultipleCompressedMember(start, len, |
139 | | &TraceTrait<T>::GetTraceDescriptor); |
140 | | #endif // defined(CPPGC_POINTER_COMPRESSION) |
141 | | } |
142 | | |
143 | | /** |
144 | | * Trace method for inlined objects that are not allocated themselves but |
145 | | * otherwise follow managed heap layout and have a Trace() method. |
146 | | * |
147 | | * \param object reference of the inlined object. |
148 | | */ |
149 | | template <typename T> |
150 | | void Trace(const T& object) { |
151 | | #if V8_ENABLE_CHECKS |
152 | | // This object is embedded in potentially multiple nested objects. The |
153 | | // outermost object must not be in construction as such objects are (a) not |
154 | | // processed immediately, and (b) only processed conservatively if not |
155 | | // otherwise possible. |
156 | | CheckObjectNotInConstruction(&object); |
157 | | #endif // V8_ENABLE_CHECKS |
158 | | TraceTrait<T>::Trace(this, &object); |
159 | | } |
160 | | |
161 | | template <typename T> |
162 | | void TraceMultiple(const T* start, size_t len) { |
163 | | #if V8_ENABLE_CHECKS |
164 | | // This object is embedded in potentially multiple nested objects. The |
165 | | // outermost object must not be in construction as such objects are (a) not |
166 | | // processed immediately, and (b) only processed conservatively if not |
167 | | // otherwise possible. |
168 | | CheckObjectNotInConstruction(start); |
169 | | #endif // V8_ENABLE_CHECKS |
170 | | for (size_t i = 0; i < len; ++i) { |
171 | | const T* object = &start[i]; |
172 | | if constexpr (std::is_polymorphic_v<T>) { |
173 | | // The object's vtable may be uninitialized in which case the object is |
174 | | // not traced. |
175 | | if (*reinterpret_cast<const uintptr_t*>(object) == 0) continue; |
176 | | } |
177 | | TraceTrait<T>::Trace(this, object); |
178 | | } |
179 | | } |
180 | | |
181 | | /** |
182 | | * Registers a weak callback method on the object of type T. See |
183 | | * LivenessBroker for an usage example. |
184 | | * |
185 | | * \param object of type T specifying a weak callback method. |
186 | | */ |
187 | | template <typename T, void (T::*method)(const LivenessBroker&)> |
188 | | void RegisterWeakCallbackMethod(const T* object) { |
189 | | RegisterWeakCallback(&WeakCallbackMethodDelegate<T, method>, object); |
190 | | } |
191 | | |
192 | | /** |
193 | | * Trace method for EphemeronPair. |
194 | | * |
195 | | * \param ephemeron_pair EphemeronPair reference weakly retaining a key object |
196 | | * and strongly retaining a value object in case the key object is alive. |
197 | | */ |
198 | | template <typename K, typename V> |
199 | | void Trace(const EphemeronPair<K, V>& ephemeron_pair) { |
200 | | TraceEphemeron(ephemeron_pair.key, &ephemeron_pair.value); |
201 | | RegisterWeakCallbackMethod<EphemeronPair<K, V>, |
202 | | &EphemeronPair<K, V>::ClearValueIfKeyIsDead>( |
203 | | &ephemeron_pair); |
204 | | } |
205 | | |
206 | | /** |
207 | | * Trace method for a single ephemeron. Used for tracing a raw ephemeron in |
208 | | * which the `key` and `value` are kept separately. |
209 | | * |
210 | | * \param weak_member_key WeakMember reference weakly retaining a key object. |
211 | | * \param member_value Member reference with ephemeron semantics. |
212 | | */ |
213 | | template <typename KeyType, typename ValueType> |
214 | | void TraceEphemeron(const WeakMember<KeyType>& weak_member_key, |
215 | | const Member<ValueType>* member_value) { |
216 | | const KeyType* key = weak_member_key.GetRawAtomic(); |
217 | | if (!key) return; |
218 | | |
219 | | // `value` must always be non-null. |
220 | | CPPGC_DCHECK(member_value); |
221 | | const ValueType* value = member_value->GetRawAtomic(); |
222 | | if (!value) return; |
223 | | |
224 | | // KeyType and ValueType may refer to GarbageCollectedMixin. |
225 | | TraceDescriptor value_desc = |
226 | | TraceTrait<ValueType>::GetTraceDescriptor(value); |
227 | | CPPGC_DCHECK(value_desc.base_object_payload); |
228 | | const void* key_base_object_payload = |
229 | | TraceTrait<KeyType>::GetTraceDescriptor(key).base_object_payload; |
230 | | CPPGC_DCHECK(key_base_object_payload); |
231 | | |
232 | | VisitEphemeron(key_base_object_payload, value, value_desc); |
233 | | } |
234 | | |
235 | | /** |
236 | | * Trace method for a single ephemeron. Used for tracing a raw ephemeron in |
237 | | * which the `key` and `value` are kept separately. Note that this overload |
238 | | * is for non-GarbageCollected `value`s that can be traced though. |
239 | | * |
240 | | * \param key `WeakMember` reference weakly retaining a key object. |
241 | | * \param value Reference weakly retaining a value object. Note that |
242 | | * `ValueType` here should not be `Member`. It is expected that |
243 | | * `TraceTrait<ValueType>::GetTraceDescriptor(value)` returns a |
244 | | * `TraceDescriptor` with a null base pointer but a valid trace method. |
245 | | */ |
246 | | template <typename KeyType, typename ValueType> |
247 | | void TraceEphemeron(const WeakMember<KeyType>& weak_member_key, |
248 | | const ValueType* value) { |
249 | | static_assert(!IsGarbageCollectedOrMixinTypeV<ValueType>, |
250 | | "garbage-collected types must use WeakMember and Member"); |
251 | | const KeyType* key = weak_member_key.GetRawAtomic(); |
252 | | if (!key) return; |
253 | | |
254 | | // `value` must always be non-null. |
255 | | CPPGC_DCHECK(value); |
256 | | TraceDescriptor value_desc = |
257 | | TraceTrait<ValueType>::GetTraceDescriptor(value); |
258 | | // `value_desc.base_object_payload` must be null as this override is only |
259 | | // taken for non-garbage-collected values. |
260 | | CPPGC_DCHECK(!value_desc.base_object_payload); |
261 | | |
262 | | // KeyType might be a GarbageCollectedMixin. |
263 | | const void* key_base_object_payload = |
264 | | TraceTrait<KeyType>::GetTraceDescriptor(key).base_object_payload; |
265 | | CPPGC_DCHECK(key_base_object_payload); |
266 | | |
267 | | VisitEphemeron(key_base_object_payload, value, value_desc); |
268 | | } |
269 | | |
270 | | /** |
271 | | * Trace method that strongifies a WeakMember. |
272 | | * |
273 | | * \param weak_member WeakMember reference retaining an object. |
274 | | */ |
275 | | template <typename T> |
276 | | void TraceStrongly(const WeakMember<T>& weak_member) { |
277 | | const T* value = weak_member.GetRawAtomic(); |
278 | | CPPGC_DCHECK(value != kSentinelPointer); |
279 | | TraceImpl(value); |
280 | | } |
281 | | |
282 | | /** |
283 | | * Trace method for retaining containers strongly. |
284 | | * |
285 | | * \param object reference to the container. |
286 | | */ |
287 | | template <typename T> |
288 | | void TraceStrongContainer(const T* object) { |
289 | | TraceImpl(object); |
290 | | } |
291 | | |
292 | | /** |
293 | | * Trace method for retaining containers weakly. Note that weak containers |
294 | | * should emit write barriers. |
295 | | * |
296 | | * \param object reference to the container. |
297 | | * \param callback to be invoked. |
298 | | * \param callback_data custom data that is passed to the callback. |
299 | | */ |
300 | | template <typename T> |
301 | | void TraceWeakContainer(const T* object, WeakCallback callback, |
302 | | const void* callback_data) { |
303 | | if (!object) return; |
304 | | VisitWeakContainer(object, TraceTrait<T>::GetTraceDescriptor(object), |
305 | | TraceTrait<T>::GetWeakTraceDescriptor(object), callback, |
306 | | callback_data); |
307 | | } |
308 | | |
309 | | /** |
310 | | * Registers a slot containing a reference to an object allocated on a |
311 | | * compactable space. Such references maybe be arbitrarily moved by the GC. |
312 | | * |
313 | | * \param slot location of reference to object that might be moved by the GC. |
314 | | * The slot must contain an uncompressed pointer. |
315 | | */ |
316 | | template <typename T> |
317 | | void RegisterMovableReference(const T** slot) { |
318 | | static_assert(internal::IsAllocatedOnCompactableSpace<T>::value, |
319 | | "Only references to objects allocated on compactable spaces " |
320 | | "should be registered as movable slots."); |
321 | | static_assert(!IsGarbageCollectedMixinTypeV<T>, |
322 | | "Mixin types do not support compaction."); |
323 | | HandleMovableReference(reinterpret_cast<const void**>(slot)); |
324 | | } |
325 | | |
326 | | /** |
327 | | * Registers a weak callback that is invoked during garbage collection. |
328 | | * |
329 | | * \param callback to be invoked. |
330 | | * \param data custom data that is passed to the callback. |
331 | | */ |
332 | 0 | virtual void RegisterWeakCallback(WeakCallback callback, const void* data) {} |
333 | | |
334 | | /** |
335 | | * Defers tracing an object from a concurrent thread to the mutator thread. |
336 | | * Should be called by Trace methods of types that are not safe to trace |
337 | | * concurrently. |
338 | | * |
339 | | * \param parameter tells the trace callback which object was deferred. |
340 | | * \param callback to be invoked for tracing on the mutator thread. |
341 | | * \param deferred_size size of deferred object. |
342 | | * |
343 | | * \returns false if the object does not need to be deferred (i.e. currently |
344 | | * traced on the mutator thread) and true otherwise (i.e. currently traced on |
345 | | * a concurrent thread). |
346 | | */ |
347 | | virtual V8_WARN_UNUSED_RESULT bool DeferTraceToMutatorThreadIfConcurrent( |
348 | 0 | const void* parameter, TraceCallback callback, size_t deferred_size) { |
349 | 0 | // By default tracing is not deferred. |
350 | 0 | return false; |
351 | 0 | } |
352 | | |
353 | | protected: |
354 | 0 | virtual void Visit(const void* self, TraceDescriptor) {} |
355 | | virtual void VisitWeak(const void* self, TraceDescriptor, WeakCallback, |
356 | 0 | const void* weak_member) {} |
357 | | virtual void VisitEphemeron(const void* key, const void* value, |
358 | 0 | TraceDescriptor value_desc) {} |
359 | | virtual void VisitWeakContainer(const void* self, TraceDescriptor strong_desc, |
360 | | TraceDescriptor weak_desc, |
361 | 0 | WeakCallback callback, const void* data) {} |
362 | 0 | virtual void HandleMovableReference(const void**) {} |
363 | | |
364 | | virtual void VisitMultipleUncompressedMember( |
365 | | const void* start, size_t len, |
366 | 0 | TraceDescriptorCallback get_trace_descriptor) { |
367 | 0 | // Default implementation merely delegates to Visit(). |
368 | 0 | const char* it = static_cast<const char*>(start); |
369 | 0 | const char* end = it + len * internal::kSizeOfUncompressedMember; |
370 | 0 | for (; it < end; it += internal::kSizeOfUncompressedMember) { |
371 | 0 | const auto* current = reinterpret_cast<const internal::RawPointer*>(it); |
372 | 0 | const void* object = current->LoadAtomic(); |
373 | 0 | if (!object) continue; |
374 | 0 |
|
375 | 0 | Visit(object, get_trace_descriptor(object)); |
376 | 0 | } |
377 | 0 | } |
378 | | |
379 | | #if defined(CPPGC_POINTER_COMPRESSION) |
380 | | virtual void VisitMultipleCompressedMember( |
381 | | const void* start, size_t len, |
382 | | TraceDescriptorCallback get_trace_descriptor) { |
383 | | // Default implementation merely delegates to Visit(). |
384 | | const char* it = static_cast<const char*>(start); |
385 | | const char* end = it + len * internal::kSizeofCompressedMember; |
386 | | for (; it < end; it += internal::kSizeofCompressedMember) { |
387 | | const auto* current = |
388 | | reinterpret_cast<const internal::CompressedPointer*>(it); |
389 | | const void* object = current->LoadAtomic(); |
390 | | if (!object) continue; |
391 | | |
392 | | Visit(object, get_trace_descriptor(object)); |
393 | | } |
394 | | } |
395 | | #endif // defined(CPPGC_POINTER_COMPRESSION) |
396 | | |
397 | | private: |
398 | | template <typename T, void (T::*method)(const LivenessBroker&)> |
399 | | static void WeakCallbackMethodDelegate(const LivenessBroker& info, |
400 | | const void* self) { |
401 | | // Callback is registered through a potential const Trace method but needs |
402 | | // to be able to modify fields. See HandleWeak. |
403 | | (const_cast<T*>(static_cast<const T*>(self))->*method)(info); |
404 | | } |
405 | | |
406 | | template <typename PointerType> |
407 | | static void HandleWeak(const LivenessBroker& info, const void* object) { |
408 | | const PointerType* weak = static_cast<const PointerType*>(object); |
409 | | if (!info.IsHeapObjectAlive(weak->GetFromGC())) { |
410 | | weak->ClearFromGC(); |
411 | | } |
412 | | } |
413 | | |
414 | | template <typename T> |
415 | | void TraceImpl(const T* t) { |
416 | | static_assert(sizeof(T), "Pointee type must be fully defined."); |
417 | | static_assert(internal::IsGarbageCollectedOrMixinType<T>::value, |
418 | | "T must be GarbageCollected or GarbageCollectedMixin type"); |
419 | | if (!t) { |
420 | | return; |
421 | | } |
422 | | Visit(t, TraceTrait<T>::GetTraceDescriptor(t)); |
423 | | } |
424 | | |
425 | | #if V8_ENABLE_CHECKS |
426 | | void CheckObjectNotInConstruction(const void* address); |
427 | | #endif // V8_ENABLE_CHECKS |
428 | | |
429 | | template <typename T, typename WeaknessPolicy, typename LocationPolicy, |
430 | | typename CheckingPolicy> |
431 | | friend class internal::BasicCrossThreadPersistent; |
432 | | template <typename T, typename WeaknessPolicy, typename LocationPolicy, |
433 | | typename CheckingPolicy> |
434 | | friend class internal::BasicPersistent; |
435 | | friend class internal::ConservativeTracingVisitor; |
436 | | friend class internal::VisitorBase; |
437 | | }; |
438 | | |
439 | | namespace internal { |
440 | | |
441 | | class V8_EXPORT RootVisitor { |
442 | | public: |
443 | 0 | explicit RootVisitor(Visitor::Key) {} |
444 | | |
445 | | virtual ~RootVisitor() = default; |
446 | | |
447 | | template <typename AnyStrongPersistentType, |
448 | | std::enable_if_t< |
449 | | AnyStrongPersistentType::IsStrongPersistent::value>* = nullptr> |
450 | | void Trace(const AnyStrongPersistentType& p) { |
451 | | using PointeeType = typename AnyStrongPersistentType::PointeeType; |
452 | | const void* object = Extract(p); |
453 | | if (!object) { |
454 | | return; |
455 | | } |
456 | | VisitRoot(object, TraceTrait<PointeeType>::GetTraceDescriptor(object), |
457 | | p.Location()); |
458 | | } |
459 | | |
460 | | template <typename AnyWeakPersistentType, |
461 | | std::enable_if_t< |
462 | | !AnyWeakPersistentType::IsStrongPersistent::value>* = nullptr> |
463 | | void Trace(const AnyWeakPersistentType& p) { |
464 | | using PointeeType = typename AnyWeakPersistentType::PointeeType; |
465 | | static_assert(!internal::IsAllocatedOnCompactableSpace<PointeeType>::value, |
466 | | "Weak references to compactable objects are not allowed"); |
467 | | const void* object = Extract(p); |
468 | | if (!object) { |
469 | | return; |
470 | | } |
471 | | VisitWeakRoot(object, TraceTrait<PointeeType>::GetTraceDescriptor(object), |
472 | | &HandleWeak<AnyWeakPersistentType>, &p, p.Location()); |
473 | | } |
474 | | |
475 | | protected: |
476 | 0 | virtual void VisitRoot(const void*, TraceDescriptor, const SourceLocation&) {} |
477 | | virtual void VisitWeakRoot(const void* self, TraceDescriptor, WeakCallback, |
478 | 0 | const void* weak_root, const SourceLocation&) {} |
479 | | |
480 | | private: |
481 | | template <typename AnyPersistentType> |
482 | | static const void* Extract(AnyPersistentType& p) { |
483 | | using PointeeType = typename AnyPersistentType::PointeeType; |
484 | | static_assert(sizeof(PointeeType), |
485 | | "Persistent's pointee type must be fully defined"); |
486 | | static_assert(internal::IsGarbageCollectedOrMixinType<PointeeType>::value, |
487 | | "Persistent's pointee type must be GarbageCollected or " |
488 | | "GarbageCollectedMixin"); |
489 | | return p.GetFromGC(); |
490 | | } |
491 | | |
492 | | template <typename PointerType> |
493 | | static void HandleWeak(const LivenessBroker& info, const void* object) { |
494 | | const PointerType* weak = static_cast<const PointerType*>(object); |
495 | | if (!info.IsHeapObjectAlive(weak->GetFromGC())) { |
496 | | weak->ClearFromGC(); |
497 | | } |
498 | | } |
499 | | }; |
500 | | |
501 | | } // namespace internal |
502 | | } // namespace cppgc |
503 | | |
504 | | #endif // INCLUDE_CPPGC_VISITOR_H_ |