mmtk/plan/concurrent/immix/
global.rs1use crate::plan::concurrent::global::ConcurrentPlan;
2use crate::plan::concurrent::immix::gc_work::ConcurrentImmixGCWorkContext;
3use crate::plan::concurrent::immix::gc_work::ConcurrentImmixSTWGCWorkContext;
4use crate::plan::concurrent::Pause;
5use crate::plan::global::BasePlan;
6use crate::plan::global::CommonPlan;
7use crate::plan::global::CreateGeneralPlanArgs;
8use crate::plan::global::CreateSpecificPlanArgs;
9use crate::plan::immix::mutator::ALLOCATOR_MAPPING;
10use crate::plan::tracing::gc_work::weakref::VMProcessWeakRefs;
11use crate::plan::AllocationSemantics;
12use crate::plan::Plan;
13use crate::plan::PlanConstraints;
14use crate::policy::immix::defrag::StatsForDefrag;
15use crate::policy::immix::ImmixSpaceArgs;
16use crate::policy::immix::TRACE_KIND_DEFRAG;
17use crate::policy::immix::TRACE_KIND_FAST;
18use crate::policy::space::Space;
19use crate::scheduler::gc_work::Release;
20use crate::scheduler::gc_work::StopMutators;
21use crate::scheduler::*;
22use crate::util::alloc::allocators::AllocatorSelector;
23use crate::util::copy::*;
24use crate::util::heap::gc_trigger::SpaceStats;
25use crate::util::heap::VMRequest;
26use crate::util::metadata::log_bit::UnlogBitsOperation;
27use crate::util::metadata::side_metadata::SideMetadataContext;
28use crate::vm::ObjectModel;
29use crate::vm::VMBinding;
30use crate::MMTK;
31use crate::{policy::immix::ImmixSpace, util::opaque_pointer::VMWorkerThread};
32use std::sync::atomic::AtomicBool;
33
34use atomic::Atomic;
35use atomic::Ordering;
36use enum_map::EnumMap;
37
38use mmtk_macros::{HasSpaces, PlanTraceObject};
39
40#[derive(HasSpaces, PlanTraceObject)]
43pub struct ConcurrentImmix<VM: VMBinding> {
44 #[post_scan]
45 #[space]
46 #[copy_semantics(CopySemantics::DefaultCopy)]
47 pub immix_space: ImmixSpace<VM>,
48 #[parent]
49 pub common: CommonPlan<VM>,
50 last_gc_was_defrag: AtomicBool,
51 current_pause: Atomic<Option<Pause>>,
52 previous_pause: Atomic<Option<Pause>>,
53 should_do_full_gc: AtomicBool,
54 concurrent_marking_active: AtomicBool,
55 unfinished_concurrent_marking: AtomicBool,
56}
57
58pub const CONCURRENT_IMMIX_CONSTRAINTS: PlanConstraints = PlanConstraints {
60 moves_objects: !cfg!(feature = "immix_non_moving"),
62 max_non_los_default_alloc_bytes: crate::policy::immix::MAX_IMMIX_OBJECT_SIZE,
64 needs_prepare_mutator: true,
65 barrier: crate::BarrierSelector::SATBBarrier,
66 needs_log_bit: true,
67 ..PlanConstraints::default()
68};
69
70impl<VM: VMBinding> Plan for ConcurrentImmix<VM> {
71 fn collection_required(&self, space_full: bool, _space: Option<SpaceStats<Self::VM>>) -> bool {
72 if self.base().collection_required(self, space_full) {
73 self.should_do_full_gc.store(true, Ordering::Release);
74 info!("Triggering full GC");
75 return true;
76 }
77
78 let concurrent_marking_in_progress = self.concurrent_marking_in_progress();
80 if concurrent_marking_in_progress
81 && self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].is_drained()
82 {
83 return true;
87 }
88
89 if self.concurrent_marking_is_disabled() {
93 return false;
94 }
95
96 let threshold = self.get_total_pages() >> 1;
97 let used_pages_after_last_gc = self.common.base.global_state.get_used_pages_after_last_gc();
98 let used_pages_now = self.get_used_pages();
99 let allocated = used_pages_now.saturating_sub(used_pages_after_last_gc);
100 if !concurrent_marking_in_progress && allocated > threshold {
101 info!("Allocated {allocated} pages since last GC ({used_pages_now} - {used_pages_after_last_gc} > {threshold}): Do concurrent marking");
102 debug_assert!(
103 self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].is_empty()
104 );
105 debug_assert!(!self.concurrent_marking_in_progress());
106 debug_assert_ne!(self.previous_pause(), Some(Pause::InitialMark));
107 return true;
108 }
109
110 false
111 }
112
113 fn last_collection_was_exhaustive(&self) -> bool {
114 self.immix_space
115 .is_last_gc_exhaustive(self.last_gc_was_defrag.load(Ordering::Relaxed))
116 }
117
118 fn constraints(&self) -> &'static PlanConstraints {
119 &CONCURRENT_IMMIX_CONSTRAINTS
120 }
121
122 fn create_copy_config(&'static self) -> CopyConfig<Self::VM> {
123 use enum_map::enum_map;
124 CopyConfig {
125 copy_mapping: enum_map! {
126 CopySemantics::DefaultCopy => CopySelector::Immix(0),
127 _ => CopySelector::Unused,
128 },
129 space_mapping: vec![(CopySelector::Immix(0), &self.immix_space)],
130 constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
131 }
132 }
133
134 fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
135 if self.concurrent_marking_is_disabled() {
141 self.should_do_full_gc.store(true, Ordering::SeqCst);
142 }
143
144 let pause = if self.concurrent_marking_in_progress() {
145 Pause::FinalMark
149 } else if self.should_do_full_gc.load(Ordering::SeqCst)
150 || self.base().global_state.is_user_triggered_collection()
153 {
154 Pause::Full
155 } else {
156 Pause::InitialMark
157 };
158
159 self.current_pause.store(Some(pause), Ordering::SeqCst);
160
161 probe!(mmtk, concurrent_pause_determined, pause as usize);
162
163 match pause {
164 Pause::Full => {
165 self.set_ref_closure_buckets_enabled(true);
168 crate::plan::immix::global::Immix::schedule_immix_full_heap_collection::<
169 ConcurrentImmix<VM>,
170 ConcurrentImmixSTWGCWorkContext<VM, TRACE_KIND_FAST>,
171 ConcurrentImmixSTWGCWorkContext<VM, TRACE_KIND_DEFRAG>,
172 >(self, &self.immix_space, scheduler);
173 }
174 Pause::InitialMark => self.schedule_concurrent_marking_initial_pause(scheduler),
175 Pause::FinalMark => self.schedule_concurrent_marking_final_pause(scheduler),
176 Pause::RefCount => unreachable!(),
177 }
178 }
179
180 fn get_allocator_mapping(&self) -> &'static EnumMap<AllocationSemantics, AllocatorSelector> {
181 &ALLOCATOR_MAPPING
182 }
183
184 fn prepare(&mut self, tls: VMWorkerThread) {
185 let pause = self.current_pause().unwrap();
186 match pause {
187 Pause::Full => {
188 self.common.prepare(tls, true);
189 self.immix_space.prepare(
190 true,
191 Some(StatsForDefrag::new(self)),
192 UnlogBitsOperation::NoOp,
194 );
195 }
196 Pause::InitialMark => {
197 self.immix_space.prepare(
198 true,
199 Some(StatsForDefrag::new(self)),
200 UnlogBitsOperation::BulkSet,
202 );
203
204 self.common.prepare(tls, true);
205 self.common
207 .schedule_unlog_bits_op(UnlogBitsOperation::BulkSet);
208 }
209 Pause::FinalMark => (),
210 Pause::RefCount => unreachable!(),
211 }
212 }
213
214 fn release(&mut self, tls: VMWorkerThread) {
215 let pause = self.current_pause().unwrap();
216 match pause {
217 Pause::InitialMark => (),
218 Pause::Full | Pause::FinalMark => {
219 self.immix_space.release(
220 true,
221 UnlogBitsOperation::BulkClear,
223 );
224
225 self.common.release(tls, true);
226
227 if pause == Pause::FinalMark {
228 self.common
230 .schedule_unlog_bits_op(UnlogBitsOperation::BulkClear);
231 } else {
232 }
238 }
239 Pause::RefCount => unreachable!(),
240 }
241 }
242
243 fn on_pause_end(&mut self, mmtk: &'static MMTK<VM>, _tls: VMWorkerThread) {
244 self.last_gc_was_defrag
245 .store(self.immix_space.end_of_gc(), Ordering::Relaxed);
246
247 let pause = self.current_pause().unwrap();
248 if pause == Pause::InitialMark {
249 self.set_concurrent_marking_state(true);
250 }
251 if pause == Pause::FinalMark {
252 let concurrent_bucket = &mmtk.scheduler.work_buckets[WorkBucketStage::Concurrent];
255 assert!(
256 concurrent_bucket.is_empty(),
257 "Concurrent bucket is not empty at the end of FinalMark ({} packets)",
258 concurrent_bucket.len()
259 );
260 }
261 self.previous_pause.store(Some(pause), Ordering::SeqCst);
262 self.current_pause.store(None, Ordering::SeqCst);
263 if pause != Pause::FinalMark {
264 self.should_do_full_gc.store(false, Ordering::SeqCst);
265 } else {
266 }
271
272 if pause != Pause::InitialMark {
275 mmtk.gc_trigger.policy.on_gc_end(mmtk);
276 }
277
278 info!("{:?} end", pause);
279 }
280
281 fn current_gc_may_move_object(&self) -> bool {
282 self.immix_space.in_defrag()
283 }
284
285 fn get_collection_reserved_pages(&self) -> usize {
286 self.immix_space.defrag_headroom_pages()
287 }
288
289 fn get_used_pages(&self) -> usize {
290 self.immix_space.reserved_pages() + self.common.get_used_pages()
291 }
292
293 fn base(&self) -> &BasePlan<VM> {
294 &self.common.base
295 }
296
297 fn base_mut(&mut self) -> &mut BasePlan<Self::VM> {
298 &mut self.common.base
299 }
300
301 fn common(&self) -> &CommonPlan<VM> {
302 &self.common
303 }
304
305 fn on_pause_start(&self, mmtk: &'static MMTK<VM>) {
306 use crate::vm::ActivePlan;
307 let pause = self.current_pause().unwrap();
308 match pause {
309 Pause::Full => {
310 self.set_concurrent_marking_state(false);
311 }
312 Pause::InitialMark => {
313 debug_assert!(
314 !self.concurrent_marking_in_progress(),
315 "prev pause: {:?}",
316 self.previous_pause().unwrap()
317 );
318 }
319 Pause::FinalMark => {
320 debug_assert!(self.concurrent_marking_in_progress());
321 for mutator in <VM as VMBinding>::VMActivePlan::mutators() {
323 mutator.barrier.flush();
324 }
325 self.set_concurrent_marking_state(false);
326 }
327 Pause::RefCount => unreachable!(),
328 }
329
330 if pause != Pause::FinalMark {
333 mmtk.gc_trigger.policy.on_gc_start(mmtk);
334 }
335
336 if self.unfinished_concurrent_marking.load(Ordering::SeqCst) {
338 info!(
339 "Concurrent marking was interrupted. Moving remaining work to STW closure bucket."
340 );
341 assert!(pause == Pause::FinalMark);
344 let leftover_concurrent_work =
345 mmtk.scheduler.work_buckets[WorkBucketStage::Concurrent].drain_all_packets();
346 mmtk.scheduler.work_buckets[WorkBucketStage::FinishConcurrentWork]
347 .bulk_add(leftover_concurrent_work);
348 self.unfinished_concurrent_marking
349 .store(false, Ordering::SeqCst);
350 }
351
352 info!("{:?} start", pause);
353 }
354
355 fn concurrent(&self) -> Option<&dyn ConcurrentPlan<VM = VM>> {
356 Some(self)
357 }
358}
359
360impl<VM: VMBinding> ConcurrentImmix<VM> {
361 pub fn new(args: CreateGeneralPlanArgs<VM>) -> Self {
362 if *args.options.concurrent_immix_disable_concurrent_marking {
363 warn!("Option 'concurrent_immix_disable_concurrent_marking' is set to true. Concurrent marking is disabled for ConcurrentImmix. This will make ConcurrentImmix behave exactly like full heap Immix.");
364 }
365
366 let spec = crate::util::metadata::extract_side_metadata(&[
367 *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC,
368 ]);
369
370 let mut plan_args = CreateSpecificPlanArgs {
371 global_args: args,
372 constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
373 global_side_metadata_specs: SideMetadataContext::new_global_specs(&spec),
374 };
375
376 let immix_args = ImmixSpaceArgs {
377 mixed_age: false,
378 never_move_objects: false,
379 };
380
381 let scheduler = &plan_args.global_args.scheduler;
384 scheduler.work_buckets[WorkBucketStage::VMRefForwarding].set_enabled(false);
385 scheduler.work_buckets[WorkBucketStage::CalculateForwarding].set_enabled(false);
386 scheduler.work_buckets[WorkBucketStage::SecondRoots].set_enabled(false);
387 scheduler.work_buckets[WorkBucketStage::RefForwarding].set_enabled(false);
388 scheduler.work_buckets[WorkBucketStage::FinalizableForwarding].set_enabled(false);
389 scheduler.work_buckets[WorkBucketStage::Compact].set_enabled(false);
390
391 ConcurrentImmix {
392 immix_space: ImmixSpace::new(
393 plan_args.get_normal_space_args("immix", true, false, VMRequest::discontiguous()),
394 immix_args,
395 ),
396 common: CommonPlan::new(plan_args),
397 last_gc_was_defrag: AtomicBool::new(false),
398 current_pause: Atomic::new(None),
399 previous_pause: Atomic::new(None),
400 should_do_full_gc: AtomicBool::new(false),
401 concurrent_marking_active: AtomicBool::new(false),
402 unfinished_concurrent_marking: AtomicBool::new(false),
403 }
404 }
405
406 fn set_ref_closure_buckets_enabled(&self, do_closure: bool) {
407 let scheduler = &self.common.base.scheduler;
408 scheduler.work_buckets[WorkBucketStage::VMRefClosure].set_enabled(do_closure);
409 scheduler.work_buckets[WorkBucketStage::WeakRefClosure].set_enabled(do_closure);
410 scheduler.work_buckets[WorkBucketStage::FinalRefClosure].set_enabled(do_closure);
411 scheduler.work_buckets[WorkBucketStage::SoftRefClosure].set_enabled(do_closure);
412 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure].set_enabled(do_closure);
413 }
414
415 pub(crate) fn schedule_concurrent_marking_initial_pause(
416 &'static self,
417 scheduler: &GCWorkScheduler<VM>,
418 ) {
419 use crate::scheduler::gc_work::Prepare;
420
421 self.set_ref_closure_buckets_enabled(false);
422
423 scheduler.work_buckets[WorkBucketStage::Unconstrained]
424 .add(StopMutators::<ConcurrentImmixGCWorkContext<VM>>::new());
425 scheduler.work_buckets[WorkBucketStage::Prepare]
426 .add(Prepare::<ConcurrentImmixGCWorkContext<VM>>::new(self));
427 }
428
429 fn schedule_concurrent_marking_final_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
430 self.set_ref_closure_buckets_enabled(true);
431
432 scheduler.work_buckets[WorkBucketStage::Unconstrained]
434 .add(StopMutators::<ConcurrentImmixGCWorkContext<VM>>::new_no_scan_roots());
435
436 scheduler.work_buckets[WorkBucketStage::Release]
437 .add(Release::<ConcurrentImmixGCWorkContext<VM>>::new(self));
438
439 #[cfg(feature = "sanity")]
441 {
442 use crate::util::sanity::sanity_checker::ScheduleSanityGC;
443 scheduler.work_buckets[WorkBucketStage::Final].add(ScheduleSanityGC::<Self>::new(self));
444 }
445
446 type RefTracePolicy<VM> =
449 crate::plan::tracing::PlanTrace<ConcurrentImmix<VM>, TRACE_KIND_FAST>;
450 if !*self.base().options.no_reference_types {
452 use crate::util::reference_processor::{
453 PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing,
454 };
455 scheduler.work_buckets[WorkBucketStage::SoftRefClosure]
456 .add(SoftRefProcessing::<RefTracePolicy<VM>>::new());
457 scheduler.work_buckets[WorkBucketStage::WeakRefClosure]
458 .add(WeakRefProcessing::<VM>::new());
459 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure]
460 .add(PhantomRefProcessing::<VM>::new());
461
462 use crate::util::reference_processor::RefEnqueue;
463 scheduler.work_buckets[WorkBucketStage::Release].add(RefEnqueue::<VM>::new());
464 }
465
466 if !*self.base().options.no_finalizer {
468 use crate::util::finalizable_processor::Finalization;
469 scheduler.work_buckets[WorkBucketStage::FinalRefClosure]
471 .add(Finalization::<RefTracePolicy<VM>>::new());
472 }
473
474 scheduler.work_buckets[WorkBucketStage::VMRefClosure]
478 .set_sentinel(Box::new(VMProcessWeakRefs::<RefTracePolicy<VM>>::new()));
479 }
480
481 pub fn concurrent_marking_in_progress(&self) -> bool {
482 self.concurrent_marking_active.load(Ordering::Acquire)
483 }
484
485 fn set_concurrent_marking_state(&self, active: bool) {
486 use crate::plan::global::HasSpaces;
487
488 let allocate_object_as_live = active;
490 self.for_each_space(&mut |space: &dyn Space<VM>| {
491 space.set_allocate_as_live(allocate_object_as_live);
492 });
493
494 self.concurrent_marking_active
496 .store(active, Ordering::SeqCst);
497
498 }
500
501 pub(super) fn is_concurrent_marking_active(&self) -> bool {
502 self.concurrent_marking_active.load(Ordering::SeqCst)
503 }
504
505 fn previous_pause(&self) -> Option<Pause> {
506 self.previous_pause.load(Ordering::SeqCst)
507 }
508
509 fn concurrent_marking_is_disabled(&self) -> bool {
510 *self
511 .base()
512 .options
513 .concurrent_immix_disable_concurrent_marking
514 }
515}
516
517impl<VM: VMBinding> ConcurrentPlan for ConcurrentImmix<VM> {
518 fn current_pause(&self) -> Option<Pause> {
519 self.current_pause.load(Ordering::SeqCst)
520 }
521
522 fn concurrent_work_in_progress(&self) -> bool {
523 self.concurrent_marking_in_progress()
524 }
525
526 fn on_concurrent_work_interrupted(&self) {
527 assert!(!self.unfinished_concurrent_marking.load(Ordering::SeqCst));
528 self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].set_enabled(false);
533 self.unfinished_concurrent_marking
534 .store(true, Ordering::SeqCst);
535 }
536
537 fn on_concurrent_work_drained(&self) {
538 }
541}