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 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: super::NURSERY_EVACUATION || super::MATURE_EVACUATION,
87 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 LXR_CONSTRAINTS.moves_objects
100 }
101
102 fn collection_required(&self, space_full: bool, _space: Option<SpaceStats<Self::VM>>) -> bool {
103 if self.base().collection_required(self, space_full) {
105 return true;
106 }
107 if self.concurrent_work_in_progress() && super::concurrent_marking_packets_drained() {
109 return true;
110 }
111 if self.rc.inc_buffer_size() >= *self.base().options.lxr_inc_buffer_limit {
113 return true;
114 }
115 let total_young_alloc_pages =
117 self.block_allocation.total_young_allocation_in_bytes() >> LOG_BYTES_IN_MBYTE;
118 let ratio = if LXR_CONSTRAINTS.moves_objects {
120 super::SURVIVAL_RATIO_PREDICTOR.copy_promote_ratio()
121 } else {
122 super::SURVIVAL_RATIO_PREDICTOR.promote_ratio()
123 };
124 let predicted_survival_mb: usize = ((total_young_alloc_pages as f64 * ratio) as usize)
125 << LOG_CONSERVATIVE_SURVIVAL_RATIO_MULTIPLER;
126 if predicted_survival_mb >= *self.base().options.lxr_max_survival_mb {
127 return true;
128 }
129 if !self.immix_space.common().contiguous {
130 let available_to_space = self.get_total_pages() - self.get_used_pages();
131 if predicted_survival_mb >= available_to_space {
132 return true;
133 }
134 }
135 false
136 }
137
138 fn last_collection_was_exhaustive(&self) -> bool {
139 self.previous_pause.load(Ordering::SeqCst) == Some(Pause::Full)
140 }
141
142 fn constraints(&self) -> &'static PlanConstraints {
143 &LXR_CONSTRAINTS
144 }
145
146 fn create_copy_config(&'static self) -> CopyConfig<VM> {
147 use enum_map::enum_map;
148 CopyConfig {
149 copy_mapping: enum_map! {
150 CopySemantics::DefaultCopy => CopySelector::Immix(0),
151 _ => CopySelector::Unused,
152 },
153 space_mapping: vec![(CopySelector::Immix(0), &self.immix_space)],
154 constraints: &LXR_CONSTRAINTS,
155 }
156 }
157
158 fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
159 if !super::LazySweepingJobs::all_finished() {
160 warn!("LXR Lazy Sweeping Not Finished");
161 }
162 let pause = self.select_collection_kind();
163 if pause == Pause::InitialMark || pause == Pause::Full {
166 self.schedule_mark_table_zeroing_tasks(Some(pause))
167 }
168 self.zeroing_packets_scheduled
169 .store(false, Ordering::SeqCst);
170 self.current_pause.store(Some(pause), Ordering::SeqCst);
172 self.perform_cycle_collection
173 .store(pause != Pause::RefCount, Ordering::SeqCst);
174 match pause {
176 Pause::Full => self.schedule_emergency_full_heap_collection(scheduler),
177 Pause::RefCount => self.schedule_rc_collection(scheduler),
178 Pause::InitialMark => self.schedule_concurrent_marking_initial_pause(scheduler),
179 Pause::FinalMark => self.schedule_concurrent_marking_final_pause(scheduler),
180 }
181 #[cfg(feature = "analysis")]
184 scheduler.work_buckets[WorkBucketStage::Unconstrained].add(GcHookWork);
185 if pause == Pause::Full || pause == Pause::FinalMark {
187 #[cfg(feature = "sanity")]
188 scheduler.work_buckets[WorkBucketStage::Final].add(ScheduleSanityGC::<Self>::new(self));
189 }
190 }
191
192 fn get_allocator_mapping(&self) -> &'static EnumMap<AllocationSemantics, AllocatorSelector> {
193 &ALLOCATOR_MAPPING
194 }
195
196 fn prepare(&mut self, _tls: VMWorkerThread) {
197 let pause = self.current_pause().unwrap();
198 if pause == Pause::FinalMark || pause == Pause::Full {
199 self.common.los.is_end_of_satb_or_full_gc = true;
200 self.immix_space.scheduler().work_buckets[WorkBucketStage::Unconstrained]
202 .add(ReleaseLOSNursery);
203 }
204
205 let starts_mark_cycle = pause == Pause::Full || pause == Pause::InitialMark;
206 if starts_mark_cycle {
208 self.common.immortal.prepare();
210 #[cfg(feature = "vm_space")]
211 self.common.base.vm_space.prepare();
212 self.common.prepare_nonmoving_space(starts_mark_cycle);
214 #[cfg(feature = "code_space")]
215 self.common.base.code_space.prepare();
216 #[cfg(feature = "code_space")]
217 self.common.base.code_lo_space.prepare();
218 #[cfg(feature = "ro_space")]
219 self.common.base.ro_space.prepare();
220 }
221 self.common.los.prepare(starts_mark_cycle);
223
224 if super::MATURE_EVACUATION && (pause == Pause::FinalMark || pause == Pause::Full) {
225 self.process_mature_evacuation_remset();
226 }
227 if super::MATURE_EVACUATION && (pause == Pause::InitialMark || pause == Pause::Full) {
228 self.schedule_defrag_selection_packets();
230 }
231 self.num_clean_blocks_released_lazy
232 .store(0, Ordering::SeqCst);
233 self.immix_space.prepare_rc(pause);
234 self.block_allocation
235 .reset_block_mark_for_mutator_reused_blocks(pause);
236 }
237
238 fn release(&mut self, tls: VMWorkerThread) {
239 super::SURVIVAL_RATIO_PREDICTOR.update_ratios();
240 let pause = self.current_pause().unwrap();
241 if pause == Pause::FinalMark || pause == Pause::Full {
242 VM::VMCollection::update_weak_processor(false);
243 }
244 <VM as VMBinding>::VMCollection::vm_release();
245 self.common.los.is_end_of_satb_or_full_gc = false;
246 self.common
247 .release(tls, pause == Pause::Full || pause == Pause::FinalMark);
248 self.block_allocation
249 .sweep_nursery_blocks(self.immix_space.scheduler(), pause);
250 self.block_allocation.sweep_mutator_reused_blocks(pause);
251 if super::disable_lasy_dec_for_current_gc() {
253 self.immix_space
254 .scheduler()
255 .process_concurrent_packets_in_pause();
256 } else {
257 debug_assert_ne!(pause, Pause::Full);
258 }
259 self.immix_space.release_rc();
260 self.schedule_mature_sweeping(pause);
261 let mut prev_roots = self.prev_roots.write().unwrap();
263 let mut curr_roots = self.curr_roots.write().unwrap();
264 std::mem::swap::<SegQueue<_>>(&mut prev_roots, &mut curr_roots);
265 debug_assert!(curr_roots.is_empty());
266 }
267
268 fn get_collection_reserved_pages(&self) -> usize {
269 let survival = {
270 let predicted_survival = (self.block_allocation.clean_nursery_mb() as f64
271 * super::SURVIVAL_RATIO_PREDICTOR.copy_promote_ratio())
272 as usize;
273 predicted_survival << LOG_CONSERVATIVE_SURVIVAL_RATIO_MULTIPLER
274 };
275 survival + self.immix_space.defrag_headroom_pages()
276 }
277
278 fn get_used_pages(&self) -> usize {
279 self.immix_space.reserved_pages() + self.common.get_used_pages()
280 }
281
282 fn base(&self) -> &BasePlan<VM> {
283 &self.common.base
284 }
285
286 fn base_mut(&mut self) -> &mut BasePlan<VM> {
287 &mut self.common.base
288 }
289
290 fn common(&self) -> &CommonPlan<VM> {
291 &self.common
292 }
293
294 fn common_mut(&mut self) -> &mut CommonPlan<Self::VM> {
296 &mut self.common
297 }
298
299 fn on_pause_start(&self, mmtk: &'static MMTK<Self::VM>) {
300 super::NO_EVAC.store(false, Ordering::SeqCst);
301 let pause = self.current_pause().unwrap();
302
303 if pause == Pause::RefCount && !self.concurrent_work_in_progress()
307 || pause == Pause::InitialMark
308 || pause == Pause::Full
309 {
310 mmtk.gc_trigger.policy.on_gc_start(mmtk);
311 }
312
313 super::SURVIVAL_RATIO_PREDICTOR
314 .set_alloc_size(self.block_allocation.total_young_allocation_in_bytes());
315
316 if pause == Pause::Full || pause == Pause::InitialMark {
317 let work_packets = self.generate_full_trace_prepare_tasks();
319 self.immix_space.scheduler().work_buckets[WorkBucketStage::RCProcessIncs]
320 .bulk_add(work_packets);
321 }
322
323 for mutator in <VM as VMBinding>::VMActivePlan::mutators() {
324 mutator.flush();
325 }
326
327 if pause == Pause::FinalMark {
328 self.set_concurrent_marking_state(false);
329 }
330 }
331
332 fn on_pause_end(&mut self, mmtk: &'static MMTK<Self::VM>, tls: VMWorkerThread) {
333 super::DISABLE_LASY_DEC_FOR_CURRENT_GC.store(false, Ordering::SeqCst);
334 let pause = self.current_pause().unwrap();
336 if pause == Pause::InitialMark {
337 self.set_concurrent_marking_state(true);
338 }
339 self.previous_pause.store(Some(pause), Ordering::SeqCst);
340 self.current_pause.store(None, Ordering::SeqCst);
341 LAZY_SWEEPING_JOBS.write().swap();
342 if super::LAZY_DECREMENTS {
343 let perform_cycle_collection =
344 self.get_available_pages() < super::CYCLE_TRIGGER_THRESHOLD;
345 self.hint_cycle_gc
346 .store(perform_cycle_collection, Ordering::SeqCst);
347 self.hint_emergency_gc.store(false, Ordering::SeqCst);
348 self.perform_cycle_collection.store(false, Ordering::SeqCst);
349 }
350 self.avail_pages_at_end_of_last_gc
351 .store(self.get_available_pages(), Ordering::SeqCst);
352 HEAP_AFTER_GC.store(self.get_reserved_pages(), Ordering::SeqCst);
353
354 self.common_mut().on_pause_end(tls);
355
356 if pause == Pause::RefCount && !self.concurrent_work_in_progress()
360 || pause == Pause::FinalMark
361 || pause == Pause::Full
362 {
363 mmtk.gc_trigger.policy.on_gc_end(mmtk);
364 }
365 }
366
367 fn root_scanning_stage(&self) -> WorkBucketStage {
368 #[cfg(feature = "non_moving_root")]
375 return WorkBucketStage::RCProcessIncsNonMoving;
376 #[cfg(not(feature = "non_moving_root"))]
377 return WorkBucketStage::RCProcessIncs;
378 }
379
380 fn concurrent(&self) -> Option<&dyn ConcurrentPlan<VM = VM>> {
381 Some(self)
382 }
383}
384
385impl<VM: VMBinding> ConcurrentPlan for LXR<VM> {
386 fn current_pause(&self) -> Option<Pause> {
387 self.current_pause.load(Ordering::SeqCst)
388 }
389
390 fn concurrent_work_in_progress(&self) -> bool {
391 self.in_concurrent_marking.load(Ordering::Acquire)
392 }
393
394 fn on_concurrent_work_interrupted(&self) {
395 }
397
398 fn on_concurrent_work_drained(&self) {
399 if self.in_concurrent_marking.load(Ordering::Acquire) {
401 return;
402 }
403 debug_assert!(
406 super::LazySweepingJobs::all_finished(),
407 "Deferred decrement or sweeping jobs outstanding with every GC worker parked and no work left"
408 );
409 if self
411 .base()
412 .global_state
413 .gc_status
414 .set_concurrent_gc_finished()
415 {
416 <VM as VMBinding>::VMCollection::concurrent_work_finished_no_pause();
417 }
418 }
419}
420
421impl<VM: VMBinding> LXR<VM> {
422 pub fn new(args: CreateGeneralPlanArgs<VM>) -> Box<Self> {
423 assert!(
428 VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC.is_in_header()
429 || !LXR_CONSTRAINTS.moves_objects,
430 "LXR does not support placing forwarding bits on the side unless evacuation is disabled."
431 );
432 let num_workers = args.scheduler.num_workers();
433 #[allow(unused_mut)]
434 let mut specs = vec![
435 MetadataSpec::OnSide(RC_TABLE),
436 MetadataSpec::OnSide(
437 *VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
438 .as_spec()
439 .extract_side_spec(),
440 ),
441 ];
442 #[cfg(feature = "lxr_object_log")]
449 specs.push(*VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.as_spec());
450 let immix_specs = metadata::extract_side_metadata(&specs);
451 let global_side_metadata_specs = SideMetadataContext::new_global_specs(&immix_specs);
452 let mut plan_args = CreateSpecificPlanArgs {
453 global_args: args,
454 constraints: &LXR_CONSTRAINTS,
455 global_side_metadata_specs,
456 };
457 let immix_space = ImmixSpace::new(
458 plan_args.get_mature_space_args("immix", true, false, VMRequest::discontiguous()),
459 ImmixSpaceArgs {
460 never_move_objects: false,
461 mixed_age: false,
462 },
463 );
464 let mut lxr = Box::new(LXR {
465 immix_space,
466 common: CommonPlan::new(plan_args),
467 perform_cycle_collection: AtomicBool::new(false),
468 hint_cycle_gc: AtomicBool::new(false),
469 hint_emergency_gc: AtomicBool::new(false),
470 current_pause: Atomic::new(None),
471 previous_pause: Atomic::new(None),
472 avail_pages_at_end_of_last_gc: AtomicUsize::new(0),
473 zeroing_packets_scheduled: AtomicBool::new(false),
474 decide_cycle_collection: (Mutex::new(true), Condvar::new()),
475 in_concurrent_marking: AtomicBool::new(false),
476 prev_roots: Default::default(),
477 curr_roots: Default::default(),
478 rc: RefCountHelper::NEW,
479 block_allocation: BlockAllocation::new(),
480 evac_set: MatureEvacuationSet::default(),
481 mature_evac_remset: MatureEvecRemSet::new(num_workers),
482 possibly_dead_mature_blocks: Default::default(),
483 num_clean_blocks_released_lazy: Default::default(),
484 });
485
486 lxr.gc_init();
487
488 lxr
493 }
494
495 pub fn cm_enabled(&self) -> bool {
496 !cfg!(feature = "lxr_no_cm")
497 }
498
499 fn schedule_defrag_selection_packets(&self) {
500 self.evac_set
501 .schedule_defrag_selection_packets(&self.immix_space)
502 }
503
504 fn generate_dead_cycle_sweep_tasks(&self) -> Vec<Box<dyn GCWork<VM>>> {
506 self.immix_space.chunk_map.generate_tasks_batched(
507 self.immix_space.scheduler().num_workers(),
508 |chunks| {
509 Box::new(SweepDeadCycles::new(
510 chunks,
511 LazySweepingJobsCounter::new_decs(),
512 ))
513 },
514 )
515 }
516
517 fn schedule_mature_sweeping(&self, pause: Pause) {
518 if pause == Pause::Full || pause == Pause::FinalMark {
519 self.evac_set
520 .sweep_mature_evac_candidates(&self.immix_space);
521 let disable_lasy_dec_for_current_gc =
522 crate::plan::lxr::disable_lasy_dec_for_current_gc();
523 let dead_cycle_sweep_packets = self.generate_dead_cycle_sweep_tasks();
524 let sweep_los = RCSweepMatureAfterSATBLOS::new(LazySweepingJobsCounter::new_decs());
525 if super::LAZY_DECREMENTS && !disable_lasy_dec_for_current_gc {
526 debug_assert_ne!(pause, Pause::Full);
527 let concurrent_bucket =
528 &self.immix_space.scheduler().work_buckets[WorkBucketStage::Concurrent];
529 concurrent_bucket.bulk_add_deferred(dead_cycle_sweep_packets);
530 concurrent_bucket.add_deferred(Box::new(sweep_los));
531 } else {
532 self.immix_space.scheduler().work_buckets[WorkBucketStage::STWRCDecsAndSweep]
533 .bulk_add(dead_cycle_sweep_packets);
534 self.immix_space.scheduler().work_buckets[WorkBucketStage::STWRCDecsAndSweep]
535 .add(sweep_los);
536 }
537 }
538 }
539
540 fn generate_full_trace_prepare_tasks(&self) -> Vec<Box<dyn GCWork<VM>>> {
542 self.immix_space
543 .chunk_map
544 .generate_tasks_batched(self.immix_space.scheduler().num_workers(), |chunks| {
545 Box::new(PrepareChunksForFullGC { chunks })
546 })
547 }
548
549 fn schedule_rc_block_sweeping_tasks(&self, counter: LazySweepingJobsCounter) {
550 let size = self.possibly_dead_mature_blocks.len();
555 let num_bins = self.immix_space.scheduler().num_workers();
556 let bin_cap = size / num_bins + if size % num_bins == 0 { 0 } else { 1 };
557 let mut bins = (0..num_bins)
558 .map(|_| Vec::with_capacity(bin_cap))
559 .collect::<Vec<Vec<(Block, bool)>>>();
560 'out: for bin in bins.iter_mut() {
561 for _ in 0..bin_cap {
562 if let Some(block) = self.possibly_dead_mature_blocks.pop() {
563 bin.push(block);
564 } else {
565 break 'out;
566 }
567 }
568 }
569 let packets = bins
570 .into_iter()
571 .map::<Box<dyn GCWork<VM>>, _>(|blocks| {
572 Box::new(SweepBlocksAfterDecs::new(blocks, counter.clone()))
573 })
574 .collect();
575 self.immix_space.scheduler().work_buckets[WorkBucketStage::Unconstrained].bulk_add(packets);
576 }
577
578 pub(super) fn process_mature_evacuation_remset(&self) {
579 self.mature_evac_remset.flush_all();
580 let packets = self.mature_evac_remset.take_global_packets();
581 self.immix_space.scheduler().work_buckets[WorkBucketStage::RCEvacuateMature]
582 .bulk_add(packets);
583 }
584
585 pub(super) fn add_to_possibly_dead_mature_blocks(&self, block: Block, is_defrag_source: bool) {
586 if block.log() {
587 self.possibly_dead_mature_blocks
588 .push((block, is_defrag_source));
589 }
590 }
591
592 fn next_gc_is_emergency_gc(
593 &self,
594 total_pages: usize,
595 mature_space_pages: usize,
596 emergency_threshold: usize,
597 ) -> bool {
598 let min_avail_pages = usize::min(total_pages * emergency_threshold / 100, 1 << 30 >> 12);
599 total_pages < min_avail_pages + mature_space_pages
600 }
601
602 fn next_gc_is_cycle_gc(&self, mature_space_pages: usize, pause: Pause) -> bool {
603 if pause == Pause::FinalMark || pause == Pause::Full {
604 super::MATURE_LIVE_PREDICTOR.update(mature_space_pages);
605 }
606 let live_mature_pages = super::MATURE_LIVE_PREDICTOR.live_pages() as usize;
607 let garbage = mature_space_pages.saturating_sub(live_mature_pages);
608 let total_pages = self.get_total_pages();
609 !self.concurrent_work_in_progress()
610 && (self.cm_enabled() && garbage * 100 >= super::TRACE_THRESHOLD * total_pages)
611 }
612
613 fn decide_next_gc_may_perform_cycle_collection(&self, pause: Pause) {
614 let (lock, cvar) = &self.decide_cycle_collection;
615 let notify = || {
616 let mut decide_cycle_collection = lock.lock().unwrap();
617 *decide_cycle_collection = true;
618 cvar.notify_one();
619 };
620 self.hint_cycle_gc.store(false, Ordering::SeqCst);
622 self.hint_emergency_gc.store(false, Ordering::SeqCst);
623 let emergency_threshold = super::RC_STOP_PERCENT;
624 let total_pages = self.get_total_pages();
626 let mature_space_pages = {
627 let released_los_pages = self.los().num_pages_released_lazy.load(Ordering::SeqCst);
628 HEAP_AFTER_GC
629 .load(Ordering::SeqCst)
630 .saturating_sub(
631 self.num_clean_blocks_released_lazy.load(Ordering::SeqCst) << Block::LOG_PAGES,
632 )
633 .saturating_sub(released_los_pages)
634 };
635 let hint_cycle_gc = self.next_gc_is_cycle_gc(mature_space_pages, pause);
637 let hint_emergency_gc =
638 self.next_gc_is_emergency_gc(total_pages, mature_space_pages, emergency_threshold);
639 self.hint_cycle_gc.store(hint_cycle_gc, Ordering::SeqCst);
641 self.hint_emergency_gc
642 .store(hint_emergency_gc, Ordering::SeqCst);
643 if !cfg!(feature = "sanity") && hint_cycle_gc {
645 self.schedule_mark_table_zeroing_tasks(None);
646 }
647 notify();
648 }
649
650 fn schedule_mark_table_zeroing_tasks(&self, pause: Option<Pause>) {
651 if let Some(pause) = pause {
652 assert!(pause == Pause::InitialMark || pause == Pause::Full);
653 if self.zeroing_packets_scheduled.load(Ordering::SeqCst) {
654 return;
655 }
656 }
657 let work_packets = self
658 .immix_space
659 .chunk_map
660 .generate_tasks_batched(self.immix_space.scheduler().num_workers(), |chunks| {
661 Box::new(ConcurrentChunkMetadataZeroing { chunks })
662 });
663 self.immix_space.scheduler().work_buckets[WorkBucketStage::Unconstrained]
664 .bulk_add(work_packets);
665 self.zeroing_packets_scheduled.store(true, Ordering::SeqCst);
666 }
667
668 fn wait_for_decide_cycle_collection(&self) {
669 let (lock, cvar) = &self.decide_cycle_collection;
670 let mut decide_cycle_collection = lock.lock().unwrap();
671 while !*decide_cycle_collection {
672 decide_cycle_collection = cvar.wait(decide_cycle_collection).unwrap();
673 }
674 *decide_cycle_collection = false;
675 }
676
677 fn select_collection_kind(&self) -> Pause {
678 self.wait_for_decide_cycle_collection();
679
680 let emergency = self.base().global_state.is_emergency_collection();
681 let user_triggered = self.base().global_state.is_user_triggered_collection();
682 let cm_in_progress = self.concurrent_work_in_progress();
683 let cm_packets_drained = super::concurrent_marking_packets_drained();
684 let hint_cycle_gc = self.hint_cycle_gc.load(Ordering::SeqCst);
685 let hint_emergency_gc = self.hint_emergency_gc.load(Ordering::SeqCst);
686 if cm_in_progress && cm_packets_drained {
688 return Pause::FinalMark;
689 }
690
691 if emergency || user_triggered || hint_emergency_gc {
693 return if cm_in_progress {
694 Pause::FinalMark
695 } else {
696 Pause::Full
697 };
698 }
699
700 if hint_cycle_gc && !cm_in_progress {
702 if self.cm_enabled() {
703 Pause::InitialMark
704 } else {
705 Pause::Full
706 }
707 } else {
708 Pause::RefCount
709 }
710 }
711
712 fn disable_unnecessary_buckets(&'static self, scheduler: &GCWorkScheduler<VM>, pause: Pause) {
713 #[cfg(feature = "non_moving_root")]
715 scheduler.work_buckets[WorkBucketStage::RCProcessIncsNonMoving].set_enabled(true);
716 scheduler.work_buckets[WorkBucketStage::RCProcessIncs].set_enabled(true);
717 scheduler.work_buckets[WorkBucketStage::Prepare].set_enabled(pause != Pause::RefCount);
718 let final_mark_or_full = pause == Pause::FinalMark || pause == Pause::Full;
719 #[cfg(feature = "non_moving_root")]
721 scheduler.work_buckets[WorkBucketStage::PinningRootsTrace].set_enabled(final_mark_or_full);
722 scheduler.work_buckets[WorkBucketStage::Closure].set_enabled(final_mark_or_full);
723 scheduler.work_buckets[WorkBucketStage::WeakRefClosure].set_enabled(final_mark_or_full);
724 scheduler.work_buckets[WorkBucketStage::FinalRefClosure].set_enabled(final_mark_or_full);
725 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure].set_enabled(final_mark_or_full);
726 scheduler.work_buckets[WorkBucketStage::STWRCDecsAndSweep]
727 .set_enabled(!(super::LAZY_DECREMENTS && pause != Pause::Full));
728 scheduler.work_buckets[WorkBucketStage::Concurrent].set_enabled(true);
730 scheduler.work_buckets[WorkBucketStage::ConcurrentResumable].set_enabled(true);
731 #[cfg(feature = "non_moving_root")]
735 scheduler.work_buckets[WorkBucketStage::TPinningClosure].set_enabled(false);
736 scheduler.work_buckets[WorkBucketStage::VMRefClosure].set_enabled(false);
737 scheduler.work_buckets[WorkBucketStage::VMRefForwarding].set_enabled(false);
738 scheduler.work_buckets[WorkBucketStage::SoftRefClosure].set_enabled(false);
739 scheduler.work_buckets[WorkBucketStage::CalculateForwarding].set_enabled(false);
740 scheduler.work_buckets[WorkBucketStage::SecondRoots].set_enabled(false);
741 scheduler.work_buckets[WorkBucketStage::RefForwarding].set_enabled(false);
742 scheduler.work_buckets[WorkBucketStage::FinalizableForwarding].set_enabled(false);
743 scheduler.work_buckets[WorkBucketStage::Compact].set_enabled(false);
744 }
745
746 fn schedule_rc_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
747 log::info!("Scheduling RC collection...");
748 self.disable_unnecessary_buckets(scheduler, Pause::RefCount);
749 self.process_prev_roots(scheduler);
751 scheduler.work_buckets[WorkBucketStage::Unconstrained]
753 .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
754 scheduler.work_buckets[WorkBucketStage::RCProcessIncs].add(FastRCPrepare);
756 scheduler.work_buckets[WorkBucketStage::Release]
758 .add(Release::<LXRGCWorkContext<VM>>::new(self));
759 }
760
761 fn schedule_concurrent_marking_initial_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
762 log::info!("Scheduling concurrent marking initial pause...");
763 self.disable_unnecessary_buckets(scheduler, Pause::InitialMark);
764 self.process_prev_roots(scheduler);
765 scheduler.work_buckets[WorkBucketStage::Unconstrained]
766 .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
767 scheduler.work_buckets[WorkBucketStage::Prepare]
768 .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
769 scheduler.work_buckets[WorkBucketStage::Release]
770 .add(Release::<LXRGCWorkContext<VM>>::new(self));
771 }
772
773 fn schedule_concurrent_marking_final_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
774 log::info!("Scheduling concurrent marking final pause...");
775 self.disable_unnecessary_buckets(scheduler, Pause::FinalMark);
776 self.process_prev_roots(scheduler);
777 scheduler.work_buckets[WorkBucketStage::Unconstrained]
778 .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
779
780 scheduler.work_buckets[WorkBucketStage::Prepare]
781 .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
782 scheduler.work_buckets[WorkBucketStage::Release]
783 .add(Release::<LXRGCWorkContext<VM>>::new(self));
784 }
785
786 fn schedule_emergency_full_heap_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
787 log::info!("Scheduling emergency full-heap collection...");
788 super::DISABLE_LASY_DEC_FOR_CURRENT_GC.store(true, Ordering::SeqCst);
789 self.disable_unnecessary_buckets(scheduler, Pause::Full);
790 self.process_prev_roots(scheduler);
792 scheduler.work_buckets[WorkBucketStage::Unconstrained]
794 .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
795 scheduler.work_buckets[WorkBucketStage::Prepare]
797 .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
798 scheduler.work_buckets[WorkBucketStage::Release]
800 .add(Release::<LXRGCWorkContext<VM>>::new(self));
801 }
802
803 fn process_prev_roots(&self, scheduler: &GCWorkScheduler<VM>) {
804 let prev_roots = self.prev_roots.read().unwrap();
805 let mut work_packets: Vec<Box<dyn GCWork<VM>>> = Vec::with_capacity(prev_roots.len());
806 while let Some(decs) = prev_roots.pop() {
807 work_packets.push(Box::new(ProcessDecs::new(
808 decs,
809 LazySweepingJobsCounter::new_decs(),
810 )))
811 }
812 if work_packets.is_empty() {
813 work_packets.push(Box::new(ProcessDecs::new(
814 vec![],
815 LazySweepingJobsCounter::new_decs(),
816 )));
817 }
818 if super::LAZY_DECREMENTS {
819 scheduler.work_buckets[WorkBucketStage::Concurrent].bulk_add_deferred(work_packets);
820 } else {
821 scheduler.work_buckets[WorkBucketStage::STWRCDecsAndSweep].bulk_add(work_packets);
822 }
823 }
824
825 pub fn current_pause(&self) -> Option<Pause> {
826 self.current_pause.load(Ordering::SeqCst)
827 }
828
829 pub fn previous_pause(&self) -> Option<Pause> {
830 self.previous_pause.load(Ordering::SeqCst)
831 }
832
833 pub fn in_defrag(&self, o: ObjectReference) -> bool {
834 self.immix_space.in_space(o) && Block::in_defrag_block(o)
835 }
836
837 pub fn address_in_defrag(&self, a: Address) -> bool {
838 self.immix_space.address_in_space(a) && Block::address_in_defrag_block(a)
839 }
840
841 pub fn mark(&self, o: ObjectReference) -> bool {
842 if self.immix_space.in_space(o) {
843 self.immix_space.attempt_mark(o)
844 } else if self.common.los.in_space(o) {
845 self.common.los.attempt_mark(o)
846 } else {
847 debug_assert!(o.is_live());
850 false
851 }
852 }
853
854 pub fn is_marked(&self, o: ObjectReference) -> bool {
855 if self.immix_space.in_space(o) {
856 self.immix_space.is_marked(o)
857 } else if self.common.los.in_space(o) {
858 self.common.los.is_marked(o)
859 } else {
860 debug_assert!(o.is_live());
863 true
864 }
865 }
866
867 pub const fn los(&self) -> &LargeObjectSpace<VM> {
868 &self.common.los
869 }
870
871 pub fn is_rc_object(&self, o: ObjectReference) -> bool {
874 self.immix_space.in_space(o) || self.common.los.in_space(o)
875 }
876
877 fn on_lazy_decs_finished(&self, c: LazySweepingJobsCounter) {
878 self.schedule_rc_block_sweeping_tasks(c);
879 }
880
881 fn on_lazy_sweeping_finished(&self) {
882 self.immix_space.flush_page_resource();
883 if !super::LAZY_DECREMENTS {
885 HEAP_AFTER_GC.store(self.get_used_pages(), Ordering::SeqCst);
886 }
887 let pause = match self.current_pause() {
888 Some(p) => p,
889 None => self.previous_pause().unwrap(),
890 };
891 self.decide_next_gc_may_perform_cycle_collection(pause);
892 }
893
894 fn gc_init(&mut self) {
895 self.immix_space.rc_enabled = true;
896 self.common.los.rc_enabled = true;
897 unsafe {
898 let me: &'static Self = &*(self as *const Self);
899 me.block_allocation.init(&me.immix_space, me);
900 me.immix_space.install_hooks(&me.block_allocation);
901 }
902 let mut lazy_sweeping_jobs = LAZY_SWEEPING_JOBS.write();
903 lazy_sweeping_jobs.swap();
904 let lxr_ptr = self as *const Self as usize;
905 lazy_sweeping_jobs.end_of_decs = Some(Box::new(move |c| {
906 let lxr = unsafe { &*(lxr_ptr as *const Self) };
907 lxr.on_lazy_decs_finished(c);
908 }));
909 lazy_sweeping_jobs.end_of_lazy = Some(Box::new(move || {
910 let lxr = unsafe { &*(lxr_ptr as *const Self) };
911 lxr.on_lazy_sweeping_finished();
912 }));
913 }
914
915 fn set_concurrent_marking_state(&self, active: bool) {
916 self.in_concurrent_marking.store(active, Ordering::SeqCst);
917 self.common
918 .los
919 .bump_page_reuse_count
920 .store(active, Ordering::SeqCst);
921 }
922}