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}
56
57pub const CONCURRENT_IMMIX_CONSTRAINTS: PlanConstraints = PlanConstraints {
59 moves_objects: !cfg!(feature = "immix_non_moving"),
61 max_non_los_default_alloc_bytes: crate::policy::immix::MAX_IMMIX_OBJECT_SIZE,
63 needs_prepare_mutator: true,
64 barrier: crate::BarrierSelector::SATBBarrier,
65 needs_log_bit: true,
66 ..PlanConstraints::default()
67};
68
69impl<VM: VMBinding> Plan for ConcurrentImmix<VM> {
70 fn collection_required(&self, space_full: bool, _space: Option<SpaceStats<Self::VM>>) -> bool {
71 if self.base().collection_required(self, space_full) {
72 self.should_do_full_gc.store(true, Ordering::Release);
73 info!("Triggering full GC");
74 return true;
75 }
76
77 let concurrent_marking_in_progress = self.concurrent_marking_in_progress();
79 if concurrent_marking_in_progress
80 && self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].is_drained()
81 {
82 return true;
86 }
87
88 if self.concurrent_marking_is_disabled() {
92 return false;
93 }
94
95 let threshold = self.get_total_pages() >> 1;
96 let used_pages_after_last_gc = self.common.base.global_state.get_used_pages_after_last_gc();
97 let used_pages_now = self.get_used_pages();
98 let allocated = used_pages_now.saturating_sub(used_pages_after_last_gc);
99 if !concurrent_marking_in_progress && allocated > threshold {
100 info!("Allocated {allocated} pages since last GC ({used_pages_now} - {used_pages_after_last_gc} > {threshold}): Do concurrent marking");
101 debug_assert!(
102 self.common.base.scheduler.work_buckets[WorkBucketStage::Concurrent].is_empty()
103 );
104 debug_assert!(!self.concurrent_marking_in_progress());
105 debug_assert_ne!(self.previous_pause(), Some(Pause::InitialMark));
106 return true;
107 }
108
109 false
110 }
111
112 fn last_collection_was_exhaustive(&self) -> bool {
113 self.immix_space
114 .is_last_gc_exhaustive(self.last_gc_was_defrag.load(Ordering::Relaxed))
115 }
116
117 fn constraints(&self) -> &'static PlanConstraints {
118 &CONCURRENT_IMMIX_CONSTRAINTS
119 }
120
121 fn create_copy_config(&'static self) -> CopyConfig<Self::VM> {
122 use enum_map::enum_map;
123 CopyConfig {
124 copy_mapping: enum_map! {
125 CopySemantics::DefaultCopy => CopySelector::Immix(0),
126 _ => CopySelector::Unused,
127 },
128 space_mapping: vec![(CopySelector::Immix(0), &self.immix_space)],
129 constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
130 }
131 }
132
133 fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
134 if self.concurrent_marking_is_disabled() {
140 self.should_do_full_gc.store(true, Ordering::SeqCst);
141 }
142
143 let pause = if self.concurrent_marking_in_progress() {
144 Pause::FinalMark
148 } else if self.should_do_full_gc.load(Ordering::SeqCst)
149 || self.base().global_state.is_user_triggered_collection()
152 {
153 Pause::Full
154 } else {
155 Pause::InitialMark
156 };
157
158 self.current_pause.store(Some(pause), Ordering::SeqCst);
159
160 probe!(mmtk, concurrent_pause_determined, pause as usize);
161
162 match pause {
163 Pause::Full => {
164 self.set_ref_closure_buckets_enabled(true);
167 crate::plan::immix::global::Immix::schedule_immix_full_heap_collection::<
168 ConcurrentImmix<VM>,
169 ConcurrentImmixSTWGCWorkContext<VM, TRACE_KIND_FAST>,
170 ConcurrentImmixSTWGCWorkContext<VM, TRACE_KIND_DEFRAG>,
171 >(self, &self.immix_space, scheduler);
172 }
173 Pause::InitialMark => self.schedule_concurrent_marking_initial_pause(scheduler),
174 Pause::FinalMark => self.schedule_concurrent_marking_final_pause(scheduler),
175 }
176 }
177
178 fn get_allocator_mapping(&self) -> &'static EnumMap<AllocationSemantics, AllocatorSelector> {
179 &ALLOCATOR_MAPPING
180 }
181
182 fn prepare(&mut self, tls: VMWorkerThread) {
183 let pause = self.current_pause().unwrap();
184 match pause {
185 Pause::Full => {
186 self.common.prepare(tls, true);
187 self.immix_space.prepare(
188 true,
189 Some(StatsForDefrag::new(self)),
190 UnlogBitsOperation::NoOp,
192 );
193 }
194 Pause::InitialMark => {
195 self.immix_space.prepare(
196 true,
197 Some(StatsForDefrag::new(self)),
198 UnlogBitsOperation::BulkSet,
200 );
201
202 self.common.prepare(tls, true);
203 self.common
205 .schedule_unlog_bits_op(UnlogBitsOperation::BulkSet);
206 }
207 Pause::FinalMark => (),
208 }
209 }
210
211 fn release(&mut self, tls: VMWorkerThread) {
212 let pause = self.current_pause().unwrap();
213 match pause {
214 Pause::InitialMark => (),
215 Pause::Full | Pause::FinalMark => {
216 self.immix_space.release(
217 true,
218 UnlogBitsOperation::BulkClear,
220 );
221
222 self.common.release(tls, true);
223
224 if pause == Pause::FinalMark {
225 self.common
227 .schedule_unlog_bits_op(UnlogBitsOperation::BulkClear);
228 } else {
229 }
235 }
236 }
237 }
238
239 fn end_of_pause(&mut self, mmtk: &'static MMTK<VM>, _tls: VMWorkerThread) {
240 self.last_gc_was_defrag
241 .store(self.immix_space.end_of_gc(), Ordering::Relaxed);
242
243 let pause = self.current_pause().unwrap();
244 if pause == Pause::InitialMark {
245 self.set_concurrent_marking_state(true);
246 }
247 self.previous_pause.store(Some(pause), Ordering::SeqCst);
248 self.current_pause.store(None, Ordering::SeqCst);
249 if pause != Pause::FinalMark {
250 self.should_do_full_gc.store(false, Ordering::SeqCst);
251 } else {
252 }
257
258 if pause != Pause::InitialMark {
261 mmtk.gc_trigger.policy.on_gc_end(mmtk);
262 }
263
264 info!("{:?} end", pause);
265 }
266
267 fn current_gc_may_move_object(&self) -> bool {
268 self.immix_space.in_defrag()
269 }
270
271 fn get_collection_reserved_pages(&self) -> usize {
272 self.immix_space.defrag_headroom_pages()
273 }
274
275 fn get_used_pages(&self) -> usize {
276 self.immix_space.reserved_pages() + self.common.get_used_pages()
277 }
278
279 fn base(&self) -> &BasePlan<VM> {
280 &self.common.base
281 }
282
283 fn base_mut(&mut self) -> &mut BasePlan<Self::VM> {
284 &mut self.common.base
285 }
286
287 fn common(&self) -> &CommonPlan<VM> {
288 &self.common
289 }
290
291 fn notify_mutators_paused(&self, mmtk: &'static MMTK<VM>) {
292 use crate::vm::ActivePlan;
293 let pause = self.current_pause().unwrap();
294 match pause {
295 Pause::Full => {
296 self.set_concurrent_marking_state(false);
297 }
298 Pause::InitialMark => {
299 debug_assert!(
300 !self.concurrent_marking_in_progress(),
301 "prev pause: {:?}",
302 self.previous_pause().unwrap()
303 );
304 }
305 Pause::FinalMark => {
306 debug_assert!(self.concurrent_marking_in_progress());
307 for mutator in <VM as VMBinding>::VMActivePlan::mutators() {
309 mutator.barrier.flush();
310 }
311 self.set_concurrent_marking_state(false);
312 }
313 }
314
315 if pause != Pause::FinalMark {
318 mmtk.gc_trigger.policy.on_gc_start(mmtk);
319 }
320
321 info!("{:?} start", pause);
322 }
323
324 fn concurrent(&self) -> Option<&dyn ConcurrentPlan<VM = VM>> {
325 Some(self)
326 }
327}
328
329impl<VM: VMBinding> ConcurrentImmix<VM> {
330 pub fn new(args: CreateGeneralPlanArgs<VM>) -> Self {
331 if *args.options.concurrent_immix_disable_concurrent_marking {
332 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.");
333 }
334
335 let spec = crate::util::metadata::extract_side_metadata(&[
336 *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC,
337 ]);
338
339 let mut plan_args = CreateSpecificPlanArgs {
340 global_args: args,
341 constraints: &CONCURRENT_IMMIX_CONSTRAINTS,
342 global_side_metadata_specs: SideMetadataContext::new_global_specs(&spec),
343 };
344
345 let immix_args = ImmixSpaceArgs {
346 mixed_age: false,
347 never_move_objects: false,
348 };
349
350 let scheduler = &plan_args.global_args.scheduler;
353 scheduler.work_buckets[WorkBucketStage::VMRefForwarding].set_enabled(false);
354 scheduler.work_buckets[WorkBucketStage::CalculateForwarding].set_enabled(false);
355 scheduler.work_buckets[WorkBucketStage::SecondRoots].set_enabled(false);
356 scheduler.work_buckets[WorkBucketStage::RefForwarding].set_enabled(false);
357 scheduler.work_buckets[WorkBucketStage::FinalizableForwarding].set_enabled(false);
358 scheduler.work_buckets[WorkBucketStage::Compact].set_enabled(false);
359
360 ConcurrentImmix {
361 immix_space: ImmixSpace::new(
362 plan_args.get_normal_space_args("immix", true, false, VMRequest::discontiguous()),
363 immix_args,
364 ),
365 common: CommonPlan::new(plan_args),
366 last_gc_was_defrag: AtomicBool::new(false),
367 current_pause: Atomic::new(None),
368 previous_pause: Atomic::new(None),
369 should_do_full_gc: AtomicBool::new(false),
370 concurrent_marking_active: AtomicBool::new(false),
371 }
372 }
373
374 fn set_ref_closure_buckets_enabled(&self, do_closure: bool) {
375 let scheduler = &self.common.base.scheduler;
376 scheduler.work_buckets[WorkBucketStage::VMRefClosure].set_enabled(do_closure);
377 scheduler.work_buckets[WorkBucketStage::WeakRefClosure].set_enabled(do_closure);
378 scheduler.work_buckets[WorkBucketStage::FinalRefClosure].set_enabled(do_closure);
379 scheduler.work_buckets[WorkBucketStage::SoftRefClosure].set_enabled(do_closure);
380 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure].set_enabled(do_closure);
381 }
382
383 pub(crate) fn schedule_concurrent_marking_initial_pause(
384 &'static self,
385 scheduler: &GCWorkScheduler<VM>,
386 ) {
387 use crate::scheduler::gc_work::Prepare;
388
389 self.set_ref_closure_buckets_enabled(false);
390
391 scheduler.work_buckets[WorkBucketStage::Unconstrained]
392 .add(StopMutators::<ConcurrentImmixGCWorkContext<VM>>::new());
393 scheduler.work_buckets[WorkBucketStage::Prepare]
394 .add(Prepare::<ConcurrentImmixGCWorkContext<VM>>::new(self));
395 }
396
397 fn schedule_concurrent_marking_final_pause(&'static self, scheduler: &GCWorkScheduler<VM>) {
398 self.set_ref_closure_buckets_enabled(true);
399
400 scheduler.work_buckets[WorkBucketStage::Unconstrained]
402 .add(StopMutators::<ConcurrentImmixGCWorkContext<VM>>::new_no_scan_roots());
403
404 scheduler.work_buckets[WorkBucketStage::Release]
405 .add(Release::<ConcurrentImmixGCWorkContext<VM>>::new(self));
406
407 #[cfg(feature = "sanity")]
409 {
410 use crate::util::sanity::sanity_checker::ScheduleSanityGC;
411 scheduler.work_buckets[WorkBucketStage::Final].add(ScheduleSanityGC::<Self>::new(self));
412 }
413
414 type RefTracePolicy<VM> =
417 crate::plan::tracing::PlanTrace<ConcurrentImmix<VM>, TRACE_KIND_FAST>;
418 if !*self.base().options.no_reference_types {
420 use crate::util::reference_processor::{
421 PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing,
422 };
423 scheduler.work_buckets[WorkBucketStage::SoftRefClosure]
424 .add(SoftRefProcessing::<RefTracePolicy<VM>>::new());
425 scheduler.work_buckets[WorkBucketStage::WeakRefClosure]
426 .add(WeakRefProcessing::<VM>::new());
427 scheduler.work_buckets[WorkBucketStage::PhantomRefClosure]
428 .add(PhantomRefProcessing::<VM>::new());
429
430 use crate::util::reference_processor::RefEnqueue;
431 scheduler.work_buckets[WorkBucketStage::Release].add(RefEnqueue::<VM>::new());
432 }
433
434 if !*self.base().options.no_finalizer {
436 use crate::util::finalizable_processor::Finalization;
437 scheduler.work_buckets[WorkBucketStage::FinalRefClosure]
439 .add(Finalization::<RefTracePolicy<VM>>::new());
440 }
441
442 scheduler.work_buckets[WorkBucketStage::VMRefClosure]
446 .set_sentinel(Box::new(VMProcessWeakRefs::<RefTracePolicy<VM>>::new()));
447 }
448
449 pub fn concurrent_marking_in_progress(&self) -> bool {
450 self.concurrent_marking_active.load(Ordering::Acquire)
451 }
452
453 fn set_concurrent_marking_state(&self, active: bool) {
454 use crate::plan::global::HasSpaces;
455
456 let allocate_object_as_live = active;
458 self.for_each_space(&mut |space: &dyn Space<VM>| {
459 space.set_allocate_as_live(allocate_object_as_live);
460 });
461
462 self.concurrent_marking_active
464 .store(active, Ordering::SeqCst);
465
466 }
468
469 pub(super) fn is_concurrent_marking_active(&self) -> bool {
470 self.concurrent_marking_active.load(Ordering::SeqCst)
471 }
472
473 fn previous_pause(&self) -> Option<Pause> {
474 self.previous_pause.load(Ordering::SeqCst)
475 }
476
477 fn concurrent_marking_is_disabled(&self) -> bool {
478 *self
479 .base()
480 .options
481 .concurrent_immix_disable_concurrent_marking
482 }
483}
484
485impl<VM: VMBinding> ConcurrentPlan for ConcurrentImmix<VM> {
486 fn current_pause(&self) -> Option<Pause> {
487 self.current_pause.load(Ordering::SeqCst)
488 }
489
490 fn concurrent_work_in_progress(&self) -> bool {
491 self.concurrent_marking_in_progress()
492 }
493}