diff --git a/docs/userguide/src/SUMMARY.md b/docs/userguide/src/SUMMARY.md index 4108d3fd34..ee6e0cb513 100644 --- a/docs/userguide/src/SUMMARY.md +++ b/docs/userguide/src/SUMMARY.md @@ -31,6 +31,9 @@ - [How to Undertake a Port](portingguide/howto/prefix.md) - [NoGC](portingguide/howto/nogc.md) - [Next Steps](portingguide/howto/next_steps.md) + - [Performance Tuning](portingguide/perf_tuning/prefix.md) + - [Link Time Optimization](portingguide/perf_tuning/lto.md) + - [Optimizing Allocation](portingguide/perf_tuning/alloc.md) ----------- diff --git a/docs/userguide/src/portingguide/SUMMARY.md b/docs/userguide/src/portingguide/SUMMARY.md deleted file mode 100644 index 07f35c8bf0..0000000000 --- a/docs/userguide/src/portingguide/SUMMARY.md +++ /dev/null @@ -1,11 +0,0 @@ -# Summary - -[MMTk Porting Guide](./prefix.md) - -- [MMTk’s Approach to Portability](./portability.md) -- [Before Starting a Port](./before_start.md) -- [How to Undertake a Port](./howto/prefix.md) - - [NoGC](./howto/nogc.md) - - [Next Steps](./howto/next_steps.md) -- [Performance Tuning]() - - [Link Time Optimization](./perf_tuning/lto.md) diff --git a/docs/userguide/src/portingguide/perf_tuning/alloc.md b/docs/userguide/src/portingguide/perf_tuning/alloc.md new file mode 100644 index 0000000000..aab41d29d2 --- /dev/null +++ b/docs/userguide/src/portingguide/perf_tuning/alloc.md @@ -0,0 +1,120 @@ +# Optimizing Allocation + +MMTk provides [`alloc()`](https://docs.mmtk.io/api/mmtk/memory_manager/fn.alloc.html) +and [`post_alloc()`](https://docs.mmtk.io/api/mmtk/memory_manager/fn.post_alloc.html), to allocate a piece of memory, and +finalize the memory as an object. Calling them is sufficient for a functional implementation, and we recommend doing +so in the early development of an MMTk integration. However, as allocation is performance critical, runtimes generally would +optimize to make allocation as fast as possible, in which invoking `alloc()` and `post_alloc()` becomes inadequent. + +The following discusses a few design decisions and optimizations related to allocation. The discussion mainly focuses on `alloc()`. +`post_alloc()` works in a similar way, and the discussion can also be applied to `post_alloc()`. +For conrete examples, you can refer to any of our supported bindings, and check the implementation in the bindings. + +Note that some of the optimizations need to make assumptions about the MMTk's internal implementation and may make the code less maintainable. +We recommend adding assertions in the binding code to make sure the assumptions are not broken across versions. + +## Efficient access to MMTk mutators + +An MMTk mutator context (created by [`bind_mutator()`](https://docs.mmtk.io/api/mmtk/memory_manager/fn.bind_mutator.html)) is a thread local data structure +of type [`Mutator`](https://docs.mmtk.io/api/mmtk/plan/struct.Mutator.html). +MMTk expects the binding to provide efficient access to the mutator structure in their thread local storage (TLS). +Usually one of the following approaches is used to store MMTk mutators. + +### Option 1: Storing the pointer + +The `Box>` returned from `mmtk::memory_manager::bind_mutator` is actually a pointer to +a `Mutator` instance allocated in the Rust heap. It is simple to store it in the TLS. +This approach does not make any assumption about the intenral of a MMTk `Mutator`. However, it requires an extra pointer dereference +whene accessing a value in the mutator. This may sound not that bad. However, this degrades the performance of +a carefully implemented inlined fastpath allocation sequence which is normally just a few instructions. +This approach could be a simple start in the early development, but we do not recommend it for an efficient implementation. + +If the VM is not implemented in Rust, +the binding needs to turn the boxed pointer into a raw pointer before storing it. + +```rust +{{#include ../../../../../vmbindings/dummyvm/src/tests/doc_mutator_storage.rs:mutator_storage_boxed_pointer}} +``` + +### Option 2: Embed the `Mutator` struct + +To remove the extra pointer dereference, the binding can embed the `Mutator` type into their TLS type. This saves the extra dereference. + +If the implementation language is not Rust, the developer needs to create a type that has the same layout as `Mutator`. It is recommended to +have an assertion to ensure that the native type has the exact same layout as the Rust type `Mutator`. + +```rust +{{#include ../../../../../vmbindings/dummyvm/src/tests/doc_mutator_storage.rs:mutator_storage_embed_mutator_struct}} +``` + +### Option 3: Embed the fastpath struct + +The size of `Mutator` is a few hundreds of bytes, which could be considered as too large for TLS in some langauge implementations. +Embedding `Mutator` also requires to duplicate a native type for the `Mutator` struct if the implementation language is not Rust. +Sometimes it is undesirable to embed the `Mutator` type. One can choose only embed the fastpath struct that is in use. + +Unlike the `Mutator` type, the fastpath struct has a C-compatible layout, and it is simple and primitive enough +so it is unlikely to change. For example, MMTk provides [`BumpPointer`](https://docs.mmtk.io/api/mmtk/util/alloc/struct.BumpPointer.html), +which simply includes a `cursor` and a `limit`. + +In the following example, we embed one `BumpPointer` struct in the TLS. +The `BumpPointer` is used in the fast path, and carefully synchronized with the allocator in the `Mutator` struct in the slow path. +Note that the `allocate_default` closure in the example below assumes the allocation semantics is `AllocationSemantics::Default` +and its selected allocator uses bump-pointer allocation. +Real-world fast-path implementations for high-performance VMs are usually JIT-compiled, inlined, and specialized for the current plan +and allocation site, so that the allocation semantics of the concrete allocation site (and therefore the selected allocator) is known to the JIT compiler. + +For the sake of simplicity, we only store _one_ `BumpPointer` in the TLS in the example. +In MMTk, each plan has multiple allocators, and the allocation semantics are mapped +to those allocator by the GC plan you choose. So a plan use multiple allocators, and +depending on how many allocation semantics are used by a binding, the binding may use multiple allocators as well. +In practice, a binding may embed multiple fastpath structs as the example for those allocators if they would like +more efficient allocation. + +Also for simpliticy, the example assumes the default allocator for the plan in use is a bump pointer allocator. +Many plans in MMTk use bump pointer allocator for their default allocation semantics (`AllocationSemantics::Default`), +which includes (but not limited to) `NoGC`, `SemiSpace`, `Immix`, generational plans, etc. +If a plan does not do bump-pointer allocation, we may still implement fast paths, but we need to embed different data structures instead of `BumpPointer`. + +```rust +{{#include ../../../../../vmbindings/dummyvm/src/tests/doc_mutator_storage.rs:mutator_storage_embed_fastpath_struct}} +``` + +## Avoid resolving the allocator at run time + +For a simple and general API of `alloc()`, MMTk requires `AllocationSemantics` as an argument in an allocation request, and resolves it at run-time. +The following is roughly what `alloc()` does internally. + +1. Resolving the allocator + 1. Find the `Allocator` for the required `AllocationSemantics`. It is defined by the plan in use. + 2. Dynamically dispatch the call to [`Allocator::alloc()`](https://docs.mmtk.io/api/mmtk/util/alloc/trait.Allocator.html#tymethod.alloc). +2. `Allocator::alloc()` executes the allocation fast path. +3. If the fastpath fails, it executes the allocation slow path [`Allocator::alloc_slow()`](https://docs.mmtk.io/api/mmtk/util/alloc/trait.Allocator.html#method.alloc_slow). +4. The slow path will further attempt to allocate memory, and may trigger a GC. + +Resolving to a specific allocator and doing dynamic dispatch is expensive for an allocation. +With the build-time or JIT-time knowledge on the object that will be allocated, an MMTK binding can possibly skip the first step in the run time. + +If you implement an efficient fastpath allocation in the binding side (like the Option 3 above, and generating allocation code in a JIT which will be discussed next), +that naturally avoids this problem. If you do not want to implement the fastpath allocation, the following is another example of how to avoid resolving the allocator. + +Once MMTK is initialized, a binding can get the memory offset for the default allocator, and save it somewhere. When we know an object should be allocated +with the default allocation semantics, we can use the offset to get a reference to the actual allocator (with unsafe code), and allocate with the allocator. + +```rust +{{#include ../../../../../vmbindings/dummyvm/src/tests/doc_avoid_resolving_allocator.rs:avoid_resolving_allocator}} +``` + +## Emitting Allocation Sequence in a JIT Compiler + +If the language has a JIT compiler, it is generally desirable to generate the code sequence for the allocation fast path, rather +than simply emitting a call instruction to the allocation function. The optimizations we talked above are relevant as well: 1. +the compiler needs to be able to access the mutator, and 2. the compiler needs to be able to resolve to a specific allocator at +JIT time. The actual implementation highly depends on the compiler implementation. + +The following are some examples from our bindings (at the time of writing): +* OpenJDK: + * + * +* JikesRVM: +* Julia: diff --git a/docs/userguide/src/portingguide/perf_tuning/prefix.md b/docs/userguide/src/portingguide/perf_tuning/prefix.md new file mode 100644 index 0000000000..e9dd76d090 --- /dev/null +++ b/docs/userguide/src/portingguide/perf_tuning/prefix.md @@ -0,0 +1,5 @@ +# Performance Tuning for Bindings + +In this section, we discuss how to achieve the best performance with MMTk in a binding implementation. +MMTk is a high performance GC library. But there are some key points that need to be done correctly +to achieve the optimal performance. diff --git a/docs/userguide/src/tutorial/SUMMARY.md b/docs/userguide/src/tutorial/SUMMARY.md deleted file mode 100644 index ff2343e6a3..0000000000 --- a/docs/userguide/src/tutorial/SUMMARY.md +++ /dev/null @@ -1,20 +0,0 @@ -# Summary - -[MMTk Tutorial](./prefix.md) - -- [Introduction]() - - [What is MMTk?](./intro/what_is_mmtk.md) - - [What will this tutorial cover?](./intro/what_will_this_tutorial_cover.md) - - [Glossary](./intro/glossary.md) -- [Preliminaries]() - - [Set up MMTk and OpenJDK](./preliminaries/set_up.md) - - [Test the build](./preliminaries/test.md) -- [MyGC]() - - [Create MyGC](./mygc/create.md) - - [Building a semispace GC](./mygc/ss/prefix.md) - - [Allocation](./mygc/ss/alloc.md) - - [Collection](./mygc/ss/collection.md) - - [Exercise](./mygc/ss/exercise.md) - - [Exercise solution](./mygc/ss/exercise_solution.md) - - [Building a generational copying GC](./mygc/gencopy.md) -- [Further Reading](./further_reading.md) diff --git a/src/memory_manager.rs b/src/memory_manager.rs index fc9d6b8c42..6db2a02db7 100644 --- a/src/memory_manager.rs +++ b/src/memory_manager.rs @@ -175,6 +175,27 @@ pub fn alloc( mutator.alloc(size, align, offset, semantics) } +/// Invoke the allocation slow path. This is only intended for use when a binding implements the fastpath on +/// the binding side. When the binding handles fast path allocation and the fast path fails, it can use this +/// method for slow path allocation. Calling before exhausting fast path allocaiton buffer will lead to bad +/// performance. +/// +/// Arguments: +/// * `mutator`: The mutator to perform this allocation request. +/// * `size`: The number of bytes required for the object. +/// * `align`: Required alignment for the object. +/// * `offset`: Offset associated with the alignment. +/// * `semantics`: The allocation semantic required for the allocation. +pub fn alloc_slow( + mutator: &mut Mutator, + size: usize, + align: usize, + offset: usize, + semantics: AllocationSemantics, +) -> Address { + mutator.alloc_slow(size, align, offset, semantics) +} + /// Perform post-allocation actions, usually initializing object metadata. For many allocators none are /// required. For performance reasons, a VM should implement the post alloc fast-path on their side /// rather than just calling this function. diff --git a/src/plan/mutator_context.rs b/src/plan/mutator_context.rs index ad434f9987..9ed1c877e6 100644 --- a/src/plan/mutator_context.rs +++ b/src/plan/mutator_context.rs @@ -5,6 +5,7 @@ use crate::plan::global::Plan; use crate::plan::AllocationSemantics; use crate::policy::space::Space; use crate::util::alloc::allocators::{AllocatorSelector, Allocators}; +use crate::util::alloc::Allocator; use crate::util::{Address, ObjectReference}; use crate::util::{VMMutatorThread, VMWorkerThread}; use crate::vm::VMBinding; @@ -118,6 +119,20 @@ impl MutatorContext for Mutator { .alloc(size, align, offset) } + fn alloc_slow( + &mut self, + size: usize, + align: usize, + offset: usize, + allocator: AllocationSemantics, + ) -> Address { + unsafe { + self.allocators + .get_allocator_mut(self.config.allocator_mapping[allocator]) + } + .alloc_slow(size, align, offset) + } + // Note that this method is slow, and we expect VM bindings that care about performance to implement allocation fastpath sequence in their bindings. fn post_alloc( &mut self, @@ -169,6 +184,80 @@ impl Mutator { unsafe { self.allocators.get_allocator_mut(selector) }.on_mutator_destroy(); } } + + /// Get the allocator for the selector. + /// + /// # Safety + /// The selector needs to be valid, and points to an allocator that has been initialized. + /// [`crate::memory_manager::get_allocator_mapping`] can be used to get a selector. + pub unsafe fn allocator(&self, selector: AllocatorSelector) -> &dyn Allocator { + self.allocators.get_allocator(selector) + } + + /// Get the mutable allocator for the selector. + /// + /// # Safety + /// The selector needs to be valid, and points to an allocator that has been initialized. + /// [`crate::memory_manager::get_allocator_mapping`] can be used to get a selector. + pub unsafe fn allocator_mut(&mut self, selector: AllocatorSelector) -> &mut dyn Allocator { + self.allocators.get_allocator_mut(selector) + } + + /// Get the allocator of a concrete type for the selector. + /// + /// # Safety + /// The selector needs to be valid, and points to an allocator that has been initialized. + /// [`crate::memory_manager::get_allocator_mapping`] can be used to get a selector. + pub unsafe fn allocator_impl>(&self, selector: AllocatorSelector) -> &T { + self.allocators.get_typed_allocator(selector) + } + + /// Get the mutable allocator of a concrete type for the selector. + /// + /// # Safety + /// The selector needs to be valid, and points to an allocator that has been initialized. + /// [`crate::memory_manager::get_allocator_mapping`] can be used to get a selector. + pub unsafe fn allocator_impl_mut>( + &mut self, + selector: AllocatorSelector, + ) -> &mut T { + self.allocators.get_typed_allocator_mut(selector) + } + + /// Return the base offset from a mutator pointer to the allocator specified by the selector. + pub fn get_allocator_base_offset(selector: AllocatorSelector) -> usize { + use crate::util::alloc::*; + use memoffset::offset_of; + use std::mem::size_of; + offset_of!(Mutator, allocators) + + match selector { + AllocatorSelector::BumpPointer(index) => { + offset_of!(Allocators, bump_pointer) + + size_of::>() * index as usize + } + AllocatorSelector::FreeList(index) => { + offset_of!(Allocators, free_list) + + size_of::>() * index as usize + } + AllocatorSelector::Immix(index) => { + offset_of!(Allocators, immix) + + size_of::>() * index as usize + } + AllocatorSelector::LargeObject(index) => { + offset_of!(Allocators, large_object) + + size_of::>() * index as usize + } + AllocatorSelector::Malloc(index) => { + offset_of!(Allocators, malloc) + + size_of::>() * index as usize + } + AllocatorSelector::MarkCompact(index) => { + offset_of!(Allocators, markcompact) + + size_of::>() * index as usize + } + AllocatorSelector::None => panic!("Expect a valid AllocatorSelector, found None"), + } + } } /// Each GC plan should provide their implementation of a MutatorContext. *Note that this trait is no longer needed as we removed @@ -186,6 +275,13 @@ pub trait MutatorContext: Send + 'static { offset: usize, allocator: AllocationSemantics, ) -> Address; + fn alloc_slow( + &mut self, + size: usize, + align: usize, + offset: usize, + allocator: AllocationSemantics, + ) -> Address; fn post_alloc(&mut self, refer: ObjectReference, bytes: usize, allocator: AllocationSemantics); fn flush_remembered_sets(&mut self) { self.barrier().flush(); diff --git a/src/util/address.rs b/src/util/address.rs index c67578c3ec..3c52a854c2 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -300,6 +300,14 @@ impl Address { &*self.to_mut_ptr() } + /// converts the Address to a mutable Rust reference + /// + /// # Safety + /// The caller must guarantee the address actually points to a Rust object. + pub unsafe fn as_mut_ref<'a, T>(self) -> &'a mut T { + &mut *self.to_mut_ptr() + } + /// converts the Address to a pointer-sized integer pub const fn as_usize(self) -> usize { self.0 diff --git a/src/util/alloc/allocators.rs b/src/util/alloc/allocators.rs index ffcd3f1950..132bb29d2c 100644 --- a/src/util/alloc/allocators.rs +++ b/src/util/alloc/allocators.rs @@ -1,4 +1,3 @@ -use std::mem::size_of; use std::mem::MaybeUninit; use memoffset::offset_of; @@ -60,6 +59,12 @@ impl Allocators { } } + /// # Safety + /// The selector needs to be valid, and points to an allocator that has been initialized. + pub unsafe fn get_typed_allocator>(&self, selector: AllocatorSelector) -> &T { + self.get_allocator(selector).downcast_ref().unwrap() + } + /// # Safety /// The selector needs to be valid, and points to an allocator that has been initialized. pub unsafe fn get_allocator_mut( @@ -83,6 +88,15 @@ impl Allocators { } } + /// # Safety + /// The selector needs to be valid, and points to an allocator that has been initialized. + pub unsafe fn get_typed_allocator_mut>( + &mut self, + selector: AllocatorSelector, + ) -> &mut T { + self.get_allocator_mut(selector).downcast_mut().unwrap() + } + pub fn new( mutator_tls: VMMutatorThread, plan: &'static dyn Plan, @@ -196,11 +210,9 @@ impl AllocatorInfo { /// Arguments: /// * `selector`: The allocator selector to query. pub fn new(selector: AllocatorSelector) -> AllocatorInfo { + let base_offset = Mutator::::get_allocator_base_offset(selector); match selector { - AllocatorSelector::BumpPointer(index) => { - let base_offset = offset_of!(Mutator, allocators) - + offset_of!(Allocators, bump_pointer) - + size_of::>() * index as usize; + AllocatorSelector::BumpPointer(_) => { let bump_pointer_offset = offset_of!(BumpAllocator, bump_pointer); AllocatorInfo::BumpPointer { @@ -208,10 +220,7 @@ impl AllocatorInfo { } } - AllocatorSelector::Immix(index) => { - let base_offset = offset_of!(Mutator, allocators) - + offset_of!(Allocators, immix) - + size_of::>() * index as usize; + AllocatorSelector::Immix(_) => { let bump_pointer_offset = offset_of!(ImmixAllocator, bump_pointer); AllocatorInfo::BumpPointer { @@ -219,10 +228,7 @@ impl AllocatorInfo { } } - AllocatorSelector::MarkCompact(index) => { - let base_offset = offset_of!(Mutator, allocators) - + offset_of!(Allocators, markcompact) - + size_of::>() * index as usize; + AllocatorSelector::MarkCompact(_) => { let bump_offset = base_offset + offset_of!(MarkCompactAllocator, bump_allocator); let bump_pointer_offset = offset_of!(BumpAllocator, bump_pointer); diff --git a/src/util/alloc/bumpallocator.rs b/src/util/alloc/bumpallocator.rs index 50b4b1da4b..bd84e64343 100644 --- a/src/util/alloc/bumpallocator.rs +++ b/src/util/alloc/bumpallocator.rs @@ -17,7 +17,7 @@ pub struct BumpAllocator { /// [`VMThread`] associated with this allocator instance pub tls: VMThread, /// Bump-pointer itself. - pub(in crate::util::alloc) bump_pointer: BumpPointer, + pub bump_pointer: BumpPointer, /// [`Space`](src/policy/space/Space) instance associated with this allocator instance. space: &'static dyn Space, /// [`Plan`] instance that this allocator instance is associated with. @@ -27,6 +27,7 @@ pub struct BumpAllocator { /// A common fast-path bump-pointer allocator shared across different allocator implementations /// that use bump-pointer allocation. #[repr(C)] +#[derive(Copy, Clone)] pub struct BumpPointer { pub cursor: Address, pub limit: Address, @@ -46,6 +47,18 @@ impl BumpPointer { } } +impl std::default::Default for BumpPointer { + fn default() -> Self { + // Defaults to 0,0. In this case, the first + // allocation would naturally fail the check + // `cursor + size < limit`, and go to the slowpath. + BumpPointer { + cursor: Address::ZERO, + limit: Address::ZERO, + } + } +} + impl BumpAllocator { pub fn set_limit(&mut self, start: Address, limit: Address) { self.bump_pointer.reset(start, limit); diff --git a/src/util/alloc/immix_allocator.rs b/src/util/alloc/immix_allocator.rs index dd0cdfee5d..41df345fb2 100644 --- a/src/util/alloc/immix_allocator.rs +++ b/src/util/alloc/immix_allocator.rs @@ -16,7 +16,7 @@ use crate::vm::*; #[repr(C)] pub struct ImmixAllocator { pub tls: VMThread, - pub(in crate::util::alloc) bump_pointer: BumpPointer, + pub bump_pointer: BumpPointer, /// [`Space`](src/policy/space/Space) instance associated with this allocator instance. space: &'static ImmixSpace, /// [`Plan`] instance that this allocator instance is associated with. diff --git a/vmbindings/dummyvm/src/tests/allocator_info.rs b/vmbindings/dummyvm/src/tests/allocator_info.rs new file mode 100644 index 0000000000..4b59434314 --- /dev/null +++ b/vmbindings/dummyvm/src/tests/allocator_info.rs @@ -0,0 +1,58 @@ +// GITHUB-CI: MMTK_PLAN=all + +use mmtk::util::alloc::AllocatorInfo; +use mmtk::util::options::PlanSelector; +use mmtk::AllocationSemantics; + +use crate::test_fixtures::{Fixture, MMTKSingleton}; +use crate::DummyVM; + +lazy_static! { + static ref MMTK_SINGLETON: Fixture = Fixture::new(); +} + +#[test] +fn test_allocator_info() { + MMTK_SINGLETON.with_fixture(|fixture| { + let selector = mmtk::memory_manager::get_allocator_mapping( + &fixture.mmtk, + AllocationSemantics::Default, + ); + let base_offset = mmtk::plan::Mutator::::get_allocator_base_offset(selector); + let allocator_info = AllocatorInfo::new::(selector); + + match *fixture.mmtk.get_options().plan { + PlanSelector::NoGC + | PlanSelector::Immix + | PlanSelector::SemiSpace + | PlanSelector::GenCopy + | PlanSelector::GenImmix + | PlanSelector::MarkCompact + | PlanSelector::StickyImmix => { + // These plans all use bump pointer allocator. + let AllocatorInfo::BumpPointer { + bump_pointer_offset, + } = allocator_info + else { + panic!("Expected AllocatorInfo for a bump pointer allocator"); + }; + // In all of those plans, the first field at base offset is tls, and the second field is the BumpPointer struct. + assert_eq!( + base_offset + mmtk::util::constants::BYTES_IN_ADDRESS, + bump_pointer_offset + ); + } + PlanSelector::MarkSweep => { + if cfg!(feature = "malloc_mark_sweep") { + // We provide no info for a malloc allocator + assert!(matches!(allocator_info, AllocatorInfo::None)) + } else { + // We haven't implemented for a free list allocator + assert!(matches!(allocator_info, AllocatorInfo::Unimplemented)) + } + } + // We provide no info for a large object allocator + PlanSelector::PageProtect => assert!(matches!(allocator_info, AllocatorInfo::None)), + } + }) +} diff --git a/vmbindings/dummyvm/src/tests/doc_avoid_resolving_allocator.rs b/vmbindings/dummyvm/src/tests/doc_avoid_resolving_allocator.rs new file mode 100644 index 0000000000..24dc07a4b1 --- /dev/null +++ b/vmbindings/dummyvm/src/tests/doc_avoid_resolving_allocator.rs @@ -0,0 +1,47 @@ +// GITHUB-CI: MMTK_PLAN=NoGC,SemiSpace,Immix,GenImmix,StickyImmix + +use crate::test_fixtures::{MMTKSingleton, SerialFixture}; +use crate::DummyVM; + +use mmtk::util::alloc::Allocator; +use mmtk::util::alloc::BumpAllocator; +use mmtk::util::Address; +use mmtk::util::OpaquePointer; +use mmtk::util::{VMMutatorThread, VMThread}; +use mmtk::AllocationSemantics; + +lazy_static! { + static ref MMTK_SINGLETON: SerialFixture = SerialFixture::new(); +} + +#[test] +pub fn acquire_typed_allocator() { + MMTK_SINGLETON.with_fixture(|fixture| { + let tls_opaque_pointer = VMMutatorThread(VMThread(OpaquePointer::UNINITIALIZED)); + static mut DEFAULT_ALLOCATOR_OFFSET: usize = 0; + + // ANCHOR: avoid_resolving_allocator + // At boot time + let selector = mmtk::memory_manager::get_allocator_mapping( + &fixture.mmtk, + AllocationSemantics::Default, + ); + unsafe { + DEFAULT_ALLOCATOR_OFFSET = + mmtk::plan::Mutator::::get_allocator_base_offset(selector); + } + let mutator = mmtk::memory_manager::bind_mutator(&fixture.mmtk, tls_opaque_pointer); + + // At run time: allocate with the default semantics without resolving allocator + let default_allocator: &mut BumpAllocator = { + let mutator_addr = Address::from_ref(&*mutator); + unsafe { + (mutator_addr + DEFAULT_ALLOCATOR_OFFSET).as_mut_ref::>() + } + }; + let addr = default_allocator.alloc(8, 8, 0); + // ANCHOR_END: avoid_resolving_allocator + + assert!(!addr.is_zero()); + }); +} diff --git a/vmbindings/dummyvm/src/tests/doc_mutator_storage.rs b/vmbindings/dummyvm/src/tests/doc_mutator_storage.rs new file mode 100644 index 0000000000..b48dcbdea6 --- /dev/null +++ b/vmbindings/dummyvm/src/tests/doc_mutator_storage.rs @@ -0,0 +1,137 @@ +// GITHUB-CI: MMTK_PLAN=NoGC,SemiSpace,Immix,GenImmix,StickyImmix + +use crate::test_fixtures::{MMTKSingleton, SerialFixture}; +use crate::DummyVM; + +use mmtk::util::Address; +use mmtk::util::OpaquePointer; +use mmtk::util::{VMMutatorThread, VMThread}; +use mmtk::AllocationSemantics; +use mmtk::Mutator; + +lazy_static! { + static ref MMTK_SINGLETON: SerialFixture = SerialFixture::new(); +} + +#[test] +pub fn boxed_pointer() { + MMTK_SINGLETON.with_fixture(|fixture| { + let tls_opaque_pointer = VMMutatorThread(VMThread(OpaquePointer::UNINITIALIZED)); + + // ANCHOR: mutator_storage_boxed_pointer + struct MutatorInTLS { + // Store the mutator as a boxed pointer. + // Accessing any value in the mutator will need a dereferencing of the boxed pointer. + ptr: Box>, + } + + // Bind an MMTk mutator + let mutator = mmtk::memory_manager::bind_mutator(&fixture.mmtk, tls_opaque_pointer); + // Store the pointer in TLS + let mut storage = MutatorInTLS { ptr: mutator }; + + // Allocate + let addr = + mmtk::memory_manager::alloc(&mut storage.ptr, 8, 8, 0, AllocationSemantics::Default); + // ANCHOR_END: mutator_storage_boxed_pointer + + assert!(!addr.is_zero()); + }); +} + +#[test] +pub fn embed_mutator_struct() { + MMTK_SINGLETON.with_fixture(|fixture| { + let tls_opaque_pointer = VMMutatorThread(VMThread(OpaquePointer::UNINITIALIZED)); + + // ANCHOR: mutator_storage_embed_mutator_struct + struct MutatorInTLS { + embed: Mutator, + } + + // Bind an MMTk mutator + let mutator = mmtk::memory_manager::bind_mutator(&fixture.mmtk, tls_opaque_pointer); + // Store the struct (or use memcpy for non-Rust code) + let mut storage = MutatorInTLS { embed: *mutator }; + // Allocate + let addr = + mmtk::memory_manager::alloc(&mut storage.embed, 8, 8, 0, AllocationSemantics::Default); + // ANCHOR_END: mutator_storage_embed_mutator_struct + + assert!(!addr.is_zero()); + }) +} + +#[test] +pub fn embed_fastpath_struct() { + MMTK_SINGLETON.with_fixture(|fixture| { + let tls_opaque_pointer = VMMutatorThread(VMThread(OpaquePointer::UNINITIALIZED)); + + // ANCHOR: mutator_storage_embed_fastpath_struct + use mmtk::util::alloc::BumpPointer; + struct MutatorInTLS { + default_bump_pointer: BumpPointer, + mutator: Box>, + } + + // Bind an MMTk mutator + let mutator = mmtk::memory_manager::bind_mutator(&fixture.mmtk, tls_opaque_pointer); + // Create a fastpath BumpPointer + let default_bump_pointer = { + // First find the allocator + let selector = mmtk::memory_manager::get_allocator_mapping( + &fixture.mmtk, + AllocationSemantics::Default, + ); + let default_allocator = unsafe { + mutator.allocator_impl::>(selector) + }; + // Copy the bump pointer struct + default_allocator.bump_pointer + }; + // Store the fastpath BumpPointer along with the mutator + let mut storage = MutatorInTLS { + default_bump_pointer, + mutator, + }; + + // Allocate + let mut allocate_default = |size: usize| -> Address { + // Alignment code is omitted here to make the code simpler to read. + // In an actual implementation, alignment and offset need to be considered by the bindings. + let new_cursor = storage.default_bump_pointer.cursor + size; + if new_cursor < storage.default_bump_pointer.limit { + let addr = storage.default_bump_pointer.cursor; + storage.default_bump_pointer.cursor = new_cursor; + addr + } else { + use crate::mmtk::util::alloc::Allocator; + let selector = mmtk::memory_manager::get_allocator_mapping( + &fixture.mmtk, + AllocationSemantics::Default, + ); + let default_allocator = unsafe { + storage + .mutator + .allocator_impl_mut::>(selector) + }; + // Copy bump pointer values to the allocator in the mutator + default_allocator.bump_pointer = storage.default_bump_pointer; + // Do slow path allocation with MMTk + let addr = default_allocator.alloc_slow(size, 8, 0); + // Copy bump pointer values to the fastpath BumpPointer + storage.default_bump_pointer = default_allocator.bump_pointer; + addr + } + }; + + // Allocate: this will fail in the fastpath, and will get an allocation buffer from the slowpath + let addr1 = allocate_default(8); + // Alloacte: this will allocate from the fastpath + let addr2 = allocate_default(8); + // ANCHOR_END: mutator_storage_embed_fastpath_struct + + assert!(!addr1.is_zero()); + assert!(!addr2.is_zero()); + }) +} diff --git a/vmbindings/dummyvm/src/tests/mod.rs b/vmbindings/dummyvm/src/tests/mod.rs index f6eb288ca0..a0ed565f53 100644 --- a/vmbindings/dummyvm/src/tests/mod.rs +++ b/vmbindings/dummyvm/src/tests/mod.rs @@ -9,6 +9,7 @@ mod allocate_with_disable_collection; mod allocate_with_initialize_collection; mod allocate_with_re_enable_collection; mod allocate_without_initialize_collection; +mod allocator_info; mod barrier_slow_path_assertion; #[cfg(feature = "is_mmtk_object")] mod conservatism; @@ -29,3 +30,7 @@ mod vm_layout_compressed_pointer_64; mod vm_layout_default; mod vm_layout_heap_start; mod vm_layout_log_address_space; + +// The code snippets of these tests are also referred in our docs. +mod doc_avoid_resolving_allocator; +mod doc_mutator_storage;