From 08ed6f21f7c9000293cc4b2c6a659f5375a58eab Mon Sep 17 00:00:00 2001 From: Kunshan Wang Date: Mon, 1 Sep 2025 15:01:29 +0800 Subject: [PATCH 1/2] Allow requesting GC while over-committing. This is useful if the VM cannot trigger GC at most of its allocation sites. If allocation over-commits while still triggering GC, the GC thread can pause the mutators at the nearest safepoints. --- src/policy/space.rs | 39 ++++++++++++++++++++++++------------- src/util/alloc/allocator.rs | 23 +++++++++++++++------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/policy/space.rs b/src/policy/space.rs index 654bcb22a6..807fa13cc4 100644 --- a/src/policy/space.rs +++ b/src/policy/space.rs @@ -108,17 +108,22 @@ 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. + // - If tls is collector, we shall not poll. + // - If gc is disabled, we shall not poll. + // - If the on_fail option does not allow polling, we shall not poll. 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 + && !alloc_options.on_fail.allow_polling(); + + // Can we continue to allocate even if GC is triggered? + let allow_overcommit = alloc_options.on_fail.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.on_fail.allow_blocking_for_gc(); trace!("Reserving pages"); let pr = self.get_page_resource(); @@ -126,18 +131,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); @@ -257,13 +270,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..290579c6d2 100644 --- a/src/util/alloc/allocator.rs +++ b/src/util/alloc/allocator.rs @@ -32,25 +32,34 @@ pub enum AllocationError { #[repr(u8)] #[derive(Copy, Clone, Default, PartialEq, bytemuck::NoUninit, Debug)] pub enum OnAllocationFail { - /// Request the GC. This is the default behavior. + /// Request the GC and block until GC finishes. This is the default behavior. #[default] RequestGC, - /// Instead of requesting GC, the allocation request returns with a failure value. + /// Request the GC. But instead of blocking for 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. + /// Instead of requesting GC, the allocation request simply overcommits the memory, and return a + /// valid result at its best efforts. GC worker threads will not be notified about the + /// allocation failure. OverCommit, + /// Request the GC. But instead of blocking for GC, the allocating thread continues to + /// allocate, overcommitting the memory. GC will be scheduled asynchronously by the GC worker + /// threads, and the current mutator may stop at a safepoint as soon as possible. + RequestAndOverCommit, } 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_polling(&self) -> bool { + *self != Self::OverCommit } pub(crate) fn allow_overcommit(&self) -> bool { - *self == Self::OverCommit + *self == Self::OverCommit || *self == Self::RequestAndOverCommit + } + pub(crate) fn allow_blocking_for_gc(&self) -> bool { + *self == Self::RequestGC } } From 77721b874c8e1b493637d1bf36d53fee0a5d6f59 Mon Sep 17 00:00:00 2001 From: Kunshan Wang Date: Mon, 1 Sep 2025 16:23:47 +0800 Subject: [PATCH 2/2] Fix typo --- src/policy/space.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/policy/space.rs b/src/policy/space.rs index 807fa13cc4..2e881b58b6 100644 --- a/src/policy/space.rs +++ b/src/policy/space.rs @@ -113,7 +113,7 @@ pub trait Space: 'static + SFT + Sync + Downcast { // - If the on_fail option does not allow polling, we shall not poll. let should_poll = VM::VMActivePlan::is_mutator(tls) && VM::VMCollection::is_collection_enabled() - && !alloc_options.on_fail.allow_polling(); + && alloc_options.on_fail.allow_polling(); // Can we continue to allocate even if GC is triggered? let allow_overcommit = alloc_options.on_fail.allow_overcommit();