From 4ca386f866e1b24640466895bc2553d99d5c233e Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Fri, 19 Jun 2026 02:08:31 +0000 Subject: [PATCH 1/2] use constructors in mark_sweep_branded, null_branded, null collectors --- .../mark_sweep_branded/ephemeron.rs | 18 ++- .../src/collectors/mark_sweep_branded/gc.rs | 10 ++ .../src/collectors/mark_sweep_branded/mod.rs | 5 +- .../mark_sweep_branded/mutation_ctx.rs | 15 +-- .../src/collectors/mark_sweep_branded/root.rs | 11 +- .../src/collectors/mark_sweep_branded/weak.rs | 13 +- .../null_collector_branded/ephemeron.rs | 53 ++++++++ .../collectors/null_collector_branded/gc.rs | 65 +++++++++ .../collectors/null_collector_branded/mod.rs | 124 ++++++++++++++++++ .../null_collector_branded/mutation_ctx.rs | 59 +++++++++ .../collectors/null_collector_branded/root.rs | 31 +++++ .../collectors/null_collector_branded/weak.rs | 46 +++++++ 12 files changed, 421 insertions(+), 29 deletions(-) create mode 100644 oscars/src/collectors/null_collector_branded/ephemeron.rs create mode 100644 oscars/src/collectors/null_collector_branded/gc.rs create mode 100644 oscars/src/collectors/null_collector_branded/mod.rs create mode 100644 oscars/src/collectors/null_collector_branded/mutation_ctx.rs create mode 100644 oscars/src/collectors/null_collector_branded/root.rs create mode 100644 oscars/src/collectors/null_collector_branded/weak.rs diff --git a/oscars/src/collectors/mark_sweep_branded/ephemeron.rs b/oscars/src/collectors/mark_sweep_branded/ephemeron.rs index e77771b..82e5887 100644 --- a/oscars/src/collectors/mark_sweep_branded/ephemeron.rs +++ b/oscars/src/collectors/mark_sweep_branded/ephemeron.rs @@ -17,6 +17,19 @@ pub struct Ephemeron<'id, K: Trace, V: Trace> { } impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { + pub(crate) fn new( + key_ptr: Option>>, + key_alloc_id: usize, + value_ptr: PoolPointer<'static, GcBox>, + ) -> Self { + Self { + key_ptr, + key_alloc_id, + value_ptr, + _marker: PhantomData, + } + } + /// Returns the value if the key is alive. pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { // SAFETY: `_cx` proves the collector is alive, alloc_id guards ABA @@ -24,10 +37,7 @@ impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { .key_ptr .is_some_and(|p| unsafe { (*p.as_ptr().as_ptr()).0.alloc_id == self.key_alloc_id }); if key_alive { - Some(Gc { - ptr: self.value_ptr, - _marker: PhantomData, - }) + Some(Gc::with_pointer(self.value_ptr)) } else { None } diff --git a/oscars/src/collectors/mark_sweep_branded/gc.rs b/oscars/src/collectors/mark_sweep_branded/gc.rs index 032d540..2c1cf96 100644 --- a/oscars/src/collectors/mark_sweep_branded/gc.rs +++ b/oscars/src/collectors/mark_sweep_branded/gc.rs @@ -25,6 +25,16 @@ impl<'gc, T: Trace + ?Sized + 'gc> Clone for Gc<'gc, T> { } } +impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> { + #[inline] + pub(crate) fn with_pointer(ptr: PoolPointer<'static, GcBox>) -> Self { + Self { + ptr, + _marker: PhantomData, + } + } +} + impl<'gc, T: Trace + 'gc> Gc<'gc, T> { /// Returns a shared reference to the value. #[inline] diff --git a/oscars/src/collectors/mark_sweep_branded/mod.rs b/oscars/src/collectors/mark_sweep_branded/mod.rs index 3f78a24..702b61c 100644 --- a/oscars/src/collectors/mark_sweep_branded/mod.rs +++ b/oscars/src/collectors/mark_sweep_branded/mod.rs @@ -139,10 +139,7 @@ impl Collector { drop(pool); - Ok(Gc { - ptr: unsafe { ptr.extend_lifetime() }, - _marker: PhantomData, - }) + Ok(Gc::with_pointer(unsafe { ptr.extend_lifetime() })) } /// Runs a collection cycle diff --git a/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs b/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs index 7c9d788..5c3c26d 100644 --- a/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs +++ b/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs @@ -32,11 +32,7 @@ impl<'id, 'gc> MutationContext<'id, 'gc> { /// Downgrades a `Gc` into a weak reference pub fn alloc_weak(&self, gc: Gc<'gc, T>) -> WeakGc<'id, T> { let alloc_id = unsafe { (*gc.ptr.as_ptr().as_ptr()).0.alloc_id }; - WeakGc { - ptr: gc.ptr, - alloc_id, - _marker: PhantomData, - } + WeakGc::with_pointer_and_alloc_id(gc.ptr, alloc_id) } /// Promotes a `Gc` pointer to a `Root` @@ -45,7 +41,7 @@ impl<'id, 'gc> MutationContext<'id, 'gc> { gc: Gc<'gc, T>, ) -> Result, PoolAllocError> { let raw = self.collector.try_alloc_root_node(gc.ptr)?; - Ok(Root { raw }) + Ok(Root::from_raw(raw)) } /// Creates an ephemeron binding `key` to `value`. @@ -66,12 +62,7 @@ impl<'id, 'gc> MutationContext<'id, 'gc> { let erased_value: PoolPointer<'static, GcBox<()>> = unsafe { value.ptr.to_erased().to_typed_pool_pointer::>() }; self.collector.register_ephemeron(erased_key, erased_value); - Ephemeron { - key_ptr: Some(key.ptr), - key_alloc_id, - value_ptr: value.ptr, - _marker: core::marker::PhantomData, - } + Ephemeron::new(Some(key.ptr), key_alloc_id, value.ptr) } /// Triggers a gc cycle. diff --git a/oscars/src/collectors/mark_sweep_branded/root.rs b/oscars/src/collectors/mark_sweep_branded/root.rs index a77fa1c..1ec8529 100644 --- a/oscars/src/collectors/mark_sweep_branded/root.rs +++ b/oscars/src/collectors/mark_sweep_branded/root.rs @@ -170,13 +170,14 @@ pub struct Root<'id, T: Trace> { } impl<'id, T: Trace> Root<'id, T> { + pub(crate) fn from_raw(raw: NonNull>) -> Self { + Self { raw } + } + /// Converts this root into a `Gc` pointer pub fn get<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Gc<'gc, T> { - Gc { - // SAFETY: `raw` is non null and valid - ptr: unsafe { self.raw.as_ref().gc_ptr }, - _marker: PhantomData, - } + // SAFETY: `raw` is non null and valid + Gc::with_pointer(unsafe { self.raw.as_ref().gc_ptr }) } } diff --git a/oscars/src/collectors/mark_sweep_branded/weak.rs b/oscars/src/collectors/mark_sweep_branded/weak.rs index bb60895..27df16d 100644 --- a/oscars/src/collectors/mark_sweep_branded/weak.rs +++ b/oscars/src/collectors/mark_sweep_branded/weak.rs @@ -18,6 +18,14 @@ pub struct WeakGc<'id, T: Trace + ?Sized> { } impl<'id, T: Trace> WeakGc<'id, T> { + pub(crate) fn with_pointer_and_alloc_id(ptr: PoolPointer<'static, GcBox>, alloc_id: usize) -> Self { + Self { + ptr, + alloc_id, + _marker: PhantomData, + } + } + /// Attempts to upgrade to a strong `Gc<'gc, T>`. pub fn upgrade<'gc>( &self, @@ -29,10 +37,7 @@ impl<'id, T: Trace> WeakGc<'id, T> { let is_valid = unsafe { (*self.ptr.as_ptr().as_ptr()).0.alloc_id == self.alloc_id }; if is_valid { - Some(Gc { - ptr: self.ptr, - _marker: PhantomData, - }) + Some(Gc::with_pointer(self.ptr)) } else { None } diff --git a/oscars/src/collectors/null_collector_branded/ephemeron.rs b/oscars/src/collectors/null_collector_branded/ephemeron.rs new file mode 100644 index 0000000..b87591c --- /dev/null +++ b/oscars/src/collectors/null_collector_branded/ephemeron.rs @@ -0,0 +1,53 @@ +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>>, + pub(crate) value_ptr: PoolPointer<'static, GcBox>, + pub(crate) _marker: PhantomData<*mut &'id ()>, +} + +impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { + pub(crate) fn new( + key_ptr: Option>>, + value_ptr: PoolPointer<'static, GcBox>, + ) -> Self { + Self { + key_ptr, + value_ptr, + _marker: PhantomData, + } + } + + /// Returns the value if the key is alive. + pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { + // In the null collector, everything stays alive until context drops. + if self.key_ptr.is_some() { + Some(Gc::with_pointer(self.value_ptr)) + } 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) {} +} diff --git a/oscars/src/collectors/null_collector_branded/gc.rs b/oscars/src/collectors/null_collector_branded/gc.rs new file mode 100644 index 0000000..63bd636 --- /dev/null +++ b/oscars/src/collectors/null_collector_branded/gc.rs @@ -0,0 +1,65 @@ +//! 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>, + 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 + ?Sized + 'gc> Gc<'gc, T> { + #[inline] + pub(crate) fn with_pointer(ptr: PoolPointer<'static, GcBox>) -> Self { + Self { + ptr, + _marker: PhantomData, + } + } +} + +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 Finalize for Gc<'_, T> {} +impl Trace for Gc<'_, T> { + fn trace(&mut self, tracer: &mut crate::collectors::null_collector_branded::trace::Tracer) { + tracer.mark(self); + } +} diff --git a/oscars/src/collectors/null_collector_branded/mod.rs b/oscars/src/collectors/null_collector_branded/mod.rs new file mode 100644 index 0000000..854ab43 --- /dev/null +++ b/oscars/src/collectors/null_collector_branded/mod.rs @@ -0,0 +1,124 @@ +//! 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>, +} + +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, PoolAllocError> { + unsafe fn drop_and_free( + pool: &mut PoolAllocator<'static>, + ptr: NonNull, + ) { + use crate::alloc::mempool3::PoolItem; + unsafe { + let typed_ptr = ptr.cast::>>(); + (*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::))?; + + drop(pool); + + Ok(Gc::with_pointer(unsafe { ptr.extend_lifetime() })) + } + + /// 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, DropFn)> = self + .pool + .borrow() + .iter_live_slots() + .map(|ptr| unsafe { + let drop_fn = (*ptr.cast::>>().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(&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 FnOnce(GcContext<'id>) -> R>(f: F) -> R { + f(GcContext { + collector: Collector::new(), + _marker: PhantomData, + }) +} diff --git a/oscars/src/collectors/null_collector_branded/mutation_ctx.rs b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs new file mode 100644 index 0000000..93a81b8 --- /dev/null +++ b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs @@ -0,0 +1,59 @@ +use crate::{ + alloc::mempool3::PoolAllocError, + collectors::null_collector_branded::{ + Collector, + ephemeron::Ephemeron, + gc::Gc, + root::Root, + trace::{Finalize, Trace}, + weak::WeakGc, + }, +}; +use core::marker::PhantomData; + +/// Handle for GC allocations +pub struct MutationContext<'id, 'gc> { + pub(crate) collector: &'gc Collector, + pub(crate) _marker: PhantomData<*mut &'id ()>, +} + +impl<'id, 'gc> MutationContext<'id, 'gc> { + /// Allocates a value on the GC heap + pub fn try_alloc( + &self, + value: T, + ) -> Result, PoolAllocError> { + self.collector.try_alloc(value) + } + + /// Downgrades a `Gc` into weak reference + pub fn alloc_weak(&self, gc: Gc<'gc, T>) -> WeakGc<'id, T> { + WeakGc::with_pointer(gc.ptr) + } + + pub fn root( + &self, + gc: Gc<'gc, T>, + ) -> Result, PoolAllocError> { + Ok(Root::new(self, gc)) + } + + /// Creates an ephemeron binding `key` to `value` + /// + /// The value is kept alive by the collector as long as the key remains + /// reachable from a root. Once the key is collected, `get_value` returns + /// `None` and the value is eligible for collection on next cycle. + pub fn alloc_ephemeron( + &self, + key: Gc<'gc, K>, + value: Gc<'gc, V>, + ) -> Ephemeron<'id, K, V> { + // In the null collector, ephemerons don't need to be registered + // since the collector never collects. + Ephemeron::new(Some(key.ptr), value.ptr) + } + + pub fn collect(&self) { + self.collector.collect(); + } +} diff --git a/oscars/src/collectors/null_collector_branded/root.rs b/oscars/src/collectors/null_collector_branded/root.rs new file mode 100644 index 0000000..4bc1298 --- /dev/null +++ b/oscars/src/collectors/null_collector_branded/root.rs @@ -0,0 +1,31 @@ +//! In the null collector, roots are a zero cost abstraction because nothing is ever collected +//! before the entire context is dropped. + +use crate::{ + alloc::mempool3::PoolPointer, + collectors::null_collector_branded::{ + gc::Gc, gc_box::GcBox, mutation_ctx::MutationContext, trace::Trace, + }, +}; +use core::marker::PhantomData; + +#[must_use = "dropping a root unregisters it from the GC"] +pub struct Root<'id, T: Trace> { + gc_ptr: PoolPointer<'static, GcBox>, + _marker: PhantomData<*mut &'id ()>, +} + +impl<'id, T: Trace> Root<'id, T> { + /// Creates a new root from a Gc pointer + pub fn new(_mc: &MutationContext<'id, '_>, value: Gc<'_, T>) -> Self { + Self { + gc_ptr: value.ptr, + _marker: PhantomData, + } + } + + /// Converts this root into a `Gc` pointer + pub fn get<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Gc<'gc, T> { + Gc::with_pointer(self.gc_ptr) + } +} diff --git a/oscars/src/collectors/null_collector_branded/weak.rs b/oscars/src/collectors/null_collector_branded/weak.rs new file mode 100644 index 0000000..b9a8152 --- /dev/null +++ b/oscars/src/collectors/null_collector_branded/weak.rs @@ -0,0 +1,46 @@ +use crate::{ + alloc::mempool3::PoolPointer, + collectors::null_collector_branded::{ + gc::Gc, + gc_box::GcBox, + trace::{Finalize, Trace}, + }, +}; +use core::marker::PhantomData; + +/// A weak reference to a GC managed value +pub struct WeakGc<'id, T: Trace + ?Sized> { + pub(crate) ptr: PoolPointer<'static, GcBox>, + pub(crate) _marker: PhantomData<*mut &'id ()>, +} + +impl<'id, T: Trace> WeakGc<'id, T> { + pub(crate) fn with_pointer(ptr: PoolPointer<'static, GcBox>) -> Self { + Self { + ptr, + _marker: PhantomData, + } + } + + /// Attempts to upgrade to a strong `Gc<'gc, T>` + pub fn upgrade<'gc>( + &self, + _cx: &crate::collectors::null_collector_branded::MutationContext<'id, 'gc>, + ) -> Option> { + // In the null collector, everything stays alive until context drops. + Some(Gc::with_pointer(self.ptr)) + } +} + +impl<'id, T: Trace + ?Sized> Clone for WeakGc<'id, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'id, T: Trace + ?Sized> Copy for WeakGc<'id, T> {} + +impl<'id, T: Trace> Finalize for WeakGc<'id, T> {} +impl<'id, T: Trace> Trace for WeakGc<'id, T> { + fn trace(&mut self, _tracer: &mut crate::collectors::null_collector_branded::trace::Tracer) {} +} From 0f02a82cdf6a0dc7fe4a06ad5f72da7ce3df42f6 Mon Sep 17 00:00:00 2001 From: shruti2522 Date: Fri, 19 Jun 2026 02:29:00 +0000 Subject: [PATCH 2/2] fix formatting --- oscars/src/collectors/mark_sweep_branded/weak.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/oscars/src/collectors/mark_sweep_branded/weak.rs b/oscars/src/collectors/mark_sweep_branded/weak.rs index 27df16d..63e4a2c 100644 --- a/oscars/src/collectors/mark_sweep_branded/weak.rs +++ b/oscars/src/collectors/mark_sweep_branded/weak.rs @@ -18,7 +18,10 @@ pub struct WeakGc<'id, T: Trace + ?Sized> { } impl<'id, T: Trace> WeakGc<'id, T> { - pub(crate) fn with_pointer_and_alloc_id(ptr: PoolPointer<'static, GcBox>, alloc_id: usize) -> Self { + pub(crate) fn with_pointer_and_alloc_id( + ptr: PoolPointer<'static, GcBox>, + alloc_id: usize, + ) -> Self { Self { ptr, alloc_id,