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