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