mmtk/plan/lxr/
global.rs

1use super::block_allocation::BlockAllocation;
2use super::gc_work::nursery_sweeping::ReleaseLOSNursery;
3use super::gc_work::prepare::FastRCPrepare;
4use super::gc_work::rc::ProcessDecs;
5use super::gc_work::LXRGCWorkContext;
6use super::mature_evac::MatureEvacuationSet;
7use super::mutator::ALLOCATOR_MAPPING;
8use super::{LazySweepingJobsCounter, LAZY_SWEEPING_JOBS};
9use crate::plan::concurrent::global::ConcurrentPlan;
10use crate::plan::concurrent::Pause;
11use crate::plan::global::CommonPlan;
12use crate::plan::global::{BasePlan, CreateGeneralPlanArgs, CreateSpecificPlanArgs};
13use crate::plan::lxr::gc_work::mature_sweeping::{RCSweepMatureAfterSATBLOS, SweepDeadCycles};
14use crate::plan::lxr::gc_work::nursery_sweeping::SweepBlocksAfterDecs;
15use crate::plan::lxr::gc_work::prepare::{ConcurrentChunkMetadataZeroing, PrepareChunksForFullGC};
16use crate::plan::lxr::mature_evac::MatureEvecRemSet;
17use crate::plan::AllocationSemantics;
18use crate::plan::MutatorContext;
19use crate::plan::Plan;
20use crate::plan::PlanConstraints;
21use crate::policy::immix::block::Block;
22use crate::policy::immix::ImmixSpaceArgs;
23use crate::policy::largeobjectspace::LargeObjectSpace;
24use crate::policy::space::Space;
25use crate::scheduler::gc_work::*;
26use crate::util::alloc::allocators::AllocatorSelector;
27#[cfg(feature = "analysis")]
28use crate::util::analysis::GcHookWork;
29use crate::util::constants::*;
30use crate::util::copy::*;
31use crate::util::heap::{SpaceStats, VMRequest};
32use crate::util::metadata::side_metadata::SideMetadataContext;
33use crate::util::metadata::MetadataSpec;
34use crate::util::rc::{RefCountHelper, RC_TABLE};
35#[cfg(feature = "sanity")]
36use crate::util::sanity::sanity_checker::*;
37use crate::util::{metadata, Address, ObjectReference};
38use crate::vm::ActivePlan;
39use crate::vm::{Collection, ObjectModel, VMBinding};
40use crate::BarrierSelector;
41use crate::{policy::immix::ImmixSpace, util::opaque_pointer::VMWorkerThread};
42use crate::{scheduler::*, MMTK};
43use atomic::{Atomic, Ordering};
44use crossbeam::queue::SegQueue;
45use enum_map::EnumMap;
46use spin::Lazy;
47use std::sync::atomic::{AtomicBool, AtomicUsize};
48use std::sync::{Condvar, Mutex, RwLock};
49
50const LOG_CONSERVATIVE_SURVIVAL_RATIO_MULTIPLER: usize = 1;
51
52static HEAP_AFTER_GC: AtomicUsize = AtomicUsize::new(0);
53
54use mmtk_macros::{HasSpaces, PlanTraceObject};
55
56#[derive(HasSpaces, PlanTraceObject)]
57pub struct LXR<VM: VMBinding> {
58    #[post_scan]
59    #[space]
60    #[copy_semantics(CopySemantics::DefaultCopy)]
61    pub immix_space: ImmixSpace<VM>,
62    #[parent]
63    pub common: CommonPlan<VM>,
64    /// Always true for non-rc immix.
65    /// For RC immix, this is used for enable backup tracing.
66    perform_cycle_collection: AtomicBool,
67    current_pause: Atomic<Option<Pause>>,
68    previous_pause: Atomic<Option<Pause>>,
69    hint_cycle_gc: AtomicBool,
70    hint_emergency_gc: AtomicBool,
71    avail_pages_at_end_of_last_gc: AtomicUsize,
72    zeroing_packets_scheduled: AtomicBool,
73    decide_cycle_collection: (Mutex<bool>, Condvar),
74    in_concurrent_marking: AtomicBool,
75    pub prev_roots: RwLock<SegQueue<Vec<ObjectReference>>>,
76    pub curr_roots: RwLock<SegQueue<Vec<ObjectReference>>>,
77    pub rc: RefCountHelper<VM>,
78    block_allocation: BlockAllocation<VM>,
79    pub(super) evac_set: MatureEvacuationSet,
80    pub(super) mature_evac_remset: MatureEvecRemSet<VM>,
81    pub(super) num_clean_blocks_released_lazy: AtomicUsize,
82    pub(super) possibly_dead_mature_blocks: SegQueue<(Block, bool)>,
83}
84
85pub static LXR_CONSTRAINTS: Lazy<PlanConstraints> = Lazy::new(|| PlanConstraints {
86    moves_objects: true,
87    // Max immix object size is half of a block.
88    max_non_los_default_alloc_bytes: crate::policy::immix::MAX_IMMIX_OBJECT_SIZE,
89    barrier: BarrierSelector::FieldBarrier,
90    needs_log_bit: true,
91    needs_field_log_bit: true,
92    rc_enabled: true,
93    needs_prepare_mutator: false,
94    ..PlanConstraints::default()
95});
96
97impl<VM: VMBinding> Plan for LXR<VM> {
98    fn current_gc_may_move_object(&self) -> bool {
99        true
100    }
101
102    fn collection_required(&self, space_full: bool, _space: Option<SpaceStats<Self::VM>>) -> bool {
103        // Spaces or heap full
104        if self.base().collection_required(self, space_full) {
105            return true;
106        }
107        // SATB is finished
108        if self.concurrent_work_in_progress() && super::concurrent_marking_packets_drained() {
109            return true;
110        }
111        // Survival limits
112        let total_young_alloc_pages =
113            self.block_allocation.total_young_allocation_in_bytes() >> LOG_BYTES_IN_MBYTE;
114        let predicted_survival_mb: usize =
115            ((total_young_alloc_pages as f64 * super::SURVIVAL_RATIO_PREDICTOR.ratio()) as usize)
116                << LOG_CONSERVATIVE_SURVIVAL_RATIO_MULTIPLER;
117        if predicted_survival_mb >= super::MAX_SURVIVAL_MB {
118            return true;
119        }
120        if !self.immix_space.common().contiguous {
121            let available_to_space = self.get_total_pages() - self.get_used_pages();
122            if predicted_survival_mb >= available_to_space {
123                return true;
124            }
125        }
126        false
127    }
128
129    fn last_collection_was_exhaustive(&self) -> bool {
130        self.previous_pause.load(Ordering::SeqCst) == Some(Pause::Full)
131    }
132
133    fn constraints(&self) -> &'static PlanConstraints {
134        &LXR_CONSTRAINTS
135    }
136
137    fn create_copy_config(&'static self) -> CopyConfig<VM> {
138        use enum_map::enum_map;
139        CopyConfig {
140            copy_mapping: enum_map! {
141                CopySemantics::DefaultCopy => CopySelector::Immix(0),
142                _ => CopySelector::Unused,
143            },
144            space_mapping: vec![(CopySelector::Immix(0), &self.immix_space)],
145            constraints: &LXR_CONSTRAINTS,
146        }
147    }
148
149    fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
150        if !super::LazySweepingJobs::all_finished() {
151            warn!("LXR Lazy Sweeping Not Finished");
152        }
153        let pause = self.select_collection_kind();
154        // Wait for concurrent packets
155        // Mark table zeroing
156        if pause == Pause::InitialMark || pause == Pause::Full {
157            self.schedule_mark_table_zeroing_tasks(Some(pause))
158        }
159        self.zeroing_packets_scheduled
160            .store(false, Ordering::SeqCst);
161        // Set current pause kind
162        self.current_pause.store(Some(pause), Ordering::SeqCst);
163        self.perform_cycle_collection
164            .store(pause != Pause::RefCount, Ordering::SeqCst);
165        // Schedule work
166        match pause {
167            Pause::Full => self.schedule_emergency_full_heap_collection(scheduler),
168            Pause::RefCount => self.schedule_rc_collection(scheduler),
169            Pause::InitialMark => self.schedule_concurrent_marking_initial_pause(scheduler),
170            Pause::FinalMark => self.schedule_concurrent_marking_final_pause(scheduler),
171        }
172        // Analysis routine that is ran. It is generally recommended to take advantage
173        // of the scheduling system we have in place for more performance
174        #[cfg(feature = "analysis")]
175        scheduler.work_buckets[WorkBucketStage::Unconstrained].add(GcHookWork);
176        // Resume mutators
177        if pause == Pause::Full || pause == Pause::FinalMark {
178            #[cfg(feature = "sanity")]
179            scheduler.work_buckets[WorkBucketStage::Final].add(ScheduleSanityGC::<Self>::new(self));
180        }
181    }
182
183    fn get_allocator_mapping(&self) -> &'static EnumMap<AllocationSemantics, AllocatorSelector> {
184        &ALLOCATOR_MAPPING
185    }
186
187    fn prepare(&mut self, tls: VMWorkerThread) {
188        let pause = self.current_pause().unwrap();
189        if pause == Pause::FinalMark || pause == Pause::Full {
190            self.common.los.is_end_of_satb_or_full_gc = true;
191            // release nursery memory before mature evacuation, to reduce the chance of to-space overflow.
192            self.immix_space.scheduler().work_buckets[WorkBucketStage::Unconstrained]
193                .add(ReleaseLOSNursery);
194        }
195        self.common
196            .prepare(tls, pause == Pause::Full || pause == Pause::InitialMark);
197        if super::MATURE_EVACUATION && (pause == Pause::FinalMark || pause == Pause::Full) {
198            self.process_mature_evacuation_remset();
199        }
200        if super::MATURE_EVACUATION && (pause == Pause::InitialMark || pause == Pause::Full) {
201            // Select mature evacuation set
202            self.schedule_defrag_selection_packets();
203        }
204        self.num_clean_blocks_released_lazy
205            .store(0, Ordering::SeqCst);
206        self.immix_space.prepare_rc(pause);
207        self.block_allocation
208            .reset_block_mark_for_mutator_reused_blocks(pause);
209    }
210
211    fn release(&mut self, tls: VMWorkerThread) {
212        let _new_ratio = super::SURVIVAL_RATIO_PREDICTOR.update_ratio();
213        let pause = self.current_pause().unwrap();
214        if pause == Pause::FinalMark || pause == Pause::Full {
215            VM::VMCollection::update_weak_processor(false);
216        }
217        <VM as VMBinding>::VMCollection::vm_release();
218        self.common.los.is_end_of_satb_or_full_gc = false;
219        self.common
220            .release(tls, pause == Pause::Full || pause == Pause::FinalMark);
221        self.block_allocation
222            .sweep_nursery_blocks(self.immix_space.scheduler(), pause);
223        self.block_allocation.sweep_mutator_reused_blocks(pause);
224        // Check if we want to do all decs and sweeping in the pause
225        if super::disable_lasy_dec_for_current_gc() {
226            self.immix_space
227                .scheduler()
228                .process_concurrent_packets_in_pause();
229        } else {
230            debug_assert_ne!(pause, Pause::Full);
231        }
232        self.immix_space.release_rc();
233        self.schedule_mature_sweeping(pause);
234        // swap roots
235        let mut prev_roots = self.prev_roots.write().unwrap();
236        let mut curr_roots = self.curr_roots.write().unwrap();
237        std::mem::swap::<SegQueue<_>>(&mut prev_roots, &mut curr_roots);
238        debug_assert!(curr_roots.is_empty());
239    }
240
241    fn get_collection_reserved_pages(&self) -> usize {
242        let survival = {
243            let predicted_survival = (self.block_allocation.clean_nursery_mb() as f64
244                * super::SURVIVAL_RATIO_PREDICTOR.ratio())
245                as usize;
246            predicted_survival << LOG_CONSERVATIVE_SURVIVAL_RATIO_MULTIPLER
247        };
248        survival + self.immix_space.defrag_headroom_pages()
249    }
250
251    fn get_used_pages(&self) -> usize {
252        self.immix_space.reserved_pages() + self.common.get_used_pages()
253    }
254
255    fn base(&self) -> &BasePlan<VM> {
256        &self.common.base
257    }
258
259    fn base_mut(&mut self) -> &mut BasePlan<VM> {
260        &mut self.common.base
261    }
262
263    fn common(&self) -> &CommonPlan<VM> {
264        &self.common
265    }
266
267    /// Get a mutable reference to the common plan. See [`Self::common`].
268    fn common_mut(&mut self) -> &mut CommonPlan<Self::VM> {
269        &mut self.common
270    }
271
272    fn on_pause_start(&self, mmtk: &'static MMTK<Self::VM>) {
273        super::NO_EVAC.store(false, Ordering::SeqCst);
274        let pause = self.current_pause().unwrap();
275
276        // Individual RC pauses that don't overlap with concurrent tracing consist of a GC cycle.
277        // Concurrent tracing, including RC pauses in between, counts as one GC cycle.
278        // A Full GC counts as a GC cycle.
279        if pause == Pause::RefCount && !self.concurrent_work_in_progress()
280            || pause == Pause::InitialMark
281            || pause == Pause::Full
282        {
283            mmtk.gc_trigger.policy.on_gc_start(mmtk);
284        }
285
286        super::SURVIVAL_RATIO_PREDICTOR
287            .set_alloc_size(self.block_allocation.total_young_allocation_in_bytes());
288
289        if pause == Pause::Full || pause == Pause::InitialMark {
290            // Reset block mark and object mark table.
291            let work_packets = self.generate_full_trace_prepare_tasks();
292            self.immix_space.scheduler().work_buckets[WorkBucketStage::RCProcessIncs]
293                .bulk_add(work_packets);
294        }
295
296        for mutator in <VM as VMBinding>::VMActivePlan::mutators() {
297            mutator.flush();
298        }
299
300        if pause == Pause::FinalMark {
301            self.set_concurrent_marking_state(false);
302        }
303    }
304
305    fn on_pause_end(&mut self, mmtk: &'static MMTK<Self::VM>, tls: VMWorkerThread) {
306        super::DISABLE_LASY_DEC_FOR_CURRENT_GC.store(false, Ordering::SeqCst);
307        // self.immix_space.flush_page_resource();
308        let pause = self.current_pause().unwrap();
309        if pause == Pause::InitialMark {
310            self.set_concurrent_marking_state(true);
311        }
312        self.previous_pause.store(Some(pause), Ordering::SeqCst);
313        self.current_pause.store(None, Ordering::SeqCst);
314        LAZY_SWEEPING_JOBS.write().swap();
315        if super::LAZY_DECREMENTS {
316            let perform_cycle_collection =
317                self.get_available_pages() < super::CYCLE_TRIGGER_THRESHOLD;
318            self.hint_cycle_gc
319                .store(perform_cycle_collection, Ordering::SeqCst);
320            self.hint_emergency_gc.store(false, Ordering::SeqCst);
321            self.perform_cycle_collection.store(false, Ordering::SeqCst);
322        }
323        self.avail_pages_at_end_of_last_gc
324            .store(self.get_available_pages(), Ordering::SeqCst);
325        HEAP_AFTER_GC.store(self.get_reserved_pages(), Ordering::SeqCst);
326
327        self.common_mut().on_pause_end(tls);
328
329        // Individual RC pauses that don't overlap with concurrent tracing consist of a GC cycle.
330        // Concurrent tracing, including RC pauses in between, counts as one GC cycle.
331        // A Full GC counts as a GC cycle.
332        if pause == Pause::RefCount && !self.concurrent_work_in_progress()
333            || pause == Pause::FinalMark
334            || pause == Pause::Full
335        {
336            mmtk.gc_trigger.policy.on_gc_end(mmtk);
337        }
338    }
339
340    fn root_scanning_stage(&self) -> WorkBucketStage {
341        WorkBucketStage::RCProcessIncs
342    }
343
344    fn concurrent(&self) -> Option<&dyn ConcurrentPlan<VM = VM>> {
345        Some(self)
346    }
347}
348
349impl<VM: VMBinding> ConcurrentPlan for LXR<VM> {
350    fn current_pause(&self) -> Option<Pause> {
351        self.current_pause.load(Ordering::SeqCst)
352    }
353
354    fn concurrent_work_in_progress(&self) -> bool {
355        self.in_concurrent_marking.load(Ordering::Acquire)
356    }
357
358    fn on_concurrent_work_interrupted(&self) {
359        // Do nothing
360    }
361}
362
363impl<VM: VMBinding> LXR<VM> {
364    pub fn new(args: CreateGeneralPlanArgs<VM>) -> Box<Self> {
365        assert!(
366            VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC.is_in_header(),
367            "LXR does not support placing forwarding bits on the side."
368        );
369        let num_workers = args.scheduler.num_workers();
370        let immix_specs = metadata::extract_side_metadata(&[
371            MetadataSpec::OnSide(RC_TABLE),
372            MetadataSpec::OnSide(
373                *VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
374                    .as_spec()
375                    .extract_side_spec(),
376            ),
377        ]);
378        let global_side_metadata_specs = SideMetadataContext::new_global_specs(&immix_specs);
379        let mut plan_args = CreateSpecificPlanArgs {
380            global_args: args,
381            constraints: &LXR_CONSTRAINTS,
382            global_side_metadata_specs,
383        };
384        let immix_space = ImmixSpace::new(
385            plan_args.get_mature_space_args("immix", true, false, VMRequest::discontiguous()),
386            ImmixSpaceArgs {
387                never_move_objects: false,
388                mixed_age: false,
389            },
390        );
391        let mut lxr = Box::new(LXR {
392            immix_space,
393            common: CommonPlan::new(plan_args),
394            perform_cycle_collection: AtomicBool::new(false),
395            hint_cycle_gc: AtomicBool::new(false),
396            hint_emergency_gc: AtomicBool::new(false),
397            current_pause: Atomic::new(None),
398            previous_pause: Atomic::new(None),
399            avail_pages_at_end_of_last_gc: AtomicUsize::new(0),
400            zeroing_packets_scheduled: AtomicBool::new(false),
401            decide_cycle_collection: (Mutex::new(true), Condvar::new()),
402            in_concurrent_marking: AtomicBool::new(false),
403            prev_roots: Default::default(),
404            curr_roots: Default::default(),
405            rc: RefCountHelper::NEW,
406            block_allocation: BlockAllocation::new(),
407            evac_set: MatureEvacuationSet::default(),
408            mature_evac_remset: MatureEvecRemSet::new(num_workers),
409            possibly_dead_mature_blocks: Default::default(),
410            num_clean_blocks_released_lazy: Default::default(),
411        });
412
413        lxr.gc_init();
414
415        // Note: `verify_side_metadata_sanity` is invoked later by `MMTK::new`, after the dynamic
416        // side metadata base address has been initialized. It must not be called here during plan
417        // construction, as the side metadata layout is not yet registered at this point.
418
419        lxr
420    }
421
422    pub fn cm_enabled(&self) -> bool {
423        !cfg!(feature = "lxr_no_cm")
424    }
425
426    fn schedule_defrag_selection_packets(&self) {
427        self.evac_set
428            .schedule_defrag_selection_packets(&self.immix_space)
429    }
430
431    /// Generate chunk sweep work packets.
432    fn generate_dead_cycle_sweep_tasks(&self) -> Vec<Box<dyn GCWork<VM>>> {
433        self.immix_space.chunk_map.generate_tasks_batched(
434            self.immix_space.scheduler().num_workers(),
435            |chunks| {
436                Box::new(SweepDeadCycles::new(
437                    chunks,
438                    LazySweepingJobsCounter::new_decs(),
439                ))
440            },
441        )
442    }
443
444    fn schedule_mature_sweeping(&self, pause: Pause) {
445        if pause == Pause::Full || pause == Pause::FinalMark {
446            self.evac_set
447                .sweep_mature_evac_candidates(&self.immix_space);
448            let disable_lasy_dec_for_current_gc =
449                crate::plan::lxr::disable_lasy_dec_for_current_gc();
450            let dead_cycle_sweep_packets = self.generate_dead_cycle_sweep_tasks();
451            let sweep_los = RCSweepMatureAfterSATBLOS::new(LazySweepingJobsCounter::new_decs());
452            if super::LAZY_DECREMENTS && !disable_lasy_dec_for_current_gc {
453                debug_assert_ne!(pause, Pause::Full);
454                let concurrent_bucket =
455                    &self.immix_space.scheduler().work_buckets[WorkBucketStage::Concurrent];
456                concurrent_bucket.bulk_add_deferred(dead_cycle_sweep_packets);
457                concurrent_bucket.add_deferred(Box::new(sweep_los));
458            } else {
459                self.immix_space.scheduler().work_buckets[WorkBucketStage::STWRCDecsAndSweep]
460                    .bulk_add(dead_cycle_sweep_packets);
461                self.immix_space.scheduler().work_buckets[WorkBucketStage::STWRCDecsAndSweep]
462                    .add(sweep_los);
463            }
464        }
465    }
466
467    /// Generate chunk sweep work packets.
468    fn generate_full_trace_prepare_tasks(&self) -> Vec<Box<dyn GCWork<VM>>> {
469        self.immix_space
470            .chunk_map
471            .generate_tasks_batched(self.immix_space.scheduler().num_workers(), |chunks| {
472                Box::new(PrepareChunksForFullGC { chunks })
473            })
474    }
475
476    fn schedule_rc_block_sweeping_tasks(&self, counter: LazySweepingJobsCounter) {
477        // while let Some(x) = self.last_mutator_recycled_blocks.pop() {
478        //     x.set_state(BlockState::Marked);
479        // }
480        // This may happen either within a pause, or in concurrent.
481        let size = self.possibly_dead_mature_blocks.len();
482        let num_bins = self.immix_space.scheduler().num_workers();
483        let bin_cap = size / num_bins + if size % num_bins == 0 { 0 } else { 1 };
484        let mut bins = (0..num_bins)
485            .map(|_| Vec::with_capacity(bin_cap))
486            .collect::<Vec<Vec<(Block, bool)>>>();
487        'out: for bin in bins.iter_mut() {
488            for _ in 0..bin_cap {
489                if let Some(block) = self.possibly_dead_mature_blocks.pop() {
490                    bin.push(block);
491                } else {
492                    break 'out;
493                }
494            }
495        }
496        let packets = bins
497            .into_iter()
498            .map::<Box<dyn GCWork<VM>>, _>(|blocks| {
499                Box::new(SweepBlocksAfterDecs::new(blocks, counter.clone()))
500            })
501            .collect();
502        self.immix_space.scheduler().work_buckets[WorkBucketStage::Unconstrained].bulk_add(packets);
503    }
504
505    pub(super) fn process_mature_evacuation_remset(&self) {
506        self.mature_evac_remset.flush_all();
507        let packets = self.mature_evac_remset.take_global_packets();
508        self.immix_space.scheduler().work_buckets[WorkBucketStage::RCEvacuateMature]
509            .bulk_add(packets);
510    }
511
512    pub(super) fn add_to_possibly_dead_mature_blocks(&self, block: Block, is_defrag_source: bool) {
513        if block.log() {
514            self.possibly_dead_mature_blocks
515                .push((block, is_defrag_source));
516        }
517    }
518
519    fn next_gc_is_emergency_gc(
520        &self,
521        total_pages: usize,
522        mature_space_pages: usize,
523        emergency_threshold: usize,
524    ) -> bool {
525        let min_avail_pages = usize::min(total_pages * emergency_threshold / 100, 1 << 30 >> 12);
526        total_pages < min_avail_pages + mature_space_pages
527    }
528
529    fn next_gc_is_cycle_gc(&self, mature_space_pages: usize, pause: Pause) -> bool {
530        if pause == Pause::FinalMark || pause == Pause::Full {
531            super::MATURE_LIVE_PREDICTOR.update(mature_space_pages);
532        }
533        let live_mature_pages = super::MATURE_LIVE_PREDICTOR.live_pages() as usize;
534        let garbage = mature_space_pages.saturating_sub(live_mature_pages);
535        let total_pages = self.get_total_pages();
536        !self.concurrent_work_in_progress()
537            && (self.cm_enabled() && garbage * 100 >= super::TRACE_THRESHOLD * total_pages)
538    }
539
540    fn decide_next_gc_may_perform_cycle_collection(&self, pause: Pause) {
541        let (lock, cvar) = &self.decide_cycle_collection;
542        let notify = || {
543            let mut decide_cycle_collection = lock.lock().unwrap();
544            *decide_cycle_collection = true;
545            cvar.notify_one();
546        };
547        // Reset states
548        self.hint_cycle_gc.store(false, Ordering::SeqCst);
549        self.hint_emergency_gc.store(false, Ordering::SeqCst);
550        let emergency_threshold = super::RC_STOP_PERCENT;
551        // Calculate mature space size
552        let total_pages = self.get_total_pages();
553        let mature_space_pages = {
554            let released_los_pages = self.los().num_pages_released_lazy.load(Ordering::SeqCst);
555            HEAP_AFTER_GC
556                .load(Ordering::SeqCst)
557                .saturating_sub(
558                    self.num_clean_blocks_released_lazy.load(Ordering::SeqCst) << Block::LOG_PAGES,
559                )
560                .saturating_sub(released_los_pages)
561        };
562        // Decide next GC kind
563        let hint_cycle_gc = self.next_gc_is_cycle_gc(mature_space_pages, pause);
564        let hint_emergency_gc =
565            self.next_gc_is_emergency_gc(total_pages, mature_space_pages, emergency_threshold);
566        // Update states
567        self.hint_cycle_gc.store(hint_cycle_gc, Ordering::SeqCst);
568        self.hint_emergency_gc
569            .store(hint_emergency_gc, Ordering::SeqCst);
570        // Eager mark-table zeroing
571        if !cfg!(feature = "sanity") && hint_cycle_gc {
572            self.schedule_mark_table_zeroing_tasks(None);
573        }
574        notify();
575    }
576
577    fn schedule_mark_table_zeroing_tasks(&self, pause: Option<Pause>) {
578        if let Some(pause) = pause {
579            assert!(pause == Pause::InitialMark || pause == Pause::Full);
580            if self.zeroing_packets_scheduled.load(Ordering::SeqCst) {
581                return;
582            }
583        }
584        let work_packets = self
585            .immix_space
586            .chunk_map
587            .generate_tasks_batched(self.immix_space.scheduler().num_workers(), |chunks| {
588                Box::new(ConcurrentChunkMetadataZeroing { chunks })
589            });
590        self.immix_space.scheduler().work_buckets[WorkBucketStage::Unconstrained]
591            .bulk_add(work_packets);
592        self.zeroing_packets_scheduled.store(true, Ordering::SeqCst);
593    }
594
595    fn wait_for_decide_cycle_collection(&self) {
596        let (lock, cvar) = &self.decide_cycle_collection;
597        let mut decide_cycle_collection = lock.lock().unwrap();
598        while !*decide_cycle_collection {
599            decide_cycle_collection = cvar.wait(decide_cycle_collection).unwrap();
600        }
601        *decide_cycle_collection = false;
602    }
603
604    fn select_collection_kind(&self) -> Pause {
605        self.wait_for_decide_cycle_collection();
606
607        let emergency = self.base().global_state.is_emergency_collection();
608        let user_triggered = self.base().global_state.is_user_triggered_collection();
609        let cm_in_progress = self.concurrent_work_in_progress();
610        let cm_packets_drained = super::concurrent_marking_packets_drained();
611        let hint_cycle_gc = self.hint_cycle_gc.load(Ordering::SeqCst);
612        let hint_emergency_gc = self.hint_emergency_gc.load(Ordering::SeqCst);
613        // If CM is finished, do a final mark pause
614        if cm_in_progress && cm_packets_drained {
615            return Pause::FinalMark;
616        }
617
618        // Either final mark pause or full pause for emergency GC
619        if emergency || user_triggered || hint_emergency_gc {
620            return if cm_in_progress {
621                Pause::FinalMark
622            } else {
623                Pause::Full
624            };
625        }
626
627        // Should trigger CM?
628        if hint_cycle_gc && !cm_in_progress {
629            if self.cm_enabled() {
630                Pause::InitialMark
631            } else {
632                Pause::Full
633            }
634        } else {
635            Pause::RefCount
636        }
637    }
638
639    fn disable_unnecessary_buckets(&'static self, scheduler: &GCWorkScheduler<VM>, pause: Pause) {
640        // Set conditional buckets
641        scheduler.work_buckets[WorkBucketStage::RCProcessIncs].set_enabled(true);
642        scheduler.work_buckets[WorkBucketStage::Prepare].set_enabled(pause != Pause::RefCount);
643        let final_mark_or_full = pause == Pause::FinalMark || pause == Pause::Full;
644        scheduler.work_buckets[WorkBucketStage::Closure].set_enabled(final_mark_or_full);
645        scheduler.work_buckets[WorkBucketStage::WeakRefClosure].set_enabled(final_mark_or_full);
646        scheduler.work_buckets[WorkBucketStage::FinalRefClosure].set_enabled(final_mark_or_full);
647        scheduler.work_buckets[WorkBucketStage::PhantomRefClosure].set_enabled(final_mark_or_full);
648        scheduler.work_buckets[WorkBucketStage::STWRCDecsAndSweep]
649            .set_enabled(!(super::LAZY_DECREMENTS && pause != Pause::Full));
650        // Always enabled
651        scheduler.work_buckets[WorkBucketStage::Concurrent].set_enabled(true);
652        scheduler.work_buckets[WorkBucketStage::ConcurrentResumable].set_enabled(true);
653        // Always disabled
654        scheduler.work_buckets[WorkBucketStage::TPinningClosure].set_enabled(false);
655        scheduler.work_buckets[WorkBucketStage::PinningRootsTrace].set_enabled(false);
656        scheduler.work_buckets[WorkBucketStage::VMRefClosure].set_enabled(false);
657        scheduler.work_buckets[WorkBucketStage::VMRefForwarding].set_enabled(false);
658        scheduler.work_buckets[WorkBucketStage::SoftRefClosure].set_enabled(false);
659        scheduler.work_buckets[WorkBucketStage::CalculateForwarding].set_enabled(false);
660        scheduler.work_buckets[WorkBucketStage::SecondRoots].set_enabled(false);
661        scheduler.work_buckets[WorkBucketStage::RefForwarding].set_enabled(false);
662        scheduler.work_buckets[WorkBucketStage::FinalizableForwarding].set_enabled(false);
663        scheduler.work_buckets[WorkBucketStage::Compact].set_enabled(false);
664    }
665
666    fn schedule_rc_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
667        log::info!("Scheduling RC collection...");
668        self.disable_unnecessary_buckets(scheduler, Pause::RefCount);
669        // Before start yielding, wrap all the roots from the previous GC with work-packets.
670        self.process_prev_roots(scheduler);
671        // Stop & scan mutators (mutator scanning can happen before STW)
672        scheduler.work_buckets[WorkBucketStage::Unconstrained]
673            .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
674        // Prepare global/collectors/mutators
675        scheduler.work_buckets[WorkBucketStage::RCProcessIncs].add(FastRCPrepare);
676        // Release global/collectors/mutators
677        scheduler.work_buckets[WorkBucketStage::Release]
678            .add(Release::<LXRGCWorkContext<VM>>::new(self));
679    }
680
681    fn schedule_concurrent_marking_initial_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
682        log::info!("Scheduling concurrent marking initial pause...");
683        self.disable_unnecessary_buckets(scheduler, Pause::InitialMark);
684        self.process_prev_roots(scheduler);
685        scheduler.work_buckets[WorkBucketStage::Unconstrained]
686            .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
687        scheduler.work_buckets[WorkBucketStage::Prepare]
688            .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
689        scheduler.work_buckets[WorkBucketStage::Release]
690            .add(Release::<LXRGCWorkContext<VM>>::new(self));
691    }
692
693    fn schedule_concurrent_marking_final_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
694        log::info!("Scheduling concurrent marking final pause...");
695        self.disable_unnecessary_buckets(scheduler, Pause::FinalMark);
696        self.process_prev_roots(scheduler);
697        scheduler.work_buckets[WorkBucketStage::Unconstrained]
698            .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
699
700        scheduler.work_buckets[WorkBucketStage::Prepare]
701            .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
702        scheduler.work_buckets[WorkBucketStage::Release]
703            .add(Release::<LXRGCWorkContext<VM>>::new(self));
704    }
705
706    fn schedule_emergency_full_heap_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
707        log::info!("Scheduling emergency full-heap collection...");
708        super::DISABLE_LASY_DEC_FOR_CURRENT_GC.store(true, Ordering::SeqCst);
709        self.disable_unnecessary_buckets(scheduler, Pause::Full);
710        // Before start yielding, wrap all the roots from the previous GC with work-packets.
711        self.process_prev_roots(scheduler);
712        // Stop & scan mutators (mutator scanning can happen before STW)
713        scheduler.work_buckets[WorkBucketStage::Unconstrained]
714            .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
715        // Prepare global/collectors/mutators
716        scheduler.work_buckets[WorkBucketStage::Prepare]
717            .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
718        // Release global/collectors/mutators
719        scheduler.work_buckets[WorkBucketStage::Release]
720            .add(Release::<LXRGCWorkContext<VM>>::new(self));
721    }
722
723    fn process_prev_roots(&self, scheduler: &GCWorkScheduler<VM>) {
724        let prev_roots = self.prev_roots.read().unwrap();
725        let mut work_packets: Vec<Box<dyn GCWork<VM>>> = Vec::with_capacity(prev_roots.len());
726        while let Some(decs) = prev_roots.pop() {
727            work_packets.push(Box::new(ProcessDecs::new(
728                decs,
729                LazySweepingJobsCounter::new_decs(),
730            )))
731        }
732        if work_packets.is_empty() {
733            work_packets.push(Box::new(ProcessDecs::new(
734                vec![],
735                LazySweepingJobsCounter::new_decs(),
736            )));
737        }
738        if super::LAZY_DECREMENTS {
739            scheduler.work_buckets[WorkBucketStage::Concurrent].bulk_add_deferred(work_packets);
740        } else {
741            scheduler.work_buckets[WorkBucketStage::STWRCDecsAndSweep].bulk_add(work_packets);
742        }
743    }
744
745    pub fn current_pause(&self) -> Option<Pause> {
746        self.current_pause.load(Ordering::SeqCst)
747    }
748
749    pub fn previous_pause(&self) -> Option<Pause> {
750        self.previous_pause.load(Ordering::SeqCst)
751    }
752
753    pub fn in_defrag(&self, o: ObjectReference) -> bool {
754        self.immix_space.in_space(o) && Block::in_defrag_block(o)
755    }
756
757    pub fn address_in_defrag(&self, a: Address) -> bool {
758        self.immix_space.address_in_space(a) && Block::address_in_defrag_block(a)
759    }
760
761    pub fn mark(&self, o: ObjectReference) -> bool {
762        if self.immix_space.in_space(o) {
763            self.immix_space.attempt_mark(o)
764        } else {
765            self.common.los.attempt_mark(o)
766        }
767    }
768
769    pub fn is_marked(&self, o: ObjectReference) -> bool {
770        if self.immix_space.in_space(o) {
771            self.immix_space.is_marked(o)
772        } else {
773            self.common.los.is_marked(o)
774        }
775    }
776
777    pub const fn los(&self) -> &LargeObjectSpace<VM> {
778        &self.common.los
779    }
780
781    fn on_lazy_decs_finished(&self, c: LazySweepingJobsCounter) {
782        self.schedule_rc_block_sweeping_tasks(c);
783    }
784
785    fn on_lazy_sweeping_finished(&self) {
786        self.immix_space.flush_page_resource();
787        // Update counters
788        if !super::LAZY_DECREMENTS {
789            HEAP_AFTER_GC.store(self.get_used_pages(), Ordering::SeqCst);
790        }
791        let pause = match self.current_pause() {
792            Some(p) => p,
793            None => self.previous_pause().unwrap(),
794        };
795        self.decide_next_gc_may_perform_cycle_collection(pause);
796    }
797
798    fn gc_init(&mut self) {
799        self.immix_space.rc_enabled = true;
800        self.common.los.rc_enabled = true;
801        unsafe {
802            let me: &'static Self = &*(self as *const Self);
803            me.block_allocation.init(&me.immix_space, me);
804            me.immix_space.install_hooks(&me.block_allocation);
805        }
806        let mut lazy_sweeping_jobs = LAZY_SWEEPING_JOBS.write();
807        lazy_sweeping_jobs.swap();
808        let lxr_ptr = self as *const Self as usize;
809        lazy_sweeping_jobs.end_of_decs = Some(Box::new(move |c| {
810            let lxr = unsafe { &*(lxr_ptr as *const Self) };
811            lxr.on_lazy_decs_finished(c);
812        }));
813        lazy_sweeping_jobs.end_of_lazy = Some(Box::new(move || {
814            let lxr = unsafe { &*(lxr_ptr as *const Self) };
815            lxr.on_lazy_sweeping_finished();
816        }));
817    }
818
819    fn set_concurrent_marking_state(&self, active: bool) {
820        self.in_concurrent_marking.store(active, Ordering::SeqCst);
821        self.common
822            .los
823            .bump_page_reuse_count
824            .store(active, Ordering::SeqCst);
825    }
826}