Skip to content

Commit 259412a

Browse files
committed
implement null collector for lifetime branded GC
1 parent e5d5712 commit 259412a

11 files changed

Lines changed: 688 additions & 1 deletion

File tree

oscars/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ mark_sweep = []
3737
mark_sweep2 = ["mark_sweep"]
3838
mark_sweep_branded = ["mark_sweep"]
3939
null_collector = ["mark_sweep"]
40+
null_collector_branded = ["mark_sweep"]
4041
thin-vec = ["dep:thin-vec", "mark_sweep"]

oscars/src/collectors/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub mod mark_sweep_arena2;
88
#[cfg(feature = "null_collector")]
99
pub mod null_collector;
1010

11-
// TODO: Implement a null collector for the branded API as well
11+
#[cfg(feature = "null_collector_branded")]
12+
pub mod null_collector_branded;
13+
1214
#[cfg(feature = "mark_sweep_branded")]
1315
pub mod mark_sweep_branded;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::collectors::null_collector_branded::trace::{Finalize, Trace, Tracer};
2+
use core::cell::{Ref, RefCell, RefMut};
3+
use core::ops::{Deref, DerefMut};
4+
5+
/// GC aware wrapper around [`RefCell<T>`]
6+
pub struct GcRefCell<T: Trace> {
7+
inner: RefCell<T>,
8+
}
9+
10+
impl<T: Trace> GcRefCell<T> {
11+
pub fn new(value: T) -> Self {
12+
Self {
13+
inner: RefCell::new(value),
14+
}
15+
}
16+
17+
/// Acquires a shared borrow of the inner value.
18+
///
19+
/// # Panics
20+
///
21+
/// Panics if the value is currently mutably borrowed.
22+
pub fn borrow(&self) -> GcRef<'_, T> {
23+
GcRef(self.inner.borrow())
24+
}
25+
26+
/// Acquires a mutable borrow of the inner value.
27+
///
28+
/// # Panics
29+
///
30+
/// Panics if the value is currently borrowed.
31+
pub fn borrow_mut(&self) -> GcRefMut<'_, T> {
32+
GcRefMut(self.inner.borrow_mut())
33+
}
34+
}
35+
36+
/// Shared borrow guard returned by [`GcRefCell::borrow`]
37+
pub struct GcRef<'a, T: Trace>(Ref<'a, T>);
38+
39+
impl<T: Trace> Deref for GcRef<'_, T> {
40+
type Target = T;
41+
fn deref(&self) -> &T {
42+
&self.0
43+
}
44+
}
45+
46+
/// A mutable borrow guard returned by [`GcRefCell::borrow_mut`]
47+
pub struct GcRefMut<'a, T: Trace>(RefMut<'a, T>);
48+
49+
impl<T: Trace> Deref for GcRefMut<'_, T> {
50+
type Target = T;
51+
fn deref(&self) -> &T {
52+
&self.0
53+
}
54+
}
55+
56+
impl<T: Trace> DerefMut for GcRefMut<'_, T> {
57+
fn deref_mut(&mut self) -> &mut T {
58+
&mut self.0
59+
}
60+
}
61+
62+
impl<T: Trace> Finalize for GcRefCell<T> {}
63+
64+
impl<T: Trace> Trace for GcRefCell<T> {
65+
fn trace(&mut self, tracer: &mut Tracer) {
66+
self.inner.get_mut().trace(tracer);
67+
}
68+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use crate::{
2+
alloc::mempool3::PoolPointer,
3+
collectors::null_collector_branded::{
4+
gc::Gc,
5+
gc_box::GcBox,
6+
mutation_ctx::MutationContext,
7+
trace::{Finalize, Trace, Tracer},
8+
},
9+
};
10+
use core::marker::PhantomData;
11+
12+
pub struct Ephemeron<'id, K: Trace, V: Trace> {
13+
pub(crate) key_ptr: Option<PoolPointer<'static, GcBox<K>>>,
14+
pub(crate) value_ptr: PoolPointer<'static, GcBox<V>>,
15+
pub(crate) _marker: PhantomData<*mut &'id ()>,
16+
}
17+
18+
impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> {
19+
/// Returns the value if the key is alive.
20+
pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option<Gc<'gc, V>> {
21+
// In the null collector, everything stays alive until context drops.
22+
if self.key_ptr.is_some() {
23+
Some(Gc {
24+
ptr: self.value_ptr,
25+
_marker: PhantomData,
26+
})
27+
} else {
28+
None
29+
}
30+
}
31+
}
32+
33+
impl<'id, K: Trace, V: Trace> Clone for Ephemeron<'id, K, V> {
34+
fn clone(&self) -> Self {
35+
*self
36+
}
37+
}
38+
39+
impl<'id, K: Trace, V: Trace> Copy for Ephemeron<'id, K, V> {}
40+
41+
impl<'id, K: Trace, V: Trace> Finalize for Ephemeron<'id, K, V> {}
42+
43+
impl<'id, K: Trace, V: Trace> Trace for Ephemeron<'id, K, V> {
44+
fn trace(&mut self, _tracer: &mut Tracer) {}
45+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//! Core pointer types.
2+
3+
use crate::{
4+
alloc::mempool3::PoolPointer,
5+
collectors::null_collector_branded::{
6+
gc_box::GcBox,
7+
trace::{Finalize, Trace},
8+
},
9+
};
10+
use core::fmt;
11+
use core::marker::PhantomData;
12+
use core::ops::Deref;
13+
14+
/// Transient pointer to a GC managed value.
15+
#[derive(Debug)]
16+
pub struct Gc<'gc, T: Trace + ?Sized + 'gc> {
17+
pub(crate) ptr: PoolPointer<'static, GcBox<T>>,
18+
pub(crate) _marker: PhantomData<(&'gc T, *const ())>,
19+
}
20+
21+
impl<'gc, T: Trace + ?Sized + 'gc> Copy for Gc<'gc, T> {}
22+
impl<'gc, T: Trace + ?Sized + 'gc> Clone for Gc<'gc, T> {
23+
fn clone(&self) -> Self {
24+
*self
25+
}
26+
}
27+
28+
impl<'gc, T: Trace + 'gc> Gc<'gc, T> {
29+
/// Returns a shared reference to the value.
30+
#[inline]
31+
pub fn get(&self) -> &T {
32+
// SAFETY: `ptr` is non-null and valid for `'gc` by construction.
33+
unsafe { &(*self.ptr.as_ptr().as_ptr()).0.value }
34+
}
35+
}
36+
37+
impl<'gc, T: Trace + fmt::Display + 'gc> fmt::Display for Gc<'gc, T> {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
fmt::Display::fmt(self.get(), f)
40+
}
41+
}
42+
43+
impl<'gc, T: Trace + 'gc> Deref for Gc<'gc, T> {
44+
type Target = T;
45+
fn deref(&self) -> &T {
46+
self.get()
47+
}
48+
}
49+
50+
impl<T: Trace> Finalize for Gc<'_, T> {}
51+
impl<T: Trace> Trace for Gc<'_, T> {
52+
fn trace(&mut self, tracer: &mut crate::collectors::null_collector_branded::trace::Tracer) {
53+
tracer.mark(self);
54+
}
55+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use core::ptr::NonNull;
2+
3+
use crate::alloc::mempool3::{PoolAllocator, PoolItem};
4+
5+
pub(crate) type DropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>);
6+
7+
/// Heap wrapper for a garbage collected value.
8+
///
9+
/// Allocated via [`PoolAllocator`]
10+
pub(crate) struct GcBox<T: ?Sized> {
11+
/// Type erased finalize and free fn
12+
pub(crate) drop_fn: DropFn,
13+
/// User value
14+
pub(crate) value: T,
15+
}
16+
17+
impl<T> GcBox<T> {
18+
/// Create a [`GcBox`] for `value`
19+
pub(crate) fn new(value: T, drop_fn: DropFn) -> Self {
20+
Self { drop_fn, value }
21+
}
22+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//! Lifetime branded null GC
2+
#![cfg_attr(not(any(test, feature = "std")), allow(unused_imports))]
3+
4+
pub mod cell;
5+
pub mod ephemeron;
6+
pub mod gc;
7+
pub mod gc_box;
8+
pub mod mutation_ctx;
9+
pub mod root;
10+
pub mod trace;
11+
pub mod weak;
12+
13+
pub use cell::GcRefCell;
14+
pub use ephemeron::Ephemeron;
15+
pub use gc::Gc;
16+
pub use mutation_ctx::MutationContext;
17+
pub use root::Root;
18+
pub use trace::{Finalize, Trace, Tracer};
19+
pub use weak::WeakGc;
20+
21+
use crate::alloc::mempool3::{PoolAllocError, PoolAllocator, PoolPointer};
22+
use core::cell::RefCell;
23+
use core::marker::PhantomData;
24+
use core::ptr::NonNull;
25+
use gc_box::{DropFn, GcBox};
26+
use rust_alloc::vec::Vec;
27+
28+
pub(crate) struct Collector {
29+
// SAFETY: We use 'static here because the PoolAllocator owns its memory,
30+
// and we ensure that `Gc` objects and pool allocations do not outlive
31+
// the `Collector` instance
32+
pub(crate) pool: RefCell<PoolAllocator<'static>>,
33+
}
34+
35+
impl Collector {
36+
fn new() -> Self {
37+
Self {
38+
pool: RefCell::new(PoolAllocator::default()),
39+
}
40+
}
41+
42+
/// Allocates a value from the pool.
43+
pub(crate) fn try_alloc<'gc, T: trace::Trace + trace::Finalize + 'gc>(
44+
&'gc self,
45+
value: T,
46+
) -> Result<Gc<'gc, T>, PoolAllocError> {
47+
unsafe fn drop_and_free<T: trace::Trace + trace::Finalize>(
48+
pool: &mut PoolAllocator<'static>,
49+
ptr: NonNull<u8>,
50+
) {
51+
use crate::alloc::mempool3::PoolItem;
52+
unsafe {
53+
let typed_ptr = ptr.cast::<PoolItem<GcBox<T>>>();
54+
(*typed_ptr.as_ptr()).0.value.finalize();
55+
core::ptr::drop_in_place(typed_ptr.as_ptr());
56+
pool.free_slot(ptr);
57+
}
58+
}
59+
60+
let mut pool = self.pool.borrow_mut();
61+
let ptr = pool.try_alloc(GcBox::new(value, drop_and_free::<T>))?;
62+
63+
drop(pool);
64+
65+
Ok(Gc {
66+
ptr: unsafe { ptr.extend_lifetime() },
67+
_marker: PhantomData,
68+
})
69+
}
70+
71+
/// Runs a collection cycle (no-op for null collector)
72+
pub(crate) fn collect(&self) {}
73+
}
74+
75+
impl Drop for Collector {
76+
/// Frees all remaining allocations
77+
fn drop(&mut self) {
78+
use crate::alloc::mempool3::PoolItem;
79+
80+
// Free all GC allocations
81+
let all: Vec<(NonNull<u8>, DropFn)> = self
82+
.pool
83+
.borrow()
84+
.iter_live_slots()
85+
.map(|ptr| unsafe {
86+
let drop_fn = (*ptr.cast::<PoolItem<GcBox<()>>>().as_ptr()).0.drop_fn;
87+
(ptr, drop_fn)
88+
})
89+
.collect();
90+
let mut pool = self.pool.borrow_mut();
91+
for (ptr, drop_fn) in all {
92+
unsafe {
93+
(drop_fn)(&mut pool, ptr);
94+
}
95+
}
96+
}
97+
}
98+
99+
/// Owns the GC and carries the `'id` context brand
100+
pub struct GcContext<'id> {
101+
collector: Collector,
102+
_marker: PhantomData<*mut &'id ()>,
103+
}
104+
105+
impl<'id> GcContext<'id> {
106+
/// Opens a mutation window and passes a [`MutationContext`] to `f`
107+
/// Triggers a gc cycle
108+
pub fn collect(&self) {
109+
self.collector.collect();
110+
}
111+
112+
pub fn mutate<R>(&self, f: impl for<'gc> FnOnce(&MutationContext<'id, 'gc>) -> R) -> R {
113+
let cx = MutationContext {
114+
collector: &self.collector,
115+
_marker: PhantomData,
116+
};
117+
f(&cx)
118+
}
119+
}
120+
121+
/// Create new GC context
122+
pub fn with_gc<R, F: for<'id> FnOnce(GcContext<'id>) -> R>(f: F) -> R {
123+
f(GcContext {
124+
collector: Collector::new(),
125+
_marker: PhantomData,
126+
})
127+
}

0 commit comments

Comments
 (0)