Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/policy/lockfreeimmortalspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ impl<VM: VMBinding> Space<VM> for LockFreeImmortalSpace<VM> {
})
.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;
Expand Down
43 changes: 27 additions & 16 deletions src/policy/space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub trait Space<VM: VMBinding>: '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,
Expand All @@ -109,36 +109,47 @@ pub trait Space<VM: VMBinding>: '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();
let pages_reserved = pr.reserve_pages(pages);
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);
Expand Down Expand Up @@ -258,13 +269,13 @@ pub trait Space<VM: VMBinding>: '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!"
);

Expand Down
73 changes: 40 additions & 33 deletions src/util/alloc/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<VM: VMBinding>(
Expand Down Expand Up @@ -378,9 +387,7 @@ pub trait Allocator<VM: VMBinding>: 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;
}

Expand Down
1 change: 0 additions & 1 deletion src/util/alloc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading