diff --git a/src/vm/mem/mod.rs b/src/vm/mem/mod.rs index d865a72..8793b1f 100644 --- a/src/vm/mem/mod.rs +++ b/src/vm/mem/mod.rs @@ -1,11 +1,16 @@ use std::any::TypeId; use std::cell::RefCell; +use std::fmt::{Debug, Formatter}; use std::marker::Unsize; use std::ops::{CoerceUnsized, Deref, DerefMut, Mul}; use std::sync::OnceLock; pub trait Trace { fn add_grays(&self, grays: &mut Vec); + + fn dynamic_size(&self) -> usize { + size_of_val(&self) + } } impl Trace for RefCell { @@ -24,6 +29,10 @@ impl Trace for String { fn add_grays(&self, grays: &mut Vec) { // no-op } + + fn dynamic_size(&self) -> usize { + self.len() + } } #[derive(Clone)] @@ -33,8 +42,7 @@ pub struct Gc { impl Gc { pub fn new(data: T) -> Self { - let size = size_of_val(&data); - let inner = Box::into_raw(Box::new(GcInner::new(data, size))); + let inner = Box::into_raw(Box::new(GcInner::new(data))); Self { inner } } @@ -56,6 +64,12 @@ impl Gc { } } +impl Debug for Gc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Gc@{:p}", self.inner) + } +} + pub struct GcRef { inner: *mut GcInner, } @@ -89,7 +103,7 @@ impl Drop for GcRef { } struct GcInner { - size: usize, + allocated_size: usize, color: GcColor, borrow_count: usize, next: Option, @@ -97,15 +111,19 @@ struct GcInner { } impl GcInner { - fn new(data: T, size: usize) -> Self { + fn new(data: T) -> Self { Self { - size, + allocated_size: size_of::(), color: GcColor::White, borrow_count: 0, next: None, data, } } + + fn dynamic_size(&self) -> usize { + self.data.dynamic_size() + } } #[derive(Clone, Copy, Debug)] @@ -116,12 +134,14 @@ pub enum GcColor { struct GcAnyMeta { trace: unsafe fn(*mut (), &mut Vec), - size: unsafe fn(*mut ()) -> usize, + allocated_size: unsafe fn(*mut ()) -> usize, + dynamic_size: unsafe fn(*mut ()) -> usize, borrow_count: unsafe fn(*mut ()) -> usize, color: unsafe fn(*mut ()) -> GcColor, set_color: unsafe fn(*mut (), GcColor), next: unsafe fn(*mut ()) -> Option, set_next: unsafe fn(*mut (), GcAny), + clear_next: unsafe fn(*mut ()), dealloc: unsafe fn(*mut ()), type_id: TypeId, } @@ -138,8 +158,12 @@ impl GcAny { (*(gc_inner as *mut GcInner)).data.add_grays(grays); } - unsafe fn size_impl(gc_inner: *mut ()) -> usize { - (*(gc_inner as *mut GcInner)).size + unsafe fn allocated_size_impl(gc_inner: *mut ()) -> usize { + (*(gc_inner as *mut GcInner)).allocated_size + } + + unsafe fn dynamic_size_impl(gc_inner: *mut ()) -> usize { + (*(gc_inner as *mut GcInner)).dynamic_size() } unsafe fn borrow_count_impl(gc_inner: *mut ()) -> usize { @@ -162,6 +186,10 @@ impl GcAny { (*(gc_inner as *mut GcInner)).next = Some(next); } + unsafe fn clear_next_impl(gc_inner: *mut ()) { + (*(gc_inner as *mut GcInner)).next = None; + } + unsafe fn dealloc_impl(gc_inner: *mut ()) { drop(Box::from_raw(gc_inner as *mut GcInner)); } @@ -169,12 +197,14 @@ impl GcAny { static VTABLE: OnceLock = OnceLock::new(); let v_table = VTABLE.get_or_init(|| GcAnyMeta { trace: trace_impl::, - size: size_impl::, + allocated_size: allocated_size_impl::, + dynamic_size: dynamic_size_impl::, borrow_count: borrow_count_impl::, color: color_impl::, set_color: set_color_impl::, next: next_impl::, set_next: set_next_impl::, + clear_next: clear_next_impl::, dealloc: dealloc_impl::, type_id: TypeId::of::(), }); @@ -190,8 +220,12 @@ impl GcAny { } } - fn size(&self) -> usize { - unsafe { (self.meta.size)(self.inner) } + fn allocated_size(&self) -> usize { + unsafe { (self.meta.allocated_size)(self.inner) } + } + + fn dynamic_size(&self) -> usize { + unsafe { (self.meta.dynamic_size)(self.inner) } } fn borrow_count(&self) -> usize { @@ -216,7 +250,11 @@ impl GcAny { unsafe { (self.meta.set_next)(self.inner, next) } } - fn dealloc(&mut self) { + fn clear_next(&mut self) { + unsafe { (self.meta.clear_next)(self.inner) } + } + + unsafe fn dealloc(&mut self) { unsafe { if self.borrow_count() > 0 { panic!("Attempt to dealloc a GcAny whose borrow count > 0"); @@ -238,6 +276,20 @@ impl GcAny { } } +impl Debug for GcAny { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "GcAny@{:p}", self.inner) + } +} + +impl PartialEq for GcAny { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for GcAny {} + enum DvmValue { Primitive(usize), Object(Gc>), @@ -280,15 +332,18 @@ impl GcHeap { } } - pub fn alloc(&mut self, t: T) -> Gc { + pub fn alloc(&mut self, stack: &Vec, t: T) -> Gc { + collect_garbage(stack, self); let gc = Gc::new(t); + let as_any = gc.as_any(); + let size = as_any.allocated_size(); + self.current_size += size; if self.tail.is_some() { - let tail = self.tail.take().unwrap(); - tail.set_next(gc.as_any()); - self.tail = Some(tail); + let tail = self.tail.replace(as_any.clone()).unwrap(); + tail.set_next(as_any); } else { - self.head = Some(gc.as_any()); - self.tail = Some(gc.as_any()); + self.head = Some(as_any.clone()); + self.tail = Some(as_any); } gc } @@ -304,8 +359,13 @@ impl GcHeap { } } - pub fn clear_head(&mut self) { + pub fn set_tail(&mut self, tail: GcAny) { + self.tail = Some(tail); + } + + pub fn clear_head_and_tail(&mut self) { self.head = None; + self.tail = None; } pub fn current_size(&self) -> usize { @@ -334,7 +394,7 @@ impl Drop for GcHeap { let mut current = self.head().take(); while let Some(gc_any) = &mut current { let next = gc_any.next(); - gc_any.dealloc(); + unsafe { gc_any.dealloc(); } current = next; } } @@ -368,22 +428,53 @@ fn collect_garbage(stack: &Vec, heap: &mut GcHeap) { let mut previous: Option = None; let mut current = heap.head(); - while let Some(ref mut current_gc_erased) = current { - if let GcColor::White = current_gc_erased.color() { - if let Some(ref previous_gc_erased) = previous { - // attach previous to next if both are Some - let next = current_gc_erased.next(); - if let Some(next_gc_erased) = next { - previous_gc_erased.set_next(next_gc_erased); + while let Some(mut current_gc) = current.take() { + if let GcColor::White = current_gc.color() { + // Must delete from linked-list and dealloc + // Case 0: Head, next None + // Case 1a: Head, next Some + // Case 1b: Non-head, next Some + // Case 2: Tail (next None) + + if current_gc == heap.head().unwrap() && current_gc.next().is_none() { + // Case 0 + heap.clear_head_and_tail(); + // no need to set current; it was already taken + // no need to set previous + } else if let Some(next_gc) = current_gc.next() { + if current_gc == heap.head().unwrap() { + // Case 1a + heap.set_head(next_gc); + current = heap.head(); // current is new head + // no previous since this is the head + } else { + // Case 1b + let previous_gc = previous.as_ref().unwrap(); + previous_gc.set_next(next_gc.clone()); + current = Some(next_gc); + // previous stays the same } + } else { + // Case 2 + let mut previous_gc = previous.as_mut().unwrap(); + previous_gc.clear_next(); + heap.set_tail(previous_gc.clone()); + // no need to set current since it was taken + // previous doesn't matter since we're done } - let size = current_gc_erased.size(); - heap.subtract_current_size(size); - collected_size = size; - current_gc_erased.dealloc(); + heap.subtract_current_size(current_gc.allocated_size()); + collected_size += current_gc.dynamic_size(); + unsafe { current_gc.dealloc(); } } else { - current_gc_erased.set_color(GcColor::White); - previous = Some(current_gc_erased.clone()); + current_gc.set_color(GcColor::White); + previous = Some(current_gc.clone()); + current = current_gc.next(); + } + } + + if let Ok(val) = std::env::var("DVM_MEM_DEBUG") { + if val == "1" { + println!("GC: collected_size: {}", collected_size); } } @@ -398,19 +489,33 @@ mod tests { fn simple_allocate() { let mut stack: Vec = vec![]; let mut heap = GcHeap::new(); - for i in 0..100 { - let o = heap.alloc(String::new()); - o.borrow().push_str("hello"); + for i in 0..100_000 { + let o = heap.alloc(&stack, String::new()); + o.borrow() + .push_str("hello world! long string! woot woot woot owo"); } } #[test] fn downcast_as_string() { + let stack = vec![]; let mut heap = GcHeap::new(); - let t = heap.alloc(String::from("hello world")); + let t = heap.alloc(&stack, String::from("hello world")); let a = t.as_any(); if let Some(s) = a.downcast::() { assert_eq!(*s.borrow(), "hello world"); } } + + #[test] + fn find_memory_leak() { + let stack = vec![]; + let mut heap = GcHeap::new(); + println!("alloc s0"); + let s0 = heap.alloc(&stack, String::from("s0")); + println!("alloc s1"); + let s1 = heap.alloc(&stack, String::from("s1")); + println!("alloc s2"); + let s2 = heap.alloc(&stack, String::from("s2")); + } }