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}