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