mmtk/policy/
lockfreeimmortalspace.rs

1use atomic::Atomic;
2
3use std::sync::atomic::Ordering;
4use std::sync::Arc;
5
6use crate::plan::tracing::{ObjectQueue, OptionObjectQueue};
7use crate::policy::sft::GCWorkerMutRef;
8use crate::policy::sft::SFT;
9use crate::policy::space::{CommonSpace, Space};
10use crate::scheduler::GCWorker;
11use crate::util::address::Address;
12use crate::util::alloc::allocator::AllocationOptions;
13use crate::util::conversions;
14use crate::util::copy::CopySemantics;
15use crate::util::heap::gc_trigger::GCTrigger;
16use crate::util::heap::layout::vm_layout::vm_layout;
17use crate::util::heap::PageResource;
18use crate::util::heap::VMRequest;
19use crate::util::metadata::side_metadata::SideMetadataContext;
20use crate::util::metadata::side_metadata::SideMetadataSanity;
21use crate::util::object_enum::ObjectEnumerator;
22use crate::util::opaque_pointer::*;
23use crate::util::os::*;
24use crate::util::ObjectReference;
25use crate::vm::VMBinding;
26
27/// This type implements a lock free version of the immortal collection
28/// policy. This is close to the OpenJDK's epsilon GC.
29/// Different from the normal ImmortalSpace, this version should only
30/// be used by NoGC plan, and it now uses the whole heap range.
31// FIXME: It is wrong that the space uses the whole heap range. It has to reserve its own
32// range from HeapMeta, and not clash with other spaces.
33pub struct LockFreeImmortalSpace<VM: VMBinding> {
34    #[allow(unused)]
35    name: &'static str,
36    /// Heap range start
37    cursor: Atomic<Address>,
38    /// Heap range end
39    limit: Address,
40    /// start of this space
41    start: Address,
42    /// Total bytes for the space
43    total_bytes: usize,
44    /// Zero memory after slow-path allocation
45    slow_path_zeroing: bool,
46    metadata: SideMetadataContext,
47    gc_trigger: Arc<GCTrigger<VM>>,
48}
49
50impl<VM: VMBinding> SFT for LockFreeImmortalSpace<VM> {
51    fn name(&self) -> &'static str {
52        self.get_name()
53    }
54    fn is_live(&self, _object: ObjectReference) -> bool {
55        unimplemented!()
56    }
57    #[cfg(feature = "object_pinning")]
58    fn pin_object(&self, _object: ObjectReference) -> bool {
59        false
60    }
61    #[cfg(feature = "object_pinning")]
62    fn unpin_object(&self, _object: ObjectReference) -> bool {
63        false
64    }
65    #[cfg(feature = "object_pinning")]
66    fn is_object_pinned(&self, _object: ObjectReference) -> bool {
67        true
68    }
69    fn is_movable(&self) -> bool {
70        unimplemented!()
71    }
72    #[cfg(feature = "sanity")]
73    fn is_sane(&self) -> bool {
74        unimplemented!()
75    }
76    fn initialize_object_metadata(&self, _object: ObjectReference, _bytes: usize) {
77        #[cfg(feature = "vo_bit")]
78        crate::util::metadata::vo_bit::set_vo_bit(_object);
79    }
80    #[cfg(feature = "vo_bit")]
81    fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
82        crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
83    }
84    #[cfg(feature = "vo_bit")]
85    fn find_object_from_internal_pointer(
86        &self,
87        ptr: Address,
88        max_search_bytes: usize,
89    ) -> Option<ObjectReference> {
90        crate::util::metadata::vo_bit::find_object_from_internal_pointer::<VM>(
91            ptr,
92            max_search_bytes,
93        )
94    }
95    fn sft_trace_object(
96        &self,
97        _queue: &mut OptionObjectQueue,
98        _object: ObjectReference,
99        _worker: GCWorkerMutRef,
100    ) -> ObjectReference {
101        unreachable!()
102    }
103}
104
105impl<VM: VMBinding> Space<VM> for LockFreeImmortalSpace<VM> {
106    fn as_space(&self) -> &dyn Space<VM> {
107        self
108    }
109    fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
110        self
111    }
112    fn get_page_resource(&self) -> &dyn PageResource<VM> {
113        unimplemented!()
114    }
115    fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
116        None
117    }
118    fn common(&self) -> &CommonSpace<VM> {
119        unimplemented!()
120    }
121
122    fn get_gc_trigger(&self) -> &GCTrigger<VM> {
123        &self.gc_trigger
124    }
125
126    fn release_multiple_pages(&mut self, _start: Address) {
127        panic!("immortalspace only releases pages enmasse")
128    }
129
130    fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap) {
131        unsafe { sft_map.eager_initialize(self.as_sft(), self.start, self.total_bytes) };
132    }
133
134    fn estimate_side_meta_pages(&self, data_pages: usize) -> usize {
135        self.metadata.calculate_reserved_pages(data_pages)
136    }
137
138    fn reserved_pages(&self) -> usize {
139        let cursor = self.cursor.load(Ordering::Relaxed);
140        let data_pages = conversions::bytes_to_pages_up(self.limit - cursor);
141        let meta_pages = self.estimate_side_meta_pages(data_pages);
142        data_pages + meta_pages
143    }
144
145    fn acquire(&self, _tls: VMThread, pages: usize, alloc_options: AllocationOptions) -> Address {
146        trace!("LockFreeImmortalSpace::acquire");
147        let bytes = conversions::pages_to_bytes(pages);
148        let start = self
149            .cursor
150            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |addr| {
151                Some(addr.add(bytes))
152            })
153            .expect("update cursor failed");
154        if start + bytes > self.limit {
155            if alloc_options.allow_oom_call {
156                panic!("OutOfMemory");
157            } else {
158                return Address::ZERO;
159            }
160        }
161        if self.slow_path_zeroing {
162            crate::util::memory::zero(start, bytes);
163        }
164        start
165    }
166
167    /// Get the name of the space
168    ///
169    /// We have to override the default implementation because
170    /// LockFreeImmortalSpace doesn't have a common space
171    fn get_name(&self) -> &'static str {
172        "LockFreeImmortalSpace"
173    }
174
175    /// We have to override the default implementation because
176    /// LockFreeImmortalSpace doesn't put metadata in a common space
177    fn verify_side_metadata_sanity(&self, side_metadata_sanity_checker: &mut SideMetadataSanity) {
178        side_metadata_sanity_checker
179            .verify_metadata_context(std::any::type_name::<Self>(), &self.metadata)
180    }
181
182    fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
183        enumerator.visit_address_range(self.start, self.start + self.total_bytes);
184    }
185
186    fn clear_side_log_bits(&self) {
187        unimplemented!()
188    }
189
190    fn set_side_log_bits(&self) {
191        unimplemented!()
192    }
193}
194
195impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LockFreeImmortalSpace<VM> {
196    fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
197        &self,
198        _queue: &mut Q,
199        _object: ObjectReference,
200        _copy: Option<CopySemantics>,
201        _worker: &mut GCWorker<VM>,
202    ) -> ObjectReference {
203        unreachable!()
204    }
205    fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
206        unreachable!()
207    }
208}
209
210impl<VM: VMBinding> LockFreeImmortalSpace<VM> {
211    #[allow(dead_code)] // Only used with certain features.
212    pub fn new(args: crate::policy::space::PlanCreateSpaceArgs<VM>) -> Self {
213        let slow_path_zeroing = args.zeroed;
214
215        // Get the total bytes for the heap.
216        let total_bytes = match *args.options.gc_trigger {
217            crate::util::options::GCTriggerSelector::FixedHeapSize(bytes) => bytes,
218            _ => unimplemented!(),
219        };
220        assert!(
221            total_bytes <= vm_layout().available_bytes(),
222            "Initial requested memory ({} bytes) overflows the heap. Max heap size is {} bytes.",
223            total_bytes,
224            vm_layout().available_bytes()
225        );
226        // Align up to chunks
227        let aligned_total_bytes = crate::util::conversions::raw_align_up(
228            total_bytes,
229            crate::util::heap::vm_layout::BYTES_IN_CHUNK,
230        );
231
232        // Create a VM request of fixed size
233        let vmrequest = VMRequest::fixed_size(aligned_total_bytes);
234        // Reserve the space
235        let VMRequest::Extent { extent, top } = vmrequest else {
236            unreachable!()
237        };
238        let start = args.heap.reserve(extent, top);
239
240        let space = Self {
241            name: args.name,
242            cursor: Atomic::new(start),
243            limit: start + aligned_total_bytes,
244            start,
245            total_bytes: aligned_total_bytes,
246            slow_path_zeroing,
247            metadata: SideMetadataContext {
248                global: args.global_side_metadata_specs,
249                local: vec![],
250            },
251            gc_trigger: args.gc_trigger,
252        };
253
254        // Eagerly memory map the entire heap (also zero all the memory)
255        let strategy = MmapStrategy::default()
256            .transparent_hugepages(*args.options.transparent_hugepages)
257            .prot(crate::util::os::MmapProtection::ReadWrite)
258            .replace(false)
259            .reserve(true);
260        crate::util::os::OS::dzmmap(
261            start,
262            aligned_total_bytes,
263            strategy,
264            &MmapAnnotation::Space {
265                name: space.get_name(),
266            },
267        )
268        .unwrap();
269        space
270            .metadata
271            .try_map_metadata_space(start, aligned_total_bytes, space.get_name())
272            .unwrap_or_else(|e| {
273                // TODO(Javad): handle meta space allocation failure
274                panic!("failed to mmap meta memory: {e}")
275            });
276
277        space
278    }
279}