use crate::{
gc::with_current_context,
trace::{Trace, Tracer},
};
use std::cell::UnsafeCell;
pub struct GcCell<T> {
value: UnsafeCell<T>,
}
impl<T: Trace + Copy> GcCell<T> {
#[inline]
pub fn new(value: T) -> Self {
Self {
value: UnsafeCell::new(value),
}
}
pub fn get(&self) -> T {
unsafe { *self.value.get() }
}
pub fn set(&self, new_value: T) {
unsafe {
let new_ref = &new_value;
with_current_context(|ctx| {
if ctx.heap.check_is_marking_and_increment_busy() {
new_ref.trace(&ctx.local_gray);
ctx.heap.merge_work(&ctx.local_gray);
ctx.heap.decrement_busy_marking();
}
});
*self.value.get() = new_value;
}
}
}
impl<T> std::fmt::Debug for GcCell<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GcCell").finish_non_exhaustive()
}
}
unsafe impl<T: Trace> Trace for GcCell<T> {
fn trace(&self, tracer: &Tracer) {
unsafe {
(*self.value.get()).trace(tracer);
}
}
}
unsafe impl<T: Send> Send for GcCell<T> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::GcContext;
#[test]
fn test_gcptrcell_basic() {
let ctx = GcContext::new();
let value1 = ctx.allocate(10);
let value2 = ctx.allocate(20);
let cell = GcCell::new(value1.as_ptr());
let retrieved = unsafe { cell.get().root() };
assert_eq!(*retrieved, 10);
cell.set(value2.as_ptr());
let retrieved = unsafe { cell.get().root() };
assert_eq!(*retrieved, 20);
}
#[test]
fn test_gcptrcell_write_barrier() {
let ctx = GcContext::off();
let value1 = ctx.allocate(10);
let value2_unrooted = ctx.allocate(20).as_ptr();
let cell_ptr = ctx.allocate(GcCell::new(value1.as_ptr()));
let old_allocated = ctx.heap().bytes_allocated();
ctx.heap().try_mark_full();
assert!(
unsafe { &*value2_unrooted.header_ptr() }.is_white(),
"Value2 should still be white here"
);
cell_ptr.set(value2_unrooted);
assert!(
!unsafe { &*value2_unrooted.header_ptr() }.is_white(),
"Value2 is now gray after write barrier"
);
ctx.heap().sweep_and_finish();
let new_allocated = ctx.heap().bytes_allocated();
assert_eq!(
new_allocated, old_allocated,
"No allocations should be freed"
);
assert_eq!(unsafe { *value2_unrooted.as_ptr() }, 20);
}
}