Skip to content

Commit

Permalink
feat: Add gc::Mutex
Browse files Browse the repository at this point in the history
`gc::Mutex` is a GC aware drop-in replacement of `std::sync::Mutex`
which lets it avoid uncollectable cycles that would otherwise occur if
Gc allocated values are stored in the `Mutex`.
  • Loading branch information
Marwes committed Jun 26, 2019
1 parent e90f02b commit d6e1246
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 35 deletions.
6 changes: 3 additions & 3 deletions book/src/marshalling-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,13 @@ cycles if your userdata stores values managed by Gluon's GC. However if it doesn
#[gluon_trace(skip)]
struct SimpleType {
name: String,
slot: Mutex<i32>,
slot: std::sync::Mutex<i32>,
}
// Here we store a `OpaqueValue` which is managed by gluon's GC. To avoid a reference cycle we must trace
// the field so gluon can find it
// the field so gluon can find it. `gc::Mutex` is a drop-in replacement for `std::sync::Mutex` which is GC aware.
#[derive(Trace)]
struct Callback(Mutex<OpaqueValue<RootedThread, fn (i32) -> String>>);
struct Callback(gluon::vm::gc::Mutex<OpaqueValue<RootedThread, fn (i32) -> String>>);
```

## Passing values to and from Gluon
Expand Down
14 changes: 8 additions & 6 deletions codegen/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,18 @@ fn gen_impl(
unsafe impl #impl_generics _gluon_gc::Trace for #ident #ty_generics
#where_clause #(#trace_bounds,)*
{
unsafe fn root(&self, gc: &mut _gluon_gc::Gc) {
unsafe fn mark<T: ?Sized + _gluon_gc::Trace>(this: &T, gc: &mut _gluon_gc::Gc) {
_gluon_gc::Trace::root(this, gc)
unsafe fn root(&self) {
unsafe fn mark<T: ?Sized + _gluon_gc::Trace>(this: &T, _: ()) {
_gluon_gc::Trace::root(this)
}
let gc = ();
#push_impl
}
unsafe fn unroot(&self, gc: &mut _gluon_gc::Gc) {
unsafe fn mark<T: ?Sized + _gluon_gc::Trace>(this: &T, gc: &mut _gluon_gc::Gc) {
_gluon_gc::Trace::unroot(this, gc)
unsafe fn unroot(&self) {
unsafe fn mark<T: ?Sized + _gluon_gc::Trace>(this: &T, _: ()) {
_gluon_gc::Trace::unroot(this)
}
let gc = ();
#push_impl
}
fn trace(&self, gc: &mut _gluon_gc:: Gc) {
Expand Down
66 changes: 60 additions & 6 deletions tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use gluon::{
scoped::{Ref, RefMut},
FunctionRef, FutureResult, Hole, OpaqueValue, OwnedFunction, RuntimeResult, VmType, IO,
},
gc,
thread::{RootedThread, Thread},
types::VmInt,
Error, ExternModule,
Expand Down Expand Up @@ -533,16 +534,16 @@ fn scoped_mutable_reference() {
);
}

#[derive(Clone, Debug, Default, Userdata, Trace)]
struct NoisyDrop(Arc<()>);
impl VmType for NoisyDrop {
type Type = NoisyDrop;
}

#[test]
fn cyclic_userdata() {
let _ = ::env_logger::try_init();

#[derive(Clone, Debug, Default, Userdata, Trace)]
struct NoisyDrop(Arc<()>);
impl VmType for NoisyDrop {
type Type = NoisyDrop;
}

#[derive(Debug, Userdata, Trace)]
struct Cyclic(OpaqueValue<RootedThread, NoisyDrop>);
impl VmType for Cyclic {
Expand Down Expand Up @@ -585,3 +586,56 @@ fn cyclic_userdata() {
"The virtual machine and its values were not dropped"
);
}

#[test]
fn cyclic_userdata_mutable() {
let _ = ::env_logger::try_init();

#[derive(Debug, Default, Userdata, Trace)]
struct Cyclic(gc::mutex::Mutex<Option<OpaqueValue<RootedThread, NoisyDrop>>>);
impl VmType for Cyclic {
type Type = Cyclic;
}

let mut noisy_drop = NoisyDrop::default();
{
let vm = make_vm();

vm.register_type::<NoisyDrop>("NoisyDrop", &[])
.unwrap_or_else(|_| panic!("Could not add type"));
vm.register_type::<Cyclic>("Cyclic", &[])
.unwrap_or_else(|_| panic!("Could not add type"));

add_extern_module(&vm, "function", |thread| {
ExternModule::new(
thread,
record! {
mk_cyclic => primitive!(1, |()| Cyclic::default()),
set => primitive!(2, |cyclic: &Cyclic, noisy| *cyclic.0.lock().unwrap() = Some(noisy))
},
)
});

let expr = r#"
let f = import! function
\noisy ->
let cyclic = f.mk_cyclic ()
f.set cyclic noisy
"#;
Compiler::new().load_script(&vm, "test", expr).unwrap();

// Allocate a `Cyclic` value in the vm
let mut f: FunctionRef<fn(NoisyDrop)> = vm
.get_global("test")
.unwrap_or_else(|err| panic!("{}", err));
f.call(noisy_drop.clone()).unwrap();

// When dropping the vm here, the `OpaqueValue<RootedThread, NoisyDrop>` field should not
// keep the vm alive
}

assert!(
Arc::get_mut(&mut noisy_drop.0).is_some(),
"The virtual machine and its values were not dropped"
);
}
26 changes: 15 additions & 11 deletions vm/src/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use crate::interner::InternedStr;
use crate::types::VmIndex;
use crate::{Error, Result};

pub mod mutex;

#[inline]
unsafe fn allocate(size: usize) -> *mut u8 {
// Allocate an extra element if it does not fit exactly
Expand Down Expand Up @@ -490,8 +492,8 @@ pub trait CollectScope {
/// A type unsafe implementing Trace must call trace on each of its fields
/// which in turn contains `GcPtr`
pub unsafe trait Trace {
unsafe fn root(&self, gc: &mut Gc);
unsafe fn unroot(&self, gc: &mut Gc);
unsafe fn root(&self);
unsafe fn unroot(&self);

fn trace(&self, gc: &mut Gc) {
let _ = gc;
Expand All @@ -502,18 +504,20 @@ pub unsafe trait Trace {
#[macro_export]
macro_rules! impl_trace {
($self_: tt, $gc: ident, $body: expr) => {
unsafe fn root(&$self_, $gc: &mut $crate::gc::Gc) {
unsafe fn root(&$self_) {
#[allow(unused)]
unsafe fn mark<T: ?Sized + Trace>(this: &T, gc: &mut $crate::gc::Gc) {
Trace::root(this, gc)
unsafe fn mark<T: ?Sized + Trace>(this: &T, _: ()) {
Trace::root(this)
}
let $gc = ();
$body
}
unsafe fn unroot(&$self_, $gc: &mut $crate::gc::Gc) {
unsafe fn unroot(&$self_) {
#[allow(unused)]
unsafe fn mark<T: ?Sized + Trace>(this: &T, gc: &mut $crate::gc::Gc) {
Trace::unroot(this, gc)
unsafe fn mark<T: ?Sized + Trace>(this: &T, _: ()) {
Trace::unroot(this)
}
let $gc = ();
$body
}
fn trace(&$self_, $gc: &mut $crate::gc::Gc) {
Expand Down Expand Up @@ -644,10 +648,10 @@ unsafe impl<T: ?Sized> Trace for GcPtr<T>
where
T: Trace,
{
unsafe fn root(&self, _gc: &mut Gc) {
unsafe fn root(&self) {
// Anything inside a `GcPtr` is implicitly rooted by the pointer itself being rooted
}
unsafe fn unroot(&self, _gc: &mut Gc) {
unsafe fn unroot(&self) {
// Anything inside a `GcPtr` is implicitly rooted by the pointer itself being rooted
}
fn trace(&self, gc: &mut Gc) {
Expand Down Expand Up @@ -825,7 +829,7 @@ impl Gc {
assert!(ret == p);
self.values = Some(ptr);
let ptr = GcPtr(NonNull::new_unchecked(p));
D::Value::unroot(&ptr, self);
D::Value::unroot(&ptr);
ptr
}
}
Expand Down
Loading

0 comments on commit d6e1246

Please sign in to comment.