1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
//! Implements a [GcCell] to allow mutating values
//! inside garbage collected objects.
//!
//! Normally garbage collected objects are immutable,
//! since their references are shared. It's typical
//! for collectors to want to trigger a write barrier
//! before writing to a field. All interior mutability
//! requires triggering appropriate write barriers,
//! which is unsafe.
//!
//! The `zerogc_derive` crate can generate setters
//! for fields that are wrapped in a [GcCell].
//! Just mark the field with `#[zerogc(mutable(public))]`
//! and it'll generate a safe wrapper.
use core::cell::Cell;
use zerogc_derive::unsafe_gc_impl;
use crate::{Trace, NullTrace, GcDirectBarrier, CollectorId, GcRebrand, GcSafe};
/// A `Cell` pointing to a garbage collected object.
///
/// This only supports mutating `NullTrace` types,
/// becuase garbage collected pointers need write barriers.
#[derive(Default, Clone, Debug)]
#[repr(transparent)]
pub struct GcCell<T: Trace + Copy>(Cell<T>);
impl<T: Trace + Copy> GcCell<T> {
/// Create a new cell
#[inline]
pub fn new(value: T) -> Self {
GcCell(Cell::new(value))
}
/// Get a mutable reference to this cell's value
///
/// This is safe because the `&mut self`
/// guarentees exclusive access to the cell.
#[inline]
pub fn get_mut(&mut self) -> &mut T {
self.0.get_mut()
}
/// Get a pointer to this cell's conent
#[inline]
pub fn as_ptr(&self) -> *mut T {
self.0.as_ptr()
}
/// Get the current value of this cell
#[inline]
pub fn get(&self) -> T {
self.0.get()
}
}
impl<T: NullTrace + Copy> GcCell<T> {
/// Change the interior of this type
/// to the specified type
///
/// The type must be `NullTrace` because
/// garbage collected
/// types need write barriers.
#[inline]
pub fn set(&self, value: T) {
self.0.set(value)
}
}
unsafe impl<'gc, OwningRef, Value> GcDirectBarrier<'gc, OwningRef> for GcCell<Value>
where Value: GcDirectBarrier<'gc, OwningRef> + Copy {
#[inline]
unsafe fn write_barrier(
&self, owner: &OwningRef,
field_offset: usize
) {
// NOTE: We are direct write because `Value` is stored inline
self.get().write_barrier(owner, field_offset)
}
}
unsafe_gc_impl!(
target => GcCell<T>,
params => [T: Trace + Copy],
NEEDS_TRACE => T::NEEDS_TRACE,
// T is Copy, so it doesn't need to be dropped
NEEDS_DROP => false,
bounds => {
GcSafe => { where T: GcSafe<'gc, Id> + Copy },
Trace => { where T: Trace + Copy },
// NOTE: TraceImmutable requires a 'NullTrace' for interior mutability
TraceImmutable => { where T: NullTrace + Copy },
GcRebrand => { where T: Trace + Copy + GcRebrand<'new_gc, Id>, Id: CollectorId,T::Branded: Copy + Trace }
},
branded_type => GcCell<T::Branded>,
null_trace => { where T: Copy + NullTrace },
trace_mut => |self, visitor| {
/*
* GcCell can only support mutating types that are `NullTrace`,
* because garbage collected types need write barriers.
*
* However, this is already enforced by the bounds of `GcCell::set`,
* so we don't need to verify here.
* In other words is possible to safely trace a `GcCell`
* with a garbage collected type, as long as it is never mutated.
*/
visitor.trace(self.get_mut())
},
trace_immutable => |self, visitor| {
/*
* See Trace documentation on the safety of mutation
*
* We require `NullTrace` in order to `set` our internals.
*/
let mut value = self.get();
visitor.trace(&mut value)?;
self.set(value);
Ok(())
}
);