Skip to content

Instantly share code, notes, and snippets.

@mystor
Last active November 1, 2023 17:43
Show Gist options
  • Save mystor/fa1141bfb30643289597 to your computer and use it in GitHub Desktop.
Save mystor/fa1141bfb30643289597 to your computer and use it in GitHub Desktop.
TraceCell
/// Will allocate a value. By default this value is rooted
unsafe fn magic_gc_allocate<T>(value: T) -> NonZero<*mut T> {
unimplemented!()
}
/// Mark the given pointer during a trace
/// Returns whether the pointer was already marked
unsafe fn magic_gc_mark<T>(ptr: *const T) -> bool {
unimplemented!()
}
/// Add the pointer as a GC root
unsafe fn magic_gc_add_root<T>(ptr: *const T) {
unimplemented!()
}
/// Remove the pointer as a GC root
unsafe fn magic_gc_remove_root<T>(ptr: *const T) {
unimplemented!()
}
/// The Trace trait which needs to be implemented on garbage collected objects
trait Trace {
/// Mark all contained Gcs
fn trace(&self);
/// Increment the root-count of all contained Gcs
unsafe fn root(&self);
/// Decrement the root-count of all contained Gcs
unsafe fn unroot(&self);
}
/// This is the internal data structure which holds a garbage collected
/// value, as well as the number of root requests which it has on it.
/// This object should always be allocated with it's new function
struct GcBox<T> {
_roots: Cell<usize>,
_value: T,
}
impl<T> GcBox {
/// Allocate a GcBox on the heap
fn new(value: T) -> NonZero<*mut GcBox<T>> {
magic_gc_allocate(GcBox {
_roots: Cell::new(1),
_value: value,
})
}
/// Get the value form the GcBox
fn value(&self) -> &T {
&self._value
}
/// Root the
fn root(&self) {
let roots = self._roots.get() + 1;
self._roots.set(roots);
if roots == 1 {
magic_gc_add_root(self as *const GcBox<T>);
}
}
fn unroot(&self) {
let roots = self._roots.get() - 1;
self._roots.set(roots);
if roots == 0 {
magic_gc_remove_root(self as *const GcBox<T>);
}
}
}
////////
// Gc //
////////
struct Gc<T> {
_ptr: NonZero<*const GcBox<T>>,
}
impl<T: Trace> Gc<T> {
pub fn new(value: T) -> Gc<T> {
unsafe {
// Allocate the box first
let ptr = GcBox::new(value);
// The thing which we are storing internally is no longer rooted!
(*ptr).value().unroot();
let gc = Gc { _ptr: ptr }
}
}
fn inner(&self) -> &GcBox<T> {
&*self._ptr
}
}
impl<T: Trace> Trace for Gc<T> {
fn trace(&self) {
// Only recurse if we haven't already marked this node
if !magic_gc_mark(self._ptr) {
self.inner().value().trace();
}
}
unsafe fn root(&self) {
self.inner().root();
}
unsafe fn unroot(&self) {
self.inner().unroot();
}
}
impl<T: Trace> Clone for Gc<T> {
fn clone(&self) -> Gc<T> {
unsafe {
self.root();
Gc { _ptr: self._ptr }
}
}
}
impl<T: Trace> Deref for Gc<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner().value()
}
}
////////////
// GcCell //
////////////
/// A mutable garbage collected pointer/cell hybrid
struct GcCell<T> {
_ptr: NonZero<*const GcBox<RefCell<T>>>,
}
impl <T: Trace> GcCell<T> {
pub fn new(value: T) -> GcCell<T> {
unsafe {
// Allocate the box first
let ptr = GcBox::new(RefCell::new(value));
// The thing which we are storing internally is no longer rooted!
(**ptr).value().borrow().unroot();
GcCell { _ptr: ptr }
}
}
fn inner(&self) -> &GcCellBox<T> {
unsafe { &(**self._ptr) }
}
pub fn borrow(&self) -> GcCellRef<T> {
self.inner().value().borrow()
}
pub fn borrow_mut(&self) -> GcCellRefMut<T> {
let inner = self.inner();
let val_ref = inner.value().borrow_mut();
// Ensure that the box remains alive for the lifetime of the GcCellRefMut
self.root();
// Root everything inside the box for the lifetime of the GcCellRefMut
val_ref.root();
GcCellRefMut {
_box: inner,
_ref: val_ref,
}
}
}
impl<T: Trace> Trace for GcCell<T> {
fn trace(&self) {
// Only recurse if we haven't already marked this node
if !magic_gc_mark(self._ptr) {
let inner = self.inner();
match inner.value().borrow_state() {
BorrowState::Writing => {
// In this case, the stuff inside is already rooted,
// so we don't have to do anything - we can just stop here
}
_ => {
self.inner().value().trace();
}
}
}
}
unsafe fn root(&self) {
self.inner().root();
}
unsafe fn unroot(&self) {
self.inner().unroot();
}
}
/// In the non-mutable case, nothing interesting needs to happen. We are just taking
/// a reference into the RefCell.
type GcCellRef<'a, T> = cell::Ref<'a, T>;
/// The GcCellRefMut struct acts as a RAII guard (like RefCell's RefMut), which
/// will provides unique mutable access to the value inside the GcCell while also
/// ensuring that the data isn't collected incorrectly
/// This means that it roots the internal box (to ensure that the pointer remains alive),
/// (although this is probably unnecessary - investigate).
/// as well as the data inside the box. (as it may be modified, or moved out of the object)
/// When this guard is present, it is not possible for the trace implementation to see inside
/// the object, so the object inside must be rooted to prevent it from being collected.
struct GcCellRefMut<'a, T> {
_box: &'a GcBox<RefCell<T>>,
_ref: ::std::cell::RefMut<'a, T>,
}
impl<'a, T: Trace> Deref for GcCellRefMut<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T { &*self._ref }
}
impl<'a, T: Trace> DerefMut for GcCellRefMut<'a, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T { &mut *self._ref }
}
impl<'a, T: Trace> Drop for GcCellRefMut<'a, T> {
fn drop(&mut self) {
// The box no longer has to be kept alive
self._box.unroot();
// The data is now within a Gc tree again
// we don't have to keep it alive explicitly any longer
self._ref.unroot();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment