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