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