mmtk/vm/scanning.rs
1use crate::plan::Mutator;
2use crate::scheduler::GCWorker;
3use crate::util::ObjectReference;
4use crate::util::VMWorkerThread;
5use crate::vm::slot::Slot;
6use crate::vm::VMBinding;
7
8/// Callback trait of scanning functions that report slots.
9pub trait SlotVisitor<SL: Slot> {
10 /// Call this function for each slot.
11 fn visit_slot(&mut self, slot: SL);
12}
13
14/// This lets us use closures as SlotVisitor.
15impl<SL: Slot, F: FnMut(SL)> SlotVisitor<SL> for F {
16 fn visit_slot(&mut self, slot: SL) {
17 #[cfg(debug_assertions)]
18 trace!(
19 "(FunctionClosure) Visit slot {:?} (pointing to {:?})",
20 slot,
21 slot.load()
22 );
23 self(slot)
24 }
25}
26
27/// Callback trait of scanning functions that directly trace through object graph edges.
28pub trait ObjectTracer {
29 /// Call this function to trace through an object graph edge which points to `object`.
30 ///
31 /// The return value is the new object reference for `object` if it is moved, or `object` if
32 /// not moved. If moved, the caller should update the slot that holds the reference to
33 /// `object` so that it points to the new location.
34 ///
35 /// Note: This function is performance-critical, therefore must be implemented efficiently.
36 fn trace_object(&mut self, object: ObjectReference) -> ObjectReference;
37}
38
39/// This lets us use closures as ObjectTracer.
40impl<F: FnMut(ObjectReference) -> ObjectReference> ObjectTracer for F {
41 fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
42 self(object)
43 }
44}
45
46/// An `ObjectTracerContext` gives a GC worker temporary access to an `ObjectTracer`, allowing
47/// the GC worker to trace objects. This trait is intended to abstract out the implementation
48/// details of tracing objects, enqueuing objects, and creating work packets that expand the
49/// transitive closure, allowing the VM binding to focus on VM-specific parts.
50///
51/// This trait is used during root scanning and binding-side weak reference processing.
52pub trait ObjectTracerContext<VM: VMBinding>: Clone + Send + 'static {
53 /// The concrete `ObjectTracer` type.
54 ///
55 /// FIXME: The current code works because of the unsafe method `ProcessEdgesWork::set_worker`.
56 /// The tracer should borrow the worker passed to `with_queuing_tracer` during its lifetime.
57 /// For this reason, `TracerType` should have a `<'w>` lifetime parameter.
58 /// Generic Associated Types (GAT) is already stablized in Rust 1.65.
59 /// We should update our toolchain version, too.
60 type TracerType: ObjectTracer;
61
62 /// Create a temporary `ObjectTracer` and provide access in the scope of `func`.
63 ///
64 /// When the `ObjectTracer::trace_object` is called, if the traced object is first visited
65 /// in this transitive closure, it will be enqueued. After `func` returns, the implememtation
66 /// will create work packets to continue computing the transitive closure from the newly
67 /// enqueued objects.
68 ///
69 /// API functions that provide `QueuingTracerFactory` should document
70 /// 1. on which fields the user is supposed to call `ObjectTracer::trace_object`, and
71 /// 2. which work bucket the generated work packet will be added to. Sometimes the user needs
72 /// to know when the computing of transitive closure finishes.
73 ///
74 /// Arguments:
75 /// - `worker`: The current GC worker.
76 /// - `func`: A caller-supplied closure in which the created `ObjectTracer` can be used.
77 ///
78 /// Returns: The return value of `func`.
79 fn with_tracer<R, F>(&self, worker: &mut GCWorker<VM>, func: F) -> R
80 where
81 F: FnOnce(&mut Self::TracerType) -> R;
82}
83
84/// Root-scanning methods use this trait to create work packets for processing roots.
85///
86/// Notes on the required traits:
87///
88/// - `Clone`: The VM may divide one root-scanning call (such as `scan_vm_specific_roots`) into
89/// multiple work packets to scan roots in parallel. In this case, the factory shall be cloned
90/// to be given to multiple work packets.
91///
92/// Cloning may be expensive if a factory contains many states. If the states are immutable, a
93/// `RootsWorkFactory` implementation may hold those states in an `Arc` field so that multiple
94/// factory instances can still share the part held in the `Arc` even after cloning.
95///
96/// - `Send` + 'static: The factory will be given to root-scanning work packets.
97/// Because work packets are distributed to and executed on different GC workers,
98/// it needs `Send` to be sent between threads. `'static` means it must not have
99/// references to variables with limited lifetime (such as local variables), because
100/// it needs to be moved between threads.
101pub trait RootsWorkFactory<SL: Slot>: Clone + Send + 'static {
102 // TODO:
103 // 1. Rename the functions and remove the repeating `create_process_` and `_work`.
104 // 2. Rename the functions to reflect both the form (slots / nodes) and the semantics (pinning
105 // / transitive pinning / non-pinning) of each function.
106 // 3. Introduce a function to give the VM binding a way to update root edges without
107 // representing the roots as slots. See: https://github.com/mmtk/mmtk-core/issues/710
108
109 /// Create work packets to handle non-pinned roots. The roots are represented as slots so that
110 /// they can be updated.
111 ///
112 /// The work packet may update the slots.
113 ///
114 /// Arguments:
115 /// * `slots`: A vector of slots.
116 fn create_process_roots_work(&mut self, slots: Vec<SL>);
117
118 /// Create work packets to handle non-transitively pinning roots.
119 ///
120 /// The work packet will prevent the objects in `nodes` from moving,
121 /// i.e. they will be pinned for the duration of the GC.
122 /// But it will not prevent the children of those objects from moving.
123 ///
124 /// This method is useful for conservative stack scanning, or VMs that cannot update some
125 /// of the root slots.
126 ///
127 /// Arguments:
128 /// * `nodes`: A vector of references to objects pointed by edges from roots.
129 fn create_process_pinning_roots_work(&mut self, nodes: Vec<ObjectReference>);
130
131 /// Create work packets to handle transitively pinning (TP) roots.
132 ///
133 /// Similar to `create_process_pinning_roots_work`, this work packet will not move objects in `nodes`.
134 /// Unlike `create_process_pinning_roots_work`, no objects in the transitive closure of `nodes` will be moved, either.
135 ///
136 /// Arguments:
137 /// * `nodes`: A vector of references to objects pointed by edges from roots.
138 fn create_process_tpinning_roots_work(&mut self, nodes: Vec<ObjectReference>);
139}
140
141/// VM-specific methods for scanning roots/objects.
142pub trait Scanning<VM: VMBinding> {
143 /// When set to `true`, all plans will guarantee that during each GC, each live object is
144 /// enqueued at most once, and therefore scanned (by either [`Scanning::scan_object`] or
145 /// [`Scanning::scan_object_and_trace_edges`]) at most once.
146 ///
147 /// When set to `false`, MMTk may enqueue an object multiple times due to optimizations, such as
148 /// using non-atomic operatios to mark objects. Consequently, an object may be scanned multiple
149 /// times during a GC.
150 ///
151 /// The default value is `false` because duplicated object-enqueuing is benign for most VMs, and
152 /// related optimizations, such as non-atomic marking, can improve GC speed. VM bindings can
153 /// override this if they need. For example, some VMs piggyback on object-scanning to visit
154 /// objects during a GC, but may have data race if multiple GC workers visit the same object at
155 /// the same time. Such VMs can set this constant to `true` to workaround this problem.
156 const UNIQUE_OBJECT_ENQUEUING: bool = false;
157
158 /// Return true if the given object supports slot enqueuing.
159 ///
160 /// - If this returns true, MMTk core will call `scan_object` on the object.
161 /// - Otherwise, MMTk core will call `scan_object_and_trace_edges` on the object.
162 ///
163 /// For maximum performance, the VM should support slot-enqueuing for as many objects as
164 /// practical. Also note that this method is called for every object to be scanned, so it
165 /// must be fast. The VM binding should avoid expensive checks and keep it as efficient as
166 /// possible.
167 ///
168 /// Arguments:
169 /// * `tls`: The VM-specific thread-local storage for the current worker.
170 /// * `object`: The object to be scanned.
171 fn support_slot_enqueuing(_tls: VMWorkerThread, _object: ObjectReference) -> bool {
172 true
173 }
174
175 /// Delegated scanning of a object, visiting each reference field encountered.
176 ///
177 /// The VM shall call `slot_visitor.visit_slot` on each reference field. This effectively
178 /// visits all outgoing edges from the current object in the form of slots.
179 ///
180 /// The VM may skip a reference field if it is not holding an object reference (e.g. if the
181 /// field is holding a null reference, or a tagged non-reference value such as small integer).
182 /// Even if not skipped, [`Slot::load`] will still return `None` if the slot is not holding an
183 /// object reference.
184 ///
185 /// The `memory_manager::is_mmtk_object` function can be used in this function if
186 /// - the "is_mmtk_object" feature is enabled, and
187 /// - `VM::VMObjectModel::NEED_VO_BITS_DURING_TRACING` is true.
188 ///
189 /// Arguments:
190 /// * `tls`: The VM-specific thread-local storage for the current worker.
191 /// * `object`: The object to be scanned.
192 /// * `slot_visitor`: Called back for each field.
193 fn scan_object<SV: SlotVisitor<VM::VMSlot>>(
194 tls: VMWorkerThread,
195 object: ObjectReference,
196 slot_visitor: &mut SV,
197 );
198
199 /// Delegated scanning of a object, visiting each reference field encountered, and tracing the
200 /// objects pointed by each field.
201 ///
202 /// The VM shall call `object_tracer.trace_object` with the argument being the object reference
203 /// held in each reference field. If the GC moves the object, the VM shall update the field so
204 /// that it refers to the object using the object reference returned from `trace_object`. This
205 /// effectively traces through all outgoing edges from the current object directly.
206 ///
207 /// The VM must skip reference fields that are not holding object references (e.g. if the
208 /// field is holding a null reference, or a tagged non-reference value such as small integer).
209 ///
210 /// The `memory_manager::is_mmtk_object` function can be used in this function if
211 /// - the "is_mmtk_object" feature is enabled, and
212 /// - `VM::VMObjectModel::NEED_VO_BITS_DURING_TRACING` is true.
213 ///
214 /// Arguments:
215 /// * `tls`: The VM-specific thread-local storage for the current worker.
216 /// * `object`: The object to be scanned.
217 /// * `object_tracer`: Called back for the object reference held in each field.
218 fn scan_object_and_trace_edges<OT: ObjectTracer>(
219 _tls: VMWorkerThread,
220 _object: ObjectReference,
221 _object_tracer: &mut OT,
222 ) {
223 unreachable!("scan_object_and_trace_edges() will not be called when support_slot_enqueuing() is always true.")
224 }
225
226 /// MMTk calls this method at the first time during a collection that thread's stacks
227 /// have been scanned. This can be used (for example) to clean up
228 /// obsolete compiled methods that are no longer being executed.
229 ///
230 /// Arguments:
231 /// * `partial_scan`: Whether the scan was partial or full-heap.
232 /// * `tls`: The GC thread that is performing the thread scan.
233 fn notify_initial_thread_scan_complete(partial_scan: bool, tls: VMWorkerThread);
234
235 /// Scan one mutator for stack roots.
236 ///
237 /// Some VM bindings may not be able to implement this method.
238 /// For example, the VM binding may only be able to enumerate all threads and
239 /// scan them while enumerating, but cannot scan stacks individually when given
240 /// the references of threads.
241 /// In that case, it can leave this method empty, and deal with stack
242 /// roots in [`Scanning::scan_vm_specific_roots`]. However, in that case, MMTk
243 /// does not know those roots are stack roots, and cannot perform any possible
244 /// optimization for the stack roots.
245 ///
246 /// The `memory_manager::is_mmtk_object` function can be used in this function if
247 /// - the "is_mmtk_object" feature is enabled.
248 ///
249 /// Arguments:
250 /// * `tls`: The GC thread that is performing this scanning.
251 /// * `mutator`: The reference to the mutator whose roots will be scanned.
252 /// * `factory`: The VM uses it to create work packets for scanning roots.
253 fn scan_roots_in_mutator_thread(
254 tls: VMWorkerThread,
255 mutator: &'static mut Mutator<VM>,
256 factory: impl RootsWorkFactory<VM::VMSlot>,
257 );
258
259 /// Scan VM-specific roots. The creation of all root scan tasks (except thread scanning)
260 /// goes here.
261 ///
262 /// The `memory_manager::is_mmtk_object` function can be used in this function if
263 /// - the "is_mmtk_object" feature is enabled.
264 ///
265 /// Arguments:
266 /// * `tls`: The GC thread that is performing this scanning.
267 /// * `factory`: The VM uses it to create work packets for scanning roots.
268 fn scan_vm_specific_roots(tls: VMWorkerThread, factory: impl RootsWorkFactory<VM::VMSlot>);
269
270 /// Return whether the VM supports return barriers. This is unused at the moment.
271 fn supports_return_barrier() -> bool;
272
273 /// Prepare for another round of root scanning in the same GC. Some GC algorithms
274 /// need multiple transitive closures, and each transitive closure starts from
275 /// root scanning. We expect the binding to provide the same root set for every
276 /// round of root scanning in the same GC. Bindings can use this call to get
277 /// ready for another round of root scanning to make sure that the same root
278 /// set will be returned in the upcoming calls of root scanning methods,
279 /// such as [`crate::vm::Scanning::scan_roots_in_mutator_thread`] and
280 /// [`crate::vm::Scanning::scan_vm_specific_roots`].
281 fn prepare_for_roots_re_scanning();
282
283 /// Process weak references.
284 ///
285 /// This function is called in a GC after the transitive closure from roots is computed, that
286 /// is, all reachable objects from roots are reached. This function gives the VM binding an
287 /// opportunitiy to process finalizers and weak references.
288 ///
289 /// MMTk core enables the VM binding to do the following in this function:
290 ///
291 /// 1. Query if an object is already reached.
292 /// - by calling `ObjectReference::is_reachable()`
293 /// 2. Get the new address of an object if it is already reached.
294 /// - by calling `ObjectReference::get_forwarded_object()`
295 /// 3. Keep an object and its descendents alive if not yet reached.
296 /// - using `tracer_context`
297 /// 4. Request this function to be called again after transitive closure is finished again.
298 /// - by returning `true`
299 ///
300 /// The `tracer_context` parameter provides the VM binding the mechanism for retaining
301 /// unreachable objects (i.e. keeping them alive in this GC). The following snippet shows a
302 /// typical use case of handling finalizable objects for a Java-like language.
303 ///
304 /// ```rust
305 /// let finalizable_objects: Vec<ObjectReference> = my_vm::get_finalizable_object();
306 /// let mut new_finalizable_objects = vec![];
307 ///
308 /// tracer_context.with_tracer(worker, |tracer| {
309 /// for object in finalizable_objects {
310 /// if object.is_reachable() {
311 /// // `object` is still reachable.
312 /// // It may have been moved if it is a copying GC.
313 /// let new_object = object.get_forwarded_object().unwrap_or(object);
314 /// new_finalizable_objects.push(new_object);
315 /// } else {
316 /// // `object` is unreachable.
317 /// // Retain it, and enqueue it for postponed finalization.
318 /// let new_object = tracer.trace_object(object);
319 /// my_vm::enqueue_finalizable_object_to_be_executed_later(new_object);
320 /// }
321 /// }
322 /// });
323 /// ```
324 ///
325 /// Within the closure `|tracer| { ... }`, the VM binding can call `tracer.trace_object(object)`
326 /// to retain `object` and get its new address if moved. After `with_tracer` returns, it will
327 /// create work packets in the `VMRefClosure` work bucket to compute the transitive closure from
328 /// the objects retained in the closure.
329 ///
330 /// The `memory_manager::is_mmtk_object` function can be used in this function if
331 /// - the "is_mmtk_object" feature is enabled, and
332 /// - `VM::VMObjectModel::NEED_VO_BITS_DURING_TRACING` is true.
333 ///
334 /// Arguments:
335 /// * `worker`: The current GC worker.
336 /// * `tracer_context`: Use this to get access an `ObjectTracer` and use it to retain and update
337 /// weak references.
338 ///
339 /// If `process_weak_refs` returns `true`, then `process_weak_refs` will be called again after
340 /// all work packets in the `VMRefClosure` work bucket has been executed, by which time all
341 /// objects reachable from the objects retained in this function will have been reached.
342 ///
343 /// # Performance notes
344 ///
345 /// **Retain as many objects as needed in one invocation of `tracer_context.with_tracer`, and
346 /// avoid calling `with_tracer` again and again** for each object. The `tracer` provided by
347 /// `ObjectTracerFactory::with_tracer` enqueues retained objects in an internal list specific to
348 /// this invocation of `with_tracer`, and will create reasonably sized work packets to compute
349 /// the transitive closure. This means the invocation of `with_tracer` has a non-trivial
350 /// overhead, but each invocation of `tracer.trace_object` is cheap.
351 ///
352 /// *Don't do this*:
353 ///
354 /// ```rust
355 /// for object in objects {
356 /// tracer_context.with_tracer(worker, |tracer| { // This is expensive! DON'T DO THIS!
357 /// tracer.trace_object(object);
358 /// });
359 /// }
360 /// ```
361 ///
362 /// **Use `ObjectReference::get_forwarded_object()` to get the forwarded address of reachable
363 /// objects. Only use `tracer.trace_object` for retaining unreachable objects.** If
364 /// `trace_object` is called on an already reached object, it will also return its new address
365 /// if moved. However, `tracer_context.with_tracer` has a cost, and the VM binding may
366 /// accidentally "resurrect" dead objects if failed to check `object.is_reachable()` first. If
367 /// the VM binding does not intend to retain any objects, it should completely avoid touching
368 /// `tracer_context`.
369 ///
370 /// **Clone the `tracer_context` for parallelism.** The `ObjectTracerContext` has `Clone` as
371 /// its supertrait. The VM binding can clone it and distribute each clone into a work packet.
372 /// By doing so, the VM binding can parallelize the processing of finalizers and weak references
373 /// by creating multiple work packets.
374 fn process_weak_refs(
375 _worker: &mut GCWorker<VM>,
376 _tracer_context: impl ObjectTracerContext<VM>,
377 ) -> bool {
378 false
379 }
380
381 /// Forward weak references.
382 ///
383 /// This function will only be called in the forwarding stage when using the mark-compact GC
384 /// algorithm. Mark-compact computes transive closure twice during each GC. It marks objects
385 /// in the first transitive closure, and forward references in the second transitive closure.
386 ///
387 /// Arguments:
388 /// * `worker`: The current GC worker.
389 /// * `tracer_context`: Use this to get access an `ObjectTracer` and use it to update weak
390 /// references.
391 fn forward_weak_refs(
392 _worker: &mut GCWorker<VM>,
393 _tracer_context: impl ObjectTracerContext<VM>,
394 ) {
395 }
396}