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..867c129 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,21 +12,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + - name: Use latest stable + run: rustup update stable - name: Cargo fmt check blockalloc 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 @@ -37,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 diff --git a/.gitignore b/.gitignore index 7585238..05ff2e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ book +target diff --git a/README.md b/README.md index 8dca04d..bedf9ee 100644 --- a/README.md +++ b/README.md @@ -12,30 +12,17 @@ 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++. -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/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/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/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/booksrc/part-stickyimmix.md b/booksrc/part-immixcons.md similarity index 98% rename from booksrc/part-stickyimmix.md rename to booksrc/part-immixcons.md index eca640e..e4e889a 100644 --- a/booksrc/part-stickyimmix.md +++ b/booksrc/part-immixcons.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 85% rename from stickyimmix/src/allocator.rs rename to immixcons/src/allocator.rs index f5440c6..ef8a717 100644 --- a/stickyimmix/src/allocator.rs +++ b/immixcons/src/allocator.rs @@ -1,4 +1,3 @@ -use std::mem::size_of; use std::ptr::NonNull; use crate::constants; @@ -107,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; @@ -122,15 +121,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/blockmeta.rs b/immixcons/src/blockmeta.rs similarity index 91% rename from stickyimmix/src/blockmeta.rs rename to immixcons/src/blockmeta.rs index d1eb514..9fceb24 100644 --- a/stickyimmix/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/stickyimmix/src/bumpblock.rs b/immixcons/src/bumpblock.rs similarity index 90% rename from stickyimmix/src/bumpblock.rs rename to immixcons/src/bumpblock.rs index 4e259db..d9c02f9 100644 --- a/stickyimmix/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/stickyimmix/src/constants.rs b/immixcons/src/constants.rs similarity index 95% rename from stickyimmix/src/constants.rs rename to immixcons/src/constants.rs index df4262f..0a8c9a5 100644 --- a/stickyimmix/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/stickyimmix/src/heap.rs b/immixcons/src/heap.rs similarity index 93% rename from stickyimmix/src/heap.rs rename to immixcons/src/heap.rs index a08142e..28d763d 100644 --- a/stickyimmix/src/heap.rs +++ b/immixcons/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; @@ -73,7 +73,7 @@ impl BlockList { space } - } as *const u8; + }; Ok(space) } @@ -152,7 +152,7 @@ impl StickyImmixHeap { space } - } as *const u8; + }; Ok(space) } @@ -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); @@ -192,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); } @@ -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); @@ -226,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), @@ -238,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 @@ -308,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 } @@ -379,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), } } @@ -400,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/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 76% rename from stickyimmix/src/rawptr.rs rename to immixcons/src/rawptr.rs index 7ef3e4b..733aecd 100644 --- a/stickyimmix/src/rawptr.rs +++ b/immixcons/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 { 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/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/arena.rs b/interpreter/src/arena.rs index a131356..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, }; @@ -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/array.rs b/interpreter/src/array.rs index a4b9459..8444d91 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, @@ -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,11 +117,7 @@ impl Array { /// Bounds-checked read // ANCHOR: DefArrayRead - fn read<'guard>( - &self, - _guard: &'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)) @@ -131,9 +127,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 +143,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 +156,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,10 +174,7 @@ impl Container for Array { } } - fn with_capacity<'guard>( - mem: &'guard 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)?), @@ -189,7 +182,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 { @@ -204,12 +197,14 @@ impl Container for Array { } impl FillContainer for Array { - fn fill<'guard>( - &self, - mem: &'guard MutatorView, - size: ArraySize, - item: T, - ) -> Result<(), RuntimeError> { + /// 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> { + if self.borrow.get() != INTERIOR_ONLY { + return Err(RuntimeError::new(ErrorKind::MutableBorrowError)); + } + let length = self.length(); if length > size { @@ -243,7 +238,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)); } @@ -271,7 +266,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)); } @@ -289,7 +284,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 { @@ -304,18 +299,14 @@ impl StackContainer for Array { impl IndexedContainer for Array { /// Return a copy of the object at the given index. Bounds-checked. - fn get<'guard>( - &self, - guard: &'guard dyn MutatorScope, - index: ArraySize, - ) -> Result { + fn get(&self, 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> { @@ -325,7 +316,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, { @@ -341,11 +332,7 @@ impl SliceableContainer for Array { pub type ArrayU8 = Array; impl Print for ArrayU8 { - fn print<'guard>( - &self, - _guard: &'guard dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ArrayU8[...]") } } @@ -354,11 +341,7 @@ impl Print for ArrayU8 { pub type ArrayU16 = Array; impl Print for ArrayU16 { - fn print<'guard>( - &self, - _guard: &'guard dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ArrayU16[...]") } } @@ -367,11 +350,7 @@ impl Print for ArrayU16 { pub type ArrayU32 = Array; impl Print for ArrayU32 { - fn print<'guard>( - &self, - _guard: &'guard dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn print(&self, _guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ArrayU32[...]") } } @@ -383,6 +362,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 { @@ -518,11 +501,7 @@ impl AnyContainerFromSlice for Array { } impl Print for Array { - fn print<'guard>( - &self, - guard: &'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() { @@ -546,8 +525,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; @@ -555,200 +534,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(); + let ptr_before = array.data.get().as_ptr(); - // fill to capacity - for _ in 0..256 { - StackAnyContainer::push(&array, view, view.nil())?; - } + // fill to capacity + for _ in 0..256 { + StackAnyContainer::push(&array, view, view.nil())?; + } - let ptr_after = array.data.get().as_ptr(); + let ptr_after = array.data.get().as_ptr(); - // array storage shouldn't have been reallocated - assert!(ptr_before == ptr_after); + // array storage shouldn't have been reallocated + assert!(ptr_before == ptr_after); - // overflow capacity, requiring reallocation - StackAnyContainer::push(&array, view, view.nil())?; + // overflow capacity, requiring reallocation + StackAnyContainer::push(&array, view, view.nil())?; - let ptr_realloc = array.data.get().as_ptr(); + let ptr_realloc = array.data.get().as_ptr(); - // array storage should have been reallocated - assert!(ptr_before != ptr_realloc); + // array storage should have been reallocated + assert!(ptr_before != ptr_realloc); - Ok(()) - } - } - - 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)?; - 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 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!") } + } - 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/bytecode.rs b/interpreter/src/bytecode.rs index b3f5602..c99a30e 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,18 +246,14 @@ impl ByteCode { } impl Print for ByteCode { - fn print<'guard>( - &self, - guard: &'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| { - 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}") } } @@ -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<'guard>( - &self, - guard: &'guard dyn MutatorScope, - ) -> Result { + pub fn get_next_opcode(&self, guard: &'_ dyn MutatorScope) -> Result { let instr = self .instructions .get(guard) @@ -308,9 +301,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( @@ -318,7 +311,7 @@ impl InstructionStream { guard, lit_id as ArraySize, )? - .get_ptr()) + .get_ptr(guard)) } /// Return the next instruction pointer diff --git a/interpreter/src/compiler.rs b/interpreter/src/compiler.rs index 474bb99..d2ad846 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,10 +154,7 @@ impl<'parent> Variables<'parent> { } /// Search for a binding, following parent scopes. - fn lookup_binding<'guard>( - &self, - name: TaggedScopedPtr<'guard>, - ) -> 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)), @@ -183,7 +180,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 +243,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 +331,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 +353,7 @@ impl<'parent> Compiler<'parent> { let fn_nonlocals = self.vars.get_nonlocals(mem)?; - Ok(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 @@ -753,7 +744,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) } @@ -866,7 +857,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; @@ -876,31 +867,15 @@ mod integration { code: &str, ) -> Result, RuntimeError> { let compiled_code = compile(mem, parse(mem, code)?)?; - println!("RUN CODE {}", code); - let result = thread.quick_vm_eval(mem, compiled_code)?; - println!("RUN RESULT {}", result); + println!("RUN CODE {code}"); + let result = thread.exec(mem, compiled_code)?; + println!("RUN RESULT {result}"); Ok(result) } 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] @@ -1006,7 +981,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(()) } @@ -1101,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/containers.rs b/interpreter/src/containers.rs index 0096655..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; @@ -20,13 +20,10 @@ 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, - capacity: ArraySize, - ) -> Result; + 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,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<'guard>( - &self, - mem: &'guard 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 @@ -58,14 +50,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 +89,12 @@ 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>( - &self, - _guard: &'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<'guard>( + fn set( &self, - _guard: &'guard dyn MutatorScope, + _guard: &'_ dyn MutatorScope, index: ArraySize, item: T, ) -> Result<(), RuntimeError>; @@ -126,7 +114,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 +164,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; } @@ -208,9 +196,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/dict.rs b/interpreter/src/dict.rs index f4f50c0..e36839c 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,10 +190,7 @@ impl Container for Dict { } } - fn with_capacity<'guard>( - mem: &'guard MutatorView, - capacity: ArraySize, - ) -> Result { + fn with_capacity(mem: &'_ MutatorView, capacity: ArraySize) -> Result { let dict = Dict { length: Cell::new(0), used_entries: Cell::new(0), @@ -206,7 +203,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 +302,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,11 +315,7 @@ impl HashIndexedAnyContainer for Dict { } impl Print for Dict { - fn print<'guard>( - &self, - _guard: &'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[...]") } @@ -331,148 +324,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 = (); + 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 lookup = dict.lookup(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_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 lookup = dict.lookup(mem, key); + let key = mem.lookup_sym("foo"); - match lookup { - Ok(_) => panic!("Key should not have been found!"), - Err(e) => assert!(*e.error_kind() == ErrorKind::KeyError), - } + let lookup = dict.lookup(mem, key); - 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 = (); - - 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 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 +421,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 = (); - - fn run( - &self, - mem: &MutatorView, - _input: Self::Input, - ) -> Result { - let dict = Dict::with_capacity(mem, 100)?; - - for num in 0..50 { - let key_name = format!("foo_{}", num); - let key = mem.lookup_sym(&key_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); + 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)?; - } + dict.assoc(mem, key, val)?; + } - 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); - assert!(dict.exists(mem, key)?); - - 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 +494,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/error.rs b/interpreter/src/error.rs index a30e64c..d4a8b6b 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 @@ -45,15 +45,12 @@ pub struct RuntimeError { impl RuntimeError { pub fn new(kind: ErrorKind) -> RuntimeError { - RuntimeError { - kind: kind, - pos: None, - } + RuntimeError { kind, pos: None } } pub fn with_pos(kind: ErrorKind, pos: SourcePos) -> RuntimeError { RuntimeError { - kind: kind, + kind, pos: Some(pos), } } @@ -69,12 +66,12 @@ 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); + println!("error: {self}"); println!("{:5}|{}", pos.line, line); println!("{:5}|{:width$}^", " ", " ", width = pos.column as usize); println!("{:5}|", " "); @@ -82,7 +79,7 @@ impl RuntimeError { } } } else { - println!("error: {}", self); + println!("error: {self}"); } } } @@ -90,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!") @@ -112,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 99f5b49..09d760e 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,11 +104,7 @@ impl Function { impl Print for Function { /// Prints a string representation of the function - fn print<'guard>( - &self, - guard: &'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); @@ -119,16 +115,12 @@ 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}))"), } } /// Prints the disassembled bytecode - fn debug<'guard>( - &self, - guard: &'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) @@ -171,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, @@ -236,11 +228,7 @@ impl Partial { impl Print for Partial { /// Prints a string representation of the Partial object - fn print<'guard>( - &self, - guard: &'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); @@ -253,26 +241,14 @@ 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}))"), } } /// Prints the associated function's disassembled bytecode - fn debug<'guard>( - &self, - guard: &'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) } } - -/// 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 b3591e0..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, }; @@ -115,16 +115,16 @@ impl AllocHeader for ObjectHeader { mark, size_class, type_id: TypeList::ArrayBackingBytes, - size_bytes: size as u32, + size_bytes: size, } } - 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/lexer.rs b/interpreter/src/lexer.rs index d6ccadd..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(); @@ -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 057136f..4bc336a 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 stickyimmix; use std::fs::File; use std::io; @@ -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 { @@ -57,68 +54,10 @@ fn load_file(filename: &str) -> Result { } /// Read and evaluate an entire file -fn read_file(filename: &str) -> Result<(), RuntimeError> { - let _contents = load_file(&filename)?; - - // TODO - - Ok(()) -} - -/// 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)?; - } +fn read_file(filename: &str) -> Result { + let contents = load_file(filename)?; - // 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)); - } - } - } - } + Ok(contents) } fn main() { @@ -135,13 +74,16 @@ 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 } else { // otherwise begin a repl - read_print_loop().unwrap_or_else(|err| { - eprintln!("Terminated: {}", 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..02fadc2 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/memory.rs @@ -2,11 +2,11 @@ /// /// 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}; -use crate::pointerops::ScopedRef; +use crate::pointerops::AsScopedRef; use crate::safeptr::{MutatorScope, ScopedPtr, TaggedScopedPtr}; use crate::symbolmap::SymbolMap; use crate::taggedptr::{FatPtr, TaggedPtr}; @@ -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,11 +64,11 @@ impl<'memory> MutatorView<'memory> { /// Return a nil-initialized runtime-tagged pointer pub fn nil(&self) -> TaggedScopedPtr<'_> { - TaggedScopedPtr::new(self, TaggedPtr::nil()) + TaggedScopedPtr::nil(self) } } -impl<'memory> MutatorScope for MutatorView<'memory> {} +impl MutatorScope for MutatorView<'_> {} /// The heap implementation // ANCHOR: DefHeapStorage @@ -137,24 +137,15 @@ impl Memory { Memory { heap: Heap::new() } } - /// Run a mutator process - // ANCHOR: DefMemoryMutate - pub fn mutate(&self, m: &M, input: M::Input) -> Result { - let mut guard = MutatorView::new(self); - m.run(&mut guard, input) + /// 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: DefMemoryMutate -} - -/// Defines the interface a heap-mutating type must use to be allowed access to the heap -// 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: DefMemoryEnter } -// ANCHOR_END: DefMutator diff --git a/interpreter/src/number.rs b/interpreter/src/number.rs index 818740a..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<'guard>( - &self, - _guard: &'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 4d23ba1..86172bd 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,11 +66,7 @@ impl Pair { } impl Print for Pair { - fn print<'guard>( - &self, - guard: &'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))?; @@ -84,18 +80,14 @@ impl Print for Pair { let second = *tail.second.get(guard); match second { Value::Nil => (), - _ => write!(f, " . {}", second)?, + _ => write!(f, " . {second}")?, } write!(f, ")") } // In debug print, use dot notation - fn debug<'guard>( - &self, - guard: &'guard dyn MutatorScope, - f: &mut fmt::Formatter, - ) -> fmt::Result { + fn debug(&self, guard: &'_ dyn MutatorScope, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "({:?} . {:?})", @@ -225,27 +217,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 9d45640..b90ba21 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(()) @@ -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::*; @@ -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) } @@ -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/pointerops.rs b/interpreter/src/pointerops.rs index 3ac2046..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; @@ -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 { @@ -47,11 +47,11 @@ 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() } } 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:?}") } diff --git a/interpreter/src/rawarray.rs b/interpreter/src/rawarray.rs index 0e9e97e..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; @@ -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,10 +53,7 @@ impl RawArray { /// Return a RawArray of the given capacity number of bytes allocated // ANCHOR: DefRawArrayWithCapacity - pub fn with_capacity<'scope>( - mem: &'scope 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) @@ -75,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<'scope>( - &mut self, - mem: &'scope 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/repl.rs b/interpreter/src/repl.rs index 9028894..f470b28 100644 --- a/interpreter/src/repl.rs +++ b/interpreter/src/repl.rs @@ -1,88 +1,123 @@ 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::vm::Thread; +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) + }; - fn run(&self, mem: &MutatorView, line: String) -> Result<(), RuntimeError> { - let thread = self.main_thread.get(mem); + match (|mem, line| -> Result { + let value = parse(mem, line)?; + + if debug { + println!("# Debug\n## Input:\n```\n{line}\n```\n## Parsed:\n```\n{value:?}\n```"); + } + + let function = compile(mem, value)?; + + if debug { + println!("## Compiled:\n```\n{function:?}\n```"); + } - // 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{value:?}\n```\n"); + } - 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 value = thread.quick_vm_eval(mem, function)?; + 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 a299d98..0a1f345 100644 --- a/interpreter/src/safeptr.rs +++ b/interpreter/src/safeptr.rs @@ -2,10 +2,10 @@ 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::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> { @@ -49,25 +32,27 @@ 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))), + ) + } } } /// 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 { @@ -75,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) } @@ -93,6 +78,37 @@ 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. @@ -141,25 +157,37 @@ pub struct TaggedScopedPtr<'guard> { // ANCHOR_END: DefTaggedScopedPtr impl<'guard> TaggedScopedPtr<'guard> { - 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 value(&self) -> Value<'guard> { - self.value + 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 get_ptr(&self) -> TaggedPtr { - self.ptr + pub fn value(&self) -> Value<'guard> { + self.value } } /// 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>; @@ -169,13 +197,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) } @@ -207,13 +235,14 @@ 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), } } - pub fn new_ptr(source: TaggedPtr) -> TaggedCellPtr { + /// Construct a new TaggedCellPtr from another + pub fn new_copy(source: &TaggedCellPtr) -> TaggedCellPtr { TaggedCellPtr { - inner: Cell::new(source), + inner: Cell::new(source.inner.get()), } } @@ -221,7 +250,7 @@ impl TaggedCellPtr { /// 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 @@ -229,12 +258,17 @@ 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(&self, other: &TaggedCellPtr) { - self.inner.set(other.inner.get()); + 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(&self, _guard: &'_ dyn MutatorScope, dest: &TaggedCellPtr) { + dest.inner.set(self.inner.get()); } /// Return true if the pointer is nil @@ -248,12 +282,16 @@ 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 + // XXX: this should be unsafe + pub fn set_to_ptr(&self, _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(&self, _guard: &'_ dyn MutatorScope) -> TaggedPtr { self.inner.get() } } diff --git a/interpreter/src/symbol.rs b/interpreter/src/symbol.rs index c0a5400..d93b14a 100644 --- a/interpreter/src/symbol.rs +++ b/interpreter/src/symbol.rs @@ -46,18 +46,14 @@ impl Symbol { impl Print for Symbol { /// Safe because the lifetime of `MutatorScope` defines a safe-access window - fn print<'guard>( - &self, - guard: &'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)) } } // 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) } } 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 c62d19e..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; @@ -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, 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 +56,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 +77,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 +98,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. @@ -197,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 @@ -286,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/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/interpreter/src/vm.rs b/interpreter/src/vm.rs index 5c3ea35..913b7f6 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -12,7 +12,10 @@ 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; @@ -43,7 +46,7 @@ pub struct CallFrame { 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), @@ -53,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), @@ -66,9 +65,9 @@ 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) + format!("in {function}") } } @@ -113,10 +112,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 +125,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 +142,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(()) } @@ -177,18 +175,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 @@ -216,12 +214,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), }) } @@ -231,11 +229,11 @@ 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 - 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) { @@ -260,10 +258,10 @@ 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::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)) @@ -279,13 +277,13 @@ 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 - stack.access_slice(mem, |full_stack| { + // Establish a 256-register window into the stack from the stack base. + 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]; @@ -302,8 +300,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 +319,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 +351,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 +362,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 +370,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 +381,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 +426,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` @@ -446,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" ))) } } @@ -503,14 +496,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); - // 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: - 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(()) }; @@ -554,8 +544,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 @@ -666,30 +655,46 @@ 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 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 @@ -703,40 +708,86 @@ 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)?; } } } } 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 - 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, - code: ScopedPtr<'guard, ByteCode>, - max_instr: ArraySize, + function: ScopedPtr<'guard, Function>, + ) -> Result, RuntimeError> { + let mut status = EvalStatus::Pending; + + let frames = self.frames.scoped_ref(mem); + frames.push(mem, CallFrame::new_main(function))?; + + let code = function.code(mem); + let instr = self.instr.scoped_ref(mem); + instr.switch_frame(code, 0); + + while status == EvalStatus::Pending { + status = self.continue_exec(mem, 1024)?; + if let EvalStatus::Return(value) = status { + return Ok(value); + } + } + + Err(err_eval("Unexpected end of evaluation")) + } + + /// 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 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 + let frames = self.frames.scoped_ref(mem); + frames.push(mem, CallFrame::new_main(function))?; + + let code = function.code(mem); + let instr = self.instr.scoped_ref(mem); instr.switch_frame(code, 0); + self.continue_exec(mem, 1024) + } + + /// Execute up to max_instr more instructions + pub fn continue_exec<'guard>( + &self, + mem: &'guard MutatorView, + max_instr: ArraySize, + ) -> Result, RuntimeError> { 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) => { // 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| { @@ -760,29 +811,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); - - while status == EvalStatus::Pending { - status = self.vm_eval_stream(mem, code, 1024)?; - match status { - EvalStatus::Return(value) => return Ok(value), - _ => (), - } - } - - Err(err_eval("Unexpected end of evaluation")) - } } diff --git a/scratchpad/notes.md b/scratchpad/notes.md new file mode 100644 index 0000000..26050e1 --- /dev/null +++ b/scratchpad/notes.md @@ -0,0 +1,242 @@ +# 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 + +Conservative stack scanning. +- 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 + +Precise object scanning. OR could it be conservative? + +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() +``` + + +## Pinning Roots + +```rust +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/.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_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(); +} 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..8bd6440 --- /dev/null +++ b/scratchpad/test_02_pinned_roots/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test7" +version = "0.1.0" +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 new file mode 100644 index 0000000..9b2a6d9 --- /dev/null +++ b/scratchpad/test_02_pinned_roots/src/main.rs @@ -0,0 +1,381 @@ +// 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; +use std::hint::black_box; +use std::marker::PhantomData; +use std::mem::MaybeUninit; +use std::ops::Deref; +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 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 } + } + + fn addr(&self) -> usize { + self.inner.addr() + } + + fn debug(&self) { + println!("object {:x}", self.inner.addr()); + } + + unsafe fn as_ref(&self) -> &T { + &*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 HeapArray { + value: RefCell>>, +} + +impl HeapArray { + fn new() -> HeapArray { + HeapArray { + value: RefCell::new(Vec::new()), + } + } + + fn push(&self, object: &Root) { + self.value.borrow_mut().push(object.var); + } +} + +impl Trace for HeapArray { + fn trace(&self, objects: &mut Vec) { + for item in self.value.borrow().iter() { + objects.push(item.addr()) + } + } +} + +/////////////////////// +struct StackItem { + value: usize, +} + +impl StackItem { + fn new(value: usize) -> StackItem { + StackItem { value } + } +} + +/////////////////////// +struct HeapItem<'memory> { + mark: bool, + address: usize, + object: Box, +} + +impl<'memory> HeapItem<'memory> { + fn new(addr: usize, tobj: Box) -> HeapItem<'memory> { + HeapItem { + mark: false, + address: addr, + object: tobj, + } + } +} + +/////////////////////// +struct MemoryInner<'heap> { + objects: BTreeMap>, + scan: Vec, +} + +/////////////////////// +struct Memory<'heap> { + inner: RefCell>, + base: usize, +} + +impl<'heap> Memory<'heap> { + fn new() -> Memory<'heap> { + let inner = MemoryInner::<'heap> { + objects: BTreeMap::new(), + scan: Vec::new(), + }; + Memory { + inner: RefCell::new(inner), + base: 0xbeefbabe, + } + } + + 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(); + + let tobj: Box = obj; + + // put Trace trait object into heap object list + let gc_ref = HeapItem::new(addr, tobj); + self.inner.borrow_mut().objects.insert(addr, gc_ref); + + println!("(alloc) {:x}", addr); + 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()) }; + 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) }; + + let stack_scan = &mut self.inner.borrow_mut().scan; + + for stack_item in slice { + // if *stack_item != 0 { + // println!("[stack] {:x}", *stack_item); + // } + stack_scan.push(StackItem::new(*stack_item)); + } + + black_box(&context); + } + + 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 scan.drain(..) { + let possible_address = item.value; + + if let Some(_) = inner.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) = inner.objects.get_mut(&heap_address) { + heap_item.mark = true; + heap_item.object.trace(&mut heap_scan); + } + } + } + } + + 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; + inner.objects.insert(heap_item.address, heap_item); + } else { + println!(" DROP {:x}", heap_item.address); + drop(heap_item.object); + } + }); + } + + fn gc(&self) { + println!(""); + self.scan(); + self.mark(); + self.collect(); + } + + fn enter<'mutator, F>(&'mutator self, mutant: F) + where + F: FnOnce(&MutatorView<'heap, 'mutator>), + { + let delegate = MutatorView::new(self); + mutant(&delegate); + } +} + +impl<'heap> Drop for Memory<'heap> { + fn drop(&mut self) { + 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); + drop(heap_item.object); + }); + } +} + +/////////////////////// +struct MutatorView<'heap, 'mutator> { + mem: &'mutator Memory<'heap>, +} + +impl<'heap, 'mutator> MutatorView<'heap, 'mutator> { + fn new(mem: &'mutator Memory<'heap>) -> Self { + MutatorView { mem } + } + + fn alloc<'mem, T: Trace + 'heap>(&'mem self, value: T) -> Root<'mem, T> { + self.mem.alloc(value) + } + + fn gc(&self) { + self.mem.gc(); + } +} + +/////////////////////// +struct Root<'root, T: Trace> { + var: Gc, + p: PhantomData<&'root T>, +} + +impl<'root, T: Trace> Root<'root, T> { + fn new(var: Gc) -> Root<'root, T> { + Root { + var, + p: PhantomData, + } + } + + fn debug(&self) { + self.var.debug(); + } +} + +impl<'root, T: Trace> Deref for Root<'root, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { self.var.as_ref() } + } +} + +/////////////////////// +fn test_do_some_stuff(mem: &MutatorView) { + let 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 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")); + // bar.debug(); + } + mem.gc(); + + test_do_some_stuff(mem); + + let bar = mem.alloc(HeapString::from("barbell")); + bar.debug(); + + mem.gc(); + + foo.debug(); + foo.print(); + + // escapees_inner.push(foo); + // escapees_outer.push(bar); + }); + } + + // for item in escapees.iter() { + // item.print(); + // } +} 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); + } +}