mmtk/plan/tracing/
mod.rs

1//! This module contains code useful for tracing,
2//! i.e. visiting the reachable objects by traversing all or part of an object graph.
3
4use std::marker::PhantomData;
5
6use crate::plan::PlanTraceObject;
7use crate::policy::gc_work::TraceKind;
8use crate::scheduler::{GCWorker, EDGES_WORK_BUFFER_SIZE};
9use crate::util::{ObjectReference, VMThread, VMWorkerThread};
10use crate::vm::{Scanning, VMBinding};
11use crate::{Plan, MMTK};
12
13pub(crate) mod gc_work;
14
15/// This trait provides methods used during a trace.  The most important method is
16/// [`Self::trace_object`] which provides the way to trace an object during the current trace.  Many
17/// work packets depend on this trait to trace objects.
18///
19/// We need different implementations of this trait for the different behaviors in
20///
21/// -   different plans
22/// -   different traces of the same plan (e.g. marking trace and forwarding trace; nursery GC and
23///     mature GC; fast GC and defrag GC; etc.), and
24/// -   pinning (transitive or not) roots and regular edges.
25///
26/// Therefore, each GC selects one [`GCWorkContext`], and each [`GCWorkContext`] selects two
27/// [`Trace`] implementations for default edges and pinning edges, respectively.
28///
29/// A type of this trait shall be stateless and immutable.  It shall be cheap to instantiate from an
30/// [`MMTK`] instance.
31///
32/// This trait requires the [`Clone`] trait, and cloned instances behave exactly the same.
33///
34/// [`GCWorkContext`]: crate::scheduler::work::GCWorkContext
35pub trait Trace: 'static + Send + Clone {
36    /// The VM binding type this type serves.
37    type VM: VMBinding;
38
39    /// Instantiate from an [`MMTK`] instance.
40    ///
41    /// Note that values of this trait are usually instantiated in work packets that use it. It
42    /// should be reasonably cheap to instantiate.  Most types (such as [`PlanTrace`] and
43    /// [`GenNurseryTrace`]) only need a reference to the plan which can be downcasted from
44    /// [`MMTK::plan`].
45    ///
46    /// [`GenNurseryTrace`]: crate::plan::generational::gc_work::GenNurseryTrace
47    fn from_mmtk(mmtk: &'static MMTK<Self::VM>) -> Self;
48
49    /// Trace the `object`.  More precisely, it visits an object graph *edge* that points to
50    /// `object`.
51    ///
52    /// It may add `object` (or the new [`ObjectReference`] for `object` in a moving GC) into the
53    /// `queue`, which means its children needs to be recursively traversed.
54    ///
55    /// In non-moving GC, the return value is always `object`.  In moving GC, the return value may
56    /// be `object` or the new [`ObjectReference`] for the `object`.  If the return value is not
57    /// `object`, the slot that represents the object graph edge needs to be updated to hold the
58    /// return value instead.
59    ///
60    /// Its implementation generally needs to figure out which space an object resides in, and
61    /// invoke the right "trace object" method of the space for the current trace.
62    ///
63    /// # Notes
64    ///
65    /// ## The enqueued value and the return value
66    ///
67    /// The return value may be different from the enqueued value.  For example, during the
68    /// forwarding stage of MarkCompact, it returns the new object reference, but enqueues the old
69    /// `object` because the object has not been moved, yet.
70    ///
71    /// ## The `queue` can be a callback instead of a collection
72    ///
73    /// `FnMut(ObjectReference)` implements the [`ObjectQueue`] trait.  This means you can use a
74    /// lambda expression at the place of the `queue` argument.  For example, you can scan the
75    /// object immediately instead of adding the object reference into a container.
76    ///
77    /// Example:
78    ///
79    /// ```rust
80    /// trace.trace_object(worker, object, &mut |enqueued_object| {
81    ///     // Process the enqueued_object here...
82    /// });
83    /// ```
84    fn trace_object<Q: ObjectQueue>(
85        &self,
86        worker: &mut GCWorker<Self::VM>,
87        object: ObjectReference,
88        queue: &mut Q,
89    ) -> ObjectReference;
90
91    /// The post-scan hook to be call after scanning `object`.
92    ///
93    /// Each object is scanned by [`Scanning::scan_object`] or
94    /// [`Scanning::scan_object_and_trace_edges`], and this function will be called after scanning
95    /// an object as a hook to invoke possible policy-specific post-scan methods.  If `object` is in
96    /// a space that needs such a hook, this method should call such hook of the space.  Otherwise,
97    /// this method may do nothing.
98    ///
99    /// Currently, only [`ImmixSpace`] needs this hook to mark the line.
100    ///
101    /// [`ImmixSpace`]: crate::policy::immix::ImmixSpace
102    fn post_scan_object(&self, object: ObjectReference);
103
104    /// Return `true` if [`Self::trace_object`] may move any object.  If any space of the current
105    /// plan may move objects during this trace, this method should return `true`.
106    ///
107    /// If it returns `false`, it means [`Self::trace_object`] is guaranteed not to move the
108    /// `object`, and it is safe to elide updating a slot after tracing the reference in the slot.
109    /// It is always correct to conservatively return `true`.
110    ///
111    /// Note that this method is called very frequently, so it must be efficient.
112    fn may_move_objects() -> bool;
113}
114
115/// A shorthand for getting the slot type from a [`Trace`] instance.
116pub type SlotOfTrace<T> = <<T as Trace>::VM as VMBinding>::VMSlot;
117
118/// A [`Trace`] implementation that dispatches the `trace_object` method through
119/// [`crate::policy::sft::SFT::sft_trace_object`] using the Space Function Table (SFT).
120///
121/// Because SFT methods cannot be general, `SFTTrace` cannot be used for plans that needs multiple
122/// traces.  It is sufficient for simple plans such as `MarkSweep` and `SemiSpace`.
123#[allow(dead_code)]
124#[derive(Default)]
125pub struct SFTTrace<VM: VMBinding> {
126    phantom_data: PhantomData<VM>,
127}
128
129impl<VM: VMBinding> Clone for SFTTrace<VM> {
130    fn clone(&self) -> Self {
131        Self {
132            phantom_data: PhantomData,
133        }
134    }
135}
136
137impl<VM: VMBinding> Trace for SFTTrace<VM> {
138    type VM = VM;
139
140    fn from_mmtk(_mmtk: &'static MMTK<Self::VM>) -> Self {
141        Default::default()
142    }
143
144    fn trace_object<Q: ObjectQueue>(
145        &self,
146        worker: &mut GCWorker<VM>,
147        object: ObjectReference,
148        queue: &mut Q,
149    ) -> ObjectReference {
150        use crate::policy::sft::GCWorkerMutRef;
151
152        // Erase <VM> type parameter
153        let worker = GCWorkerMutRef::new(worker);
154
155        // Invoke trace object on sft
156        let sft = unsafe { crate::mmtk::SFT_MAP.get_unchecked(object.to_raw_address()) };
157
158        // Because `sft.sft_trace_object` cannot have generic parameters, we can't pass `queue`
159        // directly to it.  Instead we let `sft_trace_object` enqueue to this `tmp_queue` and
160        // forward the enqueued object to `queue`.
161        let mut tmp_queue = None;
162        let result = sft.sft_trace_object(&mut tmp_queue, object, worker);
163        if let Some(queued_object) = tmp_queue {
164            queue.enqueue(queued_object);
165        }
166        result
167    }
168
169    fn post_scan_object(&self, _object: ObjectReference) {
170        // Do nothing.  SFTTrace is only suitable for plans that don't need post_scan_object.
171    }
172
173    fn may_move_objects() -> bool {
174        // We conservatively assume it may move objects.
175        true
176    }
177}
178
179/// A [`Trace`] implementation that dispatches the `trace_object` method through
180/// [`PlanTraceObject::trace_object`].  It is applicable to all plans that implement
181/// [`PlanTraceObject`].
182///
183/// Plans usually don't implement `PlanTraceObject` directly, but use the
184/// `#[derive(PlanTraceObject)]` macro. See [`PlanTraceObject`] for more details.
185pub struct PlanTrace<P: Plan + PlanTraceObject<P::VM>, const KIND: TraceKind> {
186    plan: &'static P,
187}
188
189impl<P: Plan + PlanTraceObject<P::VM>, const KIND: TraceKind> Clone for PlanTrace<P, KIND> {
190    fn clone(&self) -> Self {
191        Self { plan: self.plan }
192    }
193}
194
195impl<P: Plan + PlanTraceObject<P::VM>, const KIND: TraceKind> Trace for PlanTrace<P, KIND> {
196    type VM = P::VM;
197
198    fn from_mmtk(mmtk: &'static MMTK<Self::VM>) -> Self {
199        let plan = mmtk.get_plan().downcast_ref::<P>().unwrap();
200        Self { plan }
201    }
202
203    fn trace_object<Q: ObjectQueue>(
204        &self,
205        worker: &mut GCWorker<Self::VM>,
206        object: ObjectReference,
207        queue: &mut Q,
208    ) -> ObjectReference {
209        self.plan.trace_object::<Q, KIND>(queue, object, worker)
210    }
211
212    fn post_scan_object(&self, object: ObjectReference) {
213        self.plan.post_scan_object(object);
214    }
215
216    fn may_move_objects() -> bool {
217        P::may_move_objects::<KIND>()
218    }
219}
220
221/// A placeholder for unsupported traces.  For example, it can be used for
222/// [`crate::scheduler::GCWorkContext::PinningTrace`] for plans that don't support object pinning.
223#[derive(Default)]
224pub struct UnsupportedTrace<VM: VMBinding> {
225    phantom_data: PhantomData<VM>,
226}
227
228impl<VM: VMBinding> Clone for UnsupportedTrace<VM> {
229    fn clone(&self) -> Self {
230        Self {
231            phantom_data: PhantomData,
232        }
233    }
234}
235
236impl<VM: VMBinding> Trace for UnsupportedTrace<VM> {
237    type VM = VM;
238
239    fn from_mmtk(_mmtk: &'static MMTK<Self::VM>) -> Self {
240        panic!("UnsupportedTrace cannot be constructed.")
241    }
242
243    fn trace_object<Q: ObjectQueue>(
244        &self,
245        _worker: &mut GCWorker<VM>,
246        _object: ObjectReference,
247        _queue: &mut Q,
248    ) -> ObjectReference {
249        panic!("UnsupportedTrace::trace_object must not be called.")
250    }
251
252    fn post_scan_object(&self, _object: ObjectReference) {
253        panic!("UnsupportedTrace::post_scan_object must not be called.")
254    }
255
256    fn may_move_objects() -> bool {
257        panic!("UnsupportedTrace::may_move_objects must not be called.")
258    }
259}
260
261/// This trait represents an object queue to enqueue objects during tracing.
262pub trait ObjectQueue {
263    /// Enqueue an object into the queue.
264    fn enqueue(&mut self, object: ObjectReference);
265}
266
267impl<F: FnMut(ObjectReference)> ObjectQueue for F {
268    fn enqueue(&mut self, object: ObjectReference) {
269        self(object)
270    }
271}
272
273impl ObjectQueue for Option<ObjectReference> {
274    fn enqueue(&mut self, object: ObjectReference) {
275        debug_assert!(self.is_none());
276        *self = Some(object);
277    }
278}
279
280/// A one-element object queue backed by an `Option<ObjectReference>`.  Used by SFT to decouple the
281/// concrete [`ObjectQueue`] type from the dynamic dispatching.
282pub type OptionObjectQueue = Option<ObjectReference>;
283
284/// A vector queue for object references.
285pub type VectorObjectQueue = VectorQueue<ObjectReference>;
286
287/// An implementation of `ObjectQueue` using a `Vec`.
288///
289/// This can also be used as a buffer. For example, the mark stack or the write barrier mod-buffer.
290pub struct VectorQueue<T> {
291    /// Enqueued nodes.
292    buffer: Vec<T>,
293}
294
295impl<T> VectorQueue<T> {
296    /// Reserve a capacity of this on first enqueue to avoid frequent resizing.
297    const CAPACITY: usize = EDGES_WORK_BUFFER_SIZE;
298
299    /// Create an empty `VectorObjectQueue`.
300    pub fn new() -> Self {
301        Self { buffer: Vec::new() }
302    }
303
304    /// Return `true` if the queue is empty.
305    pub fn is_empty(&self) -> bool {
306        self.buffer.is_empty()
307    }
308
309    /// Return the contents of the underlying vector.  It will empty the queue.
310    pub fn take(&mut self) -> Vec<T> {
311        std::mem::take(&mut self.buffer)
312    }
313
314    /// Consume this `VectorObjectQueue` and return its underlying vector.
315    pub fn into_vec(self) -> Vec<T> {
316        self.buffer
317    }
318
319    /// Check if the buffer size reaches `CAPACITY`.
320    pub fn is_full(&self) -> bool {
321        self.buffer.len() >= Self::CAPACITY
322    }
323
324    /// Push an element to the queue. If the queue is empty, it will reserve
325    /// space to hold the number of elements defined by the capacity.
326    /// The user of this method needs to make sure the queue length does
327    /// not exceed the capacity to avoid allocating more space
328    /// (this method will not check the length against the capacity).
329    pub fn push(&mut self, v: T) {
330        if self.buffer.is_empty() {
331            self.buffer.reserve(Self::CAPACITY);
332        }
333        self.buffer.push(v);
334    }
335
336    /// Return the len of the queue
337    pub fn len(&self) -> usize {
338        self.buffer.len()
339    }
340
341    /// Empty the queue
342    pub fn clear(&mut self) {
343        self.buffer.clear()
344    }
345}
346
347impl<T> Default for VectorQueue<T> {
348    fn default() -> Self {
349        Self::new()
350    }
351}
352
353impl ObjectQueue for VectorQueue<ObjectReference> {
354    fn enqueue(&mut self, v: ObjectReference) {
355        self.push(v);
356    }
357}
358
359/// For iterating over the slots of an object.
360// FIXME: This type iterates slots, but all of its current use cases only care about the values in the slots.
361// And it currently only works if the object supports slot enqueuing (i.e. `Scanning::scan_object` is implemented).
362// We may refactor the interface according to <https://github.com/mmtk/mmtk-core/issues/1375>
363pub(crate) struct SlotIterator<VM: VMBinding> {
364    _p: PhantomData<VM>,
365}
366
367impl<VM: VMBinding> SlotIterator<VM> {
368    /// Iterate over the slots of an object by applying a function to each slot.
369    pub fn iterate_fields<F: FnMut(VM::VMSlot)>(object: ObjectReference, _tls: VMThread, mut f: F) {
370        // FIXME: We should use tls from the arguments.
371        // See https://github.com/mmtk/mmtk-core/issues/1375
372        let fake_tls = VMWorkerThread(VMThread::UNINITIALIZED);
373        if !<VM::VMScanning as Scanning<VM>>::support_slot_enqueuing(fake_tls, object) {
374            panic!("SlotIterator::iterate_fields cannot be used on objects that don't support slot-enqueuing");
375        }
376        <VM::VMScanning as Scanning<VM>>::scan_object(fake_tls, object, &mut f);
377    }
378}