From ba8f854b28256ebc19d39a6551d93c61b958e9ac Mon Sep 17 00:00:00 2001 From: bad Date: Sat, 7 May 2022 20:02:50 +0200 Subject: [PATCH] Start working on the GC --- Cargo.lock | 4 ++ Cargo.toml | 5 ++ gc/Cargo.toml | 8 +++ gc/src/lib.rs | 143 +++++++++++++++++++++++++++++++++++++++++++++ pre-commit-hook.sh | 6 +- 5 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 gc/Cargo.toml create mode 100644 gc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a237607..28d81fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,6 +240,10 @@ dependencies = [ "syn", ] +[[package]] +name = "gc" +version = "0.1.0" + [[package]] name = "gimli" version = "0.26.1" diff --git a/Cargo.toml b/Cargo.toml index e6ff1a6..c5ef5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,11 @@ name = "crftng-intrprtrs" version = "0.1.0" edition = "2021" +[workspace] +members = [ + "gc" +] + [dependencies] clap = { version = "3.1.9", features = ["derive"] } color-eyre = "0.6.1" diff --git a/gc/Cargo.toml b/gc/Cargo.toml new file mode 100644 index 0000000..dead181 --- /dev/null +++ b/gc/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "gc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/gc/src/lib.rs b/gc/src/lib.rs new file mode 100644 index 0000000..97fea36 --- /dev/null +++ b/gc/src/lib.rs @@ -0,0 +1,143 @@ +#![feature(drain_filter)] + +use std::collections::HashSet; + +struct Allocation { + ptr: *mut (), + drop: unsafe fn(*mut ()), +} + +impl Allocation { + fn new(x: T) -> Self { + let alloc = Box::new(x); + let ptr = Box::into_raw(alloc) as *mut (); + + let drop = |ptr| unsafe { + Box::from_raw(ptr as *mut T); + }; + + Self { ptr, drop } + } +} + +impl Drop for Allocation { + fn drop(&mut self) { + unsafe { (self.drop)(self.ptr) }; + } +} + +#[derive(Default)] +pub struct GCAllocator { + allocations: Vec, +} + +impl GCAllocator { + #[inline(always)] + pub fn alloc(&mut self, x: T) -> *mut T { + let alloc = Allocation::new(x); + let ptr = alloc.ptr as *mut T; + self.allocations.push(alloc); + ptr + } + + pub fn gc(&mut self, root: &T) { + // Mark + let mut tracer = GCTracer::with_capacity(self.allocations.len()); + tracer.mark_reachable_rec(root); + + // And sweep + self.allocations + .drain_filter(|a| !tracer.accessible.contains(&(a.ptr as *const ()))); + } +} + +pub struct GCTracer { + accessible: HashSet<*const ()>, +} + +impl GCTracer { + fn with_capacity(cap: usize) -> Self { + Self { + accessible: HashSet::with_capacity(cap), + } + } + + pub fn mark_reachable(&mut self, obj: &T) { + self.accessible.insert(obj as *const T as *const ()); + } + + pub fn mark_reachable_rec(&mut self, obj: &T) { + if !self.accessible.contains(&(obj as *const T as *const ())) { + self.accessible.insert(obj as *const T as *const ()); + obj.trace(self); + } + } +} + +/// +/// # Safety +/// Implementors of the trait *need* to ensure that every reachable reference gets marked as +/// reachable with mark_reachable or mark_reachable_rec. +pub unsafe trait GCTrace { + fn trace(&self, tracer: &mut GCTracer); +} + +unsafe impl GCTrace for [T] +where + T: GCTrace, +{ + fn trace(&self, tracer: &mut GCTracer) { + for item in self { + item.trace(tracer) + } + } +} + +unsafe impl GCTrace for () { + fn trace(&self, _tracer: &mut GCTracer) {} +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicBool, Ordering}; + + struct GCRoot<'a>(*mut GotDropped<'a>); + + unsafe impl<'a> GCTrace for GCRoot<'a> { + fn trace(&self, tracer: &mut GCTracer) { + tracer.mark_reachable(unsafe { &*self.0 }) + } + } + + struct GotDropped<'a>(&'a AtomicBool); + impl<'a> Drop for GotDropped<'a> { + fn drop(&mut self) { + self.0.store(true, Ordering::Release); + } + } + + #[test] + fn it_works() { + let dropped = AtomicBool::from(false); + let got_dropped = GotDropped(&dropped); + + let mut gc = GCAllocator::default(); + gc.alloc(got_dropped); + gc.gc(&()); + assert!(dropped.load(Ordering::Acquire)); + + let dropped = AtomicBool::from(false); + let got_dropped = gc.alloc(GotDropped(&dropped)); + let gc_root = gc.alloc(GCRoot(got_dropped)); + unsafe { + gc.gc(&*gc_root); + gc.gc(&*gc_root); + gc.gc(&*gc_root); + gc.gc(&*gc_root); + }; + assert!(!dropped.load(Ordering::Acquire)); + gc.gc(&()); + assert!(dropped.load(Ordering::Acquire)); + } +} diff --git a/pre-commit-hook.sh b/pre-commit-hook.sh index 34f56aa..206e58f 100755 --- a/pre-commit-hook.sh +++ b/pre-commit-hook.sh @@ -5,20 +5,20 @@ set -eu -if ! cargo fmt -- --check +if ! cargo fmt --all -- --check then echo "There are some code style issues." echo "Run cargo fmt first." exit 1 fi -if ! cargo clippy --all-targets -- -D warnings +if ! cargo clippy --all-targets --workspace -- -D warnings then echo "There are some clippy issues." exit 1 fi -if ! cargo test +if ! cargo test --workspace then echo "There are some test issues." exit 1