Flutter Engine
The Flutter Engine
gc_shared.h
Go to the documentation of this file.
1// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5// Logic shared between the Scavenger and Marker.
6
7#ifndef RUNTIME_VM_HEAP_GC_SHARED_H_
8#define RUNTIME_VM_HEAP_GC_SHARED_H_
9
11#if defined(SHOULD_NOT_INCLUDE_RUNTIME)
12#error "Should not include runtime"
13#endif
14
15#include "vm/dart_api_state.h"
16#include "vm/heap/heap.h"
17#include "vm/log.h"
18#include "vm/message_handler.h"
19#include "vm/object.h"
20
21namespace dart {
22
23// These object types have a linked list chaining all pending objects when
24// processing these in the GC.
25// The field should not be visited by pointer visitors.
26// The field should only be set during a GC.
27//
28// Macro params:
29// - type
30// - variable name
31#define GC_LINKED_LIST(V) \
32 V(WeakProperty, weak_properties) \
33 V(WeakArray, weak_arrays) \
34 V(WeakReference, weak_references) \
35 V(FinalizerEntry, finalizer_entries)
36
37template <typename Type, typename PtrType>
39 public:
40 void Enqueue(PtrType ptr) {
41 ASSERT(ptr->untag()->next_seen_by_gc().IsRawNull());
42 ptr->untag()->next_seen_by_gc_ = head_;
43 if (head_ == Type::null()) {
44 tail_ = ptr;
45 }
46 head_ = ptr;
47 }
48
50 if (to->head_ == Type::null()) {
51 ASSERT(to->tail_ == Type::null());
52 to->head_ = head_;
53 to->tail_ = tail_;
54 } else {
55 ASSERT(to->tail_ != Type::null());
56 ASSERT(to->tail_->untag()->next_seen_by_gc() == Type::null());
57 if (head_ != Type::null()) {
58 to->tail_->untag()->next_seen_by_gc_ = head_;
59 to->tail_ = tail_;
60 }
61 }
62 Release();
63 }
64
65 PtrType Release() {
66 PtrType return_value = head_;
67 head_ = Type::null();
68 tail_ = Type::null();
69 return return_value;
70 }
71
72 bool IsEmpty() { return head_ == Type::null() && tail_ == Type::null(); }
73
74 private:
75 PtrType head_ = Type::null();
76 PtrType tail_ = Type::null();
77};
78
80 void Release();
81 bool IsEmpty();
82 void FlushInto(GCLinkedLists* to);
83
84#define FOREACH(type, var) GCLinkedList<type, type##Ptr> var;
86#undef FOREACH
87};
88
89#ifdef DEBUG
90#define TRACE_FINALIZER(format, ...) \
91 if (FLAG_trace_finalizers) { \
92 THR_Print("%s %p " format "\n", GCVisitorType::kName, visitor, \
93 __VA_ARGS__); \
94 }
95#else
96#define TRACE_FINALIZER(format, ...)
97#endif
98
99// The space in which `raw_entry`'s `value` is.
100Heap::Space SpaceForExternal(FinalizerEntryPtr raw_entry);
101
102// Runs the finalizer if not detached, detaches the value and set external size
103// to 0.
104// TODO(http://dartbug.com/47777): Can this be merged with
105// NativeFinalizer::RunCallback?
106template <typename GCVisitorType>
107void RunNativeFinalizerCallback(NativeFinalizerPtr raw_finalizer,
108 FinalizerEntryPtr raw_entry,
109 Heap::Space before_gc_space,
110 GCVisitorType* visitor) {
111 PointerPtr callback_pointer = raw_finalizer->untag()->callback();
112 const auto callback = reinterpret_cast<NativeFinalizer::Callback>(
113 callback_pointer->untag()->data());
114 ObjectPtr token_object = raw_entry->untag()->token();
115 const bool is_detached = token_object == raw_entry;
116 const intptr_t external_size = raw_entry->untag()->external_size();
117 if (is_detached) {
118 // Detached from Dart code.
119 ASSERT(token_object == raw_entry);
120 ASSERT(external_size == 0);
121 if (FLAG_trace_finalizers) {
122 TRACE_FINALIZER("Not running native finalizer %p callback %p, detached",
123 raw_finalizer->untag(), callback);
124 }
125 } else {
126 // TODO(http://dartbug.com/48615): Unbox pointer address in entry.
127 ASSERT(token_object.IsPointer());
128 PointerPtr token = static_cast<PointerPtr>(token_object);
129 void* peer = reinterpret_cast<void*>(token->untag()->data());
130 if (FLAG_trace_finalizers) {
131 TRACE_FINALIZER("Running native finalizer %p callback %p with token %p",
132 raw_finalizer->untag(), callback, peer);
133 }
134 raw_entry.untag()->set_token(raw_entry);
135 (*callback)(peer);
136 if (external_size > 0) {
137 if (FLAG_trace_finalizers) {
138 TRACE_FINALIZER("Clearing external size %" Pd " bytes in %s space",
139 external_size, before_gc_space == 0 ? "new" : "old");
140 }
141 visitor->isolate_group()->heap()->FreedExternal(external_size,
142 before_gc_space);
143 raw_entry->untag()->set_external_size(0);
144 }
145 }
146}
147
148// This function processes all finalizer entries discovered by a scavenger or
149// marker. If an entry is referencing an object that is going to die, such entry
150// is cleared and enqueued in the respective finalizer.
151//
152// Finalizer entries belonging to unreachable finalizer entries do not get
153// processed, so the callback will not be called for these finalizers.
154//
155// For more documentation see runtime/docs/gc.md.
156//
157// |GCVisitorType| is a concrete type implementing either marker or scavenger.
158// It is expected to provide |ForwardOrSetNullIfCollected| method for clearing
159// fields referring to dead objects and |kName| field which contains visitor
160// name for tracing output.
161template <typename GCVisitorType>
162void MournFinalizerEntry(GCVisitorType* visitor,
163 FinalizerEntryPtr current_entry) {
164 TRACE_FINALIZER("Processing Entry %p", current_entry->untag());
165
166 const Heap::Space before_gc_space = SpaceForExternal(current_entry);
167 const bool value_collected_this_gc =
168 GCVisitorType::ForwardOrSetNullIfCollected(
169 current_entry, &current_entry->untag()->value_);
170 if (!value_collected_this_gc && before_gc_space == Heap::kNew) {
171 const Heap::Space after_gc_space = SpaceForExternal(current_entry);
172 if (after_gc_space == Heap::kOld) {
173 const intptr_t external_size = current_entry->untag()->external_size_;
174 TRACE_FINALIZER("Promoting external size %" Pd
175 " bytes from new to old space",
176 external_size);
177 visitor->isolate_group()->heap()->PromotedExternal(external_size);
178 }
179 }
180 GCVisitorType::ForwardOrSetNullIfCollected(current_entry,
181 &current_entry->untag()->detach_);
182 GCVisitorType::ForwardOrSetNullIfCollected(
183 current_entry, &current_entry->untag()->finalizer_);
184
185 ObjectPtr token_object = current_entry->untag()->token();
186 // See sdk/lib/_internal/vm/lib/internal_patch.dart FinalizerBase.detach.
187 const bool is_detached = token_object == current_entry;
188
189 if (!value_collected_this_gc) return;
190 if (is_detached) return;
191
192 FinalizerBasePtr finalizer = current_entry->untag()->finalizer();
193
194 if (finalizer.IsRawNull()) {
195 TRACE_FINALIZER("Value collected entry %p finalizer null",
196 current_entry->untag());
197
198 // Do nothing, the finalizer has been GCed.
199 return;
200 }
201
202 TRACE_FINALIZER("Value collected entry %p finalizer %p",
203 current_entry->untag(), finalizer->untag());
204
205 FinalizerPtr finalizer_dart = static_cast<FinalizerPtr>(finalizer);
206 // Move entry to entries collected and current head of that list as
207 // the next element. Using a atomic exchange satisfies concurrency
208 // between the parallel GC tasks.
209 // We rely on the fact that the mutator thread is not running to avoid
210 // races between GC and mutator modifying Finalizer.entries_collected.
211 //
212 // We only run in serial marker or in the finalize step in the marker,
213 // both are in safepoint.
214 // The main scavenger worker is at safepoint, the other scavenger
215 // workers are not, but they bypass safepoint because the main
216 // worker is at a safepoint already.
217 ASSERT(Thread::Current()->OwnsGCSafepoint() ||
218 Thread::Current()->BypassSafepoints());
219
220 if (finalizer.IsNativeFinalizer()) {
221 NativeFinalizerPtr native_finalizer =
222 static_cast<NativeFinalizerPtr>(finalizer);
223
224 // Immediately call native callback.
225 RunNativeFinalizerCallback(native_finalizer, current_entry, before_gc_space,
226 visitor);
227
228 // Fall-through sending a message to clear the entries and remove
229 // from detachments.
230 }
231
232 FinalizerEntryPtr previous_head =
233 finalizer_dart->untag()->exchange_entries_collected(current_entry);
234 current_entry->untag()->set_next(previous_head);
235 const bool first_entry = previous_head.IsRawNull();
236
237 // If we're in the marker, we need to ensure that we release the store
238 // buffer afterwards.
239 // If we're in the scavenger and have the finalizer in old space and
240 // a new space entry, we don't need to release the store buffer.
241 if (!first_entry && previous_head->IsNewObject() &&
242 current_entry->IsOldObject()) {
243 TRACE_FINALIZER("Entry %p (old) next is %p (new)", current_entry->untag(),
244 previous_head->untag());
245 // We must release the thread's store buffer block.
246 }
247
248 // Schedule calling Dart finalizer.
249 if (first_entry) {
250 Isolate* isolate = finalizer->untag()->isolate_;
251 if (isolate == nullptr) {
252 TRACE_FINALIZER("Not scheduling finalizer %p callback on isolate null",
253 finalizer->untag());
254 } else {
255 TRACE_FINALIZER("Scheduling finalizer %p callback on isolate %p",
256 finalizer->untag(), isolate);
257
258 PersistentHandle* handle =
260 handle->set_ptr(finalizer);
261 MessageHandler* message_handler = isolate->message_handler();
262 message_handler->PostMessage(
264 /*before_events*/ false);
265 }
266 }
267}
268
269#undef TRACE_FINALIZER
270
271} // namespace dart
272
273#endif // RUNTIME_VM_HEAP_GC_SHARED_H_
PersistentHandle * AllocatePersistentHandle()
void Enqueue(PtrType ptr)
Definition: gc_shared.h:40
PtrType Release()
Definition: gc_shared.h:65
void FlushInto(GCLinkedList< Type, PtrType > *to)
Definition: gc_shared.h:49
@ kNew
Definition: heap.h:38
@ kOld
Definition: heap.h:39
ApiState * api_state() const
Definition: isolate.h:700
MessageHandler * message_handler() const
Definition: isolate.cc:2416
IsolateGroup * group() const
Definition: isolate.h:1037
void PostMessage(std::unique_ptr< Message > message, bool before_events=false)
static std::unique_ptr< Message > New(Args &&... args)
Definition: message.h:72
@ kNormalPriority
Definition: message.h:28
void(* Callback)(void *)
Definition: object.h:13094
UntaggedObject * untag() const
static ObjectPtr null()
Definition: object.h:433
void set_ptr(ObjectPtr ref)
static Thread * Current()
Definition: thread.h:362
#define ASSERT(E)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
#define TRACE_FINALIZER(format,...)
Definition: gc_shared.h:96
#define GC_LINKED_LIST(V)
Definition: gc_shared.h:31
#define FOREACH(type, var)
Definition: gc_shared.h:84
Definition: dart_vm.cc:33
void MournFinalizerEntry(GCVisitorType *visitor, FinalizerEntryPtr current_entry)
Definition: gc_shared.h:162
Heap::Space SpaceForExternal(FinalizerEntryPtr raw_entry)
Definition: gc_shared.cc:42
void RunNativeFinalizerCallback(NativeFinalizerPtr raw_finalizer, FinalizerEntryPtr raw_entry, Heap::Space before_gc_space, GCVisitorType *visitor)
Definition: gc_shared.h:107
#define Pd
Definition: globals.h:408
void FlushInto(GCLinkedLists *to)
Definition: gc_shared.cc:34