diff --git a/src/lib.rs b/src/lib.rs index 69d2379..bb88958 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,6 @@ // You should have received a copy of the GNU General Public License along with // this program. If not, see . // -use smallvec::SmallVec; -use std::io::{Read, Write}; #[cfg(feature = "asm")] pub mod asm; @@ -21,332 +19,6 @@ pub mod conv; pub mod ops; pub mod reg; -use ops::Operation; -use reg::{Page, Register}; +mod universal_machine; -const SMALLVEC_SIZE: usize = 24; - -/// Lossless conversion to `usize`. -/// -/// This should only be implemented on types which can be losslessly -/// cast to a `usize`. -trait IntoIndex: Sized + Copy { - fn into_index(self) -> usize; -} - -macro_rules! impl_into_index { - ($t:ty) => { - impl IntoIndex for $t { - fn into_index(self) -> usize { - self as usize - } - } - }; -} - -#[cfg(target_pointer_width = "16")] -compile_error!("16 bit architectures are unsupported"); - -// usize *may* be 16 bits, so only implement if it is 32 or 64 bits. -#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] -impl_into_index!(u32); - -#[derive(Default)] -pub struct Um<'a> { - pub program_counter: u32, - registers: Page, - /// Program memory, modelled as a `Vec` of `SmallVec`. - /// - /// Memory allocations greater than `SMALLVEC_SIZE` will incur a memory - /// indirection penalty for every memory access within that block. - memory: Vec>, - free_blocks: Vec, - /// Partially decoded operations cache. - ops: Vec, - stdin: Option<&'a mut dyn Read>, - stdout: Option<&'a mut dyn Write>, -} - -impl<'a> Um<'a> { - /// Initialise a Universal Machine with the specified program scroll. - pub fn new(program: Vec) -> Self { - let ops = ops::decode(&program); - Self { - memory: vec![program.into()], - ops, - ..Default::default() - } - } - - /// Sets the output for the universal machine. - pub fn stdout(mut self, stdout: &'a mut T) -> Self { - self.stdout.replace(stdout); - self - } - - /// Sets the input for the universal machine. - pub fn stdin(mut self, stdin: &'a mut T) -> Self { - self.stdin.replace(stdin); - self - } - - /// Begins the spin-cycle of the universal machine. - #[inline(never)] - pub fn run(mut self) -> Self { - loop { - // println!( - // "{:?}, pc: {:08x}, r: {:08x?}", - // self.ops[self.program_counter as usize], self.program_counter, self.registers - // ); - match self.ops[self.program_counter as usize] { - Operation::ConditionalMove { a, b, c } => self.conditional_move(a, b, c), - Operation::ArrayIndex { a, b, c } => self.array_index(a, b, c), - Operation::ArrayAmendment { a, b, c } => self.array_amendment(a, b, c), - Operation::Addition { a, b, c } => self.addition(a, b, c), - Operation::Multiplication { a, b, c } => self.multiplication(a, b, c), - Operation::Division { a, b, c } => self.division(a, b, c), - Operation::NotAnd { a, b, c } => self.not_and(a, b, c), - Operation::Halt => break, - Operation::Allocation { b, c } => self.allocation(b, c), - Operation::Abandonment { c } => self.abandonment(c), - Operation::Output { c } => self.output(c), - Operation::Input { c } => self.input(c), - Operation::LoadProgram { b, c } => { - self.load_program(b, c); - continue; - } - Operation::Orthography { a, value } => self.orthography(a, value), - Operation::IllegalInstruction => self.illegal_instruction(), - } - self.program_counter += 1; - } - - self - } - - // Un-commenting step() slows down the sandmark benchmark by ~3-5 seconds, even - // though it has *no* interaction with the code path in Um::run(). - // - // /// Steps one instruction. - // #[inline(never)] - // pub fn step(&mut self) -> bool { - // match self.ops[self.program_counter as usize] { - // Operation::ConditionalMove { a, b, c } => self.conditional_move(a, b, c), - // Operation::ArrayIndex { a, b, c } => self.array_index(a, b, c), - // Operation::ArrayAmendment { a, b, c } => self.array_amendment(a, b, c), - // Operation::Addition { a, b, c } => self.addition(a, b, c), - // Operation::Multiplication { a, b, c } => self.multiplication(a, b, c), - // Operation::Division { a, b, c } => self.division(a, b, c), - // Operation::NotAnd { a, b, c } => self.not_and(a, b, c), - // Operation::Halt => return false, - // Operation::Allocation { b, c } => self.allocation(b, c), - // Operation::Abandonment { c } => self.abandonment(c), - // Operation::Output { c } => self.output(c), - // Operation::Input { c } => self.input(c), - // Operation::LoadProgram { b, c } => { - // self.load_program(b, c); - // return true; - // } - // Operation::Orthography { a, value } => self.orthography(a, value), - // Operation::IllegalInstruction => self.illegal_instruction(), - // } - // self.program_counter += 1; - // true - // } - - /// Loads the value from the specified register. - fn load_register(&self, register: Register) -> u32 { - self.registers[register] - } - - /// Saves a value to the specified register. - fn save_register(&mut self, register: Register, value: u32) { - self.registers[register] = value; - } - - fn conditional_move(&mut self, a: Register, b: Register, c: Register) { - if self.load_register(c) != 0 { - self.save_register(a, self.load_register(b)); - } - } - - fn array_index(&mut self, a: Register, b: Register, c: Register) { - let block = self.load_register(b); - let offset = self.load_register(c); - self.save_register(a, self.load_memory(block, offset)); - } - - fn array_amendment(&mut self, a: Register, b: Register, c: Register) { - let block = self.load_register(a); - let offset = self.load_register(b); - let value = self.load_register(c); - self.store_memory(block, offset, value); - } - - fn addition(&mut self, a: Register, b: Register, c: Register) { - self.save_register(a, self.load_register(b).wrapping_add(self.load_register(c))); - } - - fn multiplication(&mut self, a: Register, b: Register, c: Register) { - self.save_register(a, self.load_register(b).wrapping_mul(self.load_register(c))); - } - - fn division(&mut self, a: Register, b: Register, c: Register) { - self.save_register(a, self.load_register(b).wrapping_div(self.load_register(c))); - } - - fn not_and(&mut self, a: Register, b: Register, c: Register) { - self.save_register(a, !(self.load_register(b) & self.load_register(c))); - } - - fn allocation(&mut self, b: Register, c: Register) { - let length = self.load_register(c); - let index = self.allocate_memory(length); - self.save_register(b, index); - } - - fn abandonment(&mut self, c: Register) { - let block = self.load_register(c); - self.free_memory(block); - } - - fn output(&mut self, c: Register) { - let value = self.load_register(c); - if let Some(stdout) = self.stdout.as_mut() { - let buffer = [(value & 0xff) as u8]; - stdout.write_all(&buffer).unwrap(); - } - } - - fn input(&mut self, c: Register) { - if let Some(stdin) = self.stdin.as_mut() { - let mut buffer = vec![0]; - match stdin.read_exact(&mut buffer) { - Ok(()) => self.save_register(c, buffer[0] as u32), - Err(_) => self.save_register(c, u32::MAX), - } - } else { - self.save_register(c, u32::MAX); - } - } - - fn load_program(&mut self, b: Register, c: Register) { - let block = self.load_register(b); - - // Source array is always copied to array[0], but there - // is no point copying array[0] to array[0]. - if block != 0 { - let duplicated = self.duplicate_memory(block); - let ops = ops::decode(duplicated); - self.ops = ops; - } - - self.program_counter = self.load_register(c); - } - - fn orthography(&mut self, a: Register, value: u32) { - self.save_register(a, value); - } - - #[cold] - #[inline(never)] - fn illegal_instruction(&self) -> ! { - panic!( - "illegal instruction: {:08x}, pc: {:08x}, r: {:08x?}", - self.memory[0][self.program_counter.into_index()], - self.program_counter, - self.registers - ) - } - - fn load_memory(&self, block: u32, offset: u32) -> u32 { - let block = block.into_index(); - let offset = offset.into_index(); - assert!(block < self.memory.len() && offset < self.memory[block].len()); - self.memory[block][offset] - } - - fn store_memory(&mut self, block: u32, offset: u32, value: u32) { - let block = block.into_index(); - let offset = offset.into_index(); - assert!(block < self.memory.len() && offset < self.memory[block].len()); - self.memory[block][offset] = value - } - - /// Duplicates a block of memory. - /// - /// The block is copied to the first block of memory. - fn duplicate_memory(&mut self, block: u32) -> &[u32] { - let block = block.into_index(); - assert!(block < self.memory.len()); - self.memory[0] = self.memory[block].clone(); - &self.memory[0] - } - - /// Allocates a block of memory of the specified length. - fn allocate_memory(&mut self, length: u32) -> u32 { - if let Some(index) = self.free_blocks.pop() { - self.memory[index.into_index()] = Self::new_block(length.into_index()); - index - } else { - self.memory.push(Self::new_block(length.into_index())); - (self.memory.len() - 1) as u32 - } - } - - /// Frees a block of memory. - fn free_memory(&mut self, block: u32) { - assert!(block.into_index() < self.memory.len()); - self.free_blocks.push(block); - self.memory[block.into_index()] = Self::new_block(0); - } - - /// Creates a new block of memory. - /// - /// The block is initialised with `len` zeroes. - fn new_block(len: usize) -> SmallVec<[u32; SMALLVEC_SIZE]> { - smallvec::smallvec![0; len] - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[should_panic] - fn empty_program() { - Um::new(vec![]).run(); - } - - #[test] - fn just_halt() { - Um::new(vec![0x70000000]).run(); - } - - #[test] - #[cfg(feature = "asm")] - fn hello_world() { - let program = asm::assemble(include_str!("../files/hello-world.asm")); - let mut buffer = Vec::new(); - Um::new(program).stdout(&mut buffer).run(); - assert_eq!(&buffer, b"Hello, world!\n"); - } - - #[test] - #[cfg(feature = "asm")] - fn cat() { - let program = asm::assemble(include_str!("../files/cat.asm")); - let input = include_bytes!("lib.rs"); - - let mut reader = std::io::Cursor::new(input); - let mut buffer = Vec::new(); - - Um::new(program) - .stdin(&mut reader) - .stdout(&mut buffer) - .run(); - - assert_eq!(&buffer, &input); - } -} +pub use universal_machine::Um; diff --git a/src/bin/um.rs b/src/main.rs similarity index 97% rename from src/bin/um.rs rename to src/main.rs index e08055d..34b7e8c 100644 --- a/src/bin/um.rs +++ b/src/main.rs @@ -12,8 +12,12 @@ // You should have received a copy of the GNU General Public License along with // this program. If not, see . // +mod ops; +mod reg; +mod universal_machine; + use std::{path::Path, time::Instant}; -use um::Um; +use universal_machine::Um; fn main() { let mut program = Vec::new(); diff --git a/src/universal_machine.rs b/src/universal_machine.rs new file mode 100644 index 0000000..e6334be --- /dev/null +++ b/src/universal_machine.rs @@ -0,0 +1,334 @@ +use std::io::Read; +use std::io::Write; + +use smallvec::SmallVec; + +use crate::ops::Operation; +use crate::reg::{Page, Register}; + +pub(crate) const SMALLVEC_SIZE: usize = 24; + +/// Lossless conversion to `usize`. +/// +/// This should only be implemented on types which can be losslessly +/// cast to a `usize`. +pub(crate) trait IntoIndex: Sized + Copy { + fn into_index(self) -> usize; +} + +macro_rules! impl_into_index { + ($t:ty) => { + impl IntoIndex for $t { + fn into_index(self) -> usize { + self as usize + } + } + }; +} + +#[cfg(target_pointer_width = "16")] +compile_error!("16 bit architectures are unsupported"); + +// usize *may* be 16 bits, so only implement if it is 32 or 64 bits. +#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] +impl_into_index!(u32); + +#[derive(Default)] +pub struct Um<'a> { + pub program_counter: u32, + pub(crate) registers: Page, + /// Program memory, modelled as a `Vec` of `SmallVec`. + /// + /// Memory allocations greater than `SMALLVEC_SIZE` will incur a memory + /// indirection penalty for every memory access within that block. + pub(crate) memory: Vec>, + pub(crate) free_blocks: Vec, + /// Partially decoded operations cache. + pub(crate) ops: Vec, + pub(crate) stdin: Option<&'a mut dyn Read>, + pub(crate) stdout: Option<&'a mut dyn Write>, +} + +impl<'a> Um<'a> { + /// Initialise a Universal Machine with the specified program scroll. + pub fn new(program: Vec) -> Self { + let ops = crate::ops::decode(&program); + Self { + memory: vec![program.into()], + ops, + ..Default::default() + } + } + + /// Sets the output for the universal machine. + pub fn stdout(mut self, stdout: &'a mut T) -> Self { + self.stdout.replace(stdout); + self + } + + /// Sets the input for the universal machine. + pub fn stdin(mut self, stdin: &'a mut T) -> Self { + self.stdin.replace(stdin); + self + } + + /// Begins the spin-cycle of the universal machine. + #[inline(never)] + pub fn run(mut self) -> Self { + loop { + // println!( + // "{:?}, pc: {:08x}, r: {:08x?}", + // self.ops[self.program_counter as usize], self.program_counter, self.registers + // ); + match self.ops[self.program_counter as usize] { + Operation::ConditionalMove { a, b, c } => self.conditional_move(a, b, c), + Operation::ArrayIndex { a, b, c } => self.array_index(a, b, c), + Operation::ArrayAmendment { a, b, c } => self.array_amendment(a, b, c), + Operation::Addition { a, b, c } => self.addition(a, b, c), + Operation::Multiplication { a, b, c } => self.multiplication(a, b, c), + Operation::Division { a, b, c } => self.division(a, b, c), + Operation::NotAnd { a, b, c } => self.not_and(a, b, c), + Operation::Halt => break, + Operation::Allocation { b, c } => self.allocation(b, c), + Operation::Abandonment { c } => self.abandonment(c), + Operation::Output { c } => self.output(c), + Operation::Input { c } => self.input(c), + Operation::LoadProgram { b, c } => { + self.load_program(b, c); + continue; + } + Operation::Orthography { a, value } => self.orthography(a, value), + Operation::IllegalInstruction => self.illegal_instruction(), + } + self.program_counter += 1; + } + + self + } + + // Un-commenting step() slows down the sandmark benchmark by ~3-5 seconds, even + // though it has *no* interaction with the code path in Um::run(). + // + // /// Steps one instruction. + // #[inline(never)] + // pub fn step(&mut self) -> bool { + // match self.ops[self.program_counter as usize] { + // Operation::ConditionalMove { a, b, c } => self.conditional_move(a, b, c), + // Operation::ArrayIndex { a, b, c } => self.array_index(a, b, c), + // Operation::ArrayAmendment { a, b, c } => self.array_amendment(a, b, c), + // Operation::Addition { a, b, c } => self.addition(a, b, c), + // Operation::Multiplication { a, b, c } => self.multiplication(a, b, c), + // Operation::Division { a, b, c } => self.division(a, b, c), + // Operation::NotAnd { a, b, c } => self.not_and(a, b, c), + // Operation::Halt => return false, + // Operation::Allocation { b, c } => self.allocation(b, c), + // Operation::Abandonment { c } => self.abandonment(c), + // Operation::Output { c } => self.output(c), + // Operation::Input { c } => self.input(c), + // Operation::LoadProgram { b, c } => { + // self.load_program(b, c); + // return true; + // } + // Operation::Orthography { a, value } => self.orthography(a, value), + // Operation::IllegalInstruction => self.illegal_instruction(), + // } + // self.program_counter += 1; + // true + // } + + /// Loads the value from the specified register. + pub(crate) fn load_register(&self, register: Register) -> u32 { + self.registers[register] + } + + /// Saves a value to the specified register. + pub(crate) fn save_register(&mut self, register: Register, value: u32) { + self.registers[register] = value; + } + + pub(crate) fn conditional_move(&mut self, a: Register, b: Register, c: Register) { + if self.load_register(c) != 0 { + self.save_register(a, self.load_register(b)); + } + } + + pub(crate) fn array_index(&mut self, a: Register, b: Register, c: Register) { + let block = self.load_register(b); + let offset = self.load_register(c); + self.save_register(a, self.load_memory(block, offset)); + } + + pub(crate) fn array_amendment(&mut self, a: Register, b: Register, c: Register) { + let block = self.load_register(a); + let offset = self.load_register(b); + let value = self.load_register(c); + self.store_memory(block, offset, value); + } + + pub(crate) fn addition(&mut self, a: Register, b: Register, c: Register) { + self.save_register(a, self.load_register(b).wrapping_add(self.load_register(c))); + } + + pub(crate) fn multiplication(&mut self, a: Register, b: Register, c: Register) { + self.save_register(a, self.load_register(b).wrapping_mul(self.load_register(c))); + } + + pub(crate) fn division(&mut self, a: Register, b: Register, c: Register) { + self.save_register(a, self.load_register(b).wrapping_div(self.load_register(c))); + } + + pub(crate) fn not_and(&mut self, a: Register, b: Register, c: Register) { + self.save_register(a, !(self.load_register(b) & self.load_register(c))); + } + + pub(crate) fn allocation(&mut self, b: Register, c: Register) { + let length = self.load_register(c); + let index = self.allocate_memory(length); + self.save_register(b, index); + } + + pub(crate) fn abandonment(&mut self, c: Register) { + let block = self.load_register(c); + self.free_memory(block); + } + + pub(crate) fn output(&mut self, c: Register) { + let value = self.load_register(c); + if let Some(stdout) = self.stdout.as_mut() { + let buffer = [(value & 0xff) as u8]; + stdout.write_all(&buffer).unwrap(); + } + } + + pub(crate) fn input(&mut self, c: Register) { + if let Some(stdin) = self.stdin.as_mut() { + let mut buffer = vec![0]; + match stdin.read_exact(&mut buffer) { + Ok(()) => self.save_register(c, buffer[0] as u32), + Err(_) => self.save_register(c, u32::MAX), + } + } else { + self.save_register(c, u32::MAX); + } + } + + pub(crate) fn load_program(&mut self, b: Register, c: Register) { + let block = self.load_register(b); + + // Source array is always copied to array[0], but there + // is no point copying array[0] to array[0]. + if block != 0 { + let duplicated = self.duplicate_memory(block); + let ops = crate::ops::decode(duplicated); + self.ops = ops; + } + + self.program_counter = self.load_register(c); + } + + pub(crate) fn orthography(&mut self, a: Register, value: u32) { + self.save_register(a, value); + } + + #[cold] + #[inline(never)] + pub(crate) fn illegal_instruction(&self) -> ! { + panic!( + "illegal instruction: {:08x}, pc: {:08x}, r: {:08x?}", + self.memory[0][self.program_counter.into_index()], + self.program_counter, + self.registers + ) + } + + pub(crate) fn load_memory(&self, block: u32, offset: u32) -> u32 { + let block = block.into_index(); + let offset = offset.into_index(); + assert!(block < self.memory.len() && offset < self.memory[block].len()); + self.memory[block][offset] + } + + pub(crate) fn store_memory(&mut self, block: u32, offset: u32, value: u32) { + let block = block.into_index(); + let offset = offset.into_index(); + assert!(block < self.memory.len() && offset < self.memory[block].len()); + self.memory[block][offset] = value + } + + /// Duplicates a block of memory. + /// + /// The block is copied to the first block of memory. + pub(crate) fn duplicate_memory(&mut self, block: u32) -> &[u32] { + let block = block.into_index(); + assert!(block < self.memory.len()); + self.memory[0] = self.memory[block].clone(); + &self.memory[0] + } + + /// Allocates a block of memory of the specified length. + pub(crate) fn allocate_memory(&mut self, length: u32) -> u32 { + if let Some(index) = self.free_blocks.pop() { + self.memory[index.into_index()] = Self::new_block(length.into_index()); + index + } else { + self.memory.push(Self::new_block(length.into_index())); + (self.memory.len() - 1) as u32 + } + } + + /// Frees a block of memory. + pub(crate) fn free_memory(&mut self, block: u32) { + assert!(block.into_index() < self.memory.len()); + self.free_blocks.push(block); + self.memory[block.into_index()] = Self::new_block(0); + } + + /// Creates a new block of memory. + /// + /// The block is initialised with `len` zeroes. + pub(crate) fn new_block(len: usize) -> SmallVec<[u32; SMALLVEC_SIZE]> { + smallvec::smallvec![0; len] + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + #[test] + #[should_panic] + fn empty_program() { + Um::new(vec![]).run(); + } + + #[test] + fn just_halt() { + Um::new(vec![0x70000000]).run(); + } + + #[test] + #[cfg(feature = "asm")] + fn hello_world() { + let program = asm::assemble(include_str!("../files/hello-world.asm")); + let mut buffer = Vec::new(); + Um::new(program).stdout(&mut buffer).run(); + assert_eq!(&buffer, b"Hello, world!\n"); + } + + #[test] + #[cfg(feature = "asm")] + fn cat() { + let program = asm::assemble(include_str!("../files/cat.asm")); + let input = include_bytes!("lib.rs"); + + let mut reader = std::io::Cursor::new(input); + let mut buffer = Vec::new(); + + Um::new(program) + .stdin(&mut reader) + .stdout(&mut buffer) + .run(); + + assert_eq!(&buffer, &input); + } +}