Skip to content

Commit

Permalink
feat: implemented very naive mark and sweep algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Mar 23, 2020
1 parent c19e9b3 commit 104d4d2
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 11 deletions.
5 changes: 5 additions & 0 deletions crates/mun_gc/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ impl<T: Type, G: GCRuntime<T>> GCRootHandle<T, G> {
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<T: Type, G: GCRuntime<T>> Into<GCHandle> for GCRootHandle<T, G> {
Expand Down
5 changes: 5 additions & 0 deletions crates/mun_gc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = GCHandle>;

/// 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.
Expand Down
36 changes: 30 additions & 6 deletions crates/mun_gc/src/mark_sweep.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -102,13 +102,36 @@ impl<T: Type + Clone, O: GCObserver + Default> MarkSweep<T, O> {

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<T>)
} else {
None
}
})
.collect::<VecDeque<_>>();

// 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<T>;
unsafe { (*ptr).color = Color::Gray };
roots.push_back(ptr);
}
}

// This object has been traced
unsafe {
(*next).color = Color::Black;
}
}

// Sweep all non-reachable objects
Expand All @@ -131,6 +154,7 @@ impl<T: Type + Clone, O: GCObserver + Default> MarkSweep<T, O> {
#[derive(Debug, PartialEq, Eq)]
enum Color {
White,
Gray,
Black,
}

Expand Down
4 changes: 2 additions & 2 deletions crates/mun_gc/tests/alloc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[macro_use]
mod util;

use mun_gc::{Event, GCRootHandle, GCRuntime, MarkSweep};
Expand Down Expand Up @@ -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();
Expand Down
97 changes: 97 additions & 0 deletions crates/mun_gc/tests/struct.rs
Original file line number Diff line number Diff line change
@@ -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<GCHandle>) {
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::<Foo>().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::<Foo>().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::<Foo>().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);
}
70 changes: 67 additions & 3 deletions crates/mun_gc/tests/util/mod.rs
Original file line number Diff line number Diff line change
@@ -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<GCHandle>>,
}

pub trait Trace {
/// Called to collect all GC handles in the type
fn trace(&self, handles: &mut Vec<GCHandle>);
}

pub trait HasTypeInfo {
Expand All @@ -20,6 +28,7 @@ macro_rules! impl_primitive_types {
static [<TYPE_ $ty>]: TypeInfo = TypeInfo {
size: std::mem::size_of::<$ty>(),
alignment: std::mem::align_of::<$ty>(),
tracer: None
};

impl HasTypeInfo for $ty {
Expand All @@ -32,16 +41,54 @@ macro_rules! impl_primitive_types {
}
}

macro_rules! impl_struct_ty {
($ty:ident) => {
paste::item! {
#[allow(non_upper_case_globals, non_snake_case)]
fn [<trace_ $ty>](obj:GCHandle) -> Vec<GCHandle> {
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 [<TYPE_ $ty>]: TypeInfo = TypeInfo {
size: std::mem::size_of::<$ty>(),
alignment: std::mem::align_of::<$ty>(),
tracer: Some(&([<trace_ $ty>] as fn(handle: GCHandle) -> Vec<GCHandle>))
};

impl HasTypeInfo for $ty {
fn type_info() -> &'static TypeInfo {
&[<TYPE_ $ty>]
}
}
}
};
}

impl_primitive_types!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool);

impl mun_gc::Type for &'static TypeInfo {
type Trace = <Vec<GCHandle> as IntoIterator>::IntoIter;

fn size(&self) -> usize {
self.size
}

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)]
Expand All @@ -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
}
47 changes: 47 additions & 0 deletions crates/mun_runtime/src/allocator.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use gc::HasGCHandlePtr;

#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct RawTypeInfo(*const abi::TypeInfo);
Expand All @@ -11,14 +13,59 @@ impl Into<RawTypeInfo> 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<Self::Item> {
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::<u8>()
.as_ptr()
.add(offset as usize)
.cast::<GCHandle>()
});
}
}
}
None
}
}

impl gc::Type for RawTypeInfo {
type Trace = Trace;

fn size(&self) -> usize {
unsafe { (*self.0).size_in_bytes() }
}

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`
Expand Down

0 comments on commit 104d4d2

Please sign in to comment.