diff --git a/.github/scripts/ci-common.sh b/.github/scripts/ci-common.sh index bb463914ac..bd0ed8ea63 100644 --- a/.github/scripts/ci-common.sh +++ b/.github/scripts/ci-common.sh @@ -7,6 +7,8 @@ project_root=$(dirname "$0")/../.. cargo_toml=$project_root/Cargo.toml +dummyvm_toml=$project_root/docs/dummyvm/Cargo.toml + # Repeat a command for all the features. Requires the command as one argument (with double quotes) for_all_features() { # without mutually exclusive features diff --git a/.github/scripts/ci-doc.sh b/.github/scripts/ci-doc.sh index 7b2d358181..87e1c73693 100755 --- a/.github/scripts/ci-doc.sh +++ b/.github/scripts/ci-doc.sh @@ -22,6 +22,9 @@ if ! cat $project_root/src/plan/mod.rs | grep -q "pub mod mygc;"; then fi cargo build +# Check dummyvm in portingguide +cargo build --manifest-path $dummyvm_toml + # Install mdbook using the stable toolchain and the default target unset CARGO_BUILD_TARGET cargo +stable install mdbook mdbook-admonish mdbook-hide diff --git a/.github/scripts/ci-style.sh b/.github/scripts/ci-style.sh index ac285ad94b..da0216af81 100755 --- a/.github/scripts/ci-style.sh +++ b/.github/scripts/ci-style.sh @@ -51,3 +51,4 @@ style_check_auxiliary_crate() { } style_check_auxiliary_crate macros +style_check_auxiliary_crate docs/dummyvm diff --git a/.github/scripts/ci-test.sh b/.github/scripts/ci-test.sh index 42bca18c91..c25a22ec7c 100755 --- a/.github/scripts/ci-test.sh +++ b/.github/scripts/ci-test.sh @@ -37,3 +37,6 @@ find ./src ./tests -type f -name "mock_test_*" | while read -r file; do env MMTK_PLAN=$MMTK_PLAN cargo test --features mock_test,"$FEATURES" -- $t; done done + +# Test the dummy VM +cargo test --manifest-path $dummyvm_toml diff --git a/docs/dummyvm/Cargo.toml b/docs/dummyvm/Cargo.toml new file mode 100644 index 0000000000..f720393067 --- /dev/null +++ b/docs/dummyvm/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "mmtk_dummyvm" +version = "0.0.1" +authors = [" <>"] +edition = "2021" + +[lib] +name = "mmtk_dummyvm" +# be careful - LTO is only allowed for certain crate types +# We know that cdylib should work for LTO. +crate-type = ["cdylib"] + +[profile.release] +lto = true + +[dependencies] +# We use a local path as the MMTk dependency here, as we want to test the code with the current version. +# Generally for a binding, you would like to use a specific version, or a git commit. +# mmtk = "0.25.0" +# mmtk = { git = "https://github.com/mmtk/mmtk-core.git", branch = "master" } +mmtk = { path = "../../." } +libc = "0.2" +atomic = "0.6" + +[features] +default = [] +is_mmtk_object = ["mmtk/is_mmtk_object"] +malloc_counted_size = ["mmtk/malloc_counted_size"] diff --git a/docs/dummyvm/README.md b/docs/dummyvm/README.md new file mode 100644 index 0000000000..befa02b16f --- /dev/null +++ b/docs/dummyvm/README.md @@ -0,0 +1,7 @@ +An example of MMTk/Rust-side Binding Implementation +=== + +A binding needs to implement certain Rust traits and may need to expose MMTk's Rust API to native code. +This Rust crate illustrates a minimal example of what needs to be implemented on the binding side in Rust. +When starting a new port of MMTk, developers can use this crate as a starting point by directly copying +it to their port. For more details, see [the porting guide](https://docs.mmtk.io/portingguide/howto/nogc.html#set-up). diff --git a/docs/header/mmtk.h b/docs/dummyvm/include/mmtk.h similarity index 69% rename from docs/header/mmtk.h rename to docs/dummyvm/include/mmtk.h index cf9995cd65..68f5872010 100644 --- a/docs/header/mmtk.h +++ b/docs/dummyvm/include/mmtk.h @@ -17,10 +17,9 @@ extern "C" { typedef void* MMTk_Mutator; typedef void* MMTk_Builder; -typedef void* MMTk; // Initialize an MMTk instance -extern MMTk mmtk_init(MMTk_Builder builder); +extern void mmtk_init(MMTk_Builder builder); // Request MMTk to create a new mutator for the given `tls` thread extern MMTk_Mutator mmtk_bind_mutator(void* tls); @@ -28,12 +27,6 @@ extern MMTk_Mutator mmtk_bind_mutator(void* tls); // Reclaim mutator that is no longer needed extern void mmtk_destroy_mutator(MMTk_Mutator mutator); -// Flush mutator local state -extern void mmtk_flush_mutator(MMTk_Mutator mutator); - -// Initialize MMTk scheduler and GC workers -extern void mmtk_initialize_collection(void* tls); - // Allocate memory for an object extern void* mmtk_alloc(MMTk_Mutator mutator, size_t size, @@ -41,60 +34,45 @@ extern void* mmtk_alloc(MMTk_Mutator mutator, size_t offset, int allocator); -// Slowpath allocation for an object -extern void* mmtk_alloc_slow(MMTk_Mutator mutator, - size_t size, - size_t align, - size_t offset, - int allocator); - // Perform post-allocation hooks or actions such as initializing object metadata extern void mmtk_post_alloc(MMTk_Mutator mutator, void* refer, int bytes, int allocator); -// Return if the object pointed to by `ref` is live -extern bool mmtk_is_live_object(void* ref); - -// Return if the object pointed to by `ref` is in mapped memory -extern bool mmtk_is_mapped_object(void* ref); - -// Return if the address pointed to by `addr` is in mapped memory -extern bool mmtk_is_mapped_address(void* addr); - -// Return if object pointed to by `object` will never move -extern bool mmtk_will_never_move(void* object); - -// Process an MMTk option. Return true if option was processed successfully -extern bool mmtk_process(MMTk_Builder builder, char* name, char* value); - -// Process MMTk options. Return true if all options were processed successfully -extern bool mmtk_process_bulk(MMTk_Builder builder, char* options); - -// Sanity only. Scan heap for discrepancies and errors -extern void mmtk_scan_region(); - -// Request MMTk to trigger a GC. Note that this may not actually trigger a GC -extern void mmtk_handle_user_collection_request(void* tls); - // Run the main loop for a GC worker. Does not return extern void mmtk_start_worker(void* tls, void* worker); -// Return the current amount of free memory in bytes -extern size_t mmtk_free_bytes(); +// Initialize MMTk scheduler and GC workers +extern void mmtk_initialize_collection(void* tls); // Return the current amount of used memory in bytes extern size_t mmtk_used_bytes(); +// Return the current amount of free memory in bytes +extern size_t mmtk_free_bytes(); + // Return the current amount of total memory in bytes extern size_t mmtk_total_bytes(); -// Return the starting address of MMTk's heap -extern void* mmtk_starting_heap_address(); +// Return if the object pointed to by `object` is live +extern bool mmtk_is_live_object(void* object); -// Return the ending address of MMTk's heap -extern void* mmtk_last_heap_address(); +// Return if object pointed to by `object` will never move +extern bool mmtk_will_never_move(void* object); + +// Return if the address is an object in MMTk heap. +// Only available when the feature is_mmtk_object is enabled. +extern bool mmtk_is_mmtk_object(void* addr); + +// Return if the object is in any MMTk space. +extern bool mmtk_is_in_mmtk_spaces(void* object); + +// Return if the address pointed to by `addr` is in memory that is mapped by MMTk +extern bool mmtk_is_mapped_address(void* addr); + +// Request MMTk to trigger a GC. Note that this may not actually trigger a GC +extern void mmtk_handle_user_collection_request(void* tls); // Add a reference to the list of weak references extern void mmtk_add_weak_candidate(void* ref); @@ -111,6 +89,33 @@ extern void mmtk_harness_begin(void* tls); // Generic hook to allow benchmarks to be harnessed extern void mmtk_harness_end(); +// Create an MMTKBuilder +extern MMTk_Builder mmtk_create_builder(); + +// Process an MMTk option. Return true if option was processed successfully +extern bool mmtk_process(MMTk_Builder builder, char* name, char* value); + +// Return the starting address of MMTk's heap +extern void* mmtk_starting_heap_address(); + +// Return the ending address of MMTk's heap +extern void* mmtk_last_heap_address(); + +// Standard malloc functions +extern void* mmtk_malloc(size_t size); +extern void* mmtk_calloc(size_t num, size_t size); +extern void* mmtk_realloc(void* addr, size_t size); +extern void* mmtk_free(void* addr); + +// Counted versions of the malloc functions. The allocation size will be ounted into the MMTk heap. +// Only available when the feature malloc_counted_size is enabled. +extern void* mmtk_counted_malloc(size_t size); +extern void* mmtk_counted_calloc(size_t num, size_t size); +extern void* mmtk_realloc_with_old_size(void* addr, size_t size, size_t old_size); +extern void* mmtk_free_with_size(void* addr, size_t old_size); +// Get the number of active bytes in malloc. +extern size_t mmtk_get_malloc_bytes(); + #ifdef __cplusplus } #endif diff --git a/docs/dummyvm/src/active_plan.rs b/docs/dummyvm/src/active_plan.rs new file mode 100644 index 0000000000..d7748c28ba --- /dev/null +++ b/docs/dummyvm/src/active_plan.rs @@ -0,0 +1,26 @@ +use crate::DummyVM; +use mmtk::util::opaque_pointer::*; +use mmtk::vm::ActivePlan; +use mmtk::Mutator; + +pub struct VMActivePlan {} + +// Documentation: https://docs.mmtk.io/api/mmtk/vm/active_plan/trait.ActivePlan.html +impl ActivePlan for VMActivePlan { + fn number_of_mutators() -> usize { + unimplemented!() + } + + fn is_mutator(_tls: VMThread) -> bool { + // FIXME: Properly check if the thread is a mutator + true + } + + fn mutator(_tls: VMMutatorThread) -> &'static mut Mutator { + unimplemented!() + } + + fn mutators<'a>() -> Box> + 'a> { + unimplemented!() + } +} diff --git a/docs/dummyvm/src/api.rs b/docs/dummyvm/src/api.rs new file mode 100644 index 0000000000..598168d1be --- /dev/null +++ b/docs/dummyvm/src/api.rs @@ -0,0 +1,305 @@ +// All functions here are extern function. There is no point for marking them as unsafe. +#![allow(clippy::not_unsafe_ptr_arg_deref)] + +use crate::mmtk; +use crate::DummyVM; +use crate::SINGLETON; +use libc::c_char; +use mmtk::memory_manager; +use mmtk::scheduler::GCWorker; +use mmtk::util::opaque_pointer::*; +use mmtk::util::{Address, ObjectReference}; +use mmtk::AllocationSemantics; +use mmtk::MMTKBuilder; +use mmtk::Mutator; +use std::ffi::CStr; + +// This file exposes MMTk Rust API to the native code. This is not an exhaustive list of all the APIs. +// Most commonly used APIs are listed in https://docs.mmtk.io/api/mmtk/memory_manager/index.html. The binding can expose them here. + +#[no_mangle] +pub extern "C" fn mmtk_create_builder() -> *mut MMTKBuilder { + Box::into_raw(Box::new(mmtk::MMTKBuilder::new())) +} + +#[no_mangle] +pub extern "C" fn mmtk_set_option_from_string( + builder: *mut MMTKBuilder, + name: *const c_char, + value: *const c_char, +) -> bool { + let builder = unsafe { &mut *builder }; + let name_str: &CStr = unsafe { CStr::from_ptr(name) }; + let value_str: &CStr = unsafe { CStr::from_ptr(value) }; + builder.set_option(name_str.to_str().unwrap(), value_str.to_str().unwrap()) +} + +#[no_mangle] +pub extern "C" fn mmtk_set_fixed_heap_size(builder: *mut MMTKBuilder, heap_size: usize) -> bool { + let builder = unsafe { &mut *builder }; + builder + .options + .gc_trigger + .set(mmtk::util::options::GCTriggerSelector::FixedHeapSize( + heap_size, + )) +} + +#[no_mangle] +pub fn mmtk_init(builder: *mut MMTKBuilder) { + let builder = unsafe { Box::from_raw(builder) }; + + // Create MMTK instance. + let mmtk = memory_manager::mmtk_init::(&builder); + + // Set SINGLETON to the instance. + SINGLETON.set(mmtk).unwrap_or_else(|_| { + panic!("Failed to set SINGLETON"); + }); +} + +#[no_mangle] +pub extern "C" fn mmtk_bind_mutator(tls: VMMutatorThread) -> *mut Mutator { + Box::into_raw(memory_manager::bind_mutator(mmtk(), tls)) +} + +#[no_mangle] +pub extern "C" fn mmtk_destroy_mutator(mutator: *mut Mutator) { + // notify mmtk-core about destroyed mutator + memory_manager::destroy_mutator(unsafe { &mut *mutator }); + // turn the ptr back to a box, and let Rust properly reclaim it + let _ = unsafe { Box::from_raw(mutator) }; +} + +#[no_mangle] +pub extern "C" fn mmtk_alloc( + mutator: *mut Mutator, + size: usize, + align: usize, + offset: usize, + mut semantics: AllocationSemantics, +) -> Address { + // This just demonstrates that the binding should check against `max_non_los_default_alloc_bytes` to allocate large objects. + // In pratice, a binding may want to lift this code to somewhere in the runtime where the allocated bytes is constant so + // they can statically know if a normal allocation or a large object allocation is needed. + if size + >= mmtk() + .get_plan() + .constraints() + .max_non_los_default_alloc_bytes + { + semantics = AllocationSemantics::Los; + } + memory_manager::alloc::(unsafe { &mut *mutator }, size, align, offset, semantics) +} + +#[no_mangle] +pub extern "C" fn mmtk_post_alloc( + mutator: *mut Mutator, + refer: ObjectReference, + bytes: usize, + mut semantics: AllocationSemantics, +) { + // This just demonstrates that the binding should check against `max_non_los_default_alloc_bytes` to allocate large objects. + // In pratice, a binding may want to lift this code to somewhere in the runtime where the allocated bytes is constant so + // they can statically know if a normal allocation or a large object allocation is needed. + if bytes + >= mmtk() + .get_plan() + .constraints() + .max_non_los_default_alloc_bytes + { + semantics = AllocationSemantics::Los; + } + memory_manager::post_alloc::(unsafe { &mut *mutator }, refer, bytes, semantics) +} + +#[no_mangle] +pub extern "C" fn mmtk_start_worker(tls: VMWorkerThread, worker: *mut GCWorker) { + let worker = unsafe { Box::from_raw(worker) }; + memory_manager::start_worker::(mmtk(), tls, worker) +} + +#[no_mangle] +pub extern "C" fn mmtk_initialize_collection(tls: VMThread) { + memory_manager::initialize_collection(mmtk(), tls) +} + +#[no_mangle] +pub extern "C" fn mmtk_used_bytes() -> usize { + memory_manager::used_bytes(mmtk()) +} + +#[no_mangle] +pub extern "C" fn mmtk_free_bytes() -> usize { + memory_manager::free_bytes(mmtk()) +} + +#[no_mangle] +pub extern "C" fn mmtk_total_bytes() -> usize { + memory_manager::total_bytes(mmtk()) +} + +#[no_mangle] +pub extern "C" fn mmtk_is_live_object(object: ObjectReference) -> bool { + memory_manager::is_live_object::(object) +} + +#[no_mangle] +pub extern "C" fn mmtk_will_never_move(object: ObjectReference) -> bool { + !object.is_movable::() +} + +#[cfg(feature = "is_mmtk_object")] +#[no_mangle] +pub extern "C" fn mmtk_is_mmtk_object(addr: Address) -> bool { + memory_manager::is_mmtk_object(addr) +} + +#[no_mangle] +pub extern "C" fn mmtk_is_in_mmtk_spaces(object: ObjectReference) -> bool { + memory_manager::is_in_mmtk_spaces::(object) +} + +#[no_mangle] +pub extern "C" fn mmtk_is_mapped_address(address: Address) -> bool { + memory_manager::is_mapped_address(address) +} + +#[no_mangle] +pub extern "C" fn mmtk_handle_user_collection_request(tls: VMMutatorThread) { + memory_manager::handle_user_collection_request::(mmtk(), tls); +} + +#[no_mangle] +pub extern "C" fn mmtk_add_weak_candidate(reff: ObjectReference) { + memory_manager::add_weak_candidate(mmtk(), reff) +} + +#[no_mangle] +pub extern "C" fn mmtk_add_soft_candidate(reff: ObjectReference) { + memory_manager::add_soft_candidate(mmtk(), reff) +} + +#[no_mangle] +pub extern "C" fn mmtk_add_phantom_candidate(reff: ObjectReference) { + memory_manager::add_phantom_candidate(mmtk(), reff) +} + +#[no_mangle] +pub extern "C" fn mmtk_harness_begin(tls: VMMutatorThread) { + memory_manager::harness_begin(mmtk(), tls) +} + +#[no_mangle] +pub extern "C" fn mmtk_harness_end() { + memory_manager::harness_end(mmtk()) +} + +#[no_mangle] +pub extern "C" fn mmtk_starting_heap_address() -> Address { + memory_manager::starting_heap_address() +} + +#[no_mangle] +pub extern "C" fn mmtk_last_heap_address() -> Address { + memory_manager::last_heap_address() +} + +#[no_mangle] +#[cfg(feature = "malloc_counted_size")] +pub extern "C" fn mmtk_counted_malloc(size: usize) -> Address { + memory_manager::counted_malloc::(mmtk(), size) +} +#[no_mangle] +pub extern "C" fn mmtk_malloc(size: usize) -> Address { + memory_manager::malloc(size) +} + +#[no_mangle] +#[cfg(feature = "malloc_counted_size")] +pub extern "C" fn mmtk_counted_calloc(num: usize, size: usize) -> Address { + memory_manager::counted_calloc::(mmtk(), num, size) +} +#[no_mangle] +pub extern "C" fn mmtk_calloc(num: usize, size: usize) -> Address { + memory_manager::calloc(num, size) +} + +#[no_mangle] +#[cfg(feature = "malloc_counted_size")] +pub extern "C" fn mmtk_realloc_with_old_size( + addr: Address, + size: usize, + old_size: usize, +) -> Address { + memory_manager::realloc_with_old_size::(mmtk(), addr, size, old_size) +} +#[no_mangle] +pub extern "C" fn mmtk_realloc(addr: Address, size: usize) -> Address { + memory_manager::realloc(addr, size) +} + +#[no_mangle] +#[cfg(feature = "malloc_counted_size")] +pub extern "C" fn mmtk_free_with_size(addr: Address, old_size: usize) { + memory_manager::free_with_size::(mmtk(), addr, old_size) +} +#[no_mangle] +pub extern "C" fn mmtk_free(addr: Address) { + memory_manager::free(addr) +} + +#[no_mangle] +#[cfg(feature = "malloc_counted_size")] +pub extern "C" fn mmtk_get_malloc_bytes() -> usize { + memory_manager::get_malloc_bytes(mmtk()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mmtk::vm::ObjectModel; + use std::ffi::CString; + + #[test] + fn mmtk_init_test() { + // We demonstrate the main workflow to initialize MMTk, create mutators and allocate objects. + let builder = mmtk_create_builder(); + + // Set option by value using extern "C" wrapper. + let success = mmtk_set_fixed_heap_size(builder, 1048576); + assert!(success); + + // Set option by value. We set the the option direcly using `MMTKOption::set`. Useful if + // the VM binding wants to set options directly, or if the VM binding has its own format for + // command line arguments. + let name = CString::new("plan").unwrap(); + let val = CString::new("NoGC").unwrap(); + let success = mmtk_set_option_from_string(builder, name.as_ptr(), val.as_ptr()); + assert!(success); + + // Set layout if necessary + // builder.set_vm_layout(layout); + + // Init MMTk + mmtk_init(builder); + + // Create an MMTk mutator + let tls = VMMutatorThread(VMThread(OpaquePointer::UNINITIALIZED)); // FIXME: Use the actual thread pointer or identifier + let mutator = mmtk_bind_mutator(tls); + + // Do an allocation + let addr = mmtk_alloc(mutator, 16, 8, 0, mmtk::AllocationSemantics::Default); + assert!(!addr.is_zero()); + + // Turn the allocation address into the object reference + let obj = crate::object_model::VMObjectModel::address_to_ref(addr); + + // Post allocation + mmtk_post_alloc(mutator, obj, 16, mmtk::AllocationSemantics::Default); + + // If the thread quits, destroy the mutator. + mmtk_destroy_mutator(mutator); + } +} diff --git a/docs/dummyvm/src/collection.rs b/docs/dummyvm/src/collection.rs new file mode 100644 index 0000000000..8f6ef1b33e --- /dev/null +++ b/docs/dummyvm/src/collection.rs @@ -0,0 +1,29 @@ +use crate::DummyVM; +use mmtk::util::opaque_pointer::*; +use mmtk::vm::Collection; +use mmtk::vm::GCThreadContext; +use mmtk::Mutator; + +pub struct VMCollection {} + +// Documentation: https://docs.mmtk.io/api/mmtk/vm/collection/trait.Collection.html +impl Collection for VMCollection { + fn stop_all_mutators(_tls: VMWorkerThread, _mutator_visitor: F) + where + F: FnMut(&'static mut Mutator), + { + unimplemented!() + } + + fn resume_mutators(_tls: VMWorkerThread) { + unimplemented!() + } + + fn block_for_gc(_tls: VMMutatorThread) { + unimplemented!() + } + + fn spawn_gc_thread(_tls: VMThread, _ctx: GCThreadContext) { + unimplemented!() + } +} diff --git a/docs/dummyvm/src/lib.rs b/docs/dummyvm/src/lib.rs new file mode 100644 index 0000000000..19a7436b49 --- /dev/null +++ b/docs/dummyvm/src/lib.rs @@ -0,0 +1,39 @@ +extern crate libc; +extern crate mmtk; + +use std::sync::OnceLock; + +use mmtk::vm::VMBinding; +use mmtk::MMTK; + +pub mod active_plan; +pub mod api; +pub mod collection; +pub mod object_model; +pub mod reference_glue; +pub mod scanning; + +pub type DummyVMSlot = mmtk::vm::slot::SimpleSlot; + +#[derive(Default)] +pub struct DummyVM; + +// Documentation: https://docs.mmtk.io/api/mmtk/vm/trait.VMBinding.html +impl VMBinding for DummyVM { + type VMObjectModel = object_model::VMObjectModel; + type VMScanning = scanning::VMScanning; + type VMCollection = collection::VMCollection; + type VMActivePlan = active_plan::VMActivePlan; + type VMReferenceGlue = reference_glue::VMReferenceGlue; + type VMSlot = DummyVMSlot; + type VMMemorySlice = mmtk::vm::slot::UnimplementedMemorySlice; + + /// Allowed maximum alignment in bytes. + const MAX_ALIGNMENT: usize = 1 << 6; +} + +pub static SINGLETON: OnceLock>> = OnceLock::new(); + +fn mmtk() -> &'static MMTK { + SINGLETON.get().unwrap() +} diff --git a/docs/dummyvm/src/object_model.rs b/docs/dummyvm/src/object_model.rs new file mode 100644 index 0000000000..dcc5d1ad86 --- /dev/null +++ b/docs/dummyvm/src/object_model.rs @@ -0,0 +1,102 @@ +use crate::DummyVM; +use mmtk::util::copy::{CopySemantics, GCWorkerCopyContext}; +use mmtk::util::{Address, ObjectReference}; +use mmtk::vm::*; + +pub struct VMObjectModel {} + +// This is the offset from the allocation result to the object reference for the object. +// The binding can set this to a different value if the ObjectReference in the VM has an offset from the allocation starting address. +// Many methods like `address_to_ref` and `ref_to_address` use this constant. +// For bindings that this offset is not a constant, you can implement the calculation in the methods, and +// remove this constant. +pub const OBJECT_REF_OFFSET: usize = 0; + +// This is the offset from the object reference to the object header. +// This value is used in `ref_to_header` where MMTk loads header metadata from. +pub const OBJECT_HEADER_OFFSET: usize = 0; + +// Documentation: https://docs.mmtk.io/api/mmtk/vm/object_model/trait.ObjectModel.html +impl ObjectModel for VMObjectModel { + // Global metadata + + const GLOBAL_LOG_BIT_SPEC: VMGlobalLogBitSpec = VMGlobalLogBitSpec::side_first(); + + // Local metadata + + // Forwarding pointers have to be in the header. It is okay to overwrite the object payload with a forwarding pointer. + // FIXME: The bit offset needs to be set properly. + const LOCAL_FORWARDING_POINTER_SPEC: VMLocalForwardingPointerSpec = + VMLocalForwardingPointerSpec::in_header(0); + // The other metadata can be put in the side metadata. + const LOCAL_FORWARDING_BITS_SPEC: VMLocalForwardingBitsSpec = + VMLocalForwardingBitsSpec::side_first(); + const LOCAL_MARK_BIT_SPEC: VMLocalMarkBitSpec = + VMLocalMarkBitSpec::side_after(Self::LOCAL_FORWARDING_BITS_SPEC.as_spec()); + const LOCAL_LOS_MARK_NURSERY_SPEC: VMLocalLOSMarkNurserySpec = + VMLocalLOSMarkNurserySpec::side_after(Self::LOCAL_MARK_BIT_SPEC.as_spec()); + + const OBJECT_REF_OFFSET_LOWER_BOUND: isize = OBJECT_REF_OFFSET as isize; + + fn copy( + _from: ObjectReference, + _semantics: CopySemantics, + _copy_context: &mut GCWorkerCopyContext, + ) -> ObjectReference { + unimplemented!() + } + + fn copy_to(_from: ObjectReference, _to: ObjectReference, _region: Address) -> Address { + unimplemented!() + } + + fn get_current_size(_object: ObjectReference) -> usize { + unimplemented!() + } + + fn get_size_when_copied(object: ObjectReference) -> usize { + // FIXME: This assumes the object size is unchanged during copying. + Self::get_current_size(object) + } + + fn get_align_when_copied(_object: ObjectReference) -> usize { + unimplemented!() + } + + fn get_align_offset_when_copied(_object: ObjectReference) -> usize { + unimplemented!() + } + + fn get_reference_when_copied_to(_from: ObjectReference, _to: Address) -> ObjectReference { + unimplemented!() + } + + fn get_type_descriptor(_reference: ObjectReference) -> &'static [i8] { + unimplemented!() + } + + fn ref_to_object_start(object: ObjectReference) -> Address { + object.to_raw_address().sub(OBJECT_REF_OFFSET) + } + + fn ref_to_header(object: ObjectReference) -> Address { + object.to_raw_address().sub(OBJECT_HEADER_OFFSET) + } + + fn ref_to_address(object: ObjectReference) -> Address { + // This method should return an address that is within the allocation. + // Using `ref_to_object_start` is always correct here. + // However, a binding may optimize this method to make it more efficient. + Self::ref_to_object_start(object) + } + + fn address_to_ref(addr: Address) -> ObjectReference { + // This is the reverse operation of `ref_to_address`. + // If the implementation of `ref_to_address` is changed, this implementation needs to be changed accordingly. + unsafe { ObjectReference::from_raw_address_unchecked(addr.add(OBJECT_REF_OFFSET)) } + } + + fn dump_object(_object: ObjectReference) { + unimplemented!() + } +} diff --git a/docs/dummyvm/src/reference_glue.rs b/docs/dummyvm/src/reference_glue.rs new file mode 100644 index 0000000000..afe04ca973 --- /dev/null +++ b/docs/dummyvm/src/reference_glue.rs @@ -0,0 +1,24 @@ +use crate::DummyVM; +use mmtk::util::opaque_pointer::VMWorkerThread; +use mmtk::util::ObjectReference; +use mmtk::vm::ReferenceGlue; + +pub struct VMReferenceGlue {} + +// Documentation: https://docs.mmtk.io/api/mmtk/vm/reference_glue/trait.ReferenceGlue.html +impl ReferenceGlue for VMReferenceGlue { + type FinalizableType = ObjectReference; + + fn set_referent(_reference: ObjectReference, _referent: ObjectReference) { + unimplemented!() + } + fn get_referent(_object: ObjectReference) -> Option { + unimplemented!() + } + fn clear_referent(_object: ObjectReference) { + unimplemented!() + } + fn enqueue_references(_references: &[ObjectReference], _tls: VMWorkerThread) { + unimplemented!() + } +} diff --git a/docs/dummyvm/src/scanning.rs b/docs/dummyvm/src/scanning.rs new file mode 100644 index 0000000000..0465acbabc --- /dev/null +++ b/docs/dummyvm/src/scanning.rs @@ -0,0 +1,40 @@ +use crate::DummyVM; +use crate::DummyVMSlot; +use mmtk::util::opaque_pointer::*; +use mmtk::util::ObjectReference; +use mmtk::vm::RootsWorkFactory; +use mmtk::vm::Scanning; +use mmtk::vm::SlotVisitor; +use mmtk::Mutator; + +pub struct VMScanning {} + +// Documentation: https://docs.mmtk.io/api/mmtk/vm/scanning/trait.Scanning.html +impl Scanning for VMScanning { + fn scan_roots_in_mutator_thread( + _tls: VMWorkerThread, + _mutator: &'static mut Mutator, + _factory: impl RootsWorkFactory, + ) { + unimplemented!() + } + fn scan_vm_specific_roots(_tls: VMWorkerThread, _factory: impl RootsWorkFactory) { + unimplemented!() + } + fn scan_object>( + _tls: VMWorkerThread, + _object: ObjectReference, + _slot_visitor: &mut SV, + ) { + unimplemented!() + } + fn notify_initial_thread_scan_complete(_partial_scan: bool, _tls: VMWorkerThread) { + unimplemented!() + } + fn supports_return_barrier() -> bool { + unimplemented!() + } + fn prepare_for_roots_re_scanning() { + unimplemented!() + } +} diff --git a/docs/userguide/src/portingguide/howto/nogc.md b/docs/userguide/src/portingguide/howto/nogc.md index aea8dfff1a..519a614dbb 100644 --- a/docs/userguide/src/portingguide/howto/nogc.md +++ b/docs/userguide/src/portingguide/howto/nogc.md @@ -15,7 +15,9 @@ You want to set up the binding repository/directory structure before starting th - `mmtk-X/mmtk`: The MMTk side of the binding. This includes the implementation of [the `VMBinding` trait](https://docs.mmtk.io/api/mmtk/vm/trait.VMBinding.html), and any necessary Rust code to integrate MMTk with the VM code (e.g. exposing MMTk functions to native, allowing up-calls from the MMTk binding to the runtime, etc). - To start with, you can take a look at one of our officially maintained language bindings as an example: [OpenJDK](https://github.com/mmtk/mmtk-openjdk/tree/master/mmtk), + To start with, you can copy [the `DummyVM`](https://github.com/mmtk/mmtk-core/tree/master/docs/dummyvm) code and start from there. + `DummyVM` provides all the Rust boilerplates that you need to implement in the binding side. + You can also take a look at one of our officially maintained language bindings as an example: [OpenJDK](https://github.com/mmtk/mmtk-openjdk/tree/master/mmtk), [JikesRVM](https://github.com/mmtk/mmtk-jikesrvm/tree/master/mmtk), [V8](https://github.com/mmtk/mmtk-v8/tree/master/mmtk), [Julia](https://github.com/mmtk/mmtk-julia/tree/master/mmtk), [V8](https://github.com/mmtk/mmtk-v8/tree/master/mmtk). - `mmtk-X/X`: Runtime-specific code for integrating with MMTk. This should act as a bridge between the generic GC interface offered by the runtime and the MMTk side of the binding. This is implemented in the runtime's implementation language. Often this will be one of C or C++. @@ -93,9 +95,11 @@ We recommend going through the [list of metadata specifications](https://docs.mm #### `ObjectReference` vs `Address` -A key principle in MMTk is the distinction between [`ObjectReference`](https://docs.mmtk.io/api/mmtk/util/address/struct.ObjectReference.html) and [`Address`](https://docs.mmtk.io/api/mmtk/util/address/struct.Address.html). The idea is that very few operations are allowed on an `ObjectReference`. For example, MMTk does not allow address arithmetic on `ObjectReference`s. This allows us to preserve memory-safety, only performing unsafe operations when required, and gives us a cleaner and more flexible abstraction to work with as it can allow object handles or offsets etc. `Address`, on the other hand, represents an arbitrary machine address. +A key principle in MMTk is the distinction between [`ObjectReference`](https://docs.mmtk.io/api/mmtk/util/address/struct.ObjectReference.html) and [`Address`](https://docs.mmtk.io/api/mmtk/util/address/struct.Address.html). The idea is that very few operations are allowed on an `ObjectReference`. For example, MMTk does not allow address arithmetic on `ObjectReference`s. This allows us to preserve memory-safety, only performing unsafe operations when required, and gives us a cleaner and more flexible abstraction to work with as it can allow object handles or offsets etc. `Address`, on the other hand, represents an arbitrary machine address. You might be interested in reading the *Demystifying Magic: High-level Low-level Programming* paper[^3] which describes the above in more detail. -You might be interested in reading the *Demystifying Magic: High-level Low-level Programming* paper[^3] which describes the above in more detail. +In MMTk, `ObjectReference` is a special address that represents an object. A binding may use tagged references, compressed pointers, etc. +They need to deal with the encoding and the decoding in their [`Slot`](https://docs.mmtk.io/api/mmtk/vm/slot/trait.Slot.html) implementation, +and always present plain `ObjectReference`s to MMTk. See [this test](https://github.com/mmtk/mmtk-core/blob/master/src/vm/tests/mock_tests/mock_test_slots.rs) for some `Slot` implementation examples. [^3]: https://users.cecs.anu.edu.au/~steveb/pubs/papers/vmmagic-vee-2009.pdf @@ -110,6 +114,30 @@ We recommend going through the [list of constants in the documentation](https:// ## MMTk initialization Now that we have most of the boilerplate set up, the next step is to initialize MMTk so that we can start allocating objects. +In short, MMTk uses the builder pattern. The binding needs to create an [`MMTKBuilder`](https://docs.mmtk.io/api/mmtk/struct.MMTKBuilder.html), +create an [`MMTK`](https://docs.mmtk.io/api/mmtk/mmtk/struct.MMTK.html) instance from the builder, and then initialize MMTk's collection +when the runtime system is ready for GCs. +The following steps describes details. In an actual binding implementation, the binding may choose to combine several +steps into one function call to make things simpler. + +1. Create an `MMTKBuilder` using [`MMTKBuilder::new()`](https://docs.mmtk.io/api/mmtk/struct.MMTKBuilder.html#method.new). You can set + runtime options via [`set_option()`](https://docs.mmtk.io/api/mmtk/struct.MMTKBuilder.html#method.set_option) for things like + the GC plan to use, heap sizes, etc. This is [a full list of runtime options](https://docs.mmtk.io/api/mmtk/util/options/struct.Options.html). + You can also set options by directly accessing the `options` in the builder, such as `builder.options.threads.set(4)`. + It is a common practice that the VM parses its command line arguments, then sets some GC-related options + to MMTk here. You can also set virtual memory layout for MMTk. Some runtimes may require special layouts, such as using compressed pointers + with a fixed heap range. However, both setting options and VM layouts are optional -- MMTk will use the default values if none is set. +2. Create an `MMTK` instance via [`memory_manager::mmtk_init()`](https://docs.mmtk.io/api/mmtk/memory_manager/fn.mmtk_init.html). This + enables the binding to use most of the MMTk APIs in [`memory_manager`](https://docs.mmtk.io/api/mmtk/memory_manager/index.html), as most + APIs require a reference to `MMTK`. +3. When the runtime is ready for GCs (including getting its thread system ready to spawn GC threads), it is expected to call [`memory_manager::initialize_collection`] + (https://docs.mmtk.io/api/mmtk/memory_manager/fn.initialize_collection.html). Once the function returns, MMTk may trigger a GC at any appropriate time. + In terms of getting NoGC to work, this step is optional, as NoGC will not trigger GCs. + +In practice, it greatly depends on the runtime about how to expose the MMTk's Rust API above to native, and when to call the native API in the runtime. +In the following example, we assume a `MMTKBuilder` is created statically (Step 1), and expects a call from the runtime to set heap sizes to the builder +via `mmtk_set_heap_size()`. We will create an `MMTK` instance from the builder in `mmtk_init()` (Step 2). Step 3 is omitted, as we do not need it for NoGC. + ### Runtime-side changes Create a `mmtk.h` header file in the runtime folder of the binding (i.e. `mmtk-X/X`) which exposes the functions required to implement NoGC and `#include` it in the relevant runtime code. You can use the [example `mmtk.h` header file](https://github.com/mmtk/mmtk-core/blob/master/docs/header/mmtk.h) as an example.