Skip to content

Commit a98ad8e

Browse files
committed
refactor: use PoolPointer, make Ephemeron::key_ptr Option<PoolPointer>
1 parent f5d3154 commit a98ad8e

16 files changed

Lines changed: 165 additions & 143 deletions

oscars/src/alloc/mempool3/alloc.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,24 @@ 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 (), *mut 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+
}
7076

77+
impl<'pool, T: ?Sized> Copy for PoolPointer<'pool, T> {}
78+
79+
impl<'pool, T: ?Sized> PoolPointer<'pool, T> {
7180
pub fn as_inner_ref(&self) -> &'pool T
7281
where
7382
T: 'pool,
@@ -80,7 +89,10 @@ impl<'pool, T> PoolPointer<'pool, T> {
8089
self.0
8190
}
8291

83-
pub fn to_erased(self) -> ErasedPoolPointer<'pool> {
92+
pub fn to_erased(self) -> ErasedPoolPointer<'pool>
93+
where
94+
T: Sized,
95+
{
8496
ErasedPoolPointer(self.0.cast::<u8>(), PhantomData)
8597
}
8698

@@ -95,6 +107,12 @@ impl<'pool, T> PoolPointer<'pool, T> {
95107
}
96108
}
97109

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+
98116
// ==== SlotPool ==== //
99117
impl core::fmt::Debug for SlotPool {
100118
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {

oscars/src/collectors/mark_sweep_branded/ephemeron.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
alloc::mempool3::PoolItem,
2+
alloc::mempool3::PoolPointer,
33
collectors::mark_sweep_branded::{
44
gc::Gc,
55
gc_box::GcBox,
@@ -8,20 +8,21 @@ use crate::{
88
},
99
};
1010
use core::marker::PhantomData;
11-
use core::ptr::NonNull;
1211

1312
pub struct Ephemeron<'id, K: Trace, V: Trace> {
14-
pub(crate) key_ptr: NonNull<PoolItem<GcBox<K>>>,
13+
pub(crate) key_ptr: Option<PoolPointer<'static, GcBox<K>>>,
1514
pub(crate) key_alloc_id: usize,
16-
pub(crate) value_ptr: NonNull<PoolItem<GcBox<V>>>,
15+
pub(crate) value_ptr: PoolPointer<'static, GcBox<V>>,
1716
pub(crate) _marker: PhantomData<*mut &'id ()>,
1817
}
1918

2019
impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> {
2120
/// Returns the value if the key is alive.
2221
pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option<Gc<'gc, V>> {
23-
// SAFETY: `_cx` proves the collector is alive; alloc_id guards ABA.
24-
let key_alive = unsafe { (*self.key_ptr.as_ptr()).0.alloc_id == self.key_alloc_id };
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 });
2526
if key_alive {
2627
Some(Gc {
2728
ptr: self.value_ptr,

oscars/src/collectors/mark_sweep_branded/gc.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Core pointer types.
22
33
use crate::{
4-
alloc::mempool3::{PoolAllocator, PoolItem},
4+
alloc::mempool3::{PoolAllocator, PoolPointer},
55
collectors::mark_sweep_branded::{
66
gc_box::GcBox,
77
mutation_ctx::MutationContext,
@@ -19,7 +19,7 @@ pub(crate) type RootDropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>)
1919
/// A transient pointer to a GC-managed value.
2020
#[derive(Debug)]
2121
pub struct Gc<'gc, T: Trace + ?Sized + 'gc> {
22-
pub(crate) ptr: NonNull<PoolItem<GcBox<T>>>,
22+
pub(crate) ptr: PoolPointer<'static, GcBox<T>>,
2323
pub(crate) _marker: PhantomData<(&'gc T, *const ())>,
2424
}
2525

@@ -38,7 +38,7 @@ impl<'gc, T: Trace + 'gc> Gc<'gc, T> {
3838
// The `'gc` lifetime is scoped to a `mutate()` closure, collection only occurs
3939
// via `cx.collect()` within that same closure and `Gc<'gc, T>` can't
4040
// escape the closure.
41-
unsafe { &(*self.ptr.as_ptr()).0.value }
41+
unsafe { &(*self.ptr.as_ptr().as_ptr()).0.value }
4242
}
4343
}
4444

@@ -61,14 +61,25 @@ pub(crate) struct RootNode<'id, T: Trace> {
6161
/// Intrusive list link
6262
pub(crate) link: RootLink,
6363
/// Pointer to the allocation
64-
pub(crate) gc_ptr: NonNull<PoolItem<GcBox<T>>>,
64+
pub(crate) gc_ptr: PoolPointer<'static, GcBox<T>>,
6565
/// Type-erased drop function for freeing this RootNode
6666
pub(crate) drop_fn: RootDropFn,
6767
/// Raw pointer to the Collector for freeing this node
6868
pub(crate) collector_ptr: *const crate::collectors::mark_sweep_branded::Collector,
6969
pub(crate) _marker: PhantomData<*mut &'id ()>,
7070
}
7171

72+
/// Type-erased version of [`RootNode`] for use during collection.
73+
///
74+
/// Since [`RootNode`] is `repr(C)` and `link` is always the first field,
75+
/// a `NonNull<RootLink>` from the sentinel iterator can be safely cast to
76+
/// `NonNull<ErasedRootNode>` to read `gc_ptr` without knowing `T`.
77+
#[repr(C)]
78+
pub(crate) struct ErasedRootNode {
79+
pub(crate) link: RootLink,
80+
pub(crate) gc_ptr: PoolPointer<'static, GcBox<()>>,
81+
}
82+
7283
/// A handle that keeps a GC allocation live.
7384
#[must_use = "dropping a root unregisters it from the GC"]
7485
pub struct Root<'id, T: Trace> {

oscars/src/collectors/mark_sweep_branded/mod.rs

Lines changed: 33 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,18 @@ pub use mutation_ctx::MutationContext;
2020
pub use trace::{Finalize, Trace, Tracer};
2121
pub use weak::WeakGc;
2222

23-
use crate::alloc::mempool3::PoolAllocator;
23+
use crate::alloc::mempool3::{PoolAllocError, PoolAllocator, PoolPointer};
2424
use core::cell::{Cell, RefCell};
2525
use core::marker::PhantomData;
2626
use core::ptr::NonNull;
2727
use gc_box::{DropFn, GcBox, GcColor};
28-
use root_link::{RootLink, RootSentinel};
28+
use root_link::RootSentinel;
2929
use rust_alloc::vec::Vec;
3030

3131
/// Type-erased ephemeron registration.
3232
pub(crate) struct EphemeronEntry {
33-
pub(crate) key_ptr: NonNull<u8>,
34-
pub(crate) key_alloc_id: usize,
35-
pub(crate) value_ptr: NonNull<u8>,
33+
pub(crate) key_ptr: Option<PoolPointer<'static, GcBox<()>>>,
34+
pub(crate) value_ptr: PoolPointer<'static, GcBox<()>>,
3635
}
3736

3837
pub(crate) struct Collector {
@@ -61,21 +60,19 @@ impl Collector {
6160
/// Registers an ephemeron key/value pair for processing during collection.
6261
pub(crate) fn register_ephemeron(
6362
&self,
64-
key_ptr: NonNull<u8>,
65-
key_alloc_id: usize,
66-
value_ptr: NonNull<u8>,
63+
key_ptr: PoolPointer<'static, GcBox<()>>,
64+
value_ptr: PoolPointer<'static, GcBox<()>>,
6765
) {
6866
self.ephemerons.borrow_mut().push(EphemeronEntry {
69-
key_ptr,
70-
key_alloc_id,
67+
key_ptr: Some(key_ptr),
7168
value_ptr,
7269
});
7370
}
7471

7572
/// Allocates a RootNode from the dedicated root pool.
7673
pub(crate) fn alloc_root_node<'id, T: trace::Trace>(
7774
&self,
78-
gc_ptr: NonNull<crate::alloc::mempool3::PoolItem<GcBox<T>>>,
75+
gc_ptr: PoolPointer<'static, GcBox<T>>,
7976
) -> NonNull<gc::RootNode<'id, T>> {
8077
unsafe fn drop_and_free<T: trace::Trace>(
8178
pool: &mut PoolAllocator<'static>,
@@ -121,7 +118,7 @@ impl Collector {
121118
pub(crate) fn alloc<'gc, T: trace::Trace + trace::Finalize + 'gc>(
122119
&'gc self,
123120
value: T,
124-
) -> Gc<'gc, T> {
121+
) -> Result<Gc<'gc, T>, PoolAllocError> {
125122
let alloc_id = self.generic_alloc_id.get();
126123

127124
// Check for alloc_id wrap before incrementing.
@@ -151,51 +148,32 @@ impl Collector {
151148
}
152149

153150
let mut pool = self.pool.borrow_mut();
154-
let ptr = pool
155-
.try_alloc(GcBox::new(
156-
value,
157-
gc_box::trace_value::<T>,
158-
drop_and_free::<T>,
159-
alloc_id,
160-
))
161-
.expect("branded GC: pool allocation failed");
151+
let ptr = pool.try_alloc(GcBox::new(
152+
value,
153+
gc_box::trace_value::<T>,
154+
drop_and_free::<T>,
155+
alloc_id,
156+
))?;
162157

163158
drop(pool);
164159

165-
Gc {
166-
ptr: ptr.as_ptr(),
160+
Ok(Gc {
161+
ptr: unsafe { ptr.extend_lifetime() },
167162
_marker: PhantomData,
168-
}
163+
})
169164
}
170165

171166
/// Runs a collection cycle
172167
pub(crate) fn collect(&self) {
173168
let mut tracer = Tracer::new();
174169

175-
let gc_ptr_offset = core::mem::offset_of!(
176-
crate::collectors::mark_sweep_branded::gc::RootNode<'static, i32>,
177-
gc_ptr
178-
);
179-
debug_assert_eq!(
180-
gc_ptr_offset,
181-
core::mem::offset_of!(
182-
crate::collectors::mark_sweep_branded::gc::RootNode<'static, u64>,
183-
gc_ptr
184-
),
185-
"gc_ptr offset must be stable across all T: Sized"
186-
);
187-
188170
for link_ptr in self.sentinel.iter() {
189171
unsafe {
190-
// Read the `gc_ptr` field using the stable offset
191-
let gc_ptr_ptr = link_ptr
192-
.as_ptr()
193-
.cast::<u8>()
194-
.add(gc_ptr_offset)
195-
.cast::<NonNull<u8>>();
196-
let gc_ptr = gc_ptr_ptr.read();
197-
198-
tracer.mark_raw(gc_ptr.cast::<u8>());
172+
// SAFETY: link_ptr points to the `link` field which is first in repr(C) RootNode.
173+
// Casting to ErasedRootNode (also repr(C), same first two fields) lets us read
174+
// gc_ptr without knowing T, avoiding manual offset arithmetic.
175+
let erased = link_ptr.cast::<gc::ErasedRootNode>();
176+
tracer.mark_raw((*erased.as_ptr()).gc_ptr.as_ptr().cast::<u8>());
199177
}
200178
}
201179

@@ -207,15 +185,12 @@ impl Collector {
207185
loop {
208186
let mut any_newly_marked = false;
209187
for entry in self.ephemerons.borrow().iter() {
210-
use crate::alloc::mempool3::PoolItem;
188+
let Some(key_ptr) = entry.key_ptr else {
189+
continue;
190+
};
211191
unsafe {
212-
let key_box = entry.key_ptr.cast::<PoolItem<GcBox<()>>>();
213-
// Skip entries invalidated by a previous collection cycle.
214-
if (*key_box.as_ptr()).0.alloc_id != entry.key_alloc_id {
215-
continue;
216-
}
217-
if (*key_box.as_ptr()).0.color.get() != GcColor::White {
218-
any_newly_marked |= tracer.mark_raw(entry.value_ptr);
192+
if (*key_ptr.as_ptr().as_ptr()).0.color.get() != GcColor::White {
193+
any_newly_marked |= tracer.mark_raw(entry.value_ptr.as_ptr().cast::<u8>());
219194
}
220195
}
221196
}
@@ -253,12 +228,12 @@ impl Collector {
253228
}
254229

255230
// Phase 4: remove ephemeron entries whose key was swept this cycle.
231+
// A swept key has alloc_id set to FREED_ALLOC_ID by the sweep above.
232+
// Using the Option lets us express the invalid state without a stored alloc_id.
256233
self.ephemerons.borrow_mut().retain(|entry| {
257-
use crate::alloc::mempool3::PoolItem;
258-
unsafe {
259-
let key_box = entry.key_ptr.cast::<PoolItem<GcBox<()>>>();
260-
(*key_box.as_ptr()).0.alloc_id == entry.key_alloc_id
261-
}
234+
entry.key_ptr.is_some_and(|key_ptr| unsafe {
235+
(*key_ptr.as_ptr().as_ptr()).0.alloc_id != GcBox::<()>::FREED_ALLOC_ID
236+
})
262237
});
263238
}
264239
}

oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
//! `MutationContext<'id, 'gc>` handle.
22
3-
use crate::collectors::mark_sweep_branded::{
4-
Collector,
5-
ephemeron::Ephemeron,
6-
gc::{Gc, Root},
7-
root_link::RootLink,
8-
trace::{Finalize, Trace},
9-
weak::WeakGc,
3+
use crate::{
4+
alloc::mempool3::{PoolAllocError, PoolPointer},
5+
collectors::mark_sweep_branded::{
6+
Collector,
7+
ephemeron::Ephemeron,
8+
gc::{Gc, Root},
9+
gc_box::GcBox,
10+
root_link::RootLink,
11+
trace::{Finalize, Trace},
12+
weak::WeakGc,
13+
},
1014
};
1115
use core::marker::PhantomData;
1216

@@ -18,17 +22,13 @@ pub struct MutationContext<'id, 'gc> {
1822

1923
impl<'id, 'gc> MutationContext<'id, 'gc> {
2024
/// Allocates a value on the GC heap.
21-
///
22-
/// # Panics
23-
///
24-
/// Panics if the pool allocator fails to allocate.
25-
pub fn alloc<T: Trace + Finalize + 'gc>(&self, value: T) -> Gc<'gc, T> {
25+
pub fn alloc<T: Trace + Finalize + 'gc>(&self, value: T) -> Result<Gc<'gc, T>, PoolAllocError> {
2626
self.collector.alloc(value)
2727
}
2828

2929
/// Downgrades a `Gc` into a weak reference
3030
pub fn alloc_weak<T: Trace + Finalize + 'gc>(&self, gc: Gc<'gc, T>) -> WeakGc<'id, T> {
31-
let alloc_id = unsafe { (*gc.ptr.as_ptr()).0.alloc_id };
31+
let alloc_id = unsafe { (*gc.ptr.as_ptr().as_ptr()).0.alloc_id };
3232
WeakGc {
3333
ptr: gc.ptr,
3434
alloc_id,
@@ -60,14 +60,16 @@ impl<'id, 'gc> MutationContext<'id, 'gc> {
6060
key: Gc<'gc, K>,
6161
value: Gc<'gc, V>,
6262
) -> Ephemeron<'id, K, V> {
63-
let key_alloc_id = unsafe { (*key.ptr.as_ptr()).0.alloc_id };
64-
self.collector.register_ephemeron(
65-
key.ptr.cast::<u8>(),
66-
key_alloc_id,
67-
value.ptr.cast::<u8>(),
68-
);
63+
let key_alloc_id = unsafe { (*key.ptr.as_ptr().as_ptr()).0.alloc_id };
64+
// SAFETY: GcBox<K> and GcBox<V> are erased to GcBox<()>, the collector
65+
// only reads the fixed size prefix fields via this pointer
66+
let erased_key: PoolPointer<'static, GcBox<()>> =
67+
unsafe { key.ptr.to_erased().to_typed_pool_pointer::<GcBox<()>>() };
68+
let erased_value: PoolPointer<'static, GcBox<()>> =
69+
unsafe { value.ptr.to_erased().to_typed_pool_pointer::<GcBox<()>>() };
70+
self.collector.register_ephemeron(erased_key, erased_value);
6971
Ephemeron {
70-
key_ptr: key.ptr,
72+
key_ptr: Some(key.ptr),
7173
key_alloc_id,
7274
value_ptr: value.ptr,
7375
_marker: core::marker::PhantomData,

oscars/src/collectors/mark_sweep_branded/tests/api_compliance.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ mod tests {
2525
// Assert offset is correct via offset_of-like macro conceptually
2626
let make_dummy = || RootNode::<i32> {
2727
link: RootLink::new(),
28-
gc_ptr: core::ptr::NonNull::dangling(),
28+
gc_ptr: unsafe {
29+
crate::alloc::mempool3::PoolPointer::from_raw(core::ptr::NonNull::dangling())
30+
},
2931
drop_fn: |_, _| {},
3032
collector_ptr: core::ptr::null(),
3133
_marker: core::marker::PhantomData,

0 commit comments

Comments
 (0)