diff --git a/src/policy/lockfreeimmortalspace.rs b/src/policy/lockfreeimmortalspace.rs index f62520a1c1..1a34d179e1 100644 --- a/src/policy/lockfreeimmortalspace.rs +++ b/src/policy/lockfreeimmortalspace.rs @@ -151,7 +151,7 @@ impl Space for LockFreeImmortalSpace { }) .expect("update cursor failed"); if start + bytes > self.limit { - if alloc_options.on_fail.allow_oom_call() { + if alloc_options.allow_oom_call() { panic!("OutOfMemory"); } else { return Address::ZERO; diff --git a/src/policy/space.rs b/src/policy/space.rs index a80dc3f973..d47db220c7 100644 --- a/src/policy/space.rs +++ b/src/policy/space.rs @@ -85,7 +85,7 @@ pub trait Space: 'static + SFT + Sync + Downcast { alloc_options: AllocationOptions, ) -> bool { if self.will_oom_on_acquire(size) { - if alloc_options.on_fail.allow_oom_call() { + if alloc_options.allow_oom_call() { VM::VMCollection::out_of_memory( tls, crate::util::alloc::AllocationError::HeapOutOfMemory, @@ -109,17 +109,20 @@ pub trait Space: 'static + SFT + Sync + Downcast { ); // Should we poll to attempt to GC? - // - If tls is collector, we cannot attempt a GC. - // - If gc is disabled, we cannot attempt a GC. - // - If overcommit is allowed, we don't attempt a GC. - let should_poll = VM::VMActivePlan::is_mutator(tls) - && VM::VMCollection::is_collection_enabled() - && !alloc_options.on_fail.allow_overcommit(); - // Is a GC allowed here? If we should poll but are not allowed to poll, we will panic. - // initialize_collection() has to be called so we know GC is initialized. - let allow_gc = should_poll + // - If tls is collector, we shall not poll. + // - If gc is disabled, we shall not poll. + let should_poll = + VM::VMActivePlan::is_mutator(tls) && VM::VMCollection::is_collection_enabled(); + + // Can we continue to allocate even if GC is triggered? + let allow_overcommit = alloc_options.allow_overcommit; + + // Can we block for GC if polling triggers GC? + // - If the MMTk instance is not initialized, there is no GC workers, and we cannot block for GC. + // - If the on_fail option does not allow blocking, we do not block for GC, either. + let allow_blocking_for_gc = should_poll && self.common().global_state.is_initialized() - && alloc_options.on_fail.allow_gc(); + && alloc_options.at_safepoint; trace!("Reserving pages"); let pr = self.get_page_resource(); @@ -127,18 +130,26 @@ pub trait Space: 'static + SFT + Sync + Downcast { trace!("Pages reserved"); trace!("Polling .."); - if should_poll && self.get_gc_trigger().poll(false, Some(self.as_space())) { + // Whether we should try to allocate. We should try to allocate if + // - we shouldn't poll, or + // - we polled, but GC was not triggered, or + // - GC is triggered, but we allow over-committing. + let should_try_to_allocate = !should_poll + || !self.get_gc_trigger().poll(false, Some(self.as_space())) + || allow_overcommit; + + if !should_try_to_allocate { // Clear the request pr.clear_request(pages_reserved); // If we do not want GC on fail, just return zero. - if !alloc_options.on_fail.allow_gc() { + if !allow_blocking_for_gc { return Address::ZERO; } // Otherwise do GC here debug!("Collection required"); - assert!(allow_gc, "GC is not allowed here: collection is not initialized (did you call initialize_collection()?)."); + assert!(allow_blocking_for_gc, "GC is not allowed here: collection is not initialized (did you call initialize_collection()?)."); // Inform GC trigger about the pending allocation. let meta_pages_reserved = self.estimate_side_meta_pages(pages_reserved); @@ -258,13 +269,13 @@ pub trait Space: 'static + SFT + Sync + Downcast { pr.clear_request(pages_reserved); // If we do not want GC on fail, just return zero. - if !alloc_options.on_fail.allow_gc() { + if !allow_blocking_for_gc { return Address::ZERO; } // We thought we had memory to allocate, but somehow failed the allocation. Will force a GC. assert!( - allow_gc, + allow_blocking_for_gc, "Physical allocation failed when GC is not allowed!" ); diff --git a/src/util/alloc/allocator.rs b/src/util/alloc/allocator.rs index 4e53ab953d..54298ea779 100644 --- a/src/util/alloc/allocator.rs +++ b/src/util/alloc/allocator.rs @@ -28,45 +28,54 @@ pub enum AllocationError { MmapOutOfMemory, } -/// Behavior when an allocation fails, and a GC is expected. -#[repr(u8)] -#[derive(Copy, Clone, Default, PartialEq, bytemuck::NoUninit, Debug)] -pub enum OnAllocationFail { - /// Request the GC. This is the default behavior. - #[default] - RequestGC, - /// Instead of requesting GC, the allocation request returns with a failure value. - ReturnFailure, - /// Instead of requesting GC, the allocation request simply overcommits the memory, - /// and return a valid result at its best efforts. - OverCommit, -} - -impl OnAllocationFail { - pub(crate) fn allow_oom_call(&self) -> bool { - *self == Self::RequestGC - } - pub(crate) fn allow_gc(&self) -> bool { - *self == Self::RequestGC - } - pub(crate) fn allow_overcommit(&self) -> bool { - *self == Self::OverCommit - } -} - /// Allow specifying different behaviors with [`Allocator::alloc_with_options`]. #[repr(C)] -#[derive(Copy, Clone, Default, PartialEq, bytemuck::NoUninit, Debug)] +#[derive(Copy, Clone, PartialEq, bytemuck::NoUninit, Debug)] pub struct AllocationOptions { - /// When the allocation fails and a GC is originally expected, on_fail - /// allows a different behavior to avoid the GC. - pub on_fail: OnAllocationFail, + /// Whether polling is allowed. + /// + /// If true, the allocation attempt will give the GC trigger a chance to schedule a collection + /// if the GC trigger considers it needed. + /// + /// If false, the allocation attempt will not notify the GC trigger, and GC will not be + /// scheduled. + pub allow_polling: bool, + /// Whether the allocation is at a safepoint. + /// + /// If true, the allocation attempt will block for GC if GC is triggered. It will also call + /// [`Collection::out_of_memory`] when out of memory. + /// + /// If false, the allocation attempt will never block for GC, and it will never call + /// [`Collection::out_of_memory`]. Instead it returns an null address when GC is triggerted. + pub at_safepoint: bool, + /// Whether over-committing is allowed at this allocation site. Only meaningful if + /// `allow_polling == false`. + /// + /// If true, the allocation attempt will still try to allocate even if the GC trigger has + /// triggered a GC. + pub allow_overcommit: bool, +} + +/// The default value for `AllocationOptions` has the same semantics as calling [`Allocator::alloc`] +/// directly. +impl Default for AllocationOptions { + fn default() -> Self { + Self { + allow_polling: true, + at_safepoint: true, + allow_overcommit: false, + } + } } impl AllocationOptions { pub(crate) fn is_default(&self) -> bool { *self == AllocationOptions::default() } + + pub(crate) fn allow_oom_call(&self) -> bool { + self.at_safepoint + } } pub fn align_allocation_no_fill( @@ -378,9 +387,7 @@ pub trait Allocator: Downcast { return result; } - if result.is_zero() - && self.get_context().get_alloc_options().on_fail == OnAllocationFail::ReturnFailure - { + if result.is_zero() && !self.get_context().get_alloc_options().allow_oom_call() { return result; } diff --git a/src/util/alloc/mod.rs b/src/util/alloc/mod.rs index d3148dceed..2f905a913d 100644 --- a/src/util/alloc/mod.rs +++ b/src/util/alloc/mod.rs @@ -6,7 +6,6 @@ pub use allocator::fill_alignment_gap; pub use allocator::AllocationError; pub use allocator::AllocationOptions; pub use allocator::Allocator; -pub use allocator::OnAllocationFail; /// A list of all the allocators, embedded in Mutator pub(crate) mod allocators;