mmtk/plan/generational/
global.rs

1use crate::plan::global::CommonPlan;
2use crate::plan::global::CreateSpecificPlanArgs;
3use crate::plan::ObjectQueue;
4use crate::plan::Plan;
5use crate::policy::copyspace::CopySpace;
6use crate::policy::gc_work::{TraceKind, TRACE_KIND_TRANSITIVE_PIN};
7use crate::policy::space::Space;
8use crate::scheduler::*;
9use crate::util::copy::CopySemantics;
10use crate::util::heap::gc_trigger::SpaceStats;
11use crate::util::heap::VMRequest;
12use crate::util::statistics::counter::EventCounter;
13use crate::util::Address;
14use crate::util::ObjectReference;
15use crate::util::VMWorkerThread;
16use crate::vm::{ObjectModel, VMBinding};
17use std::sync::atomic::AtomicBool;
18use std::sync::atomic::Ordering;
19use std::sync::{Arc, Mutex};
20
21use mmtk_macros::{HasSpaces, PlanTraceObject};
22
23/// Common implementation for generational plans. Each generational plan
24/// should include this type, and forward calls to it where possible.
25#[derive(HasSpaces, PlanTraceObject)]
26pub struct CommonGenPlan<VM: VMBinding> {
27    /// The nursery space.
28    #[space]
29    #[copy_semantics(CopySemantics::PromoteToMature)]
30    pub nursery: CopySpace<VM>,
31    /// The common plan.
32    #[parent]
33    pub common: CommonPlan<VM>,
34    /// Is this GC full heap?
35    pub gc_full_heap: AtomicBool,
36    /// Is next GC full heap?
37    pub next_gc_full_heap: AtomicBool,
38    pub full_heap_gc_count: Arc<Mutex<EventCounter>>,
39}
40
41impl<VM: VMBinding> CommonGenPlan<VM> {
42    pub fn new(mut args: CreateSpecificPlanArgs<VM>) -> Self {
43        let nursery = CopySpace::new(
44            args.get_nursery_space_args("nursery", true, false, VMRequest::discontiguous()),
45            true,
46        );
47        let full_heap_gc_count = args
48            .global_args
49            .stats
50            .new_event_counter("majorGC", true, true);
51        let common = CommonPlan::new(args);
52
53        CommonGenPlan {
54            nursery,
55            common,
56            gc_full_heap: AtomicBool::default(),
57            next_gc_full_heap: AtomicBool::new(false),
58            full_heap_gc_count,
59        }
60    }
61
62    /// Prepare Gen. This should be called by a single thread in GC prepare work.
63    pub fn prepare(&mut self, tls: VMWorkerThread) {
64        let full_heap = !self.is_current_gc_nursery();
65
66        // Only in case of full heap collection we prepare other spaces for collection that are not generational e.g NonMoving.
67        // LOS is generational space and is part of common plan so it is prepared in both cases.
68        if full_heap {
69            self.full_heap_gc_count.lock().unwrap().inc();
70            self.common.prepare(tls, full_heap);
71        } else {
72            self.common.los.prepare(full_heap);
73        }
74
75        self.nursery.prepare(true);
76        self.nursery
77            .set_copy_for_sft_trace(Some(CopySemantics::PromoteToMature));
78    }
79
80    /// Release Gen. This should be called by a single thread in GC release work.
81    pub fn release(&mut self, tls: VMWorkerThread) {
82        let full_heap = !self.is_current_gc_nursery();
83
84        // In case of full heap collection we will release all spaces, even non generational ones like NonMoving.
85        // Only LOS space from common plan is released on nursery.
86        if full_heap {
87            self.common.release(tls, full_heap);
88        } else {
89            self.common.los.release(full_heap);
90        }
91        self.nursery.release();
92    }
93
94    pub fn on_pause_end(&mut self, tls: VMWorkerThread, next_gc_full_heap: bool) {
95        self.set_next_gc_full_heap(next_gc_full_heap);
96        self.common.on_pause_end(tls);
97    }
98
99    /// Independent of how many pages remain in the page budget (a function of heap size), we must
100    /// ensure we never exhaust virtual memory. Therefore we must never let the nursery grow to the
101    /// extent that it can't be copied into the mature space.
102    ///
103    /// Returns `true` if the nursery has grown to the extent that it may not be able to be copied
104    /// into the mature space.
105    fn virtual_memory_exhausted(plan: &dyn GenerationalPlan<VM = VM>) -> bool {
106        ((plan.get_collection_reserved_pages() as f64
107            * VM::VMObjectModel::VM_WORST_CASE_COPY_EXPANSION) as usize)
108            > plan.get_mature_physical_pages_available()
109    }
110
111    /// Check if we need a GC based on the nursery space usage. This method may mark
112    /// the following GC as a full heap GC.
113    pub fn collection_required<P: Plan<VM = VM>>(
114        &self,
115        plan: &P,
116        space_full: bool,
117        space: Option<SpaceStats<VM>>,
118    ) -> bool {
119        let cur_nursery = self.nursery.reserved_pages();
120        let max_nursery = self.common.base.gc_trigger.get_max_nursery_pages();
121        let nursery_full = cur_nursery >= max_nursery;
122        trace!(
123            "nursery_full = {:?} (nursery = {}, max_nursery = {})",
124            nursery_full,
125            cur_nursery,
126            max_nursery,
127        );
128        if nursery_full {
129            return true;
130        }
131        if Self::virtual_memory_exhausted(plan.generational().unwrap()) {
132            return true;
133        }
134
135        // Is the GC triggered by nursery?
136        // - if space is none, it is not. Return false immediately.
137        // - if space is some, we further check its descriptor.
138        let is_triggered_by_nursery =
139            space.is_some_and(|s| s.0.common().descriptor == self.nursery.common().descriptor);
140        // If space is full and the GC is not triggered by nursery, next GC will be full heap GC.
141        if space_full && !is_triggered_by_nursery {
142            self.next_gc_full_heap.store(true, Ordering::SeqCst);
143        }
144
145        self.common.base.collection_required(plan, space_full)
146    }
147
148    pub fn force_full_heap_collection(&self) {
149        self.next_gc_full_heap.store(true, Ordering::SeqCst);
150    }
151
152    pub fn last_collection_full_heap(&self) -> bool {
153        self.gc_full_heap.load(Ordering::Relaxed)
154    }
155
156    /// Check if we should do a full heap GC. It returns true if we should have a full heap GC.
157    /// It also sets gc_full_heap based on the result.
158    pub fn requires_full_heap_collection<P: Plan<VM = VM>>(&self, plan: &P) -> bool {
159        // Allow the same 'true' block for if-else.
160        // The conditions are complex, and it is easier to read if we put them to separate if blocks.
161        #[allow(clippy::if_same_then_else, clippy::needless_bool)]
162        let is_full_heap = if crate::plan::generational::FULL_NURSERY_GC {
163            trace!("full heap: forced full heap");
164            // For barrier overhead measurements, we always do full gc in nursery collections.
165            true
166        } else if self
167            .common
168            .base
169            .global_state
170            .user_triggered_collection
171            .load(Ordering::SeqCst)
172            && *self.common.base.options.full_heap_system_gc
173        {
174            trace!("full heap: user triggered");
175            // User triggered collection, and we force full heap for user triggered collection
176            true
177        } else if self.next_gc_full_heap.load(Ordering::SeqCst)
178            || self
179                .common
180                .base
181                .global_state
182                .cur_collection_attempts
183                .load(Ordering::SeqCst)
184                > 1
185        {
186            trace!(
187                "full heap: next_gc_full_heap = {}, cur_collection_attempts = {}",
188                self.next_gc_full_heap.load(Ordering::SeqCst),
189                self.common
190                    .base
191                    .global_state
192                    .cur_collection_attempts
193                    .load(Ordering::SeqCst)
194            );
195            // Forces full heap collection
196            true
197        } else if Self::virtual_memory_exhausted(plan.generational().unwrap()) {
198            trace!("full heap: virtual memory exhausted");
199            true
200        } else {
201            // We use an Appel-style nursery. The default GC (even for a "heap-full" collection)
202            // for generational GCs should be a nursery GC. A full-heap GC should only happen if
203            // there is not enough memory available for allocating into the nursery (i.e. the
204            // available pages in the nursery are less than the minimum nursery pages), if the
205            // virtual memory has been exhausted, or if it is an emergency GC.
206            false
207        };
208
209        self.gc_full_heap.store(is_full_heap, Ordering::SeqCst);
210
211        info!(
212            "{}",
213            if is_full_heap {
214                "Full heap GC"
215            } else {
216                "Nursery GC"
217            }
218        );
219
220        is_full_heap
221    }
222
223    /// Trace objects for spaces in generational and common plans for a nursery GC.
224    pub fn trace_object_nursery<Q: ObjectQueue, const KIND: TraceKind>(
225        &self,
226        queue: &mut Q,
227        object: ObjectReference,
228        worker: &mut GCWorker<VM>,
229    ) -> ObjectReference {
230        assert!(
231            KIND != TRACE_KIND_TRANSITIVE_PIN,
232            "A copying nursery cannot pin objects"
233        );
234
235        // Evacuate nursery objects
236        if self.nursery.in_space(object) {
237            return self.nursery.trace_object::<Q>(
238                queue,
239                object,
240                Some(CopySemantics::PromoteToMature),
241                worker,
242            );
243        }
244        // We may alloc large object into LOS as nursery objects. Trace them here.
245        if self.common.get_los().in_space(object) {
246            return self.common.get_los().trace_object::<Q>(queue, object);
247        }
248
249        object
250    }
251
252    /// Is the current GC a nursery GC?
253    pub fn is_current_gc_nursery(&self) -> bool {
254        !self.gc_full_heap.load(Ordering::SeqCst)
255    }
256
257    /// Check a plan to see if the next GC should be a full heap GC.
258    ///
259    /// Note that this function should be called after all spaces have been released. This is
260    /// required as we may get incorrect values since this function uses
261    /// [`get_available_pages`](crate::plan::Plan::get_available_pages)
262    /// whose value depends on which spaces have been released.
263    pub fn should_next_gc_be_full_heap(plan: &dyn Plan<VM = VM>) -> bool {
264        let available = plan.get_available_pages();
265        let min_nursery = plan.base().gc_trigger.get_min_nursery_pages();
266        let next_gc_full_heap = available < min_nursery;
267        trace!(
268            "next gc will be full heap? {}, available pages = {}, min nursery = {}",
269            next_gc_full_heap,
270            available,
271            min_nursery
272        );
273        next_gc_full_heap
274    }
275
276    /// Set next_gc_full_heap to the given value.
277    pub fn set_next_gc_full_heap(&self, next_gc_full_heap: bool) {
278        self.next_gc_full_heap
279            .store(next_gc_full_heap, Ordering::SeqCst);
280    }
281
282    /// Get pages reserved for the collection by a generational plan. A generational plan should
283    /// add their own reservation with the value returned by this method.
284    pub fn get_collection_reserved_pages(&self) -> usize {
285        self.nursery.reserved_pages()
286    }
287
288    /// Get pages used by a generational plan. A generational plan should add their own used pages
289    /// with the value returned by this method.
290    pub fn get_used_pages(&self) -> usize {
291        self.nursery.reserved_pages() + self.common.get_used_pages()
292    }
293}
294
295/// This trait includes methods that are specific to generational plans. This trait needs
296/// to be object safe.
297pub trait GenerationalPlan: Plan {
298    /// Is the current GC a nursery GC? If a GC is not a nursery GC, it will be a full heap GC.
299    /// This should only be called during GC.
300    fn is_current_gc_nursery(&self) -> bool;
301
302    /// Is the object in the nursery?
303    fn is_object_in_nursery(&self, object: ObjectReference) -> bool;
304
305    /// Is the address in the nursery? As we only know addresses rather than object references, the
306    /// implementation cannot access per-object metadata. If the plan does not have knowledge whether
307    /// the address is in nursery or not (e.g. mature/nursery objects share the same space and are
308    /// only differentiated by object metadata), the implementation should return `false` as a more
309    /// conservative result.
310    fn is_address_in_nursery(&self, addr: Address) -> bool;
311
312    /// Return the number of pages available for allocation into the mature space.
313    fn get_mature_physical_pages_available(&self) -> usize;
314
315    /// Return the number of used pages in the mature space.
316    fn get_mature_reserved_pages(&self) -> usize;
317
318    /// Return whether last GC is a full GC.
319    fn last_collection_full_heap(&self) -> bool;
320
321    /// Force the next collection to be full heap.
322    fn force_full_heap_collection(&self);
323}
324
325/// This trait is the extension trait for [`GenerationalPlan`] (see Rust's extension trait pattern).
326/// Generally any method should be put to [`GenerationalPlan`] if possible while keeping [`GenerationalPlan`]
327/// object safe. In this case, generic methods will be put to this extension trait.
328pub trait GenerationalPlanExt<VM: VMBinding>: GenerationalPlan<VM = VM> {
329    /// Trace an object in nursery collection. If the object is in nursery, we should call `trace_object`
330    /// on the space. Otherwise, we can just return the object.
331    fn trace_object_nursery<Q: ObjectQueue, const KIND: TraceKind>(
332        &self,
333        queue: &mut Q,
334        object: ObjectReference,
335        worker: &mut GCWorker<VM>,
336    ) -> ObjectReference;
337}
338
339/// Is current GC only collecting objects allocated since last GC? This method can be called
340/// with any plan (generational or not). For non generational plans, it will always return false.
341pub fn is_nursery_gc<VM: VMBinding>(plan: &dyn Plan<VM = VM>) -> bool {
342    plan.generational()
343        .is_some_and(|plan| plan.is_current_gc_nursery())
344}