From 9352220e835dbedb6eded40a23350927b7b70475 Mon Sep 17 00:00:00 2001
From: Techcable <Techcable@techcable.net>
Date: Fri, 2 Jul 2021 00:44:25 -0700
Subject: [PATCH 1/2] Use generic associated types for GcHandleSystem

Unfortunately there is a tiny issue with the implementation
so I'm not pushing this to master just yet.
---
 libs/context/src/collector.rs |  6 ++--
 libs/context/src/handle.rs    | 67 +++++++++++++++++++----------------
 libs/context/src/lib.rs       |  1 +
 src/lib.rs                    | 63 +++++++++++++++-----------------
 src/prelude.rs                |  2 --
 5 files changed, 68 insertions(+), 71 deletions(-)

diff --git a/libs/context/src/collector.rs b/libs/context/src/collector.rs
index ad4e301..4dd163c 100644
--- a/libs/context/src/collector.rs
+++ b/libs/context/src/collector.rs
@@ -342,10 +342,10 @@ impl<C: RawCollectorImpl> WeakCollectorRef<C> {
 pub unsafe trait RawSimpleAlloc: RawCollectorImpl {
     fn alloc<'gc, T: GcSafe + 'gc>(context: &'gc CollectorContext<Self>, value: T) -> Gc<'gc, T, CollectorId<Self>>;
 }
-unsafe impl<'gc, T, C> GcSimpleAlloc<'gc, T> for CollectorContext<C>
-    where T: GcSafe + 'gc, C: RawSimpleAlloc {
+unsafe impl<C> GcSimpleAlloc for CollectorContext<C>
+    where C: RawSimpleAlloc {
     #[inline]
-    fn alloc(&'gc self, value: T) -> Gc<'gc, T, Self::Id> {
+    fn alloc<'gc, T>(&'gc self, value: T) -> Gc<'gc, T, Self::Id> where T: GcSafe + 'gc, {
         C::alloc(self, value)
     }
 }
diff --git a/libs/context/src/handle.rs b/libs/context/src/handle.rs
index 77001eb..39e499a 100644
--- a/libs/context/src/handle.rs
+++ b/libs/context/src/handle.rs
@@ -8,7 +8,7 @@ use core::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering};
 use alloc::boxed::Box;
 use alloc::vec::Vec;
 
-use zerogc::{Trace, GcSafe, GcErase, GcRebrand, GcVisitor, NullTrace, TraceImmutable, GcHandleSystem, GcBindHandle};
+use zerogc::{Trace, GcSafe, GcErase, GcRebrand, GcVisitor, NullTrace, TraceImmutable, GcHandleSystem};
 use crate::{Gc, WeakCollectorRef, CollectorId, CollectorContext, CollectorRef, CollectionManager};
 use crate::collector::RawCollectorImpl;
 
@@ -417,30 +417,10 @@ unsafe impl<T: GcSafe, C: RawHandleImpl> ::zerogc::GcHandle<T> for GcHandle<T, C
     type System = CollectorRef<C>;
     type Id = CollectorId<C>;
 
-    fn use_critical<R>(&self, func: impl FnOnce(&T) -> R) -> R {
-        self.collector.ensure_valid(|collector| unsafe {
-            /*
-             * This should be sufficient to ensure
-             * the value won't be collected or relocated.
-             *
-             * Note that this is implemented using a read lock,
-             * so recursive calls will deadlock.
-             * This is preferable to using `recursive_read`,
-             * since that could starve writers (collectors).
-             */
-            C::Manager::prevent_collection(collector.as_ref(), || {
-                let value = self.inner.as_ref().value
-                    .load(Ordering::Acquire) as *mut T;
-                func(&*value)
-            })
-        })
-    }
-}
-unsafe impl<'new_gc, T, C> GcBindHandle<'new_gc, T> for GcHandle<T, C>
-    where T: GcSafe, T: GcRebrand<'new_gc, CollectorId<C>>,
-          T::Branded: GcSafe, C: RawHandleImpl {
     #[inline]
-    fn bind_to(&self, context: &'new_gc CollectorContext<C>) -> Gc<'new_gc, T::Branded, CollectorId<C>> {
+    fn bind_to<'new_gc>(&self, context: &'new_gc CollectorContext<C>) -> Gc<'new_gc, T::Branded, CollectorId<C>>
+        where T: GcRebrand<'new_gc, Self::Id>,
+              <T as GcRebrand<'new_gc, Self::Id>>::Branded: GcSafe {
         /*
          * We can safely assume the object will
          * be as valid as long as the context.
@@ -472,6 +452,24 @@ unsafe impl<'new_gc, T, C> GcBindHandle<'new_gc, T> for GcHandle<T, C>
         }
     }
 
+    fn use_critical<R>(&self, func: impl FnOnce(&T) -> R) -> R {
+        self.collector.ensure_valid(|collector| unsafe {
+            /*
+             * This should be sufficient to ensure
+             * the value won't be collected or relocated.
+             *
+             * Note that this is implemented using a read lock,
+             * so recursive calls will deadlock.
+             * This is preferable to using `recursive_read`,
+             * since that could starve writers (collectors).
+             */
+            C::Manager::prevent_collection(collector.as_ref(), || {
+                let value = self.inner.as_ref().value
+                    .load(Ordering::Acquire) as *mut T;
+                func(&*value)
+            })
+        })
+    }
 }
 unsafe impl<T: GcSafe, C: RawHandleImpl> Trace for GcHandle<T, C> {
     /// See docs on reachability
@@ -595,16 +593,23 @@ unsafe impl<T: GcSafe + Sync, C: RawHandleImpl + Sync> Send for GcHandle<T, C> {
 /// Requires that the collector is thread-safe.
 unsafe impl<T: GcSafe + Sync, C: RawHandleImpl + Sync> Sync for GcHandle<T, C> {}
 
+#[doc(hidden)]
+pub trait GcSafeErase<'a, Id: ::zerogc::CollectorId>: GcErase<'a, Id> + GcSafe
+    where <Self as GcErase<'a, Id>>::Erased: GcSafe {
+}
 /// We support handles
-unsafe impl<'gc, 'a, T, C> GcHandleSystem<'gc, 'a, T> for CollectorRef<C>
-    where C: RawHandleImpl,
-          T: GcSafe + 'gc,
-          T: GcErase<'a, CollectorId<C>>,
-          T::Erased: GcSafe {
-    type Handle = GcHandle<T::Erased, C>;
+unsafe impl<C> GcHandleSystem for CollectorRef<C>
+    where C: RawHandleImpl, {
+    type Handle<'a, T>
+        where T: GcSafe + ?Sized + GcErase<'a, CollectorId<C>>,
+              <T as GcErase<'a, CollectorId<C>>>::Erased: GcSafe
+            = GcHandle<<T as GcErase<'a, Self::Id>>::Erased, C>;
 
     #[inline]
-    fn create_handle(gc: Gc<'gc, T, CollectorId<C>>) -> Self::Handle {
+    fn create_handle<'gc, 'a, T>(gc: Gc<'gc, T, CollectorId<C>>) -> Self::Handle<'a, T>
+        where T: ?Sized + GcSafe + 'gc,
+              T: GcErase<'a, CollectorId<C>>,
+              T::Erased: GcSafe {
         unsafe {
             let collector = gc.collector_id();
             let value = gc.as_raw_ptr();
diff --git a/libs/context/src/lib.rs b/libs/context/src/lib.rs
index 2819d15..46be53b 100644
--- a/libs/context/src/lib.rs
+++ b/libs/context/src/lib.rs
@@ -2,6 +2,7 @@
     negative_impls, // !Send is much cleaner than `PhantomData<Rc>`
     untagged_unions, // I want to avoid ManuallyDrop in unions
     const_fn_trait_bound, // So generics + const fn are unstable, huh?
+    generic_associated_types, // GcHandle
 )]
 #![cfg_attr(not(feature = "std"), no_std)]
 //! The implementation of (GcContext)[`::zerogc::GcContext`] that is
diff --git a/src/lib.rs b/src/lib.rs
index 5a3792f..0c04be0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,6 @@
 #![feature(
     const_panic, // RFC 2345 - Const asserts
+    generic_associated_types, // Needed for GcHandleSystem
 )]
 #![deny(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
@@ -208,12 +209,11 @@ pub unsafe trait GcSystem {
 ///
 /// This type-system hackery is needed because
 /// we need to place bounds on `T as GcBrand`
-// TODO: Remove when we get more powerful types
-pub unsafe trait GcHandleSystem<'gc, 'a, T: GcSafe + ?Sized + 'gc>: GcSystem
-    where T: GcErase<'a, Self::Id>,
-          <T as GcErase<'a, Self::Id>>::Erased: GcSafe {
+pub unsafe trait GcHandleSystem: GcSystem {
     /// The type of handles to this object.
-    type Handle: GcHandle<<T as GcErase<'a, Self::Id>>::Erased, System=Self>;
+    type Handle<'a, T>: GcHandle<<T as GcErase<'a, Self::Id>>::Erased, System=Self>
+        where T: ?Sized + GcSafe + GcErase<'a, Self::Id>,
+                <T as GcErase<'a, Self::Id>>::Erased: GcSafe;
 
     /// Create a handle to the specified GC pointer,
     /// which can be used without a context
@@ -222,7 +222,9 @@ pub unsafe trait GcHandleSystem<'gc, 'a, T: GcSafe + ?Sized + 'gc>: GcSystem
     ///
     /// The system is implicit in the [Gc]
     #[doc(hidden)]
-    fn create_handle(gc: Gc<'gc, T, Self::Id>) -> Self::Handle;
+    fn create_handle<'gc, 'a, T>(gc: Gc<'gc, T, Self::Id>) -> Self::Handle<'a, T>
+        where T: ?Sized + GcSafe + GcErase<'a, Self::Id>,
+             <T as GcErase<'a, Self::Id>>::Erased: GcSafe;
 }
 
 /// The context of garbage collection,
@@ -321,7 +323,7 @@ pub unsafe trait GcContext: Sized {
 ///
 /// Some garbage collectors implement more complex interfaces,
 /// so implementing this is optional
-pub unsafe trait GcSimpleAlloc<'gc, T: GcSafe + 'gc>: GcContext + 'gc {
+pub unsafe trait GcSimpleAlloc: GcContext {
     /// Allocate the specified object in this garbage collector,
     /// binding it to the lifetime of this collector.
     ///
@@ -334,7 +336,7 @@ pub unsafe trait GcSimpleAlloc<'gc, T: GcSafe + 'gc>: GcContext + 'gc {
     ///
     /// This gives a immutable reference to the resulting object.
     /// Once allocated, the object can only be correctly modified with a `GcCell`
-    fn alloc(&'gc self, value: T) -> Gc<'gc, T, Self::Id>;
+    fn alloc<'gc, T: GcSafe + 'gc>(&'gc self, value: T) -> Gc<'gc, T, Self::Id>;
 }
 /// The internal representation of a frozen context
 ///
@@ -468,11 +470,11 @@ impl<'gc, T: GcSafe + ?Sized + 'gc, Id: CollectorId> Gc<'gc, T, Id> {
 
     /// Create a handle to this object, which can be used without a context
     #[inline]
-    pub fn create_handle<'a>(&self) -> <Id::System as GcHandleSystem<'gc, 'a, T>>::Handle
-        where Id::System: GcHandleSystem<'gc, 'a, T>,
+    pub fn create_handle<'a>(&self) -> <Id::System as GcHandleSystem>::Handle<'a, T>
+        where Id::System: GcHandleSystem,
               T: GcErase<'a, Id> + 'a,
               <T as GcErase<'a, Id>>::Erased: GcSafe + 'a {
-        <Id::System as GcHandleSystem<'gc, 'a, T>>::create_handle(*self)
+        <Id::System as GcHandleSystem>::create_handle(*self)
     }
 
     /// Get a reference to the system
@@ -628,6 +630,21 @@ pub unsafe trait GcHandle<T: GcSafe + ?Sized>: Clone + NullTrace {
     /// The type of [CollectorId] used with this sytem
     type Id: CollectorId;
 
+    /// Associate this handle with the specified context,
+    /// allowing its underlying object to be accessed
+    /// as long as the context is valid.
+    ///
+    /// The underlying object can be accessed just like any
+    /// other object that would be allocated from the context.
+    /// It'll be properly collected and can even be used as a root
+    /// at the next safepoint.
+    fn bind_to<'new_gc>(&self, context: &'new_gc <Self::System as GcSystem>::Context) -> Gc<
+        'new_gc,
+        <T as GcRebrand<'new_gc, Self::Id>>::Branded,
+        Self::Id
+    > where T: GcRebrand<'new_gc, Self::Id>,
+            <T as GcRebrand<'new_gc, Self::Id>>::Branded: GcSafe;
+
     /// Access this handle inside the closure,
     /// possibly associating it with the specified
     ///
@@ -646,30 +663,6 @@ pub unsafe trait GcHandle<T: GcSafe + ?Sized>: Clone + NullTrace {
      */
     fn use_critical<R>(&self, func: impl FnOnce(&T) -> R) -> R;
 }
-/// Trait for binding [GcHandle]s to contexts
-/// using [GcBindHandle::bind_to]
-///
-/// This is separate from the [GcHandle] trait
-/// because Rust doesn't have Generic Associated Types
-///
-/// TODO: Remove when we get more powerful types
-pub unsafe trait GcBindHandle<'new_gc, T: GcSafe + ?Sized>: GcHandle<T>
-    where T: GcRebrand<'new_gc, Self::Id>,
-          <T as GcRebrand<'new_gc, Self::Id>>::Branded: GcSafe {
-    /// Associate this handle with the specified context,
-    /// allowing its underlying object to be accessed
-    /// as long as the context is valid.
-    ///
-    /// The underlying object can be accessed just like any
-    /// other object that would be allocated from the context.
-    /// It'll be properly collected and can even be used as a root
-    /// at the next safepoint.
-    fn bind_to(&self, context: &'new_gc <Self::System as GcSystem>::Context) -> Gc<
-        'new_gc,
-        <T as GcRebrand<'new_gc, Self::Id>>::Branded,
-        Self::Id
-    >;
-}
 
 /// Safely trigger a write barrier before
 /// writing to a garbage collected value.
diff --git a/src/prelude.rs b/src/prelude.rs
index ce8c861..c730c9d 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -21,8 +21,6 @@ pub use crate::{
 pub use crate::{
     GcSafe, GcErase, GcRebrand, Trace, TraceImmutable, NullTrace
 };
-// Hack traits
-pub use crate::{GcBindHandle};
 // TODO: Should this trait be auto-imported???
 pub use crate::CollectorId;
 // Utils

From 09f43470358b31433d1430b8b929caeb3ef88a61 Mon Sep 17 00:00:00 2001
From: Techcable <Techcable@techcable.net>
Date: Fri, 2 Jul 2021 00:46:07 -0700
Subject: [PATCH 2/2] Begin to make tests generic over collectors

I plan to maybe add the tests from the JikesRVM collector....
https://github.com/JikesRVM/JikesRVM/tree/master/MMTk/harness/test-scripts

Also there's a possibility of implmenting a small scheme
too (I think that would also be a good test for DuckASM)
---
 Cargo.toml                                    |  2 +-
 libs/test/Cargo.toml                          | 46 ++++++++++++
 .../src}/examples/binary_trees.rs             | 48 ++++++-------
 .../src}/examples/binary_trees_parallel.rs    | 35 ++++-----
 libs/test/src/examples/mod.rs                 | 71 +++++++++++++++++++
 libs/test/src/lib.rs                          | 24 +++++++
 libs/test/src/main.rs                         | 64 +++++++++++++++++
 7 files changed, 242 insertions(+), 48 deletions(-)
 create mode 100644 libs/test/Cargo.toml
 rename libs/{simple => test/src}/examples/binary_trees.rs (58%)
 rename libs/{simple => test/src}/examples/binary_trees_parallel.rs (69%)
 create mode 100644 libs/test/src/examples/mod.rs
 create mode 100644 libs/test/src/lib.rs
 create mode 100644 libs/test/src/main.rs

diff --git a/Cargo.toml b/Cargo.toml
index 369f44b..9c6ed41 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ indexmap = { version = "1.6", optional = true }
 zerogc-derive = { path = "libs/derive", version = "0.2.0-alpha.3" }
 
 [workspace]
-members = ["libs/simple", "libs/derive", "libs/context"]
+members = ["libs/simple", "libs/derive", "libs/context", "libs/test"]
 
 [profile.dev]
 opt-level = 1
diff --git a/libs/test/Cargo.toml b/libs/test/Cargo.toml
new file mode 100644
index 0000000..33a8892
--- /dev/null
+++ b/libs/test/Cargo.toml
@@ -0,0 +1,46 @@
+[package]
+name = "zerogc-test"
+description = "The custom test harness for zerogc collectors"
+version = "0.2.0-alpha.3"
+authors = ["Techcable <Techcable@techcable.net>"]
+repository = "https://github.com/DuckLogic/zerogc"
+readme = "../../README.md"
+license = "MIT"
+edition = "2018"
+# For internal use only
+publish = false
+
+[[bin]]
+name = "zerogc-test"
+main = "src/main.rs"
+
+[dependencies]
+zerogc = { path = "../..", version = "0.2.0-alpha.3" }
+zerogc-derive = { path = "../derive", version = "0.2.0-alpha.3"}
+# Concurrency
+parking_lot = "0.11"
+crossbeam-utils = "0.8"
+# Logging
+slog = "2.7"
+slog-term = "2.6"
+# Error handling
+anyhow = "1"
+# Used for binary_trees_parallel examle
+rayon = "1.3"
+# CLI
+clap = { version = "3.0.0-beta.2", optional = true }
+
+[dependencies.zerogc-simple]
+path = "../simple"
+version = "0.2.0-alpha.3"
+no-default-features = true
+optional = true
+
+[features]
+default = ["simple", "cli"]
+# Enable the simple collector,
+simple = ["zerogc-simple", "zerogc-simple/sync", "zerogc-simple/small-object-arenas"]
+# Enable multiple collectors (for the simple collector)
+simple-multiple = ["zerogc-simple/multiple-collectors"]
+# Enable features needed for the CLI
+cli = ["clap"]
diff --git a/libs/simple/examples/binary_trees.rs b/libs/test/src/examples/binary_trees.rs
similarity index 58%
rename from libs/simple/examples/binary_trees.rs
rename to libs/test/src/examples/binary_trees.rs
index a04b5a8..e0ec3e4 100644
--- a/libs/simple/examples/binary_trees.rs
+++ b/libs/test/src/examples/binary_trees.rs
@@ -1,20 +1,18 @@
-#![feature(
-    arbitrary_self_types, // Unfortunately this is required for methods on Gc refs
-)]
 use zerogc::prelude::*;
-use zerogc_simple::{SimpleCollector, SimpleCollectorContext, Gc, CollectorId as SimpleCollectorId};
+use zerogc::{Gc, CollectorId};
 use zerogc_derive::Trace;
 
-use slog::{Logger, Drain, o};
+use slog::{Logger, Drain,};
+use crate::{GcTest, TestContext};
 
 #[derive(Trace)]
-#[zerogc(collector_id(SimpleCollectorId))]
-struct Tree<'gc> {
+#[zerogc(collector_id(Id))]
+struct Tree<'gc, Id: CollectorId> {
     #[zerogc(mutable(public))]
-    children: GcCell<Option<(Gc<'gc, Tree<'gc>>, Gc<'gc, Tree<'gc>>)>>,
+    children: GcCell<Option<(Gc<'gc, Tree<'gc, Id>, Id>, Gc<'gc, Tree<'gc, Id>, Id>)>>,
 }
 
-fn item_check(tree: &Tree) -> i32 {
+fn item_check<'gc, Id: CollectorId>(tree: &Tree<'gc, Id>) -> u32 {
     if let Some((left, right)) = tree.children.get() {
         1 + item_check(&right) + item_check(&left)
     } else {
@@ -22,8 +20,7 @@ fn item_check(tree: &Tree) -> i32 {
     }
 }
 
-fn bottom_up_tree<'gc>(collector: &'gc SimpleCollectorContext, depth: i32)
-                       -> Gc<'gc, Tree<'gc>> {
+fn bottom_up_tree<'gc, Ctx: TestContext>(collector: &'gc Ctx, depth: u32) -> Gc<'gc, Tree<'gc, Ctx::Id>, Ctx::Id> {
     let tree = collector.alloc(Tree { children: GcCell::new(None) });
     if depth > 0 {
         let right = bottom_up_tree(collector, depth - 1);
@@ -33,33 +30,29 @@ fn bottom_up_tree<'gc>(collector: &'gc SimpleCollectorContext, depth: i32)
     tree
 }
 
-fn inner(
-    gc: &mut SimpleCollectorContext,
-    depth: i32, iterations: u32
+fn inner<Ctx: TestContext>(
+    gc: &mut Ctx,
+    depth: u32, iterations: u32
 ) -> String {
-    let chk: i32 = (0 .. iterations).into_iter().map(|_| {
+    let chk: u32 = (0 .. iterations).into_iter().map(|_| {
         safepoint_recurse!(gc, |gc| {
-            let a = bottom_up_tree(&gc, depth);
+            let a = bottom_up_tree(&*gc, depth);
             item_check(&a)
         })
     }).sum();
     format!("{}\t trees of depth {}\t check: {}", iterations, depth, chk)
 }
 
-fn main() {
-    let n = std::env::args().nth(1)
-        .and_then(|n| n.parse().ok())
+pub const EXAMPLE_NAME: &str = file!();
+pub fn main<Ctx: GcTest>(logger: &Logger, gc: Ctx, args: &[&str]) -> anyhow::Result<()> {
+    let n = args.get(0)
+        .map(|arg| arg.parse::<u32>().unwrap())
         .unwrap_or(10);
+    assert!(n >= 0);
     let min_depth = 4;
     let max_depth = if min_depth + 2 > n { min_depth + 2 } else { n };
 
-    let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
-    let logger = Logger::root(
-        slog_term::FullFormat::new(plain).build().fuse(),
-        o!("bench" => file!())
-    );
-    let collector = SimpleCollector::with_logger(logger);
-    let mut gc = collector.into_context();
+    let mut gc = gc.into_context();
     {
         let depth = max_depth + 1;
         let tree = bottom_up_tree(&gc, depth);
@@ -74,11 +67,12 @@ fn main() {
             let depth = half_depth * 2;
             let iterations = 1 << ((max_depth - depth + min_depth) as u32);
             let message = safepoint_recurse!(gc, |new_gc| {
-                inner(&mut new_gc, depth, iterations)
+                inner(&mut *new_gc, depth, iterations)
             });
             println!("{}", message);
         })
     });
 
     println!("long lived tree of depth {}\t check: {}", max_depth, item_check(&long_lived_tree));
+    Ok(())
 }
\ No newline at end of file
diff --git a/libs/simple/examples/binary_trees_parallel.rs b/libs/test/src/examples/binary_trees_parallel.rs
similarity index 69%
rename from libs/simple/examples/binary_trees_parallel.rs
rename to libs/test/src/examples/binary_trees_parallel.rs
index efe5bc7..2bf8377 100644
--- a/libs/simple/examples/binary_trees_parallel.rs
+++ b/libs/test/src/examples/binary_trees_parallel.rs
@@ -2,20 +2,21 @@
     arbitrary_self_types, // Unfortunately this is required for methods on Gc refs
 )]
 use zerogc::prelude::*;
-use zerogc_simple::{SimpleCollector, SimpleCollectorContext, Gc, CollectorId as SimpleCollectorId};
 use zerogc_derive::Trace;
 
 use rayon::prelude::*;
-use slog::{Logger, Drain, o};
+use slog::{Logger, Drain};
+use crate::{GcTest, GcSyncTest, TestContext};
+use zerogc::GcHandleSystem;
 
 #[derive(Trace)]
-#[zerogc(collector_id(SimpleCollectorId))]
-struct Tree<'gc> {
+#[zerogc(collector_id(Id))]
+pub(crate) struct Tree<'gc, Id: CollectorId> {
     #[zerogc(mutable(public))]
-    children: GcCell<Option<(Gc<'gc, Tree<'gc>>, Gc<'gc, Tree<'gc>>)>>,
+    children: GcCell<Option<(Gc<'gc, Tree<'gc, Id>, Id>, Gc<'gc, Tree<'gc, Id>, Id>)>>,
 }
 
-fn item_check(tree: &Tree) -> i32 {
+fn item_check<Id: CollectorId>(tree: &Tree<Id>) -> i32 {
     if let Some((left, right)) = tree.children.get() {
         1 + item_check(&right) + item_check(&left)
     } else {
@@ -23,8 +24,7 @@ fn item_check(tree: &Tree) -> i32 {
     }
 }
 
-fn bottom_up_tree<'gc>(collector: &'gc SimpleCollectorContext, depth: i32)
-                       -> Gc<'gc, Tree<'gc>> {
+fn bottom_up_tree<'gc, Ctx: TestContext>(collector: &'gc Ctx, depth: i32) -> Gc<'gc, Tree<'gc, Ctx::Id>, Ctx::Id> {
     let tree = collector.alloc(Tree { children: GcCell::new(None) });
     if depth > 0 {
         let right = bottom_up_tree(collector, depth - 1);
@@ -34,33 +34,28 @@ fn bottom_up_tree<'gc>(collector: &'gc SimpleCollectorContext, depth: i32)
     tree
 }
 
-fn inner(
-    collector: &SimpleCollector,
+fn inner<GC: GcSyncTest>(
+    collector: &GC,
     depth: i32, iterations: u32
 ) -> String {
     let chk: i32 = (0 .. iterations).into_par_iter().map(|_| {
         let mut gc = collector.create_context();
         safepoint_recurse!(gc, |gc| {
-            let a = bottom_up_tree(&gc, depth);
+            let a = bottom_up_tree(&*gc, depth);
             item_check(&a)
         })
     }).sum();
     format!("{}\t trees of depth {}\t check: {}", iterations, depth, chk)
 }
 
-fn main() {
-    let n = std::env::args().nth(1)
+pub const EXAMPLE_NAME: &str = file!();
+pub fn main<GC: GcSyncTest>(logger: &Logger, collector: GC, args: &[&str]) -> anyhow::Result<()> {
+    let n = args.get(0)
         .and_then(|n| n.parse().ok())
         .unwrap_or(10);
     let min_depth = 4;
     let max_depth = if min_depth + 2 > n { min_depth + 2 } else { n };
 
-    let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
-    let logger = Logger::root(
-        slog_term::FullFormat::new(plain).build().fuse(),
-        o!("bench" => file!())
-    );
-    let collector = SimpleCollector::with_logger(logger);
     let mut gc = collector.create_context();
     {
         let depth = max_depth + 1;
@@ -70,7 +65,7 @@ fn main() {
     safepoint!(gc, ());
 
     let long_lived_tree = bottom_up_tree(&gc, max_depth);
-    let long_lived_tree = long_lived_tree.create_handle();
+    let long_lived_tree = GC::HandleSystem::<'_, '_, _>::create_handle(long_lived_tree);
     let frozen = freeze_context!(gc);
 
     (min_depth / 2..max_depth / 2 + 1).into_par_iter().for_each(|half_depth| {
diff --git a/libs/test/src/examples/mod.rs b/libs/test/src/examples/mod.rs
new file mode 100644
index 0000000..3b33f2c
--- /dev/null
+++ b/libs/test/src/examples/mod.rs
@@ -0,0 +1,71 @@
+use zerogc::{GcContext, Gc};
+use slog::Logger;
+use crate::{GcTest, GcSyncTest};
+use slog::Drain;
+
+mod binary_trees;
+pub(crate) mod binary_trees_parallel;
+
+
+macro_rules! define_example {
+    (def @kind sync fn $name:ident($args:ident) $body:expr) => {
+        fn $name<GC: GcSyncTest>($args: &[&str]) -> anyhow::Result<()> {
+            $body
+        }
+    };
+    (def @kind nosync fn $name:ident($args:ident) $body:expr) => {
+        fn $name<GC: GcTest>($args: &[&str]) -> anyhow::Result<()> {
+            $body
+        }
+    };
+    ($name:ident, $target_module:ident, $kind:ident) => {
+        define_example!(def @kind $kind fn $name(args) {
+            let plain = slog_term::PlainSyncDecorator::new(std::io::stdout());
+            let logger = Logger::root(
+                slog_term::FullFormat::new(plain).build().fuse(),
+                ::slog::o!("bench" => file!())
+            );
+            let collector = GC::create_collector(logger);
+            self::$target_module::main(&logger, collector, args)
+        });
+    };
+}
+define_example!(binary_trees, binary_trees, nosync);
+define_example!(binary_trees_parallel, binary_trees_parallel, sync);
+
+#[derive(Copy, Clone)]
+pub enum ExampleKind {
+    BinaryTrees,
+    BinaryTreesParallel
+}
+#[derive(Default)]
+pub struct ExampleArgs {
+    binary_trees_size: Option<u32>,
+}
+
+macro_rules! run_example {
+    ($name:ident, $gc:ident, $($arg:expr),*) => {{
+        eprintln!(concat!("Running ", stringify!($name), ":"));
+        let owned_args = vec![$(Option::map($arg, |item| item.to_string())),*];
+        while let Some(None) = owned_args.last() {
+            assert_eq!(owned_args.pop(), None);
+        }
+        let args = owned_args.iter().enumerate().map(|(index, opt)| opt.as_ref().unwrap_or_else(|| {
+            panic!("Arg #{} is None: {:?}", index, owned_args)
+        }).as_str()).collect::<Vec<_>>();
+        use anyhow::Context;
+        let () = ($name::<$gc>)(&*args).with_context(|| format!("Running example {:?}", stringify!($name)))?;
+    }};
+}
+/// Run all the example for the specified (non-Sync) collector
+pub fn run_examples_non_sync<GC: GcTest>(args: ExampleArgs) -> anyhow::Result<()> {
+    run_example!(binary_trees, GC, args.binary_trees_size);
+    Ok(())
+}
+/// Run the examples for the specified Sync collector,
+/// including all the non-sync examples;
+pub fn run_examples_sync<GC: GcSyncTest>(args: ExampleArgs) -> anyhow::Result<()> {
+    run_examples_non_sync::<GC>(args);
+    run_example!(binary_trees_parallel, GC, args.binary_trees_size);
+    Ok(())
+}
diff --git a/libs/test/src/lib.rs b/libs/test/src/lib.rs
new file mode 100644
index 0000000..1ca751f
--- /dev/null
+++ b/libs/test/src/lib.rs
@@ -0,0 +1,24 @@
+#![feature(
+    arbitrary_self_types, // Unfortunately this is required for methods on Gc refs
+    generic_associated_types, // We need more abstraction
+)]
+use zerogc::{
+    GcContext, GcSystem, GcSimpleAlloc, GcSafe, CollectorId, GcHandleSystem, GcErase,
+    GcRebrand, GcHandle, GcBindHandle
+};
+use slog::Logger;
+
+pub mod examples;
+
+pub trait TestContext: GcContext + GcSimpleAlloc {
+}
+
+pub trait GcTest: GcSystem + GcHandleSystem {
+    type Ctx: TestContext<Id=Self::Id>;
+    fn create_collector(logger: Logger) -> Self;
+    fn into_context(self) -> Self::Ctx;
+}
+pub trait GcSyncTest: Sync + GcTest {
+    fn create_context(&self) -> Self::Ctx;
+}
+
diff --git a/libs/test/src/main.rs b/libs/test/src/main.rs
new file mode 100644
index 0000000..7958987
--- /dev/null
+++ b/libs/test/src/main.rs
@@ -0,0 +1,64 @@
+use clap::Clap;
+
+/// Tests garbage collectors
+#[derive(Clap, Debug)]
+#[clap(author, version)]
+pub struct GcTestOpts {
+    #[clap(arg_enum, default_value = "GcType::all()")]
+    #[clap(subcommand)]
+    subcommand: Subcommand
+}
+
+/// The type of garbage collector
+#[derive(Clap, Debug)]
+enum GcType {
+    Simple
+}
+impl GcType {
+    fn all() -> Vec<GcType> {
+        vec![GcType::Simple]
+    }
+    fn is_supported(&self) -> bool {
+        match *self {
+            GcType::Simple => cfg!(feature = "simple")
+        }
+    }
+    fn is_sync(&self) -> bool {
+        match *self {
+            GcType::Simple =>
+        }
+    }
+}
+
+#[derive(Clap)]
+pub enum Subcommand {
+    Example(ExampleOpts)
+}
+
+/// Run the examples
+#[derive(Clap)]
+pub struct ExampleOpts {
+    /// The size of the binary trees
+    #[clap(long)]
+    binary_trees_size: Option<u32>,
+    /// The list of examples to name,
+    /// defaults to all of them
+    #[clap(default_value = "ExampleKind::all()")]
+    #[clap(arg_enum)]
+    examples: Vec<ExampleKind>
+}
+#[derive(Clap, Debug, PartialEq)]
+pub enum ExampleKind {
+    BinaryTrees,
+    BinaryTreesParallel
+}
+impl ExampleKind {
+    fn all() -> Vec<ExampleKind>
+}
+
+fn main() -> anyhow::Result<()> {
+    let opts = GcTestOpts::parse();
+    match opts {
+
+    }
+}
\ No newline at end of file