Last active
June 25, 2023 16:50
-
-
Save buttercrab/518479fea3b1bb6bf70beaf58ef453dd to your computer and use it in GitHub Desktop.
Simple GC in Rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::cell::RefCell; | |
use crate::gc::{collect, Gc, GcCtx, Trace}; | |
#[derive(Clone)] | |
struct Node { | |
id: usize, | |
next: RefCell<Option<Gc<Node>>>, | |
} | |
impl Drop for Node { | |
fn drop(&mut self) { | |
println!("drop {}", self.id); | |
} | |
} | |
impl Trace for Node { | |
fn trace(&self, ctx: &GcCtx) { | |
if let Some(next) = self.next.borrow().as_ref() { | |
ctx.mark(next) | |
} | |
} | |
} | |
fn main() { | |
{ | |
// foo1 -> foo2 -> foo1 | |
let foo1 = Gc::new(Node { | |
id: 0, | |
next: RefCell::new(None), | |
}); | |
let foo1_clone = foo1.clone(); | |
foo1_clone.unset_root(); | |
let foo2 = Gc::new(Node { | |
id: 1, | |
next: RefCell::new(Some(foo1_clone)), | |
}); | |
foo2.unset_root(); | |
*foo1.next.borrow_mut() = Some(foo2); | |
let _ = Gc::new(Node { | |
id: 2, | |
next: RefCell::new(None), | |
}); | |
collect(); | |
} | |
println!("---"); | |
collect(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::cell::{Cell, RefCell}; | |
use std::marker::PhantomData; | |
use std::ops::Deref; | |
use std::ptr::NonNull; | |
use std::rc::Rc; | |
pub trait Trace { | |
fn trace(&self, ctx: &GcCtx); | |
} | |
struct GlobalGc { | |
objects: Vec<NonNull<GcInner<dyn Trace>>>, | |
color: bool, | |
} | |
impl GlobalGc { | |
fn push(&mut self, object: NonNull<GcInner<dyn Trace>>) { | |
self.objects.push(object); | |
} | |
fn collect(&mut self) { | |
self.color = !self.color; | |
let ctx = GcCtx::new(self.color); | |
for object in &mut self.objects { | |
unsafe { | |
if object.as_ref().count > 0 { | |
object.as_mut().mark = self.color; | |
object.as_ref().value.trace(&ctx); | |
} | |
} | |
} | |
let mut i = 0; | |
while i < self.objects.len() { | |
let object = self.objects[i]; | |
unsafe { | |
if object.as_ref().count == 0 && object.as_ref().mark != self.color { | |
let t = self.objects.swap_remove(i); | |
drop(Box::from_raw(t.as_ptr())); | |
} else { | |
i += 1; | |
} | |
} | |
} | |
} | |
} | |
pub struct GcCtx { | |
color: bool, | |
} | |
impl GcCtx { | |
fn new(color: bool) -> GcCtx { | |
GcCtx { color } | |
} | |
pub fn mark<T: Trace + 'static>(&self, object: &Gc<T>) { | |
let mut inner = object.inner.get(); | |
unsafe { | |
if inner.as_ref().mark == self.color { | |
return; | |
} | |
inner.as_mut().mark = self.color; | |
inner.as_ref().value.trace(self); | |
} | |
} | |
} | |
thread_local! { | |
static LOCAL_GC: RefCell<GlobalGc> = RefCell::new(GlobalGc { | |
objects: Vec::new(), | |
color: false, | |
}); | |
} | |
pub fn collect() { | |
LOCAL_GC.with(|gc| { | |
let mut gc = gc.borrow_mut(); | |
gc.collect(); | |
}) | |
} | |
struct GcInner<T: ?Sized + 'static> { | |
mark: bool, | |
count: usize, | |
value: T, | |
} | |
impl<T> GcInner<T> { | |
fn new(value: T, mark: bool) -> GcInner<T> { | |
GcInner { | |
value, | |
mark, | |
count: 1, | |
} | |
} | |
} | |
pub struct Gc<T: 'static> { | |
inner: Cell<NonNull<GcInner<T>>>, | |
root: Cell<bool>, | |
marker: PhantomData<Rc<T>>, | |
} | |
impl<T: Trace> Gc<T> { | |
pub fn new(value: T) -> Gc<T> { | |
LOCAL_GC.with(|gc| { | |
let mut gc = gc.borrow_mut(); | |
let inner = NonNull::from(Box::leak(Box::new(GcInner::new(value, gc.color)))); | |
gc.push(inner); | |
Gc { | |
inner: Cell::new(inner), | |
root: Cell::new(true), | |
marker: PhantomData, | |
} | |
}) | |
} | |
pub fn set_root(&self) { | |
if self.root.get() { | |
return; | |
} | |
self.root.set(true); | |
let mut inner = self.inner.get(); | |
unsafe { | |
inner.as_mut().count += 1; | |
} | |
} | |
pub fn unset_root(&self) { | |
if !self.root.get() { | |
return; | |
} | |
self.root.set(false); | |
let mut inner = self.inner.get(); | |
unsafe { | |
inner.as_mut().count -= 1; | |
} | |
} | |
} | |
impl<T: Trace + Clone> Clone for Gc<T> { | |
fn clone(&self) -> Gc<T> { | |
let mut inner = self.inner.get(); | |
unsafe { | |
inner.as_mut().count += 1; | |
} | |
Gc { | |
inner: Cell::new(inner), | |
root: Cell::new(true), | |
marker: PhantomData, | |
} | |
} | |
} | |
impl<T: Trace> Deref for Gc<T> { | |
type Target = T; | |
fn deref(&self) -> &T { | |
let inner = self.inner.get(); | |
unsafe { &inner.as_ref().value } | |
} | |
} | |
impl<T> Drop for Gc<T> { | |
fn drop(&mut self) { | |
if self.root.get() { | |
let mut inner = self.inner.get(); | |
unsafe { | |
inner.as_mut().count -= 1; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment