From 37319f528f49f91bed58b0ff51803ea10ca895a0 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 19 Mar 2020 10:33:51 +0100 Subject: [PATCH 01/10] misc: abstracted ObjectHandle away into GCHandle --- crates/mun_runtime/src/allocator.rs | 20 +++++++++++++------- crates/mun_runtime/src/struct.rs | 18 +++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index ae3261c87..3ccc00529 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -6,17 +6,23 @@ use std::{pin::Pin, ptr}; #[derive(Debug)] #[repr(C)] -pub struct ObjectInfo { +struct ObjectInfo { pub ptr: *mut u8, pub type_info: *const abi::TypeInfo, } -pub type ObjectHandle = *const ObjectInfo; +/// A GC handle 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 +/// that the indirection pointer must therefor be pinned in memory whereas the pointer stored +/// at the indirection may change. +pub type GCHandle = *const *mut std::ffi::c_void; /// Provides allocator capabilities for a runtime. #[derive(Debug, Default)] pub struct Allocator { - objects: RwLock>>>, + objects: RwLock>>>, } impl Allocator { @@ -36,14 +42,14 @@ impl Allocator { /// /// `type_info` must be a valid pointer and remain valid throughout the lifetime of the created /// object. - pub(crate) unsafe fn create_object(&self, type_info: *const abi::TypeInfo) -> ObjectHandle { + pub(crate) unsafe fn create_object(&self, type_info: *const abi::TypeInfo) -> GCHandle { let type_info = type_info.as_ref().unwrap(); let ptr = self.alloc(type_info.size_in_bytes(), type_info.alignment()); let object = Box::pin(ObjectInfo { ptr, type_info }); // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = object.as_ref().deref() as *const _ as ObjectHandle; + let handle = object.as_ref().deref() as *const _ as GCHandle; let mut objects = self.objects.write(); objects.insert(handle, object); @@ -56,7 +62,7 @@ impl Allocator { /// # Safety /// /// `src` must be a valid pointer. - pub(crate) unsafe fn clone_object(&self, src: ObjectHandle) -> ObjectHandle { + pub(crate) unsafe fn clone_object(&self, src: GCHandle) -> GCHandle { let clone = { let objects = self.objects.read(); let src = objects @@ -76,7 +82,7 @@ impl Allocator { }; // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = clone.as_ref().deref() as *const _ as ObjectHandle; + let handle = clone.as_ref().deref() as *const _ as GCHandle; let mut objects = self.objects.write(); objects.insert(handle, clone); diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 21064812a..05899ad09 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,5 +1,4 @@ use crate::{ - allocator::ObjectHandle, marshal::Marshal, reflection::{ equals_argument_type, equals_return_type, ArgumentReflection, ReturnTypeReflection, @@ -11,17 +10,19 @@ use std::ffi; use std::ptr::{self, NonNull}; use std::rc::Rc; use std::sync::Arc; +use crate::allocator::GCHandle; /// Represents a Mun struct pointer. /// /// A byte pointer is used to make pointer arithmetic easier. #[repr(transparent)] #[derive(Clone)] -pub struct RawStruct(ObjectHandle); +pub struct RawStruct(GCHandle); impl RawStruct { + /// Returns a pointer to the struct memory. pub fn get_ptr(&self) -> *mut u8 { - unsafe { self.0.as_ref().unwrap() }.ptr + unsafe { (*self.0).cast::() } } } @@ -178,24 +179,23 @@ impl Marshal for RawStruct { let alloc_handle = Arc::into_raw(runtime.borrow().get_allocator()) as *mut std::ffi::c_void; let object_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { // Create a new object using the runtime's intrinsic - let object_ptr: *const *mut ffi::c_void = + let gc_handle: GCHandle = invoke_fn!(runtime.clone(), "new", type_info as *const _, alloc_handle).unwrap(); - let handle = object_ptr as ObjectHandle; let src = ptr.cast::().as_ptr() as *const _; - let dest = unsafe { handle.as_ref() }.unwrap().ptr; + let dest = unsafe { (*gc_handle).cast::() }; let size = type_info.size_in_bytes(); unsafe { ptr::copy_nonoverlapping(src, dest, size as usize) }; - handle + gc_handle } else { let ptr = unsafe { ptr.as_ref() }.0 as *const ffi::c_void; // Clone the struct using the runtime's intrinsic - let cloned_ptr: *const *mut ffi::c_void = + let cloned_ptr: GCHandle = invoke_fn!(runtime.clone(), "clone", ptr, alloc_handle).unwrap(); - cloned_ptr as ObjectHandle + cloned_ptr }; StructRef::new(runtime, type_info, RawStruct(object_handle)) From 7fbd8a1827a249f7f7423687a767f919fb39fcbb Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 19 Mar 2020 11:09:36 +0100 Subject: [PATCH 02/10] misc: GCHandle is a new type wrapping all pointer logic --- crates/mun_runtime/src/allocator.rs | 30 ++++++++++++++++++++++++++--- crates/mun_runtime/src/lib.rs | 9 +++++---- crates/mun_runtime/src/struct.rs | 15 ++++++++------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index 3ccc00529..267f35026 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -1,3 +1,4 @@ +use failure::_core::ffi::c_void; use parking_lot::RwLock; use std::alloc::Layout; use std::collections::HashMap; @@ -11,13 +12,36 @@ struct ObjectInfo { pub type_info: *const abi::TypeInfo, } +pub type RawGCHandle = *const *mut std::ffi::c_void; + /// A GC handle 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 /// that the indirection pointer must therefor be pinned in memory whereas the pointer stored /// at the indirection may change. -pub type GCHandle = *const *mut std::ffi::c_void; +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct GCHandle(RawGCHandle); + +impl GCHandle { + /// Returns a pointer to the referenced memory + pub unsafe fn get_ptr(self) -> *mut T { + (*self.0).cast::() + } +} + +impl Into for GCHandle { + fn into(self) -> *const *mut c_void { + self.0 + } +} + +impl Into for RawGCHandle { + fn into(self) -> GCHandle { + GCHandle(self) + } +} /// Provides allocator capabilities for a runtime. #[derive(Debug, Default)] @@ -49,7 +73,7 @@ impl Allocator { let object = Box::pin(ObjectInfo { ptr, type_info }); // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = object.as_ref().deref() as *const _ as GCHandle; + let handle = (object.as_ref().deref() as *const _ as RawGCHandle).into(); let mut objects = self.objects.write(); objects.insert(handle, object); @@ -82,7 +106,7 @@ impl Allocator { }; // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = clone.as_ref().deref() as *const _ as GCHandle; + let handle = (clone.as_ref().deref() as *const _ as RawGCHandle).into(); let mut objects = self.objects.write(); objects.insert(handle, clone); diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index d4e83ef31..18965e40a 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -36,6 +36,7 @@ pub use crate::marshal::Marshal; pub use crate::reflection::{ArgumentReflection, ReturnTypeReflection}; pub use crate::allocator::Allocator; +use crate::allocator::RawGCHandle; pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; @@ -159,12 +160,12 @@ extern "C" fn new( alloc_handle: *mut ffi::c_void, ) -> *const *mut ffi::c_void { let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = unsafe { allocator.create_object(type_info) as *const _ }; + let handle = unsafe { allocator.create_object(type_info) }; // Prevent destruction mem::forget(allocator); - handle + handle.into() } extern "C" fn clone( @@ -172,12 +173,12 @@ extern "C" fn clone( alloc_handle: *mut ffi::c_void, ) -> *const *mut ffi::c_void { let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = unsafe { allocator.clone_object(src as *const _) as *const _ }; + let handle = unsafe { allocator.clone_object((src as RawGCHandle).into()) }; // Prevent destruction mem::forget(allocator); - handle + handle.into() } impl Runtime { diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 05899ad09..5456612a3 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,3 +1,4 @@ +use crate::allocator::{GCHandle, RawGCHandle}; use crate::{ marshal::Marshal, reflection::{ @@ -10,7 +11,6 @@ use std::ffi; use std::ptr::{self, NonNull}; use std::rc::Rc; use std::sync::Arc; -use crate::allocator::GCHandle; /// Represents a Mun struct pointer. /// @@ -22,7 +22,7 @@ pub struct RawStruct(GCHandle); impl RawStruct { /// Returns a pointer to the struct memory. pub fn get_ptr(&self) -> *mut u8 { - unsafe { (*self.0).cast::() } + unsafe { self.0.get_ptr() } } } @@ -179,23 +179,24 @@ impl Marshal for RawStruct { let alloc_handle = Arc::into_raw(runtime.borrow().get_allocator()) as *mut std::ffi::c_void; let object_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { // Create a new object using the runtime's intrinsic - let gc_handle: GCHandle = + let gc_handle: RawGCHandle = invoke_fn!(runtime.clone(), "new", type_info as *const _, alloc_handle).unwrap(); + let gc_handle: GCHandle = gc_handle.into(); let src = ptr.cast::().as_ptr() as *const _; - let dest = unsafe { (*gc_handle).cast::() }; + let dest = unsafe { gc_handle.get_ptr::() }; let size = type_info.size_in_bytes(); unsafe { ptr::copy_nonoverlapping(src, dest, size as usize) }; gc_handle } else { - let ptr = unsafe { ptr.as_ref() }.0 as *const ffi::c_void; + let ptr = unsafe { *ptr.cast::().as_ptr() } as *const ffi::c_void; // Clone the struct using the runtime's intrinsic - let cloned_ptr: GCHandle = + let cloned_ptr: RawGCHandle = invoke_fn!(runtime.clone(), "clone", ptr, alloc_handle).unwrap(); - cloned_ptr + cloned_ptr.into() }; StructRef::new(runtime, type_info, RawStruct(object_handle)) From dcdf13f7db07bb92c5c2991bd081ca09964055b9 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 19 Mar 2020 23:45:31 +0100 Subject: [PATCH 03/10] feat: implemented gc::Type for RawTypeInfo --- crates/mun_gc/Cargo.toml | 12 +++++++++ crates/mun_gc/src/handle.rs | 37 +++++++++++++++++++++++++ crates/mun_gc/src/lib.rs | 28 +++++++++++++++++++ crates/mun_runtime/Cargo.toml | 1 + crates/mun_runtime/src/allocator.rs | 42 +++++++++-------------------- crates/mun_runtime/src/lib.rs | 2 +- crates/mun_runtime/src/struct.rs | 2 +- 7 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 crates/mun_gc/Cargo.toml create mode 100644 crates/mun_gc/src/handle.rs create mode 100644 crates/mun_gc/src/lib.rs diff --git a/crates/mun_gc/Cargo.toml b/crates/mun_gc/Cargo.toml new file mode 100644 index 000000000..0dab565a4 --- /dev/null +++ b/crates/mun_gc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mun_gc" +version = "0.2.0" +authors = ["The Mun Team "] +edition = "2018" +homepage = "https://mun-lang.org" +repository = "https://github.com/mun-lang/mun" +license = "MIT OR Apache-2.0" +description = "Garbage collection crate for Mun" + +[dependencies] +abi = { path = "../mun_abi", package = "mun_abi" } \ No newline at end of file diff --git a/crates/mun_gc/src/handle.rs b/crates/mun_gc/src/handle.rs new file mode 100644 index 000000000..f11213761 --- /dev/null +++ b/crates/mun_gc/src/handle.rs @@ -0,0 +1,37 @@ +/// A `GCHandle` 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 +/// that the indirection pointer must therefor be pinned in memory whereas the pointer stored +/// at the indirection may change. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct GCHandle(RawGCHandle); + +/// A `RawGCHandle` is an unsafe version of a `GCHandle`. It represents the raw internal pointer +/// semantics used by the runtime. +pub type RawGCHandle = *const *mut std::ffi::c_void; + +impl GCHandle { + /// 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. + pub unsafe fn get_ptr(self) -> *mut T { + (*self.0).cast::() + } +} + +impl Into for GCHandle { + fn into(self) -> RawGCHandle { + self.0 + } +} + +impl Into for RawGCHandle { + fn into(self) -> GCHandle { + GCHandle(self) + } +} diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs new file mode 100644 index 000000000..82b89b7fd --- /dev/null +++ b/crates/mun_gc/src/lib.rs @@ -0,0 +1,28 @@ +mod handle; + +pub use handle::{GCHandle, RawGCHandle}; + +/// A trait used by the GC to identify an object. +pub trait Type { + /// Returns the size in bytes of an object of this type. + fn size(&self) -> usize; + + /// Returns the alignment of a type + fn alignment(&self) -> usize; +} + +/// An object that can be used to allocate and collect memory +pub trait GCRuntime { + /// Allocates an object of the given type returning a GCHandle + fn alloc_object(&mut self, ty: T) -> GCHandle; + + /// Creates a shallow copy of `obj` and returns a handle to it. + fn clone_object(&mut self, obj: GCHandle) -> GCHandle; + + /// Returns the type of the specified `obj`. + fn object_type(&self, obj: GCHandle) -> T; + + /// Tell the runtime that the specified object should be considered a root which keeps all other + /// objects it references alive. + fn set_root(&self, obj: GCHandle, is_root: bool); +} diff --git a/crates/mun_runtime/Cargo.toml b/crates/mun_runtime/Cargo.toml index 9ac45ce87..1b3f1cc1b 100644 --- a/crates/mun_runtime/Cargo.toml +++ b/crates/mun_runtime/Cargo.toml @@ -10,6 +10,7 @@ description = "A runtime for hot reloading and invoking Mun from Rust" [dependencies] abi = { path = "../mun_abi", package = "mun_abi" } +gc = { path = "../mun_gc", package = "mun_gc" } failure = "0.1.5" libloading = "0.5" md5= "0.7.0" diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index 267f35026..d0dc17e44 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -1,46 +1,28 @@ -use failure::_core::ffi::c_void; +use gc::{GCHandle, RawGCHandle}; use parking_lot::RwLock; use std::alloc::Layout; use std::collections::HashMap; use std::ops::Deref; use std::{pin::Pin, ptr}; -#[derive(Debug)] -#[repr(C)] -struct ObjectInfo { - pub ptr: *mut u8, - pub type_info: *const abi::TypeInfo, -} - -pub type RawGCHandle = *const *mut std::ffi::c_void; - -/// A GC handle 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 -/// that the indirection pointer must therefor be pinned in memory whereas the pointer stored -/// at the indirection may change. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] -pub struct GCHandle(RawGCHandle); +struct RawTypeInfo(*const abi::TypeInfo); -impl GCHandle { - /// Returns a pointer to the referenced memory - pub unsafe fn get_ptr(self) -> *mut T { - (*self.0).cast::() +impl gc::Type for RawTypeInfo { + fn size(&self) -> usize { + unsafe { (*self.0).size_in_bytes() as usize } } -} -impl Into for GCHandle { - fn into(self) -> *const *mut c_void { - self.0 + fn alignment(&self) -> usize { + unsafe { (*self.0).alignment() as usize } } } -impl Into for RawGCHandle { - fn into(self) -> GCHandle { - GCHandle(self) - } +#[derive(Debug)] +#[repr(C)] +struct ObjectInfo { + pub ptr: *mut u8, + pub type_info: *const abi::TypeInfo, } /// Provides allocator capabilities for a runtime. diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 18965e40a..9027cff01 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -36,10 +36,10 @@ pub use crate::marshal::Marshal; pub use crate::reflection::{ArgumentReflection, ReturnTypeReflection}; pub use crate::allocator::Allocator; -use crate::allocator::RawGCHandle; pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; +use gc::RawGCHandle; use std::sync::Arc; impl_has_type_info_name!( diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 5456612a3..fc38df1ac 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,4 +1,3 @@ -use crate::allocator::{GCHandle, RawGCHandle}; use crate::{ marshal::Marshal, reflection::{ @@ -6,6 +5,7 @@ use crate::{ }, Runtime, }; +use gc::{GCHandle, RawGCHandle}; use std::cell::RefCell; use std::ffi; use std::ptr::{self, NonNull}; From a92c580367dd274174b892c09b7254588594685c Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 20 Mar 2020 16:55:50 +0100 Subject: [PATCH 04/10] feat: allocator is now defined in the mun_gc trait --- crates/mun_gc/Cargo.toml | 3 +- crates/mun_gc/src/handle.rs | 4 ++ crates/mun_gc/src/lib.rs | 28 ++++++-- crates/mun_gc/src/mark_sweep.rs | 98 ++++++++++++++++++++++++++ crates/mun_runtime/src/allocator.rs | 103 ++++------------------------ crates/mun_runtime/src/lib.rs | 7 +- 6 files changed, 144 insertions(+), 99 deletions(-) create mode 100644 crates/mun_gc/src/mark_sweep.rs diff --git a/crates/mun_gc/Cargo.toml b/crates/mun_gc/Cargo.toml index 0dab565a4..375284dc6 100644 --- a/crates/mun_gc/Cargo.toml +++ b/crates/mun_gc/Cargo.toml @@ -9,4 +9,5 @@ license = "MIT OR Apache-2.0" description = "Garbage collection crate for Mun" [dependencies] -abi = { path = "../mun_abi", package = "mun_abi" } \ No newline at end of file +abi = { path = "../mun_abi", package = "mun_abi" } +parking_lot = "0.10" \ No newline at end of file diff --git a/crates/mun_gc/src/handle.rs b/crates/mun_gc/src/handle.rs index f11213761..f43cb9c8a 100644 --- a/crates/mun_gc/src/handle.rs +++ b/crates/mun_gc/src/handle.rs @@ -8,6 +8,10 @@ #[repr(transparent)] pub struct GCHandle(RawGCHandle); +/// A `GCHandle` is thread safe. +unsafe impl Send for GCHandle {} +unsafe impl Sync for GCHandle {} + /// A `RawGCHandle` is an unsafe version of a `GCHandle`. It represents the raw internal pointer /// semantics used by the runtime. pub type RawGCHandle = *const *mut std::ffi::c_void; diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs index 82b89b7fd..4d8bd425d 100644 --- a/crates/mun_gc/src/lib.rs +++ b/crates/mun_gc/src/lib.rs @@ -1,9 +1,11 @@ mod handle; +mod mark_sweep; pub use handle::{GCHandle, RawGCHandle}; +pub use mark_sweep::MarkSweep; /// A trait used by the GC to identify an object. -pub trait Type { +pub trait Type: Send + Sync { /// Returns the size in bytes of an object of this type. fn size(&self) -> usize; @@ -11,18 +13,30 @@ pub trait Type { fn alignment(&self) -> usize; } -/// An object that can be used to allocate and collect memory -pub trait GCRuntime { +/// 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(&mut self, ty: T) -> GCHandle; + fn alloc_object(&self, ty: T) -> GCHandle; /// Creates a shallow copy of `obj` and returns a handle to it. - fn clone_object(&mut self, obj: GCHandle) -> GCHandle; + /// + /// # Safety + /// + /// This method is unsafe because the passed GCHandle could point to random memory. + unsafe fn clone_object(&self, obj: GCHandle) -> GCHandle; /// Returns the type of the specified `obj`. - fn object_type(&self, obj: GCHandle) -> T; + /// + /// # Safety + /// + /// This method is unsafe because the passed GCHandle could point to random memory. + unsafe fn object_type(&self, obj: GCHandle) -> T; /// Tell the runtime that the specified object should be considered a root which keeps all other /// objects it references alive. - fn set_root(&self, obj: GCHandle, is_root: bool); + /// + /// # Safety + /// + /// This method is unsafe because the passed GCHandle could point to random memory. + unsafe fn set_root(&self, obj: GCHandle, is_root: bool); } diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs new file mode 100644 index 000000000..92a8db049 --- /dev/null +++ b/crates/mun_gc/src/mark_sweep.rs @@ -0,0 +1,98 @@ +use crate::{GCHandle, GCRuntime, RawGCHandle, Type}; +use parking_lot::RwLock; +use std::alloc::Layout; +use std::collections::HashMap; +use std::ops::Deref; +use std::pin::Pin; +use std::ptr; + +/// Implements a simple mark-sweep type memory collector. Uses a HashMap of +#[derive(Debug)] +pub struct MarkSweep { + objects: RwLock>>>>, +} + +impl Default for MarkSweep { + fn default() -> Self { + MarkSweep { + objects: RwLock::new(HashMap::new()), + } + } +} + +impl MarkSweep { + pub fn new() -> Self { + Default::default() + } + + /// Allocates a block of memory + pub(crate) fn alloc(&self, size: usize, alignment: usize) -> *mut u8 { + unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(size, alignment)) } + } +} + +impl GCRuntime for MarkSweep { + fn alloc_object(&self, ty: T) -> GCHandle { + let ptr = self.alloc(ty.size(), ty.alignment()); + let object = Box::pin(ObjectInfo { ptr, ty }); + + // 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 mut objects = self.objects.write(); + objects.insert(handle, object); + + handle + } + + unsafe fn clone_object(&self, obj: GCHandle) -> GCHandle { + let clone = { + let objects = self.objects.read(); + let src = objects + .get(&obj) + .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", obj)); + + let size = src.ty.size(); + let dest = self.alloc(src.ty.size(), src.ty.alignment()); + ptr::copy_nonoverlapping(src.ptr, dest, size as usize); + + Box::pin(ObjectInfo { + ptr: dest, + ty: src.ty.clone(), + }) + }; + + // We want to return a pointer to the `ObjectInfo`, to be used as handle. + let handle = (clone.as_ref().deref() as *const _ as RawGCHandle).into(); + + let mut objects = self.objects.write(); + objects.insert(handle, clone); + + 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)); + + src.ty.clone() + } + + unsafe fn set_root(&self, _obj: GCHandle, _is_root: bool) { + // NOOP + } +} + +/// An indirection table that stores the address to the actual memory and the type of the object +#[derive(Debug)] +#[repr(C)] +struct ObjectInfo { + pub ptr: *mut u8, + pub ty: T, +} + +/// An `ObjectInfo` is thread-safe. +unsafe impl Send for ObjectInfo {} +unsafe impl Sync for ObjectInfo {} diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index d0dc17e44..6c2cb564b 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -1,98 +1,25 @@ -use gc::{GCHandle, RawGCHandle}; -use parking_lot::RwLock; -use std::alloc::Layout; -use std::collections::HashMap; -use std::ops::Deref; -use std::{pin::Pin, ptr}; - +#[derive(Clone, Debug)] #[repr(transparent)] -struct RawTypeInfo(*const abi::TypeInfo); +pub struct RawTypeInfo(*const abi::TypeInfo); -impl gc::Type for RawTypeInfo { - fn size(&self) -> usize { - unsafe { (*self.0).size_in_bytes() as usize } +impl Into for *const abi::TypeInfo { + fn into(self) -> RawTypeInfo { + RawTypeInfo(self) } - - fn alignment(&self) -> usize { - unsafe { (*self.0).alignment() as usize } - } -} - -#[derive(Debug)] -#[repr(C)] -struct ObjectInfo { - pub ptr: *mut u8, - pub type_info: *const abi::TypeInfo, -} - -/// Provides allocator capabilities for a runtime. -#[derive(Debug, Default)] -pub struct Allocator { - objects: RwLock>>>, } -impl Allocator { - /// Allocates a new instance of an Allocator - pub fn new() -> Self { - Default::default() - } - - /// Allocates a block of memory - fn alloc(&self, size: usize, alignment: usize) -> *mut u8 { - unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(size, alignment)) } - } - - /// Allocates a managed object of the specified type. - /// - /// # Safety - /// - /// `type_info` must be a valid pointer and remain valid throughout the lifetime of the created - /// object. - pub(crate) unsafe fn create_object(&self, type_info: *const abi::TypeInfo) -> GCHandle { - let type_info = type_info.as_ref().unwrap(); +unsafe impl Send for RawTypeInfo {} +unsafe impl Sync for RawTypeInfo {} - let ptr = self.alloc(type_info.size_in_bytes(), type_info.alignment()); - let object = Box::pin(ObjectInfo { ptr, type_info }); - - // 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 mut objects = self.objects.write(); - objects.insert(handle, object); - - handle +impl gc::Type for RawTypeInfo { + fn size(&self) -> usize { + unsafe { (*self.0).size_in_bytes() } } - /// Creates a shallow clone of the `src` object at a newly allocated memory location. - /// - /// # Safety - /// - /// `src` must be a valid pointer. - pub(crate) unsafe fn clone_object(&self, src: GCHandle) -> GCHandle { - let clone = { - let objects = self.objects.read(); - let src = objects - .get(&src) - .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", src)); - - let type_info = src.type_info.as_ref().unwrap(); - - let size = type_info.size_in_bytes(); - let dest = self.alloc(size, type_info.alignment()); - ptr::copy_nonoverlapping(src.ptr, dest, size as usize); - - Box::pin(ObjectInfo { - ptr: dest, - type_info, - }) - }; - - // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = (clone.as_ref().deref() as *const _ as RawGCHandle).into(); - - let mut objects = self.objects.write(); - objects.insert(handle, clone); - - handle + fn alignment(&self) -> usize { + unsafe { (*self.0).alignment() } } } + +/// Defines an allocator used by the `Runtime` +pub type Allocator = gc::MarkSweep; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 9027cff01..937b2211b 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -39,6 +39,7 @@ pub use crate::allocator::Allocator; pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; +use gc::GCRuntime; use gc::RawGCHandle; use std::sync::Arc; @@ -160,9 +161,9 @@ extern "C" fn new( alloc_handle: *mut ffi::c_void, ) -> *const *mut ffi::c_void { let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = unsafe { allocator.create_object(type_info) }; + let handle = allocator.alloc_object(type_info.into()); - // Prevent destruction + // Prevent destruction of the allocator mem::forget(allocator); handle.into() @@ -175,7 +176,7 @@ extern "C" fn clone( let allocator = unsafe { get_allocator(alloc_handle) }; let handle = unsafe { allocator.clone_object((src as RawGCHandle).into()) }; - // Prevent destruction + // Prevent destruction of the allocator mem::forget(allocator); handle.into() From 650007958fd17443c728f94377707f5f4891cc5e Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sat, 21 Mar 2020 12:19:40 +0100 Subject: [PATCH 05/10] misc: removed the clone intrinsic --- crates/mun_codegen/src/intrinsics.rs | 3 -- crates/mun_codegen/src/ir/intrinsics.rs | 12 +++---- .../snapshots/test__field_crash_file_ir.snap | 8 ++--- .../snapshots/test__field_crash_group_ir.snap | 6 ++-- .../snapshots/test__field_expr_file_ir.snap | 8 ++--- .../snapshots/test__field_expr_group_ir.snap | 8 ++--- .../snapshots/test__gc_struct_file_ir.snap | 8 ++--- .../snapshots/test__gc_struct_group_ir.snap | 6 ++-- .../snapshots/test__struct_test_file_ir.snap | 4 +-- .../snapshots/test__struct_test_group_ir.snap | 6 ++-- crates/mun_runtime/src/struct.rs | 33 ++++++++++--------- crates/mun_runtime/src/test.rs | 6 ++-- 12 files changed, 47 insertions(+), 61 deletions(-) diff --git a/crates/mun_codegen/src/intrinsics.rs b/crates/mun_codegen/src/intrinsics.rs index 44b2e8664..c52fb51d7 100644 --- a/crates/mun_codegen/src/intrinsics.rs +++ b/crates/mun_codegen/src/intrinsics.rs @@ -21,7 +21,4 @@ pub trait Intrinsic: Sync { intrinsics! { /// Allocates memory for the specified `type` in the allocator referred to by `alloc_handle`. pub fn new(type: *const TypeInfo, alloc_handle: *mut ffi::c_void) -> *const *mut ffi::c_void; - /// Allocates memory for and clones the specified type located at `src` into it. Memory is - /// allocated in the allocator referred to by `alloc_handle`. - pub fn clone(src: *const ffi::c_void, alloc_handle: *mut ffi::c_void) -> *const *mut ffi::c_void; } diff --git a/crates/mun_codegen/src/ir/intrinsics.rs b/crates/mun_codegen/src/ir/intrinsics.rs index a391b78f4..fc59b6a3d 100644 --- a/crates/mun_codegen/src/ir/intrinsics.rs +++ b/crates/mun_codegen/src/ir/intrinsics.rs @@ -37,8 +37,7 @@ fn collect_expr( match infer[*callee].as_callable_def() { Some(hir::CallableDef::Struct(_)) => { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic(module, entries, &intrinsics::drop); *needs_alloc = true; } Some(hir::CallableDef::Function(_)) => (), @@ -48,8 +47,7 @@ fn collect_expr( if let Expr::RecordLit { .. } = expr { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic(module, entries, &intrinsics::drop); *needs_alloc = true; } @@ -62,8 +60,7 @@ fn collect_expr( if let hir::Resolution::Def(hir::ModuleDef::Struct(_)) = resolution { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic( module, entries, &intrinsics::drop); *needs_alloc = true; } } @@ -88,7 +85,6 @@ pub fn collect_wrapper_body( needs_alloc: &mut bool, ) { collect_intrinsic(db, entries, &intrinsics::new); - collect_intrinsic(db, entries, &intrinsics::clone); - // self.collect_intrinsic(db, entries, &intrinsics::drop); + // self.collect_intrinsic(entries, &intrinsics::drop, module); *needs_alloc = true; } diff --git a/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap b/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap index 5a43277b4..225fe0f57 100644 --- a/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_crash_file_ir.snap @@ -5,13 +5,13 @@ expression: "struct(gc) Foo { a: int };\n\nfn main(c:int):int {\n let b = Foo ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %Foo = type { i64 } @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [6 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [5 x %struct.MunTypeInfo addrspace(4)*] define i64 @main(i64) { body: @@ -21,8 +21,8 @@ body: %c1 = load i64, i64* %c %add = add i64 %c1, 5 %init = insertvalue %Foo undef, i64 %add, 0 - %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) - %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([6 x %struct.MunTypeInfo addrspace(4)*], [6 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) + %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([5 x %struct.MunTypeInfo addrspace(4)*], [5 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) %type_info_ptr_to_i8_ptr = bitcast %struct.MunTypeInfo addrspace(4)* %Foo_ptr to i8 addrspace(4)* %allocator_handle = load i8*, i8** @allocatorHandle %new = call i8* addrspace(4)* %new_ptr(i8 addrspace(4)* %type_info_ptr_to_i8_ptr, i8* %allocator_handle) diff --git a/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap b/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap index df07cfe1d..6f4018a84 100644 --- a/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_crash_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @@ -22,11 +22,9 @@ source_filename = "group_name" @"type_info::<*const TypeInfo>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"=\A1-\1F\C2\A7\88`d\90\F4\B5\BEE}x", [16 x i8]* @"type_info::<*const TypeInfo>::name", i32 64, i8 8, i8 0 } @"type_info::<*const *mut core::void>::name" = private unnamed_addr constant [23 x i8] c"*const *mut core::void\00" @"type_info::<*const *mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\C5fO\BD\84\DF\06\BFd+\B1\9Abv\CE\00", [23 x i8]* @"type_info::<*const *mut core::void>::name", i32 64, i8 8, i8 0 } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } -@global_type_table = global [6 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] +@global_type_table = global [5 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] @dispatchTable = global %DispatchTable zeroinitializer @allocatorHandle = unnamed_addr global i8* null diff --git a/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap b/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap index 7ea561cb3..65991d1dc 100644 --- a/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_expr_file_ir.snap @@ -5,14 +5,14 @@ expression: "struct(value) Bar(float, Foo);\nstruct(value) Foo { a: int };\n\nfn ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } %Foo = type { i64 } %Bar = type { double, %Foo } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [8 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [7 x %struct.MunTypeInfo addrspace(4)*] define double @bar_0(%Bar) { body: @@ -43,9 +43,9 @@ define i64 @bar_1_foo_a(%Bar) { body: %.fca.0.extract = extractvalue %Bar %0, 0 %.fca.1.0.extract = extractvalue %Bar %0, 1, 0 - %bar_1_ptr = load %Foo (%Bar)*, %Foo (%Bar)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 3) + %bar_1_ptr = load %Foo (%Bar)*, %Foo (%Bar)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 2) %bar_1 = call %Foo %bar_1_ptr(%Bar %0) - %foo_a_ptr = load i64 (%Foo)*, i64 (%Foo)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 2) + %foo_a_ptr = load i64 (%Foo)*, i64 (%Foo)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) %foo_a = call i64 %foo_a_ptr(%Foo %bar_1) ret i64 %foo_a } diff --git a/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap b/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap index 72d20f4bf..640945bc8 100644 --- a/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__field_expr_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i64 (%Foo)*, %Foo (%Bar)* } %Foo = type { i64 } %Bar = type { double, %Foo } @@ -34,12 +34,10 @@ source_filename = "group_name" @"struct_info::::field_types" = private unnamed_addr constant [2 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::"] @"struct_info::::field_offsets" = private unnamed_addr constant [2 x i16] [i16 0, i16 8] @"type_info::" = private unnamed_addr constant { %struct.MunTypeInfo, %struct.MunStructInfo } { %struct.MunTypeInfo { [16 x i8] c"\DD\C3_\88\FAq\B6\EF\14*\E6\1F56FS", [4 x i8]* @"type_info::::name", i32 128, i8 8, i8 1 }, %struct.MunStructInfo { [4 x i8]* @"struct_info::::name", [2 x i8 addrspace(4)*]* @1, [2 x %struct.MunTypeInfo addrspace(4)*]* @"struct_info::::field_types", [2 x i16]* @"struct_info::::field_offsets", i16 2, i8 1 } } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } -@global_type_table = global [8 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] -@dispatchTable = global %DispatchTable { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* null, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* null, i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1 } +@global_type_table = global [7 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] +@dispatchTable = global %DispatchTable { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* null, i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1 } @allocatorHandle = unnamed_addr global i8* null declare i64 @foo_a(%Foo) diff --git a/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap b/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap index d0538c05b..82c422fb1 100644 --- a/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__gc_struct_file_ir.snap @@ -5,20 +5,20 @@ expression: "struct(gc) Foo { a: int, b: int };\n\nfn foo() {\n let a = Foo { ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %Foo = type { i64, i64 } @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [6 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [5 x %struct.MunTypeInfo addrspace(4)*] define void @foo() { body: %b5 = alloca %Foo* addrspace(4)* %a = alloca %Foo* addrspace(4)* - %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) - %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([6 x %struct.MunTypeInfo addrspace(4)*], [6 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) + %new_ptr = load i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %Foo_ptr = load %struct.MunTypeInfo addrspace(4)*, %struct.MunTypeInfo addrspace(4)** getelementptr inbounds ([5 x %struct.MunTypeInfo addrspace(4)*], [5 x %struct.MunTypeInfo addrspace(4)*]* @global_type_table, i32 0, i32 0) %type_info_ptr_to_i8_ptr = bitcast %struct.MunTypeInfo addrspace(4)* %Foo_ptr to i8 addrspace(4)* %allocator_handle = load i8*, i8** @allocatorHandle %new = call i8* addrspace(4)* %new_ptr(i8 addrspace(4)* %type_info_ptr_to_i8_ptr, i8* %allocator_handle) diff --git a/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap b/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap index d809106ab..c657a67fd 100644 --- a/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__gc_struct_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @@ -23,11 +23,9 @@ source_filename = "group_name" @"type_info::<*const TypeInfo>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"=\A1-\1F\C2\A7\88`d\90\F4\B5\BEE}x", [16 x i8]* @"type_info::<*const TypeInfo>::name", i32 64, i8 8, i8 0 } @"type_info::<*const *mut core::void>::name" = private unnamed_addr constant [23 x i8] c"*const *mut core::void\00" @"type_info::<*const *mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\C5fO\BD\84\DF\06\BFd+\B1\9Abv\CE\00", [23 x i8]* @"type_info::<*const *mut core::void>::name", i32 64, i8 8, i8 0 } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } -@global_type_table = global [6 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] +@global_type_table = global [5 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>"] @dispatchTable = global %DispatchTable zeroinitializer @allocatorHandle = unnamed_addr global i8* null diff --git a/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap b/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap index aef4ac5e1..e16e1e146 100644 --- a/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__struct_test_file_ir.snap @@ -5,7 +5,7 @@ expression: "struct(value) Bar(float, int, bool, Foo);\nstruct(value) Foo { a: i ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %Baz = type {} %Bar = type { double, i64, i1, %Foo } @@ -13,7 +13,7 @@ source_filename = "main.mun" @allocatorHandle = external global i8* @dispatchTable = external global %DispatchTable -@global_type_table = external global [10 x %struct.MunTypeInfo addrspace(4)*] +@global_type_table = external global [9 x %struct.MunTypeInfo addrspace(4)*] define void @foo() { body: diff --git a/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap b/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap index e4050515c..5d1a20f77 100644 --- a/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap +++ b/crates/mun_codegen/src/snapshots/test__struct_test_group_ir.snap @@ -7,7 +7,7 @@ source_filename = "group_name" %struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } %struct.MunStructInfo = type { i8 addrspace(4)*, i8 addrspace(4)* addrspace(4)*, %struct.MunTypeInfo addrspace(4)* addrspace(4)*, i16 addrspace(4)*, i16, i8 } -%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)*, i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } +%DispatchTable = type { i8* addrspace(4)* (i8 addrspace(4)*, i8*)* } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Foo\00" @@ -36,14 +36,12 @@ source_filename = "group_name" @"struct_info::::field_types" = private unnamed_addr constant [4 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::"] @"struct_info::::field_offsets" = private unnamed_addr constant [4 x i16] [i16 0, i16 8, i16 16, i16 24] @"type_info::" = private unnamed_addr constant { %struct.MunTypeInfo, %struct.MunStructInfo } { %struct.MunTypeInfo { [16 x i8] c"\DD\C3_\88\FAq\B6\EF\14*\E6\1F56FS", [4 x i8]* @"type_info::::name", i32 256, i8 8, i8 1 }, %struct.MunStructInfo { [4 x i8]* @"struct_info::::name", [4 x i8 addrspace(4)*]* @1, [4 x %struct.MunTypeInfo addrspace(4)*]* @"struct_info::::field_types", [4 x i16]* @"struct_info::::field_offsets", i16 4, i8 1 } } -@"type_info::<*const core::void>::name" = private unnamed_addr constant [18 x i8] c"*const core::void\00" -@"type_info::<*const core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\EF\D3\E0ac~\5C\D4\EF\AE\B1}\CA\BE\DA\16", [18 x i8]* @"type_info::<*const core::void>::name", i32 64, i8 8, i8 0 } @"type_info::<*mut core::void>::name" = private unnamed_addr constant [16 x i8] c"*mut core::void\00" @"type_info::<*mut core::void>" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"\F0Y\22\FC\95\9E\7F\CE\08T\B1\A2\CD\A7\FAz", [16 x i8]* @"type_info::<*mut core::void>::name", i32 64, i8 8, i8 0 } @"type_info::::name" = private unnamed_addr constant [4 x i8] c"Baz\00" @"struct_info::::name" = private unnamed_addr constant [4 x i8] c"Baz\00" @"type_info::" = private unnamed_addr constant { %struct.MunTypeInfo, %struct.MunStructInfo } { %struct.MunTypeInfo { [16 x i8] c"\F8\DC\E6\7F,\948\82\82\ED?\A7\97\96\8A|", [4 x i8]* @"type_info::::name", i32 0, i8 1, i8 1 }, %struct.MunStructInfo { [4 x i8]* @"struct_info::::name", i8 addrspace(4)* addrspace(4)* null, %struct.MunTypeInfo addrspace(4)* addrspace(4)* null, i16 addrspace(4)* null, i16 0, i8 1 } } -@global_type_table = global [10 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::"] +@global_type_table = global [9 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const TypeInfo>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*const *mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::<*mut core::void>", %struct.MunTypeInfo addrspace(4)* @"type_info::"] @dispatchTable = global %DispatchTable zeroinitializer @allocatorHandle = unnamed_addr global i8* null diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index fc38df1ac..63139181d 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -5,12 +5,11 @@ use crate::{ }, Runtime, }; -use gc::{GCHandle, RawGCHandle}; +use gc::GCHandle; +use gc::GCRuntime; use std::cell::RefCell; -use std::ffi; use std::ptr::{self, NonNull}; use std::rc::Rc; -use std::sync::Arc; /// Represents a Mun struct pointer. /// @@ -174,15 +173,20 @@ impl Marshal for RawStruct { ) -> StructRef { // `type_info` is only `None` for the `()` type let type_info = type_info.unwrap(); - let struct_info = type_info.as_struct().unwrap(); - let alloc_handle = Arc::into_raw(runtime.borrow().get_allocator()) as *mut std::ffi::c_void; - let object_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { + + // HACK: This is very hacky since we know nothing about the lifetime of abi::TypeInfo. + let type_info_ptr = (type_info as *const abi::TypeInfo).into(); + + // Copy the contents of the struct based on what kind of pointer we are dealing with + let gc_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { + // If this case the passed in `ptr` is a pointer to a value struct so `ptr` points to a + // struct value. + // Create a new object using the runtime's intrinsic - let gc_handle: RawGCHandle = - invoke_fn!(runtime.clone(), "new", type_info as *const _, alloc_handle).unwrap(); - let gc_handle: GCHandle = gc_handle.into(); + let gc_handle = runtime.borrow().get_allocator().alloc_object(type_info_ptr); + // Construct let src = ptr.cast::().as_ptr() as *const _; let dest = unsafe { gc_handle.get_ptr::() }; let size = type_info.size_in_bytes(); @@ -190,16 +194,13 @@ impl Marshal for RawStruct { gc_handle } else { - let ptr = unsafe { *ptr.cast::().as_ptr() } as *const ffi::c_void; - - // Clone the struct using the runtime's intrinsic - let cloned_ptr: RawGCHandle = - invoke_fn!(runtime.clone(), "clone", ptr, alloc_handle).unwrap(); + // If this case the passed in `ptr` is a pointer to a gc struct so `ptr` points to a + // GCHandle. - cloned_ptr.into() + unsafe { *ptr.cast::().as_ptr() } }; - StructRef::new(runtime, type_info, RawStruct(object_handle)) + StructRef::new(runtime, type_info, RawStruct(gc_handle)) } fn marshal_to_ptr(value: RawStruct, mut ptr: NonNull, type_info: Option<&abi::TypeInfo>) { diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index ed61177c4..0dd34cb40 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -550,9 +550,9 @@ fn marshal_struct() { test_shallow_copy(&mut foo, &foo2, &bool_data, "b"); let mut bar = qux.get::("0").unwrap(); - let bar2 = qux.get::("0").unwrap(); - test_shallow_copy(&mut bar, &bar2, &int_data, "0"); - test_shallow_copy(&mut bar, &bar2, &bool_data, "1"); + // let bar2 = qux.get::("0").unwrap(); + // test_shallow_copy(&mut bar, &bar2, &int_data, "0"); + // test_shallow_copy(&mut bar, &bar2, &bool_data, "1"); // Specify invalid return type let bar_err = bar.get::("0"); From 8ade409adcc368f6a87a3ce683c5a337df2c4df4 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sat, 21 Mar 2020 18:26:13 +0100 Subject: [PATCH 06/10] feat: adds GCRootHandle which roots the contained handle in the GC --- crates/mun_gc/src/handle.rs | 73 ++++++++++++++++++++++++++++- crates/mun_gc/src/lib.rs | 17 +++++-- crates/mun_gc/src/mark_sweep.rs | 6 ++- crates/mun_runtime/src/allocator.rs | 4 ++ crates/mun_runtime/src/lib.rs | 4 +- crates/mun_runtime/src/struct.rs | 22 ++++++--- 6 files changed, 111 insertions(+), 15 deletions(-) diff --git a/crates/mun_gc/src/handle.rs b/crates/mun_gc/src/handle.rs index f43cb9c8a..5f751b4d0 100644 --- a/crates/mun_gc/src/handle.rs +++ b/crates/mun_gc/src/handle.rs @@ -1,3 +1,7 @@ +use crate::{GCRuntime, Type}; +use std::marker::PhantomData; +use std::sync::{Arc, Weak}; + /// A `GCHandle` 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. /// @@ -16,14 +20,18 @@ unsafe impl Sync for GCHandle {} /// semantics used by the runtime. pub type RawGCHandle = *const *mut std::ffi::c_void; -impl GCHandle { +pub trait HasGCHandlePtr { /// 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. - pub unsafe fn get_ptr(self) -> *mut T { + unsafe fn get_ptr(&self) -> *mut T; +} + +impl HasGCHandlePtr for GCHandle { + unsafe fn get_ptr(&self) -> *mut T { (*self.0).cast::() } } @@ -39,3 +47,64 @@ impl Into for RawGCHandle { 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(), + } + } +} + +impl> GCRootHandle { + /// Constructs a new GCRootHandle from a runtime and a handle + /// + /// # 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 + } +} + +impl> Into for GCRootHandle { + fn into(self) -> GCHandle { + self.handle + } +} + +impl> Drop for GCRootHandle { + fn drop(&mut self) { + if let Some(runtime) = self.runtime.upgrade() { + unsafe { runtime.unroot(self.handle) } + } + } +} + +impl> HasGCHandlePtr for GCRootHandle { + unsafe fn get_ptr(&self) -> *mut R { + self.handle.get_ptr() + } +} diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs index 4d8bd425d..a4fc65a43 100644 --- a/crates/mun_gc/src/lib.rs +++ b/crates/mun_gc/src/lib.rs @@ -1,7 +1,7 @@ mod handle; mod mark_sweep; -pub use handle::{GCHandle, RawGCHandle}; +pub use handle::{GCHandle, GCRootHandle, HasGCHandlePtr, RawGCHandle}; pub use mark_sweep::MarkSweep; /// A trait used by the GC to identify an object. @@ -33,10 +33,21 @@ pub trait GCRuntime: Send + Sync { unsafe fn object_type(&self, obj: GCHandle) -> T; /// Tell the runtime that the specified object should be considered a root which keeps all other - /// objects it references alive. + /// objects it references alive. Objects marked as root, must also be unrooted before they can + /// be collected. Internally this increments a root refcount. /// /// # Safety /// /// This method is unsafe because the passed GCHandle could point to random memory. - unsafe fn set_root(&self, obj: GCHandle, is_root: bool); + unsafe fn root(&self, obj: GCHandle); + + /// 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 + /// be collected. Internally this decrements a root refcount. When the refcount reaches 0, the + /// object is considered non-rooted. + /// + /// # Safety + /// + /// This method is unsafe because the passed GCHandle could point to random memory. + unsafe fn unroot(&self, obj: GCHandle); } diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs index 92a8db049..c6fd1d098 100644 --- a/crates/mun_gc/src/mark_sweep.rs +++ b/crates/mun_gc/src/mark_sweep.rs @@ -80,7 +80,11 @@ impl GCRuntime for MarkSweep { src.ty.clone() } - unsafe fn set_root(&self, _obj: GCHandle, _is_root: bool) { + unsafe fn root(&self, _obj: GCHandle) { + // NOOP + } + + unsafe fn unroot(&self, _obj: GCHandle) { // NOOP } } diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index 6c2cb564b..e27a3a352 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -23,3 +23,7 @@ impl gc::Type for RawTypeInfo { /// Defines an allocator used by the `Runtime` pub type Allocator = gc::MarkSweep; + +pub use gc::GCHandle; + +pub type GCRootHandle = gc::GCRootHandle; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 937b2211b..547d900af 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -245,8 +245,8 @@ impl Runtime { } /// Returns the runtime's allocator. - pub fn get_allocator(&self) -> Arc { - self.allocator.clone() + pub fn allocator(&self) -> &Arc { + &self.allocator } /// Updates the state of the runtime. This includes checking for file changes, and reloading diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 63139181d..cc7558dac 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,3 +1,4 @@ +use crate::allocator::{GCHandle, GCRootHandle}; use crate::{ marshal::Marshal, reflection::{ @@ -5,8 +6,8 @@ use crate::{ }, Runtime, }; -use gc::GCHandle; use gc::GCRuntime; +use gc::HasGCHandlePtr; use std::cell::RefCell; use std::ptr::{self, NonNull}; use std::rc::Rc; @@ -29,7 +30,7 @@ impl RawStruct { /// TODO: Handle destruction of `struct(value)` pub struct StructRef { runtime: Rc>, - raw: RawStruct, + handle: GCRootHandle, info: abi::StructInfo, } @@ -40,16 +41,20 @@ impl StructRef { fn new(runtime: Rc>, type_info: &abi::TypeInfo, raw: RawStruct) -> StructRef { assert!(type_info.group.is_struct()); + let handle = { + let runtime_ref = runtime.borrow(); + unsafe { GCRootHandle::new(runtime_ref.allocator(), raw.0) } + }; Self { runtime, - raw, + handle, info: type_info.as_struct().unwrap().clone(), } } /// Consumes the `Struct`, returning a raw Mun struct. pub fn into_raw(self) -> RawStruct { - self.raw + RawStruct(self.handle.handle()) } /// Retrieves its struct information. @@ -65,7 +70,7 @@ impl StructRef { unsafe fn offset_unchecked(&self, field_idx: usize) -> NonNull { let offset = *self.info.field_offsets().get_unchecked(field_idx); // self.raw is never null - NonNull::new_unchecked(self.raw.get_ptr().add(offset as usize).cast::()) + NonNull::new_unchecked(self.handle.get_ptr::().add(offset as usize).cast::()) } /// Retrieves the value of the field corresponding to the specified `field_name`. @@ -144,7 +149,7 @@ impl ArgumentReflection for StructRef { } fn marshal(self) -> Self::Marshalled { - self.raw + self.into_raw() } } @@ -184,7 +189,10 @@ impl Marshal for RawStruct { // struct value. // Create a new object using the runtime's intrinsic - let gc_handle = runtime.borrow().get_allocator().alloc_object(type_info_ptr); + let gc_handle = { + let runtime_ref = runtime.borrow(); + runtime_ref.allocator().alloc_object(type_info_ptr) + }; // Construct let src = ptr.cast::().as_ptr() as *const _; From 142b59d36e1217334396756da843d0698c4ffdc3 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sun, 22 Mar 2020 15:01:44 +0100 Subject: [PATCH 07/10] misc: initial testing setup for gc --- crates/mun_gc/Cargo.toml | 5 +- crates/mun_gc/src/handle.rs | 11 ++- crates/mun_gc/src/lib.rs | 30 +++++-- crates/mun_gc/src/mark_sweep.rs | 125 +++++++++++++++++++--------- crates/mun_gc/tests/alloc.rs | 71 ++++++++++++++++ crates/mun_gc/tests/util/mod.rs | 62 ++++++++++++++ crates/mun_runtime/src/allocator.rs | 2 +- crates/mun_runtime/src/lib.rs | 19 ----- crates/mun_runtime/src/struct.rs | 14 +++- 9 files changed, 264 insertions(+), 75 deletions(-) create mode 100644 crates/mun_gc/tests/alloc.rs create mode 100644 crates/mun_gc/tests/util/mod.rs diff --git a/crates/mun_gc/Cargo.toml b/crates/mun_gc/Cargo.toml index 375284dc6..4dd456b3c 100644 --- a/crates/mun_gc/Cargo.toml +++ b/crates/mun_gc/Cargo.toml @@ -10,4 +10,7 @@ description = "Garbage collection crate for Mun" [dependencies] abi = { path = "../mun_abi", package = "mun_abi" } -parking_lot = "0.10" \ No newline at end of file +parking_lot = "0.10" + +[dev-dependencies] +paste = "0.1" \ No newline at end of file diff --git a/crates/mun_gc/src/handle.rs b/crates/mun_gc/src/handle.rs index 5f751b4d0..a4e9b9265 100644 --- a/crates/mun_gc/src/handle.rs +++ b/crates/mun_gc/src/handle.rs @@ -1,5 +1,6 @@ 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 @@ -27,12 +28,14 @@ pub trait HasGCHandlePtr { /// /// 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) -> *mut T; + unsafe fn get_ptr(&self) -> NonNull; } impl HasGCHandlePtr for GCHandle { - unsafe fn get_ptr(&self) -> *mut T { - (*self.0).cast::() + unsafe fn get_ptr(&self) -> NonNull { + NonNull::new(*self.0) + .expect("indirection pointer is null") + .cast::() } } @@ -104,7 +107,7 @@ impl> Drop for GCRootHandle { } impl> HasGCHandlePtr for GCRootHandle { - unsafe fn get_ptr(&self) -> *mut R { + unsafe fn get_ptr(&self) -> NonNull { self.handle.get_ptr() } } diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs index a4fc65a43..fcdb12e9b 100644 --- a/crates/mun_gc/src/lib.rs +++ b/crates/mun_gc/src/lib.rs @@ -18,13 +18,6 @@ pub trait GCRuntime: Send + Sync { /// Allocates an object of the given type returning a GCHandle fn alloc_object(&self, ty: T) -> GCHandle; - /// Creates a shallow copy of `obj` and returns a handle to it. - /// - /// # Safety - /// - /// This method is unsafe because the passed GCHandle could point to random memory. - unsafe fn clone_object(&self, obj: GCHandle) -> GCHandle; - /// Returns the type of the specified `obj`. /// /// # Safety @@ -51,3 +44,26 @@ pub trait GCRuntime: Send + Sync { /// This method is unsafe because the passed GCHandle could point to random memory. unsafe fn unroot(&self, obj: GCHandle); } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + /// The GC performed an allocation + Allocation(GCHandle), + + /// A GC cycle started + Start, + + /// A deallocation took place + Deallocation(GCHandle), + + /// A GC cycle ended + End, +} + +pub trait GCObserver: Send + Sync { + fn event(&self, _event: Event) {} +} + +#[derive(Clone, Default)] +pub struct NoopObserver; +impl GCObserver for NoopObserver {} diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs index c6fd1d098..5f8ffa092 100644 --- a/crates/mun_gc/src/mark_sweep.rs +++ b/crates/mun_gc/src/mark_sweep.rs @@ -1,73 +1,72 @@ -use crate::{GCHandle, GCRuntime, RawGCHandle, Type}; +use crate::{Event, GCHandle, GCObserver, GCRuntime, RawGCHandle, Type}; use parking_lot::RwLock; use std::alloc::Layout; use std::collections::HashMap; use std::ops::Deref; use std::pin::Pin; -use std::ptr; /// Implements a simple mark-sweep type memory collector. Uses a HashMap of #[derive(Debug)] -pub struct MarkSweep { +pub struct MarkSweep { objects: RwLock>>>>, + observer: O, } -impl Default for MarkSweep { +impl Default for MarkSweep { fn default() -> Self { MarkSweep { objects: RwLock::new(HashMap::new()), + observer: O::default(), } } } -impl MarkSweep { +impl MarkSweep { pub fn new() -> Self { Default::default() } +} +impl MarkSweep { + pub fn with_observer(observer: O) -> Self { + Self { + objects: RwLock::new(HashMap::new()), + observer, + } + } +} + +impl MarkSweep { /// Allocates a block of memory pub(crate) fn alloc(&self, size: usize, alignment: usize) -> *mut u8 { unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(size, alignment)) } } + + /// Returns the observer + pub fn observer(&self) -> &O { + &self.observer + } } -impl GCRuntime for MarkSweep { +impl GCRuntime for MarkSweep { fn alloc_object(&self, ty: T) -> GCHandle { let ptr = self.alloc(ty.size(), ty.alignment()); - let object = Box::pin(ObjectInfo { ptr, ty }); + let object = Box::pin(ObjectInfo { + ptr, + ty, + roots: 0, + color: Color::White, + }); // 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 mut objects = self.objects.write(); - objects.insert(handle, object); - - handle - } - - unsafe fn clone_object(&self, obj: GCHandle) -> GCHandle { - let clone = { - let objects = self.objects.read(); - let src = objects - .get(&obj) - .unwrap_or_else(|| panic!("Object with handle '{:?}' does not exist.", obj)); - - let size = src.ty.size(); - let dest = self.alloc(src.ty.size(), src.ty.alignment()); - ptr::copy_nonoverlapping(src.ptr, dest, size as usize); - - Box::pin(ObjectInfo { - ptr: dest, - ty: src.ty.clone(), - }) - }; - - // We want to return a pointer to the `ObjectInfo`, to be used as handle. - let handle = (clone.as_ref().deref() as *const _ as RawGCHandle).into(); - - let mut objects = self.objects.write(); - objects.insert(handle, clone); + { + let mut objects = self.objects.write(); + objects.insert(handle, object); + } + self.observer.event(Event::Allocation(handle)); handle } @@ -80,20 +79,68 @@ impl GCRuntime for MarkSweep { src.ty.clone() } - unsafe fn root(&self, _obj: GCHandle) { - // NOOP + 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, _obj: GCHandle) { - // NOOP + 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; } } +impl MarkSweep { + pub fn collect(&self) { + self.observer.event(Event::Start); + + let mut writer = self.objects.write(); + + // Mark all reachable objects + for (_, obj) in writer.iter_mut() { + if obj.roots > 0 { + unsafe { + obj.as_mut().get_unchecked_mut().color = Color::Black; + } + } + } + + // Sweep all non-reachable objects + writer.retain(|h, obj| { + if obj.color == Color::Black { + unsafe { + obj.as_mut().get_unchecked_mut().color = Color::White; + } + true + } else { + self.observer.event(Event::Deallocation(*h)); + false + } + }); + + self.observer.event(Event::End); + } +} + +#[derive(Debug, PartialEq, Eq)] +enum Color { + White, + Black, +} + /// An indirection table that stores the address to the actual memory and the type of the object #[derive(Debug)] #[repr(C)] struct ObjectInfo { pub ptr: *mut u8, + pub roots: u32, + pub color: Color, pub ty: T, } diff --git a/crates/mun_gc/tests/alloc.rs b/crates/mun_gc/tests/alloc.rs new file mode 100644 index 000000000..7782306e8 --- /dev/null +++ b/crates/mun_gc/tests/alloc.rs @@ -0,0 +1,71 @@ +mod util; + +use mun_gc::{Event, GCRootHandle, GCRuntime, MarkSweep}; +use std::sync::Arc; +use util::{EventAggregator, HasTypeInfo, TypeInfo}; + +#[test] +fn alloc() { + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); + let handle = runtime.alloc_object(i64::type_info()); + + assert!(std::ptr::eq( + unsafe { runtime.object_type(handle) }, + i64::type_info() + )); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(handle))); + assert_eq!(events.next(), None); +} + +#[test] +fn collect_simple() { + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); + let handle = runtime.alloc_object(i64::type_info()); + + runtime.collect(); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(handle))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(handle))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} + +#[test] +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())) }; + + // Collect unreachable objects, should not collect the root handle + runtime.collect(); + + // Performing a collection cycle now should not do a thing + runtime.collect(); + + // Drop the rooted handle which should become collectable now + let rooted_handle = rooted.handle(); + drop(rooted); + + // Collect unreachable objects, should now collect the rooted handle + runtime.collect(); + + // See if our version of events matched + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(handle))); + assert_eq!(events.next(), Some(Event::Allocation(rooted_handle))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(handle))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(rooted_handle))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} diff --git a/crates/mun_gc/tests/util/mod.rs b/crates/mun_gc/tests/util/mod.rs new file mode 100644 index 000000000..a38fca3b8 --- /dev/null +++ b/crates/mun_gc/tests/util/mod.rs @@ -0,0 +1,62 @@ +use mun_gc::Event; +use parking_lot::Mutex; + +pub struct TypeInfo { + size: usize, + alignment: usize, +} + +pub trait HasTypeInfo { + fn type_info() -> &'static TypeInfo; +} + +macro_rules! impl_primitive_types { + ($( + $ty:ident + ),+) => { + $( + paste::item! { + #[allow(non_upper_case_globals)] + static []: TypeInfo = TypeInfo { + size: std::mem::size_of::<$ty>(), + alignment: std::mem::align_of::<$ty>(), + }; + + impl HasTypeInfo for $ty { + fn type_info() -> &'static TypeInfo { + &[] + } + } + } + )+ + } +} + +impl_primitive_types!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool); + +impl mun_gc::Type for &'static TypeInfo { + fn size(&self) -> usize { + self.size + } + + fn alignment(&self) -> usize { + self.alignment + } +} + +#[derive(Default)] +pub struct EventAggregator { + events: Mutex>, +} + +impl EventAggregator { + pub fn take_all(&self) -> Vec { + self.events.lock().drain(..).collect() + } +} + +impl mun_gc::GCObserver for EventAggregator { + fn event(&self, event: Event) { + self.events.lock().push(event) + } +} diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index e27a3a352..e1f43917c 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -22,7 +22,7 @@ impl gc::Type for RawTypeInfo { } /// Defines an allocator used by the `Runtime` -pub type Allocator = gc::MarkSweep; +pub type Allocator = gc::MarkSweep; pub use gc::GCHandle; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 547d900af..c117192f8 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -40,7 +40,6 @@ pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; use gc::GCRuntime; -use gc::RawGCHandle; use std::sync::Arc; impl_has_type_info_name!( @@ -78,11 +77,6 @@ impl RuntimeBuilder { new as extern "C" fn(*const abi::TypeInfo, *mut ffi::c_void) -> *const *mut ffi::c_void, ); - result.insert_fn( - "clone", - clone as extern "C" fn(*const ffi::c_void, *mut ffi::c_void) -> *const *mut ffi::c_void, - ); - result } @@ -169,19 +163,6 @@ extern "C" fn new( handle.into() } -extern "C" fn clone( - src: *const ffi::c_void, - alloc_handle: *mut ffi::c_void, -) -> *const *mut ffi::c_void { - let allocator = unsafe { get_allocator(alloc_handle) }; - let handle = unsafe { allocator.clone_object((src as RawGCHandle).into()) }; - - // Prevent destruction of the allocator - mem::forget(allocator); - - handle.into() -} - impl Runtime { /// Constructs a new `Runtime` that loads the library at `library_path` and its /// dependencies. The `Runtime` contains a file watcher that is triggered with an interval diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index cc7558dac..4d2bccdf7 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -21,7 +21,7 @@ pub struct RawStruct(GCHandle); impl RawStruct { /// Returns a pointer to the struct memory. - pub fn get_ptr(&self) -> *mut u8 { + pub fn get_ptr(&self) -> NonNull { unsafe { self.0.get_ptr() } } } @@ -70,7 +70,13 @@ impl StructRef { unsafe fn offset_unchecked(&self, field_idx: usize) -> NonNull { let offset = *self.info.field_offsets().get_unchecked(field_idx); // self.raw is never null - NonNull::new_unchecked(self.handle.get_ptr::().add(offset as usize).cast::()) + NonNull::new_unchecked( + self.handle + .get_ptr::() + .as_ptr() + .add(offset as usize) + .cast::(), + ) } /// Retrieves the value of the field corresponding to the specified `field_name`. @@ -196,7 +202,7 @@ impl Marshal for RawStruct { // Construct let src = ptr.cast::().as_ptr() as *const _; - let dest = unsafe { gc_handle.get_ptr::() }; + let dest = unsafe { gc_handle.get_ptr::().as_ptr() }; let size = type_info.size_in_bytes(); unsafe { ptr::copy_nonoverlapping(src, dest, size as usize) }; @@ -219,7 +225,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(), dest, size as usize) }; + unsafe { ptr::copy_nonoverlapping(value.get_ptr().as_ptr(), dest, size as usize) }; } else { unsafe { *ptr.as_mut() = value }; } From d6809b6136cfddcd94c9fb7bd3405a97c933590c Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sun, 22 Mar 2020 19:26:39 +0100 Subject: [PATCH 08/10] feat: implemented very naive mark and sweep algorithm --- crates/mun_gc/src/handle.rs | 5 ++ crates/mun_gc/src/lib.rs | 5 ++ crates/mun_gc/src/mark_sweep.rs | 36 +++++++++-- crates/mun_gc/tests/alloc.rs | 4 +- crates/mun_gc/tests/struct.rs | 97 +++++++++++++++++++++++++++++ crates/mun_gc/tests/util/mod.rs | 70 ++++++++++++++++++++- crates/mun_runtime/src/allocator.rs | 47 ++++++++++++++ 7 files changed, 253 insertions(+), 11 deletions(-) create mode 100644 crates/mun_gc/tests/struct.rs diff --git a/crates/mun_gc/src/handle.rs b/crates/mun_gc/src/handle.rs index a4e9b9265..d19517cbd 100644 --- a/crates/mun_gc/src/handle.rs +++ b/crates/mun_gc/src/handle.rs @@ -90,6 +90,11 @@ impl> GCRootHandle { pub fn handle(&self) -> GCHandle { self.handle } + + /// Unroots the handle consuming self and returning the unrooted handle + pub fn unroot(self) -> GCHandle { + self.handle + } } impl> Into for GCRootHandle { diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs index fcdb12e9b..bfa424e4f 100644 --- a/crates/mun_gc/src/lib.rs +++ b/crates/mun_gc/src/lib.rs @@ -6,11 +6,16 @@ pub use mark_sweep::MarkSweep; /// A trait used by the GC to identify an object. pub trait Type: Send + Sync { + type Trace: Iterator; + /// Returns the size in bytes of an object of this type. fn size(&self) -> usize; /// Returns the alignment of a type 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; } /// An object that can be used to allocate and collect memory. diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs index 5f8ffa092..8d003f87b 100644 --- a/crates/mun_gc/src/mark_sweep.rs +++ b/crates/mun_gc/src/mark_sweep.rs @@ -1,7 +1,7 @@ use crate::{Event, GCHandle, GCObserver, GCRuntime, RawGCHandle, Type}; use parking_lot::RwLock; use std::alloc::Layout; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::ops::Deref; use std::pin::Pin; @@ -102,13 +102,36 @@ impl MarkSweep { let mut writer = self.objects.write(); - // Mark all reachable objects - for (_, obj) in writer.iter_mut() { - if obj.roots > 0 { - unsafe { - obj.as_mut().get_unchecked_mut().color = Color::Black; + // Get all roots + let mut roots = writer + .iter() + .filter_map(|(_, obj)| { + if obj.roots > 0 { + Some(obj.as_ref().get_ref() as *const _ as *mut ObjectInfo) + } else { + None + } + }) + .collect::>(); + + // Iterate over all roots + while let Some(next) = roots.pop_front() { + let handle = (next as *const _ as RawGCHandle).into(); + + // Trace all other objects + for reference in unsafe { (*next).ty.trace(handle) } { + let ref_ptr = writer.get_mut(&reference).expect("found invalid reference"); + if ref_ptr.color == Color::White { + let ptr = ref_ptr.as_ref().get_ref() as *const _ as *mut ObjectInfo; + unsafe { (*ptr).color = Color::Gray }; + roots.push_back(ptr); } } + + // This object has been traced + unsafe { + (*next).color = Color::Black; + } } // Sweep all non-reachable objects @@ -131,6 +154,7 @@ impl MarkSweep { #[derive(Debug, PartialEq, Eq)] enum Color { White, + Gray, Black, } diff --git a/crates/mun_gc/tests/alloc.rs b/crates/mun_gc/tests/alloc.rs index 7782306e8..3f848e643 100644 --- a/crates/mun_gc/tests/alloc.rs +++ b/crates/mun_gc/tests/alloc.rs @@ -1,3 +1,4 @@ +#[macro_use] mod util; use mun_gc::{Event, GCRootHandle, GCRuntime, MarkSweep}; @@ -49,8 +50,7 @@ fn collect_rooted() { runtime.collect(); // Drop the rooted handle which should become collectable now - let rooted_handle = rooted.handle(); - drop(rooted); + let rooted_handle = rooted.unroot(); // Collect unreachable objects, should now collect the rooted handle runtime.collect(); diff --git a/crates/mun_gc/tests/struct.rs b/crates/mun_gc/tests/struct.rs new file mode 100644 index 000000000..ef44b8c67 --- /dev/null +++ b/crates/mun_gc/tests/struct.rs @@ -0,0 +1,97 @@ +#[macro_use] +mod util; + +use mun_gc::{Event, GCHandle, GCRootHandle, GCRuntime, HasGCHandlePtr, MarkSweep, Type}; +use std::sync::Arc; +use util::{EventAggregator, HasTypeInfo, Trace, TypeInfo}; + +struct Foo { + bar: GCHandle, +} + +impl Trace for Foo { + fn trace(&self, handles: &mut Vec) { + handles.push(self.bar) + } +} + +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()); + + // Assign bar to foo.bar + unsafe { + foo_handle.get_ptr::().as_mut().bar = bar_handle; + } + + // Trace foo to see if we get bar back + let mut trace = Foo::type_info().trace(foo_handle); + + assert_eq!(trace.next(), Some(bar_handle)); + assert_eq!(trace.next(), None) +} + +#[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()); + + // Assign bar to foo.bar + unsafe { + foo.get_ptr::().as_mut().bar = bar; + } + + // Collect garbage, bar should not be collected + runtime.collect(); + + // Drop foo + let foo = foo.unroot(); + + // Collect garbage, both foo and bar should be collected + runtime.collect(); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(foo))); + assert_eq!(events.next(), Some(Event::Allocation(bar))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_variant!(events.next(), Some(Event::Deallocation(..))); // Don't care about the order + assert_variant!(events.next(), Some(Event::Deallocation(..))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} + +#[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())) }; + + // Assign bar to foo.bar + unsafe { + foo.get_ptr::().as_mut().bar = foo.handle(); + } + + // Collect garbage, nothing should be collected since foo is rooted + runtime.collect(); + + // Drop foo + let foo = foo.unroot(); + + // Collect garbage, foo should be collected + runtime.collect(); + + let mut events = runtime.observer().take_all().into_iter(); + assert_eq!(events.next(), Some(Event::Allocation(foo))); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), Some(Event::Start)); + assert_eq!(events.next(), Some(Event::Deallocation(foo))); + assert_eq!(events.next(), Some(Event::End)); + assert_eq!(events.next(), None); +} diff --git a/crates/mun_gc/tests/util/mod.rs b/crates/mun_gc/tests/util/mod.rs index a38fca3b8..666535465 100644 --- a/crates/mun_gc/tests/util/mod.rs +++ b/crates/mun_gc/tests/util/mod.rs @@ -1,9 +1,17 @@ -use mun_gc::Event; +#![allow(dead_code, unused_macros)] + +use mun_gc::{Event, GCHandle}; use parking_lot::Mutex; pub struct TypeInfo { - size: usize, - alignment: usize, + pub size: usize, + pub alignment: usize, + pub tracer: Option<&'static fn(handle: GCHandle) -> Vec>, +} + +pub trait Trace { + /// Called to collect all GC handles in the type + fn trace(&self, handles: &mut Vec); } pub trait HasTypeInfo { @@ -20,6 +28,7 @@ macro_rules! impl_primitive_types { static []: TypeInfo = TypeInfo { size: std::mem::size_of::<$ty>(), alignment: std::mem::align_of::<$ty>(), + tracer: None }; impl HasTypeInfo for $ty { @@ -32,9 +41,38 @@ macro_rules! impl_primitive_types { } } +macro_rules! impl_struct_ty { + ($ty:ident) => { + paste::item! { + #[allow(non_upper_case_globals, non_snake_case)] + fn [](obj:GCHandle) -> Vec { + let mut result = Vec::new(); + let foo = unsafe { obj.get_ptr::<$ty>().as_ptr().as_ref().unwrap() }; + foo.trace(&mut result); + result + } + + #[allow(non_upper_case_globals)] + static []: TypeInfo = TypeInfo { + size: std::mem::size_of::<$ty>(), + alignment: std::mem::align_of::<$ty>(), + tracer: Some(&([] as fn(handle: GCHandle) -> Vec)) + }; + + impl HasTypeInfo for $ty { + fn type_info() -> &'static TypeInfo { + &[] + } + } + } + }; +} + 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; + fn size(&self) -> usize { self.size } @@ -42,6 +80,15 @@ impl mun_gc::Type for &'static TypeInfo { fn alignment(&self) -> usize { self.alignment } + + fn trace(&self, obj: GCHandle) -> Self::Trace { + let handles = if let Some(tracer) = self.tracer { + tracer(obj) + } else { + Vec::new() + }; + handles.into_iter() + } } #[derive(Default)] @@ -60,3 +107,20 @@ impl mun_gc::GCObserver for EventAggregator { self.events.lock().push(event) } } + +macro_rules! assert_variant { + ($value:expr, $pattern:pat) => {{ + let value = &$value; + + if let $pattern = value { + } else { + panic!( + r#"assertion failed (value doesn't match pattern): + value: `{:?}`, + pattern: `{}`"#, + value, + stringify!($pattern) + ) + } + }}; // TODO: Additional patterns for trailing args, like assert and assert_eq +} diff --git a/crates/mun_runtime/src/allocator.rs b/crates/mun_runtime/src/allocator.rs index e1f43917c..2bb009e4b 100644 --- a/crates/mun_runtime/src/allocator.rs +++ b/crates/mun_runtime/src/allocator.rs @@ -1,3 +1,5 @@ +use gc::HasGCHandlePtr; + #[derive(Clone, Debug)] #[repr(transparent)] pub struct RawTypeInfo(*const abi::TypeInfo); @@ -11,7 +13,44 @@ impl Into for *const abi::TypeInfo { unsafe impl Send for RawTypeInfo {} unsafe impl Sync for RawTypeInfo {} +pub struct Trace { + obj: GCHandle, + ty: RawTypeInfo, + index: usize, +} + +impl Iterator for Trace { + type Item = GCHandle; + + fn next(&mut self) -> Option { + let struct_ty = unsafe { self.ty.0.as_ref() }.unwrap().as_struct()?; + let field_count = struct_ty.field_types().len(); + while self.index < field_count { + let index = self.index; + self.index += 1; + + let field_ty = struct_ty.field_types()[index]; + if let Some(field_struct_ty) = field_ty.as_struct() { + 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::() + }); + } + } + } + None + } +} + impl gc::Type for RawTypeInfo { + type Trace = Trace; + fn size(&self) -> usize { unsafe { (*self.0).size_in_bytes() } } @@ -19,6 +58,14 @@ impl gc::Type for RawTypeInfo { fn alignment(&self) -> usize { unsafe { (*self.0).alignment() } } + + fn trace(&self, obj: GCHandle) -> Self::Trace { + Trace { + ty: self.clone(), + obj, + index: 0, + } + } } /// Defines an allocator used by the `Runtime` From 85e6f32da7b543515919ef37aceec33e97e16ef8 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 23 Mar 2020 21:37:59 +0100 Subject: [PATCH 09/10] misc: general cleanup and renaming --- crates/mun_gc/src/handle.rs | 113 ++++------------- crates/mun_gc/src/lib.rs | 45 ++++--- crates/mun_gc/src/mark_sweep.rs | 118 +++++++++++++----- crates/mun_gc/src/root_handle.rs | 69 ++++++++++ crates/mun_gc/tests/alloc.rs | 10 +- crates/mun_gc/tests/struct.rs | 22 ++-- crates/mun_gc/tests/util/mod.rs | 18 +-- crates/mun_runtime/src/assembly.rs | 11 +- .../{allocator.rs => garbage_collector.rs} | 21 ++-- crates/mun_runtime/src/lib.rs | 42 ++++--- crates/mun_runtime/src/struct.rs | 33 ++--- crates/mun_runtime/src/test.rs | 35 ++++++ 12 files changed, 329 insertions(+), 208 deletions(-) create mode 100644 crates/mun_gc/src/root_handle.rs rename crates/mun_runtime/src/{allocator.rs => garbage_collector.rs} (73%) 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); +} From 4f8e3bce70e8ae1774056dd4d357bc1f127be0b2 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Wed, 25 Mar 2020 18:00:01 +0100 Subject: [PATCH 10/10] misc: applied suggestions from code review --- crates/mun_gc/Cargo.toml | 4 +- crates/mun_gc/src/lib.rs | 101 ++++++++++---------- crates/mun_gc/src/mark_sweep.rs | 98 +++++++++---------- crates/mun_gc/src/{handle.rs => ptr.rs} | 36 +++---- crates/mun_gc/src/root_handle.rs | 69 ------------- crates/mun_gc/src/root_ptr.rs | 65 +++++++++++++ crates/mun_gc/tests/alloc.rs | 15 ++- crates/mun_gc/tests/struct.rs | 18 ++-- crates/mun_gc/tests/util/mod.rs | 47 +++++---- crates/mun_runtime/src/garbage_collector.rs | 28 +++--- crates/mun_runtime/src/lib.rs | 10 +- crates/mun_runtime/src/struct.rs | 33 +++---- crates/mun_runtime/src/test.rs | 7 +- 13 files changed, 258 insertions(+), 273 deletions(-) rename crates/mun_gc/src/{handle.rs => ptr.rs} (57%) delete mode 100644 crates/mun_gc/src/root_handle.rs create mode 100644 crates/mun_gc/src/root_ptr.rs diff --git a/crates/mun_gc/Cargo.toml b/crates/mun_gc/Cargo.toml index 4dd456b3c..64e9116d2 100644 --- a/crates/mun_gc/Cargo.toml +++ b/crates/mun_gc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mun_gc" -version = "0.2.0" +version = "0.1.0" authors = ["The Mun Team "] edition = "2018" homepage = "https://mun-lang.org" @@ -13,4 +13,4 @@ abi = { path = "../mun_abi", package = "mun_abi" } parking_lot = "0.10" [dev-dependencies] -paste = "0.1" \ No newline at end of file +paste = "0.1" diff --git a/crates/mun_gc/src/lib.rs b/crates/mun_gc/src/lib.rs index 8455f6139..d0dad2b58 100644 --- a/crates/mun_gc/src/lib.rs +++ b/crates/mun_gc/src/lib.rs @@ -1,10 +1,12 @@ -mod handle; mod mark_sweep; -mod root_handle; +mod ptr; +mod root_ptr; -pub use handle::{GCPtr, HasIndirectionPtr, RawGCPtr}; pub use mark_sweep::MarkSweep; -pub use root_handle::GCRootHandle; +pub use ptr::{GcPtr, HasIndirectionPtr, RawGcPtr}; +pub use root_ptr::GcRootPtr; +use std::alloc::Layout; +use std::marker::PhantomData; /// Contains stats about the current state of a GC implementation #[derive(Debug, Clone, Default)] @@ -12,78 +14,75 @@ pub struct Stats { pub allocated_memory: usize, } -/// A trait used by the GC to identify an object. +/// A trait used by the `GcRuntime` to identify an object type. 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; - - /// Returns the alignment of a type - fn alignment(&self) -> usize; + /// Returns the memory layout of this type. + fn layout(&self) -> Layout; /// Returns an iterator to iterate over all GC objects that are referenced by the given object. - fn trace(&self, obj: GCPtr) -> 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 GCPtr - fn alloc(&self, ty: T) -> GCPtr; +pub trait GcRuntime: Send + Sync { + /// 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 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 - /// be collected. Internally this increments a root refcount. - /// - /// # Safety - /// - /// 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 - /// be collected. Internally this decrements a root refcount. When the refcount reaches 0, the - /// object is considered non-rooted. - /// - /// # Safety - /// - /// This method is unsafe because the passed GCPtr could point to random memory. - unsafe fn unroot(&self, obj: GCPtr); + fn ptr_type(&self, obj: GcPtr) -> T; + + /// Roots the specified `obj`, which keeps it and objects it references alive. Objects marked + /// as root, must call `unroot` before they can be collected. An object can be rooted multiple + /// times, but you must make sure to call `unroot` an equal number of times before the object + /// can be collected. + fn root(&self, obj: GcPtr); + + /// Unroots the specified `obj`, potentially allowing it and objects it references to be + /// collected. An object can be rooted multiple times, so you must make sure to call `unroot` + /// the same number of times as `root` was called before the object can be collected. + fn unroot(&self, obj: GcPtr); /// Returns stats about the current state of the runtime. fn stats(&self) -> Stats; } +/// The `Observer` trait allows receiving of `Event`s. +pub trait Observer: Send + Sync { + type Event; + + fn event(&self, _event: Self::Event) {} +} + +/// An `Event` is an event that can be emitted by a `GcRuntime` through the use of an `Observer`. +/// This enables tracking of the runtimes behavior which is useful for testing. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { /// The GC performed an allocation - Allocation(GCPtr), + Allocation(GcPtr), /// A GC cycle started Start, /// A deallocation took place - Deallocation(GCPtr), + Deallocation(GcPtr), /// A GC cycle ended End, } -/// 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 +/// A default implementation of an `Observer` which ensures that the compiler does not generate /// code for event handling. -#[derive(Clone, Default)] -pub struct NoopObserver; -impl Observer for NoopObserver {} +#[derive(Clone)] +pub struct NoopObserver { + data: PhantomData, +} +impl Observer for NoopObserver { + type Event = T; +} +impl Default for NoopObserver { + fn default() -> Self { + NoopObserver { data: PhantomData } + } +} diff --git a/crates/mun_gc/src/mark_sweep.rs b/crates/mun_gc/src/mark_sweep.rs index 2f700d860..8bc7acbb2 100644 --- a/crates/mun_gc/src/mark_sweep.rs +++ b/crates/mun_gc/src/mark_sweep.rs @@ -1,19 +1,18 @@ -use crate::{Event, GCPtr, GCRuntime, Observer, RawGCPtr, Stats, Type}; +use crate::{Event, GcPtr, GcRuntime, Observer, RawGcPtr, Stats, Type}; use parking_lot::RwLock; -use std::alloc::Layout; use std::collections::{HashMap, VecDeque}; use std::ops::Deref; use std::pin::Pin; -/// Implements a simple mark-sweep type memory collector. Uses a HashMap of +/// Implements a simple mark-sweep type garbage collector. #[derive(Debug)] -pub struct MarkSweep { - objects: RwLock>>>>, +pub struct MarkSweep> { + objects: RwLock>>>>, observer: O, stats: RwLock, } -impl Default for MarkSweep { +impl + Default> Default for MarkSweep { fn default() -> Self { MarkSweep { objects: RwLock::new(HashMap::new()), @@ -23,13 +22,8 @@ impl Default for MarkSweep { } } -impl MarkSweep { - pub fn new() -> Self { - Default::default() - } -} - -impl MarkSweep { +impl> MarkSweep { + /// Creates a `MarkSweep` memory collector with the specified `Observer`. pub fn with_observer(observer: O) -> Self { Self { objects: RwLock::new(HashMap::new()), @@ -37,13 +31,6 @@ impl MarkSweep { stats: RwLock::new(Stats::default()), } } -} - -impl MarkSweep { - /// Allocates a block of memory - fn alloc_memory(&self, size: usize, alignment: usize) -> *mut u8 { - unsafe { std::alloc::alloc(Layout::from_size_align_unchecked(size, alignment)) } - } /// Returns the observer pub fn observer(&self) -> &O { @@ -51,10 +38,10 @@ impl MarkSweep { } } -impl GCRuntime for MarkSweep { - fn alloc(&self, ty: T) -> GCPtr { - let size = ty.size(); - let ptr = self.alloc_memory(ty.size(), ty.alignment()); +impl> GcRuntime for MarkSweep { + fn alloc(&self, ty: T) -> GcPtr { + let layout = ty.layout(); + let ptr = unsafe { std::alloc::alloc(layout) }; let object = Box::pin(ObjectInfo { ptr, ty, @@ -63,7 +50,7 @@ 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 RawGCPtr).into(); + let handle = (object.as_ref().deref() as *const _ as RawGcPtr).into(); { let mut objects = self.objects.write(); @@ -72,41 +59,39 @@ impl GCRuntime for MarkSweep { { let mut stats = self.stats.write(); - stats.allocated_memory += size; + stats.allocated_memory += layout.size(); } self.observer.event(Event::Allocation(handle)); handle } - unsafe fn ptr_type(&self, handle: GCPtr) -> T { + 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 { (*object_info).ty.clone() } } - unsafe fn root(&self, handle: GCPtr) { + fn root(&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 { (*object_info).roots += 1 }; } - unsafe fn unroot(&self, handle: GCPtr) { + 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 { (*object_info).roots -= 1 }; } fn stats(&self) -> Stats { @@ -114,16 +99,16 @@ impl GCRuntime for MarkSweep { } } -impl MarkSweep { +impl + Default> 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(); + let mut objects = self.objects.write(); // Get all roots - let mut roots = writer + let mut roots = objects .iter() .filter_map(|(_, obj)| { if obj.roots > 0 { @@ -136,11 +121,13 @@ impl MarkSweep { // Iterate over all roots while let Some(next) = roots.pop_front() { - let handle = (next as *const _ as RawGCPtr).into(); + let handle = (next as *const _ as RawGcPtr).into(); // Trace all other objects for reference in unsafe { (*next).ty.trace(handle) } { - let ref_ptr = writer.get_mut(&reference).expect("found invalid reference"); + let ref_ptr = objects + .get_mut(&reference) + .expect("found invalid reference"); if ref_ptr.color == Color::White { let ptr = ref_ptr.as_ref().get_ref() as *const _ as *mut ObjectInfo; unsafe { (*ptr).color = Color::Gray }; @@ -155,8 +142,8 @@ impl MarkSweep { } // Sweep all non-reachable objects - let size_before = writer.len(); - writer.retain(|h, obj| { + let size_before = objects.len(); + objects.retain(|h, obj| { if obj.color == Color::Black { unsafe { obj.as_mut().get_unchecked_mut().color = Color::White; @@ -166,12 +153,12 @@ impl MarkSweep { self.observer.event(Event::Deallocation(*h)); { let mut stats = self.stats.write(); - stats.allocated_memory -= obj.ty.size(); + stats.allocated_memory -= obj.ty.layout().size(); } false } }); - let size_after = writer.len(); + let size_after = objects.len(); self.observer.event(Event::End); @@ -179,14 +166,21 @@ impl MarkSweep { } } +/// Coloring used in the Mark Sweep phase. #[derive(Debug, PartialEq, Eq)] enum Color { + /// A white object has not been seen yet by the mark phase White, + + /// A gray object has been seen by the mark phase but has not yet been visited Gray, + + /// A black object has been visited by the mark phase Black, } -/// An indirection table that stores the address to the actual memory and the type of the object +/// An indirection table that stores the address to the actual memory, the type of the object and +/// meta information. #[derive(Debug)] #[repr(C)] struct ObjectInfo { @@ -200,26 +194,26 @@ struct ObjectInfo { unsafe impl Send for ObjectInfo {} unsafe impl Sync for ObjectInfo {} -impl Into<*const ObjectInfo> for GCPtr { +impl Into<*const ObjectInfo> for GcPtr { fn into(self) -> *const ObjectInfo { self.as_ptr() as *const ObjectInfo } } -impl Into<*mut ObjectInfo> for GCPtr { +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 *const ObjectInfo { + fn into(self) -> GcPtr { + (self as RawGcPtr).into() } } -impl Into for *mut 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/handle.rs b/crates/mun_gc/src/ptr.rs similarity index 57% rename from crates/mun_gc/src/handle.rs rename to crates/mun_gc/src/ptr.rs index 0c47a6d1c..d2fdc562e 100644 --- a/crates/mun_gc/src/handle.rs +++ b/crates/mun_gc/src/ptr.rs @@ -1,20 +1,20 @@ -/// A `GCPtr` 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 -/// that the indirection pointer must therefor be pinned in memory whereas the pointer stored -/// at the indirection may change. +/// that the `GcPtr` must therefore be pinned in memory whereas the contained memory pointer may +/// change. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] -pub struct GCPtr(RawGCPtr); +pub struct GcPtr(RawGcPtr); -/// A `GCPtr` is thread safe. -unsafe impl Send for GCPtr {} -unsafe impl Sync for GCPtr {} +/// A `GcPtr` is thread safe. +unsafe impl Send for GcPtr {} +unsafe impl Sync for GcPtr {} -/// A `RawGCPtr` is an unsafe version of a `GCPtr`. 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 RawGCPtr = *const *mut std::ffi::c_void; +pub type RawGcPtr = *const *mut std::ffi::c_void; pub trait HasIndirectionPtr { /// Returns a pointer to the referenced memory. @@ -29,31 +29,31 @@ pub trait HasIndirectionPtr { /// # Safety /// /// This is an unsafe method because derefencing could result in an access violation. - unsafe fn deref_mut(&self) -> *mut T { + unsafe fn deref_mut(&mut self) -> *mut T { self.deref::() as *mut _ } } -impl HasIndirectionPtr for GCPtr { +impl HasIndirectionPtr for GcPtr { unsafe fn deref(&self) -> *const T { (*self.0).cast() } } -impl Into for GCPtr { - fn into(self) -> RawGCPtr { +impl Into for GcPtr { + fn into(self) -> RawGcPtr { self.0 } } -impl Into for RawGCPtr { - fn into(self) -> GCPtr { - GCPtr(self) +impl Into for RawGcPtr { + fn into(self) -> GcPtr { + GcPtr(self) } } -impl GCPtr { - pub(crate) fn as_ptr(self) -> RawGCPtr { +impl GcPtr { + pub(crate) fn as_ptr(self) -> RawGcPtr { self.0 } } diff --git a/crates/mun_gc/src/root_handle.rs b/crates/mun_gc/src/root_handle.rs deleted file mode 100644 index 7bbee6bcc..000000000 --- a/crates/mun_gc/src/root_handle.rs +++ /dev/null @@ -1,69 +0,0 @@ -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/src/root_ptr.rs b/crates/mun_gc/src/root_ptr.rs new file mode 100644 index 000000000..fd0d9cd40 --- /dev/null +++ b/crates/mun_gc/src/root_ptr.rs @@ -0,0 +1,65 @@ +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 GcRootPtr> { + handle: GcPtr, + runtime: Weak, + ty: PhantomData, +} + +impl> Clone for GcRootPtr { + fn clone(&self) -> Self { + if let Some(runtime) = self.runtime.upgrade() { + runtime.root(self.handle) + } + Self { + handle: self.handle, + runtime: self.runtime.clone(), + ty: PhantomData, + } + } +} + +impl> GcRootPtr { + /// Constructs a new GCRootHandle from a runtime and a handle + pub fn new(runtime: &Arc, handle: GcPtr) -> Self { + runtime.root(handle); + Self { + handle, + runtime: Arc::downgrade(runtime), + ty: PhantomData, + } + } + + /// 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 GcRootPtr { + fn into(self) -> GcPtr { + self.handle + } +} + +impl> Drop for GcRootPtr { + fn drop(&mut self) { + if let Some(runtime) = self.runtime.upgrade() { + runtime.unroot(self.handle) + } + } +} + +impl> HasIndirectionPtr for GcRootPtr { + 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 c3445712c..31921a3c2 100644 --- a/crates/mun_gc/tests/alloc.rs +++ b/crates/mun_gc/tests/alloc.rs @@ -1,19 +1,16 @@ #[macro_use] mod util; -use mun_gc::{Event, GCRootHandle, GCRuntime, MarkSweep}; +use mun_gc::{Event, GcRootPtr, GcRuntime, MarkSweep}; use std::sync::Arc; use util::{EventAggregator, HasTypeInfo, TypeInfo}; #[test] fn alloc() { - let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::default(); let handle = runtime.alloc(i64::type_info()); - assert!(std::ptr::eq( - unsafe { runtime.ptr_type(handle) }, - i64::type_info() - )); + assert!(std::ptr::eq(runtime.ptr_type(handle), i64::type_info())); let mut events = runtime.observer().take_all().into_iter(); assert_eq!(events.next(), Some(Event::Allocation(handle))); @@ -22,7 +19,7 @@ fn alloc() { #[test] fn collect_simple() { - let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::default(); let handle = runtime.alloc(i64::type_info()); runtime.collect(); @@ -37,11 +34,11 @@ fn collect_simple() { #[test] fn collect_rooted() { - let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::new()); + let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::default()); // Allocate simple object and rooted object let handle = runtime.alloc(i64::type_info()); - let rooted = unsafe { GCRootHandle::new(&runtime, runtime.alloc(i64::type_info())) }; + let rooted = GcRootPtr::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 0d01cb333..ae2c0f628 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, GCPtr, GCRootHandle, GCRuntime, HasIndirectionPtr, MarkSweep, Type}; +use mun_gc::{Event, GcPtr, GcRootPtr, GcRuntime, HasIndirectionPtr, MarkSweep, Type}; use std::sync::Arc; use util::{EventAggregator, HasTypeInfo, Trace, TypeInfo}; struct Foo { - bar: GCPtr, + bar: GcPtr, } impl Trace for Foo { - fn trace(&self, handles: &mut Vec) { + fn trace(&self, handles: &mut Vec) { handles.push(self.bar) } } @@ -19,8 +19,8 @@ impl_struct_ty!(Foo); #[test] fn test_trace() { - let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::new(); - let foo_handle = runtime.alloc(Foo::type_info()); + let runtime = MarkSweep::<&'static TypeInfo, EventAggregator>::default(); + let mut foo_handle = runtime.alloc(Foo::type_info()); let bar_handle = runtime.alloc(i64::type_info()); // Assign bar to foo.bar @@ -37,8 +37,8 @@ fn test_trace() { #[test] fn trace_collect() { - let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::new()); - let foo = unsafe { GCRootHandle::new(&runtime, runtime.alloc(Foo::type_info())) }; + let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::default()); + let mut foo = GcRootPtr::new(&runtime, runtime.alloc(Foo::type_info())); let bar = runtime.alloc(i64::type_info()); // Assign bar to foo.bar @@ -69,8 +69,8 @@ fn trace_collect() { #[test] fn trace_cycle() { - let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::new()); - let foo = unsafe { GCRootHandle::new(&runtime, runtime.alloc(Foo::type_info())) }; + let runtime = Arc::new(MarkSweep::<&'static TypeInfo, EventAggregator>::default()); + let mut foo = GcRootPtr::new(&runtime, runtime.alloc(Foo::type_info())); // Assign bar to foo.bar unsafe { diff --git a/crates/mun_gc/tests/util/mod.rs b/crates/mun_gc/tests/util/mod.rs index 4fe6d72d4..cb48b8e94 100644 --- a/crates/mun_gc/tests/util/mod.rs +++ b/crates/mun_gc/tests/util/mod.rs @@ -1,17 +1,18 @@ #![allow(dead_code, unused_macros)] -use mun_gc::{Event, GCPtr}; +use mun_gc::GcPtr; use parking_lot::Mutex; +use std::alloc::Layout; pub struct TypeInfo { pub size: usize, pub alignment: usize, - pub tracer: Option<&'static fn(handle: GCPtr) -> 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,7 +46,7 @@ macro_rules! impl_struct_ty { ($ty:ident) => { paste::item! { #[allow(non_upper_case_globals, non_snake_case)] - fn [](obj:GCPtr) -> Vec { + fn [](obj:GcPtr) -> Vec { let mut result = Vec::new(); let foo = unsafe { &(*obj.deref::<$ty>()) }; foo.trace(&mut result); @@ -56,7 +57,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: GCPtr) -> Vec)) + tracer: Some(&([] as fn(handle: GcPtr) -> Vec)) }; impl HasTypeInfo for $ty { @@ -71,17 +72,14 @@ 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 + fn layout(&self) -> Layout { + Layout::from_size_align(self.size as usize, self.alignment as usize) + .expect("invalid layout specified by TypeInfo") } - fn alignment(&self) -> usize { - self.alignment - } - - fn trace(&self, obj: GCPtr) -> Self::Trace { + fn trace(&self, obj: GcPtr) -> Self::Trace { let handles = if let Some(tracer) = self.tracer { tracer(obj) } else { @@ -91,19 +89,28 @@ impl mun_gc::Type for &'static TypeInfo { } } -#[derive(Default)] -pub struct EventAggregator { - events: Mutex>, +pub struct EventAggregator { + events: Mutex>, } -impl EventAggregator { - pub fn take_all(&self) -> Vec { +impl Default for EventAggregator { + fn default() -> Self { + EventAggregator { + events: Mutex::new(Vec::new()), + } + } +} + +impl EventAggregator { + pub fn take_all(&self) -> Vec { self.events.lock().drain(..).collect() } } -impl mun_gc::Observer for EventAggregator { - fn event(&self, event: Event) { +impl mun_gc::Observer for EventAggregator { + type Event = T; + + fn event(&self, event: T) { self.events.lock().push(event) } } diff --git a/crates/mun_runtime/src/garbage_collector.rs b/crates/mun_runtime/src/garbage_collector.rs index 2e1ea1173..ff7d0446a 100644 --- a/crates/mun_runtime/src/garbage_collector.rs +++ b/crates/mun_runtime/src/garbage_collector.rs @@ -1,4 +1,5 @@ use gc::HasIndirectionPtr; +use std::alloc::Layout; #[derive(Clone, Debug)] #[repr(transparent)] @@ -14,13 +15,13 @@ unsafe impl Send for RawTypeInfo {} unsafe impl Sync for RawTypeInfo {} pub struct Trace { - obj: GCPtr, + obj: GcPtr, ty: RawTypeInfo, index: usize, } impl Iterator for Trace { - type Item = GCPtr; + type Item = GcPtr; fn next(&mut self) -> Option { let struct_ty = unsafe { self.ty.0.as_ref() }.unwrap().as_struct()?; @@ -34,7 +35,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.deref::().add(offset as usize).cast::() + *self.obj.deref::().add(offset as usize).cast::() }); } } @@ -46,15 +47,13 @@ impl Iterator for Trace { impl gc::Type for RawTypeInfo { type Trace = Trace; - fn size(&self) -> usize { - unsafe { (*self.0).size_in_bytes() } + fn layout(&self) -> Layout { + let ty = unsafe { &*self.0 }; + Layout::from_size_align(ty.size_in_bytes(), ty.alignment()) + .expect("invalid layout from Mun Type") } - fn alignment(&self) -> usize { - unsafe { (*self.0).alignment() } - } - - fn trace(&self, obj: GCPtr) -> Self::Trace { + fn trace(&self, obj: GcPtr) -> Self::Trace { Trace { ty: self.clone(), obj, @@ -63,9 +62,8 @@ impl gc::Type for RawTypeInfo { } } -/// Defines an allocator used by the `Runtime` -pub type GarbageCollector = gc::MarkSweep; - -pub use gc::GCPtr; +/// Defines the garbage collector used by the `Runtime`. +pub type GarbageCollector = gc::MarkSweep>; -pub type GCRootHandle = gc::GCRootHandle; +pub use gc::GcPtr; +pub type GcRootPtr = gc::GcRootPtr; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 388473c4b..5f8d0c92b 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -39,7 +39,7 @@ pub use crate::assembly::Assembly; use crate::function::IntoFunctionInfo; pub use crate::r#struct::StructRef; use garbage_collector::GarbageCollector; -use gc::GCRuntime; +use gc::GcRuntime; use std::sync::Arc; impl_has_type_info_name!( @@ -184,7 +184,7 @@ impl Runtime { dispatch_table, watcher, watcher_rx: rx, - gc: Arc::new(self::garbage_collector::GarbageCollector::new()), + gc: Arc::new(self::garbage_collector::GarbageCollector::default()), _user_functions: storages, }; @@ -247,14 +247,14 @@ impl Runtime { false } - /// Returns the runtime's allocator. + /// Returns the runtime's garbage collector. 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 { + /// was reclaimed, `false` otherwise. This behavior will likely change in the future. + pub fn gc_collect(&self) -> bool { self.gc.collect() } diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 52cee0a37..bd25fc7e7 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,4 +1,4 @@ -use crate::garbage_collector::{GCPtr, GCRootHandle}; +use crate::garbage_collector::{GcPtr, GcRootPtr}; use crate::{ marshal::Marshal, reflection::{ @@ -6,7 +6,7 @@ use crate::{ }, Runtime, }; -use gc::{GCRuntime, HasIndirectionPtr}; +use gc::{GcRuntime, HasIndirectionPtr}; use std::cell::RefCell; use std::ptr::{self, NonNull}; use std::rc::Rc; @@ -16,7 +16,7 @@ use std::rc::Rc; /// A byte pointer is used to make pointer arithmetic easier. #[repr(transparent)] #[derive(Clone)] -pub struct RawStruct(GCPtr); +pub struct RawStruct(GcPtr); impl RawStruct { /// Returns a pointer to the struct memory. @@ -29,7 +29,7 @@ impl RawStruct { /// TODO: Handle destruction of `struct(value)` pub struct StructRef { runtime: Rc>, - handle: GCRootHandle, + handle: GcRootPtr, type_info: *const abi::TypeInfo, info: abi::StructInfo, } @@ -43,7 +43,7 @@ impl StructRef { let handle = { let runtime_ref = runtime.borrow(); - unsafe { GCRootHandle::new(runtime_ref.gc(), raw.0) } + GcRootPtr::new(runtime_ref.gc(), raw.0) }; Self { runtime, @@ -64,7 +64,11 @@ impl StructRef { } /// Returns the type information of the struct - pub fn type_info(&self) -> *const abi::TypeInfo { + /// + /// # Safety + /// + /// This method is unsafe because it returns a pointer that may point to deallocated memory. + pub unsafe fn type_info(&self) -> *const abi::TypeInfo { self.type_info } @@ -76,12 +80,7 @@ impl StructRef { unsafe fn offset_unchecked(&self, field_idx: usize) -> NonNull { let offset = *self.info.field_offsets().get_unchecked(field_idx); // self.raw is never null - NonNull::new_unchecked( - self.handle - .deref_mut::() - .add(offset as usize) - .cast::(), - ) + NonNull::new_unchecked(self.handle.deref::().add(offset as usize).cast::() as *mut _) } /// Retrieves the value of the field corresponding to the specified `field_name`. @@ -196,11 +195,10 @@ impl Marshal for RawStruct { // Copy the contents of the struct based on what kind of pointer we are dealing with let gc_handle = if struct_info.memory_kind == abi::StructMemoryKind::Value { - // If this case the passed in `ptr` is a pointer to a value struct so `ptr` points to a - // struct value. + // For a value struct, `ptr` points to a struct value. // Create a new object using the runtime's intrinsic - let gc_handle = { + let mut gc_handle = { let runtime_ref = runtime.borrow(); runtime_ref.gc().alloc(type_info_ptr) }; @@ -213,10 +211,9 @@ impl Marshal for RawStruct { gc_handle } else { - // If this case the passed in `ptr` is a pointer to a gc struct so `ptr` points to a - // GCPtr. + // For a gc struct, `ptr` points to a `GcPtr`. - unsafe { *ptr.cast::().as_ptr() } + unsafe { *ptr.cast::().as_ptr() } }; StructRef::new(runtime, type_info, RawStruct(gc_handle)) diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index 0685e279d..560c108c9 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -550,9 +550,6 @@ fn marshal_struct() { test_shallow_copy(&mut foo, &foo2, &bool_data, "b"); let mut bar = qux.get::("0").unwrap(); - // let bar2 = qux.get::("0").unwrap(); - // test_shallow_copy(&mut bar, &bar2, &int_data, "0"); - // test_shallow_copy(&mut bar, &bar2, &bool_data, "1"); // Specify invalid return type let bar_err = bar.get::("0"); @@ -761,11 +758,11 @@ fn gc_trace() { let value: StructRef = invoke_fn!(driver.runtime_mut(), "new_foo").unwrap(); - assert_eq!(driver.runtime_mut().borrow().collect(), false); + assert_eq!(driver.runtime_mut().borrow().gc_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_collect(), true); assert_eq!(driver.runtime_mut().borrow().gc_stats().allocated_memory, 0); }