Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ask from binding if GC is disabled #1075

Merged
merged 19 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
16 changes: 14 additions & 2 deletions benches/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@ use criterion::Criterion;

use mmtk::memory_manager;
use mmtk::util::test_util::fixtures::*;
use mmtk::util::test_util::mock_method::*;
use mmtk::util::test_util::mock_vm::{write_mockvm, MockVM};
use mmtk::AllocationSemantics;

pub fn bench(c: &mut Criterion) {
// Disable GC so we won't trigger GC
// Setting a larger heap, although the GC should be disabled in the MockVM
let mut fixture = MutatorFixture::create_with_heapsize(1 << 30);
memory_manager::disable_collection(fixture.mmtk());
{
write_mockvm(|mock| {
*mock = {
MockVM {
is_collection_enabled: MockMethod::new_fixed(Box::new(|_| false)),
..MockVM::default()
}
}
});
}

c.bench_function("alloc", |b| {
b.iter(|| {
let _addr =
Expand Down
6 changes: 0 additions & 6 deletions docs/header/mmtk.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ extern void mmtk_flush_mutator(MMTk_Mutator mutator);
// Initialize MMTk scheduler and GC workers
extern void mmtk_initialize_collection(void* tls);

// Allow MMTk to perform a GC when the heap is full
extern void mmtk_enable_collection();

// Disallow MMTk to perform a GC when the heap is full
extern void mmtk_disable_collection();

// Allocate memory for an object
extern void* mmtk_alloc(MMTk_Mutator mutator,
size_t size,
Expand Down
9 changes: 0 additions & 9 deletions src/global_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ use std::sync::Mutex;
pub struct GlobalState {
/// Whether MMTk is now ready for collection. This is set to true when initialize_collection() is called.
pub(crate) initialized: AtomicBool,
/// Should we trigger a GC when the heap is full? It seems this should always be true. However, we allow
/// bindings to temporarily disable GC, at which point, we do not trigger GC even if the heap is full.
pub(crate) trigger_gc_when_heap_is_full: AtomicBool,
/// The current GC status.
pub(crate) gc_status: Mutex<GcStatus>,
/// Is the current GC an emergency collection? Emergency means we may run out of memory soon, and we should
Expand Down Expand Up @@ -54,11 +51,6 @@ impl GlobalState {
self.initialized.load(Ordering::SeqCst)
}

/// Should MMTK trigger GC when heap is full? If GC is disabled, we wont trigger GC even if the heap is full.
pub fn should_trigger_gc_when_heap_is_full(&self) -> bool {
self.trigger_gc_when_heap_is_full.load(Ordering::SeqCst)
}

/// Set the collection kind for the current GC. This is called before
/// scheduling collection to determin what kind of collection it will be.
pub fn set_collection_kind(
Expand Down Expand Up @@ -202,7 +194,6 @@ impl Default for GlobalState {
fn default() -> Self {
Self {
initialized: AtomicBool::new(false),
trigger_gc_when_heap_is_full: AtomicBool::new(true),
gc_status: Mutex::new(GcStatus::NotInGC),
stacks_prepared: AtomicBool::new(false),
emergency_collection: AtomicBool::new(false),
Expand Down
43 changes: 3 additions & 40 deletions src/memory_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ use std::sync::atomic::Ordering;
/// 2. Set command line options for MMTKBuilder by [`crate::memory_manager::process`] or [`crate::memory_manager::process_bulk`].
/// 3. Initialize MMTk by calling this function, `mmtk_init()`, and pass the builder earlier. This call will return an MMTK instance.
/// Usually a binding store the MMTK instance statically as a singleton. We plan to allow multiple instances, but this is not yet fully
/// supported. Currently we assume a binding will only need one MMTk instance.
/// 4. Enable garbage collection in MMTk by [`crate::memory_manager::enable_collection`]. A binding should only call this once its
/// thread system is ready. MMTk will not trigger garbage collection before this call.
/// supported. Currently we assume a binding will only need one MMTk instance. Note that GC is enabled by default and the binding should
/// implement `VMCollection::is_collection_enabled()` if it requires that the GC should be disabled at a particular time.
///
/// Note that this method will attempt to initialize a logger. If the VM would like to use its own logger, it should initialize the logger before calling this method.
/// Note that, to allow MMTk to do GC properly, `initialize_collection()` needs to be called after this call when
Expand Down Expand Up @@ -454,7 +453,7 @@ pub fn gc_poll<VM: VMBinding>(mmtk: &MMTK<VM>, tls: VMMutatorThread) {
"gc_poll() can only be called by a mutator thread."
);

if mmtk.state.should_trigger_gc_when_heap_is_full() && mmtk.gc_trigger.poll(false, None) {
if VM::VMCollection::is_collection_enabled() && mmtk.gc_trigger.poll(false, None) {
debug!("Collection required");
assert!(mmtk.state.is_initialized(), "GC is not allowed here: collection is not initialized (did you call initialize_collection()?).");
VM::VMCollection::block_for_gc(tls);
Expand Down Expand Up @@ -510,42 +509,6 @@ pub fn initialize_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>, tls: VMThre
probe!(mmtk, collection_initialized);
}

/// Allow MMTk to trigger garbage collection when heap is full. This should only be used in pair with disable_collection().
/// See the comments on disable_collection(). If disable_collection() is not used, there is no need to call this function at all.
/// Note this call is not thread safe, only one VM thread should call this.
///
/// Arguments:
/// * `mmtk`: A reference to an MMTk instance.
pub fn enable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
debug_assert!(
!mmtk.state.should_trigger_gc_when_heap_is_full(),
"enable_collection() is called when GC is already enabled."
);
mmtk.state
.trigger_gc_when_heap_is_full
.store(true, Ordering::SeqCst);
}

/// Disallow MMTk to trigger garbage collection. When collection is disabled, you can still allocate through MMTk. But MMTk will
/// not trigger a GC even if the heap is full. In such a case, the allocation will exceed the MMTk's heap size (the soft heap limit).
/// However, there is no guarantee that the physical allocation will succeed, and if it succeeds, there is no guarantee that further allocation
/// will keep succeeding. So if a VM disables collection, it needs to allocate with careful consideration to make sure that the physical memory
/// allows the amount of allocation. We highly recommend not using this method. However, we support this to accomodate some VMs that require this
/// behavior. This call does not disable explicit GCs (through handle_user_collection_request()).
/// Note this call is not thread safe, only one VM thread should call this.
///
/// Arguments:
/// * `mmtk`: A reference to an MMTk instance.
pub fn disable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
debug_assert!(
mmtk.state.should_trigger_gc_when_heap_is_full(),
"disable_collection() is called when GC is not enabled."
);
mmtk.state
.trigger_gc_when_heap_is_full
.store(false, Ordering::SeqCst);
}

/// Process MMTk run-time options. Returns true if the option is processed successfully.
///
/// Arguments:
Expand Down
2 changes: 1 addition & 1 deletion src/mmtk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ impl<VM: VMBinding> MMTK<VM> {
return;
}

if force || !*self.options.ignore_system_gc {
if force || !*self.options.ignore_system_gc && VM::VMCollection::is_collection_enabled() {
info!("User triggering collection");
if exhaustive {
if let Some(gen) = self.get_plan().generational() {
Expand Down
7 changes: 2 additions & 5 deletions src/policy/space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,8 @@ 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.
let should_poll = VM::VMActivePlan::is_mutator(tls)
&& self
.common()
.global_state
.should_trigger_gc_when_heap_is_full();
let should_poll =
VM::VMActivePlan::is_mutator(tls) && VM::VMCollection::is_collection_enabled();
// 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 && self.common().global_state.is_initialized();
Expand Down
6 changes: 6 additions & 0 deletions src/util/test_util/mock_vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ pub struct MockVM {
pub schedule_finalization: MockMethod<VMWorkerThread, ()>,
pub post_forwarding: MockMethod<VMWorkerThread, ()>,
pub vm_live_bytes: MockMethod<(), usize>,
pub is_collection_enabled: MockMethod<(), bool>,
// object model
pub copy_object: MockMethod<
(
Expand Down Expand Up @@ -280,6 +281,7 @@ impl Default for MockVM {
schedule_finalization: MockMethod::new_default(),
post_forwarding: MockMethod::new_default(),
vm_live_bytes: MockMethod::new_default(),
is_collection_enabled: MockMethod::new_fixed(Box::new(|_| true)),

copy_object: MockMethod::new_unimplemented(),
copy_object_to: MockMethod::new_unimplemented(),
Expand Down Expand Up @@ -450,6 +452,10 @@ impl crate::vm::Collection<MockVM> for MockVM {
mock!(post_forwarding(tls))
}

fn is_collection_enabled() -> bool {
mock!(is_collection_enabled())
}

fn vm_live_bytes() -> usize {
mock!(vm_live_bytes())
}
Expand Down
19 changes: 19 additions & 0 deletions src/vm/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,23 @@ pub trait Collection<VM: VMBinding> {
// By default, MMTk assumes the amount of memory the VM allocates off-heap is negligible.
0
}

/// Callback function to ask the VM whether GC is enabled or disabled, allowing or disallowing MMTk
/// to trigger garbage collection. When collection is disabled, you can still allocate through MMTk,
/// but MMTk will not trigger a GC even if the heap is full. In such a case, the allocation will
/// exceed MMTk's heap size (the soft heap limit). However, there is no guarantee that the physical
/// allocation will succeed, and if it succeeds, there is no guarantee that further allocation will
/// keep succeeding. So if a VM disables collection, it needs to allocate with careful consideration
/// to make sure that the physical memory allows the amount of allocation. We highly recommend
/// to have GC always enabled (i.e. that this method always returns true). However, we support
/// this to accomodate some VMs that require this behavior. Note that
/// `handle_user_collection_request()` calls this function, too. If this function returns
/// false, `handle_user_collection_request()` will not trigger GC, either. Note also that any synchronization
/// involving enabling and disabling collections by mutator threads should be implemented by the VM.
fn is_collection_enabled() -> bool {
// By default, MMTk assumes that collections are always enabled, and the binding should define
// this method if the VM supports disabling GC, or if the VM cannot safely trigger GC until some
// initialization is done, such as initializing class metadata for scanning objects.
true
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use crate::memory_manager;
use crate::util::test_util::fixtures::*;
use crate::util::test_util::mock_vm::*;
use crate::vm::tests::mock_tests::mock_test_prelude::MockMethod;
use crate::AllocationSemantics;

/// This test allocates after calling disable_collection(). When we exceed the heap limit, MMTk will NOT trigger a GC.
/// And the allocation will succeed.
#[test]
pub fn allocate_with_disable_collection() {
with_mockvm(
default_setup,
|| -> MockVM {
MockVM {
is_collection_enabled: MockMethod::new_fixed(Box::new(|_| false)),
..MockVM::default()
}
},
|| {
// 1MB heap
const MB: usize = 1024 * 1024;
Expand All @@ -24,8 +30,6 @@ pub fn allocate_with_disable_collection() {
);
assert!(!addr.is_zero());

// Disable GC
memory_manager::disable_collection(fixture.mmtk());
// Allocate another MB. This exceeds the heap size. But as we have disabled GC, MMTk will not trigger a GC, and allow this allocation.
let addr =
memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::util::test_util::mock_method::*;
use crate::util::test_util::mock_vm::*;
use crate::AllocationSemantics;

// This test allocates after calling initialize_collection(). When we exceed the heap limit, MMTk will trigger a GC. And block_for_gc will be called.
// We havent implemented block_for_gc so it will panic. This test is similar to allocate_with_initialize_collection, except that we once disabled GC in the test.
/// This test allocates after calling `initialize_collection()`. When we exceed the heap limit for the first time, MMTk will not trigger GC since GC has been disabled
/// However, the second 1MB allocation will trigger a GC since GC is enabled again. And `block_for_gc` will be called.
/// We haven't implemented `block_for_gc` so it will panic. This test is similar to `allocate_with_initialize_collection`, except that GC is disabled once in the test.
#[test]
#[should_panic(expected = "block_for_gc is called")]
pub fn allocate_with_re_enable_collection() {
Expand All @@ -14,6 +15,11 @@ pub fn allocate_with_re_enable_collection() {
|| -> MockVM {
MockVM {
block_for_gc: MockMethod::new_fixed(Box::new(|_| panic!("block_for_gc is called"))),
is_collection_enabled: MockMethod::new_sequence(vec![
Box::new(|()| -> bool { true }), // gc is enabled but it shouldn't matter here
Box::new(|()| -> bool { false }), // gc is disabled
Box::new(|()| -> bool { true }), // gc is enabled again
]),
..MockVM::default()
}
},
Expand All @@ -31,29 +37,22 @@ pub fn allocate_with_re_enable_collection() {
);
assert!(!addr.is_zero());

// Disable GC. So we can keep allocate without triggering a GC.
memory_manager::disable_collection(fixture.mmtk());
// In the next allocation GC is disabled. So we can keep allocate without triggering a GC.
// Fill up the heap
let _ = memory_manager::alloc(
&mut fixture.mutator,
MB >> 1,
8,
0,
AllocationSemantics::Default,
);
let _ =
memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default);

// Enable GC again.
memory_manager::enable_collection(fixture.mmtk());
// Attempt another allocation. This will trigger GC.
// Attempt another allocation. This will trigger GC since GC is enabled again.
let addr =
memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default);
assert!(!addr.is_zero());
},
|| {
// This is actually redundant, as we defined block_for_gc for this test.
// This just demostrates that we can check if the method is called.
// This ensures that block_for_gc is called for this test, and that the second allocation
// does not trigger GC since we expect is_collection_enabled to be called three times.
read_mockvm(|mock| {
assert!(mock.block_for_gc.is_called());
assert!(mock.is_collection_enabled.call_count() == 3);
});
},
)
Expand Down
Loading