From dfb210ad782a7b5d2a354e8ddfe72ee4f0666d99 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Tue, 21 Oct 2025 04:20:27 +0000 Subject: [PATCH 1/8] WIP --- .../test_util/{ => mock_vm}/mock_method.rs | 0 src/util/test_util/mock_vm/mod.rs | 7 ++++ src/util/test_util/mock_vm/thread_park.rs | 35 +++++++++++++++++++ .../test_util/{mock_vm.rs => mock_vm/vm.rs} | 14 ++++++-- src/util/test_util/mod.rs | 2 -- ...test_allocate_with_re_enable_collection.rs | 1 - src/vm/tests/mock_tests/mock_test_gc.rs | 26 ++++++++++++++ src/vm/tests/mock_tests/mod.rs | 2 +- 8 files changed, 80 insertions(+), 7 deletions(-) rename src/util/test_util/{ => mock_vm}/mock_method.rs (100%) create mode 100644 src/util/test_util/mock_vm/mod.rs create mode 100644 src/util/test_util/mock_vm/thread_park.rs rename src/util/test_util/{mock_vm.rs => mock_vm/vm.rs} (98%) create mode 100644 src/vm/tests/mock_tests/mock_test_gc.rs 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..71cd2da7c4 --- /dev/null +++ b/src/util/test_util/mock_vm/mod.rs @@ -0,0 +1,7 @@ +mod vm; +pub use vm::*; + +mod mock_method; +pub use mock_method::*; + +mod thread_park; 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..6d1743e662 --- /dev/null +++ b/src/util/test_util/mock_vm/thread_park.rs @@ -0,0 +1,35 @@ +use std::sync::{Arc, Condvar, Mutex}; + +#[derive(Clone)] +pub struct ThreadPark { + inner: Arc, +} + +struct Inner { + lock: Mutex<()>, + cvar: Condvar, +} + +impl ThreadPark { + pub fn new() -> Self { + Self { + inner: Arc::new(Inner { + lock: Mutex::new(()), + cvar: Condvar::new(), + }), + } + } + + /// Park the current thread until `unpark_all()` is called. + pub fn park(&self) { + let guard = self.inner.lock.lock().unwrap(); + // Wait until notified; condvar wait automatically unlocks/relocks the mutex. + // If spurious wakeups happen, the caller should re-check its condition. + let _unused = self.inner.cvar.wait(guard).unwrap(); + } + + /// Wake up all threads currently parked on `park()`. + pub fn unpark_all(&self) { + self.inner.cvar.notify_all(); + } +} diff --git a/src/util/test_util/mock_vm.rs b/src/util/test_util/mock_vm/vm.rs similarity index 98% rename from src/util/test_util/mock_vm.rs rename to src/util/test_util/mock_vm/vm.rs index cc687f4b1f..b706946685 100644 --- a/src/util/test_util/mock_vm.rs +++ b/src/util/test_util/mock_vm/vm.rs @@ -19,6 +19,8 @@ use crate::vm::RootsWorkFactory; use crate::vm::SlotVisitor; use crate::vm::VMBinding; use crate::Mutator; +use crate::util::test_util; +use crate::util::test_util::mock_vm::thread_park::ThreadPark; use super::mock_method::*; @@ -102,12 +104,12 @@ where T: FnOnce() + std::panic::UnwindSafe, C: FnOnce(), { - super::serial_test(|| { + test_util::serial_test(|| { // Setup { write_mockvm(|mock| *mock = setup()); } - super::with_cleanup(test, cleanup); + test_util::with_cleanup(test, cleanup); }) } @@ -263,6 +265,10 @@ pub struct MockVM { pub forward_weak_refs: Box, } +lazy_static! { + static ref THREAD_PARK: ThreadPark = ThreadPark::new(); +} + impl Default for MockVM { fn default() -> Self { Self { @@ -276,7 +282,9 @@ impl Default for MockVM { stop_all_mutators: MockMethod::new_unimplemented(), resume_mutators: MockMethod::new_unimplemented(), - block_for_gc: MockMethod::new_unimplemented(), + block_for_gc: MockMethod::new_fixed(Box::new(|_| { + THREAD_PARK.park(); + })), spawn_gc_thread: MockMethod::new_default(), out_of_memory: MockMethod::new_fixed(Box::new(|(_, err)| { panic!("Out of memory with {:?}!", err) 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_with_re_enable_collection.rs b/src/vm/tests/mock_tests/mock_test_allocate_with_re_enable_collection.rs index 296e4a20a2..828d8a0ac9 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; 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..b37496d227 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_gc.rs @@ -0,0 +1,26 @@ +// GITHUB-CI: MMTK_PLAN=all + +use super::mock_test_prelude::*; +use crate::plan::AllocationSemantics; +use crate::util::{VMThread, VMMutatorThread}; + +#[test] +pub fn simple_gc() { + with_mockvm( + default_setup, + || { + // 1MB heap + const MB: usize = 1024 * 1024; + let mut fixture = MutatorFixture::create_with_heapsize(MB); + + // Normal alloc + let addr = + memory_manager::alloc(&mut 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(), VMMutatorThread(VMThread::UNINITIALIZED)); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index 9a11d03d03..cf3d0c5ac1 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -18,7 +18,6 @@ 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::*; pub use crate::vm::*; } @@ -38,6 +37,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; From b9e828491a6e2b642f8427ebb4eebd3232e57f30 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Wed, 22 Oct 2025 04:04:15 +0000 Subject: [PATCH 2/8] Can run through GCs with GenImmix (no reclaim, no roots, etc). --- benches/mock_bench/alloc.rs | 2 +- benches/mock_bench/internal_pointer.rs | 8 +- benches/mock_bench/mmapper.rs | 4 +- benches/mock_bench/sft.rs | 2 +- src/util/opaque_pointer.rs | 8 +- src/util/test_util/fixtures.rs | 52 +++---- src/util/test_util/mock_vm/mock_api.rs | 53 +++++++ src/util/test_util/mock_vm/mod.rs | 2 + src/util/test_util/mock_vm/thread_park.rs | 86 +++++++++-- src/util/test_util/mock_vm/vm.rs | 146 +++++++++++++----- .../mock_test_allocate_align_offset.rs | 4 +- ...ate_no_gc_oom_on_acquire_allow_oom_call.rs | 2 +- ...locate_no_gc_oom_on_acquire_no_oom_call.rs | 2 +- .../mock_test_allocate_no_gc_simple.rs | 2 +- .../mock_test_allocate_nonmoving.rs | 4 +- .../mock_test_allocate_overcommit.rs | 2 +- ...k_test_allocate_with_disable_collection.rs | 4 +- ...est_allocate_with_initialize_collection.rs | 6 +- ...test_allocate_with_re_enable_collection.rs | 6 +- ..._allocate_without_initialize_collection.rs | 5 +- src/vm/tests/mock_tests/mock_test_gc.rs | 4 +- .../mock_tests/mock_test_heap_traversal.rs | 2 +- .../tests/mock_tests/mock_test_init_fork.rs | 3 +- ...ock_test_internal_ptr_before_object_ref.rs | 4 +- ...st_internal_ptr_large_object_multi_page.rs | 4 +- ...est_internal_ptr_large_object_same_page.rs | 4 +- .../mock_test_internal_ptr_normal_object.rs | 4 +- ..._allocate_non_multiple_of_min_alignment.rs | 4 +- ...7_allocate_unrealistically_large_object.rs | 10 +- .../mock_tests/mock_test_nogc_lock_free.rs | 2 +- .../mock_tests/mock_test_vm_layout_default.rs | 2 +- src/vm/tests/mock_tests/mod.rs | 1 + 32 files changed, 321 insertions(+), 123 deletions(-) create mode 100644 src/util/test_util/mock_vm/mock_api.rs diff --git a/benches/mock_bench/alloc.rs b/benches/mock_bench/alloc.rs index 9a771cf05f..312d622c8a 100644 --- a/benches/mock_bench/alloc.rs +++ b/benches/mock_bench/alloc.rs @@ -23,7 +23,7 @@ pub fn bench(c: &mut Criterion) { 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..aebea7180d 100644 --- a/benches/mock_bench/internal_pointer.rs +++ b/benches/mock_bench/internal_pointer.rs @@ -27,7 +27,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 +35,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 +65,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 +73,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..71b56dc15d 100644 --- a/benches/mock_bench/mmapper.rs +++ b/benches/mock_bench/mmapper.rs @@ -12,7 +12,7 @@ pub fn bench(c: &mut Criterion) { let mut fixture = MutatorFixture::create_with_heapsize(1 << 30); let regular = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), 40, 0, 0, @@ -20,7 +20,7 @@ pub fn bench(c: &mut Criterion) { ); let large = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), 40, 0, 0, diff --git a/benches/mock_bench/sft.rs b/benches/mock_bench/sft.rs index 7f58f4aa5f..cfd745c748 100644 --- a/benches/mock_bench/sft.rs +++ b/benches/mock_bench/sft.rs @@ -8,7 +8,7 @@ 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 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/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..bb8d329e76 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 {} @@ -228,11 +228,11 @@ impl FixtureContent for SingleObject { 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() } } @@ -261,17 +261,17 @@ impl FixtureContent for TwoObjects { 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..b6145883de --- /dev/null +++ b/src/util/test_util/mock_vm/mock_api.rs @@ -0,0 +1,53 @@ +use crate::{MMTKBuilder, MMTK}; +use super::MockVM; +use super::vm; +use crate::Mutator; +use crate::util::*; + +use std::sync::atomic::Ordering; +use std::cell::UnsafeCell; + +pub static mut MMTK_SINGLETON: *mut MMTK = std::ptr::null_mut(); + +pub fn singleton() -> &'static MMTK { + unsafe { + assert!(!MMTK_SINGLETON.is_null(), "MMTK singleton is not set"); + &*MMTK_SINGLETON + } +} + +pub fn singleton_mut() -> &'static mut MMTK { + unsafe { + assert!(!MMTK_SINGLETON.is_null(), "MMTK singleton is not set"); + &mut *MMTK_SINGLETON + } +} + +pub fn set_singleton(mmtk_ptr: *mut MMTK) { + unsafe { + MMTK_SINGLETON = mmtk_ptr; + } +} + +impl VMMutatorThread { + pub fn as_mock_mutator(self) -> &'static mut Mutator { + unsafe { &mut * (*self.0.0.to_address().to_mut_ptr::()).ptr } + } +} + +pub fn bind_mutator() -> VMMutatorThread { + let mmtk = singleton(); + let mutator_handle = Box::new(vm::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; } + + vm::MUTATOR_PARK.register(tls); + tls +} diff --git a/src/util/test_util/mock_vm/mod.rs b/src/util/test_util/mock_vm/mod.rs index 71cd2da7c4..98c4b2afc7 100644 --- a/src/util/test_util/mock_vm/mod.rs +++ b/src/util/test_util/mock_vm/mod.rs @@ -5,3 +5,5 @@ 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 index 6d1743e662..9803392a85 100644 --- a/src/util/test_util/mock_vm/thread_park.rs +++ b/src/util/test_util/mock_vm/thread_park.rs @@ -1,4 +1,7 @@ +use std::collections::HashMap; use std::sync::{Arc, Condvar, Mutex}; +use std::thread::{self, ThreadId}; +use crate::util::VMMutatorThread; #[derive(Clone)] pub struct ThreadPark { @@ -6,30 +9,95 @@ pub struct ThreadPark { } struct Inner { - lock: Mutex<()>, + lock: Mutex, cvar: Condvar, } +#[derive(Default)] +struct State { + /// All registered parked and whether they are currently parked. + parked: HashMap, +} + impl ThreadPark { pub fn new() -> Self { Self { inner: Arc::new(Inner { - lock: Mutex::new(()), + lock: Mutex::new(State::default()), cvar: Condvar::new(), }), } } - /// Park the current thread until `unpark_all()` is called. - pub fn park(&self) { - let guard = self.inner.lock.lock().unwrap(); - // Wait until notified; condvar wait automatically unlocks/relocks the mutex. - // If spurious wakeups happen, the caller should re-check its condition. - let _unused = self.inner.cvar.wait(guard).unwrap(); + /// Register the current thread for coordination. + pub fn register(&self, tid: VMMutatorThread) { + info!("Register {:?} to ThreadPark", tid); + let mut state = self.inner.lock.lock().unwrap(); + state.parked.insert(tid, false); + } + + pub fn unregister(&self, tid: VMMutatorThread) { + let mut state = self.inner.lock.lock().unwrap(); + state.parked.remove(&tid); + } + + pub fn is_thread(&self, tid: VMMutatorThread) -> bool { + let state = self.inner.lock.lock().unwrap(); + state.parked.contains_key(&tid) } - /// Wake up all threads currently parked on `park()`. + 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: VMMutatorThread) { + 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 before park()", tid); + } + + // 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/vm.rs b/src/util/test_util/mock_vm/vm.rs index b706946685..5ba1c75ff1 100644 --- a/src/util/test_util/mock_vm/vm.rs +++ b/src/util/test_util/mock_vm/vm.rs @@ -21,23 +21,23 @@ use crate::vm::VMBinding; use crate::Mutator; use crate::util::test_util; use crate::util::test_util::mock_vm::thread_park::ThreadPark; +use crate::MMTK; +use crate::MMTKBuilder; +use crate::util::test_util::mock_vm::mock_api; use super::mock_method::*; use std::default::Default; use std::ops::Range; use std::sync::Mutex; +use std::sync::RwLock; /// 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()); -} +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, @@ -68,25 +68,27 @@ macro_rules! mock_any { }; } +pub fn init_mockvm(mockvm: MockVM) { + unsafe { + // assert!(MOCK_VM_INSTANCE.is_null()); + 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 @@ -105,9 +107,20 @@ where C: FnOnce(), { test_util::serial_test(|| { + { + use std::panic; + let orig_hook = panic::take_hook(); + panic::set_hook(Box::new(move |panic_info| { + // invoke the default handler and exit the process + error!("Panic occurred in test with MockVM:"); + error!("{}", panic_info); + orig_hook(panic_info); + std::process::exit(1); + })); + } // Setup { - write_mockvm(|mock| *mock = setup()); + init_mockvm(setup()); } test_util::with_cleanup(test, cleanup); }) @@ -265,27 +278,85 @@ pub struct MockVM { pub forward_weak_refs: Box, } +use std::sync::atomic::AtomicUsize; +use std::collections::HashMap; +use std::sync::atomic::Ordering; + +#[derive(Clone)] +pub struct MutatorHandle { + pub ptr: *mut Mutator, +} + +impl MutatorHandle { + pub fn as_mutator(&self) -> &'static mut Mutator { + unsafe { &mut *self.ptr } + } +} + +unsafe impl Sync for MutatorHandle {} +unsafe impl Send for MutatorHandle {} + lazy_static! { - static ref THREAD_PARK: ThreadPark = ThreadPark::new(); + pub static ref MUTATOR_PARK: ThreadPark = ThreadPark::new(); } 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(VMMutatorThread(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| 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_fixed(Box::new(|_| { - THREAD_PARK.park(); + 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(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); + })), + 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(VMThread::UNINITIALIZED); + match ctx { + GCThreadContext::Worker(w) => { + crate::memory_manager::start_worker(&mock_api::singleton(), worker_tls, w) + } + } + }); })), - spawn_gc_thread: MockMethod::new_default(), out_of_memory: MockMethod::new_fixed(Box::new(|(_, err)| { panic!("Out of memory with {:?}!", err) })), @@ -351,7 +422,7 @@ 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(), // Same here: the `MockMethod` is just a place holder. See the above comments. @@ -584,17 +655,19 @@ impl crate::vm::Scanning for MockVM { 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>, ) { - 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)) @@ -609,15 +682,18 @@ impl crate::vm::Scanning for MockVM { 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, ) { - 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/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..33863e1cae 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 @@ -21,7 +21,7 @@ pub fn allocate_no_gc_oom_on_acquire_allow_oom_call() { // 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..62a83f1c7c 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 @@ -16,7 +16,7 @@ pub fn allocate_no_gc_oom_on_acquire_no_oom_call() { // 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..9dad6fb48f 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 @@ -20,7 +20,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..8acc039a20 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs @@ -19,13 +19,13 @@ pub fn allocate_nonmoving() { // 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, + fixture.mutator(), 16, 8, 0, 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..f720ceef4e 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs @@ -22,7 +22,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..06ec190bdc 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 @@ -22,7 +22,7 @@ pub fn allocate_with_disable_collection() { // 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..ae386745b0 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 @@ -20,7 +20,7 @@ pub fn allocate_with_initialize_collection() { // Allocate half MB. It should be fine. let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), MB >> 1, 8, 0, @@ -30,7 +30,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 +39,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 828d8a0ac9..0c5fc6c854 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 @@ -28,7 +28,7 @@ pub fn allocate_with_re_enable_collection() { // Allocate half MB. It should be fine. let addr = memory_manager::alloc( - &mut fixture.mutator, + fixture.mutator(), MB >> 1, 8, 0, @@ -39,11 +39,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..4280c8ea44 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 @@ -24,10 +24,7 @@ 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 mut mutator = mock_api::bind_mutator().as_mock_mutator(); // Allocate half MB. It should be fine. let addr = diff --git a/src/vm/tests/mock_tests/mock_test_gc.rs b/src/vm/tests/mock_tests/mock_test_gc.rs index b37496d227..6f00e415d7 100644 --- a/src/vm/tests/mock_tests/mock_test_gc.rs +++ b/src/vm/tests/mock_tests/mock_test_gc.rs @@ -15,11 +15,11 @@ pub fn simple_gc() { // 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); - memory_manager::handle_user_collection_request(&fixture.mmtk(), VMMutatorThread(VMThread::UNINITIALIZED)); + 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..7995dbf2b4 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 @@ -20,7 +20,7 @@ pub fn interior_pointer_before_object_ref() { let mut 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..cdc7738f4a 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 @@ -23,7 +23,7 @@ pub fn interior_pointer_in_large_object() { let mut 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..937f65a325 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 @@ -24,7 +24,7 @@ pub fn interior_pointer_in_large_object_same_page() { let mut 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..0976e05e4b 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 @@ -22,7 +22,7 @@ pub fn interior_pointer_in_normal_object() { let mut 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..b3ffb87b0f 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 @@ -11,12 +11,12 @@ pub fn issue139_alloc_non_multiple_of_min_alignment() { // 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_vm_layout_default.rs b/src/vm/tests/mock_tests/mock_test_vm_layout_default.rs index 8bd527d9d6..7a91cad3fc 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 @@ -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 cf3d0c5ac1..c23e4080bc 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -20,6 +20,7 @@ pub(crate) mod mock_test_prelude { pub use crate::util::test_util::fixtures::*; pub use crate::util::test_util::mock_vm::*; pub use crate::vm::*; + pub use crate::util::test_util::mock_vm::mock_api; } mod mock_test_allocate_align_offset; From 9ef3602f9afe631fcdfac965b36458719b5f46e4 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Fri, 24 Oct 2025 00:03:56 +0000 Subject: [PATCH 3/8] Run mock tests with side metadata. More fixes to existing tests. --- .github/scripts/ci-test.sh | 3 +- Cargo.toml | 5 +- src/policy/space.rs | 1 + src/util/metadata/side_metadata/helpers.rs | 4 +- src/util/metadata/side_metadata/helpers_32.rs | 8 +- src/util/test_util/mock_vm/mock_api.rs | 2 +- src/util/test_util/mock_vm/thread_park.rs | 22 +- src/util/test_util/mock_vm/vm.rs | 71 ++- .../mock_tests/mock_test_allocate_align.rs | 43 ++ .../mock_test_allocate_no_gc_simple.rs | 5 + ...offset.rs => mock_test_allocate_offset.rs} | 33 -- .../mock_test_allocate_overcommit.rs | 5 + ...est_allocate_with_initialize_collection.rs | 10 + ...test_allocate_with_re_enable_collection.rs | 13 + src/vm/tests/mock_tests/mock_test_slots.rs | 438 ------------------ .../mock_tests/mock_test_slots_compressed.rs | 89 ++++ .../tests/mock_tests/mock_test_slots_mixed.rs | 110 +++++ .../mock_tests/mock_test_slots_offset.rs | 106 +++++ .../mock_tests/mock_test_slots_simple.rs | 52 +++ .../mock_tests/mock_test_slots_tagged.rs | 116 +++++ src/vm/tests/mock_tests/mod.rs | 10 +- 21 files changed, 641 insertions(+), 505 deletions(-) create mode 100644 src/vm/tests/mock_tests/mock_test_allocate_align.rs rename src/vm/tests/mock_tests/{mock_test_allocate_align_offset.rs => mock_test_allocate_offset.rs} (57%) delete mode 100644 src/vm/tests/mock_tests/mock_test_slots.rs create mode 100644 src/vm/tests/mock_tests/mock_test_slots_compressed.rs create mode 100644 src/vm/tests/mock_tests/mock_test_slots_mixed.rs create mode 100644 src/vm/tests/mock_tests/mock_test_slots_offset.rs create mode 100644 src/vm/tests/mock_tests/mock_test_slots_simple.rs create mode 100644 src/vm/tests/mock_tests/mock_test_slots_tagged.rs 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/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/metadata/side_metadata/helpers.rs b/src/util/metadata/side_metadata/helpers.rs index 24e5366dc8..44f88114e8 100644 --- a/src/util/metadata/side_metadata/helpers.rs +++ b/src/util/metadata/side_metadata/helpers.rs @@ -154,7 +154,9 @@ pub(super) fn try_mmap_contiguous_metadata_space( anno, ) } - .map(|_| mmap_size) + .map(|_| mmap_size).inspect_err(|_| { + warn!("Failed to mmap metadata space: {} - {}", mmap_start, mmap_start + mmap_size); + }) } 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..af532677ae 100644 --- a/src/util/metadata/side_metadata/helpers_32.rs +++ b/src/util/metadata/side_metadata/helpers_32.rs @@ -197,5 +197,11 @@ pub(super) fn try_mmap_metadata_chunk( memory::MmapStrategy::SIDE_METADATA, anno, ) - } + }.inspect_err(|_| { + warn!( + "Failed to mmap per-chunk metadata: {} - {}", + policy_meta_start, + policy_meta_start + local_per_chunk + ); + }) } diff --git a/src/util/test_util/mock_vm/mock_api.rs b/src/util/test_util/mock_vm/mock_api.rs index b6145883de..04a2ac4c60 100644 --- a/src/util/test_util/mock_vm/mock_api.rs +++ b/src/util/test_util/mock_vm/mock_api.rs @@ -48,6 +48,6 @@ pub fn bind_mutator() -> VMMutatorThread { unsafe { (*mutator_handle_ptr).ptr = mutator_ptr; } - vm::MUTATOR_PARK.register(tls); + vm::MUTATOR_PARK.register(tls.0); tls } diff --git a/src/util/test_util/mock_vm/thread_park.rs b/src/util/test_util/mock_vm/thread_park.rs index 9803392a85..d1d6f0de36 100644 --- a/src/util/test_util/mock_vm/thread_park.rs +++ b/src/util/test_util/mock_vm/thread_park.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; use std::sync::{Arc, Condvar, Mutex}; use std::thread::{self, ThreadId}; -use crate::util::VMMutatorThread; +use crate::util::VMThread; #[derive(Clone)] pub struct ThreadPark { + name: &'static str, inner: Arc, } @@ -16,12 +17,13 @@ struct Inner { #[derive(Default)] struct State { /// All registered parked and whether they are currently parked. - parked: HashMap, + parked: HashMap, } impl ThreadPark { - pub fn new() -> Self { + pub fn new(name: &'static str) -> Self { Self { + name, inner: Arc::new(Inner { lock: Mutex::new(State::default()), cvar: Condvar::new(), @@ -30,18 +32,18 @@ impl ThreadPark { } /// Register the current thread for coordination. - pub fn register(&self, tid: VMMutatorThread) { - info!("Register {:?} to ThreadPark", tid); + pub fn register(&self, tid: VMThread) { + info!("Register {:?} to {}", tid, self.name); let mut state = self.inner.lock.lock().unwrap(); state.parked.insert(tid, false); } - pub fn unregister(&self, tid: VMMutatorThread) { + pub fn unregister(&self, tid: VMThread) { let mut state = self.inner.lock.lock().unwrap(); state.parked.remove(&tid); } - pub fn is_thread(&self, tid: VMMutatorThread) -> bool { + pub fn is_thread(&self, tid: VMThread) -> bool { let state = self.inner.lock.lock().unwrap(); state.parked.contains_key(&tid) } @@ -51,20 +53,20 @@ impl ThreadPark { state.parked.len() } - pub fn all_threads(&self) -> Vec { + 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: VMMutatorThread) { + 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 before park()", tid); + panic!("Thread {:?} not registered to {} before park() f", tid, self.name); } // Notify any waiter that one more thread has parked diff --git a/src/util/test_util/mock_vm/vm.rs b/src/util/test_util/mock_vm/vm.rs index 5ba1c75ff1..641b4fd3fa 100644 --- a/src/util/test_util/mock_vm/vm.rs +++ b/src/util/test_util/mock_vm/vm.rs @@ -111,11 +111,15 @@ where use std::panic; let orig_hook = panic::take_hook(); panic::set_hook(Box::new(move |panic_info| { - // invoke the default handler and exit the process - error!("Panic occurred in test with MockVM:"); - error!("{}", panic_info); - orig_hook(panic_info); - std::process::exit(1); + let current_tls = current_thread_tls(); + if GC_THREADS.is_thread(current_tls) { + // 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); + std::process::exit(1); + } else { + // invoke the default handler + orig_hook(panic_info); + } })); } // Setup @@ -297,7 +301,13 @@ unsafe impl Sync for MutatorHandle {} unsafe impl Send for MutatorHandle {} lazy_static! { - pub static ref MUTATOR_PARK: ThreadPark = ThreadPark::new(); + 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 { @@ -307,7 +317,7 @@ impl Default for MockVM { // 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(VMMutatorThread(tls)))), + 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() })), @@ -316,7 +326,7 @@ impl Default for MockVM { let mutators: Vec<&'static mut Mutator> = MUTATOR_PARK .all_threads() .into_iter() - .map(|tls| tls.as_mock_mutator()) + .map(|tls| VMMutatorThread(tls).as_mock_mutator()) .collect(); Box::new(mutators.into_iter()) })), @@ -333,7 +343,7 @@ impl Default for MockVM { .all_threads() .into_iter() .for_each(|tls| { - mutator_visitor(tls.as_mock_mutator()) + mutator_visitor(VMMutatorThread(tls).as_mock_mutator()) }); })), resume_mutators: MockMethod::new_fixed(Box::new(|_tls| { @@ -341,20 +351,23 @@ impl Default for MockVM { MUTATOR_PARK.unpark_all(); })), block_for_gc: MockMethod::new_fixed(Box::new(|tls| { - MUTATOR_PARK.park(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 || { + use thread_id; // Start the worker loop - let worker_tls = VMWorkerThread(VMThread::UNINITIALIZED); + 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)| { @@ -541,15 +554,39 @@ 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); - const LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = - VMLocalLOSMarkNurserySpec::in_header(0); + const LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = 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 = MOCK_VM_LOCAL_LOS_MARK_NURSERY_SPEC; #[cfg(feature = "object_pinning")] const LOCAL_PINNING_BIT_SPEC: VMLocalPinningBitSpec = VMLocalPinningBitSpec::in_header(0); diff --git a/src/vm/tests/mock_tests/mock_test_allocate_align.rs b/src/vm/tests/mock_tests/mock_test_allocate_align.rs new file mode 100644 index 0000000000..71eaf33e42 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_allocate_align.rs @@ -0,0 +1,43 @@ +// 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_alignment() { + with_mockvm( + default_setup, + || { + MUTATOR.with_fixture_mut(|fixture| { + 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 {}", align); + let addr = memory_manager::alloc( + fixture.mutator(), + 8, + align, + 0, + AllocationSemantics::Default, + ); + assert!( + addr.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_no_gc_simple.rs b/src/vm/tests/mock_tests/mock_test_allocate_no_gc_simple.rs index 9dad6fb48f..37eb43b7cc 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 @@ -14,6 +14,11 @@ pub fn allocate_no_gc_simple() { const MB: usize = 1024 * 1024; let mut 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; // Attempt allocation: allocate 1024 bytes. We should fill up the heap by 1024 allocations or fewer (some plans reserves more memory, such as semispace and generational GCs) diff --git a/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs b/src/vm/tests/mock_tests/mock_test_allocate_offset.rs similarity index 57% rename from src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs rename to src/vm/tests/mock_tests/mock_test_allocate_offset.rs index 37e979134f..fa3af8ad99 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_offset.rs @@ -9,39 +9,6 @@ lazy_static! { static ref MUTATOR: Fixture = Fixture::new(); } -#[test] -pub fn allocate_alignment() { - with_mockvm( - default_setup, - || { - MUTATOR.with_fixture_mut(|fixture| { - 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 {}", align); - let addr = memory_manager::alloc( - fixture.mutator(), - 8, - align, - 0, - AllocationSemantics::Default, - ); - assert!( - addr.is_aligned_to(align), - "Expected allocation alignment {}, returned address is {:?}", - align, - addr - ); - align *= 2; - } - }) - }, - no_cleanup, - ) -} - #[test] pub fn allocate_offset() { with_mockvm( 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 f720ceef4e..9419b92062 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs @@ -16,6 +16,11 @@ pub fn allocate_overcommit() { const MB: usize = 1024 * 1024; let mut 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; // Attempt allocation: allocate 1024 bytes. We should fill up the heap by 1024 allocations or fewer (some plans reserves more memory, such as semispace and generational GCs) 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 ae386745b0..eb0ef1c7c2 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 @@ -18,6 +18,16 @@ pub fn allocate_with_initialize_collection() { const MB: usize = 1024 * 1024; let mut 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( fixture.mutator(), 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 0c5fc6c854..31bb1f90bf 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 @@ -26,6 +26,19 @@ pub fn allocate_with_re_enable_collection() { const MB: usize = 1024 * 1024; let mut 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( fixture.mutator(), diff --git a/src/vm/tests/mock_tests/mock_test_slots.rs b/src/vm/tests/mock_tests/mock_test_slots.rs deleted file mode 100644 index 397e0f1caf..0000000000 --- a/src/vm/tests/mock_tests/mock_test_slots.rs +++ /dev/null @@ -1,438 +0,0 @@ -// GITHUB-CI: MMTK_PLAN=NoGC - -#![allow(unused)] - -use super::mock_test_prelude::*; -use crate::{ - util::{Address, ObjectReference}, - vm::slot::{SimpleSlot, Slot}, -}; -use atomic::{Atomic, Ordering}; - -lazy_static! { - static ref FIXTURE: Fixture = Fixture::new(); -} - -mod simple_slots { - use super::*; - - #[test] - pub fn load_simple() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot: Atomic = Atomic::new(fixture.objref1); - - let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); - let objref = slot.load(); - - assert_eq!(objref, Some(fixture.objref1)); - }); - }, - no_cleanup, - ) - } - - #[test] - pub fn store_simple() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot: Atomic = Atomic::new(fixture.objref1); - - let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); - slot.store(fixture.objref2); - assert_eq!(rust_slot.load(Ordering::SeqCst), fixture.objref2); - - let objref = slot.load(); - assert_eq!(objref, Some(fixture.objref2)); - }); - }, - no_cleanup, - ) - } -} - -#[cfg(target_pointer_width = "64")] -mod compressed_oop { - use super::*; - - /// This represents a location that holds a 32-bit pointer on a 64-bit machine. - /// - /// OpenJDK uses this kind of slot to store compressed OOPs on 64-bit machines. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct CompressedOopSlot { - slot_addr: *mut Atomic, - } - - unsafe impl Send for CompressedOopSlot {} - - impl CompressedOopSlot { - pub fn from_address(address: Address) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - } - } - pub fn as_address(&self) -> Address { - Address::from_mut_ptr(self.slot_addr) - } - } - - impl Slot for CompressedOopSlot { - fn load(&self) -> Option { - let compressed = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let expanded = (compressed as usize) << 3; - ObjectReference::from_raw_address(unsafe { Address::from_usize(expanded) }) - } - - fn store(&self, object: ObjectReference) { - let expanded = object.to_raw_address().as_usize(); - let compressed = (expanded >> 3) as u32; - unsafe { (*self.slot_addr).store(compressed, atomic::Ordering::Relaxed) } - } - } - - // Two 35-bit addresses aligned to 8 bytes (3 zeros in the lowest bits). - const COMPRESSABLE_ADDR1: usize = 0b101_10111011_11011111_01111110_11111000usize; - const COMPRESSABLE_ADDR2: usize = 0b110_11110111_01101010_11011101_11101000usize; - - #[test] - pub fn load_compressed() { - // Note: We cannot guarantee GC will allocate an object in the low address region. - // So we make up addresses just for testing the bit operations of compressed OOP slots. - let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; - let objref1 = - ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR1) }); - - let mut rust_slot: Atomic = Atomic::new(compressed1); - - let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); - let objref = slot.load(); - - assert_eq!(objref, objref1); - } - - #[test] - pub fn store_compressed() { - // Note: We cannot guarantee GC will allocate an object in the low address region. - // So we make up addresses just for testing the bit operations of compressed OOP slots. - let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; - let compressed2 = (COMPRESSABLE_ADDR2 >> 3) as u32; - let objref2 = - ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR2) }) - .unwrap(); - - let mut rust_slot: Atomic = Atomic::new(compressed1); - - let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); - slot.store(objref2); - assert_eq!(rust_slot.load(Ordering::SeqCst), compressed2); - - let objref = slot.load(); - assert_eq!(objref, Some(objref2)); - } -} - -mod offset_slot { - use super::*; - - /// This represents a slot that holds a pointer to the *middle* of an object, and the offset is known. - /// - /// Julia uses this trick to facilitate deleting array elements from the front. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct OffsetSlot { - slot_addr: *mut Atomic
, - offset: usize, - } - - unsafe impl Send for OffsetSlot {} - - impl OffsetSlot { - pub fn new_no_offset(address: Address) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - offset: 0, - } - } - - pub fn new_with_offset(address: Address, offset: usize) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - offset, - } - } - - pub fn slot_address(&self) -> Address { - Address::from_mut_ptr(self.slot_addr) - } - - pub fn offset(&self) -> usize { - self.offset - } - } - - impl Slot for OffsetSlot { - fn load(&self) -> Option { - let middle = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let begin = middle - self.offset; - ObjectReference::from_raw_address(begin) - } - - fn store(&self, object: ObjectReference) { - let begin = object.to_raw_address(); - let middle = begin + self.offset; - unsafe { (*self.slot_addr).store(middle, atomic::Ordering::Relaxed) } - } - } - - pub const OFFSET: usize = 48; - - #[test] - pub fn load_offset() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let addr1 = fixture.objref1.to_raw_address(); - let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); - - let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); - let objref = slot.load(); - - assert_eq!(objref, Some(fixture.objref1)); - }); - }, - no_cleanup, - ) - } - - #[test] - pub fn store_offset() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let addr1 = fixture.objref1.to_raw_address(); - let addr2 = fixture.objref2.to_raw_address(); - let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); - - let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); - slot.store(fixture.objref2); - assert_eq!(rust_slot.load(Ordering::SeqCst), addr2 + OFFSET); - - let objref = slot.load(); - assert_eq!(objref, Some(fixture.objref2)); - }); - }, - no_cleanup, - ) - } -} - -mod tagged_slot { - use super::*; - - /// This slot represents a slot that holds a tagged pointer. - /// The last two bits are tag bits and are not part of the object reference. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct TaggedSlot { - slot_addr: *mut Atomic, - } - - unsafe impl Send for TaggedSlot {} - - impl TaggedSlot { - // The DummyVM has OBJECT_REF_OFFSET = 4. - // Using a two-bit tag should be safe on both 32-bit and 64-bit platforms. - const TAG_BITS_MASK: usize = 0b11; - - pub fn new(address: Address) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - } - } - } - - impl Slot for TaggedSlot { - fn load(&self) -> Option { - let tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let untagged = tagged & !Self::TAG_BITS_MASK; - ObjectReference::from_raw_address(unsafe { Address::from_usize(untagged) }) - } - - fn store(&self, object: ObjectReference) { - let old_tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let new_untagged = object.to_raw_address().as_usize(); - let new_tagged = new_untagged | (old_tagged & Self::TAG_BITS_MASK); - unsafe { (*self.slot_addr).store(new_tagged, atomic::Ordering::Relaxed) } - } - } - - pub const TAG1: usize = 0b01; - pub const TAG2: usize = 0b10; - - #[test] - pub fn load_tagged() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot1: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); - let mut rust_slot2: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); - - let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); - let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); - let objref1 = slot1.load(); - let objref2 = slot2.load(); - - // Tags should not affect loaded values. - assert_eq!(objref1, Some(fixture.objref1)); - assert_eq!(objref2, Some(fixture.objref1)); - }); - }, - no_cleanup, - ) - } - - #[test] - pub fn store_tagged() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot1: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); - let mut rust_slot2: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); - - let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); - let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); - slot1.store(fixture.objref2); - slot2.store(fixture.objref2); - - // Tags should be preserved. - assert_eq!( - rust_slot1.load(Ordering::SeqCst), - fixture.objref2.to_raw_address().as_usize() | TAG1 - ); - assert_eq!( - rust_slot2.load(Ordering::SeqCst), - fixture.objref2.to_raw_address().as_usize() | TAG2 - ); - - let objref1 = slot1.load(); - let objref2 = slot2.load(); - - // Tags should not affect loaded values. - assert_eq!(objref1, Some(fixture.objref2)); - assert_eq!(objref2, Some(fixture.objref2)); - }); - }, - no_cleanup, - ) - } -} - -mod mixed { - #[cfg(target_pointer_width = "64")] - use super::compressed_oop::CompressedOopSlot; - use super::offset_slot::OffsetSlot; - use super::offset_slot::OFFSET; - use super::tagged_slot::TaggedSlot; - use super::tagged_slot::TAG1; - use super::*; - use crate::vm::slot::SimpleSlot; - - /// If a VM supports multiple kinds of slots, we can use tagged union to represent all of them. - /// This is for testing, only. A Rust `enum` may not be the most efficient representation. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub enum DummyVMSlot { - Simple(SimpleSlot), - #[cfg(target_pointer_width = "64")] - Compressed(compressed_oop::CompressedOopSlot), - Offset(OffsetSlot), - Tagged(TaggedSlot), - } - - unsafe impl Send for DummyVMSlot {} - - impl Slot for DummyVMSlot { - fn load(&self) -> Option { - match self { - DummyVMSlot::Simple(e) => e.load(), - #[cfg(target_pointer_width = "64")] - DummyVMSlot::Compressed(e) => e.load(), - DummyVMSlot::Offset(e) => e.load(), - DummyVMSlot::Tagged(e) => e.load(), - } - } - - fn store(&self, object: ObjectReference) { - match self { - DummyVMSlot::Simple(e) => e.store(object), - #[cfg(target_pointer_width = "64")] - DummyVMSlot::Compressed(e) => e.store(object), - DummyVMSlot::Offset(e) => e.store(object), - DummyVMSlot::Tagged(e) => e.store(object), - } - } - } - - #[test] - pub fn mixed() { - with_mockvm( - default_setup, - || { - const OFFSET: usize = 48; - - FIXTURE.with_fixture(|fixture| { - let addr1 = fixture.objref1.to_raw_address(); - let addr2 = fixture.objref2.to_raw_address(); - - let mut rust_slot1: Atomic = Atomic::new(fixture.objref1); - let mut rust_slot3: Atomic
= Atomic::new(addr1 + OFFSET); - let mut rust_slot4: Atomic = Atomic::new(addr1.as_usize() | TAG1); - - let slot1 = SimpleSlot::from_address(Address::from_ref(&rust_slot1)); - let slot3 = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot3), OFFSET); - let slot4 = TaggedSlot::new(Address::from_ref(&rust_slot4)); - - let ds1 = DummyVMSlot::Simple(slot1); - let ds3 = DummyVMSlot::Offset(slot3); - let ds4 = DummyVMSlot::Tagged(slot4); - - let slots = [ds1, ds3, ds4]; - for (i, slot) in slots.iter().enumerate() { - let objref = slot.load(); - assert_eq!( - objref, - Some(fixture.objref1), - "Slot {} is not properly loaded", - i - ); - } - - let mutable_slots = [ds1, ds3, ds4]; - for (i, slot) in mutable_slots.iter().enumerate() { - slot.store(fixture.objref2); - let objref = slot.load(); - assert_eq!( - objref, - Some(fixture.objref2), - "Slot {} is not properly loaded after store", - i - ); - } - - assert_eq!(rust_slot1.load(Ordering::SeqCst), fixture.objref2); - assert_eq!(rust_slot3.load(Ordering::SeqCst), addr2 + OFFSET); - }); - }, - no_cleanup, - ) - } -} diff --git a/src/vm/tests/mock_tests/mock_test_slots_compressed.rs b/src/vm/tests/mock_tests/mock_test_slots_compressed.rs new file mode 100644 index 0000000000..5c4e6b9d26 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_slots_compressed.rs @@ -0,0 +1,89 @@ +// GITHUB-CI: MMTK_PLAN=NoGC + +#![allow(unused)] + +use super::mock_test_prelude::*; +use crate::{ + util::{Address, ObjectReference}, + vm::slot::{SimpleSlot, Slot}, +}; +use atomic::{Atomic, Ordering}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +/// This represents a location that holds a 32-bit pointer on a 64-bit machine. +/// +/// OpenJDK uses this kind of slot to store compressed OOPs on 64-bit machines. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct CompressedOopSlot { + slot_addr: *mut Atomic, +} + +unsafe impl Send for CompressedOopSlot {} + +impl CompressedOopSlot { + pub fn from_address(address: Address) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + } + } + pub fn as_address(&self) -> Address { + Address::from_mut_ptr(self.slot_addr) + } +} + +impl Slot for CompressedOopSlot { + fn load(&self) -> Option { + let compressed = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let expanded = (compressed as usize) << 3; + ObjectReference::from_raw_address(unsafe { Address::from_usize(expanded) }) + } + + fn store(&self, object: ObjectReference) { + let expanded = object.to_raw_address().as_usize(); + let compressed = (expanded >> 3) as u32; + unsafe { (*self.slot_addr).store(compressed, atomic::Ordering::Relaxed) } + } +} + +// Two 35-bit addresses aligned to 8 bytes (3 zeros in the lowest bits). +const COMPRESSABLE_ADDR1: usize = 0b101_10111011_11011111_01111110_11111000usize; +const COMPRESSABLE_ADDR2: usize = 0b110_11110111_01101010_11011101_11101000usize; + +#[test] +pub fn load_compressed() { + // Note: We cannot guarantee GC will allocate an object in the low address region. + // So we make up addresses just for testing the bit operations of compressed OOP slots. + let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; + let objref1 = + ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR1) }); + + let mut rust_slot: Atomic = Atomic::new(compressed1); + + let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); + let objref = slot.load(); + + assert_eq!(objref, objref1); +} + +#[test] +pub fn store_compressed() { + // Note: We cannot guarantee GC will allocate an object in the low address region. + // So we make up addresses just for testing the bit operations of compressed OOP slots. + let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; + let compressed2 = (COMPRESSABLE_ADDR2 >> 3) as u32; + let objref2 = + ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR2) }) + .unwrap(); + + let mut rust_slot: Atomic = Atomic::new(compressed1); + + let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); + slot.store(objref2); + assert_eq!(rust_slot.load(Ordering::SeqCst), compressed2); + + let objref = slot.load(); + assert_eq!(objref, Some(objref2)); +} diff --git a/src/vm/tests/mock_tests/mock_test_slots_mixed.rs b/src/vm/tests/mock_tests/mock_test_slots_mixed.rs new file mode 100644 index 0000000000..8befd1d0b6 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_slots_mixed.rs @@ -0,0 +1,110 @@ +// GITHUB-CI: MMTK_PLAN=NoGC + +#![allow(unused)] + +use super::mock_test_prelude::*; +use crate::{ + util::{Address, ObjectReference}, + vm::slot::{SimpleSlot, Slot}, +}; +use atomic::{Atomic, Ordering}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +#[cfg(target_pointer_width = "64")] +use super::mock_test_slots_compressed::CompressedOopSlot; +use super::mock_test_slots_offset::OffsetSlot; +use super::mock_test_slots_offset::OFFSET; +use super::mock_test_slots_tagged::TaggedSlot; +use super::mock_test_slots_tagged::TAG1; + +/// If a VM supports multiple kinds of slots, we can use tagged union to represent all of them. +/// This is for testing, only. A Rust `enum` may not be the most efficient representation. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DummyVMSlot { + Simple(SimpleSlot), + #[cfg(target_pointer_width = "64")] + Compressed(CompressedOopSlot), + Offset(OffsetSlot), + Tagged(TaggedSlot), +} + +unsafe impl Send for DummyVMSlot {} + +impl Slot for DummyVMSlot { + fn load(&self) -> Option { + match self { + DummyVMSlot::Simple(e) => e.load(), + #[cfg(target_pointer_width = "64")] + DummyVMSlot::Compressed(e) => e.load(), + DummyVMSlot::Offset(e) => e.load(), + DummyVMSlot::Tagged(e) => e.load(), + } + } + + fn store(&self, object: ObjectReference) { + match self { + DummyVMSlot::Simple(e) => e.store(object), + #[cfg(target_pointer_width = "64")] + DummyVMSlot::Compressed(e) => e.store(object), + DummyVMSlot::Offset(e) => e.store(object), + DummyVMSlot::Tagged(e) => e.store(object), + } + } +} + +#[test] +pub fn mixed() { + with_mockvm( + default_setup, + || { + const OFFSET: usize = 48; + + FIXTURE.with_fixture(|fixture| { + let addr1 = fixture.objref1.to_raw_address(); + let addr2 = fixture.objref2.to_raw_address(); + + let mut rust_slot1: Atomic = Atomic::new(fixture.objref1); + let mut rust_slot3: Atomic
= Atomic::new(addr1 + OFFSET); + let mut rust_slot4: Atomic = Atomic::new(addr1.as_usize() | TAG1); + + let slot1 = SimpleSlot::from_address(Address::from_ref(&rust_slot1)); + let slot3 = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot3), OFFSET); + let slot4 = TaggedSlot::new(Address::from_ref(&rust_slot4)); + + let ds1 = DummyVMSlot::Simple(slot1); + let ds3 = DummyVMSlot::Offset(slot3); + let ds4 = DummyVMSlot::Tagged(slot4); + + let slots = [ds1, ds3, ds4]; + for (i, slot) in slots.iter().enumerate() { + let objref = slot.load(); + assert_eq!( + objref, + Some(fixture.objref1), + "Slot {} is not properly loaded", + i + ); + } + + let mutable_slots = [ds1, ds3, ds4]; + for (i, slot) in mutable_slots.iter().enumerate() { + slot.store(fixture.objref2); + let objref = slot.load(); + assert_eq!( + objref, + Some(fixture.objref2), + "Slot {} is not properly loaded after store", + i + ); + } + + assert_eq!(rust_slot1.load(Ordering::SeqCst), fixture.objref2); + assert_eq!(rust_slot3.load(Ordering::SeqCst), addr2 + OFFSET); + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mock_test_slots_offset.rs b/src/vm/tests/mock_tests/mock_test_slots_offset.rs new file mode 100644 index 0000000000..b4fc688869 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_slots_offset.rs @@ -0,0 +1,106 @@ +// GITHUB-CI: MMTK_PLAN=NoGC + +#![allow(unused)] + +use super::mock_test_prelude::*; +use crate::{ + util::{Address, ObjectReference}, + vm::slot::{SimpleSlot, Slot}, +}; +use atomic::{Atomic, Ordering}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +/// This represents a slot that holds a pointer to the *middle* of an object, and the offset is known. +/// +/// Julia uses this trick to facilitate deleting array elements from the front. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct OffsetSlot { + slot_addr: *mut Atomic
, + offset: usize, +} + +unsafe impl Send for OffsetSlot {} + +impl OffsetSlot { + pub fn new_no_offset(address: Address) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + offset: 0, + } + } + + pub fn new_with_offset(address: Address, offset: usize) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + offset, + } + } + + pub fn slot_address(&self) -> Address { + Address::from_mut_ptr(self.slot_addr) + } + + pub fn offset(&self) -> usize { + self.offset + } +} + +impl Slot for OffsetSlot { + fn load(&self) -> Option { + let middle = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let begin = middle - self.offset; + ObjectReference::from_raw_address(begin) + } + + fn store(&self, object: ObjectReference) { + let begin = object.to_raw_address(); + let middle = begin + self.offset; + unsafe { (*self.slot_addr).store(middle, atomic::Ordering::Relaxed) } + } +} + +pub const OFFSET: usize = 48; + +#[test] +pub fn load_offset() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let addr1 = fixture.objref1.to_raw_address(); + let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); + + let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); + let objref = slot.load(); + + assert_eq!(objref, Some(fixture.objref1)); + }); + }, + no_cleanup, + ) +} + +#[test] +pub fn store_offset() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let addr1 = fixture.objref1.to_raw_address(); + let addr2 = fixture.objref2.to_raw_address(); + let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); + + let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); + slot.store(fixture.objref2); + assert_eq!(rust_slot.load(Ordering::SeqCst), addr2 + OFFSET); + + let objref = slot.load(); + assert_eq!(objref, Some(fixture.objref2)); + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mock_test_slots_simple.rs b/src/vm/tests/mock_tests/mock_test_slots_simple.rs new file mode 100644 index 0000000000..bb24554887 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_slots_simple.rs @@ -0,0 +1,52 @@ +// GITHUB-CI: MMTK_PLAN=NoGC + +#![allow(unused)] + +use super::mock_test_prelude::*; +use crate::{ + util::{Address, ObjectReference}, + vm::slot::{SimpleSlot, Slot}, +}; +use atomic::{Atomic, Ordering}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +#[test] +pub fn load_simple() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot: Atomic = Atomic::new(fixture.objref1); + + let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); + let objref = slot.load(); + + assert_eq!(objref, Some(fixture.objref1)); + }); + }, + no_cleanup, + ) +} + +#[test] +pub fn store_simple() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot: Atomic = Atomic::new(fixture.objref1); + + let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); + slot.store(fixture.objref2); + assert_eq!(rust_slot.load(Ordering::SeqCst), fixture.objref2); + + let objref = slot.load(); + assert_eq!(objref, Some(fixture.objref2)); + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mock_test_slots_tagged.rs b/src/vm/tests/mock_tests/mock_test_slots_tagged.rs new file mode 100644 index 0000000000..b15d840be8 --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_slots_tagged.rs @@ -0,0 +1,116 @@ +// GITHUB-CI: MMTK_PLAN=NoGC + +#![allow(unused)] + +use super::mock_test_prelude::*; +use crate::{ + util::{Address, ObjectReference}, + vm::slot::{SimpleSlot, Slot}, +}; +use atomic::{Atomic, Ordering}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +/// This slot represents a slot that holds a tagged pointer. +/// The last two bits are tag bits and are not part of the object reference. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct TaggedSlot { + slot_addr: *mut Atomic, +} + +unsafe impl Send for TaggedSlot {} + +impl TaggedSlot { + // The DummyVM has OBJECT_REF_OFFSET = 4. + // Using a two-bit tag should be safe on both 32-bit and 64-bit platforms. + const TAG_BITS_MASK: usize = 0b11; + + pub fn new(address: Address) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + } + } +} + +impl Slot for TaggedSlot { + fn load(&self) -> Option { + let tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let untagged = tagged & !Self::TAG_BITS_MASK; + ObjectReference::from_raw_address(unsafe { Address::from_usize(untagged) }) + } + + fn store(&self, object: ObjectReference) { + let old_tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let new_untagged = object.to_raw_address().as_usize(); + let new_tagged = new_untagged | (old_tagged & Self::TAG_BITS_MASK); + unsafe { (*self.slot_addr).store(new_tagged, atomic::Ordering::Relaxed) } + } +} + +pub const TAG1: usize = 0b01; +pub const TAG2: usize = 0b10; + +#[test] +pub fn load_tagged() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot1: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); + let mut rust_slot2: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); + + let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); + let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); + let objref1 = slot1.load(); + let objref2 = slot2.load(); + + // Tags should not affect loaded values. + assert_eq!(objref1, Some(fixture.objref1)); + assert_eq!(objref2, Some(fixture.objref1)); + }); + }, + no_cleanup, + ) +} + +#[test] +pub fn store_tagged() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot1: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); + let mut rust_slot2: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); + + let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); + let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); + slot1.store(fixture.objref2); + slot2.store(fixture.objref2); + + // Tags should be preserved. + assert_eq!( + rust_slot1.load(Ordering::SeqCst), + fixture.objref2.to_raw_address().as_usize() | TAG1 + ); + assert_eq!( + rust_slot2.load(Ordering::SeqCst), + fixture.objref2.to_raw_address().as_usize() | TAG2 + ); + + let objref1 = slot1.load(); + let objref2 = slot2.load(); + + // Tags should not affect loaded values. + assert_eq!(objref1, Some(fixture.objref2)); + assert_eq!(objref2, Some(fixture.objref2)); + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index c23e4080bc..4caed6bc96 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -23,7 +23,8 @@ pub(crate) mod mock_test_prelude { pub use crate::util::test_util::mock_vm::mock_api; } -mod mock_test_allocate_align_offset; +mod mock_test_allocate_align; +mod mock_test_allocate_offset; mod mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call; mod mock_test_allocate_no_gc_oom_on_acquire_no_oom_call; mod mock_test_allocate_no_gc_simple; @@ -65,7 +66,12 @@ mod mock_test_malloc_ms; mod mock_test_mmtk_julia_pr_143; #[cfg(feature = "nogc_lock_free")] mod mock_test_nogc_lock_free; -mod mock_test_slots; +mod mock_test_slots_simple; +#[cfg(target_pointer_width = "64")] +mod mock_test_slots_compressed; +mod mock_test_slots_offset; +mod mock_test_slots_tagged; +mod mock_test_slots_mixed; #[cfg(target_pointer_width = "64")] mod mock_test_vm_layout_compressed_pointer; mod mock_test_vm_layout_default; From ed9f8131d04f685d197cbf9b961172dbff56f416 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Fri, 24 Oct 2025 02:39:12 +0000 Subject: [PATCH 4/8] Multiple fixes. All tests run on x86_64 linux. --- src/util/heap/monotonepageresource.rs | 13 ++++++++++--- src/util/linear_scan.rs | 4 ++++ src/util/metadata/side_metadata/constants.rs | 7 ++++++- src/util/test_util/fixtures.rs | 4 ++-- src/util/test_util/mock_vm/mock_api.rs | 5 +---- src/util/test_util/mock_vm/thread_park.rs | 3 +-- src/util/test_util/mock_vm/vm.rs | 17 +++++++---------- ...ocate_no_gc_oom_on_acquire_allow_oom_call.rs | 2 +- ...allocate_no_gc_oom_on_acquire_no_oom_call.rs | 2 +- .../mock_test_allocate_no_gc_simple.rs | 2 +- .../mock_tests/mock_test_allocate_nonmoving.rs | 2 +- .../mock_tests/mock_test_allocate_overcommit.rs | 2 +- ...ock_test_allocate_with_disable_collection.rs | 2 +- ..._test_allocate_with_initialize_collection.rs | 4 ++-- ...k_test_allocate_with_re_enable_collection.rs | 4 ++-- ...st_allocate_without_initialize_collection.rs | 3 +-- src/vm/tests/mock_tests/mock_test_gc.rs | 3 +-- ...39_allocate_non_multiple_of_min_alignment.rs | 2 +- .../mock_tests/mock_test_vm_layout_default.rs | 2 +- 19 files changed, 45 insertions(+), 38 deletions(-) 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/test_util/fixtures.rs b/src/util/test_util/fixtures.rs index bb8d329e76..9567eda7b7 100644 --- a/src/util/test_util/fixtures.rs +++ b/src/util/test_util/fixtures.rs @@ -222,7 +222,7 @@ 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; @@ -256,7 +256,7 @@ 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; diff --git a/src/util/test_util/mock_vm/mock_api.rs b/src/util/test_util/mock_vm/mock_api.rs index 04a2ac4c60..d52508b4b3 100644 --- a/src/util/test_util/mock_vm/mock_api.rs +++ b/src/util/test_util/mock_vm/mock_api.rs @@ -1,12 +1,9 @@ -use crate::{MMTKBuilder, MMTK}; +use crate::MMTK; use super::MockVM; use super::vm; use crate::Mutator; use crate::util::*; -use std::sync::atomic::Ordering; -use std::cell::UnsafeCell; - pub static mut MMTK_SINGLETON: *mut MMTK = std::ptr::null_mut(); pub fn singleton() -> &'static MMTK { diff --git a/src/util/test_util/mock_vm/thread_park.rs b/src/util/test_util/mock_vm/thread_park.rs index d1d6f0de36..b2de168bb1 100644 --- a/src/util/test_util/mock_vm/thread_park.rs +++ b/src/util/test_util/mock_vm/thread_park.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; use std::sync::{Arc, Condvar, Mutex}; -use std::thread::{self, ThreadId}; use crate::util::VMThread; #[derive(Clone)] @@ -33,7 +32,7 @@ impl ThreadPark { /// Register the current thread for coordination. pub fn register(&self, tid: VMThread) { - info!("Register {:?} to {}", tid, self.name); + debug!("Register {:?} to {}", tid, self.name); let mut state = self.inner.lock.lock().unwrap(); state.parked.insert(tid, false); } diff --git a/src/util/test_util/mock_vm/vm.rs b/src/util/test_util/mock_vm/vm.rs index 641b4fd3fa..416add3e8f 100644 --- a/src/util/test_util/mock_vm/vm.rs +++ b/src/util/test_util/mock_vm/vm.rs @@ -21,16 +21,12 @@ use crate::vm::VMBinding; use crate::Mutator; use crate::util::test_util; use crate::util::test_util::mock_vm::thread_park::ThreadPark; -use crate::MMTK; -use crate::MMTKBuilder; use crate::util::test_util::mock_vm::mock_api; use super::mock_method::*; use std::default::Default; use std::ops::Range; -use std::sync::Mutex; -use std::sync::RwLock; /// The offset between object reference and the allocation address if we use /// the default mock VM. @@ -113,8 +109,12 @@ where 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 @@ -282,10 +282,6 @@ pub struct MockVM { pub forward_weak_refs: Box, } -use std::sync::atomic::AtomicUsize; -use std::collections::HashMap; -use std::sync::atomic::Ordering; - #[derive(Clone)] pub struct MutatorHandle { pub ptr: *mut Mutator, @@ -358,7 +354,6 @@ impl Default for MockVM { let _ = std::thread::Builder::new() .name("MMTk Worker".to_string()) .spawn(move || { - use thread_id; // Start the worker loop let worker_tls = VMWorkerThread(current_thread_tls()); GC_THREADS.register(worker_tls.0); @@ -437,7 +432,9 @@ impl Default for MockVM { >::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::< ( 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 33863e1cae..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,7 +17,7 @@ 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( 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 62a83f1c7c..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,7 +12,7 @@ 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( 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 37eb43b7cc..667751e56a 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,7 @@ 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. 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 8acc039a20..998ea8b18b 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs @@ -15,7 +15,7 @@ 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 = 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 9419b92062..0f2ff9e7e1 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,7 @@ 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. 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 06ec190bdc..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,7 +18,7 @@ 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( 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 eb0ef1c7c2..6d0b0e61c3 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,7 +16,7 @@ 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. @@ -24,7 +24,7 @@ pub fn allocate_with_initialize_collection() { write_mockvm(|mock| { use crate::util::VMMutatorThread; use crate::util::VMThread; - mock.block_for_gc.call((VMMutatorThread(VMThread::UNINITIALIZED))); + mock.block_for_gc.call(VMMutatorThread(VMThread::UNINITIALIZED)); }); } 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 31bb1f90bf..b1c96bddc2 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 @@ -24,7 +24,7 @@ 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. @@ -35,7 +35,7 @@ pub fn allocate_with_re_enable_collection() { mock.is_collection_enabled.call(()); mock.is_collection_enabled.call(()); mock.is_collection_enabled.call(()); - mock.block_for_gc.call((VMMutatorThread(VMThread::UNINITIALIZED))); + mock.block_for_gc.call(VMMutatorThread(VMThread::UNINITIALIZED)); }); } 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 4280c8ea44..79e8414542 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 diff --git a/src/vm/tests/mock_tests/mock_test_gc.rs b/src/vm/tests/mock_tests/mock_test_gc.rs index 6f00e415d7..8cd7520159 100644 --- a/src/vm/tests/mock_tests/mock_test_gc.rs +++ b/src/vm/tests/mock_tests/mock_test_gc.rs @@ -2,7 +2,6 @@ use super::mock_test_prelude::*; use crate::plan::AllocationSemantics; -use crate::util::{VMThread, VMMutatorThread}; #[test] pub fn simple_gc() { @@ -11,7 +10,7 @@ pub fn simple_gc() { || { // 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 = 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 b3ffb87b0f..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,7 +7,7 @@ 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 = 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 7a91cad3fc..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 From 4da090cb636b67ef9e268530398267a0ea684e85 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Sun, 26 Oct 2025 22:53:08 +0000 Subject: [PATCH 5/8] Use map_err instead of inspect_err for MSRV --- src/util/metadata/side_metadata/helpers.rs | 3 ++- src/util/metadata/side_metadata/helpers_32.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/metadata/side_metadata/helpers.rs b/src/util/metadata/side_metadata/helpers.rs index 44f88114e8..70c06001d6 100644 --- a/src/util/metadata/side_metadata/helpers.rs +++ b/src/util/metadata/side_metadata/helpers.rs @@ -154,8 +154,9 @@ pub(super) fn try_mmap_contiguous_metadata_space( anno, ) } - .map(|_| mmap_size).inspect_err(|_| { + .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 af532677ae..d326bfeab0 100644 --- a/src/util/metadata/side_metadata/helpers_32.rs +++ b/src/util/metadata/side_metadata/helpers_32.rs @@ -197,11 +197,12 @@ pub(super) fn try_mmap_metadata_chunk( memory::MmapStrategy::SIDE_METADATA, anno, ) - }.inspect_err(|_| { + }.map_err(|e| { warn!( "Failed to mmap per-chunk metadata: {} - {}", policy_meta_start, policy_meta_start + local_per_chunk ); + e }) } From 3784fffee2524bebeb656417246d50a9b0992423 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Sun, 26 Oct 2025 23:14:14 +0000 Subject: [PATCH 6/8] Fix style check --- .github/scripts/ci-style.sh | 6 +- benches/mock_bench/alloc.rs | 17 ++--- benches/mock_bench/internal_pointer.rs | 3 +- benches/mock_bench/mmapper.rs | 10 +-- benches/mock_bench/sft.rs | 2 +- src/util/metadata/side_metadata/helpers.rs | 9 ++- src/util/metadata/side_metadata/helpers_32.rs | 3 +- src/util/test_util/mock_vm/mock_api.rs | 16 +++-- src/util/test_util/mock_vm/thread_park.rs | 10 +-- src/util/test_util/mock_vm/vm.rs | 65 +++++++++++-------- .../mock_test_allocate_no_gc_simple.rs | 3 +- .../mock_test_allocate_nonmoving.rs | 9 +-- .../mock_test_allocate_overcommit.rs | 3 +- ...est_allocate_with_initialize_collection.rs | 6 +- ...test_allocate_with_re_enable_collection.rs | 6 +- ..._allocate_without_initialize_collection.rs | 10 ++- src/vm/tests/mock_tests/mock_test_gc.rs | 2 +- src/vm/tests/mock_tests/mod.rs | 8 +-- 18 files changed, 98 insertions(+), 90 deletions(-) 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/benches/mock_bench/alloc.rs b/benches/mock_bench/alloc.rs index 312d622c8a..a6e1a068b6 100644 --- a/benches/mock_bench/alloc.rs +++ b/benches/mock_bench/alloc.rs @@ -2,23 +2,18 @@ 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(|| { diff --git a/benches/mock_bench/internal_pointer.rs b/benches/mock_bench/internal_pointer.rs index aebea7180d..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 diff --git a/benches/mock_bench/mmapper.rs b/benches/mock_bench/mmapper.rs index 71b56dc15d..a48f99e93a 100644 --- a/benches/mock_bench/mmapper.rs +++ b/benches/mock_bench/mmapper.rs @@ -9,7 +9,7 @@ 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( fixture.mutator(), @@ -19,13 +19,7 @@ pub fn bench(c: &mut Criterion) { mmtk::AllocationSemantics::Default, ); - let large = memory_manager::alloc( - 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 cfd745c748..a1bac7f040 100644 --- a/benches/mock_bench/sft.rs +++ b/benches/mock_bench/sft.rs @@ -7,7 +7,7 @@ use mmtk::util::test_util::mock_vm::*; use mmtk::AllocationSemantics; pub fn bench(c: &mut Criterion) { - let mut fixture = MutatorFixture::create(); + let fixture = MutatorFixture::create(); let addr = memory_manager::alloc(fixture.mutator(), 8, 8, 0, AllocationSemantics::Default); let obj = MockVM::object_start_to_ref(addr); diff --git a/src/util/metadata/side_metadata/helpers.rs b/src/util/metadata/side_metadata/helpers.rs index 70c06001d6..7ea0a4965f 100644 --- a/src/util/metadata/side_metadata/helpers.rs +++ b/src/util/metadata/side_metadata/helpers.rs @@ -154,8 +154,13 @@ pub(super) fn try_mmap_contiguous_metadata_space( anno, ) } - .map(|_| mmap_size).map_err(|e| { - warn!("Failed to mmap metadata space: {} - {}", mmap_start, mmap_start + mmap_size); + .map(|_| mmap_size) + .map_err(|e| { + warn!( + "Failed to mmap metadata space: {} - {}", + mmap_start, + mmap_start + mmap_size + ); e }) } else { diff --git a/src/util/metadata/side_metadata/helpers_32.rs b/src/util/metadata/side_metadata/helpers_32.rs index d326bfeab0..7937a9a3a4 100644 --- a/src/util/metadata/side_metadata/helpers_32.rs +++ b/src/util/metadata/side_metadata/helpers_32.rs @@ -197,7 +197,8 @@ pub(super) fn try_mmap_metadata_chunk( memory::MmapStrategy::SIDE_METADATA, anno, ) - }.map_err(|e| { + } + .map_err(|e| { warn!( "Failed to mmap per-chunk metadata: {} - {}", policy_meta_start, diff --git a/src/util/test_util/mock_vm/mock_api.rs b/src/util/test_util/mock_vm/mock_api.rs index d52508b4b3..7d7b709813 100644 --- a/src/util/test_util/mock_vm/mock_api.rs +++ b/src/util/test_util/mock_vm/mock_api.rs @@ -1,8 +1,8 @@ -use crate::MMTK; -use super::MockVM; use super::vm; -use crate::Mutator; +use super::MockVM; use crate::util::*; +use crate::Mutator; +use crate::MMTK; pub static mut MMTK_SINGLETON: *mut MMTK = std::ptr::null_mut(); @@ -28,13 +28,15 @@ pub fn set_singleton(mmtk_ptr: *mut MMTK) { impl VMMutatorThread { pub fn as_mock_mutator(self) -> &'static mut Mutator { - unsafe { &mut * (*self.0.0.to_address().to_mut_ptr::()).ptr } + unsafe { &mut *(*self.0 .0.to_address().to_mut_ptr::()).ptr } } } pub fn bind_mutator() -> VMMutatorThread { let mmtk = singleton(); - let mutator_handle = Box::new(vm::MutatorHandle { ptr: std::ptr::null_mut() }); + let mutator_handle = Box::new(vm::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), @@ -43,7 +45,9 @@ pub fn bind_mutator() -> VMMutatorThread { let mutator = crate::memory_manager::bind_mutator(mmtk, tls); let mutator_ptr = Box::into_raw(mutator); - unsafe { (*mutator_handle_ptr).ptr = mutator_ptr; } + unsafe { + (*mutator_handle_ptr).ptr = mutator_ptr; + } vm::MUTATOR_PARK.register(tls.0); tls diff --git a/src/util/test_util/mock_vm/thread_park.rs b/src/util/test_util/mock_vm/thread_park.rs index b2de168bb1..7cdb601dc8 100644 --- a/src/util/test_util/mock_vm/thread_park.rs +++ b/src/util/test_util/mock_vm/thread_park.rs @@ -1,6 +1,6 @@ +use crate::util::VMThread; use std::collections::HashMap; use std::sync::{Arc, Condvar, Mutex}; -use crate::util::VMThread; #[derive(Clone)] pub struct ThreadPark { @@ -65,7 +65,10 @@ impl ThreadPark { if let Some(entry) = state.parked.get_mut(&tid) { *entry = true; } else { - panic!("Thread {:?} not registered to {} before park() f", tid, self.name); + panic!( + "Thread {:?} not registered to {} before park() f", + tid, self.name + ); } // Notify any waiter that one more thread has parked @@ -93,8 +96,7 @@ impl ThreadPark { 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); + let all_parked = !state.parked.is_empty() && state.parked.values().all(|&v| v); if all_parked { break; } diff --git a/src/util/test_util/mock_vm/vm.rs b/src/util/test_util/mock_vm/vm.rs index 416add3e8f..813dc69b1c 100644 --- a/src/util/test_util/mock_vm/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; @@ -19,11 +21,9 @@ use crate::vm::RootsWorkFactory; use crate::vm::SlotVisitor; use crate::vm::VMBinding; use crate::Mutator; -use crate::util::test_util; -use crate::util::test_util::mock_vm::thread_park::ThreadPark; -use crate::util::test_util::mock_vm::mock_api; use super::mock_method::*; +use crate::util::test_util::mock_vm::mock_api; use std::default::Default; use std::ops::Range; @@ -58,6 +58,7 @@ 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() @@ -113,7 +114,10 @@ where 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!( + "Panic occurred in GC thread with MockVM. Aborting the process. \n{}", + panic_info + ); error!("Backtrace:\n{}", bt); std::process::exit(1); } else { @@ -303,7 +307,9 @@ lazy_static! { } fn current_thread_tls() -> VMThread { - VMThread(OpaquePointer::from_address(unsafe { Address::from_usize(thread_id::get()) })) + VMThread(OpaquePointer::from_address(unsafe { + Address::from_usize(thread_id::get()) + })) } impl Default for MockVM { @@ -313,10 +319,10 @@ impl Default for MockVM { // 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() + 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 @@ -338,9 +344,7 @@ impl Default for MockVM { MUTATOR_PARK .all_threads() .into_iter() - .for_each(|tls| { - mutator_visitor(VMMutatorThread(tls).as_mock_mutator()) - }); + .for_each(|tls| mutator_visitor(VMMutatorThread(tls).as_mock_mutator())); })), resume_mutators: MockMethod::new_fixed(Box::new(|_tls| { info!("Resuming all parked threads..."); @@ -350,7 +354,7 @@ impl Default for MockVM { 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. + // 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 || { @@ -358,9 +362,11 @@ impl Default for MockVM { 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) - } + GCThreadContext::Worker(w) => crate::memory_manager::start_worker( + mock_api::singleton(), + worker_tls, + w, + ), } GC_THREADS.unregister(worker_tls.0); }); @@ -570,9 +576,10 @@ mod side_metadata { 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_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()); + VMLocalLOSMarkNurserySpec::side_after(MOCK_VM_LOCAL_MARK_BIT_SPEC.as_spec()); } #[cfg(feature = "mock_test_side_metadata")] use side_metadata::*; @@ -581,9 +588,11 @@ impl crate::vm::ObjectModel for MockVM { 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 = MOCK_VM_LOCAL_FORWARDING_BITS_SPEC; + const LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = + 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 = MOCK_VM_LOCAL_LOS_MARK_NURSERY_SPEC; + const LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = + MOCK_VM_LOCAL_LOS_MARK_NURSERY_SPEC; #[cfg(feature = "object_pinning")] const LOCAL_PINNING_BIT_SPEC: VMLocalPinningBitSpec = VMLocalPinningBitSpec::in_header(0); @@ -685,9 +694,9 @@ 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, @@ -697,8 +706,8 @@ impl crate::vm::Scanning for MockVM { 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))) warn!("scan_vm_specific_roots is not properly mocked. The default implementation does nothing."); @@ -713,8 +722,8 @@ 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)) @@ -722,8 +731,8 @@ impl crate::vm::Scanning for MockVM { 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)) 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 667751e56a..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 @@ -14,7 +14,8 @@ pub fn allocate_no_gc_simple() { const MB: usize = 1024 * 1024; let fixture = MutatorFixture::create_with_heapsize(MB); - if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC { + 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; } 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 998ea8b18b..6209a317de 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_nonmoving.rs @@ -24,13 +24,8 @@ pub fn allocate_nonmoving() { info!("Allocated default at: {:#x}", addr); // Non moving alloc - let addr = memory_manager::alloc( - 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_overcommit.rs b/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs index 0f2ff9e7e1..228ec8efe1 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_overcommit.rs @@ -16,7 +16,8 @@ pub fn allocate_overcommit() { const MB: usize = 1024 * 1024; let fixture = MutatorFixture::create_with_heapsize(MB); - if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC { + if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC + { // Overcommit still triggers GC. For NoGC plan, triggering GC causes panic. return; } 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 6d0b0e61c3..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 @@ -18,13 +18,15 @@ pub fn allocate_with_initialize_collection() { const MB: usize = 1024 * 1024; let fixture = MutatorFixture::create_with_heapsize(MB); - if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC { + 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)); + mock.block_for_gc + .call(VMMutatorThread(VMThread::UNINITIALIZED)); }); } 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 b1c96bddc2..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 @@ -26,7 +26,8 @@ pub fn allocate_with_re_enable_collection() { const MB: usize = 1024 * 1024; let fixture = MutatorFixture::create_with_heapsize(MB); - if *fixture.mmtk().get_plan().options().plan == crate::util::options::PlanSelector::NoGC { + 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| { @@ -35,7 +36,8 @@ pub fn allocate_with_re_enable_collection() { mock.is_collection_enabled.call(()); mock.is_collection_enabled.call(()); mock.is_collection_enabled.call(()); - mock.block_for_gc.call(VMMutatorThread(VMThread::UNINITIALIZED)); + mock.block_for_gc + .call(VMMutatorThread(VMThread::UNINITIALIZED)); }); } 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 79e8414542..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 @@ -23,19 +23,17 @@ pub fn allocate_without_initialize_collection() { ); // Do not initialize collection // Build mutator - let mut mutator = mock_api::bind_mutator().as_mock_mutator(); + 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 index 8cd7520159..ceb2ae863a 100644 --- a/src/vm/tests/mock_tests/mock_test_gc.rs +++ b/src/vm/tests/mock_tests/mock_test_gc.rs @@ -18,7 +18,7 @@ pub fn simple_gc() { assert!(!addr.is_zero()); info!("Allocated default at: {:#x}", addr); - memory_manager::handle_user_collection_request(&fixture.mmtk(), fixture.mutator_tls()); + memory_manager::handle_user_collection_request(fixture.mmtk(), fixture.mutator_tls()); }, no_cleanup, ) diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index 4caed6bc96..9d70fb38bf 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -18,17 +18,17 @@ 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_vm::mock_api; pub use crate::util::test_util::mock_vm::*; pub use crate::vm::*; - pub use crate::util::test_util::mock_vm::mock_api; } mod mock_test_allocate_align; -mod mock_test_allocate_offset; mod mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call; mod mock_test_allocate_no_gc_oom_on_acquire_no_oom_call; mod mock_test_allocate_no_gc_simple; mod mock_test_allocate_nonmoving; +mod mock_test_allocate_offset; mod mock_test_allocate_overcommit; mod mock_test_allocate_with_disable_collection; mod mock_test_allocate_with_initialize_collection; @@ -66,12 +66,12 @@ mod mock_test_malloc_ms; mod mock_test_mmtk_julia_pr_143; #[cfg(feature = "nogc_lock_free")] mod mock_test_nogc_lock_free; -mod mock_test_slots_simple; #[cfg(target_pointer_width = "64")] mod mock_test_slots_compressed; +mod mock_test_slots_mixed; mod mock_test_slots_offset; +mod mock_test_slots_simple; mod mock_test_slots_tagged; -mod mock_test_slots_mixed; #[cfg(target_pointer_width = "64")] mod mock_test_vm_layout_compressed_pointer; mod mock_test_vm_layout_default; From 2aaeac9af4eab307cd67e131df5df82de7f41428 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Sun, 26 Oct 2025 23:17:25 +0000 Subject: [PATCH 7/8] Downgrade home 0.5.12 to 0.5.5 for Rust edition --- .github/scripts/ci-common.sh | 1 + 1 file changed, 1 insertion(+) 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() { From a97d54be8e87e50980aaff47de753cdc812f60c6 Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Mon, 27 Oct 2025 00:40:01 +0000 Subject: [PATCH 8/8] Merge some test files that were split unncessarily. Minor changes. Docs. --- src/util/test_util/mock_vm/mock_api.rs | 38 +- src/util/test_util/mock_vm/vm.rs | 38 +- ....rs => mock_test_allocate_align_offset.rs} | 37 ++ ...ock_test_internal_ptr_before_object_ref.rs | 2 +- ...st_internal_ptr_large_object_multi_page.rs | 2 +- ...est_internal_ptr_large_object_same_page.rs | 2 +- .../mock_test_internal_ptr_normal_object.rs | 4 +- src/vm/tests/mock_tests/mock_test_slots.rs | 438 ++++++++++++++++++ .../mock_tests/mock_test_slots_compressed.rs | 89 ---- .../tests/mock_tests/mock_test_slots_mixed.rs | 110 ----- .../mock_tests/mock_test_slots_offset.rs | 106 ----- .../mock_tests/mock_test_slots_simple.rs | 52 --- .../mock_tests/mock_test_slots_tagged.rs | 116 ----- src/vm/tests/mock_tests/mod.rs | 10 +- 14 files changed, 531 insertions(+), 513 deletions(-) rename src/vm/tests/mock_tests/{mock_test_allocate_align.rs => mock_test_allocate_align_offset.rs} (51%) create mode 100644 src/vm/tests/mock_tests/mock_test_slots.rs delete mode 100644 src/vm/tests/mock_tests/mock_test_slots_compressed.rs delete mode 100644 src/vm/tests/mock_tests/mock_test_slots_mixed.rs delete mode 100644 src/vm/tests/mock_tests/mock_test_slots_offset.rs delete mode 100644 src/vm/tests/mock_tests/mock_test_slots_simple.rs delete mode 100644 src/vm/tests/mock_tests/mock_test_slots_tagged.rs diff --git a/src/util/test_util/mock_vm/mock_api.rs b/src/util/test_util/mock_vm/mock_api.rs index 7d7b709813..f67ac21cbf 100644 --- a/src/util/test_util/mock_vm/mock_api.rs +++ b/src/util/test_util/mock_vm/mock_api.rs @@ -1,11 +1,18 @@ +//! 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::Mutator; 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"); @@ -13,6 +20,7 @@ pub fn singleton() -> &'static MMTK { } } +/// 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"); @@ -20,35 +28,15 @@ pub fn singleton_mut() -> &'static mut MMTK { } } +/// 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; } } -impl VMMutatorThread { - pub fn as_mock_mutator(self) -> &'static mut Mutator { - unsafe { &mut *(*self.0 .0.to_address().to_mut_ptr::()).ptr } - } -} - +/// Bind a mutator thread to the MMTK singleton instance for MockVM. pub fn bind_mutator() -> VMMutatorThread { - let mmtk = singleton(); - let mutator_handle = Box::new(vm::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; - } - - vm::MUTATOR_PARK.register(tls.0); - tls + vm::MutatorHandle::bind() } diff --git a/src/util/test_util/mock_vm/vm.rs b/src/util/test_util/mock_vm/vm.rs index 813dc69b1c..701d021916 100644 --- a/src/util/test_util/mock_vm/vm.rs +++ b/src/util/test_util/mock_vm/vm.rs @@ -32,7 +32,7 @@ use std::ops::Range; /// 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`. +/// 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. @@ -65,9 +65,12 @@ macro_rules! mock_any { }; } +/// Initialize the static MockVM instance. pub fn init_mockvm(mockvm: MockVM) { unsafe { - // assert!(MOCK_VM_INSTANCE.is_null()); + 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); } @@ -286,12 +289,36 @@ 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 } } @@ -300,6 +327,13 @@ impl MutatorHandle { 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. diff --git a/src/vm/tests/mock_tests/mock_test_allocate_align.rs b/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs similarity index 51% rename from src/vm/tests/mock_tests/mock_test_allocate_align.rs rename to src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs index 71eaf33e42..37e979134f 100644 --- a/src/vm/tests/mock_tests/mock_test_allocate_align.rs +++ b/src/vm/tests/mock_tests/mock_test_allocate_align_offset.rs @@ -41,3 +41,40 @@ pub fn allocate_alignment() { no_cleanup, ) } + +#[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_internal_ptr_before_object_ref.rs b/src/vm/tests/mock_tests/mock_test_internal_ptr_before_object_ref.rs index 7995dbf2b4..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,7 +17,7 @@ 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( fixture.mutator(), 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 cdc7738f4a..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,7 +20,7 @@ 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( fixture.mutator(), 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 937f65a325..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,7 +21,7 @@ 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( fixture.mutator(), 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 0976e05e4b..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,9 +18,9 @@ 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( fixture.mutator(), OBJECT_SIZE, diff --git a/src/vm/tests/mock_tests/mock_test_slots.rs b/src/vm/tests/mock_tests/mock_test_slots.rs new file mode 100644 index 0000000000..dd9432d60d --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_slots.rs @@ -0,0 +1,438 @@ +// GITHUB-CI: MMTK_PLAN=NoGC + +#![allow(unused)] + +use super::mock_test_prelude::*; +use crate::{ + util::{Address, ObjectReference}, + vm::slot::{SimpleSlot, Slot}, +}; +use atomic::{Atomic, Ordering}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +mod simple_slots { + use super::*; + + #[test] + pub fn load_simple() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot: Atomic = Atomic::new(fixture.objref1); + + let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); + let objref = slot.load(); + + assert_eq!(objref, Some(fixture.objref1)); + }); + }, + no_cleanup, + ) + } + + #[test] + pub fn store_simple() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot: Atomic = Atomic::new(fixture.objref1); + + let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); + slot.store(fixture.objref2); + assert_eq!(rust_slot.load(Ordering::SeqCst), fixture.objref2); + + let objref = slot.load(); + assert_eq!(objref, Some(fixture.objref2)); + }); + }, + no_cleanup, + ) + } +} + +#[cfg(target_pointer_width = "64")] +mod compressed_oop { + use super::*; + + /// This represents a location that holds a 32-bit pointer on a 64-bit machine. + /// + /// OpenJDK uses this kind of slot to store compressed OOPs on 64-bit machines. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct CompressedOopSlot { + slot_addr: *mut Atomic, + } + + unsafe impl Send for CompressedOopSlot {} + + impl CompressedOopSlot { + pub fn from_address(address: Address) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + } + } + pub fn as_address(&self) -> Address { + Address::from_mut_ptr(self.slot_addr) + } + } + + impl Slot for CompressedOopSlot { + fn load(&self) -> Option { + let compressed = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let expanded = (compressed as usize) << 3; + ObjectReference::from_raw_address(unsafe { Address::from_usize(expanded) }) + } + + fn store(&self, object: ObjectReference) { + let expanded = object.to_raw_address().as_usize(); + let compressed = (expanded >> 3) as u32; + unsafe { (*self.slot_addr).store(compressed, atomic::Ordering::Relaxed) } + } + } + + // Two 35-bit addresses aligned to 8 bytes (3 zeros in the lowest bits). + const COMPRESSABLE_ADDR1: usize = 0b101_10111011_11011111_01111110_11111000usize; + const COMPRESSABLE_ADDR2: usize = 0b110_11110111_01101010_11011101_11101000usize; + + #[test] + pub fn load_compressed() { + // Note: We cannot guarantee GC will allocate an object in the low address region. + // So we make up addresses just for testing the bit operations of compressed OOP slots. + let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; + let objref1 = + ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR1) }); + + let mut rust_slot: Atomic = Atomic::new(compressed1); + + let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); + let objref = slot.load(); + + assert_eq!(objref, objref1); + } + + #[test] + pub fn store_compressed() { + // Note: We cannot guarantee GC will allocate an object in the low address region. + // So we make up addresses just for testing the bit operations of compressed OOP slots. + let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; + let compressed2 = (COMPRESSABLE_ADDR2 >> 3) as u32; + let objref2 = + ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR2) }) + .unwrap(); + + let mut rust_slot: Atomic = Atomic::new(compressed1); + + let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); + slot.store(objref2); + assert_eq!(rust_slot.load(Ordering::SeqCst), compressed2); + + let objref = slot.load(); + assert_eq!(objref, Some(objref2)); + } +} + +mod offset_slot { + use super::*; + + /// This represents a slot that holds a pointer to the *middle* of an object, and the offset is known. + /// + /// Julia uses this trick to facilitate deleting array elements from the front. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct OffsetSlot { + slot_addr: *mut Atomic
, + offset: usize, + } + + unsafe impl Send for OffsetSlot {} + + impl OffsetSlot { + pub fn new_no_offset(address: Address) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + offset: 0, + } + } + + pub fn new_with_offset(address: Address, offset: usize) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + offset, + } + } + + pub fn slot_address(&self) -> Address { + Address::from_mut_ptr(self.slot_addr) + } + + pub fn offset(&self) -> usize { + self.offset + } + } + + impl Slot for OffsetSlot { + fn load(&self) -> Option { + let middle = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let begin = middle - self.offset; + ObjectReference::from_raw_address(begin) + } + + fn store(&self, object: ObjectReference) { + let begin = object.to_raw_address(); + let middle = begin + self.offset; + unsafe { (*self.slot_addr).store(middle, atomic::Ordering::Relaxed) } + } + } + + pub const OFFSET: usize = 48; + + #[test] + pub fn load_offset() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let addr1 = fixture.objref1.to_raw_address(); + let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); + + let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); + let objref = slot.load(); + + assert_eq!(objref, Some(fixture.objref1)); + }); + }, + no_cleanup, + ) + } + + #[test] + pub fn store_offset() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let addr1 = fixture.objref1.to_raw_address(); + let addr2 = fixture.objref2.to_raw_address(); + let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); + + let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); + slot.store(fixture.objref2); + assert_eq!(rust_slot.load(Ordering::SeqCst), addr2 + OFFSET); + + let objref = slot.load(); + assert_eq!(objref, Some(fixture.objref2)); + }); + }, + no_cleanup, + ) + } +} + +mod tagged_slot { + use super::*; + + /// This slot represents a slot that holds a tagged pointer. + /// The last two bits are tag bits and are not part of the object reference. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct TaggedSlot { + slot_addr: *mut Atomic, + } + + unsafe impl Send for TaggedSlot {} + + impl TaggedSlot { + // The DummyVM has OBJECT_REF_OFFSET = 4. + // Using a two-bit tag should be safe on both 32-bit and 64-bit platforms. + const TAG_BITS_MASK: usize = 0b11; + + pub fn new(address: Address) -> Self { + Self { + slot_addr: address.to_mut_ptr(), + } + } + } + + impl Slot for TaggedSlot { + fn load(&self) -> Option { + let tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let untagged = tagged & !Self::TAG_BITS_MASK; + ObjectReference::from_raw_address(unsafe { Address::from_usize(untagged) }) + } + + fn store(&self, object: ObjectReference) { + let old_tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; + let new_untagged = object.to_raw_address().as_usize(); + let new_tagged = new_untagged | (old_tagged & Self::TAG_BITS_MASK); + unsafe { (*self.slot_addr).store(new_tagged, atomic::Ordering::Relaxed) } + } + } + + pub const TAG1: usize = 0b01; + pub const TAG2: usize = 0b10; + + #[test] + pub fn load_tagged() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot1: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); + let mut rust_slot2: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); + + let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); + let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); + let objref1 = slot1.load(); + let objref2 = slot2.load(); + + // Tags should not affect loaded values. + assert_eq!(objref1, Some(fixture.objref1)); + assert_eq!(objref2, Some(fixture.objref1)); + }); + }, + no_cleanup, + ) + } + + #[test] + pub fn store_tagged() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture(|fixture| { + let mut rust_slot1: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); + let mut rust_slot2: Atomic = + Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); + + let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); + let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); + slot1.store(fixture.objref2); + slot2.store(fixture.objref2); + + // Tags should be preserved. + assert_eq!( + rust_slot1.load(Ordering::SeqCst), + fixture.objref2.to_raw_address().as_usize() | TAG1 + ); + assert_eq!( + rust_slot2.load(Ordering::SeqCst), + fixture.objref2.to_raw_address().as_usize() | TAG2 + ); + + let objref1 = slot1.load(); + let objref2 = slot2.load(); + + // Tags should not affect loaded values. + assert_eq!(objref1, Some(fixture.objref2)); + assert_eq!(objref2, Some(fixture.objref2)); + }); + }, + no_cleanup, + ) + } +} + +mod mixed { + #[cfg(target_pointer_width = "64")] + use super::compressed_oop::CompressedOopSlot; + use super::offset_slot::OffsetSlot; + use super::offset_slot::OFFSET; + use super::tagged_slot::TaggedSlot; + use super::tagged_slot::TAG1; + use super::*; + use crate::vm::slot::SimpleSlot; + + /// If a VM supports multiple kinds of slots, we can use tagged union to represent all of them. + /// This is for testing, only. A Rust `enum` may not be the most efficient representation. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub enum DummyVMSlot { + Simple(SimpleSlot), + #[cfg(target_pointer_width = "64")] + Compressed(CompressedOopSlot), + Offset(OffsetSlot), + Tagged(TaggedSlot), + } + + unsafe impl Send for DummyVMSlot {} + + impl Slot for DummyVMSlot { + fn load(&self) -> Option { + match self { + DummyVMSlot::Simple(e) => e.load(), + #[cfg(target_pointer_width = "64")] + DummyVMSlot::Compressed(e) => e.load(), + DummyVMSlot::Offset(e) => e.load(), + DummyVMSlot::Tagged(e) => e.load(), + } + } + + fn store(&self, object: ObjectReference) { + match self { + DummyVMSlot::Simple(e) => e.store(object), + #[cfg(target_pointer_width = "64")] + DummyVMSlot::Compressed(e) => e.store(object), + DummyVMSlot::Offset(e) => e.store(object), + DummyVMSlot::Tagged(e) => e.store(object), + } + } + } + + #[test] + pub fn mixed() { + with_mockvm( + default_setup, + || { + const OFFSET: usize = 48; + + FIXTURE.with_fixture(|fixture| { + let addr1 = fixture.objref1.to_raw_address(); + let addr2 = fixture.objref2.to_raw_address(); + + let mut rust_slot1: Atomic = Atomic::new(fixture.objref1); + let mut rust_slot3: Atomic
= Atomic::new(addr1 + OFFSET); + let mut rust_slot4: Atomic = Atomic::new(addr1.as_usize() | TAG1); + + let slot1 = SimpleSlot::from_address(Address::from_ref(&rust_slot1)); + let slot3 = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot3), OFFSET); + let slot4 = TaggedSlot::new(Address::from_ref(&rust_slot4)); + + let ds1 = DummyVMSlot::Simple(slot1); + let ds3 = DummyVMSlot::Offset(slot3); + let ds4 = DummyVMSlot::Tagged(slot4); + + let slots = [ds1, ds3, ds4]; + for (i, slot) in slots.iter().enumerate() { + let objref = slot.load(); + assert_eq!( + objref, + Some(fixture.objref1), + "Slot {} is not properly loaded", + i + ); + } + + let mutable_slots = [ds1, ds3, ds4]; + for (i, slot) in mutable_slots.iter().enumerate() { + slot.store(fixture.objref2); + let objref = slot.load(); + assert_eq!( + objref, + Some(fixture.objref2), + "Slot {} is not properly loaded after store", + i + ); + } + + assert_eq!(rust_slot1.load(Ordering::SeqCst), fixture.objref2); + assert_eq!(rust_slot3.load(Ordering::SeqCst), addr2 + OFFSET); + }); + }, + no_cleanup, + ) + } +} diff --git a/src/vm/tests/mock_tests/mock_test_slots_compressed.rs b/src/vm/tests/mock_tests/mock_test_slots_compressed.rs deleted file mode 100644 index 5c4e6b9d26..0000000000 --- a/src/vm/tests/mock_tests/mock_test_slots_compressed.rs +++ /dev/null @@ -1,89 +0,0 @@ -// GITHUB-CI: MMTK_PLAN=NoGC - -#![allow(unused)] - -use super::mock_test_prelude::*; -use crate::{ - util::{Address, ObjectReference}, - vm::slot::{SimpleSlot, Slot}, -}; -use atomic::{Atomic, Ordering}; - -lazy_static! { - static ref FIXTURE: Fixture = Fixture::new(); -} - -/// This represents a location that holds a 32-bit pointer on a 64-bit machine. -/// -/// OpenJDK uses this kind of slot to store compressed OOPs on 64-bit machines. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct CompressedOopSlot { - slot_addr: *mut Atomic, -} - -unsafe impl Send for CompressedOopSlot {} - -impl CompressedOopSlot { - pub fn from_address(address: Address) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - } - } - pub fn as_address(&self) -> Address { - Address::from_mut_ptr(self.slot_addr) - } -} - -impl Slot for CompressedOopSlot { - fn load(&self) -> Option { - let compressed = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let expanded = (compressed as usize) << 3; - ObjectReference::from_raw_address(unsafe { Address::from_usize(expanded) }) - } - - fn store(&self, object: ObjectReference) { - let expanded = object.to_raw_address().as_usize(); - let compressed = (expanded >> 3) as u32; - unsafe { (*self.slot_addr).store(compressed, atomic::Ordering::Relaxed) } - } -} - -// Two 35-bit addresses aligned to 8 bytes (3 zeros in the lowest bits). -const COMPRESSABLE_ADDR1: usize = 0b101_10111011_11011111_01111110_11111000usize; -const COMPRESSABLE_ADDR2: usize = 0b110_11110111_01101010_11011101_11101000usize; - -#[test] -pub fn load_compressed() { - // Note: We cannot guarantee GC will allocate an object in the low address region. - // So we make up addresses just for testing the bit operations of compressed OOP slots. - let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; - let objref1 = - ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR1) }); - - let mut rust_slot: Atomic = Atomic::new(compressed1); - - let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); - let objref = slot.load(); - - assert_eq!(objref, objref1); -} - -#[test] -pub fn store_compressed() { - // Note: We cannot guarantee GC will allocate an object in the low address region. - // So we make up addresses just for testing the bit operations of compressed OOP slots. - let compressed1 = (COMPRESSABLE_ADDR1 >> 3) as u32; - let compressed2 = (COMPRESSABLE_ADDR2 >> 3) as u32; - let objref2 = - ObjectReference::from_raw_address(unsafe { Address::from_usize(COMPRESSABLE_ADDR2) }) - .unwrap(); - - let mut rust_slot: Atomic = Atomic::new(compressed1); - - let slot = CompressedOopSlot::from_address(Address::from_ref(&rust_slot)); - slot.store(objref2); - assert_eq!(rust_slot.load(Ordering::SeqCst), compressed2); - - let objref = slot.load(); - assert_eq!(objref, Some(objref2)); -} diff --git a/src/vm/tests/mock_tests/mock_test_slots_mixed.rs b/src/vm/tests/mock_tests/mock_test_slots_mixed.rs deleted file mode 100644 index 8befd1d0b6..0000000000 --- a/src/vm/tests/mock_tests/mock_test_slots_mixed.rs +++ /dev/null @@ -1,110 +0,0 @@ -// GITHUB-CI: MMTK_PLAN=NoGC - -#![allow(unused)] - -use super::mock_test_prelude::*; -use crate::{ - util::{Address, ObjectReference}, - vm::slot::{SimpleSlot, Slot}, -}; -use atomic::{Atomic, Ordering}; - -lazy_static! { - static ref FIXTURE: Fixture = Fixture::new(); -} - -#[cfg(target_pointer_width = "64")] -use super::mock_test_slots_compressed::CompressedOopSlot; -use super::mock_test_slots_offset::OffsetSlot; -use super::mock_test_slots_offset::OFFSET; -use super::mock_test_slots_tagged::TaggedSlot; -use super::mock_test_slots_tagged::TAG1; - -/// If a VM supports multiple kinds of slots, we can use tagged union to represent all of them. -/// This is for testing, only. A Rust `enum` may not be the most efficient representation. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum DummyVMSlot { - Simple(SimpleSlot), - #[cfg(target_pointer_width = "64")] - Compressed(CompressedOopSlot), - Offset(OffsetSlot), - Tagged(TaggedSlot), -} - -unsafe impl Send for DummyVMSlot {} - -impl Slot for DummyVMSlot { - fn load(&self) -> Option { - match self { - DummyVMSlot::Simple(e) => e.load(), - #[cfg(target_pointer_width = "64")] - DummyVMSlot::Compressed(e) => e.load(), - DummyVMSlot::Offset(e) => e.load(), - DummyVMSlot::Tagged(e) => e.load(), - } - } - - fn store(&self, object: ObjectReference) { - match self { - DummyVMSlot::Simple(e) => e.store(object), - #[cfg(target_pointer_width = "64")] - DummyVMSlot::Compressed(e) => e.store(object), - DummyVMSlot::Offset(e) => e.store(object), - DummyVMSlot::Tagged(e) => e.store(object), - } - } -} - -#[test] -pub fn mixed() { - with_mockvm( - default_setup, - || { - const OFFSET: usize = 48; - - FIXTURE.with_fixture(|fixture| { - let addr1 = fixture.objref1.to_raw_address(); - let addr2 = fixture.objref2.to_raw_address(); - - let mut rust_slot1: Atomic = Atomic::new(fixture.objref1); - let mut rust_slot3: Atomic
= Atomic::new(addr1 + OFFSET); - let mut rust_slot4: Atomic = Atomic::new(addr1.as_usize() | TAG1); - - let slot1 = SimpleSlot::from_address(Address::from_ref(&rust_slot1)); - let slot3 = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot3), OFFSET); - let slot4 = TaggedSlot::new(Address::from_ref(&rust_slot4)); - - let ds1 = DummyVMSlot::Simple(slot1); - let ds3 = DummyVMSlot::Offset(slot3); - let ds4 = DummyVMSlot::Tagged(slot4); - - let slots = [ds1, ds3, ds4]; - for (i, slot) in slots.iter().enumerate() { - let objref = slot.load(); - assert_eq!( - objref, - Some(fixture.objref1), - "Slot {} is not properly loaded", - i - ); - } - - let mutable_slots = [ds1, ds3, ds4]; - for (i, slot) in mutable_slots.iter().enumerate() { - slot.store(fixture.objref2); - let objref = slot.load(); - assert_eq!( - objref, - Some(fixture.objref2), - "Slot {} is not properly loaded after store", - i - ); - } - - assert_eq!(rust_slot1.load(Ordering::SeqCst), fixture.objref2); - assert_eq!(rust_slot3.load(Ordering::SeqCst), addr2 + OFFSET); - }); - }, - no_cleanup, - ) -} diff --git a/src/vm/tests/mock_tests/mock_test_slots_offset.rs b/src/vm/tests/mock_tests/mock_test_slots_offset.rs deleted file mode 100644 index b4fc688869..0000000000 --- a/src/vm/tests/mock_tests/mock_test_slots_offset.rs +++ /dev/null @@ -1,106 +0,0 @@ -// GITHUB-CI: MMTK_PLAN=NoGC - -#![allow(unused)] - -use super::mock_test_prelude::*; -use crate::{ - util::{Address, ObjectReference}, - vm::slot::{SimpleSlot, Slot}, -}; -use atomic::{Atomic, Ordering}; - -lazy_static! { - static ref FIXTURE: Fixture = Fixture::new(); -} - -/// This represents a slot that holds a pointer to the *middle* of an object, and the offset is known. -/// -/// Julia uses this trick to facilitate deleting array elements from the front. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct OffsetSlot { - slot_addr: *mut Atomic
, - offset: usize, -} - -unsafe impl Send for OffsetSlot {} - -impl OffsetSlot { - pub fn new_no_offset(address: Address) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - offset: 0, - } - } - - pub fn new_with_offset(address: Address, offset: usize) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - offset, - } - } - - pub fn slot_address(&self) -> Address { - Address::from_mut_ptr(self.slot_addr) - } - - pub fn offset(&self) -> usize { - self.offset - } -} - -impl Slot for OffsetSlot { - fn load(&self) -> Option { - let middle = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let begin = middle - self.offset; - ObjectReference::from_raw_address(begin) - } - - fn store(&self, object: ObjectReference) { - let begin = object.to_raw_address(); - let middle = begin + self.offset; - unsafe { (*self.slot_addr).store(middle, atomic::Ordering::Relaxed) } - } -} - -pub const OFFSET: usize = 48; - -#[test] -pub fn load_offset() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let addr1 = fixture.objref1.to_raw_address(); - let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); - - let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); - let objref = slot.load(); - - assert_eq!(objref, Some(fixture.objref1)); - }); - }, - no_cleanup, - ) -} - -#[test] -pub fn store_offset() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let addr1 = fixture.objref1.to_raw_address(); - let addr2 = fixture.objref2.to_raw_address(); - let mut rust_slot: Atomic
= Atomic::new(addr1 + OFFSET); - - let slot = OffsetSlot::new_with_offset(Address::from_ref(&rust_slot), OFFSET); - slot.store(fixture.objref2); - assert_eq!(rust_slot.load(Ordering::SeqCst), addr2 + OFFSET); - - let objref = slot.load(); - assert_eq!(objref, Some(fixture.objref2)); - }); - }, - no_cleanup, - ) -} diff --git a/src/vm/tests/mock_tests/mock_test_slots_simple.rs b/src/vm/tests/mock_tests/mock_test_slots_simple.rs deleted file mode 100644 index bb24554887..0000000000 --- a/src/vm/tests/mock_tests/mock_test_slots_simple.rs +++ /dev/null @@ -1,52 +0,0 @@ -// GITHUB-CI: MMTK_PLAN=NoGC - -#![allow(unused)] - -use super::mock_test_prelude::*; -use crate::{ - util::{Address, ObjectReference}, - vm::slot::{SimpleSlot, Slot}, -}; -use atomic::{Atomic, Ordering}; - -lazy_static! { - static ref FIXTURE: Fixture = Fixture::new(); -} - -#[test] -pub fn load_simple() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot: Atomic = Atomic::new(fixture.objref1); - - let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); - let objref = slot.load(); - - assert_eq!(objref, Some(fixture.objref1)); - }); - }, - no_cleanup, - ) -} - -#[test] -pub fn store_simple() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot: Atomic = Atomic::new(fixture.objref1); - - let slot = SimpleSlot::from_address(Address::from_ref(&rust_slot)); - slot.store(fixture.objref2); - assert_eq!(rust_slot.load(Ordering::SeqCst), fixture.objref2); - - let objref = slot.load(); - assert_eq!(objref, Some(fixture.objref2)); - }); - }, - no_cleanup, - ) -} diff --git a/src/vm/tests/mock_tests/mock_test_slots_tagged.rs b/src/vm/tests/mock_tests/mock_test_slots_tagged.rs deleted file mode 100644 index b15d840be8..0000000000 --- a/src/vm/tests/mock_tests/mock_test_slots_tagged.rs +++ /dev/null @@ -1,116 +0,0 @@ -// GITHUB-CI: MMTK_PLAN=NoGC - -#![allow(unused)] - -use super::mock_test_prelude::*; -use crate::{ - util::{Address, ObjectReference}, - vm::slot::{SimpleSlot, Slot}, -}; -use atomic::{Atomic, Ordering}; - -lazy_static! { - static ref FIXTURE: Fixture = Fixture::new(); -} - -/// This slot represents a slot that holds a tagged pointer. -/// The last two bits are tag bits and are not part of the object reference. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct TaggedSlot { - slot_addr: *mut Atomic, -} - -unsafe impl Send for TaggedSlot {} - -impl TaggedSlot { - // The DummyVM has OBJECT_REF_OFFSET = 4. - // Using a two-bit tag should be safe on both 32-bit and 64-bit platforms. - const TAG_BITS_MASK: usize = 0b11; - - pub fn new(address: Address) -> Self { - Self { - slot_addr: address.to_mut_ptr(), - } - } -} - -impl Slot for TaggedSlot { - fn load(&self) -> Option { - let tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let untagged = tagged & !Self::TAG_BITS_MASK; - ObjectReference::from_raw_address(unsafe { Address::from_usize(untagged) }) - } - - fn store(&self, object: ObjectReference) { - let old_tagged = unsafe { (*self.slot_addr).load(atomic::Ordering::Relaxed) }; - let new_untagged = object.to_raw_address().as_usize(); - let new_tagged = new_untagged | (old_tagged & Self::TAG_BITS_MASK); - unsafe { (*self.slot_addr).store(new_tagged, atomic::Ordering::Relaxed) } - } -} - -pub const TAG1: usize = 0b01; -pub const TAG2: usize = 0b10; - -#[test] -pub fn load_tagged() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot1: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); - let mut rust_slot2: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); - - let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); - let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); - let objref1 = slot1.load(); - let objref2 = slot2.load(); - - // Tags should not affect loaded values. - assert_eq!(objref1, Some(fixture.objref1)); - assert_eq!(objref2, Some(fixture.objref1)); - }); - }, - no_cleanup, - ) -} - -#[test] -pub fn store_tagged() { - with_mockvm( - default_setup, - || { - FIXTURE.with_fixture(|fixture| { - let mut rust_slot1: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG1); - let mut rust_slot2: Atomic = - Atomic::new(fixture.objref1.to_raw_address().as_usize() | TAG2); - - let slot1 = TaggedSlot::new(Address::from_ref(&rust_slot1)); - let slot2 = TaggedSlot::new(Address::from_ref(&rust_slot2)); - slot1.store(fixture.objref2); - slot2.store(fixture.objref2); - - // Tags should be preserved. - assert_eq!( - rust_slot1.load(Ordering::SeqCst), - fixture.objref2.to_raw_address().as_usize() | TAG1 - ); - assert_eq!( - rust_slot2.load(Ordering::SeqCst), - fixture.objref2.to_raw_address().as_usize() | TAG2 - ); - - let objref1 = slot1.load(); - let objref2 = slot2.load(); - - // Tags should not affect loaded values. - assert_eq!(objref1, Some(fixture.objref2)); - assert_eq!(objref2, Some(fixture.objref2)); - }); - }, - no_cleanup, - ) -} diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index 9d70fb38bf..b48272f4dd 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -23,12 +23,11 @@ pub(crate) mod mock_test_prelude { pub use crate::vm::*; } -mod mock_test_allocate_align; +mod mock_test_allocate_align_offset; mod mock_test_allocate_no_gc_oom_on_acquire_allow_oom_call; mod mock_test_allocate_no_gc_oom_on_acquire_no_oom_call; mod mock_test_allocate_no_gc_simple; mod mock_test_allocate_nonmoving; -mod mock_test_allocate_offset; mod mock_test_allocate_overcommit; mod mock_test_allocate_with_disable_collection; mod mock_test_allocate_with_initialize_collection; @@ -66,12 +65,7 @@ mod mock_test_malloc_ms; mod mock_test_mmtk_julia_pr_143; #[cfg(feature = "nogc_lock_free")] mod mock_test_nogc_lock_free; -#[cfg(target_pointer_width = "64")] -mod mock_test_slots_compressed; -mod mock_test_slots_mixed; -mod mock_test_slots_offset; -mod mock_test_slots_simple; -mod mock_test_slots_tagged; +mod mock_test_slots; #[cfg(target_pointer_width = "64")] mod mock_test_vm_layout_compressed_pointer; mod mock_test_vm_layout_default;