Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions oscars/src/collectors/mark_sweep/internals/ephemeron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ impl<K: Trace, V: Trace> Ephemeron<K, V> {
}
}

impl<K: Trace> Ephemeron<K, ()> {
pub(crate) fn new_empty(color: TraceColor) -> Self {
Self {
value: GcBox::new_in((), color),
vtable: vtable_of::<K, ()>(),
key: WeakGcBox::new_empty(),
active: core::cell::Cell::new(true),
}
}

pub(crate) fn set_key(&self, key: &Gc<K>) {
self.key.inner_ptr.set(Some(key.inner_ptr));
}
}

impl<K: Trace, V: Trace> Ephemeron<K, V> {
pub(crate) fn trace_fn(&self) -> EphemeronTraceFn {
self.vtable.trace_fn
Expand Down
7 changes: 7 additions & 0 deletions oscars/src/collectors/mark_sweep/internals/gc_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ impl<T: Trace + Finalize + ?Sized> WeakGcBox<T> {
}
}

pub fn new_empty() -> Self {
Self {
inner_ptr: Cell::new(None),
marker: PhantomData,
}
}

pub(crate) fn erased_inner_ptr(&self) -> Option<NonNull<GcBox<NonTraceable>>> {
// SAFETY: `as_heap_ptr` returns a valid pointer to
// `PoolItem` whose lifetime is tied to the pool
Expand Down
36 changes: 36 additions & 0 deletions oscars/src/collectors/mark_sweep/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub trait Collector {
value: V,
) -> Result<PoolPointer<'gc, Ephemeron<K, V>>, PoolAllocError>;

fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
&'gc self,
) -> Result<PoolPointer<'gc, Ephemeron<K, ()>>, PoolAllocError>;

// Register a weak map with the GC so it can prune dead entries
#[doc(hidden)]
fn track_weak_map(&self, map: core::ptr::NonNull<dyn ErasedWeakMap>);
Expand Down Expand Up @@ -487,6 +491,38 @@ impl Collector for MarkSweepGarbageCollector {
Ok(inner_ptr)
}

fn alloc_empty_ephemeron_node<'gc, K: Trace + 'static>(
&'gc self,
) -> Result<PoolPointer<'gc, Ephemeron<K, ()>>, crate::alloc::mempool3::PoolAllocError> {
if self.collect_needed.get() && !self.is_collecting.get() {
self.collect_needed.set(false);
self.collect();
}

let ephemeron = Ephemeron::<K, ()>::new_empty(self.trace_color.get());

let mut alloc = self.allocator.borrow_mut();
let inner_ptr = alloc.try_alloc(ephemeron)?;
let needs_collect = !alloc.is_below_threshold();
drop(alloc);

if needs_collect {
self.collect_needed.set(true);
}

let eph_ptr = inner_ptr
.as_ptr()
.cast::<PoolItem<Ephemeron<NonTraceable, NonTraceable>>>();

if self.is_collecting.get() {
self.pending_ephemeron_queue.borrow_mut().push(eph_ptr);
} else {
self.ephemeron_queue.borrow_mut().push(eph_ptr);
}

Ok(inner_ptr)
}

fn track_weak_map(
&self,
map: core::ptr::NonNull<
Expand Down
20 changes: 20 additions & 0 deletions oscars/src/collectors/mark_sweep/pointers/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ impl<T: Trace> Gc<T> {
gc
}

#[must_use]
pub fn new_cyclic_in<C, F>(collector: &C, data_fn: F) -> Self
where
C: Collector,
F: FnOnce(&crate::collectors::mark_sweep::WeakGc<T>) -> T,
{
let weak = unsafe {
crate::collectors::mark_sweep::WeakGc::from_raw(
collector
.alloc_empty_ephemeron_node::<T>()
.expect("Failed to allocate Ephemeron node")
.extend_lifetime(),
)
};

let gc = Self::new_in(data_fn(&weak), collector);
weak.set_key(&gc);
gc
}

/// Converts a `Gc` into a raw [`PoolPointer`].
pub fn into_raw(this: Self) -> PoolPointer<'static, GcBox<T>> {
let ptr = this.inner_ptr();
Expand Down
8 changes: 8 additions & 0 deletions oscars/src/collectors/mark_sweep/pointers/weak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,12 @@ impl<T: Trace> WeakGc<T> {
pub fn upgrade(&self) -> Option<Gc<T>> {
self.inner_ptr.as_inner_ref().upgrade()
}

pub(crate) unsafe fn from_raw(inner_ptr: PoolPointer<'static, Ephemeron<T, ()>>) -> Self {
Self { inner_ptr }
}

pub(crate) fn set_key(&self, key: &Gc<T>) {
self.inner_ptr.as_inner_ref().set_key(key);
}
}
19 changes: 19 additions & 0 deletions oscars/src/collectors/mark_sweep/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,25 @@ fn ptr_eq_distinguishes_equal_values() {
);
}

#[test]
fn new_cyclic_closure_cannot_upgrade_before_init() {
let collector = &mut MarkSweepGarbageCollector::default()
.with_page_size(256)
.with_heap_threshold(512);

let mut saw_none = false;
let gc = Gc::new_cyclic_in(collector, |weak| {
saw_none = weak.upgrade().is_none();
7u32
});

assert!(
saw_none,
"weak should not upgrade during new_cyclic construction"
);
assert_eq!(*gc, 7u32, "new_cyclic should initialize final value");
}

#[test]
fn multi_gc() {
let collector = &mut MarkSweepGarbageCollector::default()
Expand Down
15 changes: 15 additions & 0 deletions oscars/src/collectors/mark_sweep_arena2/internals/ephemeron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ impl<K: Trace, V: Trace> Ephemeron<K, V> {
}
}

impl<K: Trace> Ephemeron<K, ()> {
pub(crate) fn new_empty(color: TraceColor) -> Self {
Self {
value: GcBox::new_in((), color),
vtable: vtable_of::<K, ()>(),
key: WeakGcBox::new_empty(),
active: core::cell::Cell::new(true),
}
}

pub(crate) fn set_key(&self, key: &Gc<K>) {
self.key.inner_ptr.set(Some(key.inner_ptr));
}
}

impl<K: Trace, V: Trace> Ephemeron<K, V> {
pub(crate) fn trace_fn(&self) -> EphemeronTraceFn {
self.vtable.trace_fn
Expand Down
7 changes: 7 additions & 0 deletions oscars/src/collectors/mark_sweep_arena2/internals/gc_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ impl<T: Trace + Finalize + ?Sized> WeakGcBox<T> {
}
}

pub fn new_empty() -> Self {
Self {
inner_ptr: Cell::new(None),
marker: PhantomData,
}
}

pub(crate) fn erased_inner_ptr(&self) -> Option<NonNull<GcBox<NonTraceable>>> {
// SAFETY: `&raw mut` prevents creating `&mut` reference into the
// arena to avoid stacked borrows during Gc tracing
Expand Down
35 changes: 35 additions & 0 deletions oscars/src/collectors/mark_sweep_arena2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,41 @@ impl MarkSweepGarbageCollector {
Ok(inner_ptr)
}

fn alloc_empty_ephemeron_node<
'gc,
K: crate::collectors::mark_sweep_arena2::trace::Trace + 'static,
>(
&'gc self,
) -> Result<ArenaPointer<'gc, Ephemeron<K, ()>>, crate::alloc::arena2::ArenaAllocError> {
if self.collect_needed.get() && !self.is_collecting.get() {
self.collect_needed.set(false);
self.collect();
}

let ephemeron = Ephemeron::<K, ()>::new_empty(self.trace_color.get());

let mut alloc = self.allocator.borrow_mut();
let inner_ptr = alloc.try_alloc(ephemeron)?;
let needs_collect = !alloc.is_below_threshold();
drop(alloc);

if needs_collect {
self.collect_needed.set(true);
}

let eph_ptr = inner_ptr
.as_ptr()
.cast::<ArenaHeapItem<Ephemeron<NonTraceable, NonTraceable>>>();

if self.is_collecting.get() {
self.pending_ephemeron_queue.borrow_mut().push(eph_ptr);
} else {
self.ephemeron_queue.borrow_mut().push(eph_ptr);
}

Ok(inner_ptr)
}

fn track_weak_map(
&self,
map: core::ptr::NonNull<
Expand Down
22 changes: 22 additions & 0 deletions oscars/src/collectors/mark_sweep_arena2/pointers/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ impl<T: Trace> Gc<T> {
gc
}

#[must_use]
pub fn new_cyclic_in<F>(
collector: &crate::collectors::mark_sweep_arena2::MarkSweepGarbageCollector,
data_fn: F,
) -> Self
where
F: FnOnce(&crate::collectors::mark_sweep_arena2::WeakGc<T>) -> T,
{
let weak = unsafe {
crate::collectors::mark_sweep_arena2::WeakGc::from_raw(
collector
.alloc_empty_ephemeron_node::<T>()
.expect("Failed to allocate Ephemeron node")
.extend_lifetime(),
)
};

let gc = Self::new_in(data_fn(&weak), collector);
weak.set_key(&gc);
gc
}

/// Converts a `Gc` into a raw [`ArenaPointer`].
pub fn into_raw(this: Self) -> ArenaPointer<'static, GcBox<T>> {
let ptr = this.inner_ptr();
Expand Down
8 changes: 8 additions & 0 deletions oscars/src/collectors/mark_sweep_arena2/pointers/weak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ impl<T: Trace> WeakGc<T> {
pub fn upgrade(&self) -> Option<Gc<T>> {
self.inner_ptr.as_inner_ref().upgrade()
}

pub(crate) unsafe fn from_raw(inner_ptr: ArenaPointer<'static, Ephemeron<T, ()>>) -> Self {
Self { inner_ptr }
}

pub(crate) fn set_key(&self, key: &Gc<T>) {
self.inner_ptr.as_inner_ref().set_key(key);
}
}
19 changes: 19 additions & 0 deletions oscars/src/collectors/mark_sweep_arena2/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,25 @@ fn ptr_eq_distinguishes_equal_values() {
);
}

#[test]
fn new_cyclic_closure_cannot_upgrade_before_init() {
let collector = &mut MarkSweepGarbageCollector::default()
.with_arena_size(256)
.with_heap_threshold(512);

let mut saw_none = false;
let gc = Gc::new_cyclic_in(collector, |weak| {
saw_none = weak.upgrade().is_none();
7u32
});

assert!(
saw_none,
"weak should not upgrade during new_cyclic construction"
);
assert_eq!(*gc, 7u32, "new_cyclic should initialize final value");
}

#[test]
fn multi_gc() {
let collector = &mut MarkSweepGarbageCollector::default()
Expand Down