1use crate::plan::generational::global::GenerationalPlan;
2use crate::plan::global::CommonPlan;
3use crate::plan::global::CreateGeneralPlanArgs;
4use crate::plan::global::CreateSpecificPlanArgs;
5use crate::plan::immix;
6use crate::plan::PlanConstraints;
7use crate::policy::gc_work::TraceKind;
8use crate::policy::gc_work::TRACE_KIND_TRANSITIVE_PIN;
9use crate::policy::immix::defrag::StatsForDefrag;
10use crate::policy::immix::ImmixSpace;
11use crate::policy::immix::TRACE_KIND_FAST;
12use crate::policy::sft::SFT;
13use crate::policy::space::Space;
14use crate::util::copy::CopyConfig;
15use crate::util::copy::CopySelector;
16use crate::util::copy::CopySemantics;
17use crate::util::heap::gc_trigger::SpaceStats;
18use crate::util::metadata::log_bit::UnlogBitsOperation;
19use crate::util::metadata::side_metadata::SideMetadataContext;
20use crate::util::statistics::counter::EventCounter;
21use crate::vm::ObjectModel;
22use crate::vm::VMBinding;
23use crate::Plan;
24use crate::MMTK;
25
26use atomic::Ordering;
27use std::sync::atomic::AtomicBool;
28use std::sync::{Arc, Mutex};
29
30use mmtk_macros::{HasSpaces, PlanTraceObject};
31
32use super::gc_work::StickyImmixMatureGCWorkContext;
33use super::gc_work::StickyImmixNurseryGCWorkContext;
34
35#[derive(HasSpaces, PlanTraceObject)]
36pub struct StickyImmix<VM: VMBinding> {
37 #[parent]
38 immix: immix::Immix<VM>,
39 gc_full_heap: AtomicBool,
40 next_gc_full_heap: AtomicBool,
41 full_heap_gc_count: Arc<Mutex<EventCounter>>,
42}
43
44pub const STICKY_IMMIX_CONSTRAINTS: PlanConstraints = PlanConstraints {
46 moves_objects: !cfg!(feature = "immix_non_moving"),
48 needs_log_bit: true,
49 barrier: crate::plan::BarrierSelector::ObjectBarrier,
50 may_trace_duplicate_edges: true,
52 generational: true,
53 ..immix::IMMIX_CONSTRAINTS
54};
55
56impl<VM: VMBinding> Plan for StickyImmix<VM> {
57 fn constraints(&self) -> &'static crate::plan::PlanConstraints {
58 &STICKY_IMMIX_CONSTRAINTS
59 }
60
61 fn create_copy_config(&'static self) -> CopyConfig<Self::VM> {
62 use enum_map::enum_map;
63 CopyConfig {
64 copy_mapping: enum_map! {
65 CopySemantics::DefaultCopy => CopySelector::Immix(0),
66 _ => CopySelector::Unused,
67 },
68 space_mapping: vec![(CopySelector::Immix(0), &self.immix.immix_space)],
69 constraints: &STICKY_IMMIX_CONSTRAINTS,
70 }
71 }
72
73 fn base(&self) -> &crate::plan::global::BasePlan<Self::VM> {
74 self.immix.base()
75 }
76
77 fn base_mut(&mut self) -> &mut crate::plan::global::BasePlan<Self::VM> {
78 self.immix.base_mut()
79 }
80
81 fn generational(
82 &self,
83 ) -> Option<&dyn crate::plan::generational::global::GenerationalPlan<VM = Self::VM>> {
84 Some(self)
85 }
86
87 fn common(&self) -> &CommonPlan<Self::VM> {
88 self.immix.common()
89 }
90
91 fn schedule_collection(&'static self, scheduler: &crate::scheduler::GCWorkScheduler<Self::VM>) {
92 let is_full_heap = self.requires_full_heap_collection();
93 self.gc_full_heap.store(is_full_heap, Ordering::SeqCst);
94 probe!(mmtk, gen_full_heap, is_full_heap);
95
96 if !is_full_heap {
97 info!("Nursery GC");
98 scheduler.schedule_common_work::<StickyImmixNurseryGCWorkContext<VM>>(self);
100 } else {
101 info!("Full heap GC");
102 use crate::plan::immix::Immix;
103 use crate::policy::immix::TRACE_KIND_DEFRAG;
104 Immix::schedule_immix_full_heap_collection::<
105 StickyImmix<VM>,
106 StickyImmixMatureGCWorkContext<VM, TRACE_KIND_FAST>,
107 StickyImmixMatureGCWorkContext<VM, TRACE_KIND_DEFRAG>,
108 >(self, &self.immix.immix_space, scheduler);
109 }
110 }
111
112 fn get_allocator_mapping(
113 &self,
114 ) -> &'static enum_map::EnumMap<crate::AllocationSemantics, crate::util::alloc::AllocatorSelector>
115 {
116 &super::mutator::ALLOCATOR_MAPPING
117 }
118
119 fn prepare(&mut self, tls: crate::util::VMWorkerThread) {
120 if self.is_current_gc_nursery() {
121 self.immix.immix_space.prepare(
123 false,
124 Some(StatsForDefrag::new(self)),
125 UnlogBitsOperation::NoOp,
128 );
129 self.immix.common.los.prepare(false);
130 } else {
131 self.full_heap_gc_count.lock().unwrap().inc();
132 self.immix.prepare_inner(
133 tls,
134 UnlogBitsOperation::BulkClear,
136 );
137 }
138 }
139
140 fn release(&mut self, tls: crate::util::VMWorkerThread) {
141 if self.is_current_gc_nursery() {
142 self.immix.immix_space.release(
143 false,
144 UnlogBitsOperation::NoOp,
147 );
148 self.immix.common.los.release(false);
149 } else {
150 self.immix.release_inner(
151 tls,
152 UnlogBitsOperation::NoOp,
154 );
155 }
156 }
157
158 fn end_of_pause(
159 &mut self,
160 mmtk: &'static MMTK<VM>,
161 tls: crate::util::opaque_pointer::VMWorkerThread,
162 ) {
163 let next_gc_full_heap =
164 crate::plan::generational::global::CommonGenPlan::should_next_gc_be_full_heap(self);
165 self.next_gc_full_heap
166 .store(next_gc_full_heap, Ordering::Relaxed);
167
168 let was_defrag = self.immix.immix_space.end_of_gc();
169 self.immix
170 .set_last_gc_was_defrag(was_defrag, Ordering::Relaxed);
171
172 self.immix.common.end_of_pause(tls);
173
174 mmtk.gc_trigger.policy.on_gc_end(mmtk);
175 }
176
177 fn collection_required(&self, space_full: bool, space: Option<SpaceStats<Self::VM>>) -> bool {
178 let nursery_full = self.immix.immix_space.get_pages_allocated()
179 > self.base().gc_trigger.get_max_nursery_pages();
180 if space_full
181 && space.is_some()
182 && space.as_ref().unwrap().0.name() != self.immix.immix_space.name()
183 {
184 self.next_gc_full_heap.store(true, Ordering::SeqCst);
185 }
186 self.immix.collection_required(space_full, space) || nursery_full
187 }
188
189 fn last_collection_was_exhaustive(&self) -> bool {
190 self.gc_full_heap.load(Ordering::Relaxed) && self.immix.last_collection_was_exhaustive()
191 }
192
193 fn current_gc_may_move_object(&self) -> bool {
194 if self.is_current_gc_nursery() {
195 self.get_immix_space().prefer_copy_on_nursery_gc()
196 } else {
197 self.get_immix_space().in_defrag()
198 }
199 }
200
201 fn get_collection_reserved_pages(&self) -> usize {
202 self.immix.get_collection_reserved_pages()
203 }
204
205 fn get_used_pages(&self) -> usize {
206 self.immix.get_used_pages()
207 }
208
209 fn sanity_check_object(&self, object: crate::util::ObjectReference) -> bool {
210 if self.is_current_gc_nursery() {
211 if !VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.is_unlogged::<VM>(object, Ordering::SeqCst) {
213 error!("Object {} is not unlogged (all objects that have been traced should be unlogged/mature)", object);
214 return false;
215 }
216
217 if self.immix.immix_space.in_space(object) && !self.immix.immix_space.is_marked(object)
219 {
220 error!(
221 "Object {} is not marked (all objects that have been traced should be marked)",
222 object
223 );
224 return false;
225 } else if self.immix.common.los.in_space(object)
226 && !self.immix.common.los.is_live(object)
227 {
228 error!("LOS Object {} is not marked", object);
229 return false;
230 }
231 }
232 true
233 }
234}
235
236impl<VM: VMBinding> GenerationalPlan for StickyImmix<VM> {
237 fn is_current_gc_nursery(&self) -> bool {
238 !self.gc_full_heap.load(Ordering::SeqCst)
239 }
240
241 fn is_object_in_nursery(&self, object: crate::util::ObjectReference) -> bool {
242 self.immix.immix_space.in_space(object) && !self.immix.immix_space.is_marked(object)
243 }
244
245 fn is_address_in_nursery(&self, _addr: crate::util::Address) -> bool {
252 false
253 }
254
255 fn get_mature_physical_pages_available(&self) -> usize {
256 self.immix.immix_space.available_physical_pages()
257 }
258
259 fn get_mature_reserved_pages(&self) -> usize {
260 self.immix.immix_space.reserved_pages()
261 }
262
263 fn force_full_heap_collection(&self) {
264 self.next_gc_full_heap.store(true, Ordering::SeqCst);
265 }
266
267 fn last_collection_full_heap(&self) -> bool {
268 self.gc_full_heap.load(Ordering::SeqCst)
269 }
270}
271
272impl<VM: VMBinding> crate::plan::generational::global::GenerationalPlanExt<VM> for StickyImmix<VM> {
273 fn trace_object_nursery<Q: crate::ObjectQueue, const KIND: TraceKind>(
274 &self,
275 queue: &mut Q,
276 object: crate::util::ObjectReference,
277 worker: &mut crate::scheduler::GCWorker<VM>,
278 ) -> crate::util::ObjectReference {
279 if self.immix.immix_space.in_space(object) {
280 if !self.is_object_in_nursery(object) {
281 trace!("Immix mature object {}, skip", object);
283 return object;
284 } else {
285 let object = if KIND == TRACE_KIND_TRANSITIVE_PIN || KIND == TRACE_KIND_FAST {
287 trace!(
288 "Immix nursery object {} is being traced without moving",
289 object
290 );
291 self.immix
292 .immix_space
293 .trace_object_without_moving(queue, object)
294 } else if self.immix.immix_space.prefer_copy_on_nursery_gc() {
295 let ret = self.immix.immix_space.trace_object_with_opportunistic_copy(
296 queue,
297 object,
298 CopySemantics::DefaultCopy,
301 worker,
302 true,
303 );
304 trace!(
305 "Immix nursery object {} is being traced with opportunistic copy {}",
306 object,
307 if ret == object {
308 "".to_string()
309 } else {
310 format!("-> new object {}", ret)
311 }
312 );
313 ret
314 } else {
315 trace!(
316 "Immix nursery object {} is being traced without moving",
317 object
318 );
319 self.immix
320 .immix_space
321 .trace_object_without_moving(queue, object)
322 };
323
324 return object;
325 }
326 }
327
328 if self.immix.common().get_los().in_space(object) {
329 return self
330 .immix
331 .common()
332 .get_los()
333 .trace_object::<Q>(queue, object);
334 }
335
336 object
337 }
338}
339
340impl<VM: VMBinding> StickyImmix<VM> {
341 pub fn new(args: CreateGeneralPlanArgs<VM>) -> Self {
342 let full_heap_gc_count = args.stats.new_event_counter("majorGC", true, true);
343 let plan_args = CreateSpecificPlanArgs {
344 global_args: args,
345 constraints: &STICKY_IMMIX_CONSTRAINTS,
346 global_side_metadata_specs: SideMetadataContext::new_global_specs(
347 &crate::plan::generational::new_generational_global_metadata_specs::<VM>(),
348 ),
349 };
350
351 let immix = immix::Immix::new_with_args(
352 plan_args,
353 crate::policy::immix::ImmixSpaceArgs {
354 mixed_age: true,
356 never_move_objects: false,
357 },
358 );
359 Self {
360 immix,
361 gc_full_heap: AtomicBool::new(false),
362 next_gc_full_heap: AtomicBool::new(false),
363 full_heap_gc_count,
364 }
365 }
366
367 fn requires_full_heap_collection(&self) -> bool {
368 #[allow(clippy::if_same_then_else, clippy::needless_bool)]
370 if crate::plan::generational::FULL_NURSERY_GC {
371 trace!("full heap: forced full heap");
372 true
374 } else if self
375 .immix
376 .common
377 .base
378 .global_state
379 .user_triggered_collection
380 .load(Ordering::SeqCst)
381 && *self.immix.common.base.options.full_heap_system_gc
382 {
383 true
385 } else if self.next_gc_full_heap.load(Ordering::SeqCst)
386 || self
387 .immix
388 .common
389 .base
390 .global_state
391 .cur_collection_attempts
392 .load(Ordering::SeqCst)
393 > 1
394 {
395 true
397 } else {
398 false
399 }
400 }
401
402 pub fn get_immix_space(&self) -> &ImmixSpace<VM> {
403 &self.immix.immix_space
404 }
405}