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}