mmtk/plan/tracing/gc_work/
root.rs

1use std::marker::PhantomData;
2
3use crate::{
4    plan::{
5        tracing::{
6            gc_work::closure::{ProcessNodes, ProcessSlots},
7            Trace,
8        },
9        VectorObjectQueue,
10    },
11    scheduler::{GCWork, GCWorker, WorkBucketStage},
12    util::ObjectReference,
13    vm::{RootsKind, RootsWorkFactory, VMBinding},
14    MMTK,
15};
16
17/// An implementation of [`RootsWorkFactory`] for stop-the-world tracing GC.  It will create work
18/// packets to find the transitive closure from roots, assuming mutators are stopped during the GC.
19///
20/// It creates the [`ProcessSlots`] work packet to handle non-pinning roots, and
21/// [`ProcessPinningRoots`] to handle pinning roots (transitive or not).  The work packets will be
22/// added to the [`WorkBucketStage::TPinningClosure`], [`WorkBucketStage::PinningRootsTrace`] and
23/// [`WorkBucketStage::Closure`] buckets depending on the kinds of roots.
24///
25/// `DT` and `PT` are the [`Trace`] types for the default trace and pinning trace, respectively.
26pub(crate) struct DefaultRootsWorkFactory<VM: VMBinding, DT: Trace<VM = VM>, PT: Trace<VM = VM>> {
27    pub(crate) mmtk: &'static MMTK<VM>,
28    phantom: PhantomData<(DT, PT)>,
29}
30
31impl<VM: VMBinding, DT: Trace<VM = VM>, PT: Trace<VM = VM>> Clone
32    for DefaultRootsWorkFactory<VM, DT, PT>
33{
34    fn clone(&self) -> Self {
35        Self {
36            mmtk: self.mmtk,
37            phantom: PhantomData,
38        }
39    }
40}
41
42impl<VM: VMBinding, DT: Trace<VM = VM>, PT: Trace<VM = VM>> RootsWorkFactory<VM::VMSlot>
43    for DefaultRootsWorkFactory<VM, DT, PT>
44{
45    fn create_process_roots_work(&mut self, slots: Vec<VM::VMSlot>) {
46        // Note: We should use the same USDT name "mmtk:roots" for all the three kinds of roots. A
47        // VM binding may not call all of the three methods in this impl. For example, the OpenJDK
48        // binding only calls `create_process_roots_work`, and the Ruby binding only calls
49        // `create_process_pinning_roots_work`. Because `DefaultRootsWorkFactory<VM, DT, PT>` is a
50        // generic type, the Rust compiler emits the function bodies on demand, so the resulting
51        // machine code may not contain all three USDT trace points.  If they have different names,
52        // and our `capture.bt` mentions all of them, `bpftrace` may complain that it cannot find
53        // one or more of those USDT trace points in the binary.
54        probe!(mmtk, roots, RootsKind::NORMAL, slots.len());
55
56        #[cfg(feature = "sanity")]
57        self.mmtk
58            .sanity_checker
59            .lock()
60            .unwrap()
61            .add_root_slots(slots.clone());
62
63        crate::memory_manager::add_work_packet(
64            self.mmtk,
65            WorkBucketStage::Closure,
66            ProcessSlots::<DT>::new(slots, WorkBucketStage::Closure),
67        );
68    }
69
70    fn create_process_pinning_roots_work(&mut self, nodes: Vec<ObjectReference>) {
71        probe!(mmtk, roots, RootsKind::PINNING, nodes.len());
72
73        #[cfg(feature = "sanity")]
74        self.mmtk
75            .sanity_checker
76            .lock()
77            .unwrap()
78            .add_root_nodes(nodes.clone());
79
80        // Will process roots within the PinningRootsTrace bucket
81        // And put work in the Closure bucket
82        crate::memory_manager::add_work_packet(
83            self.mmtk,
84            WorkBucketStage::PinningRootsTrace,
85            ProcessPinningRoots::<VM, PT, DT>::new(nodes, WorkBucketStage::Closure),
86        );
87    }
88
89    fn create_process_tpinning_roots_work(&mut self, nodes: Vec<ObjectReference>) {
90        probe!(mmtk, roots, RootsKind::TPINNING, nodes.len());
91
92        #[cfg(feature = "sanity")]
93        self.mmtk
94            .sanity_checker
95            .lock()
96            .unwrap()
97            .add_root_nodes(nodes.clone());
98
99        crate::memory_manager::add_work_packet(
100            self.mmtk,
101            WorkBucketStage::TPinningClosure,
102            ProcessPinningRoots::<VM, PT, PT>::new(nodes, WorkBucketStage::TPinningClosure),
103        );
104    }
105}
106
107impl<VM: VMBinding, DT: Trace<VM = VM>, PT: Trace<VM = VM>> DefaultRootsWorkFactory<VM, DT, PT> {
108    pub(crate) fn new(mmtk: &'static MMTK<VM>) -> Self {
109        Self {
110            mmtk,
111            phantom: PhantomData,
112        }
113    }
114}
115
116/// This work packet processes pinning roots during stop-the-world tracing GC.
117///
118/// Note that by definition, a "root" is an *edge* from outside the object graph to an object.  This
119/// work packet represents each edge as the `ObjectReference` of the object the edge points to (i.e.
120/// the referent).  Because pinning roots by definition cannot be updated, we don't need to
121/// represent the edges as [`Slot`].
122///
123/// [`Slot`]: crate::vm::slot::Slot
124///
125/// The `roots` member holds a list of `ObjectReference` to objects directly pointed by roots. These
126/// objects will be traced using `R2OT` (Root-to-Object Trace).
127///
128/// After that, it will create work packets for tracing their children.  Those work packets (and the
129/// work packets further created by them) will use `O2OT` (Object-to-Object Trace) as their `Trace`
130/// implementations.
131///
132/// Because `roots` are pinning roots, `R2OT` must be a `Trace` that never moves any object.
133///
134/// The choice of `O2OT` determines whether the `roots` are transitively pinning or not.
135///
136/// -   If `O2OT` is set to a `Trace` that never moves objects, no descendents of `roots` will be
137///     moved in this GC.  That implements transitive pinning roots.
138/// -   If `O2OT` may move objects, then this `ProcessRootsNode<VM, R2OT, O2OT>` work packet will
139///     only pin the objects in `roots` (because `R2OT` must not move objects anyway), but not their
140///     descendents.
141pub(crate) struct ProcessPinningRoots<VM: VMBinding, R2OT: Trace<VM = VM>, O2OT: Trace<VM = VM>> {
142    phantom: PhantomData<(VM, R2OT, O2OT)>,
143    roots: Vec<ObjectReference>,
144    bucket: WorkBucketStage,
145}
146
147impl<VM: VMBinding, R2OT: Trace<VM = VM>, O2OT: Trace<VM = VM>>
148    ProcessPinningRoots<VM, R2OT, O2OT>
149{
150    pub fn new(nodes: Vec<ObjectReference>, bucket: WorkBucketStage) -> Self {
151        Self {
152            phantom: PhantomData,
153            roots: nodes,
154            bucket,
155        }
156    }
157}
158
159impl<VM: VMBinding, R2OT: Trace<VM = VM>, O2OT: Trace<VM = VM>> GCWork<VM>
160    for ProcessPinningRoots<VM, R2OT, O2OT>
161{
162    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
163        trace!("ProcessPinningRoots");
164
165        let num_roots = self.roots.len();
166
167        // This step conceptually traces the edges from root slots to the objects they point to.
168        // However, VMs that deliver root objects instead of root slots are incapable of updating
169        // root slots.  Therefore, we call `trace_object` on those objects, and assert the GC
170        // doesn't move those objects because we cannot store the updated references back to the
171        // slots.
172        //
173        // The `root_objects_to_scan` variable will hold those root objects which are traced for the
174        // first time.  We will create a work packet for scanning those roots.
175        let root_objects_to_scan = {
176            let mut queue = VectorObjectQueue::new();
177
178            let r2o_trace = R2OT::from_mmtk(mmtk);
179
180            for object in self.roots.iter().copied() {
181                let new_object = r2o_trace.trace_object(worker, object, &mut queue);
182                debug_assert_eq!(
183                    object, new_object,
184                    "Object moved while tracing root unmovable root object: {} -> {}",
185                    object, new_object
186                );
187            }
188
189            queue.take()
190        };
191
192        let num_enqueued_nodes = root_objects_to_scan.len();
193        probe!(mmtk, process_pinning_roots, num_roots, num_enqueued_nodes);
194
195        if !root_objects_to_scan.is_empty() {
196            let work = ProcessNodes::<O2OT>::new(root_objects_to_scan, self.bucket);
197            worker.add_work(self.bucket, work);
198        }
199
200        trace!("ProcessPinningRoots End");
201    }
202}