diff --git a/.github/scripts/ci-common.sh b/.github/scripts/ci-common.sh index b4e4e007c8..35e450bdbd 100644 --- a/.github/scripts/ci-common.sh +++ b/.github/scripts/ci-common.sh @@ -11,6 +11,7 @@ dummyvm_toml=$project_root/docs/dummyvm/Cargo.toml # Pin certain deps for our MSRV cargo update -p home@0.5.11 --precise 0.5.5 # This can be removed once we move to Rust 1.81 or newer +cargo update -p home@0.5.12 --precise 0.5.5 # This requires Rust edition 2024 # Repeat a command for all the features. Requires the command as one argument (with double quotes) for_all_features() { diff --git a/.github/scripts/ci-style.sh b/.github/scripts/ci-style.sh index 0482321b52..4bad010819 100755 --- a/.github/scripts/ci-style.sh +++ b/.github/scripts/ci-style.sh @@ -39,9 +39,9 @@ else fi # mock tests - cargo clippy --features mock_test - cargo clippy --features mock_test --tests - cargo clippy --features mock_test --benches + cargo clippy --features mock_test,mock_test_side_metadata + cargo clippy --features mock_test,mock_test_side_metadata --tests + cargo clippy --features mock_test,mock_test_side_metadata --benches # non-mock benchmarks cargo clippy --features test_private --benches diff --git a/.github/scripts/ci-test.sh b/.github/scripts/ci-test.sh index bbf660de38..6fe1936963 100755 --- a/.github/scripts/ci-test.sh +++ b/.github/scripts/ci-test.sh @@ -38,7 +38,8 @@ find ./src ./tests -type f -name "mock_test_*" | while read -r file; do # Run the test with each plan it needs. for MMTK_PLAN in $PLANS; do - env MMTK_PLAN=$MMTK_PLAN cargo test --features mock_test,"$FEATURES" -- $t; + # Currently run all tests with side metadata + env MMTK_PLAN=$MMTK_PLAN cargo test --features mock_test,mock_test_side_metadata,"$FEATURES" -- $t; done done diff --git a/Cargo.toml b/Cargo.toml index 22233eb428..f449620012 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ static_assertions = "1.1.0" strum = "0.27.1" strum_macros = "0.27.1" sysinfo = "0.33.1" +thread-id = { version = "5.0.0", optional = true } # Only used by mock VM [dev-dependencies] paste = "1.0.8" @@ -116,7 +117,9 @@ perf_counter = ["dep:pfm"] # This feature is only used for tests with MockVM. # CI scripts run those tests with this feature. -mock_test = ["test_private"] +mock_test = ["test_private", "dep:thread-id"] +mock_test_header_metadata = ["mock_test"] +mock_test_side_metadata = ["mock_test"] # This feature will expose some private functions for testings or benchmarking. test_private = [] diff --git a/benches/mock_bench/alloc.rs b/benches/mock_bench/alloc.rs index 9a771cf05f..a6e1a068b6 100644 --- a/benches/mock_bench/alloc.rs +++ b/benches/mock_bench/alloc.rs @@ -2,28 +2,23 @@ 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::util::test_util::mock_vm::*; use mmtk::AllocationSemantics; pub fn bench(c: &mut Criterion) { - // Setting a larger heap, although the GC should be disabled in the MockVM - let mut fixture = MutatorFixture::create_with_heapsize(1 << 30); { - write_mockvm(|mock| { - *mock = { - MockVM { - is_collection_enabled: MockMethod::new_fixed(Box::new(|_| false)), - ..MockVM::default() - } - } + init_mockvm(MockVM { + is_collection_enabled: MockMethod::new_fixed(Box::new(|_| false)), + ..MockVM::default() }); } + // Setting a larger heap, although the GC should be disabled in the MockVM + let fixture = MutatorFixture::create_with_heapsize(1 << 30); c.bench_function("alloc", |b| { b.iter(|| { let _addr = - memory_manager::alloc(&mut fixture.mutator, 8, 8, 0, AllocationSemantics::Default); + memory_manager::alloc(fixture.mutator(), 8, 8, 0, AllocationSemantics::Default); }) }); } diff --git a/benches/mock_bench/internal_pointer.rs b/benches/mock_bench/internal_pointer.rs index edc22f8ef3..eeeb97d97b 100644 --- a/benches/mock_bench/internal_pointer.rs +++ b/benches/mock_bench/internal_pointer.rs @@ -2,8 +2,7 @@ use criterion::Criterion; #[cfg(feature = "is_mmtk_object")] 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::util::test_util::mock_vm::*; pub fn bench(c: &mut Criterion) { // Setting a larger heap, although the GC should be disabled in the MockVM @@ -27,7 +26,7 @@ pub fn bench(c: &mut Criterion) { use mmtk::memory_manager; use mmtk::AllocationSemantics; let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), NORMAL_OBJECT_SIZE, 8, 0, @@ -35,7 +34,7 @@ pub fn bench(c: &mut Criterion) { ); let obj_ref = MockVM::object_start_to_ref(addr); memory_manager::post_alloc( - &mut fixture.mutator, + fixture.mutator(), obj_ref, NORMAL_OBJECT_SIZE, AllocationSemantics::Default, @@ -65,7 +64,7 @@ pub fn bench(c: &mut Criterion) { use mmtk::memory_manager; use mmtk::AllocationSemantics; let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), LARGE_OBJECT_SIZE, 8, 0, @@ -73,7 +72,7 @@ pub fn bench(c: &mut Criterion) { ); let obj_ref = MockVM::object_start_to_ref(addr); memory_manager::post_alloc( - &mut fixture.mutator, + fixture.mutator(), obj_ref, LARGE_OBJECT_SIZE, AllocationSemantics::Los, diff --git a/benches/mock_bench/mmapper.rs b/benches/mock_bench/mmapper.rs index ba0cfbb4e1..a48f99e93a 100644 --- a/benches/mock_bench/mmapper.rs +++ b/benches/mock_bench/mmapper.rs @@ -9,23 +9,17 @@ use mmtk::{ }; pub fn bench(c: &mut Criterion) { - let mut fixture = MutatorFixture::create_with_heapsize(1 << 30); + let fixture = MutatorFixture::create_with_heapsize(1 << 30); let regular = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), 40, 0, 0, mmtk::AllocationSemantics::Default, ); - let large = memory_manager::alloc( - &mut fixture.mutator, - 40, - 0, - 0, - mmtk::AllocationSemantics::Los, - ); + let large = memory_manager::alloc(fixture.mutator(), 40, 0, 0, mmtk::AllocationSemantics::Los); let low = unsafe { Address::from_usize(42usize) }; let high = unsafe { Address::from_usize(usize::MAX - 1024usize) }; diff --git a/benches/mock_bench/sft.rs b/benches/mock_bench/sft.rs index 7f58f4aa5f..a1bac7f040 100644 --- a/benches/mock_bench/sft.rs +++ b/benches/mock_bench/sft.rs @@ -7,8 +7,8 @@ use mmtk::util::test_util::mock_vm::*; use mmtk::AllocationSemantics; pub fn bench(c: &mut Criterion) { - let mut fixture = MutatorFixture::create(); - let addr = memory_manager::alloc(&mut fixture.mutator, 8, 8, 0, AllocationSemantics::Default); + let fixture = MutatorFixture::create(); + let addr = memory_manager::alloc(fixture.mutator(), 8, 8, 0, AllocationSemantics::Default); let obj = MockVM::object_start_to_ref(addr); c.bench_function("sft read", |b| { diff --git a/src/policy/space.rs b/src/policy/space.rs index bc016f245d..1ed6a0da9a 100644 --- a/src/policy/space.rs +++ b/src/policy/space.rs @@ -723,6 +723,7 @@ impl CommonSpace { .try_map_metadata_address_range(rtn.start, rtn.extent, rtn.name) .unwrap_or_else(|e| { // TODO(Javad): handle meta space allocation failure + warn!("{}", memory::get_process_memory_maps()); panic!("failed to mmap meta memory: {e}"); }); diff --git a/src/util/heap/monotonepageresource.rs b/src/util/heap/monotonepageresource.rs index c30cb010cc..f0d0e7f9b2 100644 --- a/src/util/heap/monotonepageresource.rs +++ b/src/util/heap/monotonepageresource.rs @@ -261,15 +261,19 @@ impl MonotonePageResource { } }*/ - pub fn reset_cursor(&self, top: Address) { + // top might be Address::ZERO + pub fn reset_cursor(&self, mut top: Address) { if self.common.contiguous { let mut guard = self.sync.lock().unwrap(); - let cursor = top.align_up(crate::util::constants::BYTES_IN_PAGE); - let chunk = chunk_align_down(top); let space_start = match guard.conditional { MonotonePageResourceConditional::Contiguous { start, .. } => start, _ => unreachable!(), }; + if top.is_zero() { + top = space_start; + } + let cursor = top.align_up(crate::util::constants::BYTES_IN_PAGE); + let chunk = chunk_align_down(top); let pages = bytes_to_pages_up(top - space_start); self.common.accounting.reset(); self.common.accounting.reserve_and_commit(pages); @@ -284,6 +288,9 @@ impl MonotonePageResource { + (self.common.vm_map.get_contiguous_region_chunks(chunk_start) << LOG_BYTES_IN_CHUNK); let next_chunk_start = self.common.vm_map.get_next_contiguous_region(chunk_start); + if top.is_zero() { + top = chunk_start; + } if top >= chunk_start && top < chunk_end { // This is the last live chunk debug_assert!(!release_regions); diff --git a/src/util/linear_scan.rs b/src/util/linear_scan.rs index ec39379afd..8902bf8c40 100644 --- a/src/util/linear_scan.rs +++ b/src/util/linear_scan.rs @@ -51,6 +51,10 @@ impl std fn next(&mut self) -> Option<::Item> { while self.cursor < self.end { + if !self.cursor.is_aligned_to(ObjectReference::ALIGNMENT) { + self.cursor += VM::MIN_ALIGNMENT; + continue; + } let is_object = if ATOMIC_LOAD_VO_BIT { vo_bit::is_vo_bit_set_for_addr(self.cursor) } else { diff --git a/src/util/metadata/side_metadata/constants.rs b/src/util/metadata/side_metadata/constants.rs index f04c94d9d1..fc0a0b20cb 100644 --- a/src/util/metadata/side_metadata/constants.rs +++ b/src/util/metadata/side_metadata/constants.rs @@ -21,11 +21,16 @@ pub const GLOBAL_SIDE_METADATA_BASE_ADDRESS: Address = unsafe { Address::from_us // is less likely to overlap with any space. But it does not solve the problem completely. // If there are more spaces, it will still overlap with some spaces. // See: https://github.com/mmtk/mmtk-core/issues/458 -#[cfg(target_pointer_width = "64")] +#[cfg(all(target_pointer_width = "64", not(feature = "mock_test")))] /// Global side metadata start address pub const GLOBAL_SIDE_METADATA_BASE_ADDRESS: Address = unsafe { Address::from_usize(0x0000_0c00_0000_0000usize) }; +#[cfg(all(target_pointer_width = "64", feature = "mock_test"))] +/// Global side metadata start address +pub const GLOBAL_SIDE_METADATA_BASE_ADDRESS: Address = + unsafe { Address::from_usize(0x0000_0100_0000_0000usize) }; + pub(crate) const GLOBAL_SIDE_METADATA_BASE_OFFSET: SideMetadataOffset = SideMetadataOffset::addr(GLOBAL_SIDE_METADATA_BASE_ADDRESS); diff --git a/src/util/metadata/side_metadata/helpers.rs b/src/util/metadata/side_metadata/helpers.rs index 24e5366dc8..7ea0a4965f 100644 --- a/src/util/metadata/side_metadata/helpers.rs +++ b/src/util/metadata/side_metadata/helpers.rs @@ -155,6 +155,14 @@ pub(super) fn try_mmap_contiguous_metadata_space( ) } .map(|_| mmap_size) + .map_err(|e| { + warn!( + "Failed to mmap metadata space: {} - {}", + mmap_start, + mmap_start + mmap_size + ); + e + }) } else { Ok(0) } diff --git a/src/util/metadata/side_metadata/helpers_32.rs b/src/util/metadata/side_metadata/helpers_32.rs index 1372d5916c..7937a9a3a4 100644 --- a/src/util/metadata/side_metadata/helpers_32.rs +++ b/src/util/metadata/side_metadata/helpers_32.rs @@ -198,4 +198,12 @@ pub(super) fn try_mmap_metadata_chunk( anno, ) } + .map_err(|e| { + warn!( + "Failed to mmap per-chunk metadata: {} - {}", + policy_meta_start, + policy_meta_start + local_per_chunk + ); + e + }) } diff --git a/src/util/opaque_pointer.rs b/src/util/opaque_pointer.rs index 1a16b98c04..df9fa1e447 100644 --- a/src/util/opaque_pointer.rs +++ b/src/util/opaque_pointer.rs @@ -5,7 +5,7 @@ use libc::c_void; /// For example, a pointer to the thread or the thread local storage is an opaque pointer for MMTK. /// The type does not provide any method for dereferencing. #[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct OpaquePointer(*mut c_void); // We never really dereference an opaque pointer in mmtk-core. @@ -44,7 +44,7 @@ impl OpaquePointer { /// so the VM knows the context. /// A VMThread may be a VMMutatorThread, a VMWorkerThread, or any VMThread. #[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct VMThread(pub OpaquePointer); impl VMThread { @@ -56,12 +56,12 @@ impl VMThread { /// When a VMMutatorThread is used as an argument or a field of a type, it generally means /// the function or the functions for the type is executed in the context of the mutator thread. #[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct VMMutatorThread(pub VMThread); /// A VMWorkerThread is a VMThread that is associates with a [`crate::scheduler::GCWorker`]. /// When a VMWorkerThread is used as an argument or a field of a type, it generally means /// the function or the functions for the type is executed in the context of the mutator thread. #[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct VMWorkerThread(pub VMThread); diff --git a/src/util/test_util/fixtures.rs b/src/util/test_util/fixtures.rs index f5aed33660..9567eda7b7 100644 --- a/src/util/test_util/fixtures.rs +++ b/src/util/test_util/fixtures.rs @@ -12,6 +12,8 @@ use crate::AllocationSemantics; use crate::MMTKBuilder; use crate::MMTK; +use crate::util::test_util::mock_vm::mock_api; + pub trait FixtureContent { fn create() -> Self; } @@ -114,9 +116,7 @@ impl Default for SerialFixture { } } -pub struct MMTKFixture { - mmtk: *mut MMTK, -} +pub struct MMTKFixture; impl FixtureContent for MMTKFixture { fn create() -> Self { @@ -143,28 +143,22 @@ impl MMTKFixture { let mmtk = memory_manager::mmtk_init(&builder); let mmtk_ptr = Box::into_raw(mmtk); + mock_api::set_singleton(mmtk_ptr); if initialize_collection { let mmtk_static: &'static MMTK = unsafe { &*mmtk_ptr }; memory_manager::initialize_collection(mmtk_static, VMThread::UNINITIALIZED); } - MMTKFixture { mmtk: mmtk_ptr } + MMTKFixture } pub fn get_mmtk(&self) -> &'static MMTK { - unsafe { &*self.mmtk } + mock_api::singleton() } pub fn get_mmtk_mut(&mut self) -> &'static mut MMTK { - unsafe { &mut *self.mmtk } - } -} - -impl Drop for MMTKFixture { - fn drop(&mut self) { - let mmtk_ptr: *const MMTK = self.mmtk as _; - let _ = unsafe { Box::from_raw(mmtk_ptr as *mut MMTK) }; + mock_api::singleton_mut() } } @@ -172,7 +166,7 @@ use crate::plan::Mutator; pub struct MutatorFixture { mmtk: MMTKFixture, - pub mutator: Box>, + mutator: VMMutatorThread, } impl FixtureContent for MutatorFixture { @@ -193,8 +187,7 @@ impl MutatorFixture { }, true, ); - let mutator = - memory_manager::bind_mutator(mmtk.get_mmtk(), VMMutatorThread(VMThread::UNINITIALIZED)); + let mutator = mock_api::bind_mutator(); Self { mmtk, mutator } } @@ -203,14 +196,21 @@ impl MutatorFixture { F: FnOnce(&mut MMTKBuilder), { let mmtk = MMTKFixture::create_with_builder(with_builder, true); - let mutator = - memory_manager::bind_mutator(mmtk.get_mmtk(), VMMutatorThread(VMThread::UNINITIALIZED)); + let mutator = mock_api::bind_mutator(); Self { mmtk, mutator } } pub fn mmtk(&self) -> &'static MMTK { self.mmtk.get_mmtk() } + + pub fn mutator(&self) -> &'static mut Mutator { + self.mutator.as_mock_mutator() + } + + pub fn mutator_tls(&self) -> VMMutatorThread { + self.mutator + } } unsafe impl Send for MutatorFixture {} @@ -222,17 +222,17 @@ pub struct SingleObject { impl FixtureContent for SingleObject { fn create() -> Self { - let mut mutator = MutatorFixture::create(); + let mutator = MutatorFixture::create(); // A relatively small object, typical for Ruby. let size = 40; let semantics = AllocationSemantics::Default; - let addr = memory_manager::alloc(&mut mutator.mutator, size, 8, 0, semantics); + let addr = memory_manager::alloc(mutator.mutator(), size, 8, 0, semantics); assert!(!addr.is_zero()); let objref = MockVM::object_start_to_ref(addr); - memory_manager::post_alloc(&mut mutator.mutator, objref, size, semantics); + memory_manager::post_alloc(mutator.mutator(), objref, size, semantics); SingleObject { objref, mutator } } @@ -240,11 +240,11 @@ impl FixtureContent for SingleObject { impl SingleObject { pub fn mutator(&self) -> &Mutator { - &self.mutator.mutator + self.mutator.mutator() } pub fn mutator_mut(&mut self) -> &mut Mutator { - &mut self.mutator.mutator + self.mutator.mutator() } } @@ -256,22 +256,22 @@ pub struct TwoObjects { impl FixtureContent for TwoObjects { fn create() -> Self { - let mut mutator = MutatorFixture::create(); + let mutator = MutatorFixture::create(); let size = 128; let semantics = AllocationSemantics::Default; - let addr1 = memory_manager::alloc(&mut mutator.mutator, size, 8, 0, semantics); + let addr1 = memory_manager::alloc(mutator.mutator(), size, 8, 0, semantics); assert!(!addr1.is_zero()); let objref1 = MockVM::object_start_to_ref(addr1); - memory_manager::post_alloc(&mut mutator.mutator, objref1, size, semantics); + memory_manager::post_alloc(mutator.mutator(), objref1, size, semantics); - let addr2 = memory_manager::alloc(&mut mutator.mutator, size, 8, 0, semantics); + let addr2 = memory_manager::alloc(mutator.mutator(), size, 8, 0, semantics); assert!(!addr2.is_zero()); let objref2 = MockVM::object_start_to_ref(addr2); - memory_manager::post_alloc(&mut mutator.mutator, objref2, size, semantics); + memory_manager::post_alloc(mutator.mutator(), objref2, size, semantics); TwoObjects { objref1, diff --git a/src/util/test_util/mock_vm/mock_api.rs b/src/util/test_util/mock_vm/mock_api.rs new file mode 100644 index 0000000000..f67ac21cbf --- /dev/null +++ b/src/util/test_util/mock_vm/mock_api.rs @@ -0,0 +1,42 @@ +//! This module includes the MMTK singleton for MockVM, and some wrapped APIs that interact with MockVM. +//! When this module provides a wrapped API, mock tests should use the wrapped API instead of +//! the APIs from [`crate:memory_manager`]. For example, [`bind_mutator`] is provided here as a wrapped API +//! which not only calls [`crate::memory_manager::bind_mutator`], but also registers the returned mutator +//! to MockVM. + +use super::vm; +use super::MockVM; +use crate::util::*; +use crate::MMTK; + +/// A singleton MMTK instance for MockVM. +pub static mut MMTK_SINGLETON: *mut MMTK = std::ptr::null_mut(); + +/// Get the singleton MMTK instance for MockVM. +pub fn singleton() -> &'static MMTK { + unsafe { + assert!(!MMTK_SINGLETON.is_null(), "MMTK singleton is not set"); + &*MMTK_SINGLETON + } +} + +/// Get a mutable reference to the singleton MMTK instance for MockVM. +pub fn singleton_mut() -> &'static mut MMTK { + unsafe { + assert!(!MMTK_SINGLETON.is_null(), "MMTK singleton is not set"); + &mut *MMTK_SINGLETON + } +} + +/// Set the singleton MMTK instance for MockVM. This method should only be called once. +pub fn set_singleton(mmtk_ptr: *mut MMTK) { + unsafe { + assert!(MMTK_SINGLETON.is_null(), "MMTK singleton is already set"); + MMTK_SINGLETON = mmtk_ptr; + } +} + +/// Bind a mutator thread to the MMTK singleton instance for MockVM. +pub fn bind_mutator() -> VMMutatorThread { + vm::MutatorHandle::bind() +} diff --git a/src/util/test_util/mock_method.rs b/src/util/test_util/mock_vm/mock_method.rs similarity index 100% rename from src/util/test_util/mock_method.rs rename to src/util/test_util/mock_vm/mock_method.rs diff --git a/src/util/test_util/mock_vm/mod.rs b/src/util/test_util/mock_vm/mod.rs new file mode 100644 index 0000000000..98c4b2afc7 --- /dev/null +++ b/src/util/test_util/mock_vm/mod.rs @@ -0,0 +1,9 @@ +mod vm; +pub use vm::*; + +mod mock_method; +pub use mock_method::*; + +mod thread_park; + +pub mod mock_api; diff --git a/src/util/test_util/mock_vm/thread_park.rs b/src/util/test_util/mock_vm/thread_park.rs new file mode 100644 index 0000000000..7cdb601dc8 --- /dev/null +++ b/src/util/test_util/mock_vm/thread_park.rs @@ -0,0 +1,106 @@ +use crate::util::VMThread; +use std::collections::HashMap; +use std::sync::{Arc, Condvar, Mutex}; + +#[derive(Clone)] +pub struct ThreadPark { + name: &'static str, + inner: Arc, +} + +struct Inner { + lock: Mutex, + cvar: Condvar, +} + +#[derive(Default)] +struct State { + /// All registered parked and whether they are currently parked. + parked: HashMap, +} + +impl ThreadPark { + pub fn new(name: &'static str) -> Self { + Self { + name, + inner: Arc::new(Inner { + lock: Mutex::new(State::default()), + cvar: Condvar::new(), + }), + } + } + + /// Register the current thread for coordination. + pub fn register(&self, tid: VMThread) { + debug!("Register {:?} to {}", tid, self.name); + let mut state = self.inner.lock.lock().unwrap(); + state.parked.insert(tid, false); + } + + pub fn unregister(&self, tid: VMThread) { + let mut state = self.inner.lock.lock().unwrap(); + state.parked.remove(&tid); + } + + pub fn is_thread(&self, tid: VMThread) -> bool { + let state = self.inner.lock.lock().unwrap(); + state.parked.contains_key(&tid) + } + + pub fn number_of_threads(&self) -> usize { + let state = self.inner.lock.lock().unwrap(); + state.parked.len() + } + + pub fn all_threads(&self) -> Vec { + let state = self.inner.lock.lock().unwrap(); + state.parked.keys().cloned().collect() + } + + /// Park the current thread (set its state = parked and wait for unpark_all()). + pub fn park(&self, tid: VMThread) { + let mut state = self.inner.lock.lock().unwrap(); + + // Mark this thread as parked + if let Some(entry) = state.parked.get_mut(&tid) { + *entry = true; + } else { + panic!( + "Thread {:?} not registered to {} before park() f", + tid, self.name + ); + } + + // Notify any waiter that one more thread has parked + self.inner.cvar.notify_all(); + + // Wait until unpark_all() is called + state = self.inner.cvar.wait(state).unwrap(); + + // Mark this thread as unparked again + if let Some(entry) = state.parked.get_mut(&tid) { + *entry = false; + } + } + + /// Unpark all registered threads (wake everyone up). + pub fn unpark_all(&self) { + let mut state = self.inner.lock.lock().unwrap(); + for v in state.parked.values_mut() { + *v = false; + } + self.inner.cvar.notify_all(); + } + + /// Block until all registered threads are parked. + pub fn wait_all_parked(&self) { + let mut state = self.inner.lock.lock().unwrap(); + loop { + let all_parked = !state.parked.is_empty() && state.parked.values().all(|&v| v); + if all_parked { + break; + } + state = self.inner.cvar.wait(state).unwrap(); + } + } +} diff --git a/src/util/test_util/mock_vm.rs b/src/util/test_util/mock_vm/vm.rs similarity index 71% rename from src/util/test_util/mock_vm.rs rename to src/util/test_util/mock_vm/vm.rs index cc687f4b1f..701d021916 100644 --- a/src/util/test_util/mock_vm.rs +++ b/src/util/test_util/mock_vm/vm.rs @@ -10,6 +10,8 @@ use crate::util::alloc::AllocationError; use crate::util::copy::*; use crate::util::heap::gc_trigger::GCTriggerPolicy; use crate::util::opaque_pointer::*; +use crate::util::test_util; +use crate::util::test_util::mock_vm::thread_park::ThreadPark; use crate::util::{Address, ObjectReference}; use crate::vm::object_model::specs::*; use crate::vm::GCThreadContext; @@ -21,21 +23,17 @@ use crate::vm::VMBinding; use crate::Mutator; use super::mock_method::*; +use crate::util::test_util::mock_vm::mock_api; use std::default::Default; use std::ops::Range; -use std::sync::Mutex; /// The offset between object reference and the allocation address if we use /// the default mock VM. pub const DEFAULT_OBJECT_REF_OFFSET: usize = crate::util::constants::BYTES_IN_ADDRESS; -// To mock static methods, we have to create a static instance of `MockVM`. -lazy_static! { - // The mutex may get poisoned any time. Accessing this mutex needs to deal with the poisoned case. - // One can use read/write_mockvm to access mock vm. - static ref MOCK_VM_INSTANCE: Mutex = Mutex::new(MockVM::default()); -} +/// To mock VMBinding methods, we have to create a static instance of `MockVM`. +pub static mut MOCK_VM_INSTANCE: *mut MockVM = std::ptr::null_mut(); // MockVM only allows mock methods with references of no lifetime or static lifetime. // If `VMBinding` methods has references of a specific lifetime, @@ -60,31 +58,37 @@ macro_rules! mock { }; } /// Call `MockAny`. +#[allow(unused_macros)] // This macro is unused for now. macro_rules! mock_any { ($fn: ident($($arg:expr),*)) => { *write_mockvm(|mock| mock.$fn.call_any(Box::new(($($arg),*)))).downcast().unwrap() }; } +/// Initialize the static MockVM instance. +pub fn init_mockvm(mockvm: MockVM) { + unsafe { + if !MOCK_VM_INSTANCE.is_null() { + warn!("MockVM is already initialized. Overwriting the existing instance. This may change the behavior of MockVM."); + } + let boxed = Box::new(mockvm); + MOCK_VM_INSTANCE = Box::into_raw(boxed); + } +} + /// Read from the static MockVM instance. It deals with the case of a poisoned lock. pub fn read_mockvm(func: F) -> R where F: FnOnce(&MockVM) -> R, { - let lock = MOCK_VM_INSTANCE - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - func(&lock) + func(unsafe { &*MOCK_VM_INSTANCE }) } /// Write to the static MockVM instance. It deals with the case of a poisoned lock. pub fn write_mockvm(func: F) -> R where F: FnOnce(&mut MockVM) -> R, { - let mut lock = MOCK_VM_INSTANCE - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()); - func(&mut lock) + func(unsafe { &mut *MOCK_VM_INSTANCE }) } /// A test that uses `MockVM` should use this method to wrap the entire test @@ -102,12 +106,34 @@ where T: FnOnce() + std::panic::UnwindSafe, C: FnOnce(), { - super::serial_test(|| { + test_util::serial_test(|| { + { + use std::panic; + let orig_hook = panic::take_hook(); + panic::set_hook(Box::new(move |panic_info| { + let current_tls = current_thread_tls(); + if GC_THREADS.is_thread(current_tls) { + use std::backtrace::Backtrace; + let bt = Backtrace::force_capture(); + + // If this is a GC thread, we make the whole process abort. + error!( + "Panic occurred in GC thread with MockVM. Aborting the process. \n{}", + panic_info + ); + error!("Backtrace:\n{}", bt); + std::process::exit(1); + } else { + // invoke the default handler + orig_hook(panic_info); + } + })); + } // Setup { - write_mockvm(|mock| *mock = setup()); + init_mockvm(setup()); } - super::with_cleanup(test, cleanup); + test_util::with_cleanup(test, cleanup); }) } @@ -263,21 +289,122 @@ pub struct MockVM { pub forward_weak_refs: Box, } +/// This struct is used to hold a pointer to a `Mutator`. +/// The pointer to this struct is used as the 'mutator tls' pointer for MMTK. +#[derive(Clone)] +pub struct MutatorHandle { + pub ptr: *mut Mutator, +} + +impl MutatorHandle { + pub fn bind() -> VMMutatorThread { + let mmtk = mock_api::singleton(); + + let mutator_handle = Box::new(MutatorHandle { + ptr: std::ptr::null_mut(), + }); + let mutator_handle_ptr = Box::into_raw(mutator_handle); + let tls = VMMutatorThread(VMThread(OpaquePointer::from_address( + Address::from_mut_ptr(mutator_handle_ptr), + ))); + + let mutator = crate::memory_manager::bind_mutator(mmtk, tls); + let mutator_ptr = Box::into_raw(mutator); + + unsafe { + (*mutator_handle_ptr).ptr = mutator_ptr; + } + + MUTATOR_PARK.register(tls.0); + tls + } + + pub fn as_mutator(&self) -> &'static mut Mutator { + unsafe { &mut *self.ptr } + } +} + +unsafe impl Sync for MutatorHandle {} +unsafe impl Send for MutatorHandle {} + +impl VMMutatorThread { + /// Get a mutable reference to the underlying Mutator. + pub fn as_mock_mutator(self) -> &'static mut Mutator { + unsafe { &mut *(*self.0 .0.to_address().to_mut_ptr::()).ptr } + } +} + +lazy_static! { + pub static ref MUTATOR_PARK: ThreadPark = ThreadPark::new("mutators"); + // We never really park GC threads. We just reuse this struct to track GC threads. + pub static ref GC_THREADS: ThreadPark = ThreadPark::new("gc workers"); +} + +fn current_thread_tls() -> VMThread { + VMThread(OpaquePointer::from_address(unsafe { + Address::from_usize(thread_id::get()) + })) +} + impl Default for MockVM { fn default() -> Self { Self { - number_of_mutators: MockMethod::new_unimplemented(), - is_mutator: MockMethod::new_fixed(Box::new(|_| true)), - mutator: MockMethod::new_unimplemented(), - mutators: MockMethod::new_unimplemented(), + number_of_mutators: MockMethod::new_fixed(Box::new(|()| { + // Just return the number of registered mutator threads + MUTATOR_PARK.number_of_threads() + })), + is_mutator: MockMethod::new_fixed(Box::new(|tls: VMThread| { + MUTATOR_PARK.is_thread(tls) + })), + mutator: MockMethod::new_fixed(Box::new(|tls| tls.as_mock_mutator())), + mutators: MockMethod::new_fixed(Box::new(|()| { + // Just return an iterator over all registered mutators + let mutators: Vec<&'static mut Mutator> = MUTATOR_PARK + .all_threads() + .into_iter() + .map(|tls| VMMutatorThread(tls).as_mock_mutator()) + .collect(); + Box::new(mutators.into_iter()) + })), vm_trace_object: MockMethod::new_fixed(Box::new(|(_, object, _)| { panic!("MMTk cannot trace object {:?} as it does not belong to any MMTk space. If the object is known to the VM, the binding can override this method and handle its tracing.", object) })), - stop_all_mutators: MockMethod::new_unimplemented(), - resume_mutators: MockMethod::new_unimplemented(), - block_for_gc: MockMethod::new_unimplemented(), - spawn_gc_thread: MockMethod::new_default(), + stop_all_mutators: MockMethod::new_fixed(Box::new(|(_tls, mut mutator_visitor)| { + info!("Waiting for all threads to park..."); + MUTATOR_PARK.wait_all_parked(); + info!("All threads are parked."); + + MUTATOR_PARK + .all_threads() + .into_iter() + .for_each(|tls| mutator_visitor(VMMutatorThread(tls).as_mock_mutator())); + })), + resume_mutators: MockMethod::new_fixed(Box::new(|_tls| { + info!("Resuming all parked threads..."); + MUTATOR_PARK.unpark_all(); + })), + block_for_gc: MockMethod::new_fixed(Box::new(|tls| { + MUTATOR_PARK.park(tls.0); + })), + spawn_gc_thread: MockMethod::new_fixed(Box::new(|(_parent_tls, ctx)| { + // Just drop the join handle. The thread will run until the process quits. + let _ = std::thread::Builder::new() + .name("MMTk Worker".to_string()) + .spawn(move || { + // Start the worker loop + let worker_tls = VMWorkerThread(current_thread_tls()); + GC_THREADS.register(worker_tls.0); + match ctx { + GCThreadContext::Worker(w) => crate::memory_manager::start_worker( + mock_api::singleton(), + worker_tls, + w, + ), + } + GC_THREADS.unregister(worker_tls.0); + }); + })), out_of_memory: MockMethod::new_fixed(Box::new(|(_, err)| { panic!("Out of memory with {:?}!", err) })), @@ -343,9 +470,11 @@ impl Default for MockVM { ), (), >::new_unimplemented()), - notify_initial_thread_scan_complete: MockMethod::new_unimplemented(), + notify_initial_thread_scan_complete: MockMethod::new_fixed(Box::new(|(_, _)| {})), supports_return_barrier: MockMethod::new_unimplemented(), - prepare_for_roots_re_scanning: MockMethod::new_unimplemented(), + prepare_for_roots_re_scanning: MockMethod::new_fixed(Box::new(|_| { + warn!("prepare_for_roots_re_scanning called on MockVM, it is empty at the moment."); + })), // Same here: the `MockMethod` is just a place holder. See the above comments. process_weak_refs: Box::new(MockMethod::< ( @@ -462,15 +591,42 @@ impl crate::vm::Collection for MockVM { } } +#[cfg(feature = "mock_test_header_metadata")] +mod header_metadata { + use super::*; + pub const MOCK_VM_GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = VMGlobalLogBitSpec::in_header(0); + pub const MOCK_VM_LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = + VMLocalForwardingBitsSpec::in_header(0); + pub const MOCK_VM_LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = VMLocalMarkBitSpec::in_header(0); + pub const MOCK_VM_LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = + VMLocalLOSMarkNurserySpec::in_header(0); +} +#[cfg(feature = "mock_test_header_metadata")] +use header_metadata::*; + +#[cfg(feature = "mock_test_side_metadata")] +mod side_metadata { + use super::*; + pub const MOCK_VM_GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = VMGlobalLogBitSpec::side_first(); + pub const MOCK_VM_LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = + VMLocalForwardingBitsSpec::side_first(); + pub const MOCK_VM_LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = + VMLocalMarkBitSpec::side_after(MOCK_VM_LOCAL_FORWARDING_BITS_SPEC.as_spec()); + pub const MOCK_VM_LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = + VMLocalLOSMarkNurserySpec::side_after(MOCK_VM_LOCAL_MARK_BIT_SPEC.as_spec()); +} +#[cfg(feature = "mock_test_side_metadata")] +use side_metadata::*; + impl crate::vm::ObjectModel for MockVM { - const GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = VMGlobalLogBitSpec::in_header(0); + const GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = MOCK_VM_GLOBAL_LOG_BIT_SPEC; const LOCAL_FORWARDING_POINTER_SPEC: VMLocalForwardingPointerSpec = VMLocalForwardingPointerSpec::in_header(0); const LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = - VMLocalForwardingBitsSpec::in_header(0); - const LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = VMLocalMarkBitSpec::in_header(0); + MOCK_VM_LOCAL_FORWARDING_BITS_SPEC; + const LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = MOCK_VM_LOCAL_MARK_BIT_SPEC; const LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = - VMLocalLOSMarkNurserySpec::in_header(0); + MOCK_VM_LOCAL_LOS_MARK_NURSERY_SPEC; #[cfg(feature = "object_pinning")] const LOCAL_PINNING_BIT_SPEC: VMLocalPinningBitSpec = VMLocalPinningBitSpec::in_header(0); @@ -572,21 +728,23 @@ impl crate::vm::Scanning for MockVM { )) } fn scan_roots_in_mutator_thread( - tls: VMWorkerThread, - mutator: &'static mut Mutator, - factory: impl RootsWorkFactory<::VMSlot>, + _tls: VMWorkerThread, + _mutator: &'static mut Mutator, + _factory: impl RootsWorkFactory<::VMSlot>, ) { - mock_any!(scan_roots_in_mutator_thread( - tls, - mutator, - Box::new(factory) - )) + // mock_any!(scan_roots_in_mutator_thread( + // tls, + // mutator, + // Box::new(factory) + // )) + warn!("scan_roots_in_mutator_thread is not properly mocked. The default implementation does nothing."); } fn scan_vm_specific_roots( - tls: VMWorkerThread, - factory: impl RootsWorkFactory<::VMSlot>, + _tls: VMWorkerThread, + _factory: impl RootsWorkFactory<::VMSlot>, ) { - mock_any!(scan_vm_specific_roots(tls, Box::new(factory))) + // mock_any!(scan_vm_specific_roots(tls, Box::new(factory))) + warn!("scan_vm_specific_roots is not properly mocked. The default implementation does nothing."); } fn notify_initial_thread_scan_complete(partial_scan: bool, tls: VMWorkerThread) { mock!(notify_initial_thread_scan_complete(partial_scan, tls)) @@ -598,18 +756,21 @@ impl crate::vm::Scanning for MockVM { mock!(prepare_for_roots_re_scanning()) } fn process_weak_refs( - worker: &mut GCWorker, - tracer_context: impl ObjectTracerContext, + _worker: &mut GCWorker, + _tracer_context: impl ObjectTracerContext, ) -> bool { - let worker: &'static mut GCWorker = lifetime!(worker); - mock_any!(process_weak_refs(worker, tracer_context)) + // let worker: &'static mut GCWorker = lifetime!(worker); + // mock_any!(process_weak_refs(worker, tracer_context)) + warn!("process_weak_refs is not properly mocked. The default implementation does nothing."); + false } fn forward_weak_refs( - worker: &mut GCWorker, - tracer_context: impl ObjectTracerContext, + _worker: &mut GCWorker, + _tracer_context: impl ObjectTracerContext, ) { - let worker: &'static mut GCWorker = lifetime!(worker); - mock_any!(forward_weak_refs(worker, tracer_context)) + // let worker: &'static mut GCWorker = lifetime!(worker); + // mock_any!(forward_weak_refs(worker, tracer_context)) + warn!("forward_weak_refs is not properly mocked. The default implementation does nothing."); } } diff --git a/src/util/test_util/mod.rs b/src/util/test_util/mod.rs index 4540cee3b8..f34c4c8f18 100644 --- a/src/util/test_util/mod.rs +++ b/src/util/test_util/mod.rs @@ -9,8 +9,6 @@ use std::time::Duration; #[cfg(feature = "mock_test")] pub mod fixtures; #[cfg(feature = "mock_test")] -pub mod mock_method; -#[cfg(feature = "mock_test")] pub mod mock_vm; // Sometimes we need to mmap for tests. We want to ensure that the mmapped addresses do not overlap diff --git a/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs b/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs index bef39dcab0..37e979134f 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs @@ -22,7 +22,7 @@ pub fn allocate_alignment() { while align <= max { info!("Test allocation with alignment {}", align); let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), 8, align, 0, @@ -59,7 +59,7 @@ pub fn allocate_offset() { align, OFFSET ); let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), 8, align, OFFSET, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call.rs b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call.rs index 01766c19ec..fc101e72ce 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call.rs @@ -17,11 +17,11 @@ pub fn allocate_no_gc_oom_on_acquire_allow_oom_call() { }, || { const KB: usize = 1024; - let mut fixture = MutatorFixture::create_with_heapsize(KB); + let fixture = MutatorFixture::create_with_heapsize(KB); // Attempt to allocate an object that is larger than the heap size. let addr = memory_manager::alloc_with_options( - &mut fixture.mutator, + fixture.mutator(), 1024 * 10, 8, 0, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_no_oom_call.rs b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_no_oom_call.rs index d35635ce13..62b4537962 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_no_oom_call.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_oom_on_acquire_no_oom_call.rs @@ -12,11 +12,11 @@ pub fn allocate_no_gc_oom_on_acquire_no_oom_call() { default_setup, || { const KB: usize = 1024; - let mut fixture = MutatorFixture::create_with_heapsize(KB); + let fixture = MutatorFixture::create_with_heapsize(KB); // Attempt to allocate an object that is larger than the heap size. let addr = memory_manager::alloc_with_options( - &mut fixture.mutator, + fixture.mutator(), 1024 * 10, 8, 0, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_no_gc_simple.rs b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_simple.rs index 14a1dfc6d1..9f76d38cad 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_no_gc_simple.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_simple.rs @@ -12,7 +12,13 @@ pub fn allocate_no_gc_simple() { default_setup, || { const MB: usize = 1024 * 1024; - let mut fixture = MutatorFixture::create_with_heapsize(MB); + let fixture = MutatorFixture::create_with_heapsize(MB); + + if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC + { + // The current thread won't be blocked. But GC will still be triggered. For NoGC plan, triggering GC causes panic. + return; + } let mut last_result = crate::util::Address::MAX; @@ -20,7 +26,7 @@ pub fn allocate_no_gc_simple() { // Run a few more times to test if we set/unset no_gc_on_fail properly. for _ in 0..1100 { last_result = memory_manager::alloc_with_options( - &mut fixture.mutator, + fixture.mutator(), 1024, 8, 0, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs b/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs index 7c7df436fe..6209a317de 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs @@ -15,22 +15,17 @@ pub fn allocate_nonmoving() { || { // 1MB heap const MB: usize = 1024 * 1024; - let mut fixture = MutatorFixture::create_with_heapsize(MB); + let fixture = MutatorFixture::create_with_heapsize(MB); // Normal alloc let addr = - memory_manager::alloc(&mut fixture.mutator, 16, 8, 0, AllocationSemantics::Default); + memory_manager::alloc(fixture.mutator(), 16, 8, 0, AllocationSemantics::Default); assert!(!addr.is_zero()); info!("Allocated default at: {:#x}", addr); // Non moving alloc - let addr = memory_manager::alloc( - &mut fixture.mutator, - 16, - 8, - 0, - AllocationSemantics::NonMoving, - ); + let addr = + memory_manager::alloc(fixture.mutator(), 16, 8, 0, AllocationSemantics::NonMoving); assert!(!addr.is_zero()); info!("Allocated nonmoving at: {:#x}", addr); }, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_offset.rs b/src/vm/tests/mock_tests/mock_test_allocate_offset.rs new file mode 100644 index 0000000000..fa3af8ad99 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_allocate_offset.rs @@ -0,0 +1,47 @@ +// GITHUB-CI: MMTK_PLAN=all + +use lazy_static::lazy_static; + +use super::mock_test_prelude::*; +use crate::plan::AllocationSemantics; + +lazy_static! { + static ref MUTATOR: Fixture = Fixture::new(); +} + +#[test] +pub fn allocate_offset() { + with_mockvm( + default_setup, + || { + MUTATOR.with_fixture_mut(|fixture| { + const OFFSET: usize = 4; + let min = MockVM::MIN_ALIGNMENT; + let max = MockVM::MAX_ALIGNMENT; + info!("Allowed alignment between {} and {}", min, max); + let mut align = min; + while align <= max { + info!( + "Test allocation with alignment {} and offset {}", + align, OFFSET + ); + let addr = memory_manager::alloc( + fixture.mutator(), + 8, + align, + OFFSET, + AllocationSemantics::Default, + ); + assert!( + (addr + OFFSET).is_aligned_to(align), + "Expected allocation alignment {}, returned address is {:?}", + align, + addr + ); + align *= 2; + } + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs b/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs index 829381d96a..228ec8efe1 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs @@ -14,7 +14,13 @@ pub fn allocate_overcommit() { default_setup, || { const MB: usize = 1024 * 1024; - let mut fixture = MutatorFixture::create_with_heapsize(MB); + let fixture = MutatorFixture::create_with_heapsize(MB); + + if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC + { + // Overcommit still triggers GC. For NoGC plan, triggering GC causes panic. + return; + } let mut last_result = crate::util::Address::MAX; @@ -22,7 +28,7 @@ pub fn allocate_overcommit() { // Run a few more times to test if we set/unset no_gc_on_fail properly. for _ in 0..1100 { last_result = memory_manager::alloc_with_options( - &mut fixture.mutator, + fixture.mutator(), 1024, 8, 0, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_with_disable_collection.rs b/src/vm/tests/mock_tests/mock_test_allocate_with_disable_collection.rs index 373381649a..6fc8c2d25c 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_with_disable_collection.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_with_disable_collection.rs @@ -18,11 +18,11 @@ pub fn allocate_with_disable_collection() { || { // 1MB heap const MB: usize = 1024 * 1024; - let mut fixture = MutatorFixture::create_with_heapsize(MB); + let fixture = MutatorFixture::create_with_heapsize(MB); // Allocate half MB. It should be fine. let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), MB >> 1, 8, 0, @@ -32,7 +32,7 @@ pub fn allocate_with_disable_collection() { // 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); + memory_manager::alloc(fixture.mutator(), MB, 8, 0, AllocationSemantics::Default); assert!(!addr.is_zero()); }, no_cleanup, diff --git a/src/vm/tests/mock_tests/mock_test_allocate_with_initialize_collection.rs b/src/vm/tests/mock_tests/mock_test_allocate_with_initialize_collection.rs index 2ec8ab95e0..8556c59f50 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_with_initialize_collection.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_with_initialize_collection.rs @@ -16,11 +16,23 @@ pub fn allocate_with_initialize_collection() { }, || { const MB: usize = 1024 * 1024; - let mut fixture = MutatorFixture::create_with_heapsize(MB); + let fixture = MutatorFixture::create_with_heapsize(MB); + + if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC + { + // The test triggers GC, which causes a different panic message for NoGC plan. + // We just mimic that block_for_gc is called for NoGC + write_mockvm(|mock| { + use crate::util::VMMutatorThread; + use crate::util::VMThread; + mock.block_for_gc + .call(VMMutatorThread(VMThread::UNINITIALIZED)); + }); + } // Allocate half MB. It should be fine. let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), MB >> 1, 8, 0, @@ -30,7 +42,7 @@ pub fn allocate_with_initialize_collection() { // Fill up the heap let _ = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), MB >> 1, 8, 0, @@ -39,7 +51,7 @@ pub fn allocate_with_initialize_collection() { // Attempt another allocation. This will trigger GC. let addr = - memory_manager::alloc(&mut fixture.mutator, MB, 8, 0, AllocationSemantics::Default); + memory_manager::alloc(fixture.mutator(), MB, 8, 0, AllocationSemantics::Default); assert!(!addr.is_zero()); }, || { diff --git a/src/vm/tests/mock_tests/mock_test_allocate_with_re_enable_collection.rs b/src/vm/tests/mock_tests/mock_test_allocate_with_re_enable_collection.rs index 296e4a20a2..b8551f51f1 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_with_re_enable_collection.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_with_re_enable_collection.rs @@ -1,6 +1,5 @@ use crate::memory_manager; use crate::util::test_util::fixtures::*; -use crate::util::test_util::mock_method::*; use crate::util::test_util::mock_vm::*; use crate::AllocationSemantics; @@ -25,11 +24,26 @@ pub fn allocate_with_re_enable_collection() { }, || { const MB: usize = 1024 * 1024; - let mut fixture = MutatorFixture::create_with_heapsize(MB); + let fixture = MutatorFixture::create_with_heapsize(MB); + + if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC + { + // The test triggers GC, which causes a different panic message for NoGC plan. + // We just mimic that block_for_gc is called for NoGC + write_mockvm(|mock| { + use crate::util::VMMutatorThread; + use crate::util::VMThread; + mock.is_collection_enabled.call(()); + mock.is_collection_enabled.call(()); + mock.is_collection_enabled.call(()); + mock.block_for_gc + .call(VMMutatorThread(VMThread::UNINITIALIZED)); + }); + } // Allocate half MB. It should be fine. let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), MB >> 1, 8, 0, @@ -40,11 +54,11 @@ pub fn allocate_with_re_enable_collection() { // 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, 8, 0, AllocationSemantics::Default); + memory_manager::alloc(fixture.mutator(), MB, 8, 0, AllocationSemantics::Default); // 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); + memory_manager::alloc(fixture.mutator(), MB, 8, 0, AllocationSemantics::Default); assert!(!addr.is_zero()); }, || { diff --git a/src/vm/tests/mock_tests/mock_test_allocate_without_initialize_collection.rs b/src/vm/tests/mock_tests/mock_test_allocate_without_initialize_collection.rs index 1a4a22b422..baf3d9df52 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_without_initialize_collection.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_without_initialize_collection.rs @@ -1,6 +1,5 @@ use super::mock_test_prelude::*; -use crate::util::opaque_pointer::*; use crate::AllocationSemantics; /// This test allocates without calling initialize_collection(). When we exceed the heap limit, a GC should be triggered by MMTk. @@ -13,7 +12,7 @@ pub fn allocate_without_initialize_collection() { default_setup, || { const MB: usize = 1024 * 1024; - let fixture = MMTKFixture::create_with_builder( + let _fixture = MMTKFixture::create_with_builder( |builder| { builder .options @@ -24,22 +23,17 @@ pub fn allocate_without_initialize_collection() { ); // Do not initialize collection // Build mutator - let mut mutator = memory_manager::bind_mutator( - fixture.get_mmtk(), - VMMutatorThread(VMThread::UNINITIALIZED), - ); + let mutator = mock_api::bind_mutator().as_mock_mutator(); // Allocate half MB. It should be fine. - let addr = - memory_manager::alloc(&mut mutator, MB >> 1, 8, 0, AllocationSemantics::Default); + let addr = memory_manager::alloc(mutator, MB >> 1, 8, 0, AllocationSemantics::Default); assert!(!addr.is_zero()); // Fill up the heap - let _ = - memory_manager::alloc(&mut mutator, MB >> 1, 8, 0, AllocationSemantics::Default); + let _ = memory_manager::alloc(mutator, MB >> 1, 8, 0, AllocationSemantics::Default); // Attempt another allocation. - let addr = memory_manager::alloc(&mut mutator, MB, 8, 0, AllocationSemantics::Default); + let addr = memory_manager::alloc(mutator, MB, 8, 0, AllocationSemantics::Default); assert!(!addr.is_zero()); }, || { diff --git a/src/vm/tests/mock_tests/mock_test_gc.rs b/src/vm/tests/mock_tests/mock_test_gc.rs new file mode 100644 index 0000000000..ceb2ae863a --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_gc.rs @@ -0,0 +1,25 @@ +// GITHUB-CI: MMTK_PLAN=all + +use super::mock_test_prelude::*; +use crate::plan::AllocationSemantics; + +#[test] +pub fn simple_gc() { + with_mockvm( + default_setup, + || { + // 1MB heap + const MB: usize = 1024 * 1024; + let fixture = MutatorFixture::create_with_heapsize(MB); + + // Normal alloc + let addr = + memory_manager::alloc(fixture.mutator(), 16, 8, 0, AllocationSemantics::Default); + assert!(!addr.is_zero()); + info!("Allocated default at: {:#x}", addr); + + memory_manager::handle_user_collection_request(fixture.mmtk(), fixture.mutator_tls()); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mock_test_heap_traversal.rs b/src/vm/tests/mock_tests/mock_test_heap_traversal.rs index 430c46ff2a..46b842972a 100644 --- a/src/vm/tests/mock_tests/mock_test_heap_traversal.rs +++ b/src/vm/tests/mock_tests/mock_test_heap_traversal.rs @@ -37,7 +37,7 @@ pub fn test_heap_traversal() { let traversal0 = get_all_objects(mmtk); assert!(traversal0.is_empty()); - let mutator = &mut fixture.mutator; + let mutator = fixture.mutator(); let align = BYTES_IN_WORD; diff --git a/src/vm/tests/mock_tests/mock_test_init_fork.rs b/src/vm/tests/mock_tests/mock_test_init_fork.rs index 596ce6fe77..0d6b6e7d05 100644 --- a/src/vm/tests/mock_tests/mock_test_init_fork.rs +++ b/src/vm/tests/mock_tests/mock_test_init_fork.rs @@ -117,7 +117,8 @@ pub fn test_initialize_collection_and_fork() { })), ..Default::default() }; - write_mockvm(move |mock_vm_ref| *mock_vm_ref = mock_vm); + // write_mockvm(move |mock_vm_ref| *mock_vm_ref = mock_vm); + init_mockvm(mock_vm); let test_thread_tls = VMThread(OpaquePointer::from_address(Address::ZERO)); diff --git a/src/vm/tests/mock_tests/mock_test_internal_ptr_before_object_ref.rs b/src/vm/tests/mock_tests/mock_test_internal_ptr_before_object_ref.rs index 329ea776d3..d015d7f097 100644 --- a/src/vm/tests/mock_tests/mock_test_internal_ptr_before_object_ref.rs +++ b/src/vm/tests/mock_tests/mock_test_internal_ptr_before_object_ref.rs @@ -17,10 +17,10 @@ pub fn interior_pointer_before_object_ref() { } }, || { - let mut fixture = MutatorFixture::create_with_heapsize(10 * MB); + let fixture = MutatorFixture::create_with_heapsize(10 * MB); let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), OBJECT_SIZE, 8, 0, @@ -36,7 +36,7 @@ pub fn interior_pointer_before_object_ref() { obj, ); memory_manager::post_alloc( - &mut fixture.mutator, + fixture.mutator(), obj, OBJECT_SIZE, AllocationSemantics::Default, diff --git a/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_multi_page.rs b/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_multi_page.rs index 675b9a7095..78f897ba8d 100644 --- a/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_multi_page.rs +++ b/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_multi_page.rs @@ -20,10 +20,10 @@ pub fn interior_pointer_in_large_object() { } }, || { - let mut fixture = MutatorFixture::create_with_heapsize(10 * MB); + let fixture = MutatorFixture::create_with_heapsize(10 * MB); let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), OBJECT_SIZE, 8, 0, @@ -40,7 +40,7 @@ pub fn interior_pointer_in_large_object() { ); memory_manager::post_alloc( - &mut fixture.mutator, + fixture.mutator(), obj, OBJECT_SIZE, AllocationSemantics::Los, diff --git a/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_same_page.rs b/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_same_page.rs index 36fc9de6ed..2401933051 100644 --- a/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_same_page.rs +++ b/src/vm/tests/mock_tests/mock_test_internal_ptr_large_object_same_page.rs @@ -21,10 +21,10 @@ pub fn interior_pointer_in_large_object_same_page() { } }, || { - let mut fixture = MutatorFixture::create_with_heapsize(10 * MB); + let fixture = MutatorFixture::create_with_heapsize(10 * MB); let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), OBJECT_SIZE, 8, 0, @@ -41,7 +41,7 @@ pub fn interior_pointer_in_large_object_same_page() { ); memory_manager::post_alloc( - &mut fixture.mutator, + fixture.mutator(), obj, OBJECT_SIZE, AllocationSemantics::Los, diff --git a/src/vm/tests/mock_tests/mock_test_internal_ptr_normal_object.rs b/src/vm/tests/mock_tests/mock_test_internal_ptr_normal_object.rs index ad94f20fdb..8c6eb947b8 100644 --- a/src/vm/tests/mock_tests/mock_test_internal_ptr_normal_object.rs +++ b/src/vm/tests/mock_tests/mock_test_internal_ptr_normal_object.rs @@ -18,11 +18,11 @@ pub fn interior_pointer_in_normal_object() { } }, || { - let mut fixture = MutatorFixture::create_with_heapsize(10 * MB); + let fixture = MutatorFixture::create_with_heapsize(10 * MB); - let mut test_obj = || { + let test_obj = || { let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), OBJECT_SIZE, 8, 0, @@ -38,7 +38,7 @@ pub fn interior_pointer_in_normal_object() { obj, ); memory_manager::post_alloc( - &mut fixture.mutator, + fixture.mutator(), obj, OBJECT_SIZE, AllocationSemantics::Default, diff --git a/src/vm/tests/mock_tests/mock_test_issue139_allocate_non_multiple_of_min_alignment.rs b/src/vm/tests/mock_tests/mock_test_issue139_allocate_non_multiple_of_min_alignment.rs index 04061cb98e..af66a68e59 100644 --- a/src/vm/tests/mock_tests/mock_test_issue139_allocate_non_multiple_of_min_alignment.rs +++ b/src/vm/tests/mock_tests/mock_test_issue139_allocate_non_multiple_of_min_alignment.rs @@ -7,16 +7,16 @@ pub fn issue139_alloc_non_multiple_of_min_alignment() { with_mockvm( default_setup, || { - let mut fixture = MutatorFixture::create(); + let fixture = MutatorFixture::create(); // Allocate 6 bytes with 8 bytes ailgnment required let addr = - memory_manager::alloc(&mut fixture.mutator, 14, 8, 0, AllocationSemantics::Default); + memory_manager::alloc(fixture.mutator(), 14, 8, 0, AllocationSemantics::Default); assert!(addr.is_aligned_to(8)); // After the allocation, the cursor is not MIN_ALIGNMENT aligned. If we have the assertion in the next allocation to check if the cursor is aligned to MIN_ALIGNMENT, it fails. // We have to remove that assertion. let addr2 = - memory_manager::alloc(&mut fixture.mutator, 14, 8, 0, AllocationSemantics::Default); + memory_manager::alloc(fixture.mutator(), 14, 8, 0, AllocationSemantics::Default); assert!(addr2.is_aligned_to(8)); }, no_cleanup, diff --git a/src/vm/tests/mock_tests/mock_test_issue867_allocate_unrealistically_large_object.rs b/src/vm/tests/mock_tests/mock_test_issue867_allocate_unrealistically_large_object.rs index 5db0a65774..7e3f6258d1 100644 --- a/src/vm/tests/mock_tests/mock_test_issue867_allocate_unrealistically_large_object.rs +++ b/src/vm/tests/mock_tests/mock_test_issue867_allocate_unrealistically_large_object.rs @@ -42,7 +42,7 @@ pub fn allocate_max_size_object() { MUTATOR.with_fixture_mut(|fixture| { alloc_default_or_large( fixture.mmtk(), - &mut fixture.mutator, + fixture.mutator(), size, align, 0, @@ -68,7 +68,7 @@ pub fn allocate_max_size_object_after_succeed() { // Allocate something so we have a thread local allocation buffer alloc_default_or_large( fixture.mmtk(), - &mut fixture.mutator, + fixture.mutator(), 8, 8, 0, @@ -77,7 +77,7 @@ pub fn allocate_max_size_object_after_succeed() { // Allocate an unrealistically large object alloc_default_or_large( fixture.mmtk(), - &mut fixture.mutator, + fixture.mutator(), usize::MAX, 8, 0, @@ -105,7 +105,7 @@ pub fn allocate_unrealistically_large_object() { MUTATOR.with_fixture_mut(|fixture| { alloc_default_or_large( fixture.mmtk(), - &mut fixture.mutator, + fixture.mutator(), size, align, 0, @@ -127,7 +127,7 @@ pub fn allocate_more_than_heap_size() { MUTATOR.with_fixture_mut(|fixture| { alloc_default_or_large( fixture.mmtk(), - &mut fixture.mutator, + fixture.mutator(), 2 * 1024 * 1024, 8, 0, diff --git a/src/vm/tests/mock_tests/mock_test_nogc_lock_free.rs b/src/vm/tests/mock_tests/mock_test_nogc_lock_free.rs index e78d5e2169..f7f98cb0bd 100644 --- a/src/vm/tests/mock_tests/mock_test_nogc_lock_free.rs +++ b/src/vm/tests/mock_tests/mock_test_nogc_lock_free.rs @@ -19,7 +19,7 @@ pub fn nogc_lock_free_allocate() { while align <= max { info!("Test allocation with alignment {}", align); let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), 8, align, 0, diff --git a/src/vm/tests/mock_tests/mock_test_slots.rs b/src/vm/tests/mock_tests/mock_test_slots.rs index 397e0f1caf..dd9432d60d 100644 --- a/src/vm/tests/mock_tests/mock_test_slots.rs +++ b/src/vm/tests/mock_tests/mock_test_slots.rs @@ -353,7 +353,7 @@ mod mixed { pub enum DummyVMSlot { Simple(SimpleSlot), #[cfg(target_pointer_width = "64")] - Compressed(compressed_oop::CompressedOopSlot), + Compressed(CompressedOopSlot), Offset(OffsetSlot), Tagged(TaggedSlot), } diff --git a/src/vm/tests/mock_tests/mock_test_vm_layout_default.rs b/src/vm/tests/mock_tests/mock_test_vm_layout_default.rs index 8bd527d9d6..f8a2163276 100644 --- a/src/vm/tests/mock_tests/mock_test_vm_layout_default.rs +++ b/src/vm/tests/mock_tests/mock_test_vm_layout_default.rs @@ -6,7 +6,7 @@ use crate::util::heap::vm_layout::VMLayout; pub fn test_with_vm_layout(layout: Option) { use crate::plan::AllocationSemantics; - let mut fixture = MutatorFixture::create_with_builder(|builder| { + let fixture = MutatorFixture::create_with_builder(|builder| { // 1MB builder .options @@ -21,7 +21,7 @@ pub fn test_with_vm_layout(layout: Option) { }); // Test allocation - let addr = memory_manager::alloc(&mut fixture.mutator, 8, 8, 0, AllocationSemantics::Default); + let addr = memory_manager::alloc(fixture.mutator(), 8, 8, 0, AllocationSemantics::Default); let obj = MockVM::object_start_to_ref(addr); // Test SFT assert!(memory_manager::is_in_mmtk_spaces(obj)); diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index 9a11d03d03..b48272f4dd 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -18,7 +18,7 @@ pub(crate) mod mock_test_prelude { pub use crate::memory_manager; pub use crate::util::test_util::fixtures::*; - pub use crate::util::test_util::mock_method::*; + pub use crate::util::test_util::mock_vm::mock_api; pub use crate::util::test_util::mock_vm::*; pub use crate::vm::*; } @@ -38,6 +38,7 @@ mod mock_test_barrier_slow_path_assertion; #[cfg(feature = "is_mmtk_object")] mod mock_test_conservatism; mod mock_test_debug_get_object_info; +mod mock_test_gc; #[cfg(target_os = "linux")] mod mock_test_handle_mmap_conflict; mod mock_test_handle_mmap_oom;