diff --git a/crates/mun_gc/src/handle.rs b/crates/mun_gc/src/handle.rs index d19517cbd..0c47a6d1c 100644 --- a/crates/mun_gc/src/handle.rs +++ b/crates/mun_gc/src/handle.rs @@ -1,9 +1,4 @@ -use crate::{GCRuntime, Type}; -use std::marker::PhantomData; -use std::ptr::NonNull; -use std::sync::{Arc, Weak}; - -/// A `GCHandle` is what you interact with outside of the allocator. It is a pointer to a piece of +/// A `GCPtr` is what you interact with outside of the allocator. It is a pointer to a piece of /// memory that points to the actual data stored in memory. /// /// This creates an indirection that must be followed to get to the actual data of the object. Note @@ -11,108 +6,54 @@ use std::sync::{Arc, Weak}; /// at the indirection may change. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] -pub struct GCHandle(RawGCHandle); +pub struct GCPtr(RawGCPtr); -/// A `GCHandle` is thread safe. -unsafe impl Send for GCHandle {} -unsafe impl Sync for GCHandle {} +/// A `GCPtr` is thread safe. +unsafe impl Send for GCPtr {} +unsafe impl Sync for GCPtr {} -/// A `RawGCHandle` is an unsafe version of a `GCHandle`. It represents the raw internal pointer +/// A `RawGCPtr` is an unsafe version of a `GCPtr`. It represents the raw internal pointer /// semantics used by the runtime. -pub type RawGCHandle = *const *mut std::ffi::c_void; +pub type RawGCPtr = *const *mut std::ffi::c_void; -pub trait HasGCHandlePtr { +pub trait HasIndirectionPtr { /// Returns a pointer to the referenced memory. /// /// # Safety /// - /// This method is unsafe because casting to a generic type T may be unsafe. We don't know the - /// type of the stored data. - unsafe fn get_ptr(&self) -> NonNull; -} - -impl HasGCHandlePtr for GCHandle { - unsafe fn get_ptr(&self) -> NonNull { - NonNull::new(*self.0) - .expect("indirection pointer is null") - .cast::() - } -} - -impl Into for GCHandle { - fn into(self) -> RawGCHandle { - self.0 - } -} - -impl Into for RawGCHandle { - fn into(self) -> GCHandle { - GCHandle(self) - } -} - -/// A `GCHandle` that automatically roots and unroots its internal `GCHandle`. -pub struct GCRootHandle> { - handle: GCHandle, - runtime: Weak, - ty: PhantomData, -} - -impl> Clone for GCRootHandle { - fn clone(&self) -> Self { - if let Some(runtime) = self.runtime.upgrade() { - unsafe { runtime.root(self.handle) } - } - Self { - handle: self.handle, - runtime: self.runtime.clone(), - ty: Default::default(), - } - } -} + /// This is an unsafe method because derefencing could result in an access violation. + unsafe fn deref(&self) -> *const T; -impl> GCRootHandle { - /// Constructs a new GCRootHandle from a runtime and a handle + /// Returns a mutable pointer to the referenced memory. /// /// # Safety /// - /// This method is unsafe because the passed GCHandle could point to random memory. - pub unsafe fn new(runtime: &Arc, handle: GCHandle) -> Self { - runtime.root(handle); - Self { - handle, - runtime: Arc::downgrade(runtime), - ty: Default::default(), - } - } - - /// Returns the handle of this instance - pub fn handle(&self) -> GCHandle { - self.handle + /// This is an unsafe method because derefencing could result in an access violation. + unsafe fn deref_mut(&self) -> *mut T { + self.deref::() as *mut _ } +} - /// Unroots the handle consuming self and returning the unrooted handle - pub fn unroot(self) -> GCHandle { - self.handle +impl HasIndirectionPtr for GCPtr { + unsafe fn deref(&self) -> *const T { + (*self.0).cast() } } -impl> Into for GCRootHandle { - fn into(self) -> GCHandle { - self.handle +impl Into for GCPtr { + fn into(self) -> RawGCPtr { + self.0 } } -impl> Drop for GCRootHandle { - fn drop(&mut self) { - if let Some(runtime) = self.runtime.upgrade() { - unsafe { runtime.unroot(self.handle) } - } +impl Into for RawGCPtr { + fn into(self) -> GCPtr { + GCPtr(self) } } -impl> HasGCHandlePtr for GCRootHandle { - unsafe fn get_ptr(&self) -> NonNull { - self.handle.get_ptr() +impl GCPtr { + pub(crate) fn as_ptr(self) -> RawGCPtr { + self.0 } } diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs index bfa424e4f..8455f6139 100644 --- a/crates/mun_gc/src/lib.rs +++ b/crates/mun_gc/src/lib.rs @@ -1,12 +1,20 @@ mod handle; mod mark_sweep; +mod root_handle; -pub use handle::{GCHandle, GCRootHandle, HasGCHandlePtr, RawGCHandle}; +pub use handle::{GCPtr, HasIndirectionPtr, RawGCPtr}; pub use mark_sweep::MarkSweep; +pub use root_handle::GCRootHandle; + +/// Contains stats about the current state of a GC implementation +#[derive(Debug, Clone, Default)] +pub struct Stats { + pub allocated_memory: usize, +} /// A trait used by the GC to identify an object. pub trait Type: Send + Sync { - type Trace: Iterator; + type Trace: Iterator; /// Returns the size in bytes of an object of this type. fn size(&self) -> usize; @@ -15,20 +23,20 @@ pub trait Type: Send + Sync { fn alignment(&self) -> usize; /// Returns an iterator to iterate over all GC objects that are referenced by the given object. - fn trace(&self, obj: GCHandle) -> Self::Trace; + fn trace(&self, obj: GCPtr) -> Self::Trace; } /// An object that can be used to allocate and collect memory. pub trait GCRuntime: Send + Sync { - /// Allocates an object of the given type returning a GCHandle - fn alloc_object(&self, ty: T) -> GCHandle; + /// Allocates an object of the given type returning a GCPtr + fn alloc(&self, ty: T) -> GCPtr; /// Returns the type of the specified `obj`. /// /// # Safety /// - /// This method is unsafe because the passed GCHandle could point to random memory. - unsafe fn object_type(&self, obj: GCHandle) -> T; + /// This method is unsafe because the passed GCPtr could point to random memory. + unsafe fn ptr_type(&self, obj: GCPtr) -> T; /// Tell the runtime that the specified object should be considered a root which keeps all other /// objects it references alive. Objects marked as root, must also be unrooted before they can @@ -36,8 +44,8 @@ pub trait GCRuntime: Send + Sync { /// /// # Safety /// - /// This method is unsafe because the passed GCHandle could point to random memory. - unsafe fn root(&self, obj: GCHandle); + /// This method is unsafe because the passed GCPtr could point to random memory. + unsafe fn root(&self, obj: GCPtr); /// Tell the runtime that the specified object should unrooted which keeps all other /// objects it references alive. Objects marked as root, must also be unrooted before they can @@ -46,29 +54,36 @@ pub trait GCRuntime: Send + Sync { /// /// # Safety /// - /// This method is unsafe because the passed GCHandle could point to random memory. - unsafe fn unroot(&self, obj: GCHandle); + /// This method is unsafe because the passed GCPtr could point to random memory. + unsafe fn unroot(&self, obj: GCPtr); + + /// Returns stats about the current state of the runtime. + fn stats(&self) -> Stats; } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { /// The GC performed an allocation - Allocation(GCHandle), + Allocation(GCPtr), /// A GC cycle started Start, /// A deallocation took place - Deallocation(GCHandle), + Deallocation(GCPtr), /// A GC cycle ended End, } -pub trait GCObserver: Send + Sync { +/// A `Observer` is trait that can receive `Event`s from a GC implementation. A `GCRuntime` can +/// be typed by a `GCObserver` which enables optional tracing of events. +pub trait Observer: Send + Sync { fn event(&self, _event: Event) {} } +/// A default implementation of a `Observer` which ensures that the compiler does not generate +/// code for event handling. #[derive(Clone, Default)] pub struct NoopObserver; -impl GCObserver for NoopObserver {} +impl Observer for NoopObserver {} diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs index 8d003f87b..2f700d860 100644 --- a/crates/mun_gc/src/mark_sweep.rs +++ b/crates/mun_gc/src/mark_sweep.rs @@ -1,4 +1,4 @@ -use crate::{Event, GCHandle, GCObserver, GCRuntime, RawGCHandle, Type}; +use crate::{Event, GCPtr, GCRuntime, Observer, RawGCPtr, Stats, Type}; use parking_lot::RwLock; use std::alloc::Layout; use std::collections::{HashMap, VecDeque}; @@ -7,38 +7,41 @@ use std::pin::Pin; /// Implements a simple mark-sweep type memory collector. Uses a HashMap of #[derive(Debug)] -pub struct MarkSweep { - objects: RwLock>>>>, +pub struct MarkSweep { + objects: RwLock>>>>, observer: O, + stats: RwLock, } -impl Default for MarkSweep { +impl Default for MarkSweep { fn default() -> Self { MarkSweep { objects: RwLock::new(HashMap::new()), observer: O::default(), + stats: RwLock::new(Stats::default()), } } } -impl MarkSweep { +impl MarkSweep { pub fn new() -> Self { Default::default() } } -impl MarkSweep { +impl MarkSweep { pub fn with_observer(observer: O) -> Self { Self { objects: RwLock::new(HashMap::new()), observer, + stats: RwLock::new(Stats::default()), } } } -impl MarkSweep { +impl MarkSweep { /// Allocates a block of memory - pub(crate) fn alloc(&self, size: usize, alignment: usize) -> *mut u8 { + fn alloc_memory(&self, size: usize, alignment: usize) -> *mut u8 { unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(size, alignment)) } } @@ -48,9 +51,10 @@ impl MarkSweep { } } -impl GCRuntime for MarkSweep { - fn alloc_object(&self, ty: T) -> GCHandle { - let ptr = self.alloc(ty.size(), ty.alignment()); +impl GCRuntime for MarkSweep { + fn alloc(&self, ty: T) -> GCPtr { + let size = ty.size(); + let ptr = self.alloc_memory(ty.size(), ty.alignment()); let object = Box::pin(ObjectInfo { ptr, ty, @@ -59,45 +63,61 @@ impl GCRuntime for MarkSweep { }); // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = (object.as_ref().deref() as *const _ as RawGCHandle).into(); + let handle = (object.as_ref().deref() as *const _ as RawGCPtr).into(); { let mut objects = self.objects.write(); objects.insert(handle, object); } + { + let mut stats = self.stats.write(); + stats.allocated_memory += size; + } + self.observer.event(Event::Allocation(handle)); handle } - unsafe fn object_type(&self, obj: GCHandle) -> T { - let objects = self.objects.read(); - let src = objects - .get(&obj) - .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", obj)); + unsafe fn ptr_type(&self, handle: GCPtr) -> T { + let _ = self.objects.read(); + + // Convert the handle to our internal representation + let object_info: *const ObjectInfo = handle.into(); + + // Return the type of the object + (*object_info).ty.clone() + } + + unsafe fn root(&self, handle: GCPtr) { + let _ = self.objects.write(); + + // Convert the handle to our internal representation + let object_info: *mut ObjectInfo = handle.into(); - src.ty.clone() + // Return the type of the object + (*object_info).roots += 1; } - unsafe fn root(&self, obj: GCHandle) { - let mut objects = self.objects.write(); - let src = objects - .get_mut(&obj) - .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", obj)); - src.as_mut().get_unchecked_mut().roots += 1; + unsafe fn unroot(&self, handle: GCPtr) { + let _ = self.objects.write(); + + // Convert the handle to our internal representation + let object_info: *mut ObjectInfo = handle.into(); + + // Return the type of the object + (*object_info).roots -= 1; } - unsafe fn unroot(&self, obj: GCHandle) { - let mut objects = self.objects.write(); - let src = objects - .get_mut(&obj) - .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", obj)); - src.as_mut().get_unchecked_mut().roots -= 1; + fn stats(&self) -> Stats { + self.stats.read().clone() } } -impl MarkSweep { - pub fn collect(&self) { +impl MarkSweep { + /// Collects all memory that is no longer referenced by rooted objects. Returns `true` if memory + /// was reclaimed, `false` otherwise. + pub fn collect(&self) -> bool { self.observer.event(Event::Start); let mut writer = self.objects.write(); @@ -116,7 +136,7 @@ impl MarkSweep { // Iterate over all roots while let Some(next) = roots.pop_front() { - let handle = (next as *const _ as RawGCHandle).into(); + let handle = (next as *const _ as RawGCPtr).into(); // Trace all other objects for reference in unsafe { (*next).ty.trace(handle) } { @@ -135,6 +155,7 @@ impl MarkSweep { } // Sweep all non-reachable objects + let size_before = writer.len(); writer.retain(|h, obj| { if obj.color == Color::Black { unsafe { @@ -143,11 +164,18 @@ impl MarkSweep { true } else { self.observer.event(Event::Deallocation(*h)); + { + let mut stats = self.stats.write(); + stats.allocated_memory -= obj.ty.size(); + } false } }); + let size_after = writer.len(); self.observer.event(Event::End); + + size_before != size_after } } @@ -171,3 +199,27 @@ struct ObjectInfo { /// An `ObjectInfo` is thread-safe. unsafe impl Send for ObjectInfo {} unsafe impl Sync for ObjectInfo {} + +impl Into<*const ObjectInfo> for GCPtr { + fn into(self) -> *const ObjectInfo { + self.as_ptr() as *const ObjectInfo + } +} + +impl Into<*mut ObjectInfo> for GCPtr { + fn into(self) -> *mut ObjectInfo { + self.as_ptr() as *mut ObjectInfo + } +} + +impl Into for *const ObjectInfo { + fn into(self) -> GCPtr { + (self as RawGCPtr).into() + } +} + +impl Into for *mut ObjectInfo { + fn into(self) -> GCPtr { + (self as RawGCPtr).into() + } +} diff --git a/crates/mun_gc/src/root_handle.rs b/crates/mun_gc/src/root_handle.rs new file mode 100644 index 000000000..7bbee6bcc --- /dev/null +++ b/crates/mun_gc/src/root_handle.rs @@ -0,0 +1,69 @@ +use crate::{GCPtr, GCRuntime, HasIndirectionPtr, Type}; +use std::marker::PhantomData; +use std::sync::{Arc, Weak}; + +/// A `GCPtr` that automatically roots and unroots its internal `GCPtr`. +pub struct GCRootHandle> { + handle: GCPtr, + runtime: Weak, + ty: PhantomData, +} + +impl> Clone for GCRootHandle { + fn clone(&self) -> Self { + if let Some(runtime) = self.runtime.upgrade() { + unsafe { runtime.root(self.handle) } + } + Self { + handle: self.handle, + runtime: self.runtime.clone(), + ty: Default::default(), + } + } +} + +impl> GCRootHandle { + /// Constructs a new GCRootHandle from a runtime and a handle + /// + /// # Safety + /// + /// This method is unsafe because the passed GCPtr could point to random memory. + pub unsafe fn new(runtime: &Arc, handle: GCPtr) -> Self { + runtime.root(handle); + Self { + handle, + runtime: Arc::downgrade(runtime), + ty: Default::default(), + } + } + + /// Returns the handle of this instance + pub fn handle(&self) -> GCPtr { + self.handle + } + + /// Unroots the handle consuming self and returning the unrooted handle + pub fn unroot(self) -> GCPtr { + self.handle + } +} + +impl> Into for GCRootHandle { + fn into(self) -> GCPtr { + self.handle + } +} + +impl> Drop for GCRootHandle { + fn drop(&mut self) { + if let Some(runtime) = self.runtime.upgrade() { + unsafe { runtime.unroot(self.handle) } + } + } +} + +impl> HasIndirectionPtr for GCRootHandle { + unsafe fn deref(&self) -> *const R { + self.handle.deref() + } +} diff --git a/crates/mun_gc/tests/alloc.rs b/crates/mun_gc/tests/alloc.rs index 3f848e643..c3445712c 100644 --- a/crates/mun_gc/tests/alloc.rs +++ b/crates/mun_gc/tests/alloc.rs @@ -8,10 +8,10 @@ use util::{EventAggregator, HasTypeInfo, TypeInfo}; #[test] fn alloc() { let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); - let handle = runtime.alloc_object(i64::type_info()); + let handle = runtime.alloc(i64::type_info()); assert!(std::ptr::eq( - unsafe { runtime.object_type(handle) }, + unsafe { runtime.ptr_type(handle) }, i64::type_info() )); @@ -23,7 +23,7 @@ fn alloc() { #[test] fn collect_simple() { let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); - let handle = runtime.alloc_object(i64::type_info()); + let handle = runtime.alloc(i64::type_info()); runtime.collect(); @@ -40,8 +40,8 @@ fn collect_rooted() { let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::new()); // Allocate simple object and rooted object - let handle = runtime.alloc_object(i64::type_info()); - let rooted = unsafe { GCRootHandle::new(&runtime, runtime.alloc_object(i64::type_info())) }; + let handle = runtime.alloc(i64::type_info()); + let rooted = unsafe { GCRootHandle::new(&runtime, runtime.alloc(i64::type_info())) }; // Collect unreachable objects, should not collect the root handle runtime.collect(); diff --git a/crates/mun_gc/tests/struct.rs b/crates/mun_gc/tests/struct.rs index ef44b8c67..0d01cb333 100644 --- a/crates/mun_gc/tests/struct.rs +++ b/crates/mun_gc/tests/struct.rs @@ -1,16 +1,16 @@ #[macro_use] mod util; -use mun_gc::{Event, GCHandle, GCRootHandle, GCRuntime, HasGCHandlePtr, MarkSweep, Type}; +use mun_gc::{Event, GCPtr, GCRootHandle, GCRuntime, HasIndirectionPtr, MarkSweep, Type}; use std::sync::Arc; use util::{EventAggregator, HasTypeInfo, Trace, TypeInfo}; struct Foo { - bar: GCHandle, + bar: GCPtr, } impl Trace for Foo { - fn trace(&self, handles: &mut Vec) { + fn trace(&self, handles: &mut Vec) { handles.push(self.bar) } } @@ -20,12 +20,12 @@ impl_struct_ty!(Foo); #[test] fn test_trace() { let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); - let foo_handle = runtime.alloc_object(Foo::type_info()); - let bar_handle = runtime.alloc_object(i64::type_info()); + let foo_handle = runtime.alloc(Foo::type_info()); + let bar_handle = runtime.alloc(i64::type_info()); // Assign bar to foo.bar unsafe { - foo_handle.get_ptr::().as_mut().bar = bar_handle; + (*foo_handle.deref_mut::()).bar = bar_handle; } // Trace foo to see if we get bar back @@ -38,12 +38,12 @@ fn test_trace() { #[test] fn trace_collect() { let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::new()); - let foo = unsafe { GCRootHandle::new(&runtime, runtime.alloc_object(Foo::type_info())) }; - let bar = runtime.alloc_object(i64::type_info()); + let foo = unsafe { GCRootHandle::new(&runtime, runtime.alloc(Foo::type_info())) }; + let bar = runtime.alloc(i64::type_info()); // Assign bar to foo.bar unsafe { - foo.get_ptr::().as_mut().bar = bar; + (*foo.deref_mut::()).bar = bar; } // Collect garbage, bar should not be collected @@ -70,11 +70,11 @@ fn trace_collect() { #[test] fn trace_cycle() { let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::new()); - let foo = unsafe { GCRootHandle::new(&runtime, runtime.alloc_object(Foo::type_info())) }; + let foo = unsafe { GCRootHandle::new(&runtime, runtime.alloc(Foo::type_info())) }; // Assign bar to foo.bar unsafe { - foo.get_ptr::().as_mut().bar = foo.handle(); + (*foo.deref_mut::()).bar = foo.handle(); } // Collect garbage, nothing should be collected since foo is rooted diff --git a/crates/mun_gc/tests/util/mod.rs b/crates/mun_gc/tests/util/mod.rs index 666535465..4fe6d72d4 100644 --- a/crates/mun_gc/tests/util/mod.rs +++ b/crates/mun_gc/tests/util/mod.rs @@ -1,17 +1,17 @@ #![allow(dead_code, unused_macros)] -use mun_gc::{Event, GCHandle}; +use mun_gc::{Event, GCPtr}; use parking_lot::Mutex; pub struct TypeInfo { pub size: usize, pub alignment: usize, - pub tracer: Option<&'static fn(handle: GCHandle) -> Vec>, + pub tracer: Option<&'static fn(handle: GCPtr) -> Vec>, } pub trait Trace { /// Called to collect all GC handles in the type - fn trace(&self, handles: &mut Vec); + fn trace(&self, handles: &mut Vec); } pub trait HasTypeInfo { @@ -45,9 +45,9 @@ macro_rules! impl_struct_ty { ($ty:ident) => { paste::item! { #[allow(non_upper_case_globals, non_snake_case)] - fn [](obj:GCHandle) -> Vec { + fn [](obj:GCPtr) -> Vec { let mut result = Vec::new(); - let foo = unsafe { obj.get_ptr::<$ty>().as_ptr().as_ref().unwrap() }; + let foo = unsafe { &(*obj.deref::<$ty>()) }; foo.trace(&mut result); result } @@ -56,7 +56,7 @@ macro_rules! impl_struct_ty { static []: TypeInfo = TypeInfo { size: std::mem::size_of::<$ty>(), alignment: std::mem::align_of::<$ty>(), - tracer: Some(&([] as fn(handle: GCHandle) -> Vec)) + tracer: Some(&([] as fn(handle: GCPtr) -> Vec)) }; impl HasTypeInfo for $ty { @@ -71,7 +71,7 @@ macro_rules! impl_struct_ty { impl_primitive_types!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool); impl mun_gc::Type for &'static TypeInfo { - type Trace = as IntoIterator>::IntoIter; + type Trace = as IntoIterator>::IntoIter; fn size(&self) -> usize { self.size @@ -81,7 +81,7 @@ impl mun_gc::Type for &'static TypeInfo { self.alignment } - fn trace(&self, obj: GCHandle) -> Self::Trace { + fn trace(&self, obj: GCPtr) -> Self::Trace { let handles = if let Some(tracer) = self.tracer { tracer(obj) } else { @@ -102,7 +102,7 @@ impl EventAggregator { } } -impl mun_gc::GCObserver for EventAggregator { +impl mun_gc::Observer for EventAggregator { fn event(&self, event: Event) { self.events.lock().push(event) } diff --git a/crates/mun_runtime/src/assembly.rs b/crates/mun_runtime/src/assembly.rs index 783d56d37..ee8d81e7d 100644 --- a/crates/mun_runtime/src/assembly.rs +++ b/crates/mun_runtime/src/assembly.rs @@ -1,7 +1,7 @@ use std::io; use std::path::{Path, PathBuf}; -use crate::{Allocator, DispatchTable}; +use crate::DispatchTable; use abi::AssemblyInfo; use failure::Error; use libloading::Symbol; @@ -9,6 +9,7 @@ use libloading::Symbol; mod temp_library; use self::temp_library::TempLibrary; +use crate::garbage_collector::GarbageCollector; use std::sync::Arc; /// An assembly is a hot reloadable compilation unit, consisting of one or more Mun modules. @@ -16,7 +17,7 @@ pub struct Assembly { library_path: PathBuf, library: Option, info: AssemblyInfo, - allocator: Arc, + allocator: Arc, } impl Assembly { @@ -24,7 +25,7 @@ impl Assembly { pub fn load( library_path: &Path, runtime_dispatch_table: &mut DispatchTable, - allocator: Arc, + gc: Arc, ) -> Result { let library = TempLibrary::new(library_path)?; @@ -35,7 +36,7 @@ impl Assembly { let set_allocator_handle: Symbol<'_, extern "C" fn(*mut std::ffi::c_void)> = unsafe { library.library().get(b"set_allocator_handle") }?; - let allocator_ptr = Arc::into_raw(allocator.clone()) as *mut std::ffi::c_void; + let allocator_ptr = Arc::into_raw(gc.clone()) as *mut std::ffi::c_void; set_allocator_handle(allocator_ptr); let info = get_info(); @@ -48,7 +49,7 @@ impl Assembly { library_path: library_path.to_path_buf(), library: Some(library), info, - allocator, + allocator: gc, }) } diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/garbage_collector.rs similarity index 73% rename from crates/mun_runtime/src/allocator.rs rename to crates/mun_runtime/src/garbage_collector.rs index 2bb009e4b..2e1ea1173 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/garbage_collector.rs @@ -1,4 +1,4 @@ -use gc::HasGCHandlePtr; +use gc::HasIndirectionPtr; #[derive(Clone, Debug)] #[repr(transparent)] @@ -14,13 +14,13 @@ unsafe impl Send for RawTypeInfo {} unsafe impl Sync for RawTypeInfo {} pub struct Trace { - obj: GCHandle, + obj: GCPtr, ty: RawTypeInfo, index: usize, } impl Iterator for Trace { - type Item = GCHandle; + type Item = GCPtr; fn next(&mut self) -> Option { let struct_ty = unsafe { self.ty.0.as_ref() }.unwrap().as_struct()?; @@ -34,12 +34,7 @@ impl Iterator for Trace { if field_struct_ty.memory_kind == abi::StructMemoryKind::GC { let offset = struct_ty.field_offsets()[index]; return Some(unsafe { - *self - .obj - .get_ptr::() - .as_ptr() - .add(offset as usize) - .cast::() + *self.obj.deref::().add(offset as usize).cast::() }); } } @@ -59,7 +54,7 @@ impl gc::Type for RawTypeInfo { unsafe { (*self.0).alignment() } } - fn trace(&self, obj: GCHandle) -> Self::Trace { + fn trace(&self, obj: GCPtr) -> Self::Trace { Trace { ty: self.clone(), obj, @@ -69,8 +64,8 @@ impl gc::Type for RawTypeInfo { } /// Defines an allocator used by the `Runtime` -pub type Allocator = gc::MarkSweep; +pub type GarbageCollector = gc::MarkSweep; -pub use gc::GCHandle; +pub use gc::GCPtr; -pub type GCRootHandle = gc::GCRootHandle; +pub type GCRootHandle = gc::GCRootHandle; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index c117192f8..388473c4b 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -8,7 +8,7 @@ mod assembly; mod function; #[macro_use] mod macros; -mod allocator; +mod garbage_collector; mod marshal; mod reflection; mod static_type_map; @@ -35,10 +35,10 @@ use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; pub use crate::marshal::Marshal; pub use crate::reflection::{ArgumentReflection, ReturnTypeReflection}; -pub use crate::allocator::Allocator; pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; +use garbage_collector::GarbageCollector; use gc::GCRuntime; use std::sync::Arc; @@ -136,7 +136,7 @@ pub struct Runtime { dispatch_table: DispatchTable, watcher: RecommendedWatcher, watcher_rx: Receiver, - allocator: Arc, + gc: Arc, _user_functions: Vec, } @@ -146,8 +146,8 @@ pub struct Runtime { /// /// The allocator must have been set using the `set_allocator_handle` call - exposed by the Mun /// library. -unsafe fn get_allocator(alloc_handle: *mut ffi::c_void) -> Arc { - Arc::from_raw(alloc_handle as *const Allocator) +unsafe fn get_allocator(alloc_handle: *mut ffi::c_void) -> Arc { + Arc::from_raw(alloc_handle as *const GarbageCollector) } extern "C" fn new( @@ -155,7 +155,7 @@ extern "C" fn new( alloc_handle: *mut ffi::c_void, ) -> *const *mut ffi::c_void { let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = allocator.alloc_object(type_info.into()); + let handle = allocator.alloc(type_info.into()); // Prevent destruction of the allocator mem::forget(allocator); @@ -184,7 +184,7 @@ impl Runtime { dispatch_table, watcher, watcher_rx: rx, - allocator: Arc::new(Allocator::new()), + gc: Arc::new(self::garbage_collector::GarbageCollector::new()), _user_functions: storages, }; @@ -203,11 +203,8 @@ impl Runtime { .into()); } - let mut assembly = Assembly::load( - &library_path, - &mut self.dispatch_table, - self.allocator.clone(), - )?; + let mut assembly = + Assembly::load(&library_path, &mut self.dispatch_table, self.gc.clone())?; for dependency in assembly.info().dependencies() { self.add_assembly(Path::new(dependency))?; } @@ -225,11 +222,6 @@ impl Runtime { self.dispatch_table.get_fn(function_name) } - /// Returns the runtime's allocator. - pub fn allocator(&self) -> &Arc { - &self.allocator - } - /// Updates the state of the runtime. This includes checking for file changes, and reloading /// compiled assemblies. pub fn update(&mut self) -> bool { @@ -254,6 +246,22 @@ impl Runtime { } false } + + /// Returns the runtime's allocator. + pub(crate) fn gc(&self) -> &Arc { + &self.gc + } + + /// Collects all memory that is no longer referenced by rooted objects. Returns `true` if memory + /// was reclaimed, `false` otherwise. + pub fn collect(&self) -> bool { + self.gc.collect() + } + + /// Returns statistics about the garbage collector. + pub fn gc_stats(&self) -> gc::Stats { + self.gc.stats() + } } /// Extends a result object with functions that allow retrying of an action. diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 4d2bccdf7..52cee0a37 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,4 +1,4 @@ -use crate::allocator::{GCHandle, GCRootHandle}; +use crate::garbage_collector::{GCPtr, GCRootHandle}; use crate::{ marshal::Marshal, reflection::{ @@ -6,8 +6,7 @@ use crate::{ }, Runtime, }; -use gc::GCRuntime; -use gc::HasGCHandlePtr; +use gc::{GCRuntime, HasIndirectionPtr}; use std::cell::RefCell; use std::ptr::{self, NonNull}; use std::rc::Rc; @@ -17,12 +16,12 @@ use std::rc::Rc; /// A byte pointer is used to make pointer arithmetic easier. #[repr(transparent)] #[derive(Clone)] -pub struct RawStruct(GCHandle); +pub struct RawStruct(GCPtr); impl RawStruct { /// Returns a pointer to the struct memory. - pub fn get_ptr(&self) -> NonNull { - unsafe { self.0.get_ptr() } + pub unsafe fn get_ptr(&self) -> *const u8 { + self.0.deref() } } @@ -31,6 +30,7 @@ impl RawStruct { pub struct StructRef { runtime: Rc>, handle: GCRootHandle, + type_info: *const abi::TypeInfo, info: abi::StructInfo, } @@ -43,11 +43,12 @@ impl StructRef { let handle = { let runtime_ref = runtime.borrow(); - unsafe { GCRootHandle::new(runtime_ref.allocator(), raw.0) } + unsafe { GCRootHandle::new(runtime_ref.gc(), raw.0) } }; Self { runtime, handle, + type_info: type_info as *const abi::TypeInfo, info: type_info.as_struct().unwrap().clone(), } } @@ -62,6 +63,11 @@ impl StructRef { &self.info } + /// Returns the type information of the struct + pub fn type_info(&self) -> *const abi::TypeInfo { + self.type_info + } + /// /// /// # Safety @@ -72,8 +78,7 @@ impl StructRef { // self.raw is never null NonNull::new_unchecked( self.handle - .get_ptr::() - .as_ptr() + .deref_mut::() .add(offset as usize) .cast::(), ) @@ -197,21 +202,21 @@ impl Marshal for RawStruct { // Create a new object using the runtime's intrinsic let gc_handle = { let runtime_ref = runtime.borrow(); - runtime_ref.allocator().alloc_object(type_info_ptr) + runtime_ref.gc().alloc(type_info_ptr) }; // Construct let src = ptr.cast::().as_ptr() as *const _; - let dest = unsafe { gc_handle.get_ptr::().as_ptr() }; + let dest = unsafe { gc_handle.deref_mut::() }; let size = type_info.size_in_bytes(); unsafe { ptr::copy_nonoverlapping(src, dest, size as usize) }; gc_handle } else { // If this case the passed in `ptr` is a pointer to a gc struct so `ptr` points to a - // GCHandle. + // GCPtr. - unsafe { *ptr.cast::().as_ptr() } + unsafe { *ptr.cast::().as_ptr() } }; StructRef::new(runtime, type_info, RawStruct(gc_handle)) @@ -225,7 +230,7 @@ impl Marshal for RawStruct { if struct_info.memory_kind == abi::StructMemoryKind::Value { let dest = ptr.cast::().as_ptr(); let size = type_info.size_in_bytes(); - unsafe { ptr::copy_nonoverlapping(value.get_ptr().as_ptr(), dest, size as usize) }; + unsafe { ptr::copy_nonoverlapping(value.get_ptr(), dest, size as usize) }; } else { unsafe { *ptr.as_mut() = value }; } diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index 0dd34cb40..0685e279d 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -734,3 +734,38 @@ fn test_primitive_types() { test_field(&mut foo, (12usize, 111usize), "l"); test_field(&mut foo, (13f64, 112f64), "m"); } + +#[test] +fn gc_trace() { + let mut driver = TestDriver::new( + r#" + pub struct Foo { + quz: float, + bar: Bar, + } + + pub struct Bar { + baz: int + } + + pub fn new_foo(): Foo { + Foo { + quz: 1.0, + bar: Bar { + baz: 3 + } + } + } + "#, + ); + + let value: StructRef = invoke_fn!(driver.runtime_mut(), "new_foo").unwrap(); + + assert_eq!(driver.runtime_mut().borrow().collect(), false); + assert!(driver.runtime_mut().borrow().gc_stats().allocated_memory > 0); + + drop(value); + + assert_eq!(driver.runtime_mut().borrow().collect(), true); + assert_eq!(driver.runtime_mut().borrow().gc_stats().allocated_memory, 0); +}