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