From 5f60718645fe49d92c61940ac20aeaef54fa922c Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Mon, 31 Jul 2023 22:58:15 -0400 Subject: [PATCH 01/32] Remove redundant function --- interpreter/src/main.rs | 4 +--- stickyimmix/src/allocator.rs | 12 ------------ stickyimmix/src/heap.rs | 10 ++++------ 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index 057136f..10efb83 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -60,9 +60,7 @@ fn load_file(filename: &str) -> Result { fn read_file(filename: &str) -> Result<(), RuntimeError> { let _contents = load_file(&filename)?; - // TODO - - Ok(()) + Ok(contents) } /// Read a line at a time, printing the input back out diff --git a/stickyimmix/src/allocator.rs b/stickyimmix/src/allocator.rs index f5440c6..4022a09 100644 --- a/stickyimmix/src/allocator.rs +++ b/stickyimmix/src/allocator.rs @@ -122,15 +122,3 @@ pub trait AllocHeader: Sized { fn type_id(&self) -> Self::TypeId; } // ANCHOR_END: DefAllocHeader - -/// Return the allocated size of an object as it's size_of::() value rounded -/// up to a double-word boundary -/// -/// TODO this isn't correctly implemented, as aligning the object to a double-word -/// boundary while considering header size (which is not known to this libarary -/// until compile time) means touching numerous bump-allocation code points with -/// some math and bitwise ops I haven't worked out yet -pub fn alloc_size_of(object_size: usize) -> usize { - let align = size_of::(); // * 2; - (object_size + (align - 1)) & !(align - 1) -} diff --git a/stickyimmix/src/heap.rs b/stickyimmix/src/heap.rs index a08142e..e8eb0cc 100644 --- a/stickyimmix/src/heap.rs +++ b/stickyimmix/src/heap.rs @@ -177,11 +177,10 @@ impl AllocRaw for StickyImmixHeap { // TODO BUG? should this be done separately for header and object? // If the base allocation address is where the header gets placed, perhaps // this breaks the double-word alignment object alignment desire? - let alloc_size = alloc_size_of(total_size); - let size_class = SizeClass::get_for_size(alloc_size)?; + let size_class = SizeClass::get_for_size(total_size)?; // attempt to allocate enough space for the header and the object - let space = self.find_space(alloc_size, size_class)?; + let space = self.find_space(total_size, size_class)?; // instantiate an object header for type T, setting the mark bit to "allocated" let header = Self::Header::new::(object_size as ArraySize, size_class, Mark::Allocated); @@ -211,11 +210,10 @@ impl AllocRaw for StickyImmixHeap { let total_size = header_size + size_bytes as usize; // round the size to the next word boundary to keep objects aligned and get the size class - let alloc_size = alloc_size_of(total_size); - let size_class = SizeClass::get_for_size(alloc_size)?; + let size_class = SizeClass::get_for_size(total_size)?; // attempt to allocate enough space for the header and the array - let space = self.find_space(alloc_size, size_class)?; + let space = self.find_space(total_size, size_class)?; // instantiate an object header for an array, setting the mark bit to "allocated" let header = Self::Header::new_array(size_bytes, size_class, Mark::Allocated); From 88613eb5c11e2f5764f48e8498a380b5e506267e Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 5 Jun 2024 22:01:43 -0400 Subject: [PATCH 02/32] Fix logic hole in Array --- interpreter/src/array.rs | 11 +++++++++++ interpreter/src/main.rs | 5 +++-- interpreter/src/vm.rs | 11 ++++++----- stickyimmix/src/allocator.rs | 1 - stickyimmix/src/heap.rs | 2 +- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/interpreter/src/array.rs b/interpreter/src/array.rs index a4b9459..9bcdbd1 100644 --- a/interpreter/src/array.rs +++ b/interpreter/src/array.rs @@ -204,12 +204,19 @@ impl Container for Array { } impl FillContainer for Array { + /// Increase the size of the array to `size` and fill the new slots with + /// copies of `item`. If `size` is less than the current length of the array, + /// does nothing. fn fill<'guard>( &self, mem: &'guard MutatorView, size: ArraySize, item: T, ) -> Result<(), RuntimeError> { + if self.borrow.get() != INTERIOR_ONLY { + return Err(RuntimeError::new(ErrorKind::MutableBorrowError)); + } + let length = self.length(); if length > size { @@ -383,6 +390,10 @@ impl FillAnyContainer for Array { size: ArraySize, item: TaggedScopedPtr<'guard>, ) -> Result<(), RuntimeError> { + if self.borrow.get() != INTERIOR_ONLY { + return Err(RuntimeError::new(ErrorKind::MutableBorrowError)); + } + let length = self.length(); if length > size { diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index 10efb83..f6c22ec 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -57,8 +57,8 @@ fn load_file(filename: &str) -> Result { } /// Read and evaluate an entire file -fn read_file(filename: &str) -> Result<(), RuntimeError> { - let _contents = load_file(&filename)?; +fn read_file(filename: &str) -> Result { + let contents = load_file(&filename)?; Ok(contents) } @@ -136,6 +136,7 @@ fn main() { eprintln!("Terminated: {}", err); process::exit(1); }); + // TODO } else { // otherwise begin a repl read_print_loop().unwrap_or_else(|err| { diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 5c3ea35..e9eb9ae 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -284,7 +284,9 @@ impl Thread { let globals = self.globals.get(mem); let instr = self.instr.get(mem); - // Establish a 256-register window into the stack from the stack base + // Establish a 256-register window into the stack from the stack base. + // TODO borrowing this slice mutably at this outer level poses problems + // for the function call opcode. stack.access_slice(mem, |full_stack| { let stack_base = self.stack_base.get() as usize; let window = &mut full_stack[stack_base..stack_base + 256]; @@ -506,10 +508,9 @@ impl Thread { self.stack_base.set(new_stack_base); instr.switch_frame(code, 0); - // Ensure the stack has 256 registers allocated - // TODO reset to nil to avoid accidental leakage of previous call values - // TODO Ruh-roh we shouldn't be able to modify the stack size from - // within an access_slice() call :grimace: + // TODO Ensure the stack has 256 registers allocated + // This will need mutable access to the stack, which + // can't be done while borrowing it mutably as a slice. stack.fill(mem, new_stack_base + 256, mem.nil())?; Ok(()) diff --git a/stickyimmix/src/allocator.rs b/stickyimmix/src/allocator.rs index 4022a09..e1b8d94 100644 --- a/stickyimmix/src/allocator.rs +++ b/stickyimmix/src/allocator.rs @@ -1,4 +1,3 @@ -use std::mem::size_of; use std::ptr::NonNull; use crate::constants; diff --git a/stickyimmix/src/heap.rs b/stickyimmix/src/heap.rs index e8eb0cc..de0abfd 100644 --- a/stickyimmix/src/heap.rs +++ b/stickyimmix/src/heap.rs @@ -5,7 +5,7 @@ use std::ptr::{write, NonNull}; use std::slice::from_raw_parts_mut; use crate::allocator::{ - alloc_size_of, AllocError, AllocHeader, AllocObject, AllocRaw, ArraySize, Mark, SizeClass, + AllocError, AllocHeader, AllocObject, AllocRaw, ArraySize, Mark, SizeClass, }; use crate::bumpblock::BumpBlock; use crate::constants; From 82d2348386b76e8d666b1f94cf6530660d9c6841 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 28 Aug 2024 18:07:42 -0400 Subject: [PATCH 03/32] Fix logic hole in VM --- interpreter/src/vm.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index e9eb9ae..1095286 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -285,9 +285,7 @@ impl Thread { let instr = self.instr.get(mem); // Establish a 256-register window into the stack from the stack base. - // TODO borrowing this slice mutably at this outer level poses problems - // for the function call opcode. - stack.access_slice(mem, |full_stack| { + let status = stack.access_slice(mem, |full_stack| { let stack_base = self.stack_base.get() as usize; let window = &mut full_stack[stack_base..stack_base + 256]; @@ -505,13 +503,11 @@ impl Thread { // Update the instruction stream to point to the new function let code = function.code(mem); - self.stack_base.set(new_stack_base); instr.switch_frame(code, 0); - // TODO Ensure the stack has 256 registers allocated - // This will need mutable access to the stack, which - // can't be done while borrowing it mutably as a slice. - stack.fill(mem, new_stack_base + 256, mem.nil())?; + // stack_base has changed and the next function will expect 256 registers. + // See end of function for expansion of stack backing array. + self.stack_base.set(new_stack_base); Ok(()) }; @@ -711,7 +707,13 @@ impl Thread { } Ok(EvalStatus::Pending) - }) + }); + + // Expand stack if necessary. This will be a no-op unless stack_base has changed + let stack_base = self.stack_base.get(); + stack.fill(mem, stack_base + 256, mem.nil())?; + + status } /// Given ByteCode, execute up to max_instr more instructions From be91860af8003d32eca083a1b90a61b1098a56db Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 28 Aug 2024 18:12:49 -0400 Subject: [PATCH 04/32] Suppress ignored args warnings --- interpreter/src/vm.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 1095286..afabc97 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -663,16 +663,32 @@ impl Thread { } // TODO - Opcode::Add { dest, reg1, reg2 } => unimplemented!(), + Opcode::Add { + dest: _, + reg1: _, + reg2: _, + } => unimplemented!(), // TODO - Opcode::Subtract { dest, left, right } => unimplemented!(), + Opcode::Subtract { + dest: _, + left: _, + right: _, + } => unimplemented!(), // TODO - Opcode::Multiply { dest, reg1, reg2 } => unimplemented!(), + Opcode::Multiply { + dest: _, + reg1: _, + reg2: _, + } => unimplemented!(), // TODO - Opcode::DivideInteger { dest, num, denom } => unimplemented!(), + Opcode::DivideInteger { + dest: _, + num: _, + denom: _, + } => unimplemented!(), // Follow the indirection of an Upvalue to retrieve the value, copy the value to a // local register From 012093ab1b4d827fab5d0d6801178ffd6bfefa30 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 1 Sep 2024 10:07:07 -0400 Subject: [PATCH 05/32] Remove a TODO --- interpreter/src/vm.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index afabc97..6de810b 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -736,14 +736,8 @@ impl Thread { fn vm_eval_stream<'guard>( &self, mem: &'guard MutatorView, - code: ScopedPtr<'guard, ByteCode>, max_instr: ArraySize, ) -> Result, RuntimeError> { - let instr = self.instr.get(mem); - // TODO this is broken logic, this function shouldn't switch back to this code object every - // time it is called - instr.switch_frame(code, 0); - for _ in 0..max_instr { match self.eval_next_instr(mem) { // Evaluation paused or completed without error @@ -793,9 +787,11 @@ impl Thread { frames.push(mem, CallFrame::new_main(function))?; let code = function.code(mem); + let instr = self.instr.get(mem); + instr.switch_frame(code, 0); while status == EvalStatus::Pending { - status = self.vm_eval_stream(mem, code, 1024)?; + status = self.vm_eval_stream(mem, 1024)?; match status { EvalStatus::Return(value) => return Ok(value), _ => (), From d16be8fb3cbb46694ab55ace050a0a99a44cc8ac Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 1 Sep 2024 10:20:02 -0400 Subject: [PATCH 06/32] Rename vm run functions --- interpreter/src/compiler.rs | 2 +- interpreter/src/repl.rs | 2 +- interpreter/src/vm.rs | 61 +++++++++++++++++++------------------ 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/interpreter/src/compiler.rs b/interpreter/src/compiler.rs index 474bb99..e3ace5e 100644 --- a/interpreter/src/compiler.rs +++ b/interpreter/src/compiler.rs @@ -877,7 +877,7 @@ mod integration { ) -> Result, RuntimeError> { let compiled_code = compile(mem, parse(mem, code)?)?; println!("RUN CODE {}", code); - let result = thread.quick_vm_eval(mem, compiled_code)?; + let result = thread.exec(mem, compiled_code)?; println!("RUN RESULT {}", result); Ok(result) } diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index 9028894..03b338a 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -61,7 +61,7 @@ impl Mutator for ReadEvalPrint { println!("## Compiled:\n```\n{:?}\n```", function); } - let value = thread.quick_vm_eval(mem, function)?; + let value = thread.exec(mem, function)?; if debug { println!("## Evaluated:\n```\n{:?}\n```\n", value); diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 6de810b..3571e5b 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -732,8 +732,38 @@ impl Thread { status } - /// Given ByteCode, execute up to max_instr more instructions - fn vm_eval_stream<'guard>( + /// Execute a Function completely. + /// The given function must take no arguments. + /// Returns the result. + pub fn exec<'guard>( + &self, + mem: &'guard MutatorView, + function: ScopedPtr<'guard, Function>, + ) -> Result, RuntimeError> { + let mut status = EvalStatus::Pending; + + let frames = self.frames.get(mem); + frames.push(mem, CallFrame::new_main(function))?; + + let code = function.code(mem); + let instr = self.instr.get(mem); + instr.switch_frame(code, 0); + + while status == EvalStatus::Pending { + status = self.continue_exec(mem, 1024)?; + match status { + EvalStatus::Return(value) => return Ok(value), + _ => (), + } + } + + Err(err_eval("Unexpected end of evaluation")) + } + + // TODO pub fn start_exec<'guard>() - start execution but don't guarantee completion + + /// Execute up to max_instr more instructions + pub fn continue_exec<'guard>( &self, mem: &'guard MutatorView, max_instr: ArraySize, @@ -773,31 +803,4 @@ impl Thread { Ok(EvalStatus::Pending) } - - /// Evaluate a Function completely, returning the result. The Function passed in should expect - /// no arguments. - pub fn quick_vm_eval<'guard>( - &self, - mem: &'guard MutatorView, - function: ScopedPtr<'guard, Function>, - ) -> Result, RuntimeError> { - let mut status = EvalStatus::Pending; - - let frames = self.frames.get(mem); - frames.push(mem, CallFrame::new_main(function))?; - - let code = function.code(mem); - let instr = self.instr.get(mem); - instr.switch_frame(code, 0); - - while status == EvalStatus::Pending { - status = self.vm_eval_stream(mem, 1024)?; - match status { - EvalStatus::Return(value) => return Ok(value), - _ => (), - } - } - - Err(err_eval("Unexpected end of evaluation")) - } } From fb2bca3c9d593dc5debf64c6c2c2afcd293c5a27 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 8 Sep 2024 22:45:50 -0400 Subject: [PATCH 07/32] Refactor thread exec iterations --- interpreter/examples/functions.evalrs | 10 ++++++++++ interpreter/src/repl.rs | 10 ++++++++-- interpreter/src/vm.rs | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 interpreter/examples/functions.evalrs diff --git a/interpreter/examples/functions.evalrs b/interpreter/examples/functions.evalrs new file mode 100644 index 0000000..e8b1ad6 --- /dev/null +++ b/interpreter/examples/functions.evalrs @@ -0,0 +1,10 @@ + +(def first (list) (car list)) + +(def rest (list) (cdr list)) + +(def last (list) (cond (nil? (cdr list)) (car list) true (last (cdr list))))) + +(set 'x '(a b c d e f g h i j k l m n o p q r x y z)) +(last x) + diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index 03b338a..5f59fae 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -3,7 +3,7 @@ use crate::error::{ErrorKind, RuntimeError}; use crate::memory::{Mutator, MutatorView}; use crate::parser::parse; use crate::safeptr::{CellPtr, TaggedScopedPtr}; -use crate::vm::Thread; +use crate::vm::{EvalStatus, Thread}; /// A mutator that returns a Repl instance pub struct RepMaker {} @@ -61,7 +61,13 @@ impl Mutator for ReadEvalPrint { println!("## Compiled:\n```\n{:?}\n```", function); } - let value = thread.exec(mem, function)?; + let mut status = thread.start_exec(mem, function)?; + let value = loop { + match status { + EvalStatus::Return(value) => break value, + _ => status = thread.continue_exec(mem, 1024)?, + }; + }; if debug { println!("## Evaluated:\n```\n{:?}\n```\n", value); diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 3571e5b..bfb2265 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -760,7 +760,23 @@ impl Thread { Err(err_eval("Unexpected end of evaluation")) } - // TODO pub fn start_exec<'guard>() - start execution but don't guarantee completion + /// Start executution of a Function. + /// The given function must take no arguments. + /// Returns the result. + pub fn start_exec<'guard>( + &self, + mem: &'guard MutatorView, + function: ScopedPtr<'guard, Function>, + ) -> Result, RuntimeError> { + let frames = self.frames.get(mem); + frames.push(mem, CallFrame::new_main(function))?; + + let code = function.code(mem); + let instr = self.instr.get(mem); + instr.switch_frame(code, 0); + + self.continue_exec(mem, 1024) + } /// Execute up to max_instr more instructions pub fn continue_exec<'guard>( From d2090f8a1802db94df0106db497763dd58154fa0 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 15 Dec 2024 22:17:59 -0500 Subject: [PATCH 08/32] Various fixes towards safe rooting --- booksrc/introduction.md | 2 +- interpreter/src/arena.rs | 4 +- interpreter/src/bytecode.rs | 2 +- interpreter/src/containers.rs | 6 - interpreter/src/headers.rs | 8 +- interpreter/src/parser.rs | 4 +- interpreter/src/safeptr.rs | 23 +++- interpreter/src/vm.rs | 44 ++++---- notes.md | 199 ++++++++++++++++++++++++++++++++++ stickyimmix/src/allocator.rs | 4 +- stickyimmix/src/heap.rs | 4 +- 11 files changed, 251 insertions(+), 49 deletions(-) create mode 100644 notes.md diff --git a/booksrc/introduction.md b/booksrc/introduction.md index da8f101..8808aba 100644 --- a/booksrc/introduction.md +++ b/booksrc/introduction.md @@ -36,7 +36,7 @@ All the links below are acknowledged as inspiration or prior art. * Bob Nystrom's [Crafting Interpreters](http://craftinginterpreters.com/) * [The Inko programming language](https://inko-lang.org/) -* kyren - [luster](https://github.com/kyren/luster) and [gc-arena](https://github.com/kyren/gc-arena) +* kyren - [piccolo](https://github.com/kyren/piccolo) and [gc-arena](https://github.com/kyren/gc-arena) ### Memory management diff --git a/interpreter/src/arena.rs b/interpreter/src/arena.rs index a131356..f074774 100644 --- a/interpreter/src/arena.rs +++ b/interpreter/src/arena.rs @@ -30,9 +30,9 @@ impl AllocHeader for ArenaHeader { ArenaHeader {} } - fn mark(&mut self) {} + fn mark(&mut self, _value: Mark) {} - fn is_marked(&self) -> bool { + fn mark_is(&self, _value: Mark) -> bool { true } diff --git a/interpreter/src/bytecode.rs b/interpreter/src/bytecode.rs index b3f5602..cf96bfa 100644 --- a/interpreter/src/bytecode.rs +++ b/interpreter/src/bytecode.rs @@ -318,7 +318,7 @@ impl InstructionStream { guard, lit_id as ArraySize, )? - .get_ptr()) + .get_ptr(guard)) } /// Return the next instruction pointer diff --git a/interpreter/src/containers.rs b/interpreter/src/containers.rs index 0096655..f8be3e1 100644 --- a/interpreter/src/containers.rs +++ b/interpreter/src/containers.rs @@ -208,9 +208,3 @@ pub trait AnyContainerFromSlice: Container { data: &[TaggedScopedPtr<'guard>], ) -> Result, RuntimeError>; } - -/// The implementor represents mutable changes via an internal version count -/// such that the use of any references to an older version return an error -pub trait VersionedContainer: Container {} - -pub trait ImmutableContainer: Container {} diff --git a/interpreter/src/headers.rs b/interpreter/src/headers.rs index b3591e0..10a418d 100644 --- a/interpreter/src/headers.rs +++ b/interpreter/src/headers.rs @@ -119,12 +119,12 @@ impl AllocHeader for ObjectHeader { } } - fn mark(&mut self) { - self.mark = Mark::Marked; + fn mark(&mut self, value: Mark) { + self.mark = value; } - fn is_marked(&self) -> bool { - self.mark == Mark::Marked + fn mark_is(&self, value: Mark) -> bool { + self.mark == value } fn size_class(&self) -> SizeClass { diff --git a/interpreter/src/parser.rs b/interpreter/src/parser.rs index 9d45640..ad332a0 100644 --- a/interpreter/src/parser.rs +++ b/interpreter/src/parser.rs @@ -18,7 +18,7 @@ struct PairList<'guard> { impl<'guard> PairList<'guard> { /// Create a new empty list - fn open(_guard: &'guard dyn MutatorScope) -> PairList { + fn open(_guard: &'guard dyn MutatorScope) -> PairList<'guard> { PairList { head: TaggedCellPtr::new_nil(), tail: TaggedCellPtr::new_nil(), @@ -51,7 +51,7 @@ impl<'guard> PairList<'guard> { pair.set_first_source_code_pos(pos); self.head.set(mem.alloc_tagged(pair)?); - self.tail.copy_from(&self.head); + self.tail.copy_from(mem, &self.head); } Ok(()) diff --git a/interpreter/src/safeptr.rs b/interpreter/src/safeptr.rs index a299d98..c10c832 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -211,6 +211,13 @@ impl TaggedCellPtr { } } + /// Construct a new TaggedCellPtr from another + pub fn new_copy(source: &TaggedCellPtr) -> TaggedCellPtr { + TaggedCellPtr { + inner: Cell::new(source.inner.get()), + } + } + pub fn new_ptr(source: TaggedPtr) -> TaggedCellPtr { TaggedCellPtr { inner: Cell::new(source), @@ -233,8 +240,13 @@ impl TaggedCellPtr { } /// Take the pointer of another `TaggedCellPtr` and set this instance to point at that object too - pub fn copy_from(&self, other: &TaggedCellPtr) { - self.inner.set(other.inner.get()); + pub fn copy_from<'guard>(&self, _guard: &'guard dyn MutatorScope, src: &TaggedCellPtr) { + self.inner.set(src.inner.get()); + } + + /// Set another instance to hold the same pointer as this instance + pub fn copy_into<'guard>(&self, _guard: &'guard dyn MutatorScope, dest: &TaggedCellPtr) { + dest.inner.set(self.inner.get()); } /// Return true if the pointer is nil @@ -248,12 +260,15 @@ impl TaggedCellPtr { } /// Set this pointer to another TaggedPtr - pub fn set_to_ptr(&self, ptr: TaggedPtr) { + // TODO DEPRECATE IF POSSIBLE + // - this is only used to set non-object tagged values and should be replaced/renamed + pub fn set_to_ptr<'guard>(&self, _guard: &'guard dyn MutatorScope, ptr: TaggedPtr) { self.inner.set(ptr) } /// Return the raw TaggedPtr from within - pub fn get_ptr(&self) -> TaggedPtr { + // TODO DEPRECATE IF POSSIBLE + pub fn get_ptr<'guard>(&self, _guard: &'guard dyn MutatorScope) -> TaggedPtr { self.inner.get() } } diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index bfb2265..a465721 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -113,10 +113,10 @@ impl Upvalue { &self, guard: &'guard dyn MutatorScope, stack: ScopedPtr<'guard, List>, - ) -> Result { + ) -> Result { match self.closed.get() { - true => Ok(self.value.get_ptr()), - false => Ok(IndexedContainer::get(&*stack, guard, self.location)?.get_ptr()), + true => Ok(TaggedCellPtr::new_copy(&self.value)), + false => Ok(IndexedContainer::get(&*stack, guard, self.location)?), } } @@ -126,12 +126,12 @@ impl Upvalue { &self, guard: &'guard dyn MutatorScope, stack: ScopedPtr<'guard, List>, - ptr: TaggedPtr, + ptr: &TaggedCellPtr, ) -> Result<(), RuntimeError> { match self.closed.get() { - true => self.value.set_to_ptr(ptr), + true => self.value.copy_from(guard, ptr), false => { - IndexedContainer::set(&*stack, guard, self.location, TaggedCellPtr::new_ptr(ptr))? + IndexedContainer::set(&*stack, guard, self.location, TaggedCellPtr::new_copy(ptr))? } }; Ok(()) @@ -143,8 +143,7 @@ impl Upvalue { guard: &'guard dyn MutatorScope, stack: ScopedPtr<'guard, List>, ) -> Result<(), RuntimeError> { - let ptr = IndexedContainer::get(&*stack, guard, self.location)?.get_ptr(); - self.value.set_to_ptr(ptr); + IndexedContainer::get(&*stack, guard, self.location)?.copy_into(guard, &self.value); self.closed.set(true); Ok(()) } @@ -302,8 +301,7 @@ impl Thread { // If the call frame stack is empty, the program completed. Opcode::Return { reg } => { // write the return value to register 0 - let result = window[reg as usize].get_ptr(); - window[RETURN_REG].set_to_ptr(result); + window[RETURN_REG].copy_from(mem, &window[reg as usize]); // remove this function's stack frame frames.pop(mem)?; @@ -322,7 +320,7 @@ impl Thread { // Load a literal into a register from the function literals array Opcode::LoadLiteral { dest, literal_id } => { let literal_ptr = instr.get_literal(mem, literal_id)?; - window[dest as usize].set_to_ptr(literal_ptr); + window[dest as usize].set_to_ptr(mem, literal_ptr); } // Evaluate whether the `test` register contains `nil` - if so, set the `dest` @@ -354,7 +352,7 @@ impl Thread { let reg_val = window[reg as usize].get(mem); match *reg_val { - Value::Pair(p) => window[dest as usize].set_to_ptr(p.first.get_ptr()), + Value::Pair(p) => window[dest as usize].copy_from(mem, &p.first), Value::Nil => window[dest as usize].set_to_nil(), _ => return Err(err_eval("Parameter to FirstOfPair is not a list")), } @@ -365,7 +363,7 @@ impl Thread { let reg_val = window[reg as usize].get(mem); match *reg_val { - Value::Pair(p) => window[dest as usize].set_to_ptr(p.second.get_ptr()), + Value::Pair(p) => window[dest as usize].copy_from(mem, &p.second), Value::Nil => window[dest as usize].set_to_nil(), _ => return Err(err_eval("Parameter to SecondOfPair is not a list")), } @@ -373,12 +371,9 @@ impl Thread { // CONS - create a Pair, pointing to `reg1` and `reg2` Opcode::MakePair { dest, reg1, reg2 } => { - let reg1_val = window[reg1 as usize].get_ptr(); - let reg2_val = window[reg2 as usize].get_ptr(); - let new_pair = Pair::new(); - new_pair.first.set_to_ptr(reg1_val); - new_pair.second.set_to_ptr(reg2_val); + new_pair.first.copy_from(mem, &window[reg1 as usize]); + new_pair.second.copy_from(mem, &window[reg2 as usize]); window[dest as usize].set(mem.alloc_tagged(new_pair)?); } @@ -387,8 +382,8 @@ impl Thread { // to the symbol "true" Opcode::IsIdentical { dest, test1, test2 } => { // compare raw pointers - identity comparison - let test1_val = window[test1 as usize].get_ptr(); - let test2_val = window[test2 as usize].get_ptr(); + let test1_val = window[test1 as usize].get_ptr(mem); + let test2_val = window[test2 as usize].get_ptr(mem); if test1_val == test2_val { window[dest as usize].set(mem.lookup_sym("true")); @@ -432,7 +427,7 @@ impl Thread { // Set the register `dest` to the inline integer literal Opcode::LoadInteger { dest, integer } => { let tagged_ptr = TaggedPtr::literal_integer(integer); - window[dest as usize].set_to_ptr(tagged_ptr); + window[dest as usize].set_to_ptr(mem, tagged_ptr); } // Lookup a global binding and put it in the register `dest` @@ -551,8 +546,7 @@ impl Thread { if arg_count == 0 && arity > 0 { // Partial is unchanged, no args added, copy directly to dest - window[dest as usize] - .set_to_ptr(window[function as usize].get_ptr()); + window[dest as usize].copy_from(mem, &window[function as usize]); return Ok(EvalStatus::Pending); } else if arg_count < arity { // Too few args, bake a new Partial from the existing one, adding the new @@ -695,14 +689,14 @@ impl Thread { Opcode::GetUpvalue { dest, src } => { let closure_env = window[ENV_REG].get(mem); let upvalue = env_upvalue_lookup(mem, closure_env, src)?; - window[dest as usize].set_to_ptr(upvalue.get(mem, stack)?); + window[dest as usize].copy_from(mem, &upvalue.get(mem, stack)?); } // Follow the indirection of an Upvalue to set the value from a local register Opcode::SetUpvalue { dest, src } => { let closure_env = window[ENV_REG].get(mem); let upvalue = env_upvalue_lookup(mem, closure_env, dest)?; - upvalue.set(mem, stack, window[src as usize].get_ptr())?; + upvalue.set(mem, stack, &window[src as usize])?; } // Move up to 3 stack register values to the Upvalue objects referring to them diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..57f109e --- /dev/null +++ b/notes.md @@ -0,0 +1,199 @@ +# Notes + +## Tracing + +This _just_ needs: + - pointer values, not any hard data on types + - to get object headers from pointers to set mark bits + - read only to object memory space + +Safety: + - unsafe to trace? Why would it be? + - gray area: are we taking immutable aliases of object references? + - gray area: this is only manipulating the object header + - gray area: how could this be _unsafe_? + - no: we are dereferencing pointers to get other pointers + - yes: we are not dereferencing pointers in safe rust + - yes: we are using cell everywhere and no threading, so safe + +```rust +pub trait Trace { + fn trace(&self); +} + +// do only roots need to be scope-guarded? +pub trait Root: Trace; + + +pub trait AllocHeader: Trace; + + +impl AllocHeader for ObjectHeader { + fn mark() {} +} + +// via header to get object type +impl Trace for ObjectHeader { + fn trace() {} +} + +// through RawPtr +RawPtr::trace(&self) {} + + +Root::trace() +``` +use std::cell::RefCell; +use std::marker::PhantomPinned; +use std::ops::Deref; +use std::pin::{pin, Pin}; + +// Trace ////////////////////////////// +trait Trace { + fn trace(&self); +} + +// Gc ////////////////////////////// +#[derive(Debug, Copy, Clone)] +struct Gc { + ptr: *const T, +} + +impl Gc { + fn new(obj: T) -> Gc { + let p = Box::new(obj); + Gc { + ptr: Box::into_raw(p), + } + } + + fn null() -> Gc { + Gc { ptr: 0 as *const T } + } +} + +impl Deref for Gc { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.ptr } + } +} + +impl Trace for Gc { + fn trace(&self) { + println!("Trace for Gc"); + } +} + +// Rooting //////////////////////////// +#[derive(Debug)] +struct Root { + ptr: Gc, + heap: *const Heap, + _pin: PhantomPinned, +} + +impl Root { + fn new(mem: &Heap, from_heap: Gc) -> Root { + Root { + ptr: from_heap, + heap: mem, + _pin: PhantomPinned, + } + } +} + +impl Drop for Root { + fn drop(&mut self) { + unsafe { + let heap = &*self.heap as &Heap; + heap.pop_root(); + } + } +} + +impl Deref for Root { + type Target = T; + fn deref(&self) -> &T { + &*self.ptr + } +} + +impl Trace for Root { + fn trace(&self) { + println!("Trace for Root"); + } +} + +// Heap /////////////////////////////// +#[derive(Default)] +struct Heap { + roots: RefCell>, +} + +impl Heap { + fn push_root(&self, root: &Root) { + self.roots + .borrow_mut() + .push(root as *const Root as *const dyn Trace); + println!("roots.push: {}", self.roots.borrow().len()); + } + + fn pop_root(&self) { + self.roots.borrow_mut().pop(); + println!("roots.pop: {}", self.roots.borrow().len()); + } +} + +// Test /////////////////////////////// + +struct PinnedRoot<'root_lt, T> { + root: Pin<&'root_lt Root>, +} + +impl<'root_lt, T> PinnedRoot<'root_lt, T> { + fn new(root: &'root_lt Root) -> PinnedRoot<'root_lt, T> { + PinnedRoot { + root: unsafe { Pin::new_unchecked(root) }, + } + } +} + +impl Deref for PinnedRoot<'_, T> { + type Target = T; + fn deref(&self) -> &T { + self.root.deref() + } +} + +// Macro ////////////////////////////// +macro_rules! root { + ($mem:ident, $root_id:ident, $value:expr) => { + let $root_id = Root::new(&$mem, $value); + $mem.push_root(&$root_id); + let $root_id = PinnedRoot::new(&$root_id); + }; +} + +// Main /////////////////////////////// +fn main() { + let heap = Heap::default(); + + { + let obj = Gc::new(String::from("foobar")); + + let root = Root::new(&heap, obj); + heap.push_root(&root); + let root = PinnedRoot::new(&root); + + println!("{}", *root); + }; + + { + let obj = Gc::new(String::from("barbaz")); + + root!(heap, foo, obj); + + println!("{}", *foo); + } +} diff --git a/stickyimmix/src/allocator.rs b/stickyimmix/src/allocator.rs index e1b8d94..ef8a717 100644 --- a/stickyimmix/src/allocator.rs +++ b/stickyimmix/src/allocator.rs @@ -106,10 +106,10 @@ pub trait AllocHeader: Sized { fn new_array(size: ArraySize, size_class: SizeClass, mark: Mark) -> Self; /// Set the Mark value to "marked" - fn mark(&mut self); + fn mark(&mut self, value: Mark); /// Get the current Mark value - fn is_marked(&self) -> bool; + fn mark_is(&self, value: Mark) -> bool; /// Get the size class of the object fn size_class(&self) -> SizeClass; diff --git a/stickyimmix/src/heap.rs b/stickyimmix/src/heap.rs index de0abfd..8692a19 100644 --- a/stickyimmix/src/heap.rs +++ b/stickyimmix/src/heap.rs @@ -306,9 +306,9 @@ mod tests { } } - fn mark(&mut self) {} + fn mark(&mut self, _value: Mark) {} - fn is_marked(&self) -> bool { + fn mark_is(&self, _value: Mark) -> bool { true } From 50fae84f5e74c8f92c79163277009569e2ae659a Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Fri, 25 Apr 2025 22:08:15 -0400 Subject: [PATCH 09/32] PoC for conservative stack scan --- notes.md => scratchpad/notes.md | 19 ++ .../test_01_conservative_scan/Cargo.lock | 16 ++ .../test_01_conservative_scan/Cargo.toml | 7 + .../test_01_conservative_scan/src/main.rs | 207 ++++++++++++++++++ 4 files changed, 249 insertions(+) rename notes.md => scratchpad/notes.md (88%) create mode 100644 scratchpad/test_01_conservative_scan/Cargo.lock create mode 100644 scratchpad/test_01_conservative_scan/Cargo.toml create mode 100644 scratchpad/test_01_conservative_scan/src/main.rs diff --git a/notes.md b/scratchpad/notes.md similarity index 88% rename from notes.md rename to scratchpad/notes.md index 57f109e..4610a11 100644 --- a/notes.md +++ b/scratchpad/notes.md @@ -1,7 +1,26 @@ # Notes +- https://www.steveblackburn.org/pubs/papers/consrc-oopsla-2014.pdf +- https://docs.rs/portable-atomic/latest/portable_atomic/struct.AtomicUsize.html +- https://www.hboehm.info/gc/gcdescr.html + +## Rooting + +Conservative stack scanning. +- allows for intrusive data structures +- simpler mutator root management + - still need to use Pin to keep roots from escaping +- need to push all registers to stack + +Depends on: +- fast map of pointer to block + - vec + heap? +- object map in each block + ## Tracing +Precise object scanning. + This _just_ needs: - pointer values, not any hard data on types - to get object headers from pointers to set mark bits diff --git a/scratchpad/test_01_conservative_scan/Cargo.lock b/scratchpad/test_01_conservative_scan/Cargo.lock new file mode 100644 index 0000000..869449c --- /dev/null +++ b/scratchpad/test_01_conservative_scan/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "test7" +version = "0.1.0" +dependencies = [ + "libc", +] diff --git a/scratchpad/test_01_conservative_scan/Cargo.toml b/scratchpad/test_01_conservative_scan/Cargo.toml new file mode 100644 index 0000000..c28d4ce --- /dev/null +++ b/scratchpad/test_01_conservative_scan/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "test7" +version = "0.1.0" +edition = "2021" + +[dependencies] +libc = "0.2" diff --git a/scratchpad/test_01_conservative_scan/src/main.rs b/scratchpad/test_01_conservative_scan/src/main.rs new file mode 100644 index 0000000..8ac2425 --- /dev/null +++ b/scratchpad/test_01_conservative_scan/src/main.rs @@ -0,0 +1,207 @@ +/* + Here we will: + - a simple allocator that keeps a copy of the object in a Vec + - a stack scanner, that will + - mark objects as live + - drop dead objects +*/ +use libc::getcontext; +use std::collections::BTreeMap; +use std::hint::black_box; +use std::mem::MaybeUninit; +use std::ops::Deref; +use std::slice::from_raw_parts; + +trait Trace { + fn trace(&self, objects: &mut Vec) {} +} + +struct Gc { + inner: *const T, +} + +impl Gc { + fn new(object: *const T) -> Gc { + Gc { inner: object } + } +} + +impl Deref for Gc { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.inner as &T } + } +} + +struct HeapString { + value: String, +} + +impl HeapString { + fn from(from: &str) -> HeapString { + HeapString { + value: String::from(from), + } + } + + fn print(&self) { + println!("{}", &self.value); + } +} + +impl Trace for HeapString { + fn trace(&self, _objects: &mut Vec) {} +} + +struct StackItem { + value: usize, +} + +impl StackItem { + fn new(value: usize) -> StackItem { + StackItem { value } + } +} + +struct HeapItem { + mark: bool, + address: usize, + object: Box, +} + +impl HeapItem { + fn new(raw: *const T, tobj: Box) -> HeapItem { + HeapItem { + mark: false, + address: raw as usize, + object: tobj, + } + } +} + +struct Memory { + objects: BTreeMap, + scan: Vec, + base: usize, +} + +impl Memory { + fn new() -> Memory { + Memory { + objects: BTreeMap::new(), + scan: Vec::new(), + base: 0xbeefbabe, + } + } + + fn alloc(&mut self, object: T) -> Gc { + let obj: Box = Box::new(object); + let raw_ptr = &*obj as *const T; + + let tobj: Box = obj; + + // put Trace trait object into heap object list + let gc_ref = HeapItem::new(raw_ptr, tobj); + self.objects.insert(raw_ptr as usize, gc_ref); + + Gc::new(raw_ptr) + } + + #[no_mangle] + fn scan(&mut self) { + let mut context = MaybeUninit::zeroed(); + let result = unsafe { getcontext(context.as_mut_ptr()) }; + if result != 0 { + panic!("could not get thread context!"); + } + let stack_top_marker: usize = 0xbeefd00d; + + let mut stack_top = (&stack_top_marker as *const usize).addr(); + let mut stack_base = (&self.base as *const usize).addr(); + + if stack_top < stack_base { + (stack_top, stack_base) = (stack_base, stack_top); + } + + let word_size = size_of::(); + let stack_len = (stack_top - stack_base) / word_size; + let slice = unsafe { from_raw_parts(stack_base as *const usize, stack_len) }; + + for stack_item in slice { + self.scan.push(StackItem::new(*stack_item)); + } + + black_box(&context); + } + + fn mark(&mut self) { + let mut heap_scan: Vec = Vec::new(); + + // #1 scan the stack for heap objects + for item in self.scan.drain(..) { + let possible_address = item.value; + + if let Some(_) = self.objects.get(&possible_address) { + heap_scan.push(possible_address); + } + } + + // #2 trace the heap object graph + while heap_scan.len() > 0 { + if let Some(heap_address) = heap_scan.pop() { + if let Some(heap_item) = self.objects.get_mut(&heap_address) { + heap_item.mark = true; + heap_item.object.trace(&mut heap_scan); + } + } + } + } + + fn collect(&mut self) { + let temp = std::mem::take(&mut self.objects); + + temp.into_values().for_each(|mut heap_item| { + if heap_item.mark { + heap_item.mark = false; + self.objects.insert(heap_item.address, heap_item); + } else { + println!("DROP {:x}", heap_item.address); + drop(heap_item.object); + } + }); + } + + fn gc(&mut self) { + self.scan(); + self.mark(); + self.collect(); + self.scan.clear(); + } +} + +impl Drop for Memory { + fn drop(&mut self) { + let temp = std::mem::take(&mut self.objects); + + temp.into_values().for_each(|heap_item| { + println!("DROP {:x}", heap_item.address); + drop(heap_item.object); + }); + } +} + +fn main() { + let mut mem = Memory::new(); + + let foo = mem.alloc(HeapString::from("foosball")); + + for i in 0x0..0xF { + let bar = mem.alloc(HeapString::from("foobar")); + } + + mem.gc(); + + foo.print(); + + mem.gc(); +} From 9b2dea13be5c108ec32e63e636818a7aefc822b8 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Mon, 28 Apr 2025 22:13:24 -0400 Subject: [PATCH 10/32] PoC for conservative stack scan - reordering problem, crashes --- scratchpad/test_02_pinned_roots/Cargo.lock | 16 ++ scratchpad/test_02_pinned_roots/Cargo.toml | 7 + scratchpad/test_02_pinned_roots/src/main.rs | 277 ++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 scratchpad/test_02_pinned_roots/Cargo.lock create mode 100644 scratchpad/test_02_pinned_roots/Cargo.toml create mode 100644 scratchpad/test_02_pinned_roots/src/main.rs diff --git a/scratchpad/test_02_pinned_roots/Cargo.lock b/scratchpad/test_02_pinned_roots/Cargo.lock new file mode 100644 index 0000000..869449c --- /dev/null +++ b/scratchpad/test_02_pinned_roots/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "test7" +version = "0.1.0" +dependencies = [ + "libc", +] diff --git a/scratchpad/test_02_pinned_roots/Cargo.toml b/scratchpad/test_02_pinned_roots/Cargo.toml new file mode 100644 index 0000000..c28d4ce --- /dev/null +++ b/scratchpad/test_02_pinned_roots/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "test7" +version = "0.1.0" +edition = "2021" + +[dependencies] +libc = "0.2" diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs new file mode 100644 index 0000000..0fdc5f8 --- /dev/null +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -0,0 +1,277 @@ +/* + Here we will: + - a simple allocator that keeps a copy of the object in a Vec + - a stack scanner, that will + - mark objects as live + - drop dead objects +*/ +use libc::getcontext; +use std::collections::BTreeMap; +use std::hint::black_box; +use std::mem::MaybeUninit; +use std::ops::{Deref, DerefMut}; +use std::slice::from_raw_parts; + +trait Trace { + /// Give me all your pointers + fn trace(&self, _objects: &mut Vec) {} +} + +struct Gc { + inner: *const T, +} + +impl Gc { + fn new(object: *const T) -> Gc { + Gc { inner: object } + } + + fn addr(&self) -> usize { + self.inner.addr() + } + + fn debug(&self) { + println!("object {:x}", self.inner.addr()); + } +} + +impl Deref for Gc { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.inner as &T } + } +} + +impl DerefMut for Gc { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *(self.inner as *mut T) } + } +} + +struct HeapString { + value: String, +} + +impl HeapString { + fn from(from: &str) -> HeapString { + HeapString { + value: String::from(from), + } + } + + fn print(&self) { + println!("{}", &self.value); + } +} + +impl Trace for HeapString { + fn trace(&self, _objects: &mut Vec) {} +} + +struct HeapArray { + value: Vec>, +} + +impl HeapArray { + fn new() -> HeapArray { + HeapArray { value: Vec::new() } + } + + fn push(&mut self, object: Gc) { + self.value.push(object); + } +} + +impl Trace for HeapArray { + fn trace(&self, objects: &mut Vec) { + for item in self.value.iter() { + objects.push(item.addr()) + } + } +} + +struct StackItem { + value: usize, +} + +impl StackItem { + fn new(value: usize) -> StackItem { + StackItem { value } + } +} + +struct HeapItem { + mark: bool, + address: usize, + object: Box, +} + +impl HeapItem { + fn new(raw: *const T, tobj: Box) -> HeapItem { + HeapItem { + mark: false, + address: raw as usize, + object: tobj, + } + } +} + +struct Memory { + objects: BTreeMap, + scan: Vec, + base: usize, +} + +impl Memory { + fn new() -> Memory { + Memory { + objects: BTreeMap::new(), + scan: Vec::new(), + base: 0xbeefbabe, + } + } + + fn run(&mut self, f: F) + where + F: FnOnce(&mut Memory), + { + f(self); + } + + fn alloc(&mut self, object: T) -> Gc { + let obj: Box = Box::new(object); + let raw_ptr = &*obj as *const T; + + let tobj: Box = obj; + + // put Trace trait object into heap object list + let gc_ref = HeapItem::new(raw_ptr, tobj); + self.objects.insert(raw_ptr as usize, gc_ref); + + println!("(alloc) {:x}", raw_ptr as usize); + Gc::new(raw_ptr) + } + + #[no_mangle] + fn scan(&mut self) { + let mut context = MaybeUninit::zeroed(); + let result = unsafe { getcontext(context.as_mut_ptr()) }; + if result != 0 { + panic!("could not get thread context!"); + } + + let stack_top_marker: usize = 0xbeefd00d; + + let mut stack_top = (&stack_top_marker as *const usize).addr(); + let mut stack_base = (&self.base as *const usize).addr(); + + let word_size = size_of::(); + + if stack_top < stack_base { + (stack_top, stack_base) = (stack_base + word_size, stack_top); + } + + let stack_len = (stack_top - stack_base) / word_size; + let slice = unsafe { from_raw_parts(stack_base as *const usize, stack_len) }; + + for stack_item in slice { + if *stack_item != 0 { + println!("[stack] {:x}", *stack_item); + } + self.scan.push(StackItem::new(*stack_item)); + } + + black_box(&context); + } + + fn mark(&mut self) { + let mut heap_scan: Vec = Vec::new(); + + // #1 scan the stack for heap objects + for item in self.scan.drain(..) { + let possible_address = item.value; + + if let Some(_) = self.objects.get(&possible_address) { + heap_scan.push(possible_address); + println!("[root] {:x}", possible_address); + } + } + + // #2 trace the heap object graph + while heap_scan.len() > 0 { + if let Some(heap_address) = heap_scan.pop() { + if let Some(heap_item) = self.objects.get_mut(&heap_address) { + heap_item.mark = true; + heap_item.object.trace(&mut heap_scan); + } + } + } + } + + fn collect(&mut self) { + let temp = std::mem::take(&mut self.objects); + + temp.into_values().for_each(|mut heap_item| { + if heap_item.mark { + heap_item.mark = false; + self.objects.insert(heap_item.address, heap_item); + } else { + println!(" DROP {:x}", heap_item.address); + drop(heap_item.object); + } + }); + } + + fn gc(&mut self) { + println!(""); + self.scan(); + self.mark(); + self.collect(); + self.scan.clear(); + } +} + +impl Drop for Memory { + fn drop(&mut self) { + let temp = std::mem::take(&mut self.objects); + + temp.into_values().for_each(|heap_item| { + println!(" EXIT {:x}", heap_item.address); + drop(heap_item.object); + }); + } +} + +fn test_do_some_stuff(mem: &mut Memory) { + let mut array = mem.alloc(HeapArray::::new()); + array.debug(); + + for _ in 0x0..0xF { + let bar = mem.alloc(HeapString::from("foobar")); + array.push(bar); + } + println!("test_do_some_stuff"); +} + +fn main() { + let mut mem = Memory::new(); + + mem.run(|mem| { + let foo = mem.alloc(HeapString::from("foosball")); + + for _ in 0x0..0xF { + let _bar = mem.alloc(HeapString::from("foobar")); + } + mem.gc(); + + test_do_some_stuff(mem); + + foo.debug(); + foo.print(); + + let bar = mem.alloc(HeapString::from("barbell")); + bar.debug(); + + mem.gc(); + }); +} From ca0acd86da62f0ce3bb906b1047fd8cfd065a8cd Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 30 Apr 2025 21:47:28 -0400 Subject: [PATCH 11/32] PoC for conservative stack scan - reordering problem, crashes - introducing scoping --- scratchpad/test_02_pinned_roots/src/main.rs | 63 +++++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index 0fdc5f8..c8f0fa1 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -107,10 +107,10 @@ struct HeapItem { } impl HeapItem { - fn new(raw: *const T, tobj: Box) -> HeapItem { + fn new(addr: usize, tobj: Box) -> HeapItem { HeapItem { mark: false, - address: raw as usize, + address: addr, object: tobj, } } @@ -131,24 +131,18 @@ impl Memory { } } - fn run(&mut self, f: F) - where - F: FnOnce(&mut Memory), - { - f(self); - } - fn alloc(&mut self, object: T) -> Gc { let obj: Box = Box::new(object); let raw_ptr = &*obj as *const T; + let addr = raw_ptr.addr(); let tobj: Box = obj; // put Trace trait object into heap object list - let gc_ref = HeapItem::new(raw_ptr, tobj); - self.objects.insert(raw_ptr as usize, gc_ref); + let gc_ref = HeapItem::new(addr, tobj); + self.objects.insert(addr, gc_ref); - println!("(alloc) {:x}", raw_ptr as usize); + println!("(alloc) {:x}", addr); Gc::new(raw_ptr) } @@ -175,9 +169,9 @@ impl Memory { let slice = unsafe { from_raw_parts(stack_base as *const usize, stack_len) }; for stack_item in slice { - if *stack_item != 0 { - println!("[stack] {:x}", *stack_item); - } + // if *stack_item != 0 { + // println!("[stack] {:x}", *stack_item); + // } self.scan.push(StackItem::new(*stack_item)); } @@ -229,6 +223,14 @@ impl Memory { self.collect(); self.scan.clear(); } + + fn enter(&mut self, run: F) + where + F: FnOnce(&mut MutatorView), + { + let mut delegate = MutatorView::new(self); + run(&mut delegate); + } } impl Drop for Memory { @@ -242,7 +244,32 @@ impl Drop for Memory { } } -fn test_do_some_stuff(mem: &mut Memory) { +trait MutatorScope {} + +struct MutatorView<'memory> { + mem: &'memory mut Memory, +} + +impl<'memory> MutatorScope for MutatorView<'memory> {} + +impl<'memory> MutatorView<'memory> { + fn new(mem: &'memory mut Memory) -> Self { + MutatorView { mem } + } + + fn alloc(&mut self, value: T) -> Gc + where + T: Trace, + { + self.mem.alloc(value) + } + + fn gc(&mut self) { + self.mem.gc(); + } +} + +fn test_do_some_stuff(mem: &mut MutatorView) { let mut array = mem.alloc(HeapArray::::new()); array.debug(); @@ -254,9 +281,9 @@ fn test_do_some_stuff(mem: &mut Memory) { } fn main() { - let mut mem = Memory::new(); + let mut arena = Memory::new(); - mem.run(|mem| { + arena.enter(|mem| { let foo = mem.alloc(HeapString::from("foosball")); for _ in 0x0..0xF { From 492132c4bf3c9035413e2cca1dae7ba77a4ceacf Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 3 May 2025 20:50:08 -0400 Subject: [PATCH 12/32] PoC for conservative stack scan - reordering problem, crashes - scoping implemented, didn't fix --- .gitignore | 1 + scratchpad/test_02_pinned_roots/src/main.rs | 94 ++++++++++++--------- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 7585238..05ff2e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ book +target diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index c8f0fa1..403df13 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -6,6 +6,7 @@ - drop dead objects */ use libc::getcontext; +use std::cell::RefCell; use std::collections::BTreeMap; use std::hint::black_box; use std::mem::MaybeUninit; @@ -100,14 +101,14 @@ impl StackItem { } } -struct HeapItem { +struct HeapItem<'memory> { mark: bool, address: usize, - object: Box, + object: Box, } -impl HeapItem { - fn new(addr: usize, tobj: Box) -> HeapItem { +impl<'memory> HeapItem<'memory> { + fn new(addr: usize, tobj: Box) -> HeapItem<'memory> { HeapItem { mark: false, address: addr, @@ -116,22 +117,29 @@ impl HeapItem { } } -struct Memory { - objects: BTreeMap, +struct MemoryInner<'memory> { + objects: BTreeMap>, scan: Vec, +} + +struct Memory<'memory> { + inner: RefCell>, base: usize, } -impl Memory { - fn new() -> Memory { - Memory { +impl<'memory> Memory<'memory> { + fn new() -> Memory<'memory> { + let inner = MemoryInner::<'memory> { objects: BTreeMap::new(), scan: Vec::new(), + }; + Memory { + inner: RefCell::new(inner), base: 0xbeefbabe, } } - fn alloc(&mut self, object: T) -> Gc { + fn alloc(&self, object: T) -> Gc { let obj: Box = Box::new(object); let raw_ptr = &*obj as *const T; let addr = raw_ptr.addr(); @@ -140,14 +148,14 @@ impl Memory { // put Trace trait object into heap object list let gc_ref = HeapItem::new(addr, tobj); - self.objects.insert(addr, gc_ref); + self.inner.borrow_mut().objects.insert(addr, gc_ref); println!("(alloc) {:x}", addr); Gc::new(raw_ptr) } #[no_mangle] - fn scan(&mut self) { + fn scan(&self) { let mut context = MaybeUninit::zeroed(); let result = unsafe { getcontext(context.as_mut_ptr()) }; if result != 0 { @@ -168,24 +176,30 @@ impl Memory { let stack_len = (stack_top - stack_base) / word_size; let slice = unsafe { from_raw_parts(stack_base as *const usize, stack_len) }; + let stack_scan = &mut self.inner.borrow_mut().scan; + for stack_item in slice { // if *stack_item != 0 { // println!("[stack] {:x}", *stack_item); // } - self.scan.push(StackItem::new(*stack_item)); + stack_scan.push(StackItem::new(*stack_item)); } black_box(&context); } - fn mark(&mut self) { + fn mark(&self) { let mut heap_scan: Vec = Vec::new(); + let mut inner = self.inner.borrow_mut(); + + let mut scan = std::mem::take(&mut inner.scan); + // #1 scan the stack for heap objects - for item in self.scan.drain(..) { + for item in scan.drain(..) { let possible_address = item.value; - if let Some(_) = self.objects.get(&possible_address) { + if let Some(_) = inner.objects.get(&possible_address) { heap_scan.push(possible_address); println!("[root] {:x}", possible_address); } @@ -194,7 +208,7 @@ impl Memory { // #2 trace the heap object graph while heap_scan.len() > 0 { if let Some(heap_address) = heap_scan.pop() { - if let Some(heap_item) = self.objects.get_mut(&heap_address) { + if let Some(heap_item) = inner.objects.get_mut(&heap_address) { heap_item.mark = true; heap_item.object.trace(&mut heap_scan); } @@ -202,13 +216,15 @@ impl Memory { } } - fn collect(&mut self) { - let temp = std::mem::take(&mut self.objects); + fn collect(&self) { + let mut inner = self.inner.borrow_mut(); + + let temp = std::mem::take(&mut inner.objects); temp.into_values().for_each(|mut heap_item| { if heap_item.mark { heap_item.mark = false; - self.objects.insert(heap_item.address, heap_item); + inner.objects.insert(heap_item.address, heap_item); } else { println!(" DROP {:x}", heap_item.address); drop(heap_item.object); @@ -216,26 +232,28 @@ impl Memory { }); } - fn gc(&mut self) { + fn gc(&self) { println!(""); self.scan(); self.mark(); self.collect(); - self.scan.clear(); } - fn enter(&mut self, run: F) + fn enter<'guard, F>(&'guard self, run: F) where - F: FnOnce(&mut MutatorView), + F: FnOnce(&MutatorView<'memory, 'guard>), + 'memory: 'guard, { - let mut delegate = MutatorView::new(self); - run(&mut delegate); + let delegate = MutatorView::new(self); + run(&delegate); } } -impl Drop for Memory { +impl<'memory> Drop for Memory<'memory> { fn drop(&mut self) { - let temp = std::mem::take(&mut self.objects); + let mut inner = self.inner.borrow_mut(); + + let temp = std::mem::take(&mut inner.objects); temp.into_values().for_each(|heap_item| { println!(" EXIT {:x}", heap_item.address); @@ -246,30 +264,30 @@ impl Drop for Memory { trait MutatorScope {} -struct MutatorView<'memory> { - mem: &'memory mut Memory, +struct MutatorView<'memory, 'guard> { + mem: &'guard Memory<'memory>, } -impl<'memory> MutatorScope for MutatorView<'memory> {} +impl<'memory, 'guard> MutatorScope for MutatorView<'memory, 'guard> {} -impl<'memory> MutatorView<'memory> { - fn new(mem: &'memory mut Memory) -> Self { - MutatorView { mem } +impl<'memory, 'guard> MutatorView<'memory, 'guard> { + fn new(mem: &'guard Memory<'memory>) -> Self { + MutatorView::<'memory, 'guard> { mem } } - fn alloc(&mut self, value: T) -> Gc + fn alloc(&self, value: T) -> Gc where T: Trace, { self.mem.alloc(value) } - fn gc(&mut self) { + fn gc(&self) { self.mem.gc(); } } -fn test_do_some_stuff(mem: &mut MutatorView) { +fn test_do_some_stuff(mem: &MutatorView) { let mut array = mem.alloc(HeapArray::::new()); array.debug(); @@ -281,7 +299,7 @@ fn test_do_some_stuff(mem: &mut MutatorView) { } fn main() { - let mut arena = Memory::new(); + let arena = Memory::new(); arena.enter(|mem| { let foo = mem.alloc(HeapString::from("foosball")); From ad25f5aaf16a074d59b830a36e42ca0c183edcfe Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 10 May 2025 23:40:47 -0400 Subject: [PATCH 13/32] add root pinning test --- scratchpad/test_00_pinning/.gitignore | 1 + scratchpad/test_00_pinning/Cargo.lock | 7 + scratchpad/test_00_pinning/Cargo.toml | 6 + scratchpad/test_00_pinning/backup/main_1.rs | 154 ++++++++++++++++++++ scratchpad/test_00_pinning/src/main.rs | 154 ++++++++++++++++++++ scratchpad/test_02_pinned_roots/Cargo.toml | 3 + scratchpad/test_02_pinned_roots/src/main.rs | 1 - 7 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 scratchpad/test_00_pinning/.gitignore create mode 100644 scratchpad/test_00_pinning/Cargo.lock create mode 100644 scratchpad/test_00_pinning/Cargo.toml create mode 100644 scratchpad/test_00_pinning/backup/main_1.rs create mode 100644 scratchpad/test_00_pinning/src/main.rs diff --git a/scratchpad/test_00_pinning/.gitignore b/scratchpad/test_00_pinning/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/scratchpad/test_00_pinning/.gitignore @@ -0,0 +1 @@ +/target diff --git a/scratchpad/test_00_pinning/Cargo.lock b/scratchpad/test_00_pinning/Cargo.lock new file mode 100644 index 0000000..2056aaf --- /dev/null +++ b/scratchpad/test_00_pinning/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test5" +version = "0.1.0" diff --git a/scratchpad/test_00_pinning/Cargo.toml b/scratchpad/test_00_pinning/Cargo.toml new file mode 100644 index 0000000..a348e27 --- /dev/null +++ b/scratchpad/test_00_pinning/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "test5" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/scratchpad/test_00_pinning/backup/main_1.rs b/scratchpad/test_00_pinning/backup/main_1.rs new file mode 100644 index 0000000..df6a462 --- /dev/null +++ b/scratchpad/test_00_pinning/backup/main_1.rs @@ -0,0 +1,154 @@ +use std::cell::RefCell; +use std::marker::PhantomPinned; +use std::ops::Deref; +use std::pin::{pin, Pin}; + +// Trace ////////////////////////////// +trait Trace { + fn trace(&self); +} + +// Gc ////////////////////////////// +#[derive(Debug, Copy, Clone)] +struct Gc { + ptr: *const T, +} + +impl Gc { + fn new(obj: T) -> Gc { + let p = Box::new(obj); + Gc { + ptr: Box::into_raw(p), + } + } + + fn null() -> Gc { + Gc { ptr: 0 as *const T } + } +} + +impl Deref for Gc { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.ptr } + } +} + +impl Trace for Gc { + fn trace(&self) { + println!("Trace for Gc"); + } +} + +// Rooting //////////////////////////// +#[derive(Debug)] +struct Root { + ptr: Gc, + heap: *const Heap, + _pin: PhantomPinned, +} + +impl Root { + fn new(mem: &Heap, from_heap: Gc) -> Root { + Root { + ptr: from_heap, + heap: mem, + _pin: PhantomPinned, + } + } +} + +impl Drop for Root { + fn drop(&mut self) { + unsafe { + let heap = &*self.heap as &Heap; + heap.pop_root(); + } + } +} + +impl Deref for Root { + type Target = T; + fn deref(&self) -> &T { + &*self.ptr + } +} + +impl Trace for Root { + fn trace(&self) { + println!("Trace for Root"); + } +} + +// Heap /////////////////////////////// +#[derive(Default)] +struct Heap { + roots: RefCell>, +} + +impl Heap { + fn push_root(&self, root: &Root) { + self.roots + .borrow_mut() + .push(root as *const Root as *const dyn Trace); + println!("roots.push: {}", self.roots.borrow().len()); + } + + fn pop_root(&self) { + self.roots.borrow_mut().pop(); + println!("roots.pop: {}", self.roots.borrow().len()); + } +} + +// Test /////////////////////////////// + +struct PinnedRoot<'root_lt, T> { + root: Pin<&'root_lt Root>, +} + +impl<'root_lt, T> PinnedRoot<'root_lt, T> { + fn new(root: &'root_lt Root) -> PinnedRoot<'root_lt, T> { + PinnedRoot { + root: unsafe { Pin::new_unchecked(root) }, + } + } +} + +impl Deref for PinnedRoot<'_, T> { + type Target = T; + fn deref(&self) -> &T { + self.root.deref() + } +} + +// Macro ////////////////////////////// +macro_rules! root { + ($mem:ident, $root_id:ident, $value:expr) => { + let $root_id = Root::new(&$mem, $value); + $mem.push_root(&$root_id); + let $root_id = PinnedRoot::new(&$root_id); + }; +} + +// Main /////////////////////////////// +fn main() { + let heap = Heap::default(); + + { + let obj = Gc::new(String::from("foobar")); + + let root = Root::new(&heap, obj); + heap.push_root(&root); + let root = PinnedRoot::new(&root); + + println!("{}", *root); + }; + + { + let obj = Gc::new(String::from("barbaz")); + + root!(heap, foo, obj); + + println!("{}", *foo); + } +} diff --git a/scratchpad/test_00_pinning/src/main.rs b/scratchpad/test_00_pinning/src/main.rs new file mode 100644 index 0000000..bd721f7 --- /dev/null +++ b/scratchpad/test_00_pinning/src/main.rs @@ -0,0 +1,154 @@ +use std::cell::RefCell; +use std::marker::PhantomPinned; +use std::ops::{Deref, DerefMut}; +use std::pin::{pin, Pin}; + +// Trace ////////////////////////////// +trait Trace { + fn trace(&self); +} + +// Gc ////////////////////////////// +#[derive(Debug, Copy, Clone)] +struct Gc { + ptr: *const T, +} + +impl Gc { + fn new(obj: T) -> Gc { + let p = Box::new(obj); + Gc { + ptr: Box::into_raw(p), + } + } + + fn null() -> Gc { + Gc { ptr: 0 as *const T } + } + + fn as_usize(&self) -> usize { + self.ptr as usize + } +} + +impl Deref for Gc { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.ptr } + } +} + +impl DerefMut for Gc { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *(self.ptr as *mut T) } + } +} + +impl Trace for Gc { + fn trace(&self) { + println!("Trace for Gc"); + } +} + +// Rooting //////////////////////////// +#[derive(Debug)] +struct Root { + ptr: Gc, + _pin: PhantomPinned, +} + +impl Root { + fn new(from_heap: Gc) -> Root { + Root { + ptr: from_heap, + _pin: PhantomPinned, + } + } +} + +impl Drop for Root { + fn drop(&mut self) {} +} + +impl Deref for Root { + type Target = T; + fn deref(&self) -> &T { + &*self.ptr + } +} + +impl DerefMut for Root { + fn deref_mut(&mut self) -> &mut T { + &mut *self.ptr + } +} + +impl Trace for Root { + fn trace(&self) { + println!("Trace for Root"); + } +} + +// Heap /////////////////////////////// +#[derive(Default)] +struct Heap { + allocations: Vec, +} + +impl Heap { + fn alloc(&mut self, obj: T) -> Gc { + let a = Gc::new(obj); + self.allocations.push(a.as_usize()); + a + } +} + +// Test /////////////////////////////// + +struct PinnedRoot<'root_lt, T> { + root: Pin<&'root_lt mut Root>, +} + +impl<'root_lt, T> PinnedRoot<'root_lt, T> { + fn new(root: &'root_lt mut Root) -> PinnedRoot<'root_lt, T> { + PinnedRoot { + root: unsafe { Pin::new_unchecked(root) }, + } + } +} + +impl Deref for PinnedRoot<'_, T> { + type Target = T; + fn deref(&self) -> &T { + self.root.deref() + } +} + +// Macro ////////////////////////////// +macro_rules! root { + ($root_id:ident, $value:expr) => { + let mut $root_id = Root::new($value); + #[allow(unused_mut)] + let mut $root_id = PinnedRoot::new(&mut $root_id); + }; +} + +// Main /////////////////////////////// +fn main() { + let mut heap = Heap::default(); + + { + let obj = heap.alloc(String::from("foobar")); + + let mut root = Root::new(obj); + let mut root = PinnedRoot::new(&mut root); + + println!("{}", *root); + }; + + { + root!(foo, heap.alloc(String::from("barbaz"))); + + println!("{}", *foo); + } +} diff --git a/scratchpad/test_02_pinned_roots/Cargo.toml b/scratchpad/test_02_pinned_roots/Cargo.toml index c28d4ce..8bd6440 100644 --- a/scratchpad/test_02_pinned_roots/Cargo.toml +++ b/scratchpad/test_02_pinned_roots/Cargo.toml @@ -5,3 +5,6 @@ edition = "2021" [dependencies] libc = "0.2" + +[profile.release] +debug = true diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index 403df13..aa7b757 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -154,7 +154,6 @@ impl<'memory> Memory<'memory> { Gc::new(raw_ptr) } - #[no_mangle] fn scan(&self) { let mut context = MaybeUninit::zeroed(); let result = unsafe { getcontext(context.as_mut_ptr()) }; From 361f70ff53839a3e2b47f8f333dcb1e7b2904e5b Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 14 May 2025 22:29:36 -0400 Subject: [PATCH 14/32] thoughts --- scratchpad/test_02_pinned_roots/src/main.rs | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index aa7b757..0a40ae3 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -9,6 +9,7 @@ use libc::getcontext; use std::cell::RefCell; use std::collections::BTreeMap; use std::hint::black_box; +use std::marker::PhantomData; use std::mem::MaybeUninit; use std::ops::{Deref, DerefMut}; use std::slice::from_raw_parts; @@ -297,9 +298,53 @@ fn test_do_some_stuff(mem: &MutatorView) { println!("test_do_some_stuff"); } +fn test_do_all_stuff(mem: &MutatorView) { + let foo = mem.alloc(HeapString::from("foosball")); + + for _ in 0x0..0xF { + let _bar = mem.alloc(HeapString::from("foobar")); + } + mem.gc(); + + test_do_some_stuff(mem); + + foo.debug(); + foo.print(); + + let bar = mem.alloc(HeapString::from("barbell")); + bar.debug(); + + mem.gc(); +} + +struct Root<'guard, T: Trace> { + p: PhantomData<&'guard Gc>, +} + +impl<'guard, T: Trace> Root<'guard, T> { + fn new(_variable: &'guard Gc) -> Root<'guard, T> { + Root { p: PhantomData } + } +} + fn main() { let arena = Memory::new(); + arena.enter(test_do_all_stuff); + + // Regarding pinning... + // + // - mutable variables can be std::mem::replace()'d etc + // - immutable variables can not + // + // Pinning requires shadowing every root to hold it in place + // + // - the assumption is that a root might escape from its scope + // - The Gc type is most at risk of being escaped by being stored somewhere that can escape + // - An immutable Root<'lifetime, T> is not at risk + // + // Thus the fix to preventing roots escaping is to make all data structures provide a root-based API + arena.enter(|mem| { let foo = mem.alloc(HeapString::from("foosball")); From fe6f3cc790d42c4536156b94c6ea6f28c59eab57 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Fri, 16 May 2025 21:08:34 -0400 Subject: [PATCH 15/32] Add permaseal example --- scratchpad/test_02_pinned_roots/src/main.rs | 74 ++++++-- scratchpad/test_03_permaseal/.gitignore | 1 + scratchpad/test_03_permaseal/Cargo.lock | 7 + scratchpad/test_03_permaseal/Cargo.toml | 6 + scratchpad/test_03_permaseal/src/main.rs | 188 ++++++++++++++++++++ 5 files changed, 263 insertions(+), 13 deletions(-) create mode 100644 scratchpad/test_03_permaseal/.gitignore create mode 100644 scratchpad/test_03_permaseal/Cargo.lock create mode 100644 scratchpad/test_03_permaseal/Cargo.toml create mode 100644 scratchpad/test_03_permaseal/src/main.rs diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index 0a40ae3..4b988ca 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -14,11 +14,13 @@ use std::mem::MaybeUninit; use std::ops::{Deref, DerefMut}; use std::slice::from_raw_parts; +/////////////////////// trait Trace { /// Give me all your pointers fn trace(&self, _objects: &mut Vec) {} } +/////////////////////// struct Gc { inner: *const T, } @@ -35,8 +37,13 @@ impl Gc { fn debug(&self) { println!("object {:x}", self.inner.addr()); } + + unsafe fn as_ref(&self) -> &T { + &*self.inner as &T + } } +/*/ impl Deref for Gc { type Target = T; fn deref(&self) -> &T { @@ -49,7 +56,9 @@ impl DerefMut for Gc { unsafe { &mut *(self.inner as *mut T) } } } +*/ +/////////////////////// struct HeapString { value: String, } @@ -70,6 +79,7 @@ impl Trace for HeapString { fn trace(&self, _objects: &mut Vec) {} } +/////////////////////// struct HeapArray { value: Vec>, } @@ -79,7 +89,7 @@ impl HeapArray { HeapArray { value: Vec::new() } } - fn push(&mut self, object: Gc) { + fn push(&mut self, object: &Root) { self.value.push(object); } } @@ -92,6 +102,7 @@ impl Trace for HeapArray { } } +/////////////////////// struct StackItem { value: usize, } @@ -102,6 +113,7 @@ impl StackItem { } } +/////////////////////// struct HeapItem<'memory> { mark: bool, address: usize, @@ -118,11 +130,13 @@ impl<'memory> HeapItem<'memory> { } } +/////////////////////// struct MemoryInner<'memory> { objects: BTreeMap>, scan: Vec, } +/////////////////////// struct Memory<'memory> { inner: RefCell>, base: usize, @@ -262,8 +276,10 @@ impl<'memory> Drop for Memory<'memory> { } } +/////////////////////// trait MutatorScope {} +/////////////////////// struct MutatorView<'memory, 'guard> { mem: &'guard Memory<'memory>, } @@ -275,18 +291,52 @@ impl<'memory, 'guard> MutatorView<'memory, 'guard> { MutatorView::<'memory, 'guard> { mem } } - fn alloc(&self, value: T) -> Gc + fn alloc_raw(&self, value: T) -> Gc where T: Trace, { self.mem.alloc(value) } + fn alloc(&self, value: T) -> Root<'memory, T> + where + T: Trace, + { + Root::new(self.alloc_raw(value)) + } + fn gc(&self) { self.mem.gc(); } } +/////////////////////// +struct Root<'guard, T: Trace> { + var: Gc, + p: PhantomData<&'guard T>, +} + +impl<'guard, T: Trace> Root<'guard, T> { + fn new(var: Gc) -> Root<'guard, T> { + Root { + var, + p: PhantomData, + } + } + + fn debug(&self) { + self.var.debug(); + } +} + +impl<'guard, T: Trace> Deref for Root<'guard, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { self.var.as_ref() } + } +} + +/////////////////////// fn test_do_some_stuff(mem: &MutatorView) { let mut array = mem.alloc(HeapArray::::new()); array.debug(); @@ -317,20 +367,10 @@ fn test_do_all_stuff(mem: &MutatorView) { mem.gc(); } -struct Root<'guard, T: Trace> { - p: PhantomData<&'guard Gc>, -} - -impl<'guard, T: Trace> Root<'guard, T> { - fn new(_variable: &'guard Gc) -> Root<'guard, T> { - Root { p: PhantomData } - } -} - fn main() { let arena = Memory::new(); - arena.enter(test_do_all_stuff); + //arena.enter(test_do_all_stuff); // Regarding pinning... // @@ -344,6 +384,12 @@ fn main() { // - An immutable Root<'lifetime, T> is not at risk // // Thus the fix to preventing roots escaping is to make all data structures provide a root-based API + // + // The interior mutability pattern must be strictly adhered to. Is there a way to enforce??? + // + // TODO: arena for reference counted input/output pointers + + let mut escapees = Vec::new(); arena.enter(|mem| { let foo = mem.alloc(HeapString::from("foosball")); @@ -362,5 +408,7 @@ fn main() { bar.debug(); mem.gc(); + + escapees.push(foo); }); } diff --git a/scratchpad/test_03_permaseal/.gitignore b/scratchpad/test_03_permaseal/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/scratchpad/test_03_permaseal/.gitignore @@ -0,0 +1 @@ +/target diff --git a/scratchpad/test_03_permaseal/Cargo.lock b/scratchpad/test_03_permaseal/Cargo.lock new file mode 100644 index 0000000..ec380b2 --- /dev/null +++ b/scratchpad/test_03_permaseal/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "test4" +version = "0.1.0" diff --git a/scratchpad/test_03_permaseal/Cargo.toml b/scratchpad/test_03_permaseal/Cargo.toml new file mode 100644 index 0000000..bb56ef9 --- /dev/null +++ b/scratchpad/test_03_permaseal/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "test4" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/scratchpad/test_03_permaseal/src/main.rs b/scratchpad/test_03_permaseal/src/main.rs new file mode 100644 index 0000000..a150b8a --- /dev/null +++ b/scratchpad/test_03_permaseal/src/main.rs @@ -0,0 +1,188 @@ +/* +A heap needs to be blocked from _general_ access. + +Something needs to mediate between native-Rust and interpreter heap. + +Impossible to prevent CellPtr and TaggedCellPtr leaks. + +Define Root types +- roots are technically taken in vm opcode handling +- escapable roots, that can be taken beyond the vm - need a name + - pinnable? + +- Pin + +*/ + +use std::cell::RefCell; +use std::ops::Deref; +use std::pin::Pin; + +//////////////////////// Rootability // +mod permaseal { + pub trait _PermaSeal {} + impl _PermaSeal for super::Allow {} + impl _PermaSeal for super::Deny {} +} + +trait _AccessControl: permaseal::_PermaSeal {} +struct Allow {} +struct Deny {} +impl _AccessControl for Allow {} +impl _AccessControl for Deny {} + +trait RootPermission { + type Rootable: _AccessControl; +} + +//////////////////////// Trace // +trait Trace: RootPermission { + fn trace(&self); +} + +//////////////////////// Heap Allocatable Object // +#[derive(Copy, Clone, Debug)] +struct HeapInt { + value: i64, +} + +impl HeapInt { + fn new(val: i64) -> HeapInt { + HeapInt { value: val } + } +} + +impl RootPermission for HeapInt { + type Rootable = Deny; +} + +impl Trace for HeapInt { + fn trace(&self) {} +} + +//////////////////////// Heap Pointer // +#[derive(Copy, Clone, Debug)] +struct Gc> { + ptr: *const T, +} + +impl<'guard, T: RootPermission> Gc { + fn new(object: *const T) -> Gc { + Gc { ptr: object } + } + + fn deref(&self, _guard: &'guard MemoryRef) -> &'guard T { + unsafe { &*self.ptr as &'guard T } + } +} + +impl> RootPermission for Gc { + type Rootable = Deny; +} + +impl> Trace for Gc { + fn trace(&self) {} +} + +//////////////////////// Rooted Heap Pointer // +#[derive(Copy, Clone, Debug)] +struct Root> { + value: Gc, +} + +impl Root +where + T: RootPermission, +{ + fn new(object: Gc) -> Root { + Root { value: object } + } +} + +impl RootPermission for Root +where + T: RootPermission, +{ + type Rootable = Allow; +} + +impl> Trace for Root { + fn trace(&self) {} +} + +//////////////////////// Heap Mutation Environment // +trait Mutator: Sized { + type Input: RootPermission; + type Output: RootPermission; + + fn run(&self, mem: &MemoryRef, input: Self::Input) -> Self::Output; +} + +struct Memory {} + +impl Memory { + fn alloc>(&self, value: T) -> Gc { + let boxed = Box::new(value); + Gc::new(Box::into_raw(boxed)) + } + + fn mutate(&self) + where + F: FnOnce(), + { + } +} + +struct MemoryRef<'scope> { + heap: &'scope Memory, +} + +impl Deref for MemoryRef<'_> { + type Target = Memory; + fn deref(&self) -> &Memory { + self.heap + } +} + +struct Mutant {} + +impl Mutator for Mutant { + type Input = IO; + type Output = IO; + + fn run(&self, mem: &MemoryRef, input: Self::Input) -> Self::Output { + let heapint = mem.alloc(HeapInt::new(3)); + heapint.trace(); + let root = Root::new(heapint); + println!("{:?}", heapint.deref(mem)); + input.escaped.borrow_mut().push(root); + input + } +} + +struct IO { + escaped: RefCell>>, +} + +impl RootPermission for IO { + type Rootable = Allow; +} + +//////////////////////// Main // +fn main() { + let m = Memory {}; + + let scope = MemoryRef { heap: &m }; + + let mutant = Mutant {}; + + let io = IO { + escaped: RefCell::new(Vec::new()), + }; + + let result = mutant.run(&scope, io); + + for value in result.escaped.borrow().iter() { + println!("result: {:?}", value); + } +} From f0452741d897039d0492794999326c889d884654 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Fri, 16 May 2025 22:07:47 -0400 Subject: [PATCH 16/32] Interior mutability plus scoped access --- scratchpad/test_02_pinned_roots/src/main.rs | 47 +++++++++------------ 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index 4b988ca..179b3a6 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -11,7 +11,7 @@ use std::collections::BTreeMap; use std::hint::black_box; use std::marker::PhantomData; use std::mem::MaybeUninit; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; use std::slice::from_raw_parts; /////////////////////// @@ -25,6 +25,14 @@ struct Gc { inner: *const T, } +impl Copy for Gc {} + +impl Clone for Gc { + fn clone(&self) -> Self { + Gc::new(self.inner) + } +} + impl Gc { fn new(object: *const T) -> Gc { Gc { inner: object } @@ -43,21 +51,6 @@ impl Gc { } } -/*/ -impl Deref for Gc { - type Target = T; - fn deref(&self) -> &T { - unsafe { &*self.inner as &T } - } -} - -impl DerefMut for Gc { - fn deref_mut(&mut self) -> &mut T { - unsafe { &mut *(self.inner as *mut T) } - } -} -*/ - /////////////////////// struct HeapString { value: String, @@ -80,23 +73,25 @@ impl Trace for HeapString { } /////////////////////// -struct HeapArray { - value: Vec>, +struct HeapArray { + value: RefCell>>, } -impl HeapArray { +impl HeapArray { fn new() -> HeapArray { - HeapArray { value: Vec::new() } + HeapArray { + value: RefCell::new(Vec::new()), + } } - fn push(&mut self, object: &Root) { - self.value.push(object); + fn push(&self, object: &Root) { + self.value.borrow_mut().push(object.var); } } -impl Trace for HeapArray { +impl Trace for HeapArray { fn trace(&self, objects: &mut Vec) { - for item in self.value.iter() { + for item in self.value.borrow().iter() { objects.push(item.addr()) } } @@ -338,12 +333,12 @@ impl<'guard, T: Trace> Deref for Root<'guard, T> { /////////////////////// fn test_do_some_stuff(mem: &MutatorView) { - let mut array = mem.alloc(HeapArray::::new()); + let array = mem.alloc(HeapArray::::new()); array.debug(); for _ in 0x0..0xF { let bar = mem.alloc(HeapString::from("foobar")); - array.push(bar); + array.push(&bar); } println!("test_do_some_stuff"); } From 3455ce0f6b8b014e624ea0582a3551fccbc41e62 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 17 May 2025 22:28:42 -0400 Subject: [PATCH 17/32] Unescapable roots --- scratchpad/test_02_pinned_roots/src/main.rs | 165 ++++++++------------ 1 file changed, 67 insertions(+), 98 deletions(-) diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index 179b3a6..bfdb1f9 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -1,10 +1,20 @@ -/* - Here we will: - - a simple allocator that keeps a copy of the object in a Vec - - a stack scanner, that will - - mark objects as live - - drop dead objects -*/ +// Regarding pinning... +// +// - mutable variables can be std::mem::replace()'d etc +// - immutable variables can not +// +// Pinning requires shadowing every root to hold it in place +// +// - the assumption is that a root might escape from its scope +// - The Gc type is most at risk of being escaped by being stored somewhere that can escape +// - An immutable Root<'lifetime, T> is not at risk +// +// Thus the fix to preventing roots escaping is to make all data structures provide a root-based API +// +// The interior mutability pattern must be strictly adhered to. Is there a way to enforce??? +// +// TODO: arena for reference counted input/output pointers + use libc::getcontext; use std::cell::RefCell; use std::collections::BTreeMap; @@ -126,20 +136,20 @@ impl<'memory> HeapItem<'memory> { } /////////////////////// -struct MemoryInner<'memory> { - objects: BTreeMap>, +struct MemoryInner<'heap> { + objects: BTreeMap>, scan: Vec, } /////////////////////// -struct Memory<'memory> { - inner: RefCell>, +struct Memory<'heap> { + inner: RefCell>, base: usize, } -impl<'memory> Memory<'memory> { - fn new() -> Memory<'memory> { - let inner = MemoryInner::<'memory> { +impl<'heap> Memory<'heap> { + fn new() -> Memory<'heap> { + let inner = MemoryInner::<'heap> { objects: BTreeMap::new(), scan: Vec::new(), }; @@ -149,7 +159,7 @@ impl<'memory> Memory<'memory> { } } - fn alloc(&self, object: T) -> Gc { + fn alloc_raw(&self, object: T) -> Gc { let obj: Box = Box::new(object); let raw_ptr = &*obj as *const T; let addr = raw_ptr.addr(); @@ -164,6 +174,10 @@ impl<'memory> Memory<'memory> { Gc::new(raw_ptr) } + fn alloc<'mem, T: Trace + 'heap>(&'mem self, value: T) -> Root<'mem, T> { + Root::new(self.alloc_raw(value)) + } + fn scan(&self) { let mut context = MaybeUninit::zeroed(); let result = unsafe { getcontext(context.as_mut_ptr()) }; @@ -248,17 +262,17 @@ impl<'memory> Memory<'memory> { self.collect(); } - fn enter<'guard, F>(&'guard self, run: F) + fn enter<'mutator, F>(&'mutator self, mutant: F) where - F: FnOnce(&MutatorView<'memory, 'guard>), - 'memory: 'guard, + F: FnOnce(&MutatorView<'heap, 'mutator>), + //'heap: 'mutator, { let delegate = MutatorView::new(self); - run(&delegate); + mutant(&delegate); } } -impl<'memory> Drop for Memory<'memory> { +impl<'heap> Drop for Memory<'heap> { fn drop(&mut self) { let mut inner = self.inner.borrow_mut(); @@ -272,47 +286,35 @@ impl<'memory> Drop for Memory<'memory> { } /////////////////////// -trait MutatorScope {} - -/////////////////////// -struct MutatorView<'memory, 'guard> { - mem: &'guard Memory<'memory>, +struct MutatorView<'heap, 'mutator> { + mem: &'mutator Memory<'heap>, } -impl<'memory, 'guard> MutatorScope for MutatorView<'memory, 'guard> {} - -impl<'memory, 'guard> MutatorView<'memory, 'guard> { - fn new(mem: &'guard Memory<'memory>) -> Self { - MutatorView::<'memory, 'guard> { mem } +impl<'heap, 'mutator> MutatorView<'heap, 'mutator> { + fn new(mem: &'mutator Memory<'heap>) -> Self { + MutatorView { mem } } - fn alloc_raw(&self, value: T) -> Gc + fn alloc<'mem, T: Trace + 'heap>(&'mem self, value: T) -> Root<'mem, T> where T: Trace, { self.mem.alloc(value) } - fn alloc(&self, value: T) -> Root<'memory, T> - where - T: Trace, - { - Root::new(self.alloc_raw(value)) - } - fn gc(&self) { self.mem.gc(); } } /////////////////////// -struct Root<'guard, T: Trace> { +struct Root<'root, T: Trace> { var: Gc, - p: PhantomData<&'guard T>, + p: PhantomData<&'root T>, } -impl<'guard, T: Trace> Root<'guard, T> { - fn new(var: Gc) -> Root<'guard, T> { +impl<'root, T: Trace> Root<'root, T> { + fn new(var: Gc) -> Root<'root, T> { Root { var, p: PhantomData, @@ -324,7 +326,7 @@ impl<'guard, T: Trace> Root<'guard, T> { } } -impl<'guard, T: Trace> Deref for Root<'guard, T> { +impl<'root, T: Trace> Deref for Root<'root, T> { type Target = T; fn deref(&self) -> &Self::Target { unsafe { self.var.as_ref() } @@ -343,67 +345,34 @@ fn test_do_some_stuff(mem: &MutatorView) { println!("test_do_some_stuff"); } -fn test_do_all_stuff(mem: &MutatorView) { - let foo = mem.alloc(HeapString::from("foosball")); - - for _ in 0x0..0xF { - let _bar = mem.alloc(HeapString::from("foobar")); - } - mem.gc(); - - test_do_some_stuff(mem); - - foo.debug(); - foo.print(); +fn main() { + // let mut escapees = Vec::new(); + { + let arena = Memory::new(); - let bar = mem.alloc(HeapString::from("barbell")); - bar.debug(); + arena.enter(|mem| { + let foo = mem.alloc(HeapString::from("foosball")); - mem.gc(); -} + for _ in 0x0..0xF { + let _bar = mem.alloc(HeapString::from("foobar")); + } + mem.gc(); -fn main() { - let arena = Memory::new(); - - //arena.enter(test_do_all_stuff); - - // Regarding pinning... - // - // - mutable variables can be std::mem::replace()'d etc - // - immutable variables can not - // - // Pinning requires shadowing every root to hold it in place - // - // - the assumption is that a root might escape from its scope - // - The Gc type is most at risk of being escaped by being stored somewhere that can escape - // - An immutable Root<'lifetime, T> is not at risk - // - // Thus the fix to preventing roots escaping is to make all data structures provide a root-based API - // - // The interior mutability pattern must be strictly adhered to. Is there a way to enforce??? - // - // TODO: arena for reference counted input/output pointers - - let mut escapees = Vec::new(); - - arena.enter(|mem| { - let foo = mem.alloc(HeapString::from("foosball")); - - for _ in 0x0..0xF { - let _bar = mem.alloc(HeapString::from("foobar")); - } - mem.gc(); + test_do_some_stuff(mem); - test_do_some_stuff(mem); + foo.debug(); + foo.print(); - foo.debug(); - foo.print(); + let bar = mem.alloc(HeapString::from("barbell")); + bar.debug(); - let bar = mem.alloc(HeapString::from("barbell")); - bar.debug(); + mem.gc(); - mem.gc(); + //escapees.push(foo); + }); + } - escapees.push(foo); - }); + // for item in escapees.iter() { + // item.print(); + // } } From e695bebacbe81fda312a2d8d7d3528d97828e310 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 7 Jun 2025 10:20:36 -0400 Subject: [PATCH 18/32] Clean up repl code a bit --- interpreter/src/main.rs | 65 +------- interpreter/src/memory.rs | 14 ++ interpreter/src/repl.rs | 158 ++++++++++++-------- interpreter/src/safeptr.rs | 3 + scratchpad/notes.md | 21 ++- scratchpad/test_02_pinned_roots/src/main.rs | 25 ++-- 6 files changed, 150 insertions(+), 136 deletions(-) diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index f6c22ec..4a6f1c7 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -13,9 +13,6 @@ use std::process; use clap::{App, Arg}; -use rustyline::error::ReadlineError; -use rustyline::Editor; - mod arena; mod array; mod bytecode; @@ -45,7 +42,7 @@ mod vm; use crate::error::RuntimeError; use crate::memory::Memory; -use crate::repl::RepMaker; +use crate::repl::repl; /// Read a file into a String fn load_file(filename: &str) -> Result { @@ -63,62 +60,6 @@ fn read_file(filename: &str) -> Result { Ok(contents) } -/// Read a line at a time, printing the input back out -fn read_print_loop() -> Result<(), RuntimeError> { - // establish a repl input history file path - let history_file = match dirs::home_dir() { - Some(mut path) => { - path.push(".evalrus_history"); - Some(String::from(path.to_str().unwrap())) - } - None => None, - }; - - // () means no completion support (TODO) - // Another TODO - find a more suitable alternative to rustyline - let mut reader = Editor::<()>::new(); - - // Try to load the repl history file - if let Some(ref path) = history_file { - if let Err(err) = reader.load_history(&path) { - eprintln!("Could not read history: {}", err); - } - } - - let mem = Memory::new(); - let rep_maker = RepMaker {}; - let rep = mem.mutate(&rep_maker, ())?; - - // repl - loop { - let readline = reader.readline("> "); - - match readline { - // valid input - Ok(line) => { - reader.add_history_entry(&line); - mem.mutate(&rep, line)?; - } - - // some kind of program termination condition - Err(e) => { - if let Some(ref path) = history_file { - reader.save_history(&path).unwrap_or_else(|err| { - eprintln!("could not save input history in {}: {}", path, err); - }); - } - - // EOF is fine - if let ReadlineError::Eof = e { - return Ok(()); - } else { - return Err(RuntimeError::from(e)); - } - } - } - } -} - fn main() { // parse command line argument, an optional filename let matches = App::new("Eval-R-Us") @@ -139,7 +80,9 @@ fn main() { // TODO } else { // otherwise begin a repl - read_print_loop().unwrap_or_else(|err| { + let mem = Memory::new(); + let result = mem.enter(repl); + result.unwrap_or_else(|err| { eprintln!("Terminated: {}", err); process::exit(1); }); diff --git a/interpreter/src/memory.rs b/interpreter/src/memory.rs index 4f91e11..b14f91f 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/memory.rs @@ -138,15 +138,29 @@ impl Memory { } /// Run a mutator process + // TODO remove this function // ANCHOR: DefMemoryMutate pub fn mutate(&self, m: &M, input: M::Input) -> Result { let mut guard = MutatorView::new(self); m.run(&mut guard, input) } // ANCHOR_END: DefMemoryMutate + + /// Enter a scope within which memory can be accessed. + /// Nothing should escape from this scope. + // ANCHOR: DefMemoryEnter + pub fn enter(&self, f: F) -> Result<(), RuntimeError> + where + F: Fn(&MutatorView) -> Result<(), RuntimeError>, + { + let guard = MutatorView::new(&self); + f(&guard) + } + // ANCHOR_END: DefMemoryEnter } /// Defines the interface a heap-mutating type must use to be allowed access to the heap +// TODO remove this trait // ANCHOR: DefMutator pub trait Mutator: Sized { type Input; diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index 5f59fae..c0257e6 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -1,94 +1,126 @@ use crate::compiler::compile; use crate::error::{ErrorKind, RuntimeError}; -use crate::memory::{Mutator, MutatorView}; +use crate::memory::MutatorView; use crate::parser::parse; -use crate::safeptr::{CellPtr, TaggedScopedPtr}; +use crate::safeptr::TaggedScopedPtr; use crate::vm::{EvalStatus, Thread}; -/// A mutator that returns a Repl instance -pub struct RepMaker {} +use rustyline::error::ReadlineError; +use rustyline::Editor; -impl Mutator for RepMaker { - type Input = (); - type Output = ReadEvalPrint; - - fn run(&self, mem: &MutatorView, _input: ()) -> Result { - ReadEvalPrint::alloc(mem) +fn get_or_create_history(filename: &str) -> Option { + match dirs::home_dir() { + Some(mut path) => { + path.push(filename); + Some(String::from(path.to_str().unwrap())) + } + None => None, } } -/// Mutator that implements the VM -pub struct ReadEvalPrint { - main_thread: CellPtr, -} +fn get_reader(history_file: &Option) -> Editor<()> { + // () means no completion support (TODO) + // TODO - find a more suitable alternative to rustyline + let mut reader = Editor::<()>::new(); -impl ReadEvalPrint { - pub fn alloc(mem: &MutatorView) -> Result { - Ok(ReadEvalPrint { - main_thread: CellPtr::new_with(Thread::alloc(mem)?), - }) + // Try to load the repl history file + if let Some(ref path) = history_file { + if let Err(err) = reader.load_history(&path) { + eprintln!("Could not read history: {}", err); + } } + + reader } -impl Mutator for ReadEvalPrint { - type Input = String; - type Output = (); +fn interpret_line(mem: &MutatorView, thread: &Thread, line: String) -> Result<(), RuntimeError> { + // If the first 2 chars of the line are ":d", then the user has requested a debug + // representation + let (line, debug) = if line.starts_with(":d ") { + (&line[3..], true) + } else { + (line.as_str(), false) + }; + + match (|mem, line| -> Result { + let value = parse(mem, line)?; + + if debug { + println!( + "# Debug\n## Input:\n```\n{}\n```\n## Parsed:\n```\n{:?}\n```", + line, value + ); + } + + let function = compile(mem, value)?; - fn run(&self, mem: &MutatorView, line: String) -> Result<(), RuntimeError> { - let thread = self.main_thread.get(mem); + if debug { + println!("## Compiled:\n```\n{:?}\n```", function); + } - // If the first 2 chars of the line are ":d", then the user has requested a debug - // representation - let (line, debug) = if line.starts_with(":d ") { - (&line[3..], true) - } else { - (line.as_str(), false) + let mut status = thread.start_exec(mem, function)?; + let value = loop { + match status { + EvalStatus::Return(value) => break value, + _ => status = thread.continue_exec(mem, 1024)?, + }; }; - match (|mem, line| -> Result { - let value = parse(mem, line)?; + if debug { + println!("## Evaluated:\n```\n{:?}\n```\n", value); + } - if debug { - println!( - "# Debug\n## Input:\n```\n{}\n```\n## Parsed:\n```\n{:?}\n```", - line, value - ); + Ok(value) + })(mem, &line) + { + Ok(value) => println!("{}", value), + + Err(e) => { + match e.error_kind() { + // non-fatal repl errors + ErrorKind::LexerError(_) => e.print_with_source(&line), + ErrorKind::ParseError(_) => e.print_with_source(&line), + ErrorKind::EvalError(_) => e.print_with_source(&line), + _ => return Err(e), } + } + } - let function = compile(mem, value)?; + Ok(()) +} - if debug { - println!("## Compiled:\n```\n{:?}\n```", function); - } +pub fn repl(mem: &MutatorView) -> Result<(), RuntimeError> { + let history_file = get_or_create_history(".evalrus.history"); + let mut reader = get_reader(&history_file); - let mut status = thread.start_exec(mem, function)?; - let value = loop { - match status { - EvalStatus::Return(value) => break value, - _ => status = thread.continue_exec(mem, 1024)?, - }; - }; + let main_thread = Thread::alloc(mem)?; - if debug { - println!("## Evaluated:\n```\n{:?}\n```\n", value); - } + // repl + loop { + let readline = reader.readline("> "); - Ok(value) - })(mem, &line) - { - Ok(value) => println!("{}", value), + match readline { + // valid input + Ok(line) => { + reader.add_history_entry(&line); + interpret_line(mem, &main_thread, line)?; + } + // some kind of program termination condition Err(e) => { - match e.error_kind() { - // non-fatal repl errors - ErrorKind::LexerError(_) => e.print_with_source(&line), - ErrorKind::ParseError(_) => e.print_with_source(&line), - ErrorKind::EvalError(_) => e.print_with_source(&line), - _ => return Err(e), + if let Some(ref path) = history_file { + reader.save_history(&path).unwrap_or_else(|err| { + eprintln!("could not save input history in {}: {}", path, err); + }); + } + + // EOF is fine + if let ReadlineError::Eof = e { + return Ok(()); + } else { + return Err(RuntimeError::from(e)); } } } - - Ok(()) } } diff --git a/interpreter/src/safeptr.rs b/interpreter/src/safeptr.rs index c10c832..5b9ef4c 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -141,6 +141,7 @@ pub struct TaggedScopedPtr<'guard> { // ANCHOR_END: DefTaggedScopedPtr impl<'guard> TaggedScopedPtr<'guard> { + // XXX: this should be unsafe - no guarantees that `ptr` is a valid pointer pub fn new(guard: &'guard dyn MutatorScope, ptr: TaggedPtr) -> TaggedScopedPtr<'guard> { TaggedScopedPtr { ptr, @@ -218,6 +219,7 @@ impl TaggedCellPtr { } } + // XXX: this should be unsafe - no guarantee that the source object is valid pub fn new_ptr(source: TaggedPtr) -> TaggedCellPtr { TaggedCellPtr { inner: Cell::new(source), @@ -262,6 +264,7 @@ impl TaggedCellPtr { /// Set this pointer to another TaggedPtr // TODO DEPRECATE IF POSSIBLE // - this is only used to set non-object tagged values and should be replaced/renamed + // XXX: this should be unsafe pub fn set_to_ptr<'guard>(&self, _guard: &'guard dyn MutatorScope, ptr: TaggedPtr) { self.inner.set(ptr) } diff --git a/scratchpad/notes.md b/scratchpad/notes.md index 4610a11..6886be5 100644 --- a/scratchpad/notes.md +++ b/scratchpad/notes.md @@ -7,15 +7,21 @@ ## Rooting Conservative stack scanning. -- allows for intrusive data structures +- allows for intrusive data structures _where used_ - simpler mutator root management - still need to use Pin to keep roots from escaping + - or do we? Interior mutability means roots only have to be readonly + - which means no mem::replace etc if we have a phantom lifetime - need to push all registers to stack + - how is this safely done? bdwgc endorses use of getcontext() or setjmp +- need to find stack base + - pthread_attr_getstack Depends on: - fast map of pointer to block - vec + heap? - object map in each block + - FIRST step, implement object block ## Tracing @@ -35,6 +41,13 @@ Safety: - yes: we are not dereferencing pointers in safe rust - yes: we are using cell everywhere and no threading, so safe +## Main interface + +- Remove the unsafe `Mutator` trait +- Redefine as a main `Thread` that is provided on entry + +--- + ```rust pub trait Trace { fn trace(&self); @@ -62,6 +75,11 @@ RawPtr::trace(&self) {} Root::trace() ``` + + +## Pinning Roots + +```rust use std::cell::RefCell; use std::marker::PhantomPinned; use std::ops::Deref; @@ -216,3 +234,4 @@ fn main() { println!("{}", *foo); } } +``` diff --git a/scratchpad/test_02_pinned_roots/src/main.rs b/scratchpad/test_02_pinned_roots/src/main.rs index bfdb1f9..9b2a6d9 100644 --- a/scratchpad/test_02_pinned_roots/src/main.rs +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -265,7 +265,6 @@ impl<'heap> Memory<'heap> { fn enter<'mutator, F>(&'mutator self, mutant: F) where F: FnOnce(&MutatorView<'heap, 'mutator>), - //'heap: 'mutator, { let delegate = MutatorView::new(self); mutant(&delegate); @@ -295,10 +294,7 @@ impl<'heap, 'mutator> MutatorView<'heap, 'mutator> { MutatorView { mem } } - fn alloc<'mem, T: Trace + 'heap>(&'mem self, value: T) -> Root<'mem, T> - where - T: Trace, - { + fn alloc<'mem, T: Trace + 'heap>(&'mem self, value: T) -> Root<'mem, T> { self.mem.alloc(value) } @@ -346,29 +342,36 @@ fn test_do_some_stuff(mem: &MutatorView) { } fn main() { - // let mut escapees = Vec::new(); + // let mut escapees_outer = Vec::new(); { let arena = Memory::new(); + // let mut escapees_inner = Vec::new(); arena.enter(|mem| { + // under some circumstances, `foo` is placed earlier on the stack than `arena`. + // This causes stack scanning to miss its presence and collect it, causing + // use after free further on. + // This never happens if all the below is in a function rather than a closure. let foo = mem.alloc(HeapString::from("foosball")); for _ in 0x0..0xF { - let _bar = mem.alloc(HeapString::from("foobar")); + let bar = mem.alloc(HeapString::from("foobar")); + // bar.debug(); } mem.gc(); test_do_some_stuff(mem); - foo.debug(); - foo.print(); - let bar = mem.alloc(HeapString::from("barbell")); bar.debug(); mem.gc(); - //escapees.push(foo); + foo.debug(); + foo.print(); + + // escapees_inner.push(foo); + // escapees_outer.push(bar); }); } From 33ea21fee0dfd96acc39f04b2ff96af32024c608 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 7 Jun 2025 11:06:10 -0400 Subject: [PATCH 19/32] Remove unsafe Mutator trait --- README.md | 29 +-- interpreter/src/array.rs | 229 ++++++++-------------- interpreter/src/compiler.rs | 20 +- interpreter/src/dict.rs | 380 +++++++++++++----------------------- interpreter/src/memory.rs | 23 --- interpreter/src/pair.rs | 20 +- interpreter/src/parser.rs | 34 +--- interpreter/src/text.rs | 92 +++------ scratchpad/notes.md | 3 +- 9 files changed, 268 insertions(+), 562 deletions(-) diff --git a/README.md b/README.md index 8dca04d..1825ede 100644 --- a/README.md +++ b/README.md @@ -15,27 +15,14 @@ interpreter in Rust including: From CPython to Ruby's YARV, V8 and SpiderMonkey, GHC to the JVM, most language runtimes are written in C/C++. -We believe that Rust is eminently suitable for implementing languages and can -provide significant productivity improvements over C and C++ while retaining -the performance advantages and low level control of both. - -While there are a number of languages implemented in Rust available now, in -varying states of completeness - interpreters, AOT compilers and -JIT-compiled - our vision is singular: - -_To create a well documented reference compiler and runtime, -permissively licensed, such that you can fork and morph it into your own -programming language._ - -That is, a platform for bootstrapping other languages, written in Rust. -To that end, the implementation provided here is not intended to be feature -complete and cannot possibly represent every variation of programming -language or local optimization. - -It is a lofty goal, and it certainly won't be the right approach for -everybody. However, we hope it will help shift the landscape in favor of more -memory-safe language implementations. - +Rust is eminently suitable for implementing languages and can provide +significant productivity improvements over C and C++ while retaining the +performance advantages and low level control of both through safe and unsafe +abstractions. + +The goal of this book is to provide well documented compiler and runtime +building blocks as a reference for learning how to build your own. The code is +permissively licensed and thus may be forked and modified as desired. ## Getting involved diff --git a/interpreter/src/array.rs b/interpreter/src/array.rs index 9bcdbd1..eea175f 100644 --- a/interpreter/src/array.rs +++ b/interpreter/src/array.rs @@ -557,8 +557,8 @@ mod test { AnyContainerFromPairList, Array, Container, IndexedAnyContainer, IndexedContainer, StackAnyContainer, StackContainer, }; - use crate::error::{ErrorKind, RuntimeError}; - use crate::memory::{Memory, Mutator, MutatorView}; + use crate::error::ErrorKind; + use crate::memory::Memory; use crate::pair::Pair; use crate::safeptr::TaggedCellPtr; use crate::taggedptr::Value; @@ -566,200 +566,135 @@ mod test { #[test] fn array_generic_push_and_pop() { let mem = Memory::new(); + mem.enter(|view| { + let array: Array = Array::new(); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let array: Array = Array::new(); - - // TODO StickyImmixHeap will only allocate up to 32k at time of writing - // test some big array sizes - for i in 0..1000 { - array.push(view, i)?; - } - - for i in 0..1000 { - assert!(array.pop(view)? == 999 - i); - } + // TODO StickyImmixHeap will only allocate up to 32k at time of writing + // test some big array sizes + for i in 0..1000 { + array.push(view, i)?; + } - Ok(()) + for i in 0..1000 { + assert!(array.pop(view)? == 999 - i); } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn array_generic_indexing() { let mem = Memory::new(); + mem.enter(|view| { + let array: Array = Array::new(); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let array: Array = Array::new(); - - for i in 0..12 { - array.push(view, i)?; - } + for i in 0..12 { + array.push(view, i)?; + } - assert!(array.get(view, 0) == Ok(0)); - assert!(array.get(view, 4) == Ok(4)); + assert!(array.get(view, 0) == Ok(0)); + assert!(array.get(view, 4) == Ok(4)); - for i in 12..1000 { - match array.get(view, i) { - Ok(_) => panic!("Array index should have been out of bounds!"), - Err(e) => assert!(*e.error_kind() == ErrorKind::BoundsError), - } + for i in 12..1000 { + match array.get(view, i) { + Ok(_) => panic!("Array index should have been out of bounds!"), + Err(e) => assert!(*e.error_kind() == ErrorKind::BoundsError), } - - Ok(()) } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn arrayany_tagged_pointers() { let mem = Memory::new(); + mem.enter(|view| { + let array: Array = Array::new(); + let array = view.alloc(array)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let array: Array = Array::new(); - let array = view.alloc(array)?; - - for _ in 0..12 { - StackAnyContainer::push(&*array, view, view.nil())?; - } - - // or by copy/clone - let pair = view.alloc_tagged(Pair::new())?; + for _ in 0..12 { + StackAnyContainer::push(&*array, view, view.nil())?; + } - IndexedAnyContainer::set(&*array, view, 3, pair)?; + // or by copy/clone + let pair = view.alloc_tagged(Pair::new())?; - Ok(()) - } - } + IndexedAnyContainer::set(&*array, view, 3, pair)?; - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn array_with_capacity_and_realloc() { let mem = Memory::new(); + mem.enter(|view| { + let array: Array = Array::with_capacity(view, 256)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let array: Array = Array::with_capacity(view, 256)?; - - let ptr_before = array.data.get().as_ptr(); - - // fill to capacity - for _ in 0..256 { - StackAnyContainer::push(&array, view, view.nil())?; - } + let ptr_before = array.data.get().as_ptr(); - let ptr_after = array.data.get().as_ptr(); + // fill to capacity + for _ in 0..256 { + StackAnyContainer::push(&array, view, view.nil())?; + } - // array storage shouldn't have been reallocated - assert!(ptr_before == ptr_after); + let ptr_after = array.data.get().as_ptr(); - // overflow capacity, requiring reallocation - StackAnyContainer::push(&array, view, view.nil())?; + // array storage shouldn't have been reallocated + assert!(ptr_before == ptr_after); - let ptr_realloc = array.data.get().as_ptr(); + // overflow capacity, requiring reallocation + StackAnyContainer::push(&array, view, view.nil())?; - // array storage should have been reallocated - assert!(ptr_before != ptr_realloc); + let ptr_realloc = array.data.get().as_ptr(); - Ok(()) - } - } + // array storage should have been reallocated + assert!(ptr_before != ptr_realloc); - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn arrayany_from_pair_list() { let mem = Memory::new(); + mem.enter(|view| { + let array: Array = Array::new(); + let array = view.alloc(array)?; + + let pair = Pair::new(); + pair.first.set(view.lookup_sym("thing0")); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let array: Array = Array::new(); - let array = view.alloc(array)?; - - let pair = Pair::new(); - pair.first.set(view.lookup_sym("thing0")); - - let head = view.alloc_tagged(pair)?; - let mut tail = head; - - for n in 1..12 { - if let Value::Pair(pair) = *tail { - tail = pair.append(view, view.lookup_sym(&format!("thing{}", n)))?; - } else { - panic!("expected pair!") - } + let head = view.alloc_tagged(pair)?; + let mut tail = head; + + for n in 1..12 { + if let Value::Pair(pair) = *tail { + tail = pair.append(view, view.lookup_sym(&format!("thing{}", n)))?; + } else { + panic!("expected pair!") } + } - array.from_pair_list(view, head)?; + array.from_pair_list(view, head)?; - for n in 0..12 { - let thing = IndexedAnyContainer::get(&*array, view, n)?; + for n in 0..12 { + let thing = IndexedAnyContainer::get(&*array, view, n)?; - match *thing { - Value::Symbol(s) => assert!(s.as_str(view) == format!("thing{}", n)), - _ => panic!("expected symbol!"), - } + match *thing { + Value::Symbol(s) => assert!(s.as_str(view) == format!("thing{}", n)), + _ => panic!("expected symbol!"), } - - Ok(()) } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } } diff --git a/interpreter/src/compiler.rs b/interpreter/src/compiler.rs index e3ace5e..6fad47e 100644 --- a/interpreter/src/compiler.rs +++ b/interpreter/src/compiler.rs @@ -866,7 +866,7 @@ pub fn compile<'guard>( #[cfg(test)] mod integration { use super::*; - use crate::memory::{Memory, Mutator}; + use crate::memory::Memory; use crate::parser::parse; use crate::vm::Thread; @@ -884,23 +884,7 @@ mod integration { fn test_helper(test_fn: fn(&MutatorView) -> Result<(), RuntimeError>) { let mem = Memory::new(); - - struct Test {} - impl Mutator for Test { - type Input = fn(&MutatorView) -> Result<(), RuntimeError>; - type Output = (); - - fn run( - &self, - mem: &MutatorView, - test_fn: Self::Input, - ) -> Result { - test_fn(mem) - } - } - - let test = Test {}; - mem.mutate(&test, test_fn).unwrap(); + mem.enter(test_fn).unwrap(); } #[test] diff --git a/interpreter/src/dict.rs b/interpreter/src/dict.rs index f4f50c0..7add20a 100644 --- a/interpreter/src/dict.rs +++ b/interpreter/src/dict.rs @@ -331,148 +331,96 @@ impl Print for Dict { #[cfg(test)] mod test { use super::{Container, Dict, HashIndexedAnyContainer}; - use crate::error::{ErrorKind, RuntimeError}; - use crate::memory::{Memory, Mutator, MutatorView}; + use crate::error::ErrorKind; + use crate::memory::Memory; use crate::pair::Pair; #[test] fn dict_empty_assoc_lookup() { let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::new(); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); + let key = mem.lookup_sym("foo"); + let val = mem.lookup_sym("bar"); - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::new(); + dict.assoc(mem, key, val)?; - let key = mem.lookup_sym("foo"); - let val = mem.lookup_sym("bar"); + let lookup = dict.lookup(mem, key)?; - dict.assoc(mem, key, val)?; - - let lookup = dict.lookup(mem, key)?; - - assert!(lookup == val); + assert!(lookup == val); - Ok(()) - } - } - - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn dict_assoc_lookup() { let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 256)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 256)?; + let key = mem.lookup_sym("foo"); + let val = mem.lookup_sym("bar"); - let key = mem.lookup_sym("foo"); - let val = mem.lookup_sym("bar"); + dict.assoc(mem, key, val)?; - dict.assoc(mem, key, val)?; + let lookup = dict.lookup(mem, key)?; - let lookup = dict.lookup(mem, key)?; + assert!(lookup == val); - assert!(lookup == val); - - Ok(()) - } - } - - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn dict_lookup_fail() { let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 256)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 256)?; - - let key = mem.lookup_sym("foo"); + let key = mem.lookup_sym("foo"); - let lookup = dict.lookup(mem, key); + let lookup = dict.lookup(mem, key); - match lookup { - Ok(_) => panic!("Key should not have been found!"), - Err(e) => assert!(*e.error_kind() == ErrorKind::KeyError), - } - - Ok(()) + match lookup { + Ok(_) => panic!("Key should not have been found!"), + Err(e) => assert!(*e.error_kind() == ErrorKind::KeyError), } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn dict_dissoc_lookup() { let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 256)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); + let key = mem.lookup_sym("foo"); + let val = mem.lookup_sym("bar"); - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 256)?; + dict.assoc(mem, key, val)?; - let key = mem.lookup_sym("foo"); - let val = mem.lookup_sym("bar"); - - dict.assoc(mem, key, val)?; + let value = dict.lookup(mem, key)?; + assert!(value == val); - let value = dict.lookup(mem, key)?; - assert!(value == val); + let value = dict.dissoc(mem, key)?; + assert!(value == val); - let value = dict.dissoc(mem, key)?; - assert!(value == val); - - let result = dict.lookup(mem, key); - match result { - Ok(_) => panic!("Key should not have been found!"), - Err(e) => assert!(*e.error_kind() == ErrorKind::KeyError), - } - - Ok(()) + let result = dict.lookup(mem, key); + match result { + Ok(_) => panic!("Key should not have been found!"), + Err(e) => assert!(*e.error_kind() == ErrorKind::KeyError), } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] @@ -480,98 +428,72 @@ mod test { // this test should not require resizing the internal array, so should simply test that // find_entry() is returning a valid entry for all inserted items let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 100)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); + for num in 0..50 { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 100)?; + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - for num in 0..50 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); - - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); - - dict.assoc(mem, key, val)?; - } - - for num in 0..50 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); + dict.assoc(mem, key, val)?; + } - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); + for num in 0..50 { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); - assert!(dict.exists(mem, key)?); + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - let lookup = dict.lookup(mem, key)?; + assert!(dict.exists(mem, key)?); - assert!(lookup == val); - } + let lookup = dict.lookup(mem, key)?; - Ok(()) + assert!(lookup == val); } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn dict_assoc_lookup_500_into_capacity_20() { // this test forces several resizings and should test the final state of the dict is as expected let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 20)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 20)?; + for num in 0..500 { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); - for num in 0..500 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); - - dict.assoc(mem, key, val)?; - } - - for num in 0..500 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); + dict.assoc(mem, key, val)?; + } - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); + for num in 0..500 { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); - assert!(dict.exists(mem, key)?); + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - let lookup = dict.lookup(mem, key)?; + assert!(dict.exists(mem, key)?); - assert!(lookup == val); - } + let lookup = dict.lookup(mem, key)?; - Ok(()) + assert!(lookup == val); } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] @@ -579,104 +501,78 @@ mod test { // this test should not require resizing the internal array, so should simply test that // find_entry() is returning a valid entry for all inserted items let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 100)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); + for num in 0..50 { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 100)?; + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - for num in 0..50 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); + dict.assoc(mem, key, val)?; + } - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); + // delete every other key + for num in (0..50).step_by(2) { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); + dict.dissoc(mem, key)?; + } - dict.assoc(mem, key, val)?; - } + // add more stuff + for num in 0..20 { + let key_name = format!("ignore_{}", num); + let key = mem.lookup_sym(&key_name); - // delete every other key - for num in (0..50).step_by(2) { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); - dict.dissoc(mem, key)?; - } + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - // add more stuff - for num in 0..20 { - let key_name = format!("ignore_{}", num); - let key = mem.lookup_sym(&key_name); + dict.assoc(mem, key, val)?; + } - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); + // check that the originally inserted keys are discoverable or not as expected + for num in 0..50 { + let key_name = format!("foo_{}", num); + let key = mem.lookup_sym(&key_name); - dict.assoc(mem, key, val)?; - } + let val_name = format!("val_{}", num); + let val = mem.lookup_sym(&val_name); - // check that the originally inserted keys are discoverable or not as expected - for num in 0..50 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_name); - - let val_name = format!("val_{}", num); - let val = mem.lookup_sym(&val_name); - - if num % 2 == 0 { - assert!(!dict.exists(mem, key)?); - } else { - assert!(dict.exists(mem, key)?); - let lookup = dict.lookup(mem, key)?; - assert!(lookup == val); - } + if num % 2 == 0 { + assert!(!dict.exists(mem, key)?); + } else { + assert!(dict.exists(mem, key)?); + let lookup = dict.lookup(mem, key)?; + assert!(lookup == val); } - - Ok(()) } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn dict_unhashable() { let mem = Memory::new(); + mem.enter(|mem| { + let dict = Dict::with_capacity(mem, 256)?; - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 256)?; + // a Pair type does not implement Hashable + let key = mem.alloc_tagged(Pair::new())?; + let val = mem.lookup_sym("bar"); - // a Pair type does not implement Hashable - let key = mem.alloc_tagged(Pair::new())?; - let val = mem.lookup_sym("bar"); + let result = dict.assoc(mem, key, val); - let result = dict.assoc(mem, key, val); - - match result { - Ok(_) => panic!("Key should not have been found!"), - Err(e) => assert!(*e.error_kind() == ErrorKind::UnhashableError), - } - - Ok(()) + match result { + Ok(_) => panic!("Key should not have been found!"), + Err(e) => assert!(*e.error_kind() == ErrorKind::UnhashableError), } - } - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } } diff --git a/interpreter/src/memory.rs b/interpreter/src/memory.rs index b14f91f..e3d0957 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/memory.rs @@ -137,15 +137,6 @@ impl Memory { Memory { heap: Heap::new() } } - /// Run a mutator process - // TODO remove this function - // ANCHOR: DefMemoryMutate - pub fn mutate(&self, m: &M, input: M::Input) -> Result { - let mut guard = MutatorView::new(self); - m.run(&mut guard, input) - } - // ANCHOR_END: DefMemoryMutate - /// Enter a scope within which memory can be accessed. /// Nothing should escape from this scope. // ANCHOR: DefMemoryEnter @@ -158,17 +149,3 @@ impl Memory { } // ANCHOR_END: DefMemoryEnter } - -/// Defines the interface a heap-mutating type must use to be allowed access to the heap -// TODO remove this trait -// ANCHOR: DefMutator -pub trait Mutator: Sized { - type Input; - type Output; - - fn run(&self, mem: &MutatorView, input: Self::Input) -> Result; - - // TODO - // function to return iterator that iterates over roots -} -// ANCHOR_END: DefMutator diff --git a/interpreter/src/pair.rs b/interpreter/src/pair.rs index 4d23ba1..9b708a7 100644 --- a/interpreter/src/pair.rs +++ b/interpreter/src/pair.rs @@ -225,27 +225,11 @@ pub fn values_from_3_pairs<'guard>( mod test { use super::*; use crate::error::RuntimeError; - use crate::memory::{Memory, Mutator, MutatorView}; + use crate::memory::{Memory, MutatorView}; fn test_helper(test_fn: fn(&MutatorView) -> Result<(), RuntimeError>) { let mem = Memory::new(); - - struct Test {} - impl Mutator for Test { - type Input = fn(&MutatorView) -> Result<(), RuntimeError>; - type Output = (); - - fn run( - &self, - mem: &MutatorView, - test_fn: Self::Input, - ) -> Result { - test_fn(mem) - } - } - - let test = Test {}; - mem.mutate(&test, test_fn).unwrap(); + mem.enter(test_fn).unwrap(); } #[test] diff --git a/interpreter/src/parser.rs b/interpreter/src/parser.rs index ad332a0..279bc94 100644 --- a/interpreter/src/parser.rs +++ b/interpreter/src/parser.rs @@ -281,35 +281,19 @@ pub fn parse<'guard>( #[cfg(test)] mod test { use super::*; - use crate::memory::{Memory, Mutator, MutatorView}; + use crate::memory::Memory; use crate::printer::print; fn check(input: &str, expect: &str) { let mem = Memory::new(); - - struct Test<'a> { - input: &'a str, - expect: &'a str, - } - - impl<'a> Mutator for Test<'a> { - type Input = (); // not convenient to pass &str as Input as Output because of the lifetime - type Output = (); - - fn run(&self, mem: &MutatorView, _: Self::Input) -> Result { - let ast = parse(mem, self.input)?; - println!( - "expect: {}\ngot: {}\ndebug: {:?}", - &self.expect, &ast, *ast - ); - assert!(print(*ast) == self.expect); - - Ok(()) - } - } - - let test = Test { input, expect }; - mem.mutate(&test, ()).unwrap(); + mem.enter(|mem| { + let ast = parse(mem, input)?; + println!("expect: {}\ngot: {}\ndebug: {:?}", expect, &ast, *ast); + assert!(print(*ast) == expect); + + Ok(()) + }) + .unwrap(); } #[test] diff --git a/interpreter/src/text.rs b/interpreter/src/text.rs index ec30bb2..5d0731e 100644 --- a/interpreter/src/text.rs +++ b/interpreter/src/text.rs @@ -82,92 +82,52 @@ impl Hashable for Text { #[cfg(test)] mod test { use super::Text; - use crate::error::RuntimeError; - use crate::memory::{Memory, Mutator, MutatorView}; + use crate::memory::Memory; #[test] fn text_empty_string() { let mem = Memory::new(); + mem.enter(|view| { + let text = Text::new_empty(); + assert!(text.as_str(view) == ""); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let text = Text::new_empty(); - assert!(text.as_str(view) == ""); - - Ok(()) - } - } - - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn text_from_static_str() { let mem = Memory::new(); + mem.enter(|view| { + let expected = "こんにちは"; + let text = Text::new_from_str(view, expected)?; + let got = text.as_str(view); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let expected = "こんにちは"; - let text = Text::new_from_str(view, expected)?; - let got = text.as_str(view); - - assert!(got == expected); - - Ok(()) - } - } + assert!(got == expected); - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } #[test] fn value_from_string() { let mem = Memory::new(); + mem.enter(|view| { + let input = String::from("こんにちは"); + // the Value representation of the object is wrapped in quotes + let expected = format!("\"{}\"", input); - struct Test {} - impl Mutator for Test { - type Input = (); - type Output = (); - - fn run( - &self, - view: &MutatorView, - _input: Self::Input, - ) -> Result { - let input = String::from("こんにちは"); - // the Value representation of the object is wrapped in quotes - let expected = format!("\"{}\"", input); - - let text = Text::new_from_str(view, &input)?; - let heap_text = view.alloc_tagged(text)?; + let text = Text::new_from_str(view, &input)?; + let heap_text = view.alloc_tagged(text)?; - let got = format!("{}", heap_text.value()); + let got = format!("{}", heap_text.value()); - assert!(got == expected); - - Ok(()) - } - } + assert!(got == expected); - let test = Test {}; - mem.mutate(&test, ()).unwrap(); + Ok(()) + }) + .unwrap(); } } diff --git a/scratchpad/notes.md b/scratchpad/notes.md index 6886be5..80d593a 100644 --- a/scratchpad/notes.md +++ b/scratchpad/notes.md @@ -25,7 +25,7 @@ Depends on: ## Tracing -Precise object scanning. +Precise object scanning. OR could it be conservative? This _just_ needs: - pointer values, not any hard data on types @@ -44,7 +44,6 @@ Safety: ## Main interface - Remove the unsafe `Mutator` trait -- Redefine as a main `Thread` that is provided on entry --- From 1dce8f569ca704e05a4df219153a89fb9d0bfcbe Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 7 Jun 2025 17:02:42 -0400 Subject: [PATCH 20/32] Remove unsafe pointer conversion, mark others as unsafe --- interpreter/src/memory.rs | 6 +++--- interpreter/src/safeptr.rs | 38 ++++++++++++++++++++++++-------------- interpreter/src/vm.rs | 4 ++-- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/interpreter/src/memory.rs b/interpreter/src/memory.rs index e3d0957..c04c988 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/memory.rs @@ -29,7 +29,7 @@ impl<'memory> MutatorView<'memory> { /// Get a Symbol pointer from its name // ANCHOR: DefMutatorViewLookupSym pub fn lookup_sym(&self, name: &str) -> TaggedScopedPtr<'_> { - TaggedScopedPtr::new(self, self.heap.lookup_sym(name)) + unsafe { TaggedScopedPtr::new(self, self.heap.lookup_sym(name)) } } // ANCHOR_END: DefMutatorViewLookupSym @@ -53,7 +53,7 @@ impl<'memory> MutatorView<'memory> { FatPtr: From>, T: AllocObject, { - Ok(TaggedScopedPtr::new(self, self.heap.alloc_tagged(object)?)) + Ok(unsafe { TaggedScopedPtr::new(self, self.heap.alloc_tagged(object)?) }) } // ANCHOR_END: DefMutatorViewAllocTagged @@ -64,7 +64,7 @@ impl<'memory> MutatorView<'memory> { /// Return a nil-initialized runtime-tagged pointer pub fn nil(&self) -> TaggedScopedPtr<'_> { - TaggedScopedPtr::new(self, TaggedPtr::nil()) + TaggedScopedPtr::nil(self) } } diff --git a/interpreter/src/safeptr.rs b/interpreter/src/safeptr.rs index 5b9ef4c..4c40edb 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -49,10 +49,12 @@ impl<'guard, T: Sized> ScopedPtr<'guard, T> { FatPtr: From>, T: AllocObject, { - TaggedScopedPtr::new( - guard, - TaggedPtr::from(FatPtr::from(RawPtr::new(self.value))), - ) + unsafe { + TaggedScopedPtr::new( + guard, + TaggedPtr::from(FatPtr::from(RawPtr::new(self.value))), + ) + } } } @@ -141,14 +143,29 @@ pub struct TaggedScopedPtr<'guard> { // ANCHOR_END: DefTaggedScopedPtr impl<'guard> TaggedScopedPtr<'guard> { - // XXX: this should be unsafe - no guarantees that `ptr` is a valid pointer - pub fn new(guard: &'guard dyn MutatorScope, ptr: TaggedPtr) -> TaggedScopedPtr<'guard> { + // This is unsafe because there is no guarantees that `ptr` is a valid pointer + pub unsafe fn new(guard: &'guard dyn MutatorScope, ptr: TaggedPtr) -> TaggedScopedPtr<'guard> { TaggedScopedPtr { ptr, value: FatPtr::from(ptr).as_value(guard), } } + pub fn number(guard: &'guard dyn MutatorScope, num: isize) -> TaggedScopedPtr<'guard> { + let ptr_val = TaggedPtr::number(num); + TaggedScopedPtr { + ptr: ptr_val, + value: FatPtr::from(ptr_val).as_value(guard), + } + } + + pub fn nil(guard: &'guard dyn MutatorScope) -> TaggedScopedPtr<'guard> { + TaggedScopedPtr { + ptr: TaggedPtr::nil(), + value: FatPtr::Nil.as_value(guard), + } + } + pub fn value(&self) -> Value<'guard> { self.value } @@ -219,18 +236,11 @@ impl TaggedCellPtr { } } - // XXX: this should be unsafe - no guarantee that the source object is valid - pub fn new_ptr(source: TaggedPtr) -> TaggedCellPtr { - TaggedCellPtr { - inner: Cell::new(source), - } - } - /// Return the pointer as a `TaggedScopedPtr` type that carries a copy of the `TaggedPtr` and /// a `Value` type for both copying and access convenience // ANCHOR: DefTaggedCellPtrGet pub fn get<'guard>(&self, guard: &'guard dyn MutatorScope) -> TaggedScopedPtr<'guard> { - TaggedScopedPtr::new(guard, self.inner.get()) + unsafe { TaggedScopedPtr::new(guard, self.inner.get()) } } // ANCHOR_END: DefTaggedCellPtrGet diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index a465721..4ecdec3 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -234,7 +234,7 @@ impl Thread { // Convert the location integer to a TaggedScopedPtr for passing // into the Thread's upvalues Dict - let location_ptr = TaggedScopedPtr::new(guard, TaggedPtr::number(location as isize)); + let location_ptr = TaggedScopedPtr::number(guard, location as isize); // Lookup upvalue in upvalues dict match upvalues.lookup(guard, location_ptr) { @@ -262,7 +262,7 @@ impl Thread { let upvalues = self.upvalues.get(mem); let upvalue = Upvalue::alloc(mem, location)?; - let location_ptr = TaggedScopedPtr::new(mem, TaggedPtr::number(location as isize)); + let location_ptr = TaggedScopedPtr::number(mem, location as isize); upvalues.assoc(mem, location_ptr, upvalue.as_tagged(mem))?; Ok((location_ptr, upvalue)) From b16374fb7646840bffad7a46781d238f7ad7f1dc Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 18 Jun 2025 19:02:09 -0400 Subject: [PATCH 21/32] Remove unused functions --- interpreter/src/pointerops.rs | 2 +- scratchpad/notes.md | 3 --- stickyimmix/src/rawptr.rs | 13 ++++--------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/interpreter/src/pointerops.rs b/interpreter/src/pointerops.rs index 3ac2046..4732f1b 100644 --- a/interpreter/src/pointerops.rs +++ b/interpreter/src/pointerops.rs @@ -36,7 +36,7 @@ pub trait Tagged { impl Tagged for RawPtr { fn tag(self, tag: usize) -> NonNull { - unsafe { NonNull::new_unchecked((self.as_word() | tag) as *mut T) } + unsafe { NonNull::new_unchecked((self.addr() | tag) as *mut T) } } fn untag(from: NonNull) -> RawPtr { diff --git a/scratchpad/notes.md b/scratchpad/notes.md index 80d593a..76a8b0b 100644 --- a/scratchpad/notes.md +++ b/scratchpad/notes.md @@ -41,9 +41,6 @@ Safety: - yes: we are not dereferencing pointers in safe rust - yes: we are using cell everywhere and no threading, so safe -## Main interface - -- Remove the unsafe `Mutator` trait --- diff --git a/stickyimmix/src/rawptr.rs b/stickyimmix/src/rawptr.rs index 7ef3e4b..733aecd 100644 --- a/stickyimmix/src/rawptr.rs +++ b/stickyimmix/src/rawptr.rs @@ -21,11 +21,14 @@ impl RawPtr { pub fn as_ptr(self) -> *const T { self.ptr.as_ptr() } + /// Get the pointer value as a word-sized integer - pub fn as_word(self) -> usize { + pub fn addr(self) -> usize { self.ptr.as_ptr() as usize } + /// Get the pointer as a null-type value + // XXX: is this really needed? Any added benefit? pub fn as_untyped(self) -> NonNull<()> { self.ptr.cast() } @@ -35,14 +38,6 @@ impl RawPtr { pub unsafe fn as_ref(&self) -> &T { self.ptr.as_ref() } - - /// Get a `&mut` reference to the object. Unsafe because there are no guarantees at this level - /// about the internal pointer's validity. - /// In addition, there can be no compile-time guarantees of mutable aliasing prevention. - /// Use with caution! - pub unsafe fn as_mut_ref(&mut self) -> &mut T { - self.ptr.as_mut() - } } impl Clone for RawPtr { From 98bbedc52c50f1cdc390ec9a46aaa702a4746627 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Fri, 27 Jun 2025 23:42:09 -0400 Subject: [PATCH 22/32] Cleaning up, linting --- interpreter/src/arena.rs | 2 +- interpreter/src/memory.rs | 6 +-- interpreter/src/pointerops.rs | 6 ++- interpreter/src/safeptr.rs | 85 ++++++++++++++++++++--------------- interpreter/src/taggedptr.rs | 8 ++-- interpreter/src/vm.rs | 49 ++++++++++---------- 6 files changed, 85 insertions(+), 71 deletions(-) diff --git a/interpreter/src/arena.rs b/interpreter/src/arena.rs index f074774..7008e86 100644 --- a/interpreter/src/arena.rs +++ b/interpreter/src/arena.rs @@ -2,7 +2,7 @@ /// Currently implemented on top of stickyimmix without any gc which includes unnecessary /// overhead. use std::ptr::NonNull; - + use stickyimmix::{ AllocError, AllocHeader, AllocObject, AllocRaw, ArraySize, Mark, RawPtr, SizeClass, StickyImmixHeap, diff --git a/interpreter/src/memory.rs b/interpreter/src/memory.rs index c04c988..2138dcd 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/memory.rs @@ -6,7 +6,7 @@ use stickyimmix::{AllocObject, AllocRaw, ArraySize, RawPtr, StickyImmixHeap}; use crate::error::RuntimeError; use crate::headers::{ObjectHeader, TypeList}; -use crate::pointerops::ScopedRef; +use crate::pointerops::AsScopedRef; use crate::safeptr::{MutatorScope, ScopedPtr, TaggedScopedPtr}; use crate::symbolmap::SymbolMap; use crate::taggedptr::{FatPtr, TaggedPtr}; @@ -68,7 +68,7 @@ impl<'memory> MutatorView<'memory> { } } -impl<'memory> MutatorScope for MutatorView<'memory> {} +impl MutatorScope for MutatorView<'_> {} /// The heap implementation // ANCHOR: DefHeapStorage @@ -144,7 +144,7 @@ impl Memory { where F: Fn(&MutatorView) -> Result<(), RuntimeError>, { - let guard = MutatorView::new(&self); + let guard = MutatorView::new(self); f(&guard) } // ANCHOR_END: DefMemoryEnter diff --git a/interpreter/src/pointerops.rs b/interpreter/src/pointerops.rs index 4732f1b..46a027d 100644 --- a/interpreter/src/pointerops.rs +++ b/interpreter/src/pointerops.rs @@ -47,13 +47,15 @@ impl Tagged for RawPtr { /// For accessing a pointer target, given a lifetime // ANCHOR: DefScopedRef -pub trait ScopedRef { +pub trait AsScopedRef { fn scoped_ref<'scope>(&self, guard: &'scope dyn MutatorScope) -> &'scope T; } -impl ScopedRef for RawPtr { +impl AsScopedRef for RawPtr { fn scoped_ref<'scope>(&self, _guard: &'scope dyn MutatorScope) -> &'scope T { unsafe { &*self.as_ptr() } } } // ANCHOR_END: DefScopedRef + + diff --git a/interpreter/src/safeptr.rs b/interpreter/src/safeptr.rs index 4c40edb..e9dca20 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -5,7 +5,7 @@ use std::ops::Deref; use stickyimmix::{AllocObject, RawPtr}; use crate::headers::TypeList; -use crate::pointerops::ScopedRef; +use crate::pointerops::AsScopedRef; use crate::printer::Print; use crate::taggedptr::{FatPtr, TaggedPtr, Value}; @@ -14,23 +14,6 @@ use crate::taggedptr::{FatPtr, TaggedPtr, Value}; pub trait MutatorScope {} // ANCHOR_END: DefMutatorScope -// Copy On Write semantics? Maybe the below... -// TODO, add MutatorView methods that can return MutScopedPtr? -// -// pub trait CopyOnWrite { -// fn copy_mut<'guard>(&self, _guard: &'guard MutatorView) -> MutScopedPtr<'guard, Self>; -// } -// -// pub struct MutScopedPtr<'guard, T: Sized> { -// value: &mut 'guard T -// } -// -// impl Deref, DerefMut for MutScopedPtr -// -// impl<'guard, T: Sized> MutScopedPtr<'guard, T> { -// pub fn into_immut(self) -> ScopedPtr<'guard, T> {} -// } - /// An untagged compile-time typed pointer with scope limited by `MutatorScope` // ANCHOR: DefScopedPtr pub struct ScopedPtr<'guard, T: Sized> { @@ -59,17 +42,17 @@ impl<'guard, T: Sized> ScopedPtr<'guard, T> { } /// Anything that _has_ a scope lifetime can pass as a scope representation -impl<'scope, T: Sized> MutatorScope for ScopedPtr<'scope, T> {} +impl MutatorScope for ScopedPtr<'_, T> {} impl<'guard, T: Sized> Clone for ScopedPtr<'guard, T> { fn clone(&self) -> ScopedPtr<'guard, T> { - ScopedPtr { value: self.value } + *self } } -impl<'guard, T: Sized> Copy for ScopedPtr<'guard, T> {} +impl Copy for ScopedPtr<'_, T> {} -impl<'guard, T: Sized> Deref for ScopedPtr<'guard, T> { +impl Deref for ScopedPtr<'_, T> { type Target = T; fn deref(&self) -> &T { @@ -77,13 +60,13 @@ impl<'guard, T: Sized> Deref for ScopedPtr<'guard, T> { } } -impl<'guard, T: Sized + Print> fmt::Display for ScopedPtr<'guard, T> { +impl fmt::Display for ScopedPtr<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.print(self, f) } } -impl<'guard, T: Sized + Print> fmt::Debug for ScopedPtr<'guard, T> { +impl fmt::Debug for ScopedPtr<'_, T> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.print(self, f) } @@ -95,6 +78,38 @@ impl<'guard, T: Sized + PartialEq> PartialEq for ScopedPtr<'guard, T> { } } +pub trait AsScopedPtr { + fn scoped_ptr<'scope>(&self, guard: &'scope dyn MutatorScope) -> ScopedPtr<'scope, T>; +} + + +/// A wrapper around untagged raw pointers for storing compile-time typed pointers in +/// data structures that are not expected to change in pointer value, i.e. once the +/// object is initialized, it remains immutably pointing at that object. +pub struct RefPtr { + inner: RawPtr, +} + +impl RefPtr { + pub fn new_with(source: ScopedPtr) -> RefPtr { + RefPtr { + inner: RawPtr::new(source.value), + } + } +} + +impl AsScopedRef for RefPtr { + fn scoped_ref<'scope>(&self, guard: &'scope dyn MutatorScope) -> &'scope T { + self.inner.scoped_ref(guard) + } +} + +impl AsScopedPtr for RefPtr { + fn scoped_ptr<'scope>(&self, guard: &'scope dyn MutatorScope) -> ScopedPtr<'scope, T> { + ScopedPtr::new(guard, self.inner.scoped_ref(guard)) + } +} + /// A wrapper around untagged raw pointers for storing compile-time typed pointers in data /// structures with interior mutability, allowing pointers to be updated to point at different /// target objects. @@ -169,15 +184,11 @@ impl<'guard> TaggedScopedPtr<'guard> { pub fn value(&self) -> Value<'guard> { self.value } - - pub fn get_ptr(&self) -> TaggedPtr { - self.ptr - } } /// Anything that _has_ a scope lifetime can pass as a scope representation. `Value` also implements /// `MutatorScope` so this is largely for consistency. -impl<'scope> MutatorScope for TaggedScopedPtr<'scope> {} +impl MutatorScope for TaggedScopedPtr<'_> {} impl<'guard> Deref for TaggedScopedPtr<'guard> { type Target = Value<'guard>; @@ -187,13 +198,13 @@ impl<'guard> Deref for TaggedScopedPtr<'guard> { } } -impl<'guard> fmt::Display for TaggedScopedPtr<'guard> { +impl fmt::Display for TaggedScopedPtr<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.fmt(f) } } -impl<'guard> fmt::Debug for TaggedScopedPtr<'guard> { +impl fmt::Debug for TaggedScopedPtr<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.value.fmt(f) } @@ -225,7 +236,7 @@ impl TaggedCellPtr { /// Construct a new TaggedCellPtr from a TaggedScopedPtr pub fn new_with(source: TaggedScopedPtr) -> TaggedCellPtr { TaggedCellPtr { - inner: Cell::new(TaggedPtr::from(source.ptr)), + inner: Cell::new(source.ptr), } } @@ -248,16 +259,16 @@ impl TaggedCellPtr { /// The explicit 'guard lifetime bound to MutatorScope is omitted here since the TaggedScopedPtr /// carries this lifetime already so we can assume that this operation is safe pub fn set(&self, source: TaggedScopedPtr) { - self.inner.set(TaggedPtr::from(source.ptr)) + self.inner.set(source.ptr) } /// Take the pointer of another `TaggedCellPtr` and set this instance to point at that object too - pub fn copy_from<'guard>(&self, _guard: &'guard dyn MutatorScope, src: &TaggedCellPtr) { + pub fn copy_from(&self, _guard: &'_ dyn MutatorScope, src: &TaggedCellPtr) { self.inner.set(src.inner.get()); } /// Set another instance to hold the same pointer as this instance - pub fn copy_into<'guard>(&self, _guard: &'guard dyn MutatorScope, dest: &TaggedCellPtr) { + pub fn copy_into(&self, _guard: &'_ dyn MutatorScope, dest: &TaggedCellPtr) { dest.inner.set(self.inner.get()); } @@ -275,13 +286,13 @@ impl TaggedCellPtr { // TODO DEPRECATE IF POSSIBLE // - this is only used to set non-object tagged values and should be replaced/renamed // XXX: this should be unsafe - pub fn set_to_ptr<'guard>(&self, _guard: &'guard dyn MutatorScope, ptr: TaggedPtr) { + pub fn set_to_ptr(&self, _guard: &'_ dyn MutatorScope, ptr: TaggedPtr) { self.inner.set(ptr) } /// Return the raw TaggedPtr from within // TODO DEPRECATE IF POSSIBLE - pub fn get_ptr<'guard>(&self, _guard: &'guard dyn MutatorScope) -> TaggedPtr { + pub fn get_ptr(&self, _guard: &'_ dyn MutatorScope) -> TaggedPtr { self.inner.get() } } diff --git a/interpreter/src/taggedptr.rs b/interpreter/src/taggedptr.rs index c62d19e..f6dd8fb 100644 --- a/interpreter/src/taggedptr.rs +++ b/interpreter/src/taggedptr.rs @@ -23,7 +23,7 @@ use crate::list::List; use crate::memory::HeapStorage; use crate::number::NumberObject; use crate::pair::Pair; -use crate::pointerops::{get_tag, ScopedRef, Tagged, TAG_NUMBER, TAG_OBJECT, TAG_PAIR, TAG_SYMBOL}; +use crate::pointerops::{get_tag, AsScopedRef, Tagged, TAG_NUMBER, TAG_OBJECT, TAG_PAIR, TAG_SYMBOL}; use crate::printer::Print; use crate::safeptr::{MutatorScope, ScopedPtr}; use crate::symbol::Symbol; @@ -54,7 +54,7 @@ pub enum Value<'guard> { // ANCHOR_END: DefValue /// `Value` can have a safe `Display` implementation -impl<'guard> fmt::Display for Value<'guard> { +impl fmt::Display for Value<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Value::Nil => write!(f, "nil"), @@ -75,7 +75,7 @@ impl<'guard> fmt::Display for Value<'guard> { } } -impl<'guard> fmt::Debug for Value<'guard> { +impl fmt::Debug for Value<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Value::ArrayU8(a) => a.debug(self, f), @@ -96,7 +96,7 @@ impl<'guard> fmt::Debug for Value<'guard> { } } -impl<'guard> MutatorScope for Value<'guard> {} +impl MutatorScope for Value<'_> {} /// An unpacked tagged Fat Pointer that carries the type information in the enum structure. /// This should represent every type native to the runtime. diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 4ecdec3..8dfd70d 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -12,7 +12,8 @@ use crate::function::{Function, Partial}; use crate::list::List; use crate::memory::MutatorView; use crate::pair::Pair; -use crate::safeptr::{CellPtr, MutatorScope, ScopedPtr, TaggedCellPtr, TaggedScopedPtr}; +use crate::pointerops::AsScopedRef; +use crate::safeptr::{AsScopedPtr, CellPtr, MutatorScope, RefPtr, ScopedPtr, TaggedCellPtr, TaggedScopedPtr}; use crate::taggedptr::{TaggedPtr, Value}; pub const RETURN_REG: usize = 0; @@ -40,7 +41,7 @@ pub struct CallFrame { base: ArraySize, } // ANCHOR_END: DefCallFrame - + impl CallFrame { /// Instantiate an outer-level call frame at the beginning of the stack pub fn new_main<'guard>(main_fn: ScopedPtr<'guard, Function>) -> CallFrame { @@ -176,18 +177,18 @@ fn env_upvalue_lookup<'guard>( // ANCHOR: DefThread pub struct Thread { /// An array of CallFrames - frames: CellPtr, + frames: RefPtr, /// An array of pointers any object type - stack: CellPtr, + stack: RefPtr, /// The current stack base pointer stack_base: Cell, /// A dict that should only contain Number keys and Upvalue values. This is a mapping of /// absolute stack indeces to Upvalue objects where stack values are closed over. - upvalues: CellPtr, + upvalues: RefPtr, /// A dict that should only contain Symbol keys but any type as values - globals: CellPtr, + globals: RefPtr, /// The current instruction location - instr: CellPtr, + instr: RefPtr, } // ANCHOR_END: DefThread @@ -215,12 +216,12 @@ impl Thread { let instr = InstructionStream::alloc(mem, blank_code)?; mem.alloc(Thread { - frames: CellPtr::new_with(frames), - stack: CellPtr::new_with(stack), + frames: RefPtr::new_with(frames), + stack: RefPtr::new_with(stack), stack_base: Cell::new(0), - upvalues: CellPtr::new_with(upvalues), - globals: CellPtr::new_with(globals), - instr: CellPtr::new_with(instr), + upvalues: RefPtr::new_with(upvalues), + globals: RefPtr::new_with(globals), + instr: RefPtr::new_with(instr), }) } @@ -230,7 +231,7 @@ impl Thread { guard: &'guard dyn MutatorScope, location: ArraySize, ) -> Result<(TaggedScopedPtr<'guard>, ScopedPtr<'guard, Upvalue>), RuntimeError> { - let upvalues = self.upvalues.get(guard); + let upvalues = self.upvalues.scoped_ptr(guard); // Convert the location integer to a TaggedScopedPtr for passing // into the Thread's upvalues Dict @@ -259,7 +260,7 @@ impl Thread { match self.upvalue_lookup(mem, location) { Ok(v) => Ok(v), Err(_) => { - let upvalues = self.upvalues.get(mem); + let upvalues = self.upvalues.scoped_ptr(mem); let upvalue = Upvalue::alloc(mem, location)?; let location_ptr = TaggedScopedPtr::number(mem, location as isize); @@ -278,10 +279,10 @@ impl Thread { ) -> Result, RuntimeError> { // TODO not all these locals are required in every opcode - optimize and get them only // where needed - let frames = self.frames.get(mem); - let stack = self.stack.get(mem); - let globals = self.globals.get(mem); - let instr = self.instr.get(mem); + let frames = self.frames.scoped_ptr(mem); + let stack = self.stack.scoped_ptr(mem); + let globals = self.globals.scoped_ptr(mem); + let instr = self.instr.scoped_ptr(mem); // Establish a 256-register window into the stack from the stack base. let status = stack.access_slice(mem, |full_stack| { @@ -710,7 +711,7 @@ impl Thread { let (location_ptr, upvalue) = self.upvalue_lookup(mem, location)?; // close it and unanchor from the Thread upvalue.close(mem, stack)?; - self.upvalues.get(mem).dissoc(mem, location_ptr)?; + self.upvalues.scoped_ref(mem).dissoc(mem, location_ptr)?; } } } @@ -736,11 +737,11 @@ impl Thread { ) -> Result, RuntimeError> { let mut status = EvalStatus::Pending; - let frames = self.frames.get(mem); + let frames = self.frames.scoped_ref(mem); frames.push(mem, CallFrame::new_main(function))?; let code = function.code(mem); - let instr = self.instr.get(mem); + let instr = self.instr.scoped_ref(mem); instr.switch_frame(code, 0); while status == EvalStatus::Pending { @@ -762,11 +763,11 @@ impl Thread { mem: &'guard MutatorView, function: ScopedPtr<'guard, Function>, ) -> Result, RuntimeError> { - let frames = self.frames.get(mem); + let frames = self.frames.scoped_ref(mem); frames.push(mem, CallFrame::new_main(function))?; let code = function.code(mem); - let instr = self.instr.get(mem); + let instr = self.instr.scoped_ref(mem); instr.switch_frame(code, 0); self.continue_exec(mem, 1024) @@ -789,7 +790,7 @@ impl Thread { // Evaluation hit an error Err(rt_error) => { // unwind the stack, printing a trace - let frames = self.frames.get(mem); + let frames = self.frames.scoped_ref(mem); // Print a stack trace if the error is multiple call frames deep frames.access_slice(mem, |window| { From 44f8de9075417aad5a5737f39043806348d8705b Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 28 Jun 2025 22:41:06 -0400 Subject: [PATCH 23/32] Cleaning up, linting --- interpreter/src/array.rs | 58 +++++++++++++++++------------------ interpreter/src/bytecode.rs | 22 ++++++------- interpreter/src/compiler.rs | 28 ++++++++--------- interpreter/src/containers.rs | 30 +++++++++--------- interpreter/src/dict.rs | 20 ++++++------ interpreter/src/error.rs | 8 ++--- interpreter/src/function.rs | 25 ++++++--------- interpreter/src/hashable.rs | 2 +- interpreter/src/headers.rs | 2 +- interpreter/src/lexer.rs | 14 ++++----- interpreter/src/main.rs | 2 +- interpreter/src/number.rs | 4 +-- interpreter/src/pair.rs | 10 +++--- interpreter/src/parser.rs | 8 ++--- interpreter/src/rawarray.rs | 13 +++----- interpreter/src/repl.rs | 8 ++--- interpreter/src/symbol.rs | 6 ++-- 17 files changed, 125 insertions(+), 135 deletions(-) diff --git a/interpreter/src/array.rs b/interpreter/src/array.rs index eea175f..68d8047 100644 --- a/interpreter/src/array.rs +++ b/interpreter/src/array.rs @@ -101,9 +101,9 @@ impl Array { /// Bounds-checked write // ANCHOR: DefArrayWrite - fn write<'guard>( + fn write( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, index: ArraySize, item: T, ) -> Result<&T, RuntimeError> { @@ -117,9 +117,9 @@ impl Array { /// Bounds-checked read // ANCHOR: DefArrayRead - fn read<'guard>( + fn read( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, index: ArraySize, ) -> Result { unsafe { @@ -131,9 +131,9 @@ impl Array { /// Bounds-checked reference-read // ANCHOR: DefArrayReadRef - pub fn read_ref<'guard>( + pub fn read_ref( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, index: ArraySize, ) -> Result<&T, RuntimeError> { unsafe { @@ -147,7 +147,7 @@ impl Array { /// duration because while a slice is held, other code can cause array internals to change /// that might cause the slice pointer and length to become invalid. Interior mutability /// patterns such as RefCell-style should be used in addition. - pub unsafe fn as_slice<'guard>(&self, _guard: &'guard dyn MutatorScope) -> &mut [T] { + pub unsafe fn as_slice(&self, _guard: &'_ dyn MutatorScope) -> &mut [T] { if let Some(ptr) = self.data.get().as_ptr() { from_raw_parts_mut(ptr as *mut T, self.length.get() as usize) } else { @@ -160,7 +160,7 @@ impl Array { /// duration because while a slice is held, other code can cause array internals to change /// that might cause the slice pointer and length to become invalid. Interior mutability /// patterns such as RefCell-style should be used in addition. - pub unsafe fn as_capacity_slice<'guard>(&self, _guard: &'guard dyn MutatorScope) -> &mut [T] { + pub unsafe fn as_capacity_slice(&self, _guard: &'_ dyn MutatorScope) -> &mut [T] { if let Some(ptr) = self.data.get().as_ptr() { from_raw_parts_mut(ptr as *mut T, self.data.get().capacity() as usize) } else { @@ -178,8 +178,8 @@ impl Container for Array { } } - fn with_capacity<'guard>( - mem: &'guard MutatorView, + fn with_capacity( + mem: &'_ MutatorView, capacity: ArraySize, ) -> Result, RuntimeError> { Ok(Array { @@ -189,7 +189,7 @@ impl Container for Array { }) } - fn clear<'guard>(&self, _guard: &'guard MutatorView) -> Result<(), RuntimeError> { + fn clear(&self, _guard: &'_ MutatorView) -> Result<(), RuntimeError> { if self.borrow.get() != INTERIOR_ONLY { Err(RuntimeError::new(ErrorKind::MutableBorrowError)) } else { @@ -207,9 +207,9 @@ impl FillContainer for Array { /// Increase the size of the array to `size` and fill the new slots with /// copies of `item`. If `size` is less than the current length of the array, /// does nothing. - fn fill<'guard>( + fn fill( &self, - mem: &'guard MutatorView, + mem: &'_ MutatorView, size: ArraySize, item: T, ) -> Result<(), RuntimeError> { @@ -250,7 +250,7 @@ impl FillContainer for Array { impl StackContainer for Array { /// Push can trigger an underlying array resize, hence it requires the ability to allocate // ANCHOR: DefStackContainerArrayPush - fn push<'guard>(&self, mem: &'guard MutatorView, item: T) -> Result<(), RuntimeError> { + fn push(&self, mem: &'_ MutatorView, item: T) -> Result<(), RuntimeError> { if self.borrow.get() != INTERIOR_ONLY { return Err(RuntimeError::new(ErrorKind::MutableBorrowError)); } @@ -278,7 +278,7 @@ impl StackContainer for Array { /// Pop returns None if the container is empty, otherwise moves the last item of the array /// out to the caller. - fn pop<'guard>(&self, guard: &'guard dyn MutatorScope) -> Result { + fn pop(&self, guard: &'_ dyn MutatorScope) -> Result { if self.borrow.get() != INTERIOR_ONLY { return Err(RuntimeError::new(ErrorKind::MutableBorrowError)); } @@ -296,7 +296,7 @@ impl StackContainer for Array { } /// Return the value at the top of the stack without removing it - fn top<'guard>(&self, guard: &'guard dyn MutatorScope) -> Result { + fn top(&self, guard: &'_ dyn MutatorScope) -> Result { let length = self.length.get(); if length == 0 { @@ -311,18 +311,18 @@ impl StackContainer for Array { impl IndexedContainer for Array { /// Return a copy of the object at the given index. Bounds-checked. - fn get<'guard>( + fn get( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, index: ArraySize, ) -> Result { self.read(guard, index) } /// Move an object into the array at the given index. Bounds-checked. - fn set<'guard>( + fn set( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, index: ArraySize, item: T, ) -> Result<(), RuntimeError> { @@ -332,7 +332,7 @@ impl IndexedContainer for Array { } impl SliceableContainer for Array { - fn access_slice<'guard, F, R>(&self, guard: &'guard dyn MutatorScope, f: F) -> R + fn access_slice(&self, guard: &'_ dyn MutatorScope, f: F) -> R where F: FnOnce(&mut [T]) -> R, { @@ -348,9 +348,9 @@ impl SliceableContainer for Array { pub type ArrayU8 = Array; impl Print for ArrayU8 { - fn print<'guard>( + fn print( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { write!(f, "ArrayU8[...]") @@ -361,9 +361,9 @@ impl Print for ArrayU8 { pub type ArrayU16 = Array; impl Print for ArrayU16 { - fn print<'guard>( + fn print( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { write!(f, "ArrayU16[...]") @@ -374,9 +374,9 @@ impl Print for ArrayU16 { pub type ArrayU32 = Array; impl Print for ArrayU32 { - fn print<'guard>( + fn print( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { write!(f, "ArrayU32[...]") @@ -529,9 +529,9 @@ impl AnyContainerFromSlice for Array { } impl Print for Array { - fn print<'guard>( + fn print( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { write!(f, "[")?; diff --git a/interpreter/src/bytecode.rs b/interpreter/src/bytecode.rs index cf96bfa..c690bd3 100644 --- a/interpreter/src/bytecode.rs +++ b/interpreter/src/bytecode.rs @@ -185,14 +185,14 @@ impl ByteCode { } /// Append an instuction to the back of the sequence - pub fn push<'guard>(&self, mem: &'guard MutatorView, op: Opcode) -> Result<(), RuntimeError> { + pub fn push(&self, mem: &'_ MutatorView, op: Opcode) -> Result<(), RuntimeError> { self.code.push(mem, op) } /// Set the jump offset of an existing jump instruction to a new value - pub fn update_jump_offset<'guard>( + pub fn update_jump_offset( &self, - mem: &'guard MutatorView, + mem: &'_ MutatorView, instruction: ArraySize, offset: JumpOffset, ) -> Result<(), RuntimeError> { @@ -212,9 +212,9 @@ impl ByteCode { } /// Append a literal-load operation to the back of the sequence - pub fn push_loadlit<'guard>( + pub fn push_loadlit( &self, - mem: &'guard MutatorView, + mem: &'_ MutatorView, dest: Register, literal_id: LiteralId, ) -> Result<(), RuntimeError> { @@ -246,9 +246,9 @@ impl ByteCode { } impl Print for ByteCode { - fn print<'guard>( + fn print( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { let mut instr_str = String::new(); @@ -293,9 +293,9 @@ impl InstructionStream { /// Retrieve the next instruction and return it, incrementing the instruction pointer // TODO: https://github.com/rust-hosted-langs/book/issues/39 // ANCHOR: DefInstructionStreamGetNextOpcode - pub fn get_next_opcode<'guard>( + pub fn get_next_opcode( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, ) -> Result { let instr = self .instructions @@ -308,9 +308,9 @@ impl InstructionStream { // ANCHOR_END: DefInstructionStreamGetNextOpcode /// Given an index into the literals list, return the pointer in the list at that index. - pub fn get_literal<'guard>( + pub fn get_literal( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, lit_id: LiteralId, ) -> Result { Ok(IndexedContainer::get( diff --git a/interpreter/src/compiler.rs b/interpreter/src/compiler.rs index 6fad47e..1126706 100644 --- a/interpreter/src/compiler.rs +++ b/interpreter/src/compiler.rs @@ -69,9 +69,9 @@ impl Scope { } /// Add a Symbol->Register binding to this scope - fn push_binding<'guard>( + fn push_binding( &mut self, - name: TaggedScopedPtr<'guard>, + name: TaggedScopedPtr<'_>, reg: Register, ) -> Result<(), RuntimeError> { let name_string = match *name { @@ -86,9 +86,9 @@ impl Scope { /// Push a block of bindings into this scope, returning the next register available /// after these bound registers. All these variables will be Unclosed by default. - fn push_bindings<'guard>( + fn push_bindings( &mut self, - names: &[TaggedScopedPtr<'guard>], + names: &[TaggedScopedPtr<'_>], start_reg: Register, ) -> Result { let mut reg = start_reg; @@ -100,7 +100,7 @@ impl Scope { } /// Find a Symbol->Register binding in this scope - fn lookup_binding<'guard>(&self, name: &str) -> Option<&Variable> { + fn lookup_binding(&self, name: &str) -> Option<&Variable> { self.bindings.get(name) } } @@ -154,9 +154,9 @@ impl<'parent> Variables<'parent> { } /// Search for a binding, following parent scopes. - fn lookup_binding<'guard>( + fn lookup_binding( &self, - name: TaggedScopedPtr<'guard>, + name: TaggedScopedPtr<'_>, ) -> Result, RuntimeError> { // return value should be (count-of-parent-functions-followed, Variable) let name_string = match *name { @@ -183,7 +183,7 @@ impl<'parent> Variables<'parent> { // Create a new upvalue reference if one does not exist. let mut nonlocals = self.nonlocals.borrow_mut(); - if let None = nonlocals.get(&name_string) { + if nonlocals.get(&name_string).is_none() { // Create a new non-local descriptor and add it let nonlocal = Nonlocal::new( self.acquire_upvalue_id(), @@ -246,7 +246,7 @@ impl<'parent> Variables<'parent> { } /// Pop the last scoped variables and create close-upvalue instructions for any closed over - fn pop_scope<'guard>(&mut self) -> Vec { + fn pop_scope(&mut self) -> Vec { let mut closings = Vec::new(); if let Some(scope) = self.scopes.pop() { @@ -334,7 +334,7 @@ impl<'parent> Compiler<'parent> { self.vars.scopes.push(param_scope); // validate expression list - if exprs.len() == 0 { + if exprs.is_empty() { return Err(err_eval("A function must have at least one expression")); } @@ -356,13 +356,13 @@ impl<'parent> Compiler<'parent> { let fn_nonlocals = self.vars.get_nonlocals(mem)?; - Ok(Function::alloc( + Function::alloc( mem, fn_name, fn_params, fn_bytecode, fn_nonlocals, - )?) + ) } // ANCHOR_END: DefCompilerCompileFunction @@ -753,7 +753,7 @@ impl<'parent> Compiler<'parent> { } /// Push an instruction to the function bytecode list - fn push<'guard>(&mut self, mem: &'guard MutatorView, op: Opcode) -> Result<(), RuntimeError> { + fn push(&mut self, mem: &'_ MutatorView, op: Opcode) -> Result<(), RuntimeError> { self.bytecode.get(mem).push(mem, op) } @@ -990,7 +990,7 @@ mod integration { let result = vec_from_pairs(mem, result)?; let sym_nil = mem.nil(); let sym_true = mem.lookup_sym("true"); - assert!(result == &[sym_nil, sym_true, sym_nil, sym_nil, sym_true]); + assert!(result == [sym_nil, sym_true, sym_nil, sym_nil, sym_true]); Ok(()) } diff --git a/interpreter/src/containers.rs b/interpreter/src/containers.rs index f8be3e1..46b9288 100644 --- a/interpreter/src/containers.rs +++ b/interpreter/src/containers.rs @@ -20,13 +20,13 @@ pub trait Container: Sized { fn new() -> Self; /// Create a new container instance with the given capacity. // TODO: this may not make sense for tree types - fn with_capacity<'guard>( - mem: &'guard MutatorView, + fn with_capacity( + mem: &'_ MutatorView, capacity: ArraySize, ) -> Result; /// Reset the size of the container to zero - empty - fn clear<'guard>(&self, mem: &'guard MutatorView) -> Result<(), RuntimeError>; + fn clear(&self, mem: &'_ MutatorView) -> Result<(), RuntimeError>; /// Count of items in the container fn length(&self) -> ArraySize; @@ -35,9 +35,9 @@ pub trait Container: Sized { /// If implemented, the container can be filled with a set number of values in one operation pub trait FillContainer: Container { /// The `item` is an object to copy into each container memory slot. - fn fill<'guard>( + fn fill( &self, - mem: &'guard MutatorView, + mem: &'_ MutatorView, size: ArraySize, item: T, ) -> Result<(), RuntimeError>; @@ -58,14 +58,14 @@ pub trait FillAnyContainer: FillContainer { // ANCHOR: DefStackContainer pub trait StackContainer: Container { /// Push can trigger an underlying array resize, hence it requires the ability to allocate - fn push<'guard>(&self, mem: &'guard MutatorView, item: T) -> Result<(), RuntimeError>; + fn push(&self, mem: &'_ MutatorView, item: T) -> Result<(), RuntimeError>; /// Pop returns a bounds error if the container is empty, otherwise moves the last item of the /// array out to the caller. - fn pop<'guard>(&self, _guard: &'guard dyn MutatorScope) -> Result; + fn pop(&self, _guard: &'_ dyn MutatorScope) -> Result; /// Return the value at the top of the stack without removing it - fn top<'guard>(&self, _guard: &'guard dyn MutatorScope) -> Result; + fn top(&self, _guard: &'_ dyn MutatorScope) -> Result; } // ANCHOR_END: DefStackContainer @@ -97,16 +97,16 @@ pub trait StackAnyContainer: StackContainer { /// Generic indexed-access trait. If implemented, the container can function as an indexable vector pub trait IndexedContainer: Container { /// Return a copy of the object at the given index. Bounds-checked. - fn get<'guard>( + fn get( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, index: ArraySize, ) -> Result; /// Move an object into the array at the given index. Bounds-checked. - fn set<'guard>( + fn set( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, index: ArraySize, item: T, ) -> Result<(), RuntimeError>; @@ -126,7 +126,7 @@ pub trait SliceableContainer: IndexedContainer { /// the implementing container must maintain a RefCell-style flag to catch runtime /// container modifications that would render the slice invalid or cause undefined /// behavior. - fn access_slice<'guard, F, R>(&self, _guard: &'guard dyn MutatorScope, f: F) -> R + fn access_slice(&self, _guard: &'_ dyn MutatorScope, f: F) -> R where F: FnOnce(&mut [T]) -> R; } @@ -176,9 +176,9 @@ pub trait HashIndexedAnyContainer { ) -> Result, RuntimeError>; /// Returns true if the key exists in the container. - fn exists<'guard>( + fn exists( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, key: TaggedScopedPtr, ) -> Result; } diff --git a/interpreter/src/dict.rs b/interpreter/src/dict.rs index 7add20a..fb2d50e 100644 --- a/interpreter/src/dict.rs +++ b/interpreter/src/dict.rs @@ -104,8 +104,8 @@ fn find_entry<'guard>( // ANCHOR_END: DefFindEntry /// Reset all slots to a blank entry -fn fill_with_blank_entries<'guard>( - _guard: &'guard dyn MutatorScope, +fn fill_with_blank_entries( + _guard: &'_ dyn MutatorScope, data: &RawArray, ) -> Result<(), RuntimeError> { let ptr = data @@ -158,7 +158,7 @@ impl Dict { } /// Scale capacity up if needed - fn grow_capacity<'guard>(&self, mem: &'guard MutatorView) -> Result<(), RuntimeError> { + fn grow_capacity(&self, mem: &'_ MutatorView) -> Result<(), RuntimeError> { let data = self.data.get(); let new_capacity = default_array_growth(data.capacity())?; @@ -190,8 +190,8 @@ impl Container for Dict { } } - fn with_capacity<'guard>( - mem: &'guard MutatorView, + fn with_capacity( + mem: &'_ MutatorView, capacity: ArraySize, ) -> Result { let dict = Dict { @@ -206,7 +206,7 @@ impl Container for Dict { Ok(dict) } - fn clear<'guard>(&self, mem: &'guard MutatorView) -> Result<(), RuntimeError> { + fn clear(&self, mem: &'_ MutatorView) -> Result<(), RuntimeError> { let data = self.data.get(); fill_with_blank_entries(mem, &data)?; self.length.set(0); @@ -305,9 +305,9 @@ impl HashIndexedAnyContainer for Dict { } // ANCHOR_END: DefHashIndexedAnyContainerForDictDissoc - fn exists<'guard>( + fn exists( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, key: TaggedScopedPtr, ) -> Result { let hash = hash_key(guard, key)?; @@ -318,9 +318,9 @@ impl HashIndexedAnyContainer for Dict { } impl Print for Dict { - fn print<'guard>( + fn print( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { // TODO diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index a30e64c..2d73f94 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -46,14 +46,14 @@ pub struct RuntimeError { impl RuntimeError { pub fn new(kind: ErrorKind) -> RuntimeError { RuntimeError { - kind: kind, + kind, pos: None, } } pub fn with_pos(kind: ErrorKind, pos: SourcePos) -> RuntimeError { RuntimeError { - kind: kind, + kind, pos: Some(pos), } } @@ -69,9 +69,9 @@ impl RuntimeError { /// Given the relevant source code string, show the error in context pub fn print_with_source(&self, source: &str) { if let Some(ref pos) = self.pos { - let mut iter = source.lines().enumerate(); + let iter = source.lines().enumerate(); - while let Some((count, line)) = iter.next() { + for (count, line) in iter { // count starts at 0, line numbers start at 1 if count + 1 == pos.line as usize { println!("error: {}", self); diff --git a/interpreter/src/function.rs b/interpreter/src/function.rs index 99f5b49..3b190e2 100644 --- a/interpreter/src/function.rs +++ b/interpreter/src/function.rs @@ -84,7 +84,7 @@ impl Function { } /// Return true if the function is a closure - it has nonlocal variable references - pub fn is_closure<'guard>(&self) -> bool { + pub fn is_closure(&self) -> bool { !self.nonlocal_refs.is_nil() } @@ -104,9 +104,9 @@ impl Function { impl Print for Function { /// Prints a string representation of the function - fn print<'guard>( + fn print( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { let name = self.name.get(guard); @@ -124,9 +124,9 @@ impl Print for Function { } /// Prints the disassembled bytecode - fn debug<'guard>( + fn debug( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { self.print(guard, f)?; @@ -236,9 +236,9 @@ impl Partial { impl Print for Partial { /// Prints a string representation of the Partial object - fn print<'guard>( + fn print( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { let function = self.func.get(guard); @@ -258,9 +258,9 @@ impl Print for Partial { } /// Prints the associated function's disassembled bytecode - fn debug<'guard>( + fn debug( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { self.print(guard, f)?; @@ -269,10 +269,3 @@ impl Print for Partial { } } -/// A list of arguments to apply to functions -pub struct CurriedArguments { - // TODO - // not sure of the mechanics of this. - // The ghc runtime would push all these to the stack and then consume the stack with - // function continuations -} diff --git a/interpreter/src/hashable.rs b/interpreter/src/hashable.rs index dad650c..bc05437 100644 --- a/interpreter/src/hashable.rs +++ b/interpreter/src/hashable.rs @@ -6,6 +6,6 @@ use crate::safeptr::MutatorScope; // ANCHOR: DefHashable /// Similar to Hash but for use in a mutator lifetime-limited scope pub trait Hashable { - fn hash<'guard, H: Hasher>(&self, _guard: &'guard dyn MutatorScope, hasher: &mut H); + fn hash(&self, _guard: &'_ dyn MutatorScope, hasher: &mut H); } // ANCHOR_END: DefHashable diff --git a/interpreter/src/headers.rs b/interpreter/src/headers.rs index 10a418d..9691759 100644 --- a/interpreter/src/headers.rs +++ b/interpreter/src/headers.rs @@ -115,7 +115,7 @@ impl AllocHeader for ObjectHeader { mark, size_class, type_id: TypeList::ArrayBackingBytes, - size_bytes: size as u32, + size_bytes: size, } } diff --git a/interpreter/src/lexer.rs b/interpreter/src/lexer.rs index d6ccadd..4b79f27 100644 --- a/interpreter/src/lexer.rs +++ b/interpreter/src/lexer.rs @@ -178,9 +178,9 @@ mod test { #[test] fn lexer_empty_string() { if let Ok(tokens) = tokenize("") { - assert!(tokens.len() == 0); + assert!(tokens.is_empty()); } else { - assert!(false, "unexpected error"); + panic!("unexpected error"); } } @@ -203,7 +203,7 @@ mod test { ); assert_eq!(tokens[4], Token::new(spos(1, 12), TokenType::CloseParen)); } else { - assert!(false, "unexpected error"); + panic!("unexpected error"); } } @@ -226,7 +226,7 @@ mod test { ); assert_eq!(tokens[4], Token::new(spos(4, 0), TokenType::CloseParen)); } else { - assert!(false, "unexpected error"); + panic!("unexpected error"); } } @@ -237,10 +237,10 @@ mod test { assert_eq!(line, 2); assert_eq!(column, 0); } else { - assert!(false, "Expected error position"); + panic!("Expected error position"); } } else { - assert!(false, "expected ParseEvalError for tab character"); + panic!("expected ParseEvalError for tab character"); } } @@ -249,7 +249,7 @@ mod test { if let Ok(_tokens) = tokenize("(foo \"text\" bar)") { // TODO } else { - assert!(false, "unexpected error") + panic!("unexpected error") } } } diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index 4a6f1c7..a8de174 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -55,7 +55,7 @@ fn load_file(filename: &str) -> Result { /// Read and evaluate an entire file fn read_file(filename: &str) -> Result { - let contents = load_file(&filename)?; + let contents = load_file(filename)?; Ok(contents) } diff --git a/interpreter/src/number.rs b/interpreter/src/number.rs index 818740a..8aae27b 100644 --- a/interpreter/src/number.rs +++ b/interpreter/src/number.rs @@ -11,9 +11,9 @@ pub struct NumberObject { } impl Print for NumberObject { - fn print<'guard>( + fn print( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { // TODO diff --git a/interpreter/src/pair.rs b/interpreter/src/pair.rs index 9b708a7..f6c20b8 100644 --- a/interpreter/src/pair.rs +++ b/interpreter/src/pair.rs @@ -51,7 +51,7 @@ impl Pair { /// Set Pair.second to the given value // ANCHOR: DefPairDot - pub fn dot<'guard>(&self, value: TaggedScopedPtr<'guard>) { + pub fn dot(&self, value: TaggedScopedPtr<'_>) { self.second.set(value); } // ANCHOR_END: DefPairDot @@ -66,9 +66,9 @@ impl Pair { } impl Print for Pair { - fn print<'guard>( + fn print( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { let mut tail = ScopedPtr::new(guard, self); @@ -91,9 +91,9 @@ impl Print for Pair { } // In debug print, use dot notation - fn debug<'guard>( + fn debug( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { write!( diff --git a/interpreter/src/parser.rs b/interpreter/src/parser.rs index 279bc94..af599a5 100644 --- a/interpreter/src/parser.rs +++ b/interpreter/src/parser.rs @@ -90,12 +90,12 @@ impl<'guard> PairList<'guard> { // If a list token is: // * a Dot, it must be followed by an s-expression and a CloseParen // -fn parse_list<'guard, 'i, I: 'i>( +fn parse_list<'guard, 'i, I>( mem: &'guard MutatorView, tokens: &mut Peekable, ) -> Result, RuntimeError> where - I: Iterator, + I: 'i + Iterator, { use self::TokenType::*; @@ -195,12 +195,12 @@ where // * symbol // * or a list // -fn parse_sexpr<'guard, 'i, I: 'i>( +fn parse_sexpr<'guard, 'i, I>( mem: &'guard MutatorView, tokens: &mut Peekable, ) -> Result, RuntimeError> where - I: Iterator, + I: 'i + Iterator, { use self::TokenType::*; diff --git a/interpreter/src/rawarray.rs b/interpreter/src/rawarray.rs index 0e9e97e..b72fd71 100644 --- a/interpreter/src/rawarray.rs +++ b/interpreter/src/rawarray.rs @@ -36,10 +36,7 @@ pub struct RawArray { /// be used in a Cell impl Clone for RawArray { fn clone(&self) -> Self { - RawArray { - capacity: self.capacity, - ptr: self.ptr, - } + *self } } @@ -56,8 +53,8 @@ impl RawArray { /// Return a RawArray of the given capacity number of bytes allocated // ANCHOR: DefRawArrayWithCapacity - pub fn with_capacity<'scope>( - mem: &'scope MutatorView, + pub fn with_capacity( + mem: &'_ MutatorView, capacity: u32, ) -> Result, RuntimeError> { // convert to bytes, checking for possible overflow of ArraySize limit @@ -75,9 +72,9 @@ impl RawArray { /// Resize the array to the new capacity /// TODO the inner implementation of this should live in the allocator API to make /// better use of optimizations - pub fn resize<'scope>( + pub fn resize( &mut self, - mem: &'scope MutatorView, + mem: &'_ MutatorView, new_capacity: u32, ) -> Result<(), RuntimeError> { // If we're reducing the capacity to 0, simply detach the array pointer diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index c0257e6..f1ed124 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -71,16 +71,16 @@ fn interpret_line(mem: &MutatorView, thread: &Thread, line: String) -> Result<() } Ok(value) - })(mem, &line) + })(mem, line) { Ok(value) => println!("{}", value), Err(e) => { match e.error_kind() { // non-fatal repl errors - ErrorKind::LexerError(_) => e.print_with_source(&line), - ErrorKind::ParseError(_) => e.print_with_source(&line), - ErrorKind::EvalError(_) => e.print_with_source(&line), + ErrorKind::LexerError(_) => e.print_with_source(line), + ErrorKind::ParseError(_) => e.print_with_source(line), + ErrorKind::EvalError(_) => e.print_with_source(line), _ => return Err(e), } } diff --git a/interpreter/src/symbol.rs b/interpreter/src/symbol.rs index c0a5400..514653c 100644 --- a/interpreter/src/symbol.rs +++ b/interpreter/src/symbol.rs @@ -46,9 +46,9 @@ impl Symbol { impl Print for Symbol { /// Safe because the lifetime of `MutatorScope` defines a safe-access window - fn print<'guard>( + fn print( &self, - guard: &'guard dyn MutatorScope, + guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { write!(f, "{}", self.as_str(guard)) @@ -57,7 +57,7 @@ impl Print for Symbol { // ANCHOR: DefImplHashableForSymbol impl Hashable for Symbol { - fn hash<'guard, H: Hasher>(&self, guard: &'guard dyn MutatorScope, h: &mut H) { + fn hash(&self, guard: &'_ dyn MutatorScope, h: &mut H) { self.as_str(guard).hash(h) } } From 18df9dc733c61b0df02898774aa83a4d20be8698 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sat, 28 Jun 2025 23:30:18 -0400 Subject: [PATCH 24/32] Cleaning up, linting --- interpreter/src/vm.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 8dfd70d..8c39905 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -13,7 +13,9 @@ use crate::list::List; use crate::memory::MutatorView; use crate::pair::Pair; use crate::pointerops::AsScopedRef; -use crate::safeptr::{AsScopedPtr, CellPtr, MutatorScope, RefPtr, ScopedPtr, TaggedCellPtr, TaggedScopedPtr}; +use crate::safeptr::{ + AsScopedPtr, CellPtr, MutatorScope, RefPtr, ScopedPtr, TaggedCellPtr, TaggedScopedPtr, +}; use crate::taggedptr::{TaggedPtr, Value}; pub const RETURN_REG: usize = 0; @@ -41,10 +43,10 @@ pub struct CallFrame { base: ArraySize, } // ANCHOR_END: DefCallFrame - + impl CallFrame { /// Instantiate an outer-level call frame at the beginning of the stack - pub fn new_main<'guard>(main_fn: ScopedPtr<'guard, Function>) -> CallFrame { + pub fn new_main(main_fn: ScopedPtr<'_, Function>) -> CallFrame { CallFrame { function: CellPtr::new_with(main_fn), ip: Cell::new(0), @@ -54,11 +56,7 @@ impl CallFrame { /// Instantiate a new stack frame for the given function, beginning execution at the given /// instruction pointer and a register window at `base` - fn new<'guard>( - function: ScopedPtr<'guard, Function>, - ip: ArraySize, - base: ArraySize, - ) -> CallFrame { + fn new(function: ScopedPtr<'_, Function>, ip: ArraySize, base: ArraySize) -> CallFrame { CallFrame { function: CellPtr::new_with(function), ip: Cell::new(ip), @@ -67,7 +65,7 @@ impl CallFrame { } /// Return a string representation of this stack frame - fn as_string<'guard>(&self, guard: &'guard dyn MutatorScope) -> String { + fn as_string(&self, guard: &'_ dyn MutatorScope) -> String { let function = self.function.get(guard); format!("in {}", function) } @@ -746,9 +744,8 @@ impl Thread { while status == EvalStatus::Pending { status = self.continue_exec(mem, 1024)?; - match status { - EvalStatus::Return(value) => return Ok(value), - _ => (), + if let EvalStatus::Return(value) = status { + return Ok(value); } } @@ -782,10 +779,11 @@ impl Thread { for _ in 0..max_instr { match self.eval_next_instr(mem) { // Evaluation paused or completed without error - Ok(exit_cond) => match exit_cond { - EvalStatus::Return(value) => return Ok(EvalStatus::Return(value)), - _ => (), - }, + Ok(exit_cond) => { + if let EvalStatus::Return(value) = exit_cond { + return Ok(EvalStatus::Return(value)); + } + } // Evaluation hit an error Err(rt_error) => { From d5140fd0674e5739904ff935016d8f9aed1d8445 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 29 Jun 2025 21:03:19 -0400 Subject: [PATCH 25/32] Attempt to fix GitHub Actions --- .github/workflows/book.yml | 10 +++++----- .github/workflows/ci.yml | 12 ++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index ee0966b..9e8942d 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -6,18 +6,18 @@ jobs: deploy: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup mdBook - uses: peaceiris/actions-mdbook@v1 + uses: peaceiris/actions-mdbook@v2 with: - mdbook-version: '0.4.5' - # mdbook-version: 'latest' + # mdbook-version: '0.4.5' + mdbook-version: 'latest' - run: mdbook build - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} publish_dir: ./book diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index caab1e2..52ce69c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,14 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + uses: actions/checkout@v4 + + - name: Use latest stable + run: rustup update stable - name: Cargo fmt check blockalloc working-directory: ./blockalloc From ff1353f20870f531a7b5f2cfca578e9a8ce85cfe Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 29 Jun 2025 21:05:29 -0400 Subject: [PATCH 26/32] Apply rust formatting --- interpreter/src/arena.rs | 2 +- interpreter/src/array.rs | 48 ++++++----------------------------- interpreter/src/bytecode.rs | 11 ++------ interpreter/src/compiler.rs | 13 ++-------- interpreter/src/containers.rs | 18 +++---------- interpreter/src/dict.rs | 11 ++------ interpreter/src/error.rs | 5 +--- interpreter/src/function.rs | 25 +++--------------- interpreter/src/number.rs | 6 +---- interpreter/src/pair.rs | 12 ++------- interpreter/src/pointerops.rs | 2 -- interpreter/src/rawarray.rs | 11 ++------ interpreter/src/safeptr.rs | 3 +-- interpreter/src/symbol.rs | 6 +---- interpreter/src/taggedptr.rs | 4 ++- 15 files changed, 33 insertions(+), 144 deletions(-) diff --git a/interpreter/src/arena.rs b/interpreter/src/arena.rs index 7008e86..f074774 100644 --- a/interpreter/src/arena.rs +++ b/interpreter/src/arena.rs @@ -2,7 +2,7 @@ /// Currently implemented on top of stickyimmix without any gc which includes unnecessary /// overhead. use std::ptr::NonNull; - + use stickyimmix::{ AllocError, AllocHeader, AllocObject, AllocRaw, ArraySize, Mark, RawPtr, SizeClass, StickyImmixHeap, diff --git a/interpreter/src/array.rs b/interpreter/src/array.rs index 68d8047..d6787dd 100644 --- a/interpreter/src/array.rs +++ b/interpreter/src/array.rs @@ -117,11 +117,7 @@ impl Array { /// Bounds-checked read // ANCHOR: DefArrayRead - fn read( - &self, - _guard: &'_ dyn MutatorScope, - index: ArraySize, - ) -> Result { + fn read(&self, _guard: &'_ dyn MutatorScope, index: ArraySize) -> Result { unsafe { let dest = self.get_offset(index)?; Ok(read(dest)) @@ -178,10 +174,7 @@ impl Container for Array { } } - fn with_capacity( - mem: &'_ MutatorView, - capacity: ArraySize, - ) -> Result, RuntimeError> { + fn with_capacity(mem: &'_ MutatorView, capacity: ArraySize) -> Result, RuntimeError> { Ok(Array { length: Cell::new(0), data: Cell::new(RawArray::with_capacity(mem, capacity)?), @@ -207,12 +200,7 @@ impl FillContainer for Array { /// Increase the size of the array to `size` and fill the new slots with /// copies of `item`. If `size` is less than the current length of the array, /// does nothing. - fn fill( - &self, - mem: &'_ MutatorView, - size: ArraySize, - item: T, - ) -> Result<(), RuntimeError> { + fn fill(&self, mem: &'_ MutatorView, size: ArraySize, item: T) -> Result<(), RuntimeError> { if self.borrow.get() != INTERIOR_ONLY { return Err(RuntimeError::new(ErrorKind::MutableBorrowError)); } @@ -311,11 +299,7 @@ impl StackContainer for Array { impl IndexedContainer for Array { /// Return a copy of the object at the given index. Bounds-checked. - fn get( - &self, - guard: &'_ dyn MutatorScope, - index: ArraySize, - ) -> Result { + fn get(&self, guard: &'_ dyn MutatorScope, index: ArraySize) -> Result { self.read(guard, index) } @@ -348,11 +332,7 @@ impl SliceableContainer for Array { pub type ArrayU8 = Array; impl Print for ArrayU8 { - fn print( - &self, - _guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ArrayU8[...]") } } @@ -361,11 +341,7 @@ impl Print for ArrayU8 { pub type ArrayU16 = Array; impl Print for ArrayU16 { - fn print( - &self, - _guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ArrayU16[...]") } } @@ -374,11 +350,7 @@ impl Print for ArrayU16 { pub type ArrayU32 = Array; impl Print for ArrayU32 { - fn print( - &self, - _guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ArrayU32[...]") } } @@ -529,11 +501,7 @@ impl AnyContainerFromSlice for Array { } impl Print for Array { - fn print( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[")?; for i in 0..self.length() { diff --git a/interpreter/src/bytecode.rs b/interpreter/src/bytecode.rs index c690bd3..66823f9 100644 --- a/interpreter/src/bytecode.rs +++ b/interpreter/src/bytecode.rs @@ -246,11 +246,7 @@ impl ByteCode { } impl Print for ByteCode { - fn print( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { let mut instr_str = String::new(); self.code.access_slice(guard, |code| { @@ -293,10 +289,7 @@ impl InstructionStream { /// Retrieve the next instruction and return it, incrementing the instruction pointer // TODO: https://github.com/rust-hosted-langs/book/issues/39 // ANCHOR: DefInstructionStreamGetNextOpcode - pub fn get_next_opcode( - &self, - guard: &'_ dyn MutatorScope, - ) -> Result { + pub fn get_next_opcode(&self, guard: &'_ dyn MutatorScope) -> Result { let instr = self .instructions .get(guard) diff --git a/interpreter/src/compiler.rs b/interpreter/src/compiler.rs index 1126706..946668b 100644 --- a/interpreter/src/compiler.rs +++ b/interpreter/src/compiler.rs @@ -154,10 +154,7 @@ impl<'parent> Variables<'parent> { } /// Search for a binding, following parent scopes. - fn lookup_binding( - &self, - name: TaggedScopedPtr<'_>, - ) -> Result, RuntimeError> { + fn lookup_binding(&self, name: TaggedScopedPtr<'_>) -> Result, RuntimeError> { // return value should be (count-of-parent-functions-followed, Variable) let name_string = match *name { Value::Symbol(s) => String::from(s.as_str(&name)), @@ -356,13 +353,7 @@ impl<'parent> Compiler<'parent> { let fn_nonlocals = self.vars.get_nonlocals(mem)?; - Function::alloc( - mem, - fn_name, - fn_params, - fn_bytecode, - fn_nonlocals, - ) + Function::alloc(mem, fn_name, fn_params, fn_bytecode, fn_nonlocals) } // ANCHOR_END: DefCompilerCompileFunction diff --git a/interpreter/src/containers.rs b/interpreter/src/containers.rs index 46b9288..5188da4 100644 --- a/interpreter/src/containers.rs +++ b/interpreter/src/containers.rs @@ -20,10 +20,7 @@ pub trait Container: Sized { fn new() -> Self; /// Create a new container instance with the given capacity. // TODO: this may not make sense for tree types - fn with_capacity( - mem: &'_ MutatorView, - capacity: ArraySize, - ) -> Result; + fn with_capacity(mem: &'_ MutatorView, capacity: ArraySize) -> Result; /// Reset the size of the container to zero - empty fn clear(&self, mem: &'_ MutatorView) -> Result<(), RuntimeError>; @@ -35,12 +32,7 @@ pub trait Container: Sized { /// If implemented, the container can be filled with a set number of values in one operation pub trait FillContainer: Container { /// The `item` is an object to copy into each container memory slot. - fn fill( - &self, - mem: &'_ MutatorView, - size: ArraySize, - item: T, - ) -> Result<(), RuntimeError>; + fn fill(&self, mem: &'_ MutatorView, size: ArraySize, item: T) -> Result<(), RuntimeError>; } /// If implemented, the container can be filled with a set number of values in one operation @@ -97,11 +89,7 @@ pub trait StackAnyContainer: StackContainer { /// Generic indexed-access trait. If implemented, the container can function as an indexable vector pub trait IndexedContainer: Container { /// Return a copy of the object at the given index. Bounds-checked. - fn get( - &self, - _guard: &'_ dyn MutatorScope, - index: ArraySize, - ) -> Result; + fn get(&self, _guard: &'_ dyn MutatorScope, index: ArraySize) -> Result; /// Move an object into the array at the given index. Bounds-checked. fn set( diff --git a/interpreter/src/dict.rs b/interpreter/src/dict.rs index fb2d50e..39c8d59 100644 --- a/interpreter/src/dict.rs +++ b/interpreter/src/dict.rs @@ -190,10 +190,7 @@ impl Container for Dict { } } - fn with_capacity( - mem: &'_ MutatorView, - capacity: ArraySize, - ) -> Result { + fn with_capacity(mem: &'_ MutatorView, capacity: ArraySize) -> Result { let dict = Dict { length: Cell::new(0), used_entries: Cell::new(0), @@ -318,11 +315,7 @@ impl HashIndexedAnyContainer for Dict { } impl Print for Dict { - fn print( - &self, - _guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { // TODO write!(f, "Dict[...]") } diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index 2d73f94..4c4abab 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -45,10 +45,7 @@ pub struct RuntimeError { impl RuntimeError { pub fn new(kind: ErrorKind) -> RuntimeError { - RuntimeError { - kind, - pos: None, - } + RuntimeError { kind, pos: None } } pub fn with_pos(kind: ErrorKind, pos: SourcePos) -> RuntimeError { diff --git a/interpreter/src/function.rs b/interpreter/src/function.rs index 3b190e2..b71aa0e 100644 --- a/interpreter/src/function.rs +++ b/interpreter/src/function.rs @@ -104,11 +104,7 @@ impl Function { impl Print for Function { /// Prints a string representation of the function - fn print( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { let name = self.name.get(guard); let params = self.param_names.get(guard); @@ -124,11 +120,7 @@ impl Print for Function { } /// Prints the disassembled bytecode - fn debug( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn debug(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { self.print(guard, f)?; write!(f, "\nbytecode follows:\n")?; self.code(guard).debug(guard, f) @@ -236,11 +228,7 @@ impl Partial { impl Print for Partial { /// Prints a string representation of the Partial object - fn print( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { let function = self.func.get(guard); let name = function.name.get(guard); let params = function.param_names.get(guard); @@ -258,14 +246,9 @@ impl Print for Partial { } /// Prints the associated function's disassembled bytecode - fn debug( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn debug(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { self.print(guard, f)?; write!(f, "\nbytecode follows:\n")?; self.func.get(guard).code(guard).debug(guard, f) } } - diff --git a/interpreter/src/number.rs b/interpreter/src/number.rs index 8aae27b..b997b47 100644 --- a/interpreter/src/number.rs +++ b/interpreter/src/number.rs @@ -11,11 +11,7 @@ pub struct NumberObject { } impl Print for NumberObject { - fn print( - &self, - _guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { // TODO write!(f, "NumberObject(nan)") } diff --git a/interpreter/src/pair.rs b/interpreter/src/pair.rs index f6c20b8..c350d25 100644 --- a/interpreter/src/pair.rs +++ b/interpreter/src/pair.rs @@ -66,11 +66,7 @@ impl Pair { } impl Print for Pair { - fn print( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { let mut tail = ScopedPtr::new(guard, self); write!(f, "({}", tail.first.get(guard))?; @@ -91,11 +87,7 @@ impl Print for Pair { } // In debug print, use dot notation - fn debug( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn debug(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "({:?} . {:?})", diff --git a/interpreter/src/pointerops.rs b/interpreter/src/pointerops.rs index 46a027d..d16b00f 100644 --- a/interpreter/src/pointerops.rs +++ b/interpreter/src/pointerops.rs @@ -57,5 +57,3 @@ impl AsScopedRef for RawPtr { } } // ANCHOR_END: DefScopedRef - - diff --git a/interpreter/src/rawarray.rs b/interpreter/src/rawarray.rs index b72fd71..9e15ca6 100644 --- a/interpreter/src/rawarray.rs +++ b/interpreter/src/rawarray.rs @@ -53,10 +53,7 @@ impl RawArray { /// Return a RawArray of the given capacity number of bytes allocated // ANCHOR: DefRawArrayWithCapacity - pub fn with_capacity( - mem: &'_ MutatorView, - capacity: u32, - ) -> Result, RuntimeError> { + pub fn with_capacity(mem: &'_ MutatorView, capacity: u32) -> Result, RuntimeError> { // convert to bytes, checking for possible overflow of ArraySize limit let capacity_bytes = capacity .checked_mul(size_of::() as ArraySize) @@ -72,11 +69,7 @@ impl RawArray { /// Resize the array to the new capacity /// TODO the inner implementation of this should live in the allocator API to make /// better use of optimizations - pub fn resize( - &mut self, - mem: &'_ MutatorView, - new_capacity: u32, - ) -> Result<(), RuntimeError> { + pub fn resize(&mut self, mem: &'_ MutatorView, new_capacity: u32) -> Result<(), RuntimeError> { // If we're reducing the capacity to 0, simply detach the array pointer if new_capacity == 0 { self.capacity = 0; diff --git a/interpreter/src/safeptr.rs b/interpreter/src/safeptr.rs index e9dca20..23f7d89 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -46,7 +46,7 @@ impl MutatorScope for ScopedPtr<'_, T> {} impl<'guard, T: Sized> Clone for ScopedPtr<'guard, T> { fn clone(&self) -> ScopedPtr<'guard, T> { - *self + *self } } @@ -82,7 +82,6 @@ pub trait AsScopedPtr { fn scoped_ptr<'scope>(&self, guard: &'scope dyn MutatorScope) -> ScopedPtr<'scope, T>; } - /// A wrapper around untagged raw pointers for storing compile-time typed pointers in /// data structures that are not expected to change in pointer value, i.e. once the /// object is initialized, it remains immutably pointing at that object. diff --git a/interpreter/src/symbol.rs b/interpreter/src/symbol.rs index 514653c..d93b14a 100644 --- a/interpreter/src/symbol.rs +++ b/interpreter/src/symbol.rs @@ -46,11 +46,7 @@ impl Symbol { impl Print for Symbol { /// Safe because the lifetime of `MutatorScope` defines a safe-access window - fn print( - &self, - guard: &'_ dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str(guard)) } } diff --git a/interpreter/src/taggedptr.rs b/interpreter/src/taggedptr.rs index f6dd8fb..e1a1c71 100644 --- a/interpreter/src/taggedptr.rs +++ b/interpreter/src/taggedptr.rs @@ -23,7 +23,9 @@ use crate::list::List; use crate::memory::HeapStorage; use crate::number::NumberObject; use crate::pair::Pair; -use crate::pointerops::{get_tag, AsScopedRef, Tagged, TAG_NUMBER, TAG_OBJECT, TAG_PAIR, TAG_SYMBOL}; +use crate::pointerops::{ + get_tag, AsScopedRef, Tagged, TAG_NUMBER, TAG_OBJECT, TAG_PAIR, TAG_SYMBOL, +}; use crate::printer::Print; use crate::safeptr::{MutatorScope, ScopedPtr}; use crate::symbol::Symbol; From a5c0869cbc4a1510426adf73c387334b92bace21 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 27 Aug 2025 20:38:43 -0400 Subject: [PATCH 27/32] Rename stickyimmix to immixcons --- README.md | 2 +- booksrc/SUMMARY.md | 2 +- booksrc/chapter-allocation-api.md | 12 ++++++------ booksrc/chapter-allocation-impl.md | 8 ++++---- booksrc/chapter-interp-alloc.md | 2 +- booksrc/chapter-managing-blocks.md | 4 ++-- booksrc/chapter-simple-bump.md | 12 ++++++------ booksrc/part-stickyimmix.md | 2 +- {stickyimmix => immixcons}/.gitignore | 0 {stickyimmix => immixcons}/Cargo.lock | 2 +- {stickyimmix => immixcons}/Cargo.toml | 2 +- {stickyimmix => immixcons}/LICENSE.txt | 0 {stickyimmix => immixcons}/README.md | 0 {stickyimmix => immixcons}/src/allocator.rs | 0 {stickyimmix => immixcons}/src/blockmeta.rs | 0 {stickyimmix => immixcons}/src/bumpblock.rs | 0 {stickyimmix => immixcons}/src/constants.rs | 0 {stickyimmix => immixcons}/src/heap.rs | 0 {stickyimmix => immixcons}/src/lib.rs | 0 {stickyimmix => immixcons}/src/rawptr.rs | 0 interpreter/Cargo.toml | 2 +- interpreter/README.md | 2 +- interpreter/src/arena.rs | 4 ++-- interpreter/src/array.rs | 2 +- interpreter/src/containers.rs | 2 +- interpreter/src/error.rs | 2 +- interpreter/src/headers.rs | 2 +- interpreter/src/main.rs | 2 +- interpreter/src/memory.rs | 2 +- interpreter/src/pointerops.rs | 2 +- interpreter/src/rawarray.rs | 2 +- interpreter/src/repl.rs | 13 ++++++------- interpreter/src/safeptr.rs | 2 +- interpreter/src/symbolmap.rs | 2 +- interpreter/src/taggedptr.rs | 6 +++--- scratchpad/notes.md | 13 +++++++++++-- 36 files changed, 58 insertions(+), 50 deletions(-) rename {stickyimmix => immixcons}/.gitignore (100%) rename {stickyimmix => immixcons}/Cargo.lock (91%) rename {stickyimmix => immixcons}/Cargo.toml (89%) rename {stickyimmix => immixcons}/LICENSE.txt (100%) rename {stickyimmix => immixcons}/README.md (100%) rename {stickyimmix => immixcons}/src/allocator.rs (100%) rename {stickyimmix => immixcons}/src/blockmeta.rs (100%) rename {stickyimmix => immixcons}/src/bumpblock.rs (100%) rename {stickyimmix => immixcons}/src/constants.rs (100%) rename {stickyimmix => immixcons}/src/heap.rs (100%) rename {stickyimmix => immixcons}/src/lib.rs (100%) rename {stickyimmix => immixcons}/src/rawptr.rs (100%) diff --git a/README.md b/README.md index 1825ede..bedf9ee 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ interpreter in Rust including: ## Project vision -From CPython to Ruby's YARV, V8 and SpiderMonkey, GHC to the JVM, most language +From Python to Ruby, V8 and SpiderMonkey, GHC to the JVM, most language runtimes are written in C/C++. Rust is eminently suitable for implementing languages and can provide diff --git a/booksrc/SUMMARY.md b/booksrc/SUMMARY.md index e32d4c6..1548bf9 100644 --- a/booksrc/SUMMARY.md +++ b/booksrc/SUMMARY.md @@ -5,7 +5,7 @@ - [Alignment](./chapter-alignment.md) - [Obtaining blocks of memory](./chapter-blocks.md) - [The type of allocation](./chapter-what-is-alloc.md) -- [An allocator: Sticky Immix](./part-stickyimmix.md) +- [An allocator: Sticky Immix](./part-immixcons.md) - [Bump allocation](./chapter-simple-bump.md) - [Allocating into multiple blocks](./chapter-managing-blocks.md) - [Defining the allocation API](./chapter-allocation-api.md) diff --git a/booksrc/chapter-allocation-api.md b/booksrc/chapter-allocation-api.md index be6999f..7541df4 100644 --- a/booksrc/chapter-allocation-api.md +++ b/booksrc/chapter-allocation-api.md @@ -28,7 +28,7 @@ than checking a pointer for being null. We'll allow for distinguishing between Out Of Memory and an allocation request that for whatever reason is invalid. ```rust,ignore -{{#include ../stickyimmix/src/allocator.rs:DefAllocError}} +{{#include ../immixcons/src/allocator.rs:DefAllocError}} ``` The second change is that instead of a `*const T` value in the success @@ -37,7 +37,7 @@ will amount to little more than containing a `std::ptr::NonNull` instance and some functions to access the pointer. ```rust,ignore -{{#include ../stickyimmix/src/rawptr.rs:DefRawPtr}} +{{#include ../immixcons/src/rawptr.rs:DefRawPtr}} ``` This'll be better to work with on the user-of-the-crate side. @@ -63,7 +63,7 @@ collector in _this_ crate need. We'll define a trait for the user to implement. ```rust,ignore -{{#include ../stickyimmix/src/allocator.rs:DefAllocHeader}} +{{#include ../immixcons/src/allocator.rs:DefAllocHeader}} ``` Now we have a bunch more questions to answer! Some of these trait methods are @@ -95,7 +95,7 @@ pub trait AllocHeader: Sized { where `AllocTypeId` is define simply as: ```rust,ignore -{{#include ../stickyimmix/src/allocator.rs:DefAllocTypeId}} +{{#include ../immixcons/src/allocator.rs:DefAllocTypeId}} ``` This means the interpreter is free to implement a type identifier type however @@ -124,7 +124,7 @@ header is being instantiated for. And what is `AllocObject`? Simply: ```rust,ignore -{{#include ../stickyimmix/src/allocator.rs:DefAllocObject}} +{{#include ../immixcons/src/allocator.rs:DefAllocObject}} ``` In summary, we have: @@ -367,7 +367,7 @@ pub trait AllocRaw { Our complete `AllocRaw` trait definition now looks like this: ```rust,ignore -{{#include ../stickyimmix/src/allocator.rs:DefAllocRaw}} +{{#include ../immixcons/src/allocator.rs:DefAllocRaw}} ``` In the next chapter we'll build out the `AllocRaw` trait implementation. diff --git a/booksrc/chapter-allocation-impl.md b/booksrc/chapter-allocation-impl.md index 05233d1..07c5441 100644 --- a/booksrc/chapter-allocation-impl.md +++ b/booksrc/chapter-allocation-impl.md @@ -29,7 +29,7 @@ Let's look at the implementation. ```rust,ignore impl AllocRaw for StickyImmixHeap { -{{#include ../stickyimmix/src/heap.rs:DefAlloc}} +{{#include ../immixcons/src/heap.rs:DefAlloc}} } ``` @@ -49,7 +49,7 @@ write into the array itself. ```rust,ignore impl AllocRaw for StickyImmixHeap { -{{#include ../stickyimmix/src/heap.rs:DefAllocArray}} +{{#include ../immixcons/src/heap.rs:DefAllocArray}} } ``` @@ -69,7 +69,7 @@ pointer. ```rust,ignore impl AllocRaw for StickyImmixHeap { -{{#include ../stickyimmix/src/heap.rs:DefGetHeader}} +{{#include ../immixcons/src/heap.rs:DefGetHeader}} } ``` @@ -78,7 +78,7 @@ to the header pointer results in the object pointer: ```rust,ignore impl AllocRaw for StickyImmixHeap { -{{#include ../stickyimmix/src/heap.rs:DefGetObject}} +{{#include ../immixcons/src/heap.rs:DefGetObject}} } ``` diff --git a/booksrc/chapter-interp-alloc.md b/booksrc/chapter-interp-alloc.md index b8b661a..7296357 100644 --- a/booksrc/chapter-interp-alloc.md +++ b/booksrc/chapter-interp-alloc.md @@ -6,7 +6,7 @@ defined in the Sticky Immix crate. Let's first recall this interface: ```rust,ignore -{{#include ../stickyimmix/src/allocator.rs:DefAllocRaw}} +{{#include ../immixcons/src/allocator.rs:DefAllocRaw}} ``` These are the functions we'll be calling. When we allocate an object, we'll get diff --git a/booksrc/chapter-managing-blocks.md b/booksrc/chapter-managing-blocks.md index 79311a3..839659c 100644 --- a/booksrc/chapter-managing-blocks.md +++ b/booksrc/chapter-managing-blocks.md @@ -8,7 +8,7 @@ blocks so we can allocate - in theory - indefinitely. We'll need a new struct for wrapping multiple blocks: ```rust,ignore -{{#include ../stickyimmix/src/heap.rs:DefBlockList}} +{{#include ../immixcons/src/heap.rs:DefBlockList}} ``` Immix maintains several lists of blocks. We won't include them all in the first @@ -172,7 +172,7 @@ struct definition below there is reference to a generic type `H` that the _user_ of the heap will define as the object header. ```rust,ignore -{{#include ../stickyimmix/src/heap.rs:DefStickyImmixHeap}} +{{#include ../immixcons/src/heap.rs:DefStickyImmixHeap}} ``` Since object headers are not owned directly by the heap struct, we need a diff --git a/booksrc/chapter-simple-bump.md b/booksrc/chapter-simple-bump.md index d2657ee..42eb03b 100644 --- a/booksrc/chapter-simple-bump.md +++ b/booksrc/chapter-simple-bump.md @@ -20,14 +20,14 @@ original [Immix paper][1]. This size can be any power of two though and different use cases may show different optimal sizes. ```rust,ignore -{{#include ../stickyimmix/src/constants.rs:ConstBlockSize}} +{{#include ../immixcons/src/constants.rs:ConstBlockSize}} ``` Now we'll define a struct that wraps the block with a bump pointer and garbage collection metadata: ```rust,ignore -{{#include ../stickyimmix/src/bumpblock.rs:DefBumpBlock}} +{{#include ../immixcons/src/bumpblock.rs:DefBumpBlock}} ``` ## Bump allocation basics @@ -118,7 +118,7 @@ but first some constants that we need in order to know - how many bytes remain in the `Block` for allocating into ```rust,ignore -{{#include ../stickyimmix/src/constants.rs:ConstLineSize}} +{{#include ../immixcons/src/constants.rs:ConstLineSize}} ``` For clarity, let's put some numbers to the definitions we've made so far: @@ -141,7 +141,7 @@ The definition of `BumpBlock` contains member `meta` which is of type need to represent a pointer to the line mark section at the end of the `Block`: ```rust,ignore -{{#include ../stickyimmix/src/blockmeta.rs:DefBlockMeta}} +{{#include ../immixcons/src/blockmeta.rs:DefBlockMeta}} ``` This pointer could be easily calculated, of course, so this is just a handy @@ -154,7 +154,7 @@ shortcut. The struct `BlockMeta` contains one function we will study: ```rust,ignore -{{#include ../stickyimmix/src/blockmeta.rs:DefFindNextHole}} +{{#include ../immixcons/src/blockmeta.rs:DefFindNextHole}} ``` The purpose of this function is to locate a gap of unmarked lines of sufficient @@ -288,7 +288,7 @@ reached the end of the block, exhausting our options. The new definition of `BumpBlock::inner_alloc()` reads as follows: ```rust,ignore -{{#include ../stickyimmix/src/bumpblock.rs:DefBumpBlockAlloc}} +{{#include ../immixcons/src/bumpblock.rs:DefBumpBlockAlloc}} ``` and as you can see, this implementation is recursive. diff --git a/booksrc/part-stickyimmix.md b/booksrc/part-stickyimmix.md index eca640e..e4e889a 100644 --- a/booksrc/part-stickyimmix.md +++ b/booksrc/part-stickyimmix.md @@ -23,7 +23,7 @@ Each block is divided into lines. In the original paper, blocks are sized at 32k and lines at 128 bytes. Objects are allocated into blocks using bump allocation and objects can cross line boundaries. -![StickyImmix Block](img/stickyimmix_block.png) +![StickyImmix Block](img/immixcons_block.png) During tracing to discover live objects, objects are marked as live, but the line, or lines, that each object occupies are also marked as live. This can mean, of diff --git a/stickyimmix/.gitignore b/immixcons/.gitignore similarity index 100% rename from stickyimmix/.gitignore rename to immixcons/.gitignore diff --git a/stickyimmix/Cargo.lock b/immixcons/Cargo.lock similarity index 91% rename from stickyimmix/Cargo.lock rename to immixcons/Cargo.lock index ba8d359..0d54c83 100644 --- a/stickyimmix/Cargo.lock +++ b/immixcons/Cargo.lock @@ -5,7 +5,7 @@ name = "blockalloc" version = "0.1.0" [[package]] -name = "stickyimmix" +name = "immixcons" version = "0.1.0" dependencies = [ "blockalloc 0.1.0", diff --git a/stickyimmix/Cargo.toml b/immixcons/Cargo.toml similarity index 89% rename from stickyimmix/Cargo.toml rename to immixcons/Cargo.toml index 355bd1b..6d42116 100644 --- a/stickyimmix/Cargo.toml +++ b/immixcons/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "stickyimmix" +name = "immixcons" version = "0.1.0" authors = ["Peter Liniker "] edition = "2018" diff --git a/stickyimmix/LICENSE.txt b/immixcons/LICENSE.txt similarity index 100% rename from stickyimmix/LICENSE.txt rename to immixcons/LICENSE.txt diff --git a/stickyimmix/README.md b/immixcons/README.md similarity index 100% rename from stickyimmix/README.md rename to immixcons/README.md diff --git a/stickyimmix/src/allocator.rs b/immixcons/src/allocator.rs similarity index 100% rename from stickyimmix/src/allocator.rs rename to immixcons/src/allocator.rs diff --git a/stickyimmix/src/blockmeta.rs b/immixcons/src/blockmeta.rs similarity index 100% rename from stickyimmix/src/blockmeta.rs rename to immixcons/src/blockmeta.rs diff --git a/stickyimmix/src/bumpblock.rs b/immixcons/src/bumpblock.rs similarity index 100% rename from stickyimmix/src/bumpblock.rs rename to immixcons/src/bumpblock.rs diff --git a/stickyimmix/src/constants.rs b/immixcons/src/constants.rs similarity index 100% rename from stickyimmix/src/constants.rs rename to immixcons/src/constants.rs diff --git a/stickyimmix/src/heap.rs b/immixcons/src/heap.rs similarity index 100% rename from stickyimmix/src/heap.rs rename to immixcons/src/heap.rs diff --git a/stickyimmix/src/lib.rs b/immixcons/src/lib.rs similarity index 100% rename from stickyimmix/src/lib.rs rename to immixcons/src/lib.rs diff --git a/stickyimmix/src/rawptr.rs b/immixcons/src/rawptr.rs similarity index 100% rename from stickyimmix/src/rawptr.rs rename to immixcons/src/rawptr.rs diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index 9ae9d64..73cd7b4 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -11,5 +11,5 @@ dirs = "1.0" fnv = "1.0.3" itertools = "0.9" rustyline = "6.1.2" -stickyimmix = { path = "../stickyimmix" } +immixcons = { path = "../immixcons" } blockalloc = { path = "../blockalloc" } diff --git a/interpreter/README.md b/interpreter/README.md index a9921d3..b0db7ba 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -1,5 +1,5 @@ # The Eval-rs -A simple interpreter, built on the `stickyimmix` allocator. +A simple interpreter, built on the `immixcons` allocator. ![The Evalrus](https://pliniker.github.io/assets/img/evalrus-medium.png) diff --git a/interpreter/src/arena.rs b/interpreter/src/arena.rs index f074774..a939dac 100644 --- a/interpreter/src/arena.rs +++ b/interpreter/src/arena.rs @@ -1,9 +1,9 @@ /// A memory arena implemented as an ever growing pool of blocks. -/// Currently implemented on top of stickyimmix without any gc which includes unnecessary +/// Currently implemented on top of immixcons without any gc which includes unnecessary /// overhead. use std::ptr::NonNull; -use stickyimmix::{ +use immixcons::{ AllocError, AllocHeader, AllocObject, AllocRaw, ArraySize, Mark, RawPtr, SizeClass, StickyImmixHeap, }; diff --git a/interpreter/src/array.rs b/interpreter/src/array.rs index d6787dd..ca53437 100644 --- a/interpreter/src/array.rs +++ b/interpreter/src/array.rs @@ -9,7 +9,7 @@ use std::fmt; use std::ptr::{read, write}; use std::slice::from_raw_parts_mut; -pub use stickyimmix::{AllocObject, ArraySize}; +pub use immixcons::{AllocObject, ArraySize}; use crate::containers::{ AnyContainerFromPairList, AnyContainerFromSlice, Container, ContainerFromSlice, diff --git a/interpreter/src/containers.rs b/interpreter/src/containers.rs index 5188da4..d94bb86 100644 --- a/interpreter/src/containers.rs +++ b/interpreter/src/containers.rs @@ -1,7 +1,7 @@ /// Container traits /// /// TODO iterators/views -use stickyimmix::ArraySize; +use immixcons::ArraySize; use crate::error::RuntimeError; use crate::memory::MutatorView; diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index 4c4abab..c25ec34 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -5,7 +5,7 @@ use std::io; use rustyline::error::ReadlineError; use blockalloc::BlockError; -use stickyimmix::AllocError; +use immixcons::AllocError; /// Source code position // ANCHOR: DefSourcePos diff --git a/interpreter/src/headers.rs b/interpreter/src/headers.rs index 9691759..a895f88 100644 --- a/interpreter/src/headers.rs +++ b/interpreter/src/headers.rs @@ -1,6 +1,6 @@ /// Defines an `ObjectHeader` type to immediately preceed each heap allocated /// object, which also contains a type tag but with space for many more types. -use stickyimmix::{ +use immixcons::{ AllocHeader, AllocObject, AllocRaw, AllocTypeId, ArraySize, Mark, RawPtr, SizeClass, }; diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index a8de174..a22843e 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -4,7 +4,7 @@ extern crate dirs; extern crate fnv; extern crate itertools; extern crate rustyline; -extern crate stickyimmix; +extern crate immixcons; use std::fs::File; use std::io; diff --git a/interpreter/src/memory.rs b/interpreter/src/memory.rs index 2138dcd..02fadc2 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/memory.rs @@ -2,7 +2,7 @@ /// /// Defines Stack, Heap and Memory types, and a MemoryView type that gives a mutator a safe /// view into the stack and heap. -use stickyimmix::{AllocObject, AllocRaw, ArraySize, RawPtr, StickyImmixHeap}; +use immixcons::{AllocObject, AllocRaw, ArraySize, RawPtr, StickyImmixHeap}; use crate::error::RuntimeError; use crate::headers::{ObjectHeader, TypeList}; diff --git a/interpreter/src/pointerops.rs b/interpreter/src/pointerops.rs index d16b00f..bc14670 100644 --- a/interpreter/src/pointerops.rs +++ b/interpreter/src/pointerops.rs @@ -1,7 +1,7 @@ /// Miscelaneous pointer operations use std::ptr::NonNull; -use stickyimmix::RawPtr; +use immixcons::RawPtr; use crate::safeptr::MutatorScope; diff --git a/interpreter/src/rawarray.rs b/interpreter/src/rawarray.rs index 9e15ca6..52d0911 100644 --- a/interpreter/src/rawarray.rs +++ b/interpreter/src/rawarray.rs @@ -2,7 +2,7 @@ use std::mem::size_of; use std::ptr::NonNull; use std::slice::from_raw_parts_mut; -pub use stickyimmix::ArraySize; +pub use immixcons::ArraySize; use crate::error::{ErrorKind, RuntimeError}; use crate::memory::MutatorView; diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index f1ed124..2d4ba64 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -26,7 +26,7 @@ fn get_reader(history_file: &Option) -> Editor<()> { // Try to load the repl history file if let Some(ref path) = history_file { if let Err(err) = reader.load_history(&path) { - eprintln!("Could not read history: {}", err); + eprintln!("Could not read history: {err}"); } } @@ -47,15 +47,14 @@ fn interpret_line(mem: &MutatorView, thread: &Thread, line: String) -> Result<() if debug { println!( - "# Debug\n## Input:\n```\n{}\n```\n## Parsed:\n```\n{:?}\n```", - line, value + "# Debug\n## Input:\n```\n{line}\n```\n## Parsed:\n```\n{value:?}\n```" ); } let function = compile(mem, value)?; if debug { - println!("## Compiled:\n```\n{:?}\n```", function); + println!("## Compiled:\n```\n{function:?}\n```"); } let mut status = thread.start_exec(mem, function)?; @@ -67,13 +66,13 @@ fn interpret_line(mem: &MutatorView, thread: &Thread, line: String) -> Result<() }; if debug { - println!("## Evaluated:\n```\n{:?}\n```\n", value); + println!("## Evaluated:\n```\n{value:?}\n```\n"); } Ok(value) })(mem, line) { - Ok(value) => println!("{}", value), + Ok(value) => println!("{value}"), Err(e) => { match e.error_kind() { @@ -110,7 +109,7 @@ pub fn repl(mem: &MutatorView) -> Result<(), RuntimeError> { Err(e) => { if let Some(ref path) = history_file { reader.save_history(&path).unwrap_or_else(|err| { - eprintln!("could not save input history in {}: {}", path, err); + eprintln!("could not save input history in {path}: {err}"); }); } diff --git a/interpreter/src/safeptr.rs b/interpreter/src/safeptr.rs index 23f7d89..0a1f345 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -2,7 +2,7 @@ use std::cell::Cell; use std::fmt; use std::ops::Deref; -use stickyimmix::{AllocObject, RawPtr}; +use immixcons::{AllocObject, RawPtr}; use crate::headers::TypeList; use crate::pointerops::AsScopedRef; diff --git a/interpreter/src/symbolmap.rs b/interpreter/src/symbolmap.rs index 0e125f6..29fe547 100644 --- a/interpreter/src/symbolmap.rs +++ b/interpreter/src/symbolmap.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::collections::HashMap; -use stickyimmix::{AllocRaw, RawPtr}; +use immixcons::{AllocRaw, RawPtr}; use crate::arena::Arena; use crate::symbol::Symbol; diff --git a/interpreter/src/taggedptr.rs b/interpreter/src/taggedptr.rs index e1a1c71..8f338d0 100644 --- a/interpreter/src/taggedptr.rs +++ b/interpreter/src/taggedptr.rs @@ -14,7 +14,7 @@ use std::fmt; use std::ptr::NonNull; -use stickyimmix::{AllocRaw, RawPtr}; +use immixcons::{AllocRaw, RawPtr}; use crate::array::{ArrayU16, ArrayU32, ArrayU8}; use crate::dict::Dict; @@ -199,7 +199,7 @@ impl From for FatPtr { // ANCHOR: FromTaggedPtrForFatPtr impl From for FatPtr { fn from(ptr: TaggedPtr) -> FatPtr { - ptr.into_fat_ptr() + ptr.as_fat_ptr() } } // ANCHOR_END: FromTaggedPtrForFatPtr @@ -288,7 +288,7 @@ impl TaggedPtr { } // ANCHOR: DefTaggedPtrIntoFatPtr - fn into_fat_ptr(&self) -> FatPtr { + fn as_fat_ptr(&self) -> FatPtr { unsafe { if self.tag == 0 { FatPtr::Nil diff --git a/scratchpad/notes.md b/scratchpad/notes.md index 76a8b0b..26050e1 100644 --- a/scratchpad/notes.md +++ b/scratchpad/notes.md @@ -1,10 +1,19 @@ # Notes +## TODOs + +- [ ] replace Pairs with Arrays in parser and compiler +- [ ] implement some additional builtins - math operators, strings +- [ ] implement some integration tests +- [ ] conservative immix + +## Conservative Immix + - https://www.steveblackburn.org/pubs/papers/consrc-oopsla-2014.pdf - https://docs.rs/portable-atomic/latest/portable_atomic/struct.AtomicUsize.html - https://www.hboehm.info/gc/gcdescr.html -## Rooting +### Rooting Conservative stack scanning. - allows for intrusive data structures _where used_ @@ -23,7 +32,7 @@ Depends on: - object map in each block - FIRST step, implement object block -## Tracing +### Tracing Precise object scanning. OR could it be conservative? From 7c83cd5a47df2e90dbd54c3aace6cd8e26c98039 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 27 Aug 2025 20:45:30 -0400 Subject: [PATCH 28/32] Rename stickyimmix to immixcons - GHA --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52ce69c..867c129 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,8 @@ jobs: working-directory: ./blockalloc run: cargo fmt --all -- --check - - name: Cargo fmt check stickyimmix - working-directory: ./stickyimmix + - name: Cargo fmt check immixcons + working-directory: ./immixcons run: cargo fmt --all -- --check - name: Cargo fmt check interpreter @@ -33,8 +33,8 @@ jobs: working-directory: ./blockalloc run: cargo test - - name: Cargo test stickyimmix - working-directory: ./stickyimmix + - name: Cargo test immixcons + working-directory: ./immixcons run: cargo test - name: Cargo test interpreter From ac5fb25d2153216c90728b4aa4504892782adcad Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Wed, 27 Aug 2025 20:46:44 -0400 Subject: [PATCH 29/32] Apply rustfmt --- interpreter/src/main.rs | 2 +- interpreter/src/repl.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index a22843e..3ca962d 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -2,9 +2,9 @@ extern crate blockalloc; extern crate clap; extern crate dirs; extern crate fnv; +extern crate immixcons; extern crate itertools; extern crate rustyline; -extern crate immixcons; use std::fs::File; use std::io; diff --git a/interpreter/src/repl.rs b/interpreter/src/repl.rs index 2d4ba64..f470b28 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -46,9 +46,7 @@ fn interpret_line(mem: &MutatorView, thread: &Thread, line: String) -> Result<() let value = parse(mem, line)?; if debug { - println!( - "# Debug\n## Input:\n```\n{line}\n```\n## Parsed:\n```\n{value:?}\n```" - ); + println!("# Debug\n## Input:\n```\n{line}\n```\n## Parsed:\n```\n{value:?}\n```"); } let function = compile(mem, value)?; From 5a36c1701ad2d077911dc27bd134ea7e7e8f8333 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 31 Aug 2025 21:21:08 -0400 Subject: [PATCH 30/32] Cleaning up, linting --- blockalloc/src/lib.rs | 2 ++ ...{part-stickyimmix.md => part-immixcons.md} | 0 immixcons/src/blockmeta.rs | 24 +++++---------- immixcons/src/bumpblock.rs | 23 ++++++-------- immixcons/src/constants.rs | 2 +- immixcons/src/heap.rs | 16 +++++----- interpreter/src/array.rs | 4 +-- interpreter/src/bytecode.rs | 4 +-- interpreter/src/compiler.rs | 6 ++-- interpreter/src/dict.rs | 30 +++++++++---------- interpreter/src/error.rs | 16 +++++----- interpreter/src/function.rs | 6 ++-- interpreter/src/lexer.rs | 2 +- interpreter/src/main.rs | 4 +-- interpreter/src/pair.rs | 2 +- interpreter/src/parser.rs | 2 +- 16 files changed, 65 insertions(+), 78 deletions(-) rename booksrc/{part-stickyimmix.md => part-immixcons.md} (100%) diff --git a/blockalloc/src/lib.rs b/blockalloc/src/lib.rs index 471858b..2450312 100644 --- a/blockalloc/src/lib.rs +++ b/blockalloc/src/lib.rs @@ -68,6 +68,8 @@ impl Block { self.size } + /// # Safety + /// /// Unsafely reassemble from pointer and size pub unsafe fn from_raw_parts(ptr: BlockPtr, size: BlockSize) -> Block { Block { ptr, size } diff --git a/booksrc/part-stickyimmix.md b/booksrc/part-immixcons.md similarity index 100% rename from booksrc/part-stickyimmix.md rename to booksrc/part-immixcons.md diff --git a/immixcons/src/blockmeta.rs b/immixcons/src/blockmeta.rs index d1eb514..9fceb24 100644 --- a/immixcons/src/blockmeta.rs +++ b/immixcons/src/blockmeta.rs @@ -49,7 +49,7 @@ impl BlockMeta { } } - /// Return an iterator over all the line mark flags + // Return an iterator over all the line mark flags //pub fn line_iter(&self) -> impl Iterator { // self.line_mark.iter() //} @@ -128,7 +128,7 @@ mod tests { let got = meta.find_next_available_hole(10 * constants::LINE_SIZE, constants::LINE_SIZE); - println!("test_find_next_hole got {:?} expected {:?}", got, expect); + println!("test_find_next_hole got {got:?} expected {expect:?}"); assert!(got == expect); } @@ -147,10 +147,7 @@ mod tests { let got = meta.find_next_available_hole(3 * constants::LINE_SIZE, constants::LINE_SIZE); - println!( - "test_find_next_hole_at_line_zero got {:?} expected {:?}", - got, expect - ); + println!("test_find_next_hole_at_line_zero got {got:?} expected {expect:?}"); assert!(got == expect); } @@ -173,10 +170,7 @@ mod tests { let got = meta.find_next_available_hole(constants::BLOCK_CAPACITY, constants::LINE_SIZE); - println!( - "test_find_next_hole_at_block_end got {:?} expected {:?}", - got, expect - ); + println!("test_find_next_hole_at_block_end got {got:?} expected {expect:?}"); assert!(got == expect); } @@ -197,12 +191,8 @@ mod tests { let got = meta.find_next_available_hole(constants::BLOCK_CAPACITY, constants::LINE_SIZE); - println!( - "test_find_hole_all_conservatively_marked got {:?} expected None", - got - ); - - assert!(got == None); + println!("test_find_hole_all_conservatively_marked got {got:?} expected None"); + assert!(got.is_none()); } #[test] @@ -214,7 +204,7 @@ mod tests { let expect = Some((constants::BLOCK_CAPACITY, 0)); let got = meta.find_next_available_hole(constants::BLOCK_CAPACITY, constants::LINE_SIZE); - println!("test_find_entire_block got {:?} expected {:?}", got, expect); + println!("test_find_entire_block got {got:?} expected {expect:?}"); assert!(got == expect); } diff --git a/immixcons/src/bumpblock.rs b/immixcons/src/bumpblock.rs index 4e259db..d9c02f9 100644 --- a/immixcons/src/bumpblock.rs +++ b/immixcons/src/bumpblock.rs @@ -108,20 +108,15 @@ mod tests { let mut v = Vec::new(); let mut index = 0; - loop { - //println!("cursor={}, limit={}", b.cursor, b.limit); - if let Some(ptr) = b.inner_alloc(TEST_UNIT_SIZE) { - let u32ptr = ptr as *mut u32; + while let Some(ptr) = b.inner_alloc(TEST_UNIT_SIZE) { + let u32ptr = ptr as *mut u32; - assert!(!v.contains(&u32ptr)); + assert!(!v.contains(&u32ptr)); - v.push(u32ptr); - unsafe { *u32ptr = index } + v.push(u32ptr); + unsafe { *u32ptr = index } - index += 1; - } else { - break; - } + index += 1; } for (index, u32ptr) in v.iter().enumerate() { @@ -140,7 +135,7 @@ mod tests { let count = loop_check_allocate(&mut b); let expect = constants::BLOCK_CAPACITY / TEST_UNIT_SIZE; - println!("expect={}, count={}", expect, count); + println!("expect={expect}, count={count}"); assert!(count == expect); } @@ -160,7 +155,7 @@ mod tests { let expect = (constants::BLOCK_CAPACITY - constants::LINE_SIZE - occupied_bytes) / TEST_UNIT_SIZE; - println!("expect={}, count={}", expect, count); + println!("expect={expect}, count={count}"); assert!(count == expect); } @@ -181,7 +176,7 @@ mod tests { let count = loop_check_allocate(&mut b); - println!("count={}", count); + println!("count={count}"); assert!(count == 0); } } diff --git a/immixcons/src/constants.rs b/immixcons/src/constants.rs index df4262f..0a8c9a5 100644 --- a/immixcons/src/constants.rs +++ b/immixcons/src/constants.rs @@ -24,7 +24,7 @@ pub const ALLOC_ALIGN_BYTES: usize = 16; pub const ALLOC_ALIGN_MASK: usize = !(ALLOC_ALIGN_BYTES - 1); // Object size ranges -pub const MAX_ALLOC_SIZE: usize = std::u32::MAX as usize; +pub const MAX_ALLOC_SIZE: usize = u32::MAX as usize; pub const SMALL_OBJECT_MIN: usize = 1; pub const SMALL_OBJECT_MAX: usize = LINE_SIZE; pub const MEDIUM_OBJECT_MIN: usize = SMALL_OBJECT_MAX + 1; diff --git a/immixcons/src/heap.rs b/immixcons/src/heap.rs index 8692a19..28d763d 100644 --- a/immixcons/src/heap.rs +++ b/immixcons/src/heap.rs @@ -73,7 +73,7 @@ impl BlockList { space } - } as *const u8; + }; Ok(space) } @@ -152,7 +152,7 @@ impl StickyImmixHeap { space } - } as *const u8; + }; Ok(space) } @@ -191,7 +191,7 @@ impl AllocRaw for StickyImmixHeap { } // write the object into the allocated space after the header - let object_space = unsafe { space.offset(header_size as isize) }; + let object_space = unsafe { space.add(header_size) }; unsafe { write(object_space as *mut T, object); } @@ -224,7 +224,7 @@ impl AllocRaw for StickyImmixHeap { } // calculate where the array will begin after the header - let array_space = unsafe { space.offset(header_size as isize) }; + let array_space = unsafe { space.add(header_size) }; // Initialize object_space to zero here. // If using the system allocator for any objects (SizeClass::Large, for example), @@ -236,7 +236,7 @@ impl AllocRaw for StickyImmixHeap { } // return a pointer to the array in the allocated space - Ok(RawPtr::new(array_space as *const u8)) + Ok(RawPtr::new(array_space)) } // ANCHOR_END: DefAllocArray @@ -377,8 +377,8 @@ mod tests { // allocate a sequence of numbers for i in 0..(constants::BLOCK_SIZE * 3) { - match mem.alloc(i as usize) { - Err(_) => assert!(false, "Allocation failed unexpectedly"), + match mem.alloc(i) { + Err(_) => panic!("Allocation failed unexpectedly"), Ok(ptr) => obs.push(ptr), } } @@ -398,7 +398,7 @@ mod tests { let size = 2048; match mem.alloc_array(size) { - Err(_) => assert!(false, "Array allocation failed unexpectedly"), + Err(_) => panic!("Array allocation failed unexpectedly"), Ok(ptr) => { // Validate that array is zero initialized all the way through diff --git a/interpreter/src/array.rs b/interpreter/src/array.rs index ca53437..8444d91 100644 --- a/interpreter/src/array.rs +++ b/interpreter/src/array.rs @@ -644,7 +644,7 @@ mod test { for n in 1..12 { if let Value::Pair(pair) = *tail { - tail = pair.append(view, view.lookup_sym(&format!("thing{}", n)))?; + tail = pair.append(view, view.lookup_sym(&format!("thing{n}")))?; } else { panic!("expected pair!") } @@ -656,7 +656,7 @@ mod test { let thing = IndexedAnyContainer::get(&*array, view, n)?; match *thing { - Value::Symbol(s) => assert!(s.as_str(view) == format!("thing{}", n)), + Value::Symbol(s) => assert!(s.as_str(view) == format!("thing{n}")), _ => panic!("expected symbol!"), } } diff --git a/interpreter/src/bytecode.rs b/interpreter/src/bytecode.rs index 66823f9..c99a30e 100644 --- a/interpreter/src/bytecode.rs +++ b/interpreter/src/bytecode.rs @@ -250,10 +250,10 @@ impl Print for ByteCode { let mut instr_str = String::new(); self.code.access_slice(guard, |code| { - instr_str = join(code.iter().map(|opcode| format!("{:?}", opcode)), "\n") + instr_str = join(code.iter().map(|opcode| format!("{opcode:?}")), "\n") }); - write!(f, "{}", instr_str) + write!(f, "{instr_str}") } } diff --git a/interpreter/src/compiler.rs b/interpreter/src/compiler.rs index 946668b..d2ad846 100644 --- a/interpreter/src/compiler.rs +++ b/interpreter/src/compiler.rs @@ -867,9 +867,9 @@ mod integration { code: &str, ) -> Result, RuntimeError> { let compiled_code = compile(mem, parse(mem, code)?)?; - println!("RUN CODE {}", code); + println!("RUN CODE {code}"); let result = thread.exec(mem, compiled_code)?; - println!("RUN RESULT {}", result); + println!("RUN RESULT {result}"); Ok(result) } @@ -1076,7 +1076,7 @@ mod integration { let sym_x = mem.lookup_sym("x"); let sym_y = mem.lookup_sym("y"); let sym_z = mem.lookup_sym("z"); - assert!(result == &[sym_x, sym_y, sym_z, sym_z, sym_y]); + assert!(result == [sym_x, sym_y, sym_z, sym_z, sym_y]); Ok(()) } diff --git a/interpreter/src/dict.rs b/interpreter/src/dict.rs index 39c8d59..e36839c 100644 --- a/interpreter/src/dict.rs +++ b/interpreter/src/dict.rs @@ -425,20 +425,20 @@ mod test { let dict = Dict::with_capacity(mem, 100)?; for num in 0..50 { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); dict.assoc(mem, key, val)?; } for num in 0..50 { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); assert!(dict.exists(mem, key)?); @@ -461,20 +461,20 @@ mod test { let dict = Dict::with_capacity(mem, 20)?; for num in 0..500 { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); dict.assoc(mem, key, val)?; } for num in 0..500 { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); assert!(dict.exists(mem, key)?); @@ -498,10 +498,10 @@ mod test { let dict = Dict::with_capacity(mem, 100)?; for num in 0..50 { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); dict.assoc(mem, key, val)?; @@ -509,17 +509,17 @@ mod test { // delete every other key for num in (0..50).step_by(2) { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); dict.dissoc(mem, key)?; } // add more stuff for num in 0..20 { - let key_name = format!("ignore_{}", num); + let key_name = format!("ignore_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); dict.assoc(mem, key, val)?; @@ -527,10 +527,10 @@ mod test { // check that the originally inserted keys are discoverable or not as expected for num in 0..50 { - let key_name = format!("foo_{}", num); + let key_name = format!("foo_{num}"); let key = mem.lookup_sym(&key_name); - let val_name = format!("val_{}", num); + let val_name = format!("val_{num}"); let val = mem.lookup_sym(&val_name); if num % 2 == 0 { diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index c25ec34..d4a8b6b 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -71,7 +71,7 @@ impl RuntimeError { for (count, line) in iter { // count starts at 0, line numbers start at 1 if count + 1 == pos.line as usize { - println!("error: {}", self); + println!("error: {self}"); println!("{:5}|{}", pos.line, line); println!("{:5}|{:width$}^", " ", " ", width = pos.column as usize); println!("{:5}|", " "); @@ -79,7 +79,7 @@ impl RuntimeError { } } } else { - println!("error: {}", self); + println!("error: {self}"); } } } @@ -87,10 +87,10 @@ impl RuntimeError { impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.kind { - ErrorKind::IOError(ref reason) => write!(f, "IO Error: {}", reason), - ErrorKind::LexerError(ref reason) => write!(f, "Parse error: {}", reason), - ErrorKind::ParseError(ref reason) => write!(f, "Parse error: {}", reason), - ErrorKind::EvalError(ref reason) => write!(f, "Evaluation error: {}", reason), + ErrorKind::IOError(ref reason) => write!(f, "IO Error: {reason}"), + ErrorKind::LexerError(ref reason) => write!(f, "Parse error: {reason}"), + ErrorKind::ParseError(ref reason) => write!(f, "Parse error: {reason}"), + ErrorKind::EvalError(ref reason) => write!(f, "Evaluation error: {reason}"), ErrorKind::OutOfMemory => write!(f, "Out of memory!"), ErrorKind::BadAllocationRequest => { write!(f, "An invalid memory size allocation was requested!") @@ -109,14 +109,14 @@ impl fmt::Display for RuntimeError { /// Convert from io::Error impl From for RuntimeError { fn from(other: io::Error) -> RuntimeError { - RuntimeError::new(ErrorKind::IOError(format!("{}", other))) + RuntimeError::new(ErrorKind::IOError(format!("{other}"))) } } /// Convert from ReadlineError impl From for RuntimeError { fn from(other: ReadlineError) -> RuntimeError { - RuntimeError::new(ErrorKind::IOError(format!("{}", other))) + RuntimeError::new(ErrorKind::IOError(format!("{other}"))) } } diff --git a/interpreter/src/function.rs b/interpreter/src/function.rs index b71aa0e..09d760e 100644 --- a/interpreter/src/function.rs +++ b/interpreter/src/function.rs @@ -115,7 +115,7 @@ impl Print for Function { match *name { Value::Symbol(s) => write!(f, "(Function {} ({}))", s.as_str(guard), param_string), - _ => write!(f, "(Function ({}))", param_string), + _ => write!(f, "(Function ({param_string}))"), } } @@ -163,7 +163,7 @@ impl Partial { }; // copy args to the Partial's own list - let args_list: ScopedPtr<'guard, List> = ContainerFromSlice::from_slice(mem, &args)?; + let args_list: ScopedPtr<'guard, List> = ContainerFromSlice::from_slice(mem, args)?; mem.alloc(Partial { arity, @@ -241,7 +241,7 @@ impl Print for Partial { match *name { Value::Symbol(s) => write!(f, "(Partial {} ({}))", s.as_str(guard), param_string), - _ => write!(f, "(Partial ({}))", param_string), + _ => write!(f, "(Partial ({param_string}))"), } } diff --git a/interpreter/src/lexer.rs b/interpreter/src/lexer.rs index 4b79f27..6204f93 100644 --- a/interpreter/src/lexer.rs +++ b/interpreter/src/lexer.rs @@ -47,7 +47,7 @@ pub fn tokenize(input: &str) -> Result, RuntimeError> { // characters that terminate a symbol let terminating = [OPEN_PAREN, CLOSE_PAREN, SPACE, TAB, CR, LF, DOUBLE_QUOTE]; - let is_terminating = |c: char| terminating.iter().any(|t| c == *t); + let is_terminating = |c: char| terminating.contains(&c); // return value let mut tokens = Vec::new(); diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs index 3ca962d..4bc336a 100644 --- a/interpreter/src/main.rs +++ b/interpreter/src/main.rs @@ -74,7 +74,7 @@ fn main() { if let Some(filename) = matches.value_of("filename") { // if a filename was specified, read it into a String read_file(filename).unwrap_or_else(|err| { - eprintln!("Terminated: {}", err); + eprintln!("Terminated: {err}"); process::exit(1); }); // TODO @@ -83,7 +83,7 @@ fn main() { let mem = Memory::new(); let result = mem.enter(repl); result.unwrap_or_else(|err| { - eprintln!("Terminated: {}", err); + eprintln!("Terminated: {err}"); process::exit(1); }); } diff --git a/interpreter/src/pair.rs b/interpreter/src/pair.rs index c350d25..86172bd 100644 --- a/interpreter/src/pair.rs +++ b/interpreter/src/pair.rs @@ -80,7 +80,7 @@ impl Print for Pair { let second = *tail.second.get(guard); match second { Value::Nil => (), - _ => write!(f, " . {}", second)?, + _ => write!(f, " . {second}")?, } write!(f, ")") diff --git a/interpreter/src/parser.rs b/interpreter/src/parser.rs index af599a5..b90ba21 100644 --- a/interpreter/src/parser.rs +++ b/interpreter/src/parser.rs @@ -231,7 +231,7 @@ where pos: _, }) => { tokens.next(); - let text = mem.alloc_tagged(text::Text::new_from_str(mem, &string)?)?; + let text = mem.alloc_tagged(text::Text::new_from_str(mem, string)?)?; Ok(text) } From 5075c488c914cc6e208787c39e54fb098e7dfc23 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 31 Aug 2025 21:23:34 -0400 Subject: [PATCH 31/32] Cleaning up, linting --- interpreter/src/vm.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 8c39905..913b7f6 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -67,7 +67,7 @@ impl CallFrame { /// Return a string representation of this stack frame fn as_string(&self, guard: &'_ dyn MutatorScope) -> String { let function = self.function.get(guard); - format!("in {}", function) + format!("in {function}") } } @@ -440,8 +440,7 @@ impl Thread { Ok(binding) => window[dest as usize].set(binding), Err(_) => { return Err(err_eval(&format!( - "Symbol {} is not bound to a value", - name_val + "Symbol {name_val} is not bound to a value" ))) } } From 40880e3f188643e9efe385066e75214ca6d87004 Mon Sep 17 00:00:00 2001 From: Peter Liniker Date: Sun, 31 Aug 2025 21:28:02 -0400 Subject: [PATCH 32/32] Cleaning up, linting --- interpreter/src/printer.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interpreter/src/printer.rs b/interpreter/src/printer.rs index a9fb3fb..20c7dd0 100644 --- a/interpreter/src/printer.rs +++ b/interpreter/src/printer.rs @@ -6,15 +6,15 @@ use crate::taggedptr::Value; /// Trait for using a `Value` lifted pointer in the `Display` trait pub trait Print { - fn print<'guard>( + fn print( &self, - _guard: &'guard dyn MutatorScope, + _guard: &dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result; - fn debug<'guard>( + fn debug( &self, - _guard: &'guard dyn MutatorScope, + _guard: &dyn MutatorScope, f: &mut fmt::Formatter, ) -> fmt::Result { self.print(_guard, f) @@ -30,9 +30,9 @@ pub trait Print { } pub fn print(value: Value) -> String { - format!("{}", value) + format!("{value}") } pub fn debug(value: Value) -> String { - format!("{:?}", value) + format!("{value:?}") }