Skip to content

Commit 08de4ff

Browse files
committed
implement tri-color marking
1 parent 2fbadae commit 08de4ff

14 files changed

Lines changed: 267 additions & 256 deletions

File tree

oscars/src/alloc/mempool3/alloc.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,17 @@ impl<'pool> ErasedPoolPointer<'pool> {
6161
/// typed pointer into a pool slot
6262
#[derive(Debug, Clone, Copy)]
6363
#[repr(transparent)]
64-
pub struct PoolPointer<'pool, T>(NonNull<PoolItem<T>>, PhantomData<&'pool T>);
64+
pub struct PoolPointer<'pool, T>(NonNull<PoolItem<T>>, PhantomData<(&'pool (), *mut T)>);
6565

6666
impl<'pool, T> PoolPointer<'pool, T> {
6767
pub(crate) unsafe fn from_raw(raw: NonNull<PoolItem<T>>) -> Self {
6868
Self(raw, PhantomData)
6969
}
7070

71-
pub fn as_inner_ref(&self) -> &'pool T {
71+
pub fn as_inner_ref(&self) -> &'pool T
72+
where
73+
T: 'pool,
74+
{
7275
// SAFETY: pointer is valid and properly aligned
7376
unsafe { &(*self.0.as_ptr()).0 }
7477
}

oscars/src/alloc/mempool3/mod.rs

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -255,67 +255,6 @@ impl<'alloc> PoolAllocator<'alloc> {
255255
}
256256
}
257257

258-
/// Allocates a raw slot of the given size without type constraints.
259-
/// Returns a pointer to uninitialized memory.
260-
///
261-
/// # Safety
262-
/// Caller must initialize the memory before use and ensure proper cleanup.
263-
pub fn alloc_slot_raw(&mut self, size: usize) -> Result<NonNull<u8>, PoolAllocError> {
264-
let sc_idx = size_class_index_for(size);
265-
let slot_size = SIZE_CLASSES.get(sc_idx).copied().unwrap_or(size);
266-
267-
let cached_idx = self.alloc_cache[sc_idx].get();
268-
if cached_idx < self.slot_pools.len() {
269-
let pool = &self.slot_pools[cached_idx];
270-
if pool.slot_size == slot_size
271-
&& let Some(slot_ptr) = pool.alloc_slot()
272-
{
273-
return Ok(slot_ptr);
274-
}
275-
}
276-
277-
// try existing pools with matching slot_size first
278-
for (i, pool) in self.slot_pools.iter().enumerate().rev() {
279-
if pool.slot_size == slot_size
280-
&& let Some(slot_ptr) = pool.alloc_slot()
281-
{
282-
self.alloc_cache[sc_idx].set(i);
283-
return Ok(slot_ptr);
284-
}
285-
}
286-
287-
// need a new pool for this size class
288-
// try the recycle list first
289-
if let Some(pos) = self
290-
.recycled_pools
291-
.iter()
292-
.rposition(|p| p.slot_size == slot_size)
293-
{
294-
let pool = self.recycled_pools.swap_remove(pos);
295-
let slot_ptr = pool.alloc_slot().ok_or(PoolAllocError::OutOfMemory)?;
296-
let insert_idx = self.slot_pools.len();
297-
let (base, end) = pool.slot_range();
298-
let spos = self.sorted_ranges.partition_point(|&(b, _, _)| b < base);
299-
self.sorted_ranges.insert(spos, (base, end, insert_idx));
300-
self.slot_pools.push(pool);
301-
self.alloc_cache[sc_idx].set(insert_idx);
302-
return Ok(slot_ptr);
303-
}
304-
305-
// Allocate a fresh page from the OS
306-
let total = self.page_size.max(slot_size * 4);
307-
let new_pool = SlotPool::try_init(slot_size, total, 16)?;
308-
self.current_heap_size += new_pool.layout.size();
309-
let slot_ptr = new_pool.alloc_slot().ok_or(PoolAllocError::OutOfMemory)?;
310-
let insert_idx = self.slot_pools.len();
311-
let (base, end) = new_pool.slot_range();
312-
let spos = self.sorted_ranges.partition_point(|&(b, _, _)| b < base);
313-
self.sorted_ranges.insert(spos, (base, end, insert_idx));
314-
self.slot_pools.push(new_pool);
315-
self.alloc_cache[sc_idx].set(insert_idx);
316-
Ok(slot_ptr)
317-
}
318-
319258
/// drops the value at `ptr` and returns the slot to the allocator
320259
///
321260
/// # Safety

oscars/src/collectors/mark_sweep_branded/cell.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Interior mutability for GC-managed values.
22
3-
use crate::collectors::mark_sweep_branded::trace::{Finalize, Trace, TraceColor};
3+
use crate::collectors::mark_sweep_branded::trace::{Finalize, Trace, Tracer};
44
use core::cell::{Ref, RefCell, RefMut};
55
use core::ops::{Deref, DerefMut};
66

@@ -65,7 +65,7 @@ impl<T: Trace> DerefMut for GcRefMut<'_, T> {
6565
impl<T: Trace> Finalize for GcRefCell<T> {}
6666

6767
impl<T: Trace> Trace for GcRefCell<T> {
68-
fn trace(&self, color: &TraceColor) {
69-
self.inner.borrow().trace(color);
68+
fn trace(&mut self, tracer: &mut Tracer) {
69+
self.inner.get_mut().trace(tracer);
7070
}
7171
}

oscars/src/collectors/mark_sweep_branded/ephemeron.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
gc::Gc,
55
gc_box::GcBox,
66
mutation_ctx::MutationContext,
7-
trace::{Finalize, Trace, TraceColor},
7+
trace::{Finalize, Trace, Tracer},
88
},
99
};
1010
use core::marker::PhantomData;
@@ -44,5 +44,5 @@ impl<'id, K: Trace, V: Trace> Copy for Ephemeron<'id, K, V> {}
4444
impl<'id, K: Trace, V: Trace> Finalize for Ephemeron<'id, K, V> {}
4545

4646
impl<'id, K: Trace, V: Trace> Trace for Ephemeron<'id, K, V> {
47-
fn trace(&self, _color: &TraceColor) {}
47+
fn trace(&mut self, _tracer: &mut Tracer) {}
4848
}

oscars/src/collectors/mark_sweep_branded/gc.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Core pointer types.
22
33
use crate::{
4-
alloc::mempool3::PoolItem,
4+
alloc::mempool3::{PoolAllocator, PoolItem},
55
collectors::mark_sweep_branded::{
66
gc_box::GcBox,
77
mutation_ctx::MutationContext,
@@ -13,7 +13,8 @@ use core::fmt;
1313
use core::marker::PhantomData;
1414
use core::ops::Deref;
1515
use core::ptr::NonNull;
16-
use rust_alloc::boxed::Box;
16+
17+
pub(crate) type RootDropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>);
1718

1819
/// A transient pointer to a GC-managed value.
1920
#[derive(Debug)]
@@ -61,6 +62,10 @@ pub(crate) struct RootNode<'id, T: Trace> {
6162
pub(crate) link: RootLink,
6263
/// Pointer to the allocation
6364
pub(crate) gc_ptr: NonNull<PoolItem<GcBox<T>>>,
65+
/// Type-erased drop function for freeing this RootNode
66+
pub(crate) drop_fn: RootDropFn,
67+
/// Raw pointer to the Collector for freeing this node
68+
pub(crate) collector_ptr: *const crate::collectors::mark_sweep_branded::Collector,
6469
pub(crate) _marker: PhantomData<*mut &'id ()>,
6570
}
6671

@@ -83,21 +88,21 @@ impl<'id, T: Trace> Root<'id, T> {
8388

8489
impl<'id, T: Trace> Drop for Root<'id, T> {
8590
fn drop(&mut self) {
86-
// SAFETY:
87-
// * `self.raw` was created by `Box::into_raw`
88-
// * The address is stable.
8991
unsafe {
90-
let node = Box::from_raw(self.raw.as_ptr());
91-
if node.link.is_linked() {
92-
RootLink::unlink(NonNull::from(&node.link));
92+
let node_ref = self.raw.as_ref();
93+
if node_ref.link.is_linked() {
94+
RootLink::unlink(NonNull::from(&node_ref.link));
9395
}
96+
// SAFETY: collector_ptr is valid for the lifetime of the GcContext
97+
let collector = &*node_ref.collector_ptr;
98+
collector.free_root_node(self.raw.cast::<u8>(), node_ref.drop_fn);
9499
}
95100
}
96101
}
97102

98103
impl<T: Trace> Finalize for Gc<'_, T> {}
99104
impl<T: Trace> Trace for Gc<'_, T> {
100-
fn trace(&self, color: &crate::collectors::mark_sweep_branded::trace::TraceColor) {
101-
color.mark(self);
105+
fn trace(&mut self, tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {
106+
tracer.mark(self);
102107
}
103108
}

oscars/src/collectors/mark_sweep_branded/gc_box.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,29 @@
33
use core::cell::Cell;
44
use core::ptr::NonNull;
55

6-
use crate::alloc::mempool3::PoolAllocator;
7-
use crate::collectors::mark_sweep_branded::trace::TraceFn;
6+
use crate::alloc::mempool3::{PoolAllocator, PoolItem};
7+
use crate::collectors::mark_sweep_branded::trace::{Trace, TraceFn, Tracer};
88

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

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+
1123
/// Heap wrapper for a garbage-collected value.
1224
///
1325
/// Allocated via [`PoolAllocator`].
1426
pub(crate) struct GcBox<T: ?Sized> {
15-
/// Reachability flag set by the mark phase.
16-
pub(crate) marked: Cell<bool>,
27+
/// tricolor marking state, updated by the mark phase
28+
pub(crate) color: Cell<GcColor>,
1729
/// Type-erased trace function.
1830
pub(crate) trace_fn: TraceFn,
1931
/// Type-erased finalize and free fn
@@ -27,3 +39,29 @@ pub(crate) struct GcBox<T: ?Sized> {
2739
impl<T: ?Sized> GcBox<T> {
2840
pub(crate) const FREED_ALLOC_ID: usize = usize::MAX;
2941
}
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)