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: true,
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 true
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 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 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 self.current_pause.store(Some(pause), Ordering::SeqCst);
163 self.perform_cycle_collection
164 .store(pause != Pause::RefCount, Ordering::SeqCst);
165 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 #[cfg(feature = "analysis")]
175 scheduler.work_buckets[WorkBucketStage::Unconstrained].add(GcHookWork);
176 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 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 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 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 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 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 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 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 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 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 }
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 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 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 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 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 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 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 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 self.hint_cycle_gc.store(hint_cycle_gc, Ordering::SeqCst);
568 self.hint_emergency_gc
569 .store(hint_emergency_gc, Ordering::SeqCst);
570 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_in_progress && cm_packets_drained {
615 return Pause::FinalMark;
616 }
617
618 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 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 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 scheduler.work_buckets[WorkBucketStage::Concurrent].set_enabled(true);
652 scheduler.work_buckets[WorkBucketStage::ConcurrentResumable].set_enabled(true);
653 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 self.process_prev_roots(scheduler);
671 scheduler.work_buckets[WorkBucketStage::Unconstrained]
673 .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
674 scheduler.work_buckets[WorkBucketStage::RCProcessIncs].add(FastRCPrepare);
676 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 self.process_prev_roots(scheduler);
712 scheduler.work_buckets[WorkBucketStage::Unconstrained]
714 .add(StopMutators::<LXRGCWorkContext<VM>>::new_with_flush());
715 scheduler.work_buckets[WorkBucketStage::Prepare]
717 .add(Prepare::<LXRGCWorkContext<VM>>::new(self));
718 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 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}