mmtk/scheduler/
gc_work.rs

1use super::work_bucket::WorkBucketStage;
2use super::*;
3use crate::vm::*;
4use crate::*;
5use std::marker::PhantomData;
6use std::sync::atomic::Ordering;
7
8pub struct ScheduleCollection;
9
10impl<VM: VMBinding> GCWork<VM> for ScheduleCollection {
11    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
12        // Tell GC trigger that GC started.
13        mmtk.gc_trigger.policy.on_gc_start(mmtk);
14
15        // Determine collection kind
16        let is_emergency = mmtk.state.set_collection_kind(
17            mmtk.get_plan().last_collection_was_exhaustive(),
18            mmtk.gc_trigger.policy.can_heap_size_grow(),
19        );
20        if is_emergency {
21            mmtk.get_plan().notify_emergency_collection();
22        }
23        mmtk.state.stacks_prepared.store(false, Ordering::SeqCst);
24        // FIXME: This seems to be a weird place to start counting GC statistics.
25        // This is not when we set the status to PauseRequested or InPause. Instead, this is just a random place during transition
26        // See https://github.com/mmtk/mmtk-core/issues/1330
27        mmtk.stats.start_gc();
28
29        // Let the plan to schedule collection work
30        mmtk.get_plan().schedule_collection(worker.scheduler());
31    }
32}
33
34/// The global GC Preparation Work
35/// This work packet invokes prepare() for the plan (which will invoke prepare() for each space), and
36/// pushes work packets for preparing mutators and collectors.
37/// We should only have one such work packet per GC, before any actual GC work starts.
38/// We assume this work packet is the only running work packet that accesses plan, and there should
39/// be no other concurrent work packet that accesses plan (read or write). Otherwise, there may
40/// be a race condition.
41pub struct Prepare<C: GCWorkContext> {
42    pub plan: *const C::PlanType,
43}
44
45unsafe impl<C: GCWorkContext> Send for Prepare<C> {}
46
47impl<C: GCWorkContext> Prepare<C> {
48    pub fn new(plan: *const C::PlanType) -> Self {
49        Self { plan }
50    }
51}
52
53impl<C: GCWorkContext> GCWork<C::VM> for Prepare<C> {
54    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
55        trace!("Prepare Global");
56        // We assume this is the only running work packet that accesses plan at the point of execution
57        let plan_mut: &mut C::PlanType = unsafe { &mut *(self.plan as *const _ as *mut _) };
58        plan_mut.prepare(worker.tls);
59
60        if plan_mut.constraints().needs_prepare_mutator {
61            let prepare_mutator_packets = <C::VM as VMBinding>::VMActivePlan::mutators()
62                .map(|mutator| Box::new(PrepareMutator::<C::VM>::new(mutator)) as _)
63                .collect::<Vec<_>>();
64            // Just in case the VM binding is inconsistent about the number of mutators and the actual mutator list.
65            debug_assert_eq!(
66                prepare_mutator_packets.len(),
67                <C::VM as VMBinding>::VMActivePlan::number_of_mutators()
68            );
69            mmtk.scheduler.work_buckets[WorkBucketStage::Prepare].bulk_add(prepare_mutator_packets);
70        }
71
72        for w in &mmtk.scheduler.worker_group.workers_shared {
73            let result = w.designated_work.push(Box::new(PrepareCollector));
74            debug_assert!(result.is_ok());
75        }
76    }
77}
78
79/// The mutator GC Preparation Work
80pub struct PrepareMutator<VM: VMBinding> {
81    // The mutator reference has static lifetime.
82    // It is safe because the actual lifetime of this work-packet will not exceed the lifetime of a GC.
83    pub mutator: &'static mut Mutator<VM>,
84}
85
86impl<VM: VMBinding> PrepareMutator<VM> {
87    pub fn new(mutator: &'static mut Mutator<VM>) -> Self {
88        Self { mutator }
89    }
90}
91
92impl<VM: VMBinding> GCWork<VM> for PrepareMutator<VM> {
93    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
94        trace!("Prepare Mutator");
95        self.mutator.prepare(worker.tls);
96    }
97}
98
99/// The collector GC Preparation Work
100#[derive(Default)]
101pub struct PrepareCollector;
102
103impl<VM: VMBinding> GCWork<VM> for PrepareCollector {
104    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
105        trace!("Prepare Collector");
106        worker.get_copy_context_mut().prepare();
107        mmtk.get_plan().prepare_worker(worker);
108    }
109}
110
111/// The global GC release Work
112/// This work packet invokes release() for the plan (which will invoke release() for each space), and
113/// pushes work packets for releasing mutators and collectors.
114/// We should only have one such work packet per GC, after all actual GC work ends.
115/// We assume this work packet is the only running work packet that accesses plan, and there should
116/// be no other concurrent work packet that accesses plan (read or write). Otherwise, there may
117/// be a race condition.
118pub struct Release<C: GCWorkContext> {
119    pub plan: *const C::PlanType,
120}
121
122impl<C: GCWorkContext> Release<C> {
123    pub fn new(plan: *const C::PlanType) -> Self {
124        Self { plan }
125    }
126}
127
128unsafe impl<C: GCWorkContext> Send for Release<C> {}
129
130impl<C: GCWorkContext + 'static> GCWork<C::VM> for Release<C> {
131    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
132        trace!("Release Global");
133
134        mmtk.gc_trigger.policy.on_gc_release(mmtk);
135        // We assume this is the only running work packet that accesses plan at the point of execution
136
137        let plan_mut: &mut C::PlanType = unsafe { &mut *(self.plan as *const _ as *mut _) };
138        plan_mut.release(worker.tls);
139
140        let release_mutator_packets = <C::VM as VMBinding>::VMActivePlan::mutators()
141            .map(|mutator| Box::new(ReleaseMutator::<C::VM>::new(mutator)) as _)
142            .collect::<Vec<_>>();
143        // Just in case the VM binding is inconsistent about the number of mutators and the actual mutator list.
144        debug_assert_eq!(
145            release_mutator_packets.len(),
146            <C::VM as VMBinding>::VMActivePlan::number_of_mutators()
147        );
148        mmtk.scheduler.work_buckets[WorkBucketStage::Release].bulk_add(release_mutator_packets);
149
150        for w in &mmtk.scheduler.worker_group.workers_shared {
151            let result = w.designated_work.push(Box::new(ReleaseCollector));
152            debug_assert!(result.is_ok());
153        }
154    }
155}
156
157/// The mutator release Work
158pub struct ReleaseMutator<VM: VMBinding> {
159    // The mutator reference has static lifetime.
160    // It is safe because the actual lifetime of this work-packet will not exceed the lifetime of a GC.
161    pub mutator: &'static mut Mutator<VM>,
162}
163
164impl<VM: VMBinding> ReleaseMutator<VM> {
165    pub fn new(mutator: &'static mut Mutator<VM>) -> Self {
166        Self { mutator }
167    }
168}
169
170impl<VM: VMBinding> GCWork<VM> for ReleaseMutator<VM> {
171    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
172        trace!("Release Mutator");
173        self.mutator.release(worker.tls);
174    }
175}
176
177/// The collector release Work
178#[derive(Default)]
179pub struct ReleaseCollector;
180
181impl<VM: VMBinding> GCWork<VM> for ReleaseCollector {
182    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
183        trace!("Release Collector");
184        worker.get_copy_context_mut().release();
185    }
186}
187
188/// Stop all mutators
189///
190/// TODO: Smaller work granularity
191#[derive(Default)]
192pub struct StopMutators<C: GCWorkContext> {
193    /// If this is true, we skip creating root-scanning work packets.
194    /// By default, this is false.
195    skip_roots: bool,
196    /// Flush mutators once they are stopped. By default this is false. [`ScanMutatorRoots`] will flush mutators.
197    flush_mutator: bool,
198    phantom: PhantomData<C>,
199}
200
201impl<C: GCWorkContext> StopMutators<C> {
202    pub fn new() -> Self {
203        Self {
204            skip_roots: false,
205            flush_mutator: false,
206            phantom: PhantomData,
207        }
208    }
209
210    /// Create a `StopMutators` work packet that does not create any root-scanning work packets, and will simply flush mutators.
211    pub fn new_no_scan_roots() -> Self {
212        Self {
213            skip_roots: true,
214            flush_mutator: true,
215            phantom: PhantomData,
216        }
217    }
218}
219
220impl<C: GCWorkContext> GCWork<C::VM> for StopMutators<C> {
221    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
222        trace!("stop_all_mutators start");
223        mmtk.state.prepare_for_stack_scanning();
224        <C::VM as VMBinding>::VMCollection::stop_all_mutators(worker.tls, |mutator| {
225            // TODO: The stack scanning work won't start immediately, as the `Prepare` bucket is not opened yet (the bucket is opened in notify_mutators_paused).
226            // Should we push to Unconstrained instead?
227
228            if self.flush_mutator {
229                mutator.flush();
230            }
231            if !self.skip_roots {
232                mmtk.scheduler.work_buckets[WorkBucketStage::Prepare]
233                    .add(ScanMutatorRoots::<C>(mutator));
234            }
235        });
236        trace!("stop_all_mutators end");
237        mmtk.get_plan().notify_mutators_paused(&mmtk.scheduler);
238        mmtk.scheduler.notify_mutators_paused(mmtk);
239        if !self.skip_roots {
240            mmtk.scheduler.work_buckets[WorkBucketStage::Prepare]
241                .add(ScanVMSpecificRoots::<C>::new());
242        }
243    }
244}
245
246pub struct ScanMutatorRoots<C: GCWorkContext>(pub &'static mut Mutator<C::VM>);
247
248impl<C: GCWorkContext> GCWork<C::VM> for ScanMutatorRoots<C> {
249    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
250        trace!("ScanMutatorRoots for mutator {:?}", self.0.get_tls());
251        let mutators = <C::VM as VMBinding>::VMActivePlan::number_of_mutators();
252        let factory = C::make_roots_work_factory(mmtk);
253        <C::VM as VMBinding>::VMScanning::scan_roots_in_mutator_thread(
254            worker.tls,
255            unsafe { &mut *(self.0 as *mut _) },
256            factory,
257        );
258        self.0.flush();
259
260        if mmtk.state.inform_stack_scanned(mutators) {
261            <C::VM as VMBinding>::VMScanning::notify_initial_thread_scan_complete(
262                false, worker.tls,
263            );
264        }
265    }
266}
267
268#[derive(Default)]
269pub struct ScanVMSpecificRoots<C: GCWorkContext>(PhantomData<C>);
270
271impl<C: GCWorkContext> ScanVMSpecificRoots<C> {
272    pub fn new() -> Self {
273        Self(PhantomData)
274    }
275}
276
277impl<C: GCWorkContext> GCWork<C::VM> for ScanVMSpecificRoots<C> {
278    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
279        trace!("ScanStaticRoots");
280        let factory = C::make_roots_work_factory(mmtk);
281        <C::VM as VMBinding>::VMScanning::scan_vm_specific_roots(worker.tls, factory);
282    }
283}