mmtk/plan/tracing/gc_work/
closure.rs

1use std::marker::PhantomData;
2
3use crate::{
4    plan::{
5        tracing::{gc_work::DefaultObjectTracerContext, SlotOfTrace, Trace},
6        VectorObjectQueue, VectorQueue,
7    },
8    scheduler::{GCWork, GCWorker, GCWorkerShared, WorkBucketStage},
9    util::{ObjectReference, VMWorkerThread},
10    vm::{slot::Slot, ObjectTracerContext, Scanning, VMBinding},
11    MMTK,
12};
13
14/// A work packet for processing slots during a stop-the-world tracing GC and the final mark pause
15/// of a concurrent GC.
16///
17/// It will call `trace_object` on the value of each slot, and updates the slot if the object is
18/// moved or forwarded.  It will spawn or immediately run the [`ProcessNodes`] work packet to
19/// scan newly traced objects.
20pub struct ProcessSlots<T: Trace> {
21    slots: Vec<SlotOfTrace<T>>,
22    bucket: WorkBucketStage,
23}
24
25impl<T: Trace> ProcessSlots<T> {
26    const SCAN_OBJECTS_IMMEDIATELY: bool = true;
27
28    pub fn new(slots: Vec<SlotOfTrace<T>>, bucket: WorkBucketStage) -> Self {
29        Self { slots, bucket }
30    }
31
32    fn process_slots(
33        &mut self,
34        worker: &mut GCWorker<T::VM>,
35        trace: T,
36    ) -> VectorQueue<ObjectReference> {
37        let mut queue = VectorObjectQueue::new();
38
39        for slot in self.slots.iter() {
40            if let Some(object) = slot.load() {
41                let new_object = trace.trace_object(worker, object, &mut queue);
42                if T::may_move_objects() && new_object != object {
43                    slot.store(new_object);
44                }
45            }
46        }
47
48        queue
49    }
50
51    fn flush(&mut self, worker: &mut GCWorker<T::VM>, mut queue: VectorQueue<ObjectReference>) {
52        if queue.is_empty() {
53            return;
54        }
55
56        let queued_objects = queue.take();
57        let mut work = ProcessNodes::<T>::new(queued_objects, self.bucket);
58
59        if Self::SCAN_OBJECTS_IMMEDIATELY {
60            work.do_work(worker, worker.mmtk);
61        } else {
62            worker.add_work(self.bucket, work);
63        }
64    }
65}
66
67impl<T: Trace> GCWork<T::VM> for ProcessSlots<T> {
68    fn do_work(&mut self, worker: &mut GCWorker<T::VM>, mmtk: &'static MMTK<T::VM>) {
69        probe!(mmtk, process_slots, self.slots.len());
70
71        let trace = T::from_mmtk(mmtk);
72
73        #[cfg(feature = "extreme_assertions")]
74        if crate::util::slot_logger::should_check_duplicate_slots(mmtk.get_plan()) {
75            for slot in self.slots.iter() {
76                // log slot, panic if already logged
77                mmtk.slot_logger.log_slot(*slot);
78            }
79        }
80
81        let queue = self.process_slots(worker, trace);
82
83        self.flush(worker, queue);
84    }
85}
86
87/// A work packet for scanning objects and optionally do node-enqueuing tracing during a
88/// stop-the-world tracing GC and the final mark pause of a concurrent GC.
89///
90/// It will scan each object.  For objects that supports slot enqueuing, it will collect their slots
91/// and spawn [`ProcessSlots`] work packets to trace them.  For objects that don't support slot
92/// enqueuing, it will immediately trace their slots and spawn other [`ProcessNodes`] work packets
93/// to process their newly traced children.  It is the VM's responsibility to implement
94/// [`Scanning::scan_object_and_trace_edges`] to update the references to point to the new addresses
95/// in such a case.
96pub struct ProcessNodes<T: Trace> {
97    objects: Vec<ObjectReference>,
98    bucket: WorkBucketStage,
99    phantom_data: PhantomData<T>,
100}
101
102impl<T: Trace> ProcessNodes<T> {
103    pub fn new(objects: Vec<ObjectReference>, bucket: WorkBucketStage) -> Self {
104        Self {
105            objects,
106            bucket,
107            phantom_data: PhantomData,
108        }
109    }
110
111    fn try_enqueue_slots(
112        &mut self,
113        worker: &mut GCWorker<T::VM>,
114        tls: VMWorkerThread,
115        trace: &T,
116    ) -> Vec<ObjectReference> {
117        // We record objects that don't support slot-enqueuing tracing and process them later.
118        let mut scan_later = Vec::new();
119
120        let mut slots = VectorQueue::new();
121
122        let flush = |slots: &mut VectorQueue<_>, worker: &mut GCWorker<T::VM>| {
123            let buffer = slots.take();
124            let work_packet = ProcessSlots::<T>::new(buffer, self.bucket);
125            worker.add_work(self.bucket, work_packet);
126        };
127
128        // For any object we need to scan, we count its live bytes.
129        // Check the option outside the loop for better performance.
130        //
131        // TODO: Currently all objects reached in a GC will be processed here,
132        // so it is a good place to do statistics for all reachable objects.
133        // In the future, when we refactor the ProcessNodes and ProcessSlots work packets
134        // so that each of them can compute the transitive closure alone (i.e. removing double enqueuing),
135        // we need to make sure both work packets will count the live bytes.
136        if crate::util::rust_util::unlikely(*worker.mmtk.get_options().count_live_bytes_in_gc) {
137            // Borrow before the loop.
138            let mut live_bytes_stats = worker.shared.live_bytes_per_space.borrow_mut();
139            for object in self.objects.iter().copied() {
140                GCWorkerShared::<T::VM>::increase_live_bytes(&mut live_bytes_stats, object);
141            }
142        }
143
144        for object in self.objects.iter().copied() {
145            if <T::VM as VMBinding>::VMScanning::support_slot_enqueuing(tls, object) {
146                trace!("Scan object (slot) {}", object);
147                // If an object supports slot-enqueuing, we enqueue its slots.
148                <T::VM as VMBinding>::VMScanning::scan_object(tls, object, &mut |slot| {
149                    slots.push(slot);
150                    if slots.is_full() {
151                        flush(&mut slots, worker);
152                    }
153                });
154                trace.post_scan_object(object);
155            } else {
156                // If an object does not support slot-enqueuing, we have to use
157                // `Scanning::scan_object_and_trace_edges` and offload the job of updating the
158                // reference field to the VM.
159                //
160                // TODO: We may refactor this work packet to do slot-enqueuing and node-enqueuing in
161                // one loop.
162                scan_later.push(object);
163            }
164        }
165
166        if !slots.is_empty() {
167            flush(&mut slots, worker);
168        }
169
170        scan_later
171    }
172
173    fn do_node_enqueuing_tracing(
174        &mut self,
175        worker: &mut GCWorker<T::VM>,
176        tls: VMWorkerThread,
177        trace: T,
178        scan_later: Vec<ObjectReference>,
179    ) {
180        if scan_later.is_empty() {
181            return;
182        }
183
184        let object_tracer_context = DefaultObjectTracerContext::<T>::new(self.bucket);
185
186        object_tracer_context.with_tracer(worker, |object_tracer| {
187            // Scan objects and trace their outgoing edges at the same time.
188            for object in scan_later.iter().copied() {
189                trace!("Scan object (node) {}", object);
190                <T::VM as VMBinding>::VMScanning::scan_object_and_trace_edges(
191                    tls,
192                    object,
193                    object_tracer,
194                );
195                trace.post_scan_object(object);
196            }
197        });
198    }
199}
200
201impl<T: Trace> GCWork<T::VM> for ProcessNodes<T> {
202    fn do_work(&mut self, worker: &mut GCWorker<T::VM>, mmtk: &'static MMTK<T::VM>) {
203        trace!("ScanObjects");
204
205        let tls = worker.tls;
206        let trace = T::from_mmtk(mmtk);
207
208        // Go through the object list and scan objects that supports slot-enququing.
209        let scan_later = self.try_enqueue_slots(worker, tls, &trace);
210
211        let total_objects = self.objects.len();
212        let scan_and_trace = scan_later.len();
213        probe!(mmtk, process_nodes, total_objects, scan_and_trace);
214
215        // If any objects do not support slot-enqueuing, we process them now.
216        self.do_node_enqueuing_tracing(worker, tls, trace, scan_later);
217
218        trace!("ScanObjects End");
219    }
220}