mmtk/policy/
largeobjectspace.rs

1use atomic::Ordering;
2
3use crate::plan::tracing::{ObjectQueue, OptionObjectQueue};
4use crate::policy::sft::GCWorkerMutRef;
5use crate::policy::sft::SFT;
6use crate::policy::space::{CommonSpace, Space};
7use crate::util::alloc::allocator::AllocationOptions;
8use crate::util::constants::BYTES_IN_PAGE;
9use crate::util::heap::{FreeListPageResource, PageResource};
10use crate::util::metadata;
11use crate::util::metadata::side_metadata::spec_defs::LOS_PAGE_REUSE_COUNT;
12use crate::util::metadata::MetadataSpec;
13use crate::util::object_enum::ClosureObjectEnumerator;
14use crate::util::object_enum::ObjectEnumerator;
15use crate::util::opaque_pointer::*;
16use crate::util::rc::RefCountHelper;
17use crate::util::treadmill::TreadMill;
18use crate::util::{Address, ObjectReference};
19use crate::vm::ObjectModel;
20use crate::vm::VMBinding;
21use std::sync::atomic::AtomicBool;
22use std::sync::atomic::AtomicUsize;
23
24#[allow(unused)]
25const PAGE_MASK: usize = !(BYTES_IN_PAGE - 1);
26const MARK_BIT: u8 = 0b01;
27const NURSERY_BIT: u8 = 0b10;
28const LOS_BIT_MASK: u8 = 0b11;
29
30/// This type implements a policy for large objects. Each instance corresponds
31/// to one Treadmill space.
32pub struct LargeObjectSpace<VM: VMBinding> {
33    common: CommonSpace<VM>,
34    pr: FreeListPageResource<VM>,
35    mark_state: u8,
36    in_nursery_gc: bool,
37    treadmill: TreadMill,
38    clear_log_bit_on_sweep: bool,
39    pub num_pages_released_lazy: AtomicUsize,
40    pub rc_enabled: bool,
41    pub(crate) rc: RefCountHelper<VM>,
42    pub is_end_of_satb_or_full_gc: bool,
43    /// Whether newly allocated LOS objects should bump `LOS_PAGE_REUSE_COUNT` on their pages, so
44    /// remembered-set entries recorded against a page's previous occupant are invalidated. Only
45    /// needed while concurrent marking can be validating a remembered set; set/cleared by the
46    /// owning plan (currently only LXR) as concurrent marking starts/ends.
47    pub(crate) bump_page_reuse_count: AtomicBool,
48}
49
50impl<VM: VMBinding> SFT for LargeObjectSpace<VM> {
51    fn name(&self) -> &'static str {
52        self.get_name()
53    }
54    fn is_live(&self, object: ObjectReference) -> bool {
55        if self.rc_enabled {
56            if self.is_end_of_satb_or_full_gc {
57                return self.is_marked(object) && self.rc.count(object) > 0;
58            }
59            return self.rc.count(object) > 0;
60        }
61        self.test_mark_bit(object, self.mark_state)
62    }
63    fn is_reachable(&self, object: ObjectReference) -> bool {
64        if self.rc_enabled {
65            self.test_mark_bit(object, self.mark_state) && self.rc.count(object) > 0
66        } else {
67            self.is_live(object)
68        }
69    }
70    #[cfg(feature = "object_pinning")]
71    fn pin_object(&self, _object: ObjectReference) -> bool {
72        false
73    }
74    #[cfg(feature = "object_pinning")]
75    fn unpin_object(&self, _object: ObjectReference) -> bool {
76        false
77    }
78    #[cfg(feature = "object_pinning")]
79    fn is_object_pinned(&self, _object: ObjectReference) -> bool {
80        true
81    }
82    fn is_movable(&self) -> bool {
83        false
84    }
85    #[cfg(feature = "sanity")]
86    fn is_sane(&self) -> bool {
87        true
88    }
89
90    fn initialize_object_metadata(&self, object: ObjectReference, bytes: usize) {
91        // VO bit: Set for all objects.
92        #[cfg(feature = "vo_bit")]
93        crate::util::metadata::vo_bit::set_vo_bit(object);
94        #[cfg(all(feature = "vo_bit", debug_assertions))]
95        {
96            use crate::util::constants::LOG_BYTES_IN_PAGE;
97            let vo_addr = object.to_raw_address();
98            let offset_from_page_start = vo_addr & ((1 << LOG_BYTES_IN_PAGE) - 1) as usize;
99            debug_assert!(
100                offset_from_page_start < crate::util::metadata::vo_bit::VO_BIT_WORD_TO_REGION,
101                "The raw address of ObjectReference is not in the first 512 bytes of a page. The internal pointer searching for LOS won't work."
102            );
103        }
104
105        if self.rc_enabled {
106            // Add to treadmill nursery
107            self.treadmill.add_to_treadmill(object, true);
108            // Initialize mark bit
109            self.test_and_mark(object, self.mark_state);
110            // Initialize metadata
111            if self.bump_page_reuse_count.load(Ordering::Acquire) {
112                for off in (0..bytes).step_by(BYTES_IN_PAGE) {
113                    let a = object.to_raw_address() + off;
114                    let count = LOS_PAGE_REUSE_COUNT.load_atomic::<u8>(a, Ordering::SeqCst);
115                    let new_count = if count == u8::MAX { 0 } else { count + 1 };
116                    LOS_PAGE_REUSE_COUNT.store_atomic::<u8>(a, new_count, Ordering::SeqCst);
117                }
118            }
119            return;
120        }
121
122        let allocate_as_live = self.should_allocate_as_live();
123        let into_nursery = !allocate_as_live;
124
125        // mark/nursery bits: Set mark state plus optionally nursery bit.
126        {
127            let mark_nursery_state = if into_nursery {
128                self.mark_state | NURSERY_BIT
129            } else {
130                self.mark_state
131            };
132
133            VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.store_atomic::<VM, u8>(
134                object,
135                mark_nursery_state,
136                None,
137                Ordering::SeqCst,
138            );
139        }
140
141        // global unlog bit: Set if `unlog_allocated_object`.  Ensure it is not set otherwise.
142        if self.common.unlog_allocated_object {
143            debug_assert!(self.common.needs_log_bit);
144            debug_assert!(
145                !allocate_as_live,
146                "Currently only ConcurrentImmix can allocate as live, and it doesn't unlog allocated objects in LOS."
147            );
148
149            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
150        } else {
151            #[cfg(debug_assertions)]
152            if self.common.needs_log_bit {
153                debug_assert_eq!(
154                    VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.load_atomic::<VM, u8>(
155                        object,
156                        None,
157                        Ordering::Acquire
158                    ),
159                    0
160                );
161            }
162        }
163
164        // Add to the treadmill.  Nursery and mature objects need to be added to different sets.
165        self.treadmill.add_to_treadmill(object, into_nursery);
166    }
167
168    #[cfg(feature = "vo_bit")]
169    fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
170        crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
171    }
172    #[cfg(feature = "vo_bit")]
173    fn find_object_from_internal_pointer(
174        &self,
175        ptr: Address,
176        max_search_bytes: usize,
177    ) -> Option<ObjectReference> {
178        use crate::{util::metadata::vo_bit, MMAPPER};
179
180        let mmap_granularity = MMAPPER.granularity();
181
182        // We need to check if metadata address is mapped or not.  But we make use of the granularity of
183        // the `Mmapper` to reduce the number of checks.  This records the start of a grain that is
184        // tested to be mapped.
185        let mut mapped_grain = Address::MAX;
186
187        // For large object space, it is a bit special. We only need to check VO bit for each page.
188        let mut cur_page = ptr.align_down(BYTES_IN_PAGE);
189        let low_page = ptr
190            .saturating_sub(max_search_bytes)
191            .align_down(BYTES_IN_PAGE);
192        while cur_page >= low_page {
193            if cur_page < mapped_grain {
194                if !cur_page.is_mapped() {
195                    // If the page start is not mapped, there can't be an object in it.
196                    return None;
197                }
198                // This is mapped. No need to check for this chunk.
199                mapped_grain = cur_page.align_down(mmap_granularity);
200            }
201            // For performance, we only check the first word which maps to the first 512 bytes in the page.
202            // In almost all the cases, it should be sufficient.
203            // However, if the raw address of ObjectReference is not in the first 512 bytes, this won't work.
204            // We assert this when we set VO bit for LOS.
205            if vo_bit::get_raw_vo_bit_word(cur_page) != 0 {
206                // Find the exact address that has vo bit set
207                for offset in 0..vo_bit::VO_BIT_WORD_TO_REGION {
208                    let addr = cur_page + offset;
209                    if unsafe { vo_bit::is_vo_addr(addr) } {
210                        return vo_bit::is_internal_ptr_from_vo_bit::<VM>(addr, ptr);
211                    }
212                }
213                unreachable!(
214                    "We found vo bit in the raw word, but we cannot find the exact address"
215                );
216            }
217
218            cur_page -= BYTES_IN_PAGE;
219        }
220        None
221    }
222    fn sft_trace_object(
223        &self,
224        queue: &mut OptionObjectQueue,
225        object: ObjectReference,
226        _worker: GCWorkerMutRef,
227    ) -> ObjectReference {
228        self.trace_object(queue, object)
229    }
230
231    fn debug_print_object_info(&self, object: ObjectReference) {
232        println!("marked = {}", self.test_mark_bit(object, self.mark_state));
233        println!("nursery = {}", self.is_in_nursery(object));
234        self.common.debug_print_object_global_info(object);
235    }
236}
237
238impl<VM: VMBinding> Space<VM> for LargeObjectSpace<VM> {
239    fn as_space(&self) -> &dyn Space<VM> {
240        self
241    }
242    fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
243        self
244    }
245    fn get_page_resource(&self) -> &dyn PageResource<VM> {
246        &self.pr
247    }
248    fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
249        Some(&mut self.pr)
250    }
251
252    fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap) {
253        self.common().initialize_sft(self.as_sft(), sft_map)
254    }
255
256    fn common(&self) -> &CommonSpace<VM> {
257        &self.common
258    }
259
260    fn release_multiple_pages(&mut self, start: Address) {
261        self.pr.release_pages(start);
262    }
263
264    fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
265        // `MMTK::enumerate_objects` is not allowed during GC, so the collection nursery and the
266        // from space must be empty.  In `ConcurrentImmix`, mutators may run during GC and call
267        // `MMTK::enumerate_objects`.  It has undefined behavior according to the current API, so
268        // the assertion failure is expected.
269        assert!(
270            self.treadmill.is_collect_nursery_empty(),
271            "Collection nursery is not empty"
272        );
273        assert!(
274            self.treadmill.is_from_space_empty(),
275            "From-space is not empty"
276        );
277
278        // Visit objects in the allocation nursery and the to-space, which contain young and old
279        // objects, respectively, during mutator time.
280        self.treadmill.enumerate_objects(enumerator, false);
281    }
282
283    fn clear_side_log_bits(&self) {
284        let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
285            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
286        });
287        // Visit all objects.  It can be ordered arbitrarily with `Self::Release` which sweeps dead
288        // objects (removing them from the treadmill) and clears their unlog bits, too.
289        self.treadmill.enumerate_objects(&mut enumerator, true);
290    }
291
292    fn set_side_log_bits(&self) {
293        let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
294            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
295        });
296        // Visit all objects.
297        self.treadmill.enumerate_objects(&mut enumerator, true);
298    }
299}
300
301use crate::scheduler::GCWorker;
302use crate::util::copy::CopySemantics;
303
304impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LargeObjectSpace<VM> {
305    fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
306        &self,
307        queue: &mut Q,
308        object: ObjectReference,
309        _copy: Option<CopySemantics>,
310        _worker: &mut GCWorker<VM>,
311    ) -> ObjectReference {
312        self.trace_object(queue, object)
313    }
314    fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
315        false
316    }
317}
318
319impl<VM: VMBinding> LargeObjectSpace<VM> {
320    pub fn new(
321        args: crate::policy::space::PlanCreateSpaceArgs<VM>,
322        protect_memory_on_release: bool,
323        clear_log_bit_on_sweep: bool,
324    ) -> Self {
325        let is_discontiguous = args.vmrequest.is_discontiguous();
326        let vm_map = args.vm_map;
327        let rc_enabled = args.constraints.rc_enabled;
328        let specs = if rc_enabled {
329            vec![
330                *VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC,
331                MetadataSpec::OnSide(LOS_PAGE_REUSE_COUNT),
332            ]
333        } else {
334            vec![*VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC]
335        };
336        let policy_args =
337            args.into_policy_args(false, false, metadata::extract_side_metadata(&specs));
338        let common = CommonSpace::new(policy_args);
339        let mut pr = if is_discontiguous {
340            FreeListPageResource::new_discontiguous(vm_map)
341        } else {
342            FreeListPageResource::new_contiguous(common.start, common.extent, vm_map)
343        };
344        pr.protect_memory_on_release = if protect_memory_on_release {
345            Some(common.mmap_protection())
346        } else {
347            None
348        };
349        LargeObjectSpace {
350            pr,
351            common,
352            mark_state: 0,
353            in_nursery_gc: false,
354            treadmill: TreadMill::new(),
355            clear_log_bit_on_sweep,
356            num_pages_released_lazy: Default::default(),
357            rc_enabled: false,
358            rc: RefCountHelper::NEW,
359            is_end_of_satb_or_full_gc: false,
360            bump_page_reuse_count: AtomicBool::new(false),
361        }
362    }
363
364    fn release_object(&self, object: ObjectReference) -> usize {
365        let start = get_super_page(object.to_object_start::<VM>());
366        #[cfg(feature = "vo_bit")]
367        crate::util::metadata::vo_bit::unset_vo_bit(object);
368        if self.rc_enabled {
369            debug_assert_eq!(self.rc.count(object), 0);
370            let pages = self.pr.get_pages(start);
371            // TODO: Currently this code path assumes the collector is LXR and it uses field log bit.
372            // When we can use object log bit for LXR, we should merge with `sweep_large_pages`
373            // and clear object log bit instead.
374            VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
375                .as_spec()
376                .extract_side_spec()
377                .bzero_metadata(start, pages * BYTES_IN_PAGE);
378        }
379        self.pr.release_pages(start)
380    }
381
382    pub fn release_rc_nursery_objects(&self) {
383        debug_assert!(self.rc_enabled);
384        // promote nursery objects or release dead nursery
385        for o in self.treadmill.collect_alloc_nursery() {
386            if self.rc.count(o) == 0 {
387                self.release_object(o);
388            } else {
389                self.treadmill.add_to_treadmill(o, false);
390            }
391        }
392    }
393
394    pub fn prepare(&mut self, full_heap: bool) {
395        if full_heap {
396            self.mark_state = MARK_BIT - self.mark_state;
397        }
398        self.num_pages_released_lazy.store(0, Ordering::Relaxed);
399        if self.rc_enabled {
400            return;
401        }
402        self.treadmill.flip(full_heap);
403        self.in_nursery_gc = !full_heap;
404    }
405
406    pub fn release(&mut self, full_heap: bool) {
407        if self.rc_enabled {
408            self.release_rc_nursery_objects();
409            return;
410        }
411        // We swapped the allocation nursery and the collection nursery when GC starts, and we don't
412        // add objects to the allocation nursery during GC.  It should have remained empty during
413        // the whole GC.
414        debug_assert!(self.treadmill.is_alloc_nursery_empty());
415
416        self.sweep_large_pages(true);
417        debug_assert!(self.treadmill.is_collect_nursery_empty());
418        if full_heap {
419            self.sweep_large_pages(false);
420            debug_assert!(self.treadmill.is_from_space_empty());
421        }
422    }
423
424    // Allow nested-if for this function to make it clear that test_and_mark() is only executed
425    // for the outer condition is met.
426    #[allow(clippy::collapsible_if)]
427    pub fn trace_object<Q: ObjectQueue>(
428        &self,
429        queue: &mut Q,
430        object: ObjectReference,
431    ) -> ObjectReference {
432        #[cfg(feature = "vo_bit")]
433        debug_assert!(
434            crate::util::metadata::vo_bit::is_vo_bit_set(object),
435            "{:x}: VO bit not set",
436            object
437        );
438        if self.rc_enabled {
439            if self.test_and_mark(object, self.mark_state) {
440                queue.enqueue(object);
441            }
442            return object;
443        }
444        let nursery_object = self.is_in_nursery(object);
445        trace!(
446            "LOS object {} {} a nursery object",
447            object,
448            if nursery_object { "is" } else { "is not" }
449        );
450        if !self.in_nursery_gc || nursery_object {
451            // Note that test_and_mark() has side effects of
452            // clearing nursery bit/moving objects out of logical nursery
453            if self.test_and_mark(object, self.mark_state) {
454                trace!("LOS object {} is being marked now", object);
455                self.treadmill.copy(object, nursery_object);
456                // We just moved the object out of the logical nursery, mark it as unlogged.
457                // We also unlog mature objects as their unlog bit may have been unset before the
458                // full-heap GC
459                if self.common.unlog_traced_object {
460                    VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
461                        .mark_as_unlogged::<VM>(object, Ordering::SeqCst);
462                }
463                queue.enqueue(object);
464            } else {
465                trace!(
466                    "LOS object {} is not being marked now, it was marked before",
467                    object
468                );
469            }
470        }
471        object
472    }
473
474    fn sweep_large_pages(&mut self, sweep_nursery: bool) {
475        let sweep = |object: ObjectReference| {
476            // Clear log bits for dead objects to prevent a new nursery object having the unlog bit set
477            // TODO: This code path assumes the log bit is object log bit instead of field log bit.
478            // When generational plans and ConcurrentImmix support field log bit,
479            // we can push this clean-up operation into `Self::release_object`.
480            if self.clear_log_bit_on_sweep {
481                VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
482            }
483            self.release_object(object);
484        };
485        if sweep_nursery {
486            for object in self.treadmill.collect_nursery() {
487                sweep(object);
488            }
489        } else {
490            for object in self.treadmill.collect_mature() {
491                sweep(object)
492            }
493        }
494    }
495
496    /// Enumerate objects in the to-space.  It is a workaround for OVC which currently needs
497    /// to enumerate reachable objects for during reference forwarding.
498    pub(crate) fn enumerate_to_space_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
499        // This function is intended to enumerate objects in the to-space.
500        // The alloc nursery should have remained empty during the GC.
501        debug_assert!(self.treadmill.is_alloc_nursery_empty());
502        // We only need to visit the to_space, which contains all objects determined to be live.
503        self.treadmill.enumerate_objects(enumerator, false);
504    }
505
506    /// Allocate an object
507    pub fn allocate_pages(
508        &self,
509        tls: VMThread,
510        pages: usize,
511        alloc_options: AllocationOptions,
512    ) -> Address {
513        self.acquire(tls, pages, alloc_options)
514    }
515
516    pub fn attempt_mark(&self, object: ObjectReference) -> bool {
517        self.test_and_mark(object, self.mark_state)
518    }
519
520    pub fn rc_free(&self, o: ObjectReference) {
521        if self.treadmill.remove_mature(o) {
522            let pages = self.release_object(o);
523            self.num_pages_released_lazy
524                .fetch_add(pages, Ordering::Relaxed);
525        }
526    }
527
528    /// Test if the object's mark bit is the same as the given value. If it is not the same,
529    /// the method will attemp to mark the object and clear its nursery bit. If the attempt
530    /// succeeds, the method will return true, meaning the object is marked by this invocation.
531    /// Otherwise, it returns false.
532    fn test_and_mark(&self, object: ObjectReference, value: u8) -> bool {
533        loop {
534            let mask = if self.rc_enabled {
535                MARK_BIT
536            } else if self.in_nursery_gc {
537                LOS_BIT_MASK
538            } else {
539                MARK_BIT
540            };
541            let old_value = VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
542                object,
543                None,
544                Ordering::SeqCst,
545            );
546            let mark_bit = old_value & mask;
547            if mark_bit == value {
548                return false;
549            }
550            // using LOS_BIT_MASK have side effects of clearing nursery bit
551            if VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC
552                .compare_exchange_metadata::<VM, u8>(
553                    object,
554                    old_value,
555                    old_value & !LOS_BIT_MASK | value,
556                    None,
557                    Ordering::SeqCst,
558                    Ordering::SeqCst,
559                )
560                .is_ok()
561            {
562                break;
563            }
564        }
565        true
566    }
567
568    fn test_mark_bit(&self, object: ObjectReference, value: u8) -> bool {
569        VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
570            object,
571            None,
572            Ordering::SeqCst,
573        ) & MARK_BIT
574            == value
575    }
576
577    /// Check if a given object is in nursery
578    fn is_in_nursery(&self, object: ObjectReference) -> bool {
579        VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
580            object,
581            None,
582            Ordering::Relaxed,
583        ) & NURSERY_BIT
584            == NURSERY_BIT
585    }
586
587    pub fn sweep_rc_mature_objects_after_satb(&self, is_live: &impl Fn(ObjectReference) -> bool) {
588        self.treadmill.retain_mature(|o| {
589            if !is_live(*o) {
590                self.rc.set(*o, 0);
591                let pages = self.release_object(*o);
592                self.num_pages_released_lazy
593                    .fetch_add(pages, Ordering::Relaxed);
594                false
595            } else {
596                true
597            }
598        });
599    }
600
601    pub fn is_marked(&self, object: ObjectReference) -> bool {
602        self.test_mark_bit(object, self.mark_state)
603    }
604}
605
606fn get_super_page(cell: Address) -> Address {
607    cell.align_down(BYTES_IN_PAGE)
608}