Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make tests generic over any GcSystem #28

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions libs/context/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
67 changes: 36 additions & 31 deletions libs/context/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions libs/context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions libs/test/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "zerogc-test"
description = "The custom test harness for zerogc collectors"
version = "0.2.0-alpha.3"
authors = ["Techcable <[email protected]>"]
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"]
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
#![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 {
1
}
}

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);
Expand All @@ -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);
Expand All @@ -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(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@
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 {
1
}
}

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);
Expand All @@ -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;
Expand All @@ -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| {
Expand Down
Loading