From 104d4d22ffa8c6a3cecf943c83e5c48323126e20 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sun, 22 Mar 2020 19:26:39 +0100 Subject: [PATCH] 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`