From ad8946245119e7b24737e21703f780a545f2e792 Mon Sep 17 00:00:00 2001 From: Greg Hawkins <162815+gmorpheme@users.noreply.github.com> Date: Sun, 21 Jan 2024 15:17:06 +0000 Subject: [PATCH] First draft new GC feature. --- Cargo.toml | 3 + src/eval/machine/cont.rs | 19 ++--- src/eval/machine/env.rs | 14 +--- src/eval/machine/mod.rs | 7 +- src/eval/machine/vm.rs | 45 ++++++++++-- src/eval/memory/array.rs | 6 +- src/eval/memory/block.rs | 16 ++++ src/eval/memory/bump.rs | 146 +++++++++++++++++++++++++++++++++++-- src/eval/memory/collect.rs | 78 +++++++++++++++++--- src/eval/memory/heap.rs | 101 ++++++++++++++++--------- src/eval/memory/mod.rs | 2 +- src/eval/memory/syntax.rs | 36 ++++----- src/eval/stg/mod.rs | 3 + src/eval/stg/testing.rs | 3 +- 14 files changed, 369 insertions(+), 110 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e4acc6..9005a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,9 @@ version = "0.2.0" authors = ["gmorpheme "] edition = "2021" +[unstable] +feature="core_intrinsics" + [build-dependencies] lalrpop = { version = "0.19.8", features = ["lexer"] } diff --git a/src/eval/machine/cont.rs b/src/eval/machine/cont.rs index b4f9c64..813b36a 100644 --- a/src/eval/machine/cont.rs +++ b/src/eval/machine/cont.rs @@ -101,19 +101,16 @@ impl GcScannable for Continuation { marker: &'b mut CollectorHeapView<'a>, ) -> Vec> { let mut grey = vec![]; - dbg!("cont"); match self { Continuation::Branch { branches, fallback, environment, } => { - if let Some(data) = branches.allocated_data() { - if marker.mark(data) { - for (_tag, branch) in branches.iter() { - marker.mark(*branch); - grey.push(ScanPtr::from_non_null(scope, *branch)); - } + if marker.mark_array(branches) { + for (_tag, branch) in branches.iter() { + marker.mark(*branch); + grey.push(ScanPtr::from_non_null(scope, *branch)); } } @@ -136,11 +133,9 @@ impl GcScannable for Continuation { } } Continuation::ApplyTo { args } => { - if let Some(data) = args.allocated_data() { - if marker.mark(data) { - for arg in args.iter() { - grey.push(ScanPtr::new(scope, arg)); - } + if marker.mark_array(args) { + for arg in args.iter() { + grey.push(ScanPtr::new(scope, arg)); } } } diff --git a/src/eval/machine/env.rs b/src/eval/machine/env.rs index 91f4307..3846039 100644 --- a/src/eval/machine/env.rs +++ b/src/eval/machine/env.rs @@ -23,12 +23,6 @@ where impl StgObject for Closing where S: Copy {} -impl Default for Closing { - fn default() -> Self { - Self(Default::default(), RefPtr::dangling()) - } -} - impl InfoTable for Closing where S: Copy, @@ -370,11 +364,9 @@ impl GcScannable for EnvFrame { let bindings = &self.bindings; - if let Some(data) = bindings.allocated_data() { - if marker.mark(data) { - for binding in bindings.iter() { - grey.push(ScanPtr::new(scope, binding)); - } + if marker.mark_array(bindings) { + for binding in bindings.iter() { + grey.push(ScanPtr::new(scope, binding)); } } diff --git a/src/eval/machine/mod.rs b/src/eval/machine/mod.rs index 972f5be..7751cb2 100644 --- a/src/eval/machine/mod.rs +++ b/src/eval/machine/mod.rs @@ -69,7 +69,12 @@ pub fn standard_machine<'a>( emitter: Box, runtime: &'a dyn Runtime, ) -> Result, ExecutionError> { - let mut machine = Machine::new(emitter, settings.trace_steps, settings.heap_limit_mib); + let mut machine = Machine::new( + emitter, + settings.trace_steps, + settings.heap_limit_mib, + settings.heap_dump_at_gc, + ); let (root_env, globals, closure) = { machine.mutate(Initialiser { syntax, runtime }, ())? }; machine.initialise(root_env, globals, closure, runtime.intrinsics())?; diff --git a/src/eval/machine/vm.rs b/src/eval/machine/vm.rs index 18afee5..7c707fa 100644 --- a/src/eval/machine/vm.rs +++ b/src/eval/machine/vm.rs @@ -621,14 +621,16 @@ impl GcScannable for MachineState { ) -> Vec> { let mut grey = vec![]; - marker.mark(self.globals); - grey.push(ScanPtr::from_non_null(scope, self.globals)); + if marker.mark(self.globals) { + grey.push(ScanPtr::from_non_null(scope, self.globals)); + } grey.push(ScanPtr::new(scope, &self.closure)); for cont in &self.stack { - marker.mark(*cont); - grey.push(ScanPtr::from_non_null(scope, *cont)); + if marker.mark(*cont) { + grey.push(ScanPtr::from_non_null(scope, *cont)); + } } grey @@ -637,6 +639,7 @@ impl GcScannable for MachineState { pub struct MachineSettings { pub trace_steps: bool, + pub dump_heap: bool, } /// An STG machine variant using cactus environment @@ -668,13 +671,19 @@ impl<'a> Machine<'a> { emitter: Box, trace_steps: bool, heap_limit_mib: Option, + dump_heap: bool, ) -> Self { Machine { - heap: Heap::with_limit(heap_limit_mib.unwrap_or(256)), + heap: heap_limit_mib + .map(Heap::with_limit) + .unwrap_or_else(|| Heap::new()), state: Default::default(), intrinsics: vec![], emitter, - settings: MachineSettings { trace_steps }, + settings: MachineSettings { + trace_steps, + dump_heap, + }, metrics: Metrics::default(), clock: Clock::default(), } @@ -789,16 +798,36 @@ impl<'a> Machine<'a> { pub fn run(&mut self, limit: Option) -> Result, ExecutionError> { self.clock.switch(ThreadOccupation::Mutator); + let gc_check_freq = 500; + while !self.state.terminated { if let Some(limit) = limit { if self.metrics.ticks() as usize >= limit { return Err(ExecutionError::DidntTerminate(limit)); } } + + if self.metrics.ticks() % gc_check_freq == 0 { + if self.heap().policy_requires_collection() { + collect::collect( + &self.state, + &mut self.heap, + &mut self.clock, + self.settings.dump_heap, + ); + self.clock.switch(ThreadOccupation::Mutator); + } + } + self.step()?; } - collect::collect(&self.state, &mut self.heap, &mut self.clock); + collect::collect( + &self.state, + &mut self.heap, + &mut self.clock, + self.settings.dump_heap, + ); self.clock.stop(); @@ -990,7 +1019,7 @@ pub mod tests { } fn machine(syn: Rc) -> Machine<'static> { - let mut m = Machine::new(Box::new(DebugEmitter::default()), true); + let mut m = Machine::new(Box::new(DebugEmitter::default()), true, None, false); let blank = m.mutate(Init, ()).unwrap(); let closure = m.mutate(Load { syntax: syn }, blank).unwrap(); m.initialise(blank, blank, closure, vec![]).unwrap(); diff --git a/src/eval/memory/array.rs b/src/eval/memory/array.rs index b59b291..21c1d24 100644 --- a/src/eval/memory/array.rs +++ b/src/eval/memory/array.rs @@ -253,6 +253,8 @@ impl Array { // TODO: needs guard /// Read only iterator pub fn iter(&self) -> std::slice::Iter { + debug_assert_ne!(self.length, usize::MAX); + debug_assert!(self.length < u32::MAX as usize); self.as_slice().iter() } @@ -297,8 +299,8 @@ impl Array { // Return pointer to allocated data for navigating to header (and // marking during GC) - pub fn allocated_data(&self) -> Option> { - self.data.ptr + pub fn allocated_data(&self) -> Option> { + self.data.ptr.map(|p| p.cast()) } } diff --git a/src/eval/memory/block.rs b/src/eval/memory/block.rs index 4a5981e..69d5e34 100644 --- a/src/eval/memory/block.rs +++ b/src/eval/memory/block.rs @@ -23,6 +23,7 @@ pub enum BlockError { } impl Block { + /// Defer to global allocatore to create new block of given size pub fn new(size: usize) -> Result { if !size.is_power_of_two() { Err(BlockError::BadSize) @@ -48,11 +49,26 @@ impl Block { if ptr.is_null() { Err(BlockError::OOM) } else { + if cfg!(debug_assertions) { + // fill memory with 0xff to aid debugging + let mem = std::slice::from_raw_parts_mut(ptr, size); + mem.fill(0xff); + } Ok(NonNull::new_unchecked(ptr)) } } } + /// Fill areas that are meant to be dead with 0xff to aid debugging + #[cfg(debug_assertions)] + pub fn fill(&self, offset_bytes: usize, size_bytes: usize) { + unsafe { + let start = self.ptr.as_ptr().add(offset_bytes); + let mem = std::slice::from_raw_parts_mut(start, size_bytes); + mem.fill(0xff); + } + } + fn dealloc_block(ptr: NonNull, size: usize) { unsafe { dealloc(ptr.as_ptr(), Layout::from_size_align_unchecked(size, size)) } } diff --git a/src/eval/memory/bump.rs b/src/eval/memory/bump.rs index f75806f..72820b3 100644 --- a/src/eval/memory/bump.rs +++ b/src/eval/memory/bump.rs @@ -71,7 +71,7 @@ impl LineMap { /// "conservative marking" that means we need two clear lines to /// recognise a gap. /// - /// Returns memory offsets (within the block) of the next hole. + /// Returns memory byte offsets (within the block) of the next hole. pub fn find_hole(&self, below_offset: usize) -> Option<(usize, usize)> { let limit_line = below_offset / LINE_SIZE_BYTES; let mut count = 0; @@ -96,7 +96,13 @@ impl LineMap { if lower > 0 { lower += 1; // conservative mark } - return Some((lower * LINE_SIZE_BYTES, upper * LINE_SIZE_BYTES)); + + let lower_bytes = lower * LINE_SIZE_BYTES; + let upper_bytes = upper * LINE_SIZE_BYTES; + + debug_assert!(upper_bytes <= BLOCK_SIZE_BYTES); + + return Some((lower_bytes, upper_bytes)); } } @@ -135,8 +141,8 @@ impl Debug for LineMap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let dwords: [u128; 2] = self.0.into(); for dword in dwords { - let lo = dword as u64; - let hi = (dword >> 64) as u64; + let lo = (dword as u64).reverse_bits(); + let hi = ((dword >> 64) as u64).reverse_bits(); writeln!(f, "{:#018x} {:#018x}", lo, hi)?; } Ok(()) @@ -192,7 +198,32 @@ impl Debug for BumpBlock { self.cursor, self.lower )?; - writeln!(f, "{:?}", self.line_map) + + // decorate line map with cursor range + let upper_char = (self.cursor / LINE_SIZE_BYTES) / 4; + let lower_char = (self.lower / LINE_SIZE_BYTES) / 4; + let length = (self.block.size() / LINE_SIZE_BYTES) / 4; + let cursor_map: Vec = (0..length) + .map(|i| i >= lower_char && i <= upper_char) + .collect(); + + let above: String = cursor_map[0..(length / 2)] + .iter() + .map(|b| if *b { '_' } else { ' ' }) + .collect(); + let below: String = cursor_map[(length / 2)..] + .iter() + .map(|b| if *b { '‾' } else { ' ' }) + .collect(); + + let halfway = below.char_indices().take(1 + (length / 4)).last().unwrap(); + + let (q1, q2) = above.split_at(length / 4); + let (q3, q4) = below.split_at(halfway.0); + + writeln!(f, " {} {}", q1, q2)?; + write!(f, "{:?}", self.line_map)?; + writeln!(f, " {} {}", q3, q4) } } @@ -216,6 +247,10 @@ impl BumpBlock { if next < self.lower { // find next hole if let Some((lower, cursor)) = self.line_map.find_hole(self.cursor) { + if cfg!(debug_assertions) { + self.block.fill(lower, cursor - lower); + } + self.lower = lower; self.cursor = cursor; self.bump(size) @@ -284,6 +319,8 @@ impl BumpBlock { #[cfg(test)] pub mod tests { + use regex::Regex; + use super::*; #[test] @@ -293,7 +330,70 @@ pub mod tests { map.mark(10); assert_eq!( map.find_hole(10 * LINE_SIZE_BYTES), - Some((2 * LINE_SIZE_BYTES, 10 * LINE_SIZE_BYTES)) + Some((LINE_SIZE_BYTES, 9 * LINE_SIZE_BYTES)) + ); + } + + #[test] + pub fn test_find_hole2() { + let mut map = LineMap::default(); + map.mark(0); + map.mark(10); + map.mark(11); + map.mark(12); + map.mark(13); + map.mark(14); + map.mark(20); + assert_eq!( + map.find_hole(20 * LINE_SIZE_BYTES), + Some((15 * LINE_SIZE_BYTES, 19 * LINE_SIZE_BYTES)) + ); + } + + #[test] + pub fn test_find_hole3() { + let mut map = LineMap::default(); + map.mark(0); + map.mark(10); + map.mark(11); + map.mark(12); + map.mark(13); + map.mark(14); + map.mark(18); + map.mark(19); + map.mark(20); + assert_eq!( + map.find_hole(20 * LINE_SIZE_BYTES), + Some((15 * LINE_SIZE_BYTES, 17 * LINE_SIZE_BYTES)) + ); + } + + #[test] + pub fn test_find_hole4() { + let mut map = LineMap::default(); + map.mark(0); + map.mark(10); + map.mark(11); + map.mark(12); + map.mark(13); + map.mark(14); + map.mark(18); + assert_eq!( + map.find_hole(20 * LINE_SIZE_BYTES), + Some((15 * LINE_SIZE_BYTES, 17 * LINE_SIZE_BYTES)) + ); + } + + #[test] + pub fn test_find_hole5() { + let mut map = LineMap::default(); + map.mark(124); + map.mark(125); + map.mark(126); + map.mark(127); + assert_eq!( + map.find_hole(128 * LINE_SIZE_BYTES), + Some((0, 123 * LINE_SIZE_BYTES)) ); } @@ -311,4 +411,38 @@ pub mod tests { assert_eq!(marked, 6); assert_eq!(count_holes, 5); } + + /// Parse a line map from a dump like + /// + /// 0xfffffff8ef8c0007 0xfffffffdfdffffff + /// 0xe1ffffffffffffff 0xffffffffffffffff + /// + pub fn linemap_from_dump(dump: &str) -> LineMap { + let re = Regex::new(r#"0x(\S+) 0x(\S+)\n0x(\S+) 0x(\S+)"#).unwrap(); + if let Some(caps) = re.captures(dump) { + let components: Vec<_> = caps + .iter() + .skip(1) + .map(|s| u64::from_str_radix(s.unwrap().as_str(), 16).unwrap()) + .map(|u| u.reverse_bits()) + .collect(); + let dword1 = components[0] as u128 | ((components[1] as u128) << 64); + let dword2 = components[2] as u128 | ((components[3] as u128) << 64); + LineMap(Bitmap::from([dword1, dword2])) + } else { + panic!("can't parse linemap"); + } + } + + #[test] + pub fn test_real_linemap() { + let m = linemap_from_dump( + "0xe00031f71fffffff 0xffffffbfbfffffff\n0xffffffffffffff87 0xffffffffffffffff", + ); + + if let Some((lo, hi)) = m.find_hole(BLOCK_SIZE_BYTES) { + assert_eq!(lo, 23680); + assert_eq!(hi, 24064); + } + } } diff --git a/src/eval/memory/collect.rs b/src/eval/memory/collect.rs index 36f1c11..d434227 100644 --- a/src/eval/memory/collect.rs +++ b/src/eval/memory/collect.rs @@ -8,7 +8,7 @@ use std::{collections::VecDeque, ptr::NonNull}; use crate::eval::machine::metrics::{Clock, ThreadOccupation}; -use super::{heap::Heap, mark::flip_mark_state}; +use super::{array::Array, heap::Heap, mark::flip_mark_state}; pub struct ScanPtr<'scope> { value: &'scope dyn GcScannable, @@ -80,6 +80,8 @@ impl<'guard> CollectorHeapView<'guard> { /// Mark object if not already marked and return whether marked pub fn mark(&mut self, obj: NonNull) -> bool { + debug_assert!(obj != NonNull::dangling()); + debug_assert!(obj.as_ptr() as usize != 0xffffffffffffffff); if obj != NonNull::dangling() && !self.heap.is_marked(obj) { self.heap.mark_object(obj); self.heap.mark_line(obj); @@ -89,6 +91,23 @@ impl<'guard> CollectorHeapView<'guard> { } } + /// Mark object if not already marked and return whether marked + pub fn mark_array(&mut self, arr: &Array) -> bool { + if let Some(ptr) = arr.allocated_data() { + debug_assert!(ptr != NonNull::dangling()); + debug_assert!(ptr.as_ptr() as usize != 0xffffffffffffffff); + if !self.heap.is_marked(ptr) { + self.heap.mark_object(ptr); + self.heap.mark_lines_for_bytes(ptr); + true + } else { + false + } + } else { + false + } + } + pub fn is_marked(&self, obj: NonNull) -> bool { self.heap.is_marked(obj) } @@ -101,7 +120,11 @@ impl<'guard> CollectorHeapView<'guard> { pub struct Scope(); impl CollectorScope for Scope {} -pub fn collect(roots: &dyn GcScannable, heap: &mut Heap, clock: &mut Clock) { +pub fn collect(roots: &dyn GcScannable, heap: &mut Heap, clock: &mut Clock, dump_heap: bool) { + if dump_heap { + eprintln!("GC!"); + } + clock.switch(ThreadOccupation::CollectorMark); let mut heap_view = CollectorHeapView { heap }; @@ -120,11 +143,19 @@ pub fn collect(roots: &dyn GcScannable, heap: &mut Heap, clock: &mut Clock) { queue.extend(scanptr.as_ref().scan(&scope, &mut heap_view).drain(..)); } + if dump_heap { + eprintln!("Heap after mark:\n\n{:?}", &heap_view.heap) + } + clock.switch(ThreadOccupation::CollectorSweep); // sweep to region heap_view.sweep(); + if dump_heap { + eprintln!("Heap after sweep:\n\n{:?}", &heap_view.heap) + } + // After collection, flip mark state ready for next collection flip_mark_state(); } @@ -181,24 +212,47 @@ pub mod tests { scoped_ptr.as_ptr() }; - eprintln!("{:?}", &heap); - { - collect(&vec![let_ptr, bif_ptr], &mut heap, &mut clock); + collect(&vec![let_ptr, bif_ptr], &mut heap, &mut clock, true); } - eprintln!("{:?}", &heap); - let stats_a = dbg!(heap.stats()); + let stats_a = heap.stats(); { - let guard = Scope {}; + collect(&vec![bif_ptr], &mut heap, &mut clock, true); + } + + let stats_b = heap.stats(); + + clock.switch(ThreadOccupation::Mutator); + + let let_ptr2 = { + let view = MutatorHeapView::new(&heap); + + // A bunch of garbage... + + let ids = repeat_with(|| -> LambdaForm { + LambdaForm::new(1, view.atom(Ref::L(0)).unwrap().as_ptr(), Smid::default()) + }) + .take(256) + .collect::>(); + let idarray = view.array(ids.as_slice()); - let app_bif = ScanPtr::from_non_null(&guard, bif_ptr).as_ref(); - collect(app_bif, &mut heap, &mut clock); + view.let_( + idarray, + view.app(Ref::L(0), view.singleton(view.sym_ref("foo").unwrap())) + .unwrap(), + ) + .unwrap() + .as_ptr() + }; + + { + collect(&vec![let_ptr2], &mut heap, &mut clock, true); } - eprintln!("{:?}", &heap); - let stats_b = dbg!(heap.stats()); + let stats_c = heap.stats(); + eprintln!("Final stats: {:?}", &stats_c); assert!(stats_a.recycled < stats_b.recycled); } diff --git a/src/eval/memory/heap.rs b/src/eval/memory/heap.rs index bed35db..b8e3d11 100644 --- a/src/eval/memory/heap.rs +++ b/src/eval/memory/heap.rs @@ -1,5 +1,6 @@ //! The STG heap implementation +use std::collections::LinkedList; use std::fmt::Debug; use std::ptr::NonNull; use std::{cell::UnsafeCell, mem::size_of}; @@ -56,9 +57,9 @@ pub struct HeapState { /// For allocating medium objects overflow: Option, /// Recycled - part used but reclaimed - recycled: Vec, + recycled: LinkedList, /// Part used - not yet reclaimed - rest: Vec, + rest: LinkedList, /// Large object blocks - each contains single object lobs: Vec, } @@ -72,7 +73,11 @@ impl Default for HeapState { impl std::fmt::Debug for HeapState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for block in &self.rest { - writeln!(f, "{:?}", block)?; + writeln!(f, "(XX) {:?}", block)?; + } + + for block in &self.recycled { + writeln!(f, "(Cy) {:?}", block)?; } if let Some(head) = &self.head { @@ -96,8 +101,8 @@ impl HeapState { HeapState { head: None, overflow: None, - recycled: vec![], - rest: vec![], + recycled: LinkedList::default(), + rest: LinkedList::default(), lobs: vec![], } } @@ -121,8 +126,14 @@ impl HeapState { } pub fn replace_head(&mut self) -> &mut BumpBlock { - self.head.replace(BumpBlock::new()).and_then(|old| { - self.rest.push(old); + let replacement = if let Some(block) = self.recycled.pop_front() { + block + } else { + BumpBlock::new() + }; + + self.head.replace(replacement).and_then(|old| { + self.rest.push_back(old); None as Option }); self.head.as_mut().unwrap() @@ -130,7 +141,7 @@ impl HeapState { pub fn replace_overflow(&mut self) -> &mut BumpBlock { self.overflow.replace(BumpBlock::new()).and_then(|old| { - self.rest.push(old); + self.rest.push_back(old); None as Option }); self.overflow.as_mut().unwrap() @@ -145,28 +156,17 @@ impl HeapState { /// Look for reclaimable blocks and move to recycled list pub fn sweep(&mut self) { - // move any recyclable blocks to - let mut i = 0; - while i < self.rest.len() { - let (holes, _, _) = self.rest[i].stats(); - if holes > 0 { - if let Some(b) = self.rest.get_mut(i) { - if b.recycle() { - self.recycled.push(self.rest.remove(i)); - } else { - i += 1; - } - } else { - i += 1; - } + let mut unusable: LinkedList = LinkedList::default(); + + while let Some(mut block) = self.rest.pop_front() { + if block.recycle() { + self.recycled.push_back(block); } else { - i += 1; + unusable.push_back(block); } } - // order rest and recycled - self.rest.sort(); - self.recycled.sort(); + self.rest.append(&mut unusable); } /// Statistics @@ -223,6 +223,16 @@ impl Heap { pub fn stats(&self) -> HeapStats { unsafe { (*self.state.get()).stats() } } + + pub fn policy_requires_collection(&self) -> bool { + if let Some(limit) = self.limit { + let stats = self.stats(); + stats.blocks_allocated >= limit + && (stats.recycled as f32 / stats.blocks_allocated as f32) < 0.25 + } else { + false + } + } } impl Allocator for Heap { @@ -273,7 +283,10 @@ impl Allocator for Heap { /// Get header from object pointer fn get_header(&self, object: NonNull) -> NonNull { - unsafe { NonNull::new_unchecked(object.cast::().as_ptr().offset(-1)) } + let header_ptr = + unsafe { NonNull::new_unchecked(object.cast::().as_ptr().offset(-1)) }; + debug_assert!(header_ptr.as_ptr() as usize > 0); + header_ptr } /// Get object from header pointer @@ -337,11 +350,13 @@ impl Heap { /// Check wither an object is marked pub fn is_marked(&self, ptr: NonNull) -> bool { let header: NonNull = self.get_header(ptr); + debug_assert!(header.as_ptr() as usize > 0); unsafe { (*header.as_ptr()).is_marked() } } /// Mark an object as live pub fn mark_object(&self, ptr: NonNull) { + debug_assert!(ptr != NonNull::dangling() && ptr.as_ptr() as usize != 0xffffffffffffffff); let header: NonNull = self.get_header(ptr); unsafe { (*header.as_ptr()).mark(); @@ -350,6 +365,7 @@ impl Heap { /// Unmark object pub fn unmark_object(&self, ptr: NonNull) { + debug_assert!(ptr != NonNull::dangling() && ptr.as_ptr() as usize != 0xffffffffffffffff); let header = self.get_header(ptr); unsafe { (*header.as_ptr()).unmark() } } @@ -378,13 +394,24 @@ impl Heap { // depending on size of object + header, mark line or lines let heap_state = unsafe { &mut *self.state.get() }; - // TODO: fix - if let Some(head) = &mut heap_state.head { - head.mark_line(ptr); - } + let size = size_of::(); + // TODO: fix this - go directly to the right block! + if SizeClass::for_size(size) == SizeClass::Medium { + if let Some(head) = &mut heap_state.head { + head.mark_region(ptr.cast(), size); + } - for block in &mut heap_state.rest { - block.mark_line(ptr); + for block in &mut heap_state.rest { + block.mark_region(ptr.cast(), size); + } + } else { + if let Some(head) = &mut heap_state.head { + head.mark_line(ptr); + } + + for block in &mut heap_state.rest { + block.mark_line(ptr); + } } } @@ -421,7 +448,13 @@ pub mod tests { let ptr = heap.alloc(Ref::num(99)).unwrap(); unsafe { assert_eq!(*ptr.as_ref(), Ref::num(99)) }; - // TODO: heap.get_header(ptr); + let header_ptr = heap.get_header(ptr); + let difference = ptr.as_ptr() as usize - header_ptr.as_ptr() as usize; + assert_eq!(difference, size_of::()); + + unsafe { + assert!(!(*header_ptr.as_ptr()).is_marked()); + } } #[test] diff --git a/src/eval/memory/mod.rs b/src/eval/memory/mod.rs index d545a3e..fb1bca6 100644 --- a/src/eval/memory/mod.rs +++ b/src/eval/memory/mod.rs @@ -9,7 +9,7 @@ pub mod heap; pub mod infotable; pub mod loader; pub mod lob; +pub mod mark; pub mod mutator; pub mod string; pub mod syntax; -pub mod mark; diff --git a/src/eval/memory/syntax.rs b/src/eval/memory/syntax.rs index 66bfb86..f15d53d 100644 --- a/src/eval/memory/syntax.rs +++ b/src/eval/memory/syntax.rs @@ -255,9 +255,11 @@ impl GcScannable for HeapSyn { if marker.mark(*scrutinee) { grey.push(ScanPtr::from_non_null(scope, *scrutinee)); } - for (_, b) in branches.iter() { - if marker.mark(*b) { - grey.push(ScanPtr::from_non_null(scope, *b)); + if marker.mark_array(branches) { + for (_, b) in branches.iter() { + if marker.mark(*b) { + grey.push(ScanPtr::from_non_null(scope, *b)); + } } } if let Some(f) = fallback { @@ -267,26 +269,18 @@ impl GcScannable for HeapSyn { } } HeapSyn::Cons { tag: _, args } => { - if let Some(data) = args.allocated_data() { - marker.mark(data); - } + marker.mark_array(args); } HeapSyn::App { callable: _, args } => { - if let Some(data) = args.allocated_data() { - marker.mark(data); - } + marker.mark_array(args); } HeapSyn::Bif { intrinsic: _, args } => { - if let Some(data) = args.allocated_data() { - marker.mark(data); - } + marker.mark_array(args); } HeapSyn::Let { bindings, body } => { - if let Some(data) = bindings.allocated_data() { - if marker.mark(data) { - for bindings in bindings.iter() { - grey.push(ScanPtr::new(scope, bindings)); - } + if marker.mark_array(bindings) { + for bindings in bindings.iter() { + grey.push(ScanPtr::new(scope, bindings)); } } @@ -295,11 +289,9 @@ impl GcScannable for HeapSyn { } } HeapSyn::LetRec { bindings, body } => { - if let Some(data) = bindings.allocated_data() { - if marker.mark(data) { - for bindings in bindings.iter() { - grey.push(ScanPtr::new(scope, bindings)); - } + if marker.mark_array(bindings) { + for bindings in bindings.iter() { + grey.push(ScanPtr::new(scope, bindings)); } } diff --git a/src/eval/stg/mod.rs b/src/eval/stg/mod.rs index 5738aa0..756569a 100644 --- a/src/eval/stg/mod.rs +++ b/src/eval/stg/mod.rs @@ -190,6 +190,9 @@ pub struct StgSettings { /// Limit managed heap to SIZE MiB #[structopt(long)] pub heap_limit_mib: Option, + /// Dump heap to stderr at GC time for debugging + #[structopt(long)] + pub heap_dump_at_gc: bool, } /// Compile core syntax to STG ready for execution diff --git a/src/eval/stg/testing.rs b/src/eval/stg/testing.rs index 7463e8a..7e0ec9f 100644 --- a/src/eval/stg/testing.rs +++ b/src/eval/stg/testing.rs @@ -26,7 +26,8 @@ lazy_static! { suppress_updates: false, suppress_inlining: false, suppress_optimiser: false, - heap_limit_mib: Some(256), + heap_limit_mib: Some(2), + heap_dump_at_gc: false, }; }