Skip to content

Commit d8901ef

Browse files
authored
feat: implement mark_sweep _branded based on approved API redesign (#83)
* feat: implement mark_sweep_branded based on approved API redesign * fix * add ephemeron support * replace Tracer with TraceColor * remove pool_entries Vec and allocation_count * implement tri-color marking * refactor: use PoolPointer, make Ephemeron::key_ptr Option<PoolPointer> * refactor: move RootNode, Root and RootLink into separate root module
1 parent 5f39bfe commit d8901ef

25 files changed

Lines changed: 1735 additions & 7 deletions

oscars/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ default = ["mark_sweep"]
3333
std = []
3434
mark_sweep = []
3535
mark_sweep2 = ["mark_sweep"]
36+
mark_sweep_branded = ["mark_sweep"]
3637
thin-vec = ["dep:thin-vec", "mark_sweep"]

oscars/src/alloc/mempool3/alloc.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,28 @@ impl<'pool> ErasedPoolPointer<'pool> {
5959
}
6060

6161
/// typed pointer into a pool slot
62-
#[derive(Debug, Clone, Copy)]
6362
#[repr(transparent)]
64-
pub struct PoolPointer<'pool, T>(NonNull<PoolItem<T>>, PhantomData<&'pool T>);
63+
pub struct PoolPointer<'pool, T: ?Sized>(NonNull<PoolItem<T>>, PhantomData<(&'pool (), *mut T)>);
6564

66-
impl<'pool, T> PoolPointer<'pool, T> {
67-
pub(crate) unsafe fn from_raw(raw: NonNull<PoolItem<T>>) -> Self {
68-
Self(raw, PhantomData)
65+
impl<'pool, T: ?Sized> core::fmt::Debug for PoolPointer<'pool, T> {
66+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67+
write!(f, "PoolPointer({:p})", self.0.as_ptr())
6968
}
69+
}
70+
71+
impl<'pool, T: ?Sized> Clone for PoolPointer<'pool, T> {
72+
fn clone(&self) -> Self {
73+
*self
74+
}
75+
}
76+
77+
impl<'pool, T: ?Sized> Copy for PoolPointer<'pool, T> {}
7078

71-
pub fn as_inner_ref(&self) -> &'pool T {
79+
impl<'pool, T: ?Sized> PoolPointer<'pool, T> {
80+
pub fn as_inner_ref(&self) -> &'pool T
81+
where
82+
T: 'pool,
83+
{
7284
// SAFETY: pointer is valid and properly aligned
7385
unsafe { &(*self.0.as_ptr()).0 }
7486
}
@@ -77,7 +89,10 @@ impl<'pool, T> PoolPointer<'pool, T> {
7789
self.0
7890
}
7991

80-
pub fn to_erased(self) -> ErasedPoolPointer<'pool> {
92+
pub fn to_erased(self) -> ErasedPoolPointer<'pool>
93+
where
94+
T: Sized,
95+
{
8196
ErasedPoolPointer(self.0.cast::<u8>(), PhantomData)
8297
}
8398

@@ -92,6 +107,12 @@ impl<'pool, T> PoolPointer<'pool, T> {
92107
}
93108
}
94109

110+
impl<'pool, T> PoolPointer<'pool, T> {
111+
pub(crate) unsafe fn from_raw(raw: NonNull<PoolItem<T>>) -> Self {
112+
Self(raw, PhantomData)
113+
}
114+
}
115+
95116
// ==== SlotPool ==== //
96117
impl core::fmt::Debug for SlotPool {
97118
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@@ -310,6 +331,18 @@ impl SlotPool {
310331
self.live.set(self.live.get().saturating_sub(1));
311332
}
312333

334+
/// Iterates over all live (allocated) slot pointers in this pool.
335+
pub(crate) fn iter_live(&self) -> impl Iterator<Item = NonNull<u8>> + '_ {
336+
(0..self.slot_count).filter_map(move |i| {
337+
let chunk = self.bitmap_chunk(i);
338+
if chunk.get() & (1u64 << (i % 64)) != 0 {
339+
Some(self.slot_ptr(i))
340+
} else {
341+
None
342+
}
343+
})
344+
}
345+
313346
/// returns true when the pool is empty and safe to drop
314347
/// `live` tracks the count, so no bitmap scan is needed
315348
pub fn run_drop_check(&self) -> bool {

oscars/src/alloc/mempool3/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum PoolAllocError {
1919
LayoutError(LayoutError),
2020
OutOfMemory,
2121
AlignmentNotPossible,
22+
AllocIdExhausted,
2223
}
2324

2425
impl From<LayoutError> for PoolAllocError {
@@ -121,6 +122,13 @@ impl<'alloc> PoolAllocator<'alloc> {
121122
self.current_heap_size
122123
}
123124

125+
/// Iterates over every live slot pointer across all slot pools.
126+
///
127+
/// Yields one `NonNull<u8>` per allocated (not yet freed) slot.
128+
pub fn iter_live_slots(&self) -> impl Iterator<Item = core::ptr::NonNull<u8>> + '_ {
129+
self.slot_pools.iter().flat_map(|pool| pool.iter_live())
130+
}
131+
124132
pub fn is_below_threshold(&self) -> bool {
125133
// keep 25% headroom so collection fires before the last page fills
126134
let margin = self.heap_threshold / 4;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//! Interior mutability for GC-managed values.
2+
3+
use crate::collectors::mark_sweep_branded::trace::{Finalize, Trace, Tracer};
4+
use core::cell::{Ref, RefCell, RefMut};
5+
use core::ops::{Deref, DerefMut};
6+
7+
/// A GC-aware wrapper around [`RefCell<T>`].
8+
pub struct GcRefCell<T: Trace> {
9+
inner: RefCell<T>,
10+
}
11+
12+
impl<T: Trace> GcRefCell<T> {
13+
/// Wraps `value` in a new `GcRefCell`.
14+
pub fn new(value: T) -> Self {
15+
Self {
16+
inner: RefCell::new(value),
17+
}
18+
}
19+
20+
/// Acquires a shared borrow of the inner value.
21+
///
22+
/// # Panics
23+
///
24+
/// Panics if the value is currently mutably borrowed.
25+
pub fn borrow(&self) -> GcRef<'_, T> {
26+
GcRef(self.inner.borrow())
27+
}
28+
29+
/// Acquires a mutable borrow of the inner value.
30+
///
31+
/// # Panics
32+
///
33+
/// Panics if the value is currently borrowed.
34+
pub fn borrow_mut(&self) -> GcRefMut<'_, T> {
35+
GcRefMut(self.inner.borrow_mut())
36+
}
37+
}
38+
39+
/// A shared borrow guard returned by [`GcRefCell::borrow`].
40+
pub struct GcRef<'a, T: Trace>(Ref<'a, T>);
41+
42+
impl<T: Trace> Deref for GcRef<'_, T> {
43+
type Target = T;
44+
fn deref(&self) -> &T {
45+
&self.0
46+
}
47+
}
48+
49+
/// A mutable borrow guard returned by [`GcRefCell::borrow_mut`].
50+
pub struct GcRefMut<'a, T: Trace>(RefMut<'a, T>);
51+
52+
impl<T: Trace> Deref for GcRefMut<'_, T> {
53+
type Target = T;
54+
fn deref(&self) -> &T {
55+
&self.0
56+
}
57+
}
58+
59+
impl<T: Trace> DerefMut for GcRefMut<'_, T> {
60+
fn deref_mut(&mut self) -> &mut T {
61+
&mut self.0
62+
}
63+
}
64+
65+
impl<T: Trace> Finalize for GcRefCell<T> {}
66+
67+
impl<T: Trace> Trace for GcRefCell<T> {
68+
fn trace(&mut self, tracer: &mut Tracer) {
69+
self.inner.get_mut().trace(tracer);
70+
}
71+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::{
2+
alloc::mempool3::PoolPointer,
3+
collectors::mark_sweep_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) key_alloc_id: usize,
15+
pub(crate) value_ptr: PoolPointer<'static, GcBox<V>>,
16+
pub(crate) _marker: PhantomData<*mut &'id ()>,
17+
}
18+
19+
impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> {
20+
/// Returns the value if the key is alive.
21+
pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option<Gc<'gc, V>> {
22+
// SAFETY: `_cx` proves the collector is alive, alloc_id guards ABA
23+
let key_alive = self
24+
.key_ptr
25+
.is_some_and(|p| unsafe { (*p.as_ptr().as_ptr()).0.alloc_id == self.key_alloc_id });
26+
if key_alive {
27+
Some(Gc {
28+
ptr: self.value_ptr,
29+
_marker: PhantomData,
30+
})
31+
} else {
32+
None
33+
}
34+
}
35+
}
36+
37+
impl<'id, K: Trace, V: Trace> Clone for Ephemeron<'id, K, V> {
38+
fn clone(&self) -> Self {
39+
*self
40+
}
41+
}
42+
43+
impl<'id, K: Trace, V: Trace> Copy for Ephemeron<'id, K, V> {}
44+
45+
impl<'id, K: Trace, V: Trace> Finalize for Ephemeron<'id, K, V> {}
46+
47+
impl<'id, K: Trace, V: Trace> Trace for Ephemeron<'id, K, V> {
48+
fn trace(&mut self, _tracer: &mut Tracer) {}
49+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//! Core pointer types.
2+
3+
use crate::{
4+
alloc::mempool3::PoolPointer,
5+
collectors::mark_sweep_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+
/// A 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+
// The `'gc` lifetime is scoped to a `mutate()` closure, collection only occurs
34+
// via `cx.collect()` within that same closure and `Gc<'gc, T>` can't
35+
// escape the closure.
36+
unsafe { &(*self.ptr.as_ptr().as_ptr()).0.value }
37+
}
38+
}
39+
40+
impl<'gc, T: Trace + fmt::Display + 'gc> fmt::Display for Gc<'gc, T> {
41+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42+
fmt::Display::fmt(self.get(), f)
43+
}
44+
}
45+
46+
impl<'gc, T: Trace + 'gc> Deref for Gc<'gc, T> {
47+
type Target = T;
48+
fn deref(&self) -> &T {
49+
self.get()
50+
}
51+
}
52+
53+
impl<T: Trace> Finalize for Gc<'_, T> {}
54+
impl<T: Trace> Trace for Gc<'_, T> {
55+
fn trace(&mut self, tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {
56+
tracer.mark(self);
57+
}
58+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//! The heap header wrapping every GC-managed value.
2+
3+
use core::cell::Cell;
4+
use core::ptr::NonNull;
5+
6+
use crate::alloc::mempool3::{PoolAllocator, PoolItem};
7+
use crate::collectors::mark_sweep_branded::trace::{Trace, TraceFn, Tracer};
8+
9+
pub(crate) type DropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>);
10+
11+
/// The tri-color marking state of a [`GcBox`]
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13+
#[repr(u8)]
14+
pub(crate) enum GcColor {
15+
/// Not yet reached by mark phase
16+
White = 0,
17+
/// Reached and queued in the worklist, children not yet traced.
18+
Gray = 1,
19+
/// Reached and dequeued from the worklist, all children traced
20+
Black = 2,
21+
}
22+
23+
/// Heap wrapper for a garbage-collected value.
24+
///
25+
/// Allocated via [`PoolAllocator`].
26+
pub(crate) struct GcBox<T: ?Sized> {
27+
/// tricolor marking state, updated by the mark phase
28+
pub(crate) color: Cell<GcColor>,
29+
/// Type-erased trace function.
30+
pub(crate) trace_fn: TraceFn,
31+
/// Type-erased finalize and free fn
32+
pub(crate) drop_fn: DropFn,
33+
/// Allocation ID used to validate weak pointers.
34+
pub(crate) alloc_id: usize,
35+
/// The user value.
36+
pub(crate) value: T,
37+
}
38+
39+
impl<T: ?Sized> GcBox<T> {
40+
pub(crate) const FREED_ALLOC_ID: usize = usize::MAX;
41+
}
42+
43+
impl<T> GcBox<T> {
44+
/// Create a [`GcBox`] for `value`, `color` starts as [`GcColor::White`]
45+
pub(crate) fn new(value: T, trace_fn: TraceFn, drop_fn: DropFn, alloc_id: usize) -> Self {
46+
Self {
47+
color: Cell::new(GcColor::White),
48+
trace_fn,
49+
drop_fn,
50+
alloc_id,
51+
value,
52+
}
53+
}
54+
}
55+
56+
/// type-erased trace function for a `GcBox<T>` slot.
57+
///
58+
/// # Safety
59+
///
60+
/// `ptr` must point to a live `PoolItem<GcBox<T>>` in the pool allocator
61+
pub(crate) unsafe fn trace_value<T: Trace>(ptr: NonNull<u8>, tracer: &mut Tracer<'_>) {
62+
let pool_item_ptr = ptr.cast::<PoolItem<GcBox<T>>>();
63+
unsafe {
64+
(*pool_item_ptr.as_ptr()).0.color.set(GcColor::Black);
65+
(*pool_item_ptr.as_ptr()).0.value.trace(tracer);
66+
}
67+
}

0 commit comments

Comments
 (0)