Skip to content
Merged
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
1 change: 1 addition & 0 deletions oscars/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ mark_sweep = []
mark_sweep2 = ["mark_sweep"]
mark_sweep_branded = ["mark_sweep"]
null_collector = ["mark_sweep"]
null_collector_branded = ["mark_sweep"]
thin-vec = ["dep:thin-vec", "mark_sweep"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ error: lifetime may not live long enough
14 | let _escaped = ctx.mutate(|cx| {
| --- return type of closure is oscars::collectors::mark_sweep_branded::Gc<'2, i32>
| |
| has type `&MutationContext<'_, '1>`
| has type `&oscars::collectors::mark_sweep_branded::MutationContext<'_, '1>`
15 | cx.try_alloc(42i32).unwrap() // ERROR: Gc<'gc, i32> cannot escape mutate()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error: lifetime may not live long enough
13 | with_gc(|ctx1| {
| ---- lifetime `'2` appears in the type of `ctx1`
14 | with_gc(|ctx2| {
| ---- has type `GcContext<'1>`
| ---- has type `oscars::collectors::mark_sweep_branded::GcContext<'1>`
15 | // root carries 'id of ctx1
16 | let root = ctx1.mutate(|cx| cx.root(cx.try_alloc(123i32).unwrap()).unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
Expand All @@ -13,7 +13,7 @@ error: lifetime may not live long enough
--> src/collectors/mark_sweep_branded/tests/ui/root_cross_context.rs:16:41
|
13 | with_gc(|ctx1| {
| ---- has type `GcContext<'1>`
| ---- has type `oscars::collectors::mark_sweep_branded::GcContext<'1>`
...
16 | let root = ctx1.mutate(|cx| cx.root(cx.try_alloc(123i32).unwrap()).unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`
4 changes: 3 additions & 1 deletion oscars/src/collectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod mark_sweep_arena2;
#[cfg(feature = "null_collector")]
pub mod null_collector;

// TODO: Implement a null collector for the branded API as well
#[cfg(feature = "null_collector_branded")]
pub mod null_collector_branded;

#[cfg(feature = "mark_sweep_branded")]
pub mod mark_sweep_branded;
68 changes: 68 additions & 0 deletions oscars/src/collectors/null_collector_branded/cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::collectors::null_collector_branded::trace::{Finalize, Trace, Tracer};
use core::cell::{Ref, RefCell, RefMut};
use core::ops::{Deref, DerefMut};

/// GC aware wrapper around [`RefCell<T>`]
pub struct GcRefCell<T: Trace> {
inner: RefCell<T>,
}

impl<T: Trace> GcRefCell<T> {
pub fn new(value: T) -> Self {
Self {
inner: RefCell::new(value),
}
}

/// Acquires a shared borrow of the inner value.
///
/// # Panics
///
/// Panics if the value is currently mutably borrowed.
pub fn borrow(&self) -> GcRef<'_, T> {
GcRef(self.inner.borrow())
}

/// Acquires a mutable borrow of the inner value.
///
/// # Panics
///
/// Panics if the value is currently borrowed.
pub fn borrow_mut(&self) -> GcRefMut<'_, T> {
GcRefMut(self.inner.borrow_mut())
}
}

/// Shared borrow guard returned by [`GcRefCell::borrow`]
pub struct GcRef<'a, T: Trace>(Ref<'a, T>);

impl<T: Trace> Deref for GcRef<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}

/// A mutable borrow guard returned by [`GcRefCell::borrow_mut`]
pub struct GcRefMut<'a, T: Trace>(RefMut<'a, T>);

impl<T: Trace> Deref for GcRefMut<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}

impl<T: Trace> DerefMut for GcRefMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}

impl<T: Trace> Finalize for GcRefCell<T> {}

impl<T: Trace> Trace for GcRefCell<T> {
fn trace(&mut self, tracer: &mut Tracer) {
self.inner.get_mut().trace(tracer);
}
}
45 changes: 45 additions & 0 deletions oscars/src/collectors/null_collector_branded/ephemeron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use crate::{
alloc::mempool3::PoolPointer,
collectors::null_collector_branded::{
gc::Gc,
gc_box::GcBox,
mutation_ctx::MutationContext,
trace::{Finalize, Trace, Tracer},
},
};
use core::marker::PhantomData;

pub struct Ephemeron<'id, K: Trace, V: Trace> {
pub(crate) key_ptr: Option<PoolPointer<'static, GcBox<K>>>,
pub(crate) value_ptr: PoolPointer<'static, GcBox<V>>,
pub(crate) _marker: PhantomData<*mut &'id ()>,
}

impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> {
/// Returns the value if the key is alive.
pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option<Gc<'gc, V>> {
// In the null collector, everything stays alive until context drops.
if self.key_ptr.is_some() {
Some(Gc {
ptr: self.value_ptr,
_marker: PhantomData,
})
} else {
None
}
}
}

impl<'id, K: Trace, V: Trace> Clone for Ephemeron<'id, K, V> {
fn clone(&self) -> Self {
*self
}
}

impl<'id, K: Trace, V: Trace> Copy for Ephemeron<'id, K, V> {}

impl<'id, K: Trace, V: Trace> Finalize for Ephemeron<'id, K, V> {}

impl<'id, K: Trace, V: Trace> Trace for Ephemeron<'id, K, V> {
fn trace(&mut self, _tracer: &mut Tracer) {}
}
55 changes: 55 additions & 0 deletions oscars/src/collectors/null_collector_branded/gc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Core pointer types.

use crate::{
alloc::mempool3::PoolPointer,
collectors::null_collector_branded::{
gc_box::GcBox,
trace::{Finalize, Trace},
},
};
use core::fmt;
use core::marker::PhantomData;
use core::ops::Deref;

/// Transient pointer to a GC managed value.
#[derive(Debug)]
pub struct Gc<'gc, T: Trace + ?Sized + 'gc> {
pub(crate) ptr: PoolPointer<'static, GcBox<T>>,
pub(crate) _marker: PhantomData<(&'gc T, *const ())>,
}

impl<'gc, T: Trace + ?Sized + 'gc> Copy for Gc<'gc, T> {}
impl<'gc, T: Trace + ?Sized + 'gc> Clone for Gc<'gc, T> {
fn clone(&self) -> Self {
*self
}
}

impl<'gc, T: Trace + 'gc> Gc<'gc, T> {
/// Returns a shared reference to the value.
#[inline]
pub fn get(&self) -> &T {
// SAFETY: `ptr` is non-null and valid for `'gc` by construction.
unsafe { &(*self.ptr.as_ptr().as_ptr()).0.value }
}
}

impl<'gc, T: Trace + fmt::Display + 'gc> fmt::Display for Gc<'gc, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.get(), f)
}
}

impl<'gc, T: Trace + 'gc> Deref for Gc<'gc, T> {
type Target = T;
fn deref(&self) -> &T {
self.get()
}
}

impl<T: Trace> Finalize for Gc<'_, T> {}
impl<T: Trace> Trace for Gc<'_, T> {
fn trace(&mut self, tracer: &mut crate::collectors::null_collector_branded::trace::Tracer) {
tracer.mark(self);
}
}
22 changes: 22 additions & 0 deletions oscars/src/collectors/null_collector_branded/gc_box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use core::ptr::NonNull;

use crate::alloc::mempool3::PoolAllocator;

pub(crate) type DropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>);

/// Heap wrapper for a garbage collected value.
///
/// Allocated via [`PoolAllocator`]
pub(crate) struct GcBox<T: ?Sized> {
/// Type erased finalize and free fn
pub(crate) drop_fn: DropFn,
/// User value
pub(crate) value: T,
}

impl<T> GcBox<T> {
/// Create a [`GcBox`] for `value`
pub(crate) fn new(value: T, drop_fn: DropFn) -> Self {
Self { drop_fn, value }
}
}
127 changes: 127 additions & 0 deletions oscars/src/collectors/null_collector_branded/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Lifetime branded null GC
#![cfg_attr(not(any(test, feature = "std")), allow(unused_imports))]

pub mod cell;
pub mod ephemeron;
pub mod gc;
pub mod gc_box;
pub mod mutation_ctx;
pub mod root;
pub mod trace;
pub mod weak;

pub use cell::GcRefCell;
pub use ephemeron::Ephemeron;
pub use gc::Gc;
pub use mutation_ctx::MutationContext;
pub use root::Root;
pub use trace::{Finalize, Trace, Tracer};
pub use weak::WeakGc;

use crate::alloc::mempool3::{PoolAllocError, PoolAllocator};
use core::cell::RefCell;
use core::marker::PhantomData;
use core::ptr::NonNull;
use gc_box::{DropFn, GcBox};
use rust_alloc::vec::Vec;

pub(crate) struct Collector {
// SAFETY: We use 'static here because the PoolAllocator owns its memory,
// and we ensure that `Gc` objects and pool allocations do not outlive
// the `Collector` instance
pub(crate) pool: RefCell<PoolAllocator<'static>>,
}

impl Collector {
fn new() -> Self {
Self {
pool: RefCell::new(PoolAllocator::default()),
}
}

/// Allocates a value from the pool.
pub(crate) fn try_alloc<'gc, T: trace::Trace + trace::Finalize + 'gc>(
&'gc self,
value: T,
) -> Result<Gc<'gc, T>, PoolAllocError> {
unsafe fn drop_and_free<T: trace::Trace + trace::Finalize>(
pool: &mut PoolAllocator<'static>,
ptr: NonNull<u8>,
) {
use crate::alloc::mempool3::PoolItem;
unsafe {
let typed_ptr = ptr.cast::<PoolItem<GcBox<T>>>();
(*typed_ptr.as_ptr()).0.value.finalize();
core::ptr::drop_in_place(typed_ptr.as_ptr());
pool.free_slot(ptr);
}
}

let mut pool = self.pool.borrow_mut();
let ptr = pool.try_alloc(GcBox::new(value, drop_and_free::<T>))?;

drop(pool);

Ok(Gc {
ptr: unsafe { ptr.extend_lifetime() },
_marker: PhantomData,
})
}

/// Runs a collection cycle (no-op for null collector)
pub(crate) fn collect(&self) {}
}

impl Drop for Collector {
/// Frees all remaining allocations
fn drop(&mut self) {
use crate::alloc::mempool3::PoolItem;

// Free all GC allocations
let all: Vec<(NonNull<u8>, DropFn)> = self
.pool
.borrow()
.iter_live_slots()
.map(|ptr| unsafe {
let drop_fn = (*ptr.cast::<PoolItem<GcBox<()>>>().as_ptr()).0.drop_fn;
(ptr, drop_fn)
})
.collect();
let mut pool = self.pool.borrow_mut();
for (ptr, drop_fn) in all {
unsafe {
(drop_fn)(&mut pool, ptr);
}
}
}
}

/// Owns the GC and carries the `'id` context brand
pub struct GcContext<'id> {
collector: Collector,
_marker: PhantomData<*mut &'id ()>,
}

impl<'id> GcContext<'id> {
/// Opens a mutation window and passes a [`MutationContext`] to `f`
/// Triggers a gc cycle
pub fn collect(&self) {
self.collector.collect();
}

pub fn mutate<R>(&self, f: impl for<'gc> FnOnce(&MutationContext<'id, 'gc>) -> R) -> R {
let cx = MutationContext {
collector: &self.collector,
_marker: PhantomData,
};
f(&cx)
}
}

/// Create new GC context
pub fn with_gc<R, F: for<'id> FnOnce(GcContext<'id>) -> R>(f: F) -> R {
f(GcContext {
collector: Collector::new(),
_marker: PhantomData,
})
}
Loading
Loading