mmtk/plan/concurrent/immix/
global.rs

1use crate::plan::concurrent::concurrent_marking_work::ProcessRootSlots;
2use crate::plan::concurrent::global::ConcurrentPlan;
3use crate::plan::concurrent::immix::gc_work::ConcurrentImmixGCWorkContext;
4use crate::plan::concurrent::immix::gc_work::ConcurrentImmixSTWGCWorkContext;
5use crate::plan::concurrent::Pause;
6use crate::plan::global::BasePlan;
7use crate::plan::global::CommonPlan;
8use crate::plan::global::CreateGeneralPlanArgs;
9use crate::plan::global::CreateSpecificPlanArgs;
10use crate::plan::immix::mutator::ALLOCATOR_MAPPING;
11use crate::plan::AllocationSemantics;
12use crate::plan::Plan;
13use crate::plan::PlanConstraints;
14use crate::policy::immix::defrag::StatsForDefrag;
15use crate::policy::immix::ImmixSpaceArgs;
16use crate::policy::immix::TRACE_KIND_DEFRAG;
17use crate::policy::immix::TRACE_KIND_FAST;
18use crate::policy::space::Space;
19use crate::scheduler::gc_work::Release;
20use crate::scheduler::gc_work::StopMutators;
21use crate::scheduler::gc_work::UnsupportedProcessEdges;
22use crate::scheduler::gc_work::VMProcessWeakRefs;
23use crate::scheduler::*;
24use crate::util::alloc::allocators::AllocatorSelector;
25use crate::util::copy::*;
26use crate::util::heap::gc_trigger::SpaceStats;
27use crate::util::heap::VMRequest;
28use crate::util::metadata::log_bit::UnlogBitsOperation;
29use crate::util::metadata::side_metadata::SideMetadataContext;
30use crate::vm::ObjectModel;
31use crate::vm::VMBinding;
32use crate::{policy::immix::ImmixSpace, util::opaque_pointer::VMWorkerThread};
33use std::sync::atomic::AtomicBool;
34
35use atomic::Atomic;
36use atomic::Ordering;
37use enum_map::EnumMap;
38
39use mmtk_macros::{HasSpaces, PlanTraceObject};
40
41/// A concurrent Immix plan. The plan supports concurrent collection (strictly non-moving) and STW full heap collection (which may do defrag).
42/// The concurrent GC consists of two STW pauses (initial mark and final mark) with concurrent marking in between.
43#[derive(HasSpaces, PlanTraceObject)]
44pub struct ConcurrentImmix<VM: VMBinding> {
45    #[post_scan]
46    #[space]
47    #[copy_semantics(CopySemantics::DefaultCopy)]
48    pub immix_space: ImmixSpace<VM>,
49    #[parent]
50    pub common: CommonPlan<VM>,
51    last_gc_was_defrag: AtomicBool,
52    current_pause: Atomic<Option<Pause>>,
53    previous_pause: Atomic<Option<Pause>>,
54    should_do_full_gc: AtomicBool,
55    concurrent_marking_active: AtomicBool,
56}
57
58/// The plan constraints for the concurrent immix plan.
59pub const CONCURRENT_IMMIX_CONSTRAINTS: PlanConstraints = PlanConstraints {
60    // If we disable moving in Immix, this is a non-moving plan.
61    moves_objects: !cfg!(feature = "immix_non_moving"),
62    // Max immix object size is half of a block.
63    max_non_los_default_alloc_bytes: crate::policy::immix::MAX_IMMIX_OBJECT_SIZE,
64    needs_prepare_mutator: true,
65    barrier: crate::BarrierSelector::SATBBarrier,
66    needs_log_bit: true,
67    ..PlanConstraints::default()
68};
69
70impl<VM: VMBinding> Plan for ConcurrentImmix<VM> {
71    fn collection_required(&self, space_full: bool, _space: Option<SpaceStats<Self::VM>>) -> bool {
72        if self.base().collection_required(self, space_full) {
73            self.should_do_full_gc.store(true, Ordering::Release);
74            info!("Triggering full GC");
75            return true;
76        }
77
78        // Check stw for final mark
79        let concurrent_marking_in_progress = self.concurrent_marking_in_progress();
80        if concurrent_marking_in_progress
81            && self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].is_drained()
82        {
83            // After the Concurrent bucket is drained during concurrent marking,
84            // we trigger the FinalMark pause at the next poll() site (here).
85            // FIXME: Immediately trigger FinalMark when the Concurrent bucket is drained.
86            return true;
87        }
88
89        // Check stw for initial mark
90
91        // If concurrent marking is disbled, no need to check further.
92        if self.concurrent_marking_is_disabled() {
93            return false;
94        }
95
96        let threshold = self.get_total_pages() >> 1;
97        let used_pages_after_last_gc = self.common.base.global_state.get_used_pages_after_last_gc();
98        let used_pages_now = self.get_used_pages();
99        let allocated = used_pages_now.saturating_sub(used_pages_after_last_gc);
100        if !concurrent_marking_in_progress && allocated > threshold {
101            info!("Allocated {allocated} pages since last GC ({used_pages_now} - {used_pages_after_last_gc} > {threshold}): Do concurrent marking");
102            debug_assert!(
103                self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].is_empty()
104            );
105            debug_assert!(!self.concurrent_marking_in_progress());
106            debug_assert_ne!(self.previous_pause(), Some(Pause::InitialMark));
107            return true;
108        }
109
110        false
111    }
112
113    fn last_collection_was_exhaustive(&self) -> bool {
114        self.immix_space
115            .is_last_gc_exhaustive(self.last_gc_was_defrag.load(Ordering::Relaxed))
116    }
117
118    fn constraints(&self) -> &'static PlanConstraints {
119        &CONCURRENT_IMMIX_CONSTRAINTS
120    }
121
122    fn create_copy_config(&'static self) -> CopyConfig<Self::VM> {
123        use enum_map::enum_map;
124        CopyConfig {
125            copy_mapping: enum_map! {
126                CopySemantics::DefaultCopy => CopySelector::Immix(0),
127                _ => CopySelector::Unused,
128            },
129            space_mapping: vec![(CopySelector::Immix(0), &self.immix_space)],
130            constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
131        }
132    }
133
134    fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
135        let pause = if self.concurrent_marking_in_progress() {
136            // FIXME: Currently it is unsafe to bypass `FinalMark` and go directly from `InitialMark` to `Full`.
137            // It is related to defragmentation.  See https://github.com/mmtk/mmtk-core/issues/1357 for more details.
138            // We currently force `FinalMark` to happen if the last pause is `InitialMark`.
139            Pause::FinalMark
140        } else if self.should_do_full_gc.load(Ordering::SeqCst) {
141            Pause::Full
142        } else {
143            Pause::InitialMark
144        };
145
146        self.current_pause.store(Some(pause), Ordering::SeqCst);
147
148        probe!(mmtk, concurrent_pause_determined, pause as usize);
149
150        match pause {
151            Pause::Full => {
152                // Ref closure buckets is disabled by initial mark, and needs to be re-enabled for full GC before
153                // we reuse the normal Immix scheduling.
154                self.set_ref_closure_buckets_enabled(true);
155                crate::plan::immix::global::Immix::schedule_immix_full_heap_collection::<
156                    ConcurrentImmix<VM>,
157                    ConcurrentImmixSTWGCWorkContext<VM, TRACE_KIND_FAST>,
158                    ConcurrentImmixSTWGCWorkContext<VM, TRACE_KIND_DEFRAG>,
159                >(self, &self.immix_space, scheduler);
160            }
161            Pause::InitialMark => self.schedule_concurrent_marking_initial_pause(scheduler),
162            Pause::FinalMark => self.schedule_concurrent_marking_final_pause(scheduler),
163        }
164    }
165
166    fn get_allocator_mapping(&self) -> &'static EnumMap<AllocationSemantics, AllocatorSelector> {
167        &ALLOCATOR_MAPPING
168    }
169
170    fn prepare(&mut self, tls: VMWorkerThread) {
171        let pause = self.current_pause().unwrap();
172        match pause {
173            Pause::Full => {
174                self.common.prepare(tls, true);
175                self.immix_space.prepare(
176                    true,
177                    Some(StatsForDefrag::new(self)),
178                    // Ignore unlog bits in full GCs because unlog bits should be all 0.
179                    UnlogBitsOperation::NoOp,
180                );
181            }
182            Pause::InitialMark => {
183                self.immix_space.prepare(
184                    true,
185                    Some(StatsForDefrag::new(self)),
186                    // Bulk set log bits so SATB barrier will be triggered on the existing objects.
187                    UnlogBitsOperation::BulkSet,
188                );
189
190                self.common.prepare(tls, true);
191                // Bulk set log bits so SATB barrier will be triggered on the existing objects.
192                self.common
193                    .schedule_unlog_bits_op(UnlogBitsOperation::BulkSet);
194            }
195            Pause::FinalMark => (),
196        }
197    }
198
199    fn release(&mut self, tls: VMWorkerThread) {
200        let pause = self.current_pause().unwrap();
201        match pause {
202            Pause::InitialMark => (),
203            Pause::Full | Pause::FinalMark => {
204                self.immix_space.release(
205                    true,
206                    // Bulk clear log bits so SATB barrier will not be triggered.
207                    UnlogBitsOperation::BulkClear,
208                );
209
210                self.common.release(tls, true);
211
212                if pause == Pause::FinalMark {
213                    // Bulk clear log bits so SATB barrier will not be triggered.
214                    self.common
215                        .schedule_unlog_bits_op(UnlogBitsOperation::BulkClear);
216                } else {
217                    // Full pauses didn't set unlog bits in the first place,
218                    // so there is no need to clear them.
219                    // TODO: Currently InitialMark must be followed by a FinalMark.
220                    // If we allow upgrading a concurrent GC to a full STW GC,
221                    // we will need to clear the unlog bits at an appropriate place.
222                }
223            }
224        }
225    }
226
227    fn end_of_gc(&mut self, _tls: VMWorkerThread) {
228        self.last_gc_was_defrag
229            .store(self.immix_space.end_of_gc(), Ordering::Relaxed);
230
231        let pause = self.current_pause().unwrap();
232        if pause == Pause::InitialMark {
233            self.set_concurrent_marking_state(true);
234        }
235        self.previous_pause.store(Some(pause), Ordering::SeqCst);
236        self.current_pause.store(None, Ordering::SeqCst);
237        if pause != Pause::FinalMark {
238            self.should_do_full_gc.store(false, Ordering::SeqCst);
239        } else {
240            // FIXME: Currently it is unsafe to trigger full GC during concurrent marking.
241            // See `Self::schedule_collection`.
242            // We keep the value of `self.should_do_full_gc` so that if full GC is triggered,
243            // the next GC will be full GC.
244        }
245        info!("{:?} end", pause);
246    }
247
248    fn current_gc_may_move_object(&self) -> bool {
249        self.immix_space.in_defrag()
250    }
251
252    fn get_collection_reserved_pages(&self) -> usize {
253        self.immix_space.defrag_headroom_pages()
254    }
255
256    fn get_used_pages(&self) -> usize {
257        self.immix_space.reserved_pages() + self.common.get_used_pages()
258    }
259
260    fn base(&self) -> &BasePlan<VM> {
261        &self.common.base
262    }
263
264    fn base_mut(&mut self) -> &mut BasePlan<Self::VM> {
265        &mut self.common.base
266    }
267
268    fn common(&self) -> &CommonPlan<VM> {
269        &self.common
270    }
271
272    fn notify_mutators_paused(&self, _scheduler: &GCWorkScheduler<VM>) {
273        use crate::vm::ActivePlan;
274        let pause = self.current_pause().unwrap();
275        match pause {
276            Pause::Full => {
277                self.set_concurrent_marking_state(false);
278            }
279            Pause::InitialMark => {
280                debug_assert!(
281                    !self.concurrent_marking_in_progress(),
282                    "prev pause: {:?}",
283                    self.previous_pause().unwrap()
284                );
285            }
286            Pause::FinalMark => {
287                debug_assert!(self.concurrent_marking_in_progress());
288                // Flush barrier buffers
289                for mutator in <VM as VMBinding>::VMActivePlan::mutators() {
290                    mutator.barrier.flush();
291                }
292                self.set_concurrent_marking_state(false);
293            }
294        }
295        info!("{:?} start", pause);
296    }
297
298    fn concurrent(&self) -> Option<&dyn ConcurrentPlan<VM = VM>> {
299        Some(self)
300    }
301}
302
303impl<VM: VMBinding> ConcurrentImmix<VM> {
304    pub fn new(args: CreateGeneralPlanArgs<VM>) -> Self {
305        if *args.options.concurrent_immix_disable_concurrent_marking {
306            warn!("Option 'concurrent_immix_disable_concurrent_marking' is set to true. Concurrent marking is disabled for ConcurrentImmix. This will make ConcurrentImmix behave exactly like full heap Immix.");
307        }
308
309        let spec = crate::util::metadata::extract_side_metadata(&[
310            *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC,
311        ]);
312
313        let mut plan_args = CreateSpecificPlanArgs {
314            global_args: args,
315            constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
316            global_side_metadata_specs: SideMetadataContext::new_global_specs(&spec),
317        };
318
319        let immix_args = ImmixSpaceArgs {
320            mixed_age: false,
321            never_move_objects: false,
322        };
323
324        // These buckets are not used in an Immix plan. We can simply disable them.
325        // TODO: We should be more systmatic on this, and disable unnecessary buckets for other plans as well.
326        let scheduler = &plan_args.global_args.scheduler;
327        scheduler.work_buckets[WorkBucketStage::VMRefForwarding].set_enabled(false);
328        scheduler.work_buckets[WorkBucketStage::CalculateForwarding].set_enabled(false);
329        scheduler.work_buckets[WorkBucketStage::SecondRoots].set_enabled(false);
330        scheduler.work_buckets[WorkBucketStage::RefForwarding].set_enabled(false);
331        scheduler.work_buckets[WorkBucketStage::FinalizableForwarding].set_enabled(false);
332        scheduler.work_buckets[WorkBucketStage::Compact].set_enabled(false);
333
334        let immix = ConcurrentImmix {
335            immix_space: ImmixSpace::new(
336                plan_args.get_normal_space_args("immix", true, false, VMRequest::discontiguous()),
337                immix_args,
338            ),
339            common: CommonPlan::new(plan_args),
340            last_gc_was_defrag: AtomicBool::new(false),
341            current_pause: Atomic::new(None),
342            previous_pause: Atomic::new(None),
343            should_do_full_gc: AtomicBool::new(false),
344            concurrent_marking_active: AtomicBool::new(false),
345        };
346
347        immix.verify_side_metadata_sanity();
348
349        immix
350    }
351
352    fn set_ref_closure_buckets_enabled(&self, do_closure: bool) {
353        let scheduler = &self.common.base.scheduler;
354        scheduler.work_buckets[WorkBucketStage::VMRefClosure].set_enabled(do_closure);
355        scheduler.work_buckets[WorkBucketStage::WeakRefClosure].set_enabled(do_closure);
356        scheduler.work_buckets[WorkBucketStage::FinalRefClosure].set_enabled(do_closure);
357        scheduler.work_buckets[WorkBucketStage::SoftRefClosure].set_enabled(do_closure);
358        scheduler.work_buckets[WorkBucketStage::PhantomRefClosure].set_enabled(do_closure);
359    }
360
361    pub(crate) fn schedule_concurrent_marking_initial_pause(
362        &'static self,
363        scheduler: &GCWorkScheduler<VM>,
364    ) {
365        use crate::scheduler::gc_work::Prepare;
366
367        self.set_ref_closure_buckets_enabled(false);
368
369        scheduler.work_buckets[WorkBucketStage::Unconstrained].add(StopMutators::<
370            ConcurrentImmixGCWorkContext<ProcessRootSlots<VM, Self, TRACE_KIND_FAST>>,
371        >::new());
372        scheduler.work_buckets[WorkBucketStage::Prepare].add(Prepare::<
373            ConcurrentImmixGCWorkContext<UnsupportedProcessEdges<VM>>,
374        >::new(self));
375    }
376
377    fn schedule_concurrent_marking_final_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
378        self.set_ref_closure_buckets_enabled(true);
379
380        // Skip root scanning in the final mark
381        scheduler.work_buckets[WorkBucketStage::Unconstrained].add(StopMutators::<
382            ConcurrentImmixGCWorkContext<ProcessRootSlots<VM, Self, TRACE_KIND_FAST>>,
383        >::new_no_scan_roots());
384
385        scheduler.work_buckets[WorkBucketStage::Release].add(Release::<
386            ConcurrentImmixGCWorkContext<UnsupportedProcessEdges<VM>>,
387        >::new(self));
388
389        // Deal with weak ref and finalizers
390        // TODO: Check against schedule_common_work and see if we are still missing any work packet
391        type RefProcessingEdges<VM> =
392            crate::scheduler::gc_work::PlanProcessEdges<VM, ConcurrentImmix<VM>, TRACE_KIND_FAST>;
393        // Reference processing
394        if !*self.base().options.no_reference_types {
395            use crate::util::reference_processor::{
396                PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing,
397            };
398            scheduler.work_buckets[WorkBucketStage::SoftRefClosure]
399                .add(SoftRefProcessing::<RefProcessingEdges<VM>>::new());
400            scheduler.work_buckets[WorkBucketStage::WeakRefClosure]
401                .add(WeakRefProcessing::<VM>::new());
402            scheduler.work_buckets[WorkBucketStage::PhantomRefClosure]
403                .add(PhantomRefProcessing::<VM>::new());
404
405            use crate::util::reference_processor::RefEnqueue;
406            scheduler.work_buckets[WorkBucketStage::Release].add(RefEnqueue::<VM>::new());
407        }
408
409        // Finalization
410        if !*self.base().options.no_finalizer {
411            use crate::util::finalizable_processor::Finalization;
412            // finalization
413            scheduler.work_buckets[WorkBucketStage::FinalRefClosure]
414                .add(Finalization::<RefProcessingEdges<VM>>::new());
415        }
416
417        // VM-specific weak ref processing
418        // Note that ConcurrentImmix does not have a separate forwarding stage,
419        // so we don't schedule the `VMForwardWeakRefs` work packet.
420        scheduler.work_buckets[WorkBucketStage::VMRefClosure]
421            .set_sentinel(Box::new(VMProcessWeakRefs::<RefProcessingEdges<VM>>::new()));
422    }
423
424    pub fn concurrent_marking_in_progress(&self) -> bool {
425        self.concurrent_marking_active.load(Ordering::Acquire)
426    }
427
428    fn set_concurrent_marking_state(&self, active: bool) {
429        use crate::plan::global::HasSpaces;
430
431        // Tell the spaces to allocate new objects as live
432        let allocate_object_as_live = active;
433        self.for_each_space(&mut |space: &dyn Space<VM>| {
434            space.set_allocate_as_live(allocate_object_as_live);
435        });
436
437        // Store the state.
438        self.concurrent_marking_active
439            .store(active, Ordering::SeqCst);
440
441        // We also set SATB barrier as active -- this is done in Mutator prepare/release.
442    }
443
444    pub(super) fn is_concurrent_marking_active(&self) -> bool {
445        self.concurrent_marking_active.load(Ordering::SeqCst)
446    }
447
448    fn previous_pause(&self) -> Option<Pause> {
449        self.previous_pause.load(Ordering::SeqCst)
450    }
451
452    fn concurrent_marking_is_disabled(&self) -> bool {
453        *self
454            .base()
455            .options
456            .concurrent_immix_disable_concurrent_marking
457    }
458}
459
460impl<VM: VMBinding> ConcurrentPlan for ConcurrentImmix<VM> {
461    fn current_pause(&self) -> Option<Pause> {
462        self.current_pause.load(Ordering::SeqCst)
463    }
464
465    fn concurrent_work_in_progress(&self) -> bool {
466        self.concurrent_marking_in_progress()
467    }
468}