mmtk/plan/sticky/immix/
global.rs

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
44/// The plan constraints for the sticky immix plan.
45pub const STICKY_IMMIX_CONSTRAINTS: PlanConstraints = PlanConstraints {
46    // If we disable moving in Immix, this is a non-moving plan.
47    moves_objects: !cfg!(feature = "immix_non_moving"),
48    needs_log_bit: true,
49    barrier: crate::plan::BarrierSelector::ObjectBarrier,
50    // We may trace duplicate edges in sticky immix (or any plan that uses object remembering barrier). See https://github.com/mmtk/mmtk-core/issues/743.
51    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            // nursery GC -- we schedule it
99            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            // Prepare both large object space and immix space
122            self.immix.immix_space.prepare(
123                false,
124                Some(StatsForDefrag::new(self)),
125                // We don't do anything special to unlog bits during nursery GC
126                // because ProcessModBuf will set the unlog bits back.
127                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                // We will reconstruct unlog bits during tracing.
135                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                // We don't do anything special to unlog bits during nursery GC
145                // because ProcessModBuf has set the unlog bits back.
146                UnlogBitsOperation::NoOp,
147            );
148            self.immix.common.los.release(false);
149        } else {
150            self.immix.release_inner(
151                tls,
152                // We reconstructred unlog bits during tracing.  Keep them.
153                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            // Every reachable object should be logged
212            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            // Every reachable object should be marked
218            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    // This check is used for memory slice copying barrier, where we only know addresses instead of objects.
246    // As sticky immix needs object metadata to know if an object is an nursery object or not, we cannot really tell
247    // whether an address is in nursery or not. In this case, we just return false -- this is a conservative return value
248    // for the memory slice copying barrier. It means we will treat the object as if it is in mature space, and will
249    // push it to the remembered set.
250    // FIXME: this will remember excessive objects, and can cause serious slowdown in some cases.
251    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                // Mature object
282                trace!("Immix mature object {}, skip", object);
283                return object;
284            } else {
285                // Nursery object
286                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                        // We just use default copy here. We have set args for ImmixSpace to deal with unlogged bit,
299                        // and we do not need to use CopySemantics::PromoteToMature.
300                        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                // In StickyImmix, both young and old objects are allocated in the ImmixSpace.
355                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        // Separate each condition so the code is clear
369        #[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            // For barrier overhead measurements, we always do full gc in nursery collections.
373            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            // User triggered collection, and we force full heap for user triggered collection
384            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            // Forces full heap collection
396            true
397        } else {
398            false
399        }
400    }
401
402    pub fn get_immix_space(&self) -> &ImmixSpace<VM> {
403        &self.immix.immix_space
404    }
405}