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