mmtk/policy/
space.rs

1use crate::global_state::GlobalState;
2use crate::plan::PlanConstraints;
3use crate::scheduler::GCWorkScheduler;
4use crate::util::conversions::*;
5use crate::util::metadata::side_metadata::{
6    SideMetadataContext, SideMetadataSanity, SideMetadataSpec,
7};
8use crate::util::object_enum::ObjectEnumerator;
9use crate::util::Address;
10use crate::util::ObjectReference;
11
12use crate::util::heap::layout::vm_layout::{vm_layout, LOG_BYTES_IN_CHUNK};
13use crate::util::heap::{PageResource, VMRequest};
14use crate::util::options::Options;
15use crate::vm::{ActivePlan, Collection};
16
17use crate::util::constants::LOG_BYTES_IN_MBYTE;
18use crate::util::conversions;
19use crate::util::opaque_pointer::*;
20
21use crate::mmtk::SFT_MAP;
22#[cfg(debug_assertions)]
23use crate::policy::sft::EMPTY_SFT_NAME;
24use crate::policy::sft::SFT;
25use crate::util::alloc::allocator::AllocationOptions;
26use crate::util::copy::*;
27use crate::util::heap::gc_trigger::GCTrigger;
28use crate::util::heap::layout::vm_layout::BYTES_IN_CHUNK;
29use crate::util::heap::layout::Mmapper;
30use crate::util::heap::layout::VMMap;
31use crate::util::heap::space_descriptor::SpaceDescriptor;
32use crate::util::heap::HeapMeta;
33use crate::util::os::*;
34use crate::vm::VMBinding;
35
36use std::marker::PhantomData;
37use std::sync::atomic::AtomicBool;
38use std::sync::Arc;
39use std::sync::Mutex;
40
41use downcast_rs::Downcast;
42
43pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
44    fn as_space(&self) -> &dyn Space<VM>;
45    fn as_sft(&self) -> &(dyn SFT + Sync + 'static);
46    fn get_page_resource(&self) -> &dyn PageResource<VM>;
47
48    /// Get a mutable reference to the underlying page resource, or `None` if the space does not
49    /// have a page resource.
50    fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>>;
51
52    /// Initialize entires in SFT map for the space. This is called when the Space object
53    /// has a non-moving address, as we will use the address to set sft.
54    /// Currently after we create a boxed plan, spaces in the plan have a non-moving address.
55    fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap);
56
57    /// Initialize side metadata for the space. This is called after spaces and the plan are contructued
58    /// and the side metadata has been initialized. If a space needs to access side metadata during its
59    /// construction, it can override this method to initialize side metadata here.  By default, this method does nothing.
60    fn initialize_side_metadata(&self) {}
61
62    fn acquire(&self, tls: VMThread, pages: usize, alloc_options: AllocationOptions) -> Address {
63        trace!(
64            "Space.acquire, tls={:?}, alloc_options={:?}",
65            tls,
66            alloc_options
67        );
68
69        debug_assert!(
70            !self.get_gc_trigger().will_oom_on_alloc(pages << crate::util::constants::LOG_BYTES_IN_PAGE),
71            "The requested pages is larger than the max heap size. Is will_go_oom_on_acquire used before acquring memory?"
72        );
73
74        trace!("Reserving pages");
75        let pr = self.get_page_resource();
76        let pages_reserved = pr.reserve_pages(pages);
77        trace!("Pages reserved");
78
79        // Should we poll before acquring pages from page resources so that it can trigger a GC?
80        // - If tls is collector, we cannot attempt a GC.
81        let should_poll = VM::VMActivePlan::is_mutator(tls);
82
83        // If we should poll, do it now.  Record if it has triggered a GC.
84        // If we should not poll, GC is not triggered.
85        let gc_triggered = should_poll && {
86            trace!("Polling ..");
87            self.get_gc_trigger().poll(false, Some(self.as_space()))
88        };
89
90        // We can try to get pages if
91        // - GC is not triggered, or
92        // - GC is triggered, but we allow over-committing.
93        let should_get_pages = !gc_triggered || alloc_options.allow_overcommit;
94
95        // Get new pages if we should. If we didn't get new pages from the page resource for any
96        // reason (if we decided not to, or if we tried and failed), this function shall return a
97        // null address.
98        if should_get_pages {
99            if let Some(addr) = self.get_new_pages_and_initialize(tls, pages, pr, pages_reserved) {
100                addr
101            } else {
102                self.not_acquiring(tls, alloc_options, pr, pages_reserved, true);
103                Address::ZERO
104            }
105        } else {
106            self.not_acquiring(tls, alloc_options, pr, pages_reserved, false);
107            Address::ZERO
108        }
109    }
110
111    /// Get new pages from the page resource, and do necessary initialization, including mmapping
112    /// and zeroing the memory.
113    ///
114    /// The caller must have reserved pages from the page resource.  If successfully acquired pages
115    /// from the page resource, the reserved pages will be committed.
116    ///
117    /// Returns `None` if failed to acquire memory from the page resource.  The caller should call
118    /// `pr.clear_request`.
119    fn get_new_pages_and_initialize(
120        &self,
121        tls: VMThread,
122        pages: usize,
123        pr: &dyn PageResource<VM>,
124        pages_reserved: usize,
125    ) -> Option<Address> {
126        // We need this lock: Othrewise, it is possible that one thread acquires pages in a new chunk, but not yet
127        // set SFT for it (in grow_space()), and another thread acquires pages in the same chunk, which is not
128        // a new chunk so grow_space() won't be called on it. The second thread could return a result in the chunk before
129        // its SFT is properly set.
130        // We need to minimize the scope of this lock for performance when we have many threads (mutator threads, or GC threads with copying allocators).
131        // See: https://github.com/mmtk/mmtk-core/issues/610
132        let lock = self.common().acquire_lock.lock().unwrap();
133
134        let Ok(res) = pr.get_new_pages(self.common().descriptor, pages_reserved, pages, tls) else {
135            return None;
136        };
137
138        debug!(
139            "Got new pages {} ({} pages) for {} in chunk {}, new_chunk? {}",
140            res.start,
141            res.pages,
142            self.get_name(),
143            conversions::chunk_align_down(res.start),
144            res.new_chunk
145        );
146        let bytes = conversions::pages_to_bytes(res.pages);
147        #[cfg(debug_assertions)]
148        self.common()
149            .metadata
150            .assert_metadata_ranges_in_reserved_range(res.start, bytes, self.get_name());
151
152        let mmap = || {
153            // Mmap the pages and the side metadata, and handle error. In case of any error,
154            // we will either call back to the VM for OOM, or simply panic.
155            if let Err(mmap_error) = self
156                .common()
157                .mmapper
158                .ensure_mapped(
159                    res.start,
160                    res.pages,
161                    self.common()
162                        .options
163                        .transparent_hugepages_as_huge_page_support(),
164                    self.common().mmap_protection(),
165                    &MmapAnnotation::Space {
166                        name: self.get_name(),
167                    },
168                )
169                .and(self.common().metadata.try_map_metadata_space(
170                    res.start,
171                    bytes,
172                    self.get_name(),
173                ))
174            {
175                OS::handle_mmap_error::<VM>(mmap_error, tls);
176            }
177        };
178        let grow_space = || {
179            self.grow_space(res.start, bytes, res.new_chunk);
180        };
181
182        // The scope of the lock is important in terms of performance when we have many allocator threads.
183        if SFT_MAP.get_side_metadata().is_some() {
184            // If the SFT map uses side metadata, so we have to initialize side metadata first.
185            mmap();
186            // then grow space, which will use the side metadata we mapped above
187            grow_space();
188            // then we can drop the lock after grow_space()
189            drop(lock);
190        } else {
191            // In normal cases, we can drop lock immediately after grow_space()
192            grow_space();
193            drop(lock);
194            // and map side metadata without holding the lock
195            mmap();
196        }
197
198        // TODO: Concurrent zeroing
199        if self.common().zeroed {
200            crate::util::memory::zero(res.start, bytes);
201        }
202
203        // Some assertions
204        {
205            // --- Assert the start of the allocated region ---
206            // The start address SFT should be correct.
207            debug_assert_eq!(SFT_MAP.get_checked(res.start).name(), self.get_name());
208            // The start address is in our space.
209            debug_assert!(self.address_in_space(res.start));
210            // The descriptor should be correct.
211            debug_assert_eq!(
212                self.common().vm_map().get_descriptor_for_address(res.start),
213                self.common().descriptor
214            );
215
216            // --- Assert the last byte in the allocated region ---
217            let last_byte = res.start + bytes - 1;
218            // The SFT for the last byte in the allocated memory should be correct.
219            debug_assert_eq!(SFT_MAP.get_checked(last_byte).name(), self.get_name());
220            // The last byte in the allocated memory should be in this space.
221            debug_assert!(self.address_in_space(last_byte));
222            // The descriptor for the last byte should be correct.
223            debug_assert_eq!(
224                self.common().vm_map().get_descriptor_for_address(last_byte),
225                self.common().descriptor
226            );
227        }
228
229        debug!("Space.acquire(), returned = {}", res.start);
230        Some(res.start)
231    }
232
233    /// Handle the case where [`Space::acquire`] will not or can not acquire pages from the page
234    /// resource.  This may happen when
235    /// -   GC is triggered and the allocation does not allow over-committing, or
236    /// -   the allocation tried to acquire pages from the page resource but ran out of physical
237    ///     memory.
238    fn not_acquiring(
239        &self,
240        tls: VMThread,
241        alloc_options: AllocationOptions,
242        pr: &dyn PageResource<VM>,
243        pages_reserved: usize,
244        attempted_allocation_and_failed: bool,
245    ) {
246        assert!(
247            VM::VMActivePlan::is_mutator(tls),
248            "A non-mutator thread failed to get pages from page resource.  \
249            Copying GC plans should compute the copying headroom carefully to prevent this."
250        );
251
252        // Clear the request
253        pr.clear_request(pages_reserved);
254
255        // If we are not at a safepoint, return immediately.
256        if !alloc_options.at_safepoint {
257            return;
258        }
259
260        debug!("Collection required");
261
262        if !self.common().global_state.is_initialized() {
263            // Otherwise do GC here
264            panic!(
265                "GC is not allowed here: collection is not initialized \
266                    (did you call initialize_collection()?).  \
267                    Out of physical memory: {phy}",
268                phy = attempted_allocation_and_failed
269            );
270        }
271
272        if attempted_allocation_and_failed {
273            // We thought we had memory to allocate, but somehow failed the allocation. Will force a GC.
274            let gc_performed = self.get_gc_trigger().poll(true, Some(self.as_space()));
275            debug_assert!(gc_performed, "GC not performed when forced.");
276        }
277
278        // Inform GC trigger about the pending allocation.
279        let meta_pages_reserved = self.estimate_side_meta_pages(pages_reserved);
280        let total_pages_reserved = pages_reserved + meta_pages_reserved;
281        self.get_gc_trigger()
282            .policy
283            .on_pending_allocation(total_pages_reserved);
284
285        VM::VMCollection::block_for_gc(VMMutatorThread(tls)); // We have checked that this is mutator
286    }
287
288    fn address_in_space(&self, start: Address) -> bool {
289        if !self.common().descriptor.is_contiguous() {
290            self.common().vm_map().get_descriptor_for_address(start) == self.common().descriptor
291        } else {
292            start >= self.common().start && start < self.common().start + self.common().extent
293        }
294    }
295
296    fn in_space(&self, object: ObjectReference) -> bool {
297        self.address_in_space(object.to_raw_address())
298    }
299
300    /**
301     * This is called after we get result from page resources.  The space may
302     * tap into the hook to monitor heap growth.  The call is made from within the
303     * page resources' critical region, immediately before yielding the lock.
304     *
305     * @param start The start of the newly allocated space
306     * @param bytes The size of the newly allocated space
307     * @param new_chunk {@code true} if the new space encroached upon or started a new chunk or chunks.
308     */
309    fn grow_space(&self, start: Address, bytes: usize, new_chunk: bool) {
310        trace!(
311            "Grow space from {} for {} bytes (new chunk = {})",
312            start,
313            bytes,
314            new_chunk
315        );
316
317        // If this is not a new chunk, the SFT for [start, start + bytes) should alreayd be initialized.
318        #[cfg(debug_assertions)]
319        if !new_chunk {
320            debug_assert!(
321                SFT_MAP.get_checked(start).name() != EMPTY_SFT_NAME,
322                "In grow_space(start = {}, bytes = {}, new_chunk = {}), we have empty SFT entries (chunk for {} = {})",
323                start,
324                bytes,
325                new_chunk,
326                start,
327                SFT_MAP.get_checked(start).name()
328            );
329            debug_assert!(
330                SFT_MAP.get_checked(start + bytes - 1).name() != EMPTY_SFT_NAME,
331                "In grow_space(start = {}, bytes = {}, new_chunk = {}), we have empty SFT entries (chunk for {} = {})",
332                start,
333                bytes,
334                new_chunk,
335                start + bytes - 1,
336                SFT_MAP.get_checked(start + bytes - 1).name()
337            );
338        }
339
340        if new_chunk {
341            unsafe { SFT_MAP.update(self.as_sft(), start, bytes) };
342        }
343    }
344
345    /// Estimate the amount of side metadata memory needed for a give data memory size in pages. The
346    /// result will over-estimate the amount of metadata pages needed, with at least one page per
347    /// side metadata.  This relatively accurately describes the number of side metadata pages the
348    /// space actually consumes.
349    ///
350    /// This function is used for both triggering GC (via [`Space::reserved_pages`]) and resizing
351    /// the heap (via [`crate::util::heap::GCTriggerPolicy::on_pending_allocation`]).
352    fn estimate_side_meta_pages(&self, data_pages: usize) -> usize {
353        self.common().metadata.calculate_reserved_pages(data_pages)
354    }
355
356    fn reserved_pages(&self) -> usize {
357        let data_pages = self.get_page_resource().reserved_pages();
358        let meta_pages = self.estimate_side_meta_pages(data_pages);
359        data_pages + meta_pages
360    }
361
362    /// Return the number of physical pages available.
363    fn available_physical_pages(&self) -> usize {
364        self.get_page_resource().get_available_physical_pages()
365    }
366
367    fn get_name(&self) -> &'static str {
368        self.common().name
369    }
370
371    fn get_descriptor(&self) -> SpaceDescriptor {
372        self.common().descriptor
373    }
374
375    fn common(&self) -> &CommonSpace<VM>;
376    fn get_gc_trigger(&self) -> &GCTrigger<VM> {
377        self.common().gc_trigger.as_ref()
378    }
379
380    fn release_multiple_pages(&mut self, start: Address);
381
382    /// What copy semantic we should use for this space if we copy objects from this space.
383    /// This is only needed for plans that use [`crate::plan::tracing::SFTTrace`].
384    fn set_copy_for_sft_trace(&mut self, _semantics: Option<CopySemantics>) {
385        panic!("A copying space should override this method")
386    }
387
388    /// Ensure that the current space's metadata context does not have any issues.
389    /// Panics with a suitable message if any issue is detected.
390    /// It also initialises the sanity maps which will then be used if the `extreme_assertions` feature is active.
391    /// Internally this calls verify_metadata_context() from `util::metadata::sanity`
392    ///
393    /// This function is called once per space by its parent plan but may be called multiple times per policy.
394    ///
395    /// Arguments:
396    /// * `side_metadata_sanity_checker`: The `SideMetadataSanity` object instantiated in the calling plan.
397    fn verify_side_metadata_sanity(&self, side_metadata_sanity_checker: &mut SideMetadataSanity) {
398        side_metadata_sanity_checker
399            .verify_metadata_context(std::any::type_name::<Self>(), &self.common().metadata)
400    }
401
402    /// Enumerate objects in the current space.
403    ///
404    /// Implementers can use the `enumerator` to report
405    ///
406    /// -   individual objects within the space using `enumerator.visit_object`, and
407    /// -   ranges of address that may contain objects using `enumerator.visit_address_range`. The
408    ///     caller will then enumerate objects in the range using the VO bits metadata.
409    ///
410    /// Each object in the space shall be covered by one of the two methods above.
411    ///
412    /// # Implementation considerations
413    ///
414    /// **Skipping empty ranges**: When enumerating address ranges, spaces can skip ranges (blocks,
415    /// chunks, etc.) that are guarenteed not to contain objects.
416    ///
417    /// **Dynamic dispatch**: Because `Space` is a trait object type and `enumerator` is a `dyn`
418    /// reference, invoking methods of `enumerator` involves a dynamic dispatching.  But the
419    /// overhead is OK if we call it a block at a time because scanning the VO bits will dominate
420    /// the execution time.  For LOS, it will be cheaper to enumerate individual objects than
421    /// scanning VO bits because it is sparse.
422    fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator);
423
424    fn set_allocate_as_live(&self, live: bool) {
425        self.common()
426            .allocate_as_live
427            .store(live, std::sync::atomic::Ordering::SeqCst);
428    }
429
430    fn should_allocate_as_live(&self) -> bool {
431        self.common()
432            .allocate_as_live
433            .load(std::sync::atomic::Ordering::Acquire)
434    }
435
436    /// Clear the side log bits for allocated regions in this space.
437    /// This method is only called if the plan knows the log bits are side metadata.
438    fn clear_side_log_bits(&self);
439
440    /// Set the side log bits for allocated regions in this space.
441    /// This method is only called if the plan knows the log bits are side metadata.
442    fn set_side_log_bits(&self);
443}
444
445/// Print the VM map for a space.
446/// Space needs to be object-safe, so it cannot have methods that use extra generic type paramters. So this method is placed outside the Space trait.
447/// This method can be invoked on a &dyn Space (space.as_space() will return &dyn Space).
448#[allow(unused)]
449pub(crate) fn print_vm_map<VM: VMBinding>(
450    space: &dyn Space<VM>,
451    out: &mut impl std::fmt::Write,
452) -> Result<(), std::fmt::Error> {
453    let common = space.common();
454    write!(out, "{} ", common.name)?;
455    if common.immortal {
456        write!(out, "I")?;
457    } else {
458        write!(out, " ")?;
459    }
460    if common.movable {
461        write!(out, " ")?;
462    } else {
463        write!(out, "N")?;
464    }
465    write!(out, " ")?;
466    if common.contiguous {
467        write!(
468            out,
469            "{}->{}",
470            common.start,
471            common.start + common.extent - 1
472        )?;
473        match common.vmrequest {
474            VMRequest::Extent { extent, .. } => {
475                write!(out, " E {}", extent)?;
476            }
477            VMRequest::Fraction { frac, .. } => {
478                write!(out, " F {}", frac)?;
479            }
480            _ => {}
481        }
482    } else {
483        let mut a = space
484            .get_page_resource()
485            .common()
486            .get_head_discontiguous_region();
487        while !a.is_zero() {
488            write!(
489                out,
490                "{}->{}",
491                a,
492                a + space.common().vm_map().get_contiguous_region_size(a) - 1
493            )?;
494            a = space.common().vm_map().get_next_contiguous_region(a);
495            if !a.is_zero() {
496                write!(out, " ")?;
497            }
498        }
499    }
500    writeln!(out)?;
501
502    Ok(())
503}
504
505impl_downcast!(Space<VM> where VM: VMBinding);
506
507pub struct CommonSpace<VM: VMBinding> {
508    pub name: &'static str,
509    pub descriptor: SpaceDescriptor,
510    pub vmrequest: VMRequest,
511
512    /// For a copying space that allows sft_trace_object(), this should be set before each GC so we know
513    // the copy semantics for the space.
514    pub copy: Option<CopySemantics>,
515
516    pub immortal: bool,
517    pub movable: bool,
518    pub contiguous: bool,
519    pub zeroed: bool,
520
521    pub permission_exec: bool,
522
523    pub start: Address,
524    pub extent: usize,
525
526    pub vm_map: &'static dyn VMMap,
527    pub mmapper: &'static dyn Mmapper,
528
529    pub(crate) metadata: SideMetadataContext,
530
531    /// This field equals to needs_log_bit in the plan constraints.
532    // TODO: This should be a constant for performance.
533    pub needs_log_bit: bool,
534    pub needs_field_log_bit: bool,
535    pub unlog_allocated_object: bool,
536    pub unlog_traced_object: bool,
537
538    /// A lock used during acquire() to make sure only one thread can allocate.
539    pub acquire_lock: Mutex<()>,
540
541    pub gc_trigger: Arc<GCTrigger<VM>>,
542    pub global_state: Arc<GlobalState>,
543    pub options: Arc<Options>,
544
545    pub allocate_as_live: AtomicBool,
546
547    p: PhantomData<VM>,
548}
549
550/// Arguments passed from a policy to create a space. This includes policy specific args.
551pub struct PolicyCreateSpaceArgs<'a, VM: VMBinding> {
552    pub plan_args: PlanCreateSpaceArgs<'a, VM>,
553    pub movable: bool,
554    pub immortal: bool,
555    pub local_side_metadata_specs: Vec<SideMetadataSpec>,
556}
557
558/// Arguments passed from a plan to create a space.
559pub struct PlanCreateSpaceArgs<'a, VM: VMBinding> {
560    pub name: &'static str,
561    pub zeroed: bool,
562    pub permission_exec: bool,
563    pub unlog_allocated_object: bool,
564    pub unlog_traced_object: bool,
565    pub vmrequest: VMRequest,
566    pub global_side_metadata_specs: Vec<SideMetadataSpec>,
567    pub vm_map: &'static dyn VMMap,
568    pub mmapper: &'static dyn Mmapper,
569    pub heap: &'a mut HeapMeta,
570    pub constraints: &'a PlanConstraints,
571    pub gc_trigger: Arc<GCTrigger<VM>>,
572    pub scheduler: Arc<GCWorkScheduler<VM>>,
573    pub options: Arc<Options>,
574    pub global_state: Arc<GlobalState>,
575}
576
577impl<'a, VM: VMBinding> PlanCreateSpaceArgs<'a, VM> {
578    /// Turning PlanCreateSpaceArgs into a PolicyCreateSpaceArgs
579    pub fn into_policy_args(
580        self,
581        movable: bool,
582        immortal: bool,
583        policy_metadata_specs: Vec<SideMetadataSpec>,
584    ) -> PolicyCreateSpaceArgs<'a, VM> {
585        PolicyCreateSpaceArgs {
586            movable,
587            immortal,
588            local_side_metadata_specs: policy_metadata_specs,
589            plan_args: self,
590        }
591    }
592}
593
594impl<VM: VMBinding> CommonSpace<VM> {
595    pub fn new(args: PolicyCreateSpaceArgs<VM>) -> Self {
596        let mut rtn = CommonSpace {
597            name: args.plan_args.name,
598            descriptor: SpaceDescriptor::UNINITIALIZED,
599            vmrequest: args.plan_args.vmrequest,
600            copy: None,
601            immortal: args.immortal,
602            movable: args.movable,
603            contiguous: true,
604            permission_exec: args.plan_args.permission_exec,
605            zeroed: args.plan_args.zeroed,
606            start: unsafe { Address::zero() },
607            extent: 0,
608            vm_map: args.plan_args.vm_map,
609            mmapper: args.plan_args.mmapper,
610            needs_log_bit: args.plan_args.constraints.needs_log_bit,
611            needs_field_log_bit: args.plan_args.constraints.needs_field_log_bit,
612            unlog_allocated_object: args.plan_args.unlog_allocated_object,
613            unlog_traced_object: args.plan_args.unlog_traced_object,
614            gc_trigger: args.plan_args.gc_trigger.clone(),
615            metadata: SideMetadataContext {
616                global: args.plan_args.global_side_metadata_specs,
617                local: args.local_side_metadata_specs,
618            },
619            acquire_lock: Mutex::new(()),
620            global_state: args.plan_args.global_state,
621            options: args.plan_args.options.clone(),
622            allocate_as_live: AtomicBool::new(false),
623            p: PhantomData,
624        };
625
626        let vmrequest = args.plan_args.vmrequest;
627        if vmrequest.is_discontiguous() {
628            rtn.contiguous = false;
629            // FIXME
630            rtn.descriptor = SpaceDescriptor::create_descriptor();
631            // VM.memory.setHeapRange(index, HEAP_START, HEAP_END);
632            return rtn;
633        }
634
635        let (extent, align, top) = match vmrequest {
636            VMRequest::Fraction { frac, top: _top } => (get_frac_available(frac), None, _top),
637            VMRequest::Extent {
638                extent: _extent,
639                top: _top,
640            } => (_extent, None, _top),
641            VMRequest::AlignedExtent { align, extent, top } => (extent, Some(align), top),
642            VMRequest::Fixed {
643                extent: _extent, ..
644            } => (_extent, None, false),
645            _ => unreachable!(),
646        };
647
648        assert!(
649            extent == raw_align_up(extent, BYTES_IN_CHUNK),
650            "{} requested non-aligned extent: {} bytes",
651            rtn.name,
652            extent
653        );
654
655        // The given extent might be too large for the heap. We get an estimate of the required virtual memory for the heap size,
656        // then use the min of the given extent and the estimate as the actual extent for the space.
657        let reasonable_extent = conversions::raw_align_up(
658            Self::estimate_reasonable_contiguous_extent(
659                &args.plan_args.options,
660                &args.plan_args.gc_trigger,
661                args.plan_args.vm_map,
662                extent,
663            ),
664            BYTES_IN_CHUNK,
665        );
666        debug!(
667            "resonable_extent for space {} is {} bytes",
668            rtn.name, reasonable_extent
669        );
670
671        let anno = MmapAnnotation::Space { name: rtn.name };
672        let huge_page_option = args
673            .plan_args
674            .options
675            .transparent_hugepages_as_huge_page_support();
676
677        let start = if let VMRequest::Fixed { start: _start, .. } = vmrequest {
678            if let Err(mmap_error) = args.plan_args.mmapper.quarantine_address_range(
679                _start,
680                bytes_to_pages_up(reasonable_extent),
681                huge_page_option,
682                &anno,
683            ) {
684                panic!(
685                    "Failed to quarantine fixed contiguous space {} [{}, {}) for {} bytes: {}",
686                    rtn.name,
687                    _start,
688                    _start + extent,
689                    extent,
690                    mmap_error
691                );
692            }
693            _start
694        } else {
695            args.plan_args
696                .heap
697                .reserve_quarantined(
698                    reasonable_extent,
699                    align,
700                    top,
701                    args.plan_args.mmapper,
702                    huge_page_option,
703                    &anno,
704                )
705                .unwrap_or_else(|mmap_error| {
706                    panic!(
707                        "Failed to quarantine contiguous space {} for {} bytes: {}",
708                        rtn.name, extent, mmap_error
709                    )
710                })
711        };
712        assert!(
713            start == chunk_align_up(start),
714            "{} starting on non-aligned boundary: {}",
715            rtn.name,
716            start
717        );
718
719        rtn.contiguous = true;
720        rtn.start = start;
721        rtn.extent = reasonable_extent;
722        rtn.descriptor =
723            SpaceDescriptor::create_descriptor_from_heap_range(start, start + reasonable_extent);
724
725        // We only initialize our vm map if the range of the space is in our available heap range. For normally spaces,
726        // they are definitely in our heap range. But for VM space, a runtime could give us an arbitrary range. We only
727        // insert into our vm map if the range overlaps with our heap.
728        {
729            use crate::util::heap::layout;
730            let overlap =
731                Address::range_intersection(&(start..start + extent), &layout::available_range());
732            if !overlap.is_empty() {
733                args.plan_args.vm_map.insert(
734                    overlap.start,
735                    overlap.end - overlap.start,
736                    rtn.descriptor,
737                );
738            }
739        }
740
741        debug!(
742            "Created space {} [{}, {}) for {} bytes",
743            rtn.name,
744            start,
745            start + reasonable_extent,
746            reasonable_extent
747        );
748
749        rtn
750    }
751
752    /// This function return an estimate value of required virtual memory for the heap size.
753    /// Considering virtual memory fragmentation, we may need more virtual memory than the actual heap size.
754    /// When a space needs to quarantine a virtual memory range, it can use this function to get an estimate
755    /// of the required virtual memory, and use that to decide how much virtual memory to quarantine.
756    pub fn estimate_reasonable_contiguous_extent(
757        options: &Options,
758        gc_trigger: &GCTrigger<VM>,
759        vm_map: &dyn VMMap,
760        extent: usize,
761    ) -> usize {
762        // PageProtect will consume virtual memory very quickly, and it is not a performant plan anyway. Just return extent.
763        if *options.plan == crate::util::options::PlanSelector::PageProtect {
764            return extent;
765        }
766
767        // To accomodate virtual memory fragmentation, we use a fixed ratio to estimate the required virtual memory.
768        // The following value (2) is a pure estimate, we may chanage it to whatever is reasonable based on the actual
769        // fragmentation observed in different platforms.
770        const VIRTUAL_MEMORY_RATIO_TO_MAX_HEAP_SIZE: usize = 2;
771        let estimate = conversions::pages_to_bytes(gc_trigger.policy.get_max_heap_size_in_pages())
772            * VIRTUAL_MEMORY_RATIO_TO_MAX_HEAP_SIZE;
773
774        estimate.max(vm_map.min_contiguous_extent())
775    }
776
777    pub fn initialize_sft(
778        &self,
779        sft: &(dyn SFT + Sync + 'static),
780        sft_map: &mut dyn crate::policy::sft_map::SFTMap,
781    ) {
782        // We have to keep this for now: if a space is contiguous, our page resource will NOT consider newly allocated chunks
783        // as new chunks (new_chunks = true). In that case, in grow_space(), we do not set SFT when new_chunks = false.
784        // We can fix this by either of these:
785        // * fix page resource, so it propelry returns new_chunk
786        // * change grow_space() so it sets SFT no matter what the new_chunks value is.
787        // FIXME: eagerly initializing SFT is not a good idea.
788        if self.contiguous {
789            unsafe { sft_map.eager_initialize(sft, self.start, self.extent) };
790        }
791    }
792
793    pub fn vm_map(&self) -> &'static dyn VMMap {
794        self.vm_map
795    }
796
797    pub fn mmap_protection(&self) -> MmapProtection {
798        if self.permission_exec || cfg!(feature = "exec_permission_on_all_spaces") {
799            MmapProtection::ReadWriteExec
800        } else {
801            MmapProtection::ReadWrite
802        }
803    }
804
805    pub(crate) fn debug_print_object_global_info(&self, object: ObjectReference) {
806        #[cfg(feature = "vo_bit")]
807        println!(
808            "vo bit = {}",
809            crate::util::metadata::vo_bit::is_vo_bit_set(object)
810        );
811        if self.needs_log_bit {
812            use crate::vm::object_model::ObjectModel;
813            use std::sync::atomic::Ordering;
814            println!(
815                "log bit = {}",
816                VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.is_unlogged::<VM>(object, Ordering::Relaxed),
817            );
818        }
819        println!("is live = {}", object.is_live());
820    }
821}
822
823fn get_frac_available(frac: f32) -> usize {
824    trace!("AVAILABLE_START={}", vm_layout().available_start());
825    trace!("AVAILABLE_END={}", vm_layout().available_end());
826    let bytes = (frac * vm_layout().available_bytes() as f32) as usize;
827    trace!("bytes={}*{}={}", frac, vm_layout().available_bytes(), bytes);
828    let mb = bytes >> LOG_BYTES_IN_MBYTE;
829    let rtn = mb << LOG_BYTES_IN_MBYTE;
830    trace!("rtn={}", rtn);
831    let aligned_rtn = raw_align_up(rtn, BYTES_IN_CHUNK);
832    trace!("aligned_rtn={}", aligned_rtn);
833    aligned_rtn
834}
835
836pub fn required_chunks(pages: usize) -> usize {
837    let extent = raw_align_up(pages_to_bytes(pages), BYTES_IN_CHUNK);
838    extent >> LOG_BYTES_IN_CHUNK
839}