Skip to content

Commit cc0ee35

Browse files
committed
mark_sweep: add new_cyclic_in parity semantics and tests
1 parent cb97ce4 commit cc0ee35

12 files changed

Lines changed: 216 additions & 0 deletions

File tree

oscars/src/collectors/mark_sweep/internals/ephemeron.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ impl<K: Trace, V: Trace> Ephemeron<K, V> {
6666
}
6767
}
6868

69+
impl<K: Trace> Ephemeron<K, ()> {
70+
pub(crate) fn new_empty(color: TraceColor) -> Self {
71+
Self {
72+
value: GcBox::new_in((), color),
73+
vtable: vtable_of::<K, ()>(),
74+
key: WeakGcBox::new_empty(),
75+
active: core::cell::Cell::new(true),
76+
}
77+
}
78+
79+
pub(crate) fn set_key(&self, key: &Gc<K>) {
80+
self.key.inner_ptr.set(Some(key.inner_ptr));
81+
}
82+
}
83+
6984
impl<K: Trace, V: Trace> Ephemeron<K, V> {
7085
pub(crate) fn trace_fn(&self) -> EphemeronTraceFn {
7186
self.vtable.trace_fn

oscars/src/collectors/mark_sweep/internals/gc_box.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ impl<T: Trace + Finalize + ?Sized> WeakGcBox<T> {
5050
}
5151
}
5252

53+
pub fn new_empty() -> Self {
54+
Self {
55+
inner_ptr: Cell::new(None),
56+
marker: PhantomData,
57+
}
58+
}
59+
5360
pub(crate) fn erased_inner_ptr(&self) -> Option<NonNull<GcBox<NonTraceable>>> {
5461
// SAFETY: `as_heap_ptr` returns a valid pointer to
5562
// `PoolItem` whose lifetime is tied to the pool

oscars/src/collectors/mark_sweep/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ pub trait Collector {
5151
value: V,
5252
) -> Result<PoolPointer<'gc, Ephemeron<K, V>>, PoolAllocError>;
5353

54+
fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
55+
&'gc self,
56+
) -> Result<PoolPointer<'gc, Ephemeron<K, ()>>, PoolAllocError>;
57+
5458
// Register a weak map with the GC so it can prune dead entries
5559
#[doc(hidden)]
5660
fn track_weak_map(&self, map: core::ptr::NonNull<dyn ErasedWeakMap>);
@@ -493,6 +497,38 @@ impl Collector for MarkSweepGarbageCollector {
493497
Ok(inner_ptr)
494498
}
495499

500+
fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
501+
&'gc self,
502+
) -> Result<PoolPointer<'gc, Ephemeron<K, ()>>, crate::alloc::mempool3::PoolAllocError> {
503+
if self.collect_needed.get() && !self.is_collecting.get() {
504+
self.collect_needed.set(false);
505+
self.collect();
506+
}
507+
508+
let ephemeron = Ephemeron::<K, ()>::new_empty(self.trace_color.get());
509+
510+
let mut alloc = self.allocator.borrow_mut();
511+
let inner_ptr = alloc.try_alloc(ephemeron)?;
512+
let needs_collect = !alloc.is_below_threshold();
513+
drop(alloc);
514+
515+
if needs_collect {
516+
self.collect_needed.set(true);
517+
}
518+
519+
let eph_ptr = inner_ptr
520+
.as_ptr()
521+
.cast::<PoolItem<Ephemeron<NonTraceable, NonTraceable>>>();
522+
523+
if self.is_collecting.get() {
524+
self.pending_ephemeron_queue.borrow_mut().push(eph_ptr);
525+
} else {
526+
self.ephemeron_queue.borrow_mut().push(eph_ptr);
527+
}
528+
529+
Ok(inner_ptr)
530+
}
531+
496532
fn track_weak_map(
497533
&self,
498534
map: core::ptr::NonNull<

oscars/src/collectors/mark_sweep/pointers/gc.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,26 @@ impl<T: Trace> Gc<T> {
3535
gc
3636
}
3737

38+
#[must_use]
39+
pub fn new_cyclic_in<C, F>(collector: &C, data_fn: F) -> Self
40+
where
41+
C: Collector,
42+
F: FnOnce(&crate::collectors::mark_sweep::WeakGc<T>) -> T,
43+
{
44+
let weak = unsafe {
45+
crate::collectors::mark_sweep::WeakGc::from_raw(
46+
collector
47+
.alloc_empty_ephemeron_node::<T>()
48+
.expect("Failed to allocate Ephemeron node")
49+
.extend_lifetime(),
50+
)
51+
};
52+
53+
let gc = Self::new_in(data_fn(&weak), collector);
54+
weak.set_key(&gc);
55+
gc
56+
}
57+
3858
/// Converts a `Gc` into a raw [`PoolPointer`].
3959
pub fn into_raw(this: Self) -> PoolPointer<'static, GcBox<T>> {
4060
let ptr = this.inner_ptr();

oscars/src/collectors/mark_sweep/pointers/weak.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,12 @@ impl<T: Trace> WeakGc<T> {
3434
pub fn upgrade(&self) -> Option<Gc<T>> {
3535
self.inner_ptr.as_inner_ref().upgrade()
3636
}
37+
38+
pub(crate) unsafe fn from_raw(inner_ptr: PoolPointer<'static, Ephemeron<T, ()>>) -> Self {
39+
Self { inner_ptr }
40+
}
41+
42+
pub(crate) fn set_key(&self, key: &Gc<T>) {
43+
self.inner_ptr.as_inner_ref().set_key(key);
44+
}
3745
}

oscars/src/collectors/mark_sweep/tests.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,25 @@ fn cast_ref_unchecked_preserves_identity_and_value() {
240240
);
241241
}
242242

243+
#[test]
244+
fn new_cyclic_closure_cannot_upgrade_before_init() {
245+
let collector = &mut MarkSweepGarbageCollector::default()
246+
.with_page_size(256)
247+
.with_heap_threshold(512);
248+
249+
let mut saw_none = false;
250+
let gc = Gc::new_cyclic_in(collector, |weak| {
251+
saw_none = weak.upgrade().is_none();
252+
7u32
253+
});
254+
255+
assert!(
256+
saw_none,
257+
"weak should not upgrade during new_cyclic construction"
258+
);
259+
assert_eq!(*gc, 7u32, "new_cyclic should initialize final value");
260+
}
261+
243262
#[test]
244263
fn multi_gc() {
245264
let collector = &mut MarkSweepGarbageCollector::default()

oscars/src/collectors/mark_sweep_arena2/internals/ephemeron.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ impl<K: Trace, V: Trace> Ephemeron<K, V> {
6262
}
6363
}
6464

65+
impl<K: Trace> Ephemeron<K, ()> {
66+
pub(crate) fn new_empty(color: TraceColor) -> Self {
67+
Self {
68+
value: GcBox::new_in((), color),
69+
vtable: vtable_of::<K, ()>(),
70+
key: WeakGcBox::new_empty(),
71+
active: core::cell::Cell::new(true),
72+
}
73+
}
74+
75+
pub(crate) fn set_key(&self, key: &Gc<K>) {
76+
self.key.inner_ptr.set(Some(key.inner_ptr));
77+
}
78+
}
79+
6580
impl<K: Trace, V: Trace> Ephemeron<K, V> {
6681
pub(crate) fn trace_fn(&self) -> EphemeronTraceFn {
6782
self.vtable.trace_fn

oscars/src/collectors/mark_sweep_arena2/internals/gc_box.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ impl<T: Trace + Finalize + ?Sized> WeakGcBox<T> {
4444
}
4545
}
4646

47+
pub fn new_empty() -> Self {
48+
Self {
49+
inner_ptr: Cell::new(None),
50+
marker: PhantomData,
51+
}
52+
}
53+
4754
pub(crate) fn erased_inner_ptr(&self) -> Option<NonNull<GcBox<NonTraceable>>> {
4855
// SAFETY: `&raw mut` prevents creating `&mut` reference into the
4956
// arena to avoid stacked borrows during Gc tracing

oscars/src/collectors/mark_sweep_arena2/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ pub trait Collector {
5757
crate::alloc::arena2::ArenaAllocError,
5858
>;
5959

60+
fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
61+
&'gc self,
62+
) -> Result<
63+
crate::alloc::arena2::ArenaPointer<'gc, internals::Ephemeron<K, ()>>,
64+
crate::alloc::arena2::ArenaAllocError,
65+
>;
66+
6067
// Register a weak map with the GC so it can prune dead entries.
6168
#[doc(hidden)]
6269
fn track_weak_map(&self, map: core::ptr::NonNull<dyn ErasedWeakMap>);
@@ -487,6 +494,38 @@ impl Collector for MarkSweepGarbageCollector {
487494
Ok(inner_ptr)
488495
}
489496

497+
fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
498+
&'gc self,
499+
) -> Result<ArenaPointer<'gc, Ephemeron<K, ()>>, crate::alloc::arena2::ArenaAllocError> {
500+
if self.collect_needed.get() && !self.is_collecting.get() {
501+
self.collect_needed.set(false);
502+
self.collect();
503+
}
504+
505+
let ephemeron = Ephemeron::<K, ()>::new_empty(self.trace_color.get());
506+
507+
let mut alloc = self.allocator.borrow_mut();
508+
let inner_ptr = alloc.try_alloc(ephemeron)?;
509+
let needs_collect = !alloc.is_below_threshold();
510+
drop(alloc);
511+
512+
if needs_collect {
513+
self.collect_needed.set(true);
514+
}
515+
516+
let eph_ptr = inner_ptr
517+
.as_ptr()
518+
.cast::<ArenaHeapItem<Ephemeron<NonTraceable, NonTraceable>>>();
519+
520+
if self.is_collecting.get() {
521+
self.pending_ephemeron_queue.borrow_mut().push(eph_ptr);
522+
} else {
523+
self.ephemeron_queue.borrow_mut().push(eph_ptr);
524+
}
525+
526+
Ok(inner_ptr)
527+
}
528+
490529
fn track_weak_map(&self, map: core::ptr::NonNull<dyn ErasedWeakMap>) {
491530
self.weak_maps.borrow_mut().push(map);
492531
}

oscars/src/collectors/mark_sweep_arena2/pointers/gc.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::alloc::arena2::{ArenaHeapItem, ArenaPointer, ErasedArenaPointer};
22

3+
use crate::collectors::mark_sweep_arena2::Collector;
34
use crate::collectors::mark_sweep_arena2::Finalize;
45
use crate::collectors::mark_sweep_arena2::internals::NonTraceable;
56
use crate::collectors::mark_sweep_arena2::{Trace, internals::GcBox};
@@ -37,6 +38,28 @@ impl<T: Trace> Gc<T> {
3738
gc
3839
}
3940

41+
#[must_use]
42+
pub fn new_cyclic_in<F>(
43+
collector: &crate::collectors::mark_sweep_arena2::MarkSweepGarbageCollector,
44+
data_fn: F,
45+
) -> Self
46+
where
47+
F: FnOnce(&crate::collectors::mark_sweep_arena2::WeakGc<T>) -> T,
48+
{
49+
let weak = unsafe {
50+
crate::collectors::mark_sweep_arena2::WeakGc::from_raw(
51+
collector
52+
.alloc_empty_ephemeron_node::<T>()
53+
.expect("Failed to allocate Ephemeron node")
54+
.extend_lifetime(),
55+
)
56+
};
57+
58+
let gc = Self::new_in(data_fn(&weak), collector);
59+
weak.set_key(&gc);
60+
gc
61+
}
62+
4063
/// Converts a `Gc` into a raw [`ArenaPointer`].
4164
pub fn into_raw(this: Self) -> ArenaPointer<'static, GcBox<T>> {
4265
let ptr = this.inner_ptr();

0 commit comments

Comments
 (0)