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 self.previous_pause.store(Some(pause), Ordering::SeqCst);
252 self.current_pause.store(None, Ordering::SeqCst);
253 if pause != Pause::FinalMark {
254 self.should_do_full_gc.store(false, Ordering::SeqCst);
255 } else {
256 }
261
262 if pause != Pause::InitialMark {
265 mmtk.gc_trigger.policy.on_gc_end(mmtk);
266 }
267
268 info!("{:?} end", pause);
269 }
270
271 fn current_gc_may_move_object(&self) -> bool {
272 self.immix_space.in_defrag()
273 }
274
275 fn get_collection_reserved_pages(&self) -> usize {
276 self.immix_space.defrag_headroom_pages()
277 }
278
279 fn get_used_pages(&self) -> usize {
280 self.immix_space.reserved_pages() + self.common.get_used_pages()
281 }
282
283 fn base(&self) -> &BasePlan<VM> {
284 &self.common.base
285 }
286
287 fn base_mut(&mut self) -> &mut BasePlan<Self::VM> {
288 &mut self.common.base
289 }
290
291 fn common(&self) -> &CommonPlan<VM> {
292 &self.common
293 }
294
295 fn on_pause_start(&self, mmtk: &'static MMTK<VM>) {
296 use crate::vm::ActivePlan;
297 let pause = self.current_pause().unwrap();
298 match pause {
299 Pause::Full => {
300 self.set_concurrent_marking_state(false);
301 }
302 Pause::InitialMark => {
303 debug_assert!(
304 !self.concurrent_marking_in_progress(),
305 "prev pause: {:?}",
306 self.previous_pause().unwrap()
307 );
308 }
309 Pause::FinalMark => {
310 debug_assert!(self.concurrent_marking_in_progress());
311 for mutator in <VM as VMBinding>::VMActivePlan::mutators() {
313 mutator.barrier.flush();
314 }
315 self.set_concurrent_marking_state(false);
316 }
317 Pause::RefCount => unreachable!(),
318 }
319
320 if pause != Pause::FinalMark {
323 mmtk.gc_trigger.policy.on_gc_start(mmtk);
324 }
325
326 if self.unfinished_concurrent_marking.load(Ordering::SeqCst) {
328 info!(
329 "Concurrent marking was interrupted. Moving remaining work to STW closure bucket."
330 );
331 assert!(pause == Pause::FinalMark);
334 let leftover_concurrent_work =
335 mmtk.scheduler.work_buckets[WorkBucketStage::Concurrent].drain_all_packets();
336 mmtk.scheduler.work_buckets[WorkBucketStage::FinishConcurrentWork]
337 .bulk_add(leftover_concurrent_work);
338 self.unfinished_concurrent_marking
339 .store(false, Ordering::SeqCst);
340 }
341
342 info!("{:?} start", pause);
343 }
344
345 fn concurrent(&self) -> Option<&dyn ConcurrentPlan<VM = VM>> {
346 Some(self)
347 }
348}
349
350impl<VM: VMBinding> ConcurrentImmix<VM> {
351 pub fn new(args: CreateGeneralPlanArgs<VM>) -> Self {
352 if *args.options.concurrent_immix_disable_concurrent_marking {
353 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.");
354 }
355
356 let spec = crate::util::metadata::extract_side_metadata(&[
357 *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC,
358 ]);
359
360 let mut plan_args = CreateSpecificPlanArgs {
361 global_args: args,
362 constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
363 global_side_metadata_specs: SideMetadataContext::new_global_specs(&spec),
364 };
365
366 let immix_args = ImmixSpaceArgs {
367 mixed_age: false,
368 never_move_objects: false,
369 };
370
371 let scheduler = &plan_args.global_args.scheduler;
374 scheduler.work_buckets[WorkBucketStage::VMRefForwarding].set_enabled(false);
375 scheduler.work_buckets[WorkBucketStage::CalculateForwarding].set_enabled(false);
376 scheduler.work_buckets[WorkBucketStage::SecondRoots].set_enabled(false);
377 scheduler.work_buckets[WorkBucketStage::RefForwarding].set_enabled(false);
378 scheduler.work_buckets[WorkBucketStage::FinalizableForwarding].set_enabled(false);
379 scheduler.work_buckets[WorkBucketStage::Compact].set_enabled(false);
380
381 ConcurrentImmix {
382 immix_space: ImmixSpace::new(
383 plan_args.get_normal_space_args("immix", true, false, VMRequest::discontiguous()),
384 immix_args,
385 ),
386 common: CommonPlan::new(plan_args),
387 last_gc_was_defrag: AtomicBool::new(false),
388 current_pause: Atomic::new(None),
389 previous_pause: Atomic::new(None),
390 should_do_full_gc: AtomicBool::new(false),
391 concurrent_marking_active: AtomicBool::new(false),
392 unfinished_concurrent_marking: AtomicBool::new(false),
393 }
394 }
395
396 fn set_ref_closure_buckets_enabled(&self, do_closure: bool) {
397 let scheduler = &self.common.base.scheduler;
398 scheduler.work_buckets[WorkBucketStage::VMRefClosure].set_enabled(do_closure);
399 scheduler.work_buckets[WorkBucketStage::WeakRefClosure].set_enabled(do_closure);
400 scheduler.work_buckets[WorkBucketStage::FinalRefClosure].set_enabled(do_closure);
401 scheduler.work_buckets[WorkBucketStage::SoftRefClosure].set_enabled(do_closure);
402 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure].set_enabled(do_closure);
403 }
404
405 pub(crate) fn schedule_concurrent_marking_initial_pause(
406 &'static self,
407 scheduler: &GCWorkScheduler<VM>,
408 ) {
409 use crate::scheduler::gc_work::Prepare;
410
411 self.set_ref_closure_buckets_enabled(false);
412
413 scheduler.work_buckets[WorkBucketStage::Unconstrained]
414 .add(StopMutators::<ConcurrentImmixGCWorkContext<VM>>::new());
415 scheduler.work_buckets[WorkBucketStage::Prepare]
416 .add(Prepare::<ConcurrentImmixGCWorkContext<VM>>::new(self));
417 }
418
419 fn schedule_concurrent_marking_final_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
420 self.set_ref_closure_buckets_enabled(true);
421
422 scheduler.work_buckets[WorkBucketStage::Unconstrained]
424 .add(StopMutators::<ConcurrentImmixGCWorkContext<VM>>::new_no_scan_roots());
425
426 scheduler.work_buckets[WorkBucketStage::Release]
427 .add(Release::<ConcurrentImmixGCWorkContext<VM>>::new(self));
428
429 #[cfg(feature = "sanity")]
431 {
432 use crate::util::sanity::sanity_checker::ScheduleSanityGC;
433 scheduler.work_buckets[WorkBucketStage::Final].add(ScheduleSanityGC::<Self>::new(self));
434 }
435
436 type RefTracePolicy<VM> =
439 crate::plan::tracing::PlanTrace<ConcurrentImmix<VM>, TRACE_KIND_FAST>;
440 if !*self.base().options.no_reference_types {
442 use crate::util::reference_processor::{
443 PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing,
444 };
445 scheduler.work_buckets[WorkBucketStage::SoftRefClosure]
446 .add(SoftRefProcessing::<RefTracePolicy<VM>>::new());
447 scheduler.work_buckets[WorkBucketStage::WeakRefClosure]
448 .add(WeakRefProcessing::<VM>::new());
449 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure]
450 .add(PhantomRefProcessing::<VM>::new());
451
452 use crate::util::reference_processor::RefEnqueue;
453 scheduler.work_buckets[WorkBucketStage::Release].add(RefEnqueue::<VM>::new());
454 }
455
456 if !*self.base().options.no_finalizer {
458 use crate::util::finalizable_processor::Finalization;
459 scheduler.work_buckets[WorkBucketStage::FinalRefClosure]
461 .add(Finalization::<RefTracePolicy<VM>>::new());
462 }
463
464 scheduler.work_buckets[WorkBucketStage::VMRefClosure]
468 .set_sentinel(Box::new(VMProcessWeakRefs::<RefTracePolicy<VM>>::new()));
469 }
470
471 pub fn concurrent_marking_in_progress(&self) -> bool {
472 self.concurrent_marking_active.load(Ordering::Acquire)
473 }
474
475 fn set_concurrent_marking_state(&self, active: bool) {
476 use crate::plan::global::HasSpaces;
477
478 let allocate_object_as_live = active;
480 self.for_each_space(&mut |space: &dyn Space<VM>| {
481 space.set_allocate_as_live(allocate_object_as_live);
482 });
483
484 self.concurrent_marking_active
486 .store(active, Ordering::SeqCst);
487
488 }
490
491 pub(super) fn is_concurrent_marking_active(&self) -> bool {
492 self.concurrent_marking_active.load(Ordering::SeqCst)
493 }
494
495 fn previous_pause(&self) -> Option<Pause> {
496 self.previous_pause.load(Ordering::SeqCst)
497 }
498
499 fn concurrent_marking_is_disabled(&self) -> bool {
500 *self
501 .base()
502 .options
503 .concurrent_immix_disable_concurrent_marking
504 }
505}
506
507impl<VM: VMBinding> ConcurrentPlan for ConcurrentImmix<VM> {
508 fn current_pause(&self) -> Option<Pause> {
509 self.current_pause.load(Ordering::SeqCst)
510 }
511
512 fn concurrent_work_in_progress(&self) -> bool {
513 self.concurrent_marking_in_progress()
514 }
515
516 fn on_concurrent_work_interrupted(&self) {
517 assert!(!self.unfinished_concurrent_marking.load(Ordering::SeqCst));
518 self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].set_enabled(false);
523 self.unfinished_concurrent_marking
524 .store(true, Ordering::SeqCst);
525 }
526}