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
8/// The kind of a set of roots. Used by LXR to decide how roots should be processed
9/// (e.g. reference counting and remembered-set recording).
10#[repr(u8)]
11#[derive(Debug, Eq, PartialEq, Clone, Copy)]
12pub enum RootKind {
13    /// Ordinary strong roots, e.g. mutator stacks and globals.  These are reference-counted and
14    /// marked like any other strong reference.
15    Strong,
16    /// Roots held by recently JIT-compiled ("young") code-cache entries.  These are recorded into
17    /// the remembered set (instead of being reference-counted) so the code cache can be
18    /// re-scanned on a later GC.
19    YoungCodeCacheRoots,
20    /// Roots that hold weak references.  These must not keep their referents alive and are not
21    /// reference-counted.
22    Weak,
23}
24
25impl RootKind {
26    /// Whether roots of this kind should be recorded into the remembered set rather than being
27    /// processed like normal roots.
28    pub fn should_record_remset(&self) -> bool {
29        matches!(self, RootKind::YoungCodeCacheRoots)
30    }
31
32    /// Whether roots of this kind should skip marking and reference-count decrements.
33    pub fn should_skip_mark_and_decs(&self) -> bool {
34        matches!(self, RootKind::YoungCodeCacheRoots) || matches!(self, RootKind::Weak)
35    }
36
37    /// Whether roots of this kind should skip reference-count decrements.
38    pub fn should_skip_decs(&self) -> bool {
39        matches!(self, RootKind::YoungCodeCacheRoots) || matches!(self, RootKind::Weak)
40    }
41}
42
43pub struct ScheduleCollection;
44
45impl<VM: VMBinding> GCWork<VM> for ScheduleCollection {
46    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
47        // Determine collection kind
48        let is_emergency = mmtk.state.set_collection_kind(
49            mmtk.get_plan().last_collection_was_exhaustive(),
50            mmtk.gc_trigger.policy.can_heap_size_grow(),
51        );
52        if is_emergency {
53            mmtk.get_plan().notify_emergency_collection();
54        }
55        mmtk.state.stacks_prepared.store(false, Ordering::SeqCst);
56        // FIXME: This seems to be a weird place to start counting GC statistics.
57        // This is not when we set the status to PauseRequested or InPause. Instead, this is just a random place during transition
58        // See https://github.com/mmtk/mmtk-core/issues/1330
59        mmtk.stats.start_gc();
60
61        // Let the plan to schedule collection work
62        mmtk.get_plan().schedule_collection(worker.scheduler());
63    }
64}
65
66/// The global GC Preparation Work
67/// This work packet invokes prepare() for the plan (which will invoke prepare() for each space), and
68/// pushes work packets for preparing mutators and collectors.
69/// We should only have one such work packet per GC, before any actual GC work starts.
70/// We assume this work packet is the only running work packet that accesses plan, and there should
71/// be no other concurrent work packet that accesses plan (read or write). Otherwise, there may
72/// be a race condition.
73pub struct Prepare<C: GCWorkContext> {
74    pub plan: *const C::PlanType,
75}
76
77unsafe impl<C: GCWorkContext> Send for Prepare<C> {}
78
79impl<C: GCWorkContext> Prepare<C> {
80    pub fn new(plan: *const C::PlanType) -> Self {
81        Self { plan }
82    }
83}
84
85impl<C: GCWorkContext> GCWork<C::VM> for Prepare<C> {
86    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
87        trace!("Prepare Global");
88        // We assume this is the only running work packet that accesses plan at the point of execution
89        let plan_mut: &mut C::PlanType = unsafe { &mut *(self.plan as *const _ as *mut _) };
90        plan_mut.prepare(worker.tls);
91
92        if plan_mut.constraints().needs_prepare_mutator {
93            let prepare_mutator_packets = <C::VM as VMBinding>::VMActivePlan::mutators()
94                .map(|mutator| Box::new(PrepareMutator::<C::VM>::new(mutator)) as _)
95                .collect::<Vec<_>>();
96            // Just in case the VM binding is inconsistent about the number of mutators and the actual mutator list.
97            debug_assert_eq!(
98                prepare_mutator_packets.len(),
99                <C::VM as VMBinding>::VMActivePlan::number_of_mutators()
100            );
101            mmtk.scheduler.work_buckets[WorkBucketStage::Prepare].bulk_add(prepare_mutator_packets);
102        }
103
104        for w in &mmtk.scheduler.worker_group.workers_shared {
105            let result = w.designated_work.push(Box::new(PrepareCollector));
106            debug_assert!(result.is_ok());
107        }
108    }
109}
110
111/// The mutator GC Preparation Work
112pub struct PrepareMutator<VM: VMBinding> {
113    // The mutator reference has static lifetime.
114    // It is safe because the actual lifetime of this work-packet will not exceed the lifetime of a GC.
115    pub mutator: &'static mut Mutator<VM>,
116}
117
118impl<VM: VMBinding> PrepareMutator<VM> {
119    pub fn new(mutator: &'static mut Mutator<VM>) -> Self {
120        Self { mutator }
121    }
122}
123
124impl<VM: VMBinding> GCWork<VM> for PrepareMutator<VM> {
125    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
126        trace!("Prepare Mutator");
127        self.mutator.prepare(worker.tls);
128    }
129}
130
131/// The collector GC Preparation Work
132#[derive(Default)]
133pub struct PrepareCollector;
134
135impl<VM: VMBinding> GCWork<VM> for PrepareCollector {
136    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
137        trace!("Prepare Collector");
138        worker.get_copy_context_mut().prepare();
139        mmtk.get_plan().prepare_worker(worker);
140    }
141}
142
143/// The global GC release Work
144/// This work packet invokes release() for the plan (which will invoke release() for each space), and
145/// pushes work packets for releasing mutators and collectors.
146/// We should only have one such work packet per GC, after all actual GC work ends.
147/// We assume this work packet is the only running work packet that accesses plan, and there should
148/// be no other concurrent work packet that accesses plan (read or write). Otherwise, there may
149/// be a race condition.
150pub struct Release<C: GCWorkContext> {
151    pub plan: *const C::PlanType,
152}
153
154impl<C: GCWorkContext> Release<C> {
155    pub fn new(plan: *const C::PlanType) -> Self {
156        Self { plan }
157    }
158}
159
160unsafe impl<C: GCWorkContext> Send for Release<C> {}
161
162impl<C: GCWorkContext + 'static> GCWork<C::VM> for Release<C> {
163    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
164        trace!("Release Global");
165
166        mmtk.gc_trigger.policy.on_gc_release(mmtk);
167        // We assume this is the only running work packet that accesses plan at the point of execution
168
169        let plan_mut: &mut C::PlanType = unsafe { &mut *(self.plan as *const _ as *mut _) };
170        plan_mut.release(worker.tls);
171
172        let release_mutator_packets = <C::VM as VMBinding>::VMActivePlan::mutators()
173            .map(|mutator| Box::new(ReleaseMutator::<C::VM>::new(mutator)) as _)
174            .collect::<Vec<_>>();
175        // Just in case the VM binding is inconsistent about the number of mutators and the actual mutator list.
176        debug_assert_eq!(
177            release_mutator_packets.len(),
178            <C::VM as VMBinding>::VMActivePlan::number_of_mutators()
179        );
180        mmtk.scheduler.work_buckets[WorkBucketStage::Release].bulk_add(release_mutator_packets);
181
182        for w in &mmtk.scheduler.worker_group.workers_shared {
183            let result = w.designated_work.push(Box::new(ReleaseCollector));
184            debug_assert!(result.is_ok());
185        }
186    }
187}
188
189/// The mutator release Work
190pub struct ReleaseMutator<VM: VMBinding> {
191    // The mutator reference has static lifetime.
192    // It is safe because the actual lifetime of this work-packet will not exceed the lifetime of a GC.
193    pub mutator: &'static mut Mutator<VM>,
194}
195
196impl<VM: VMBinding> ReleaseMutator<VM> {
197    pub fn new(mutator: &'static mut Mutator<VM>) -> Self {
198        Self { mutator }
199    }
200}
201
202impl<VM: VMBinding> GCWork<VM> for ReleaseMutator<VM> {
203    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
204        trace!("Release Mutator");
205        self.mutator.release(worker.tls);
206    }
207}
208
209/// The collector release Work
210#[derive(Default)]
211pub struct ReleaseCollector;
212
213impl<VM: VMBinding> GCWork<VM> for ReleaseCollector {
214    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
215        trace!("Release Collector");
216        worker.get_copy_context_mut().release();
217    }
218}
219
220/// Stop all mutators
221///
222/// TODO: Smaller work granularity
223#[derive(Default)]
224pub struct StopMutators<C: GCWorkContext> {
225    /// If this is true, we skip creating [`ScanMutatorRoots`] work packets for mutators.
226    /// By default, this is false.
227    skip_mutator_roots: bool,
228    /// If this is true, we skip scanning VM-specific roots.
229    /// By default, this is false.
230    skip_vm_roots: bool,
231    /// Flush mutators once they are stopped. By default this is false. [`ScanMutatorRoots`] will flush mutators.
232    flush_mutator: bool,
233    phantom: PhantomData<C>,
234}
235
236impl<C: GCWorkContext> StopMutators<C> {
237    pub fn new() -> Self {
238        Self {
239            skip_mutator_roots: false,
240            skip_vm_roots: false,
241            flush_mutator: false,
242            phantom: PhantomData,
243        }
244    }
245
246    pub fn new_with_flush() -> Self {
247        let mut me = Self::new();
248        me.flush_mutator = true;
249        me
250    }
251
252    /// Create a `StopMutators` work packet that does not create any root-scanning work packets, and will simply flush mutators.
253    pub fn new_no_scan_roots() -> Self {
254        Self {
255            skip_mutator_roots: true,
256            skip_vm_roots: true,
257            flush_mutator: true,
258            phantom: PhantomData,
259        }
260    }
261}
262
263impl<C: GCWorkContext> GCWork<C::VM> for StopMutators<C> {
264    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
265        trace!("stop_all_mutators start");
266        mmtk.state.prepare_for_stack_scanning();
267        <C::VM as VMBinding>::VMCollection::stop_all_mutators(worker.tls, |mutator| {
268            // 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).
269            // Should we push to Unconstrained instead?
270
271            if self.flush_mutator {
272                mutator.flush();
273            }
274            if !self.skip_mutator_roots {
275                mmtk.scheduler.work_buckets[mmtk.get_plan().root_scanning_stage()]
276                    .add(ScanMutatorRoots::<C>(mutator));
277            }
278        });
279        trace!("stop_all_mutators end");
280        // All mutators have just stopped: this is the end of the "time-to-yield" window
281        mmtk.stats
282            .record_time_to_yield(mmtk.state.take_time_to_yield());
283        mmtk.state.record_pause_start_time();
284        // This also tells the GC trigger whether a new GC cycle has started (see `Plan::gc_pause_start`).
285        mmtk.get_plan().on_pause_start(mmtk);
286        mmtk.scheduler.notify_mutators_paused(mmtk);
287        // Tell GC trigger that the pause started.
288        mmtk.gc_trigger.policy.on_pause_start(mmtk);
289        if !self.skip_vm_roots {
290            let factory = C::make_roots_work_factory(mmtk);
291            <C::VM as VMBinding>::VMScanning::scan_vm_specific_roots(worker.tls, factory);
292        }
293    }
294}
295
296pub struct ScanMutatorRoots<C: GCWorkContext>(pub &'static mut Mutator<C::VM>);
297
298impl<C: GCWorkContext> GCWork<C::VM> for ScanMutatorRoots<C> {
299    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
300        trace!("ScanMutatorRoots for mutator {:?}", self.0.get_tls());
301        let mutators = <C::VM as VMBinding>::VMActivePlan::number_of_mutators();
302        let factory = C::make_roots_work_factory(mmtk);
303        <C::VM as VMBinding>::VMScanning::scan_roots_in_mutator_thread(
304            worker.tls,
305            unsafe { &mut *(self.0 as *mut _) },
306            factory,
307        );
308        self.0.flush();
309
310        if mmtk.state.inform_stack_scanned(mutators) {
311            <C::VM as VMBinding>::VMScanning::notify_initial_thread_scan_complete(
312                false, worker.tls,
313            );
314        }
315    }
316}
317
318#[derive(Default)]
319pub struct ScanVMSpecificRoots<C: GCWorkContext>(PhantomData<C>);
320
321impl<C: GCWorkContext> ScanVMSpecificRoots<C> {
322    pub fn new() -> Self {
323        Self(PhantomData)
324    }
325}
326
327impl<C: GCWorkContext> GCWork<C::VM> for ScanVMSpecificRoots<C> {
328    fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) {
329        trace!("ScanStaticRoots");
330        let factory = C::make_roots_work_factory(mmtk);
331        <C::VM as VMBinding>::VMScanning::scan_vm_specific_roots(worker.tls, factory);
332    }
333}