use jrsonnet_gc::{force_collect, Finalize, Gc, GcCell, Trace};
use jrsonnet_gc_derive::{Finalize, Trace};
use std::cell::Cell;
use std::thread::LocalKey;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
struct GcWatchFlags {
trace: i32,
root: i32,
unroot: i32,
drop: i32,
finalize: i32,
}
impl GcWatchFlags {
fn new(trace: i32, root: i32, unroot: i32, drop: i32, finalize: i32) -> GcWatchFlags {
GcWatchFlags {
trace,
root,
unroot,
drop,
finalize,
}
}
fn zero() -> Cell<GcWatchFlags> {
Cell::new(GcWatchFlags {
trace: 0,
root: 0,
unroot: 0,
drop: 0,
finalize: 0,
})
}
}
struct GcWatch(&'static LocalKey<Cell<GcWatchFlags>>);
impl Drop for GcWatch {
fn drop(&mut self) {
self.0.with(|f| {
let mut of = f.get();
of.drop += 1;
f.set(of);
});
}
}
impl Finalize for GcWatch {
fn finalize(&self) {
self.0.with(|f| {
let mut of = f.get();
of.finalize += 1;
f.set(of);
});
}
}
unsafe impl Trace for GcWatch {
unsafe fn trace(&self) {
self.0.with(|f| {
let mut of = f.get();
of.trace += 1;
f.set(of);
});
}
unsafe fn root(&self) {
self.0.with(|f| {
let mut of = f.get();
of.root += 1;
f.set(of);
});
}
unsafe fn unroot(&self) {
self.0.with(|f| {
let mut of = f.get();
of.unroot += 1;
f.set(of);
});
}
fn finalize_glue(&self) {
Finalize::finalize(self);
}
}
#[derive(Trace, Finalize)]
struct GcWatchCycle {
watch: GcWatch,
cycle: GcCell<Option<Gc<GcWatchCycle>>>,
}
#[test]
fn basic_allocate() {
thread_local!(static FLAGS: Cell<GcWatchFlags> = GcWatchFlags::zero());
{
let _gced_val = Gc::new(GcWatch(&FLAGS));
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 1, 0, 0)));
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
}
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 1, 1)));
}
#[test]
fn basic_cycle_allocate() {
thread_local!(static FLAGS1: Cell<GcWatchFlags> = GcWatchFlags::zero());
thread_local!(static FLAGS2: Cell<GcWatchFlags> = GcWatchFlags::zero());
{
let node1 = Gc::new(GcWatchCycle {
watch: GcWatch(&FLAGS1),
cycle: GcCell::new(None),
});
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 1, 0, 0)));
let node2 = Gc::new(GcWatchCycle {
watch: GcWatch(&FLAGS2),
cycle: GcCell::new(Some(node1.clone())),
});
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 1, 0, 0)));
force_collect();
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
{
*node1.cycle.borrow_mut() = Some(node2);
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
force_collect();
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 0, 1, 0, 0)));
}
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 0, 1, 0, 0)));
force_collect();
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 0, 1, 0, 0)));
}
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 0, 1, 0, 0)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 0, 1, 0, 0)));
force_collect();
FLAGS1.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 0, 1, 1, 1)));
FLAGS2.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 0, 1, 1, 1)));
}
#[test]
fn gccell_rooting() {
thread_local!(static FLAGS: Cell<GcWatchFlags> = GcWatchFlags::zero());
{
let cell = GcCell::new(GcWatch(&FLAGS));
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 0, 0, 0)));
{
let _borrowed = cell.borrow();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 0, 0, 0)));
let _borrowed2 = cell.borrow();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 0, 0, 0)));
}
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 0, 0, 0)));
{
let _borrowed = cell.borrow_mut();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 0, 0, 0)));
}
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 0, 0, 0)));
let gc_wrapper = Gc::new(cell);
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(0, 0, 1, 0, 0)));
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
{
let _borrowed = gc_wrapper.borrow();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
let _borrowed2 = gc_wrapper.borrow();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(1, 0, 1, 0, 0)));
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 0, 1, 0, 0)));
}
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 0, 1, 0, 0)));
{
let _borrowed = gc_wrapper.borrow_mut();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 1, 1, 0, 0)));
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 1, 1, 0, 0)));
}
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(2, 1, 2, 0, 0)));
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 1, 2, 0, 0)));
}
force_collect();
FLAGS.with(|f| assert_eq!(f.get(), GcWatchFlags::new(3, 1, 2, 1, 1)));
}
#[cfg(feature = "nightly")]
#[test]
fn trait_gc() {
#[derive(Finalize, Trace)]
struct Bar;
trait Foo: Trace {
fn f(&self) -> i32;
}
impl Foo for Bar {
fn f(&self) -> i32 {
10
}
}
fn use_trait_gc(x: Gc<dyn Foo>) {
assert_eq!(x.f(), 10);
}
let gc_bar = Gc::new(Bar);
let gc_foo: Gc<dyn Foo> = gc_bar.clone();
use_trait_gc(gc_foo);
use_trait_gc(gc_bar);
}
#[test]
fn ptr_eq() {
#[derive(Finalize, Trace)]
struct A;
#[derive(Finalize, Trace)]
struct B(Gc<A>);
let a = Gc::new(A);
let aa = a.clone();
assert!(Gc::ptr_eq(&a, &aa));
let b = Gc::new(B(aa));
assert!(Gc::ptr_eq(&a, &b.0));
let bb = Gc::new(B(a.clone()));
assert!(Gc::ptr_eq(&b.0, &bb.0));
let a2 = Gc::new(A);
assert!(!Gc::ptr_eq(&a, &a2));
let b2 = Gc::new(B(a2.clone()));
assert!(Gc::ptr_eq(&a2, &b2.0));
assert!(!Gc::ptr_eq(&a, &b2.0));
assert!(!Gc::ptr_eq(&b.0, &b2.0));
assert!(!Gc::ptr_eq(&b.0, &a2));
}