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