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 initialize_side_metadata(&self) {
135        self.metadata
136            .try_map_metadata_space(self.start, self.total_bytes, self.get_name())
137            .unwrap_or_else(|e| {
138                // TODO(Javad): handle meta space allocation failure
139                panic!("failed to mmap meta memory: {e}")
140            });
141    }
142
143    fn estimate_side_meta_pages(&self, data_pages: usize) -> usize {
144        self.metadata.calculate_reserved_pages(data_pages)
145    }
146
147    fn reserved_pages(&self) -> usize {
148        let cursor = self.cursor.load(Ordering::Relaxed);
149        let data_pages = conversions::bytes_to_pages_up(self.limit - cursor);
150        let meta_pages = self.estimate_side_meta_pages(data_pages);
151        data_pages + meta_pages
152    }
153
154    fn acquire(&self, _tls: VMThread, pages: usize, alloc_options: AllocationOptions) -> Address {
155        trace!("LockFreeImmortalSpace::acquire");
156        let bytes = conversions::pages_to_bytes(pages);
157        let start = self
158            .cursor
159            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |addr| {
160                Some(addr.add(bytes))
161            })
162            .expect("update cursor failed");
163        if start + bytes > self.limit {
164            if alloc_options.allow_oom_call {
165                panic!("OutOfMemory");
166            } else {
167                return Address::ZERO;
168            }
169        }
170        if self.slow_path_zeroing {
171            crate::util::memory::zero(start, bytes);
172        }
173        start
174    }
175
176    /// Get the name of the space
177    ///
178    /// We have to override the default implementation because
179    /// LockFreeImmortalSpace doesn't have a common space
180    fn get_name(&self) -> &'static str {
181        "LockFreeImmortalSpace"
182    }
183
184    /// We have to override the default implementation because
185    /// LockFreeImmortalSpace doesn't put metadata in a common space
186    fn verify_side_metadata_sanity(&self, side_metadata_sanity_checker: &mut SideMetadataSanity) {
187        side_metadata_sanity_checker
188            .verify_metadata_context(std::any::type_name::<Self>(), &self.metadata)
189    }
190
191    fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
192        enumerator.visit_address_range(self.start, self.start + self.total_bytes);
193    }
194
195    fn clear_side_log_bits(&self) {
196        unimplemented!()
197    }
198
199    fn set_side_log_bits(&self) {
200        unimplemented!()
201    }
202}
203
204impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LockFreeImmortalSpace<VM> {
205    fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
206        &self,
207        _queue: &mut Q,
208        _object: ObjectReference,
209        _copy: Option<CopySemantics>,
210        _worker: &mut GCWorker<VM>,
211    ) -> ObjectReference {
212        unreachable!()
213    }
214    fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
215        unreachable!()
216    }
217}
218
219impl<VM: VMBinding> LockFreeImmortalSpace<VM> {
220    #[allow(dead_code)] // Only used with certain features.
221    pub fn new(args: crate::policy::space::PlanCreateSpaceArgs<VM>) -> Self {
222        let slow_path_zeroing = args.zeroed;
223
224        // Get the total bytes for the heap.
225        let total_bytes = match *args.options.gc_trigger {
226            crate::util::options::GCTriggerSelector::FixedHeapSize(bytes) => bytes,
227            _ => unimplemented!(),
228        };
229        assert!(
230            total_bytes <= vm_layout().available_bytes(),
231            "Initial requested memory ({} bytes) overflows the heap. Max heap size is {} bytes.",
232            total_bytes,
233            vm_layout().available_bytes()
234        );
235        // Align up to chunks
236        let aligned_total_bytes = crate::util::conversions::raw_align_up(
237            total_bytes,
238            crate::util::heap::vm_layout::BYTES_IN_CHUNK,
239        );
240
241        // Create a VM request of fixed size
242        let vmrequest = VMRequest::fixed_size(aligned_total_bytes);
243        // Reserve the space
244        let (extent, align, top) = match vmrequest {
245            VMRequest::Extent { extent, top } => (extent, None, top),
246            VMRequest::AlignedExtent { extent, align, top } => (extent, Some(align), top),
247            _ => unreachable!(),
248        };
249        let anno = MmapAnnotation::Space { name: args.name };
250        let huge_page_option = args.options.transparent_hugepages_as_huge_page_support();
251        let reasonable_extent = CommonSpace::estimate_reasonable_contiguous_extent(
252            &args.options,
253            &args.gc_trigger,
254            args.vm_map,
255            extent,
256        );
257        let start = args
258            .heap
259            .reserve_quarantined(
260                reasonable_extent,
261                align,
262                top,
263                args.mmapper,
264                huge_page_option,
265                &anno,
266            )
267            .unwrap_or_else(|mmap_error| {
268                panic!(
269                    "Failed to quarantine contiguous space {} for {} bytes: {}",
270                    args.name, reasonable_extent, mmap_error
271                )
272            });
273
274        let space = Self {
275            name: args.name,
276            cursor: Atomic::new(start),
277            limit: start + aligned_total_bytes,
278            start,
279            total_bytes: aligned_total_bytes,
280            slow_path_zeroing,
281            metadata: SideMetadataContext {
282                global: args.global_side_metadata_specs,
283                local: vec![],
284            },
285            gc_trigger: args.gc_trigger,
286        };
287
288        // Eagerly memory map the entire heap (also zero all the memory)
289        let strategy = MmapStrategy::default()
290            .transparent_hugepages(*args.options.transparent_hugepages)
291            .prot(crate::util::os::MmapProtection::ReadWrite)
292            .replace(true)
293            .reserve(true);
294        crate::util::os::OS::dzmmap(start, aligned_total_bytes, strategy, &anno).unwrap();
295
296        space
297    }
298}