Skip to content

Commit b5073be

Browse files
committed
mark_sweep: add new_cyclic_in parity semantics and tests
1 parent a050cf5 commit b5073be

12 files changed

Lines changed: 211 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>);
@@ -487,6 +491,38 @@ impl Collector for MarkSweepGarbageCollector {
487491
Ok(inner_ptr)
488492
}
489493

494+
fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
495+
&'gc self,
496+
) -> Result<PoolPointer<'gc, Ephemeron<K, ()>>, crate::alloc::mempool3::PoolAllocError> {
497+
if self.collect_needed.get() && !self.is_collecting.get() {
498+
self.collect_needed.set(false);
499+
self.collect();
500+
}
501+
502+
let ephemeron = Ephemeron::<K, ()>::new_empty(self.trace_color.get());
503+
504+
let mut alloc = self.allocator.borrow_mut();
505+
let inner_ptr = alloc.try_alloc(ephemeron)?;
506+
let needs_collect = !alloc.is_below_threshold();
507+
drop(alloc);
508+
509+
if needs_collect {
510+
self.collect_needed.set(true);
511+
}
512+
513+
let eph_ptr = inner_ptr
514+
.as_ptr()
515+
.cast::<PoolItem<Ephemeron<NonTraceable, NonTraceable>>>();
516+
517+
if self.is_collecting.get() {
518+
self.pending_ephemeron_queue.borrow_mut().push(eph_ptr);
519+
} else {
520+
self.ephemeron_queue.borrow_mut().push(eph_ptr);
521+
}
522+
523+
Ok(inner_ptr)
524+
}
525+
490526
fn track_weak_map(
491527
&self,
492528
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
@@ -149,6 +149,25 @@ fn ptr_eq_distinguishes_equal_values() {
149149
);
150150
}
151151

152+
#[test]
153+
fn new_cyclic_closure_cannot_upgrade_before_init() {
154+
let collector = &mut MarkSweepGarbageCollector::default()
155+
.with_page_size(256)
156+
.with_heap_threshold(512);
157+
158+
let mut saw_none = false;
159+
let gc = Gc::new_cyclic_in(collector, |weak| {
160+
saw_none = weak.upgrade().is_none();
161+
7u32
162+
});
163+
164+
assert!(
165+
saw_none,
166+
"weak should not upgrade during new_cyclic construction"
167+
);
168+
assert_eq!(*gc, 7u32, "new_cyclic should initialize final value");
169+
}
170+
152171
#[test]
153172
fn multi_gc() {
154173
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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,41 @@ impl MarkSweepGarbageCollector {
445445
Ok(inner_ptr)
446446
}
447447

448+
fn alloc_empty_ephemeron_node<
449+
'gc,
450+
K: crate::collectors::mark_sweep_arena2::trace::Trace + 'static,
451+
>(
452+
&'gc self,
453+
) -> Result<ArenaPointer<'gc, Ephemeron<K, ()>>, crate::alloc::arena2::ArenaAllocError> {
454+
if self.collect_needed.get() && !self.is_collecting.get() {
455+
self.collect_needed.set(false);
456+
self.collect();
457+
}
458+
459+
let ephemeron = Ephemeron::<K, ()>::new_empty(self.trace_color.get());
460+
461+
let mut alloc = self.allocator.borrow_mut();
462+
let inner_ptr = alloc.try_alloc(ephemeron)?;
463+
let needs_collect = !alloc.is_below_threshold();
464+
drop(alloc);
465+
466+
if needs_collect {
467+
self.collect_needed.set(true);
468+
}
469+
470+
let eph_ptr = inner_ptr
471+
.as_ptr()
472+
.cast::<ArenaHeapItem<Ephemeron<NonTraceable, NonTraceable>>>();
473+
474+
if self.is_collecting.get() {
475+
self.pending_ephemeron_queue.borrow_mut().push(eph_ptr);
476+
} else {
477+
self.ephemeron_queue.borrow_mut().push(eph_ptr);
478+
}
479+
480+
Ok(inner_ptr)
481+
}
482+
448483
fn track_weak_map(
449484
&self,
450485
map: core::ptr::NonNull<

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@ impl<T: Trace> Gc<T> {
3838
gc
3939
}
4040

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+
4163
/// Converts a `Gc` into a raw [`ArenaPointer`].
4264
pub fn into_raw(this: Self) -> ArenaPointer<'static, GcBox<T>> {
4365
let ptr = this.inner_ptr();

0 commit comments

Comments
 (0)