Skip to content

Commit 7cafe56

Browse files
udesouqinsoonwks
authored
Ask from binding if GC is disabled (#1075)
MMTk currently supports disabling GC (a.k.a. allowing the heap to grow above its limit) via two api functions: [`disable_collection`](https://github.com/mmtk/mmtk-core/blob/ef2bd6d043d8675badaa415db89be7b52439725f/src/memory_manager.rs#L539) and [`enable_collection`](https://github.com/mmtk/mmtk-core/blob/ef2bd6d043d8675badaa415db89be7b52439725f/src/memory_manager.rs#L519). This PR replaces this strategy since currently, MMTk relies on the binding ensuring [thread safety](https://github.com/mmtk/mmtk-core/blob/ef2bd6d043d8675badaa415db89be7b52439725f/src/memory_manager.rs#L515) when calling those functions. In this refactoring, MMTk asks if GC is disabled in `VM::VMCollection::is_collection_disabled()` function, and expects any thread safety logic to be implemented in the VM itself, and not necessarily depend on MMTk. --------- Co-authored-by: Yi Lin <qinsoon@gmail.com> Co-authored-by: Kunshan Wang <wks1986@gmail.com>
1 parent 092b756 commit 7cafe56

10 files changed

+67
-82
lines changed

benches/alloc.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@ use criterion::Criterion;
22

33
use mmtk::memory_manager;
44
use mmtk::util::test_util::fixtures::*;
5+
use mmtk::util::test_util::mock_method::*;
6+
use mmtk::util::test_util::mock_vm::{write_mockvm, MockVM};
57
use mmtk::AllocationSemantics;
68

79
pub fn bench(c: &mut Criterion) {
8-
// Disable GC so we won't trigger GC
10+
// Setting a larger heap, although the GC should be disabled in the MockVM
911
let mut fixture = MutatorFixture::create_with_heapsize(1 << 30);
10-
memory_manager::disable_collection(fixture.mmtk());
12+
{
13+
write_mockvm(|mock| {
14+
*mock = {
15+
MockVM {
16+
is_collection_enabled: MockMethod::new_fixed(Box::new(|_| false)),
17+
..MockVM::default()
18+
}
19+
}
20+
});
21+
}
22+
1123
c.bench_function("alloc", |b| {
1224
b.iter(|| {
1325
let _addr =

docs/header/mmtk.h

-6
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@ extern void mmtk_flush_mutator(MMTk_Mutator mutator);
3434
// Initialize MMTk scheduler and GC workers
3535
extern void mmtk_initialize_collection(void* tls);
3636

37-
// Allow MMTk to perform a GC when the heap is full
38-
extern void mmtk_enable_collection();
39-
40-
// Disallow MMTk to perform a GC when the heap is full
41-
extern void mmtk_disable_collection();
42-
4337
// Allocate memory for an object
4438
extern void* mmtk_alloc(MMTk_Mutator mutator,
4539
size_t size,

src/global_state.rs

-9
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ use std::sync::Mutex;
1313
pub struct GlobalState {
1414
/// Whether MMTk is now ready for collection. This is set to true when initialize_collection() is called.
1515
pub(crate) initialized: AtomicBool,
16-
/// Should we trigger a GC when the heap is full? It seems this should always be true. However, we allow
17-
/// bindings to temporarily disable GC, at which point, we do not trigger GC even if the heap is full.
18-
pub(crate) trigger_gc_when_heap_is_full: AtomicBool,
1916
/// The current GC status.
2017
pub(crate) gc_status: Mutex<GcStatus>,
2118
/// Is the current GC an emergency collection? Emergency means we may run out of memory soon, and we should
@@ -54,11 +51,6 @@ impl GlobalState {
5451
self.initialized.load(Ordering::SeqCst)
5552
}
5653

57-
/// Should MMTK trigger GC when heap is full? If GC is disabled, we wont trigger GC even if the heap is full.
58-
pub fn should_trigger_gc_when_heap_is_full(&self) -> bool {
59-
self.trigger_gc_when_heap_is_full.load(Ordering::SeqCst)
60-
}
61-
6254
/// Set the collection kind for the current GC. This is called before
6355
/// scheduling collection to determin what kind of collection it will be.
6456
pub fn set_collection_kind(
@@ -202,7 +194,6 @@ impl Default for GlobalState {
202194
fn default() -> Self {
203195
Self {
204196
initialized: AtomicBool::new(false),
205-
trigger_gc_when_heap_is_full: AtomicBool::new(true),
206197
gc_status: Mutex::new(GcStatus::NotInGC),
207198
stacks_prepared: AtomicBool::new(false),
208199
emergency_collection: AtomicBool::new(false),

src/memory_manager.rs

+3-40
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ use std::sync::atomic::Ordering;
3535
/// 2. Set command line options for MMTKBuilder by [`crate::memory_manager::process`] or [`crate::memory_manager::process_bulk`].
3636
/// 3. Initialize MMTk by calling this function, `mmtk_init()`, and pass the builder earlier. This call will return an MMTK instance.
3737
/// Usually a binding store the MMTK instance statically as a singleton. We plan to allow multiple instances, but this is not yet fully
38-
/// supported. Currently we assume a binding will only need one MMTk instance.
39-
/// 4. Enable garbage collection in MMTk by [`crate::memory_manager::enable_collection`]. A binding should only call this once its
40-
/// thread system is ready. MMTk will not trigger garbage collection before this call.
38+
/// supported. Currently we assume a binding will only need one MMTk instance. Note that GC is enabled by default and the binding should
39+
/// implement `VMCollection::is_collection_enabled()` if it requires that the GC should be disabled at a particular time.
4140
///
4241
/// 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.
4342
/// Note that, to allow MMTk to do GC properly, `initialize_collection()` needs to be called after this call when
@@ -454,7 +453,7 @@ pub fn gc_poll<VM: VMBinding>(mmtk: &MMTK<VM>, tls: VMMutatorThread) {
454453
"gc_poll() can only be called by a mutator thread."
455454
);
456455

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

513-
/// Allow MMTk to trigger garbage collection when heap is full. This should only be used in pair with disable_collection().
514-
/// See the comments on disable_collection(). If disable_collection() is not used, there is no need to call this function at all.
515-
/// Note this call is not thread safe, only one VM thread should call this.
516-
///
517-
/// Arguments:
518-
/// * `mmtk`: A reference to an MMTk instance.
519-
pub fn enable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
520-
debug_assert!(
521-
!mmtk.state.should_trigger_gc_when_heap_is_full(),
522-
"enable_collection() is called when GC is already enabled."
523-
);
524-
mmtk.state
525-
.trigger_gc_when_heap_is_full
526-
.store(true, Ordering::SeqCst);
527-
}
528-
529-
/// Disallow MMTk to trigger garbage collection. When collection is disabled, you can still allocate through MMTk. But MMTk will
530-
/// 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).
531-
/// However, there is no guarantee that the physical allocation will succeed, and if it succeeds, there is no guarantee that further allocation
532-
/// will keep succeeding. So if a VM disables collection, it needs to allocate with careful consideration to make sure that the physical memory
533-
/// allows the amount of allocation. We highly recommend not using this method. However, we support this to accomodate some VMs that require this
534-
/// behavior. This call does not disable explicit GCs (through handle_user_collection_request()).
535-
/// Note this call is not thread safe, only one VM thread should call this.
536-
///
537-
/// Arguments:
538-
/// * `mmtk`: A reference to an MMTk instance.
539-
pub fn disable_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
540-
debug_assert!(
541-
mmtk.state.should_trigger_gc_when_heap_is_full(),
542-
"disable_collection() is called when GC is not enabled."
543-
);
544-
mmtk.state
545-
.trigger_gc_when_heap_is_full
546-
.store(false, Ordering::SeqCst);
547-
}
548-
549512
/// Process MMTk run-time options. Returns true if the option is processed successfully.
550513
///
551514
/// Arguments:

src/mmtk.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ impl<VM: VMBinding> MMTK<VM> {
318318
return;
319319
}
320320

321-
if force || !*self.options.ignore_system_gc {
321+
if force || !*self.options.ignore_system_gc && VM::VMCollection::is_collection_enabled() {
322322
info!("User triggering collection");
323323
if exhaustive {
324324
if let Some(gen) = self.get_plan().generational() {

src/policy/space.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,8 @@ pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
8686
// Should we poll to attempt to GC?
8787
// - If tls is collector, we cannot attempt a GC.
8888
// - If gc is disabled, we cannot attempt a GC.
89-
let should_poll = VM::VMActivePlan::is_mutator(tls)
90-
&& self
91-
.common()
92-
.global_state
93-
.should_trigger_gc_when_heap_is_full();
89+
let should_poll =
90+
VM::VMActivePlan::is_mutator(tls) && VM::VMCollection::is_collection_enabled();
9491
// Is a GC allowed here? If we should poll but are not allowed to poll, we will panic.
9592
// initialize_collection() has to be called so we know GC is initialized.
9693
let allow_gc = should_poll && self.common().global_state.is_initialized();

src/util/test_util/mock_vm.rs

+6
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ pub struct MockVM {
204204
pub schedule_finalization: MockMethod<VMWorkerThread, ()>,
205205
pub post_forwarding: MockMethod<VMWorkerThread, ()>,
206206
pub vm_live_bytes: MockMethod<(), usize>,
207+
pub is_collection_enabled: MockMethod<(), bool>,
207208
// object model
208209
pub copy_object: MockMethod<
209210
(
@@ -280,6 +281,7 @@ impl Default for MockVM {
280281
schedule_finalization: MockMethod::new_default(),
281282
post_forwarding: MockMethod::new_default(),
282283
vm_live_bytes: MockMethod::new_default(),
284+
is_collection_enabled: MockMethod::new_fixed(Box::new(|_| true)),
283285

284286
copy_object: MockMethod::new_unimplemented(),
285287
copy_object_to: MockMethod::new_unimplemented(),
@@ -450,6 +452,10 @@ impl crate::vm::Collection<MockVM> for MockVM {
450452
mock!(post_forwarding(tls))
451453
}
452454

455+
fn is_collection_enabled() -> bool {
456+
mock!(is_collection_enabled())
457+
}
458+
453459
fn vm_live_bytes() -> usize {
454460
mock!(vm_live_bytes())
455461
}

src/vm/collection.rs

+19
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,23 @@ pub trait Collection<VM: VMBinding> {
139139
// By default, MMTk assumes the amount of memory the VM allocates off-heap is negligible.
140140
0
141141
}
142+
143+
/// Callback function to ask the VM whether GC is enabled or disabled, allowing or disallowing MMTk
144+
/// to trigger garbage collection. When collection is disabled, you can still allocate through MMTk,
145+
/// but MMTk will not trigger a GC even if the heap is full. In such a case, the allocation will
146+
/// exceed MMTk's heap size (the soft heap limit). However, there is no guarantee that the physical
147+
/// allocation will succeed, and if it succeeds, there is no guarantee that further allocation will
148+
/// keep succeeding. So if a VM disables collection, it needs to allocate with careful consideration
149+
/// to make sure that the physical memory allows the amount of allocation. We highly recommend
150+
/// to have GC always enabled (i.e. that this method always returns true). However, we support
151+
/// this to accomodate some VMs that require this behavior. Note that
152+
/// `handle_user_collection_request()` calls this function, too. If this function returns
153+
/// false, `handle_user_collection_request()` will not trigger GC, either. Note also that any synchronization
154+
/// involving enabling and disabling collections by mutator threads should be implemented by the VM.
155+
fn is_collection_enabled() -> bool {
156+
// By default, MMTk assumes that collections are always enabled, and the binding should define
157+
// this method if the VM supports disabling GC, or if the VM cannot safely trigger GC until some
158+
// initialization is done, such as initializing class metadata for scanning objects.
159+
true
160+
}
142161
}

src/vm/tests/mock_tests/mock_test_allocate_with_disable_collection.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
use crate::memory_manager;
22
use crate::util::test_util::fixtures::*;
33
use crate::util::test_util::mock_vm::*;
4+
use crate::vm::tests::mock_tests::mock_test_prelude::MockMethod;
45
use crate::AllocationSemantics;
56

67
/// This test allocates after calling disable_collection(). When we exceed the heap limit, MMTk will NOT trigger a GC.
78
/// And the allocation will succeed.
89
#[test]
910
pub fn allocate_with_disable_collection() {
1011
with_mockvm(
11-
default_setup,
12+
|| -> MockVM {
13+
MockVM {
14+
is_collection_enabled: MockMethod::new_fixed(Box::new(|_| false)),
15+
..MockVM::default()
16+
}
17+
},
1218
|| {
1319
// 1MB heap
1420
const MB: usize = 1024 * 1024;
@@ -24,8 +30,6 @@ pub fn allocate_with_disable_collection() {
2430
);
2531
assert!(!addr.is_zero());
2632

27-
// Disable GC
28-
memory_manager::disable_collection(fixture.mmtk());
2933
// Allocate another MB. This exceeds the heap size. But as we have disabled GC, MMTk will not trigger a GC, and allow this allocation.
3034
let addr =
3135
memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default);

src/vm/tests/mock_tests/mock_test_allocate_with_re_enable_collection.rs

+15-16
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use crate::util::test_util::mock_method::*;
44
use crate::util::test_util::mock_vm::*;
55
use crate::AllocationSemantics;
66

7-
// 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.
8-
// 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.
7+
/// 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
8+
/// However, the second 1MB allocation will trigger a GC since GC is enabled again. And `block_for_gc` will be called.
9+
/// 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.
910
#[test]
1011
#[should_panic(expected = "block_for_gc is called")]
1112
pub fn allocate_with_re_enable_collection() {
@@ -14,6 +15,11 @@ pub fn allocate_with_re_enable_collection() {
1415
|| -> MockVM {
1516
MockVM {
1617
block_for_gc: MockMethod::new_fixed(Box::new(|_| panic!("block_for_gc is called"))),
18+
is_collection_enabled: MockMethod::new_sequence(vec![
19+
Box::new(|()| -> bool { true }), // gc is enabled but it shouldn't matter here
20+
Box::new(|()| -> bool { false }), // gc is disabled
21+
Box::new(|()| -> bool { true }), // gc is enabled again
22+
]),
1723
..MockVM::default()
1824
}
1925
},
@@ -31,29 +37,22 @@ pub fn allocate_with_re_enable_collection() {
3137
);
3238
assert!(!addr.is_zero());
3339

34-
// Disable GC. So we can keep allocate without triggering a GC.
35-
memory_manager::disable_collection(fixture.mmtk());
40+
// In the next allocation GC is disabled. So we can keep allocate without triggering a GC.
3641
// Fill up the heap
37-
let _ = memory_manager::alloc(
38-
&mut fixture.mutator,
39-
MB >> 1,
40-
8,
41-
0,
42-
AllocationSemantics::Default,
43-
);
42+
let _ =
43+
memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default);
4444

45-
// Enable GC again.
46-
memory_manager::enable_collection(fixture.mmtk());
47-
// Attempt another allocation. This will trigger GC.
45+
// Attempt another allocation. This will trigger GC since GC is enabled again.
4846
let addr =
4947
memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default);
5048
assert!(!addr.is_zero());
5149
},
5250
|| {
53-
// This is actually redundant, as we defined block_for_gc for this test.
54-
// This just demostrates that we can check if the method is called.
51+
// This ensures that block_for_gc is called for this test, and that the second allocation
52+
// does not trigger GC since we expect is_collection_enabled to be called three times.
5553
read_mockvm(|mock| {
5654
assert!(mock.block_for_gc.is_called());
55+
assert!(mock.is_collection_enabled.call_count() == 3);
5756
});
5857
},
5958
)

0 commit comments

Comments
 (0)