commit 92fc7ff44b9130c5c72786d64ff6a2c5d9371968 Author: Michael Sippel Date: Sat May 4 18:11:13 2024 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..119a79a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +./target +*~ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1e60a06 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "tisc" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..81da7d0 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# tisc: Tiny Stack Computer + +Minimalistic stack-based VM with Rust interfaces to +build & execute programs. diff --git a/src/assembler.rs b/src/assembler.rs new file mode 100644 index 0000000..d03d58f --- /dev/null +++ b/src/assembler.rs @@ -0,0 +1,58 @@ +use { + crate::{VM_Word, VM_Instruction} +}; + +pub struct Assembler { + instructions: Vec< VM_Word > +} + +impl Assembler { + pub fn new() -> Self { + Assembler { + instructions: Vec::new() + } + } + + pub fn build(mut self) -> Vec< VM_Word > { + self.instructions.push( VM_Instruction::Ret as VM_Word ); + self.instructions + } + + pub fn lit(mut self, w: VM_Word) -> Assembler { + self.instructions.push( VM_Instruction::Lit as VM_Word ); + self.instructions.push( w ); + self + } + + pub fn litf(mut self, wf: f64) -> Assembler { + let w : VM_Word = unsafe{ std::mem::transmute(wf) }; + self.instructions.push( VM_Instruction::Lit as VM_Word ); + self.instructions.push( w ); + self + } + + pub fn instruction(mut self, i: VM_Instruction) -> Assembler { + self.instructions.push( i as VM_Word ); + self + } + + pub fn call(mut self, addr: VM_Word) -> Assembler { + self.instructions.push(VM_Instruction::Call as VM_Word); + self.instructions.push(addr); + self + } + + pub fn branch(mut self, mut if_branch: Assembler, mut else_branch: Assembler) -> Assembler { + self.instructions.push(VM_Instruction::Branch as VM_Word ); + self.instructions.push( if_branch.instructions.len() as VM_Word + 2); + + self.instructions.append( &mut if_branch.instructions ); + self.instructions.push( VM_Instruction::Jmp as VM_Word ); + self.instructions.push( else_branch.instructions.len() as VM_Word); + + self.instructions.append( &mut else_branch.instructions ); + + self + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4f502b4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +//! Tiny Stack Computer +//! +//! Minimalistic stack-based VM with Rust interfaces to +//! build & execute programs. + +pub mod vm; +pub mod assembler; +pub mod linker; +pub mod test; + +pub use { + vm::{VM_Instruction, VM_Word, VM}, + assembler::Assembler, + linker::Linker +}; + diff --git a/src/linker.rs b/src/linker.rs new file mode 100644 index 0000000..985ac57 --- /dev/null +++ b/src/linker.rs @@ -0,0 +1,27 @@ +use std::collections::HashMap; + +pub struct Linker { + symbols: HashMap, + current_addr: crate::VM_Word, +} + +impl Linker { + pub fn new(start_addr: crate::VM_Word) -> Self { + Linker { + symbols: HashMap::new(), + current_addr: start_addr + } + } + + pub fn resolve_symbol(&self, name: &String) -> Option< crate::VM_Word > { + self.symbols.get(name).cloned() + } + + pub fn link(&mut self, vm: &mut crate::VM, symbol: String, bytecode: Vec< crate::VM_Word >) { + self.symbols.insert(symbol, self.current_addr); + for i in 0 .. bytecode.len() { + vm.memory[ self.current_addr as usize ] = bytecode[i]; + self.current_addr += 1; + } + } +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..2bfa155 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,70 @@ +use crate::{ + VM, Linker, Assembler +}; + +#[test] +fn test_vm() { + let mut vm = VM::new(0x1000); + let mut linker = Linker::new(0x100); + + linker.link(&mut vm, "@".into(), crate::Assembler::new() + .instruction( crate::VM_Instruction::Fetch ) + .build()); + linker.link(&mut vm, "!".into(), crate::Assembler::new() + .instruction( crate::VM_Instruction::Store ) + .build()); + linker.link(&mut vm, "addi".into(), crate::Assembler::new() + .instruction( crate::VM_Instruction::Add ) + .build()); + linker.link(&mut vm, "subi".into(), crate::Assembler::new() + .instruction( crate::VM_Instruction::BitwiseNot ) + .lit(1) + .instruction( crate::VM_Instruction::Add ) + .instruction( crate::VM_Instruction::Add ) + .build()); + + // declare variable 'x' at address 0x86 + linker.link(&mut vm, "x".into(), crate::Assembler::new() + .lit(0x86) + .build()); + + + /* + * compile & run the following program: + * + * x = 100 + 23; + * if( x != 123 ) { + * emit('*'); + * 333 + * } else { + * emit('+'); + * 666 + * } + */ + linker.link(&mut vm, "main".into(), crate::Assembler::new() + // x = 123 + .lit(100) + .lit(23) + .call( linker.resolve_symbol(&"addi".into()).expect("unknown symbol 'addi'") ) + .call( linker.resolve_symbol(&"x".into()).expect("unknown symbol 'x'") ) + .call( linker.resolve_symbol(&"!".into()).expect("unknown symbol '!'") ) + + // 200 - x + .lit(123) + .call( linker.resolve_symbol(&"x".into()).expect("unknown symbol 'x'") ) + .call( linker.resolve_symbol(&"@".into()).expect("unknown symbol '@'") ) + .call( linker.resolve_symbol(&"subi".into()).expect("unknown symbol 'subi'") ) + .branch( + crate::Assembler::new() + .lit(42).instruction(crate::VM_Instruction::Emit) + .lit(111), + crate::Assembler::new() + .lit(43).instruction(crate::VM_Instruction::Emit) + .lit(222) + ) + .build()); + + vm.execute( linker.resolve_symbol(&"main".into()).expect("unknown symbol 'main'") ); + assert_eq!( vm.data_stack, vec![ 222 ] ); + println!("\nvm.datastack = {:?}", vm.data_stack); +} diff --git a/src/vm.rs b/src/vm.rs new file mode 100644 index 0000000..bad2fe2 --- /dev/null +++ b/src/vm.rs @@ -0,0 +1,152 @@ +pub type VM_Word = i64; + +#[derive(Clone, Copy, Debug)] +#[repr(u64)] +pub enum VM_Instruction { + Nop, Jmp, Call, Branch, Ret, + Lit, Dup, Drop, Swap, Rot, + Fetch, Store, + Accept, Emit, + Add, //Sub, Mul, Div, Rem, + Addf,//Subf, Mulf, Divf + BitwiseNot, +} + +pub struct VM { + pub data_stack: Vec< VM_Word >, + pub call_stack: Vec< VM_Word >, + + pub inst_ptr: VM_Word, + pub memory: Vec< VM_Word >, +} + +impl VM { + pub fn new(memsize: usize) -> Self { + VM { + data_stack: Vec::new(), + call_stack: Vec::new(), + + inst_ptr: 0, + memory: vec![0; memsize] + } + } + + pub fn execute(&mut self, entry: VM_Word) { + self.inst_ptr = entry; + while self.execute_step() {} + } + + pub fn execute_step(&mut self) -> bool { + // fetch instruction from memory + let inst = unsafe { std::mem::transmute(self.memory[ self.inst_ptr as usize ]) }; + self.inst_ptr += 1; + + // decode instruction & load immediate if neccesary + let mut imm : VM_Word = 0; + match inst { + VM_Instruction::Jmp | + VM_Instruction::Call | + VM_Instruction::Branch | + VM_Instruction::Lit => { + imm = self.memory[ self.inst_ptr as usize ]; + self.inst_ptr += 1; + } + _ => {} + } + + // execute instruction + self.execute_instruction( inst, imm ) + } + + pub fn execute_instruction(&mut self, inst: VM_Instruction, imm: VM_Word) -> bool { + // eprintln!("execute {:?}, {}", inst, imm); + match inst { + VM_Instruction::Nop => {} + VM_Instruction::Lit => { + self.data_stack.push( imm ); + } + VM_Instruction::Jmp => { + self.inst_ptr += imm; + } + VM_Instruction::Call => { + self.call_stack.push( self.inst_ptr ); + self.inst_ptr = imm; + } + VM_Instruction::Branch => { + if self.data_stack.pop().expect("branch expects val") == 0 { + //self.call_stack.push( self.inst_ptr ); + self.inst_ptr += imm; + } + } + VM_Instruction::Ret => { + if let Some(target) = self.call_stack.pop() { + self.inst_ptr = target; + } else { + return false; + } + } + VM_Instruction::Fetch => { + let addr = self.data_stack.pop().expect("fetch requries addr"); + let val = self.memory[ addr as usize ]; + self.data_stack.push(val); + } + VM_Instruction::Store => { + let addr = self.data_stack.pop().expect("store requires addr"); + let val = self.data_stack.pop().expect("store requires val"); + self.memory[ addr as usize ] = val; + } + VM_Instruction::Accept => { + // todo: read word from stdin + self.data_stack.push(0); + } + VM_Instruction::Emit => { + let val = char::from(self.data_stack.pop().expect("emit expects value") as u8); + print!("{}", val); + } + VM_Instruction::Dup => { + self.data_stack.push( *self.data_stack.last().expect("dup expect val") ); + } + VM_Instruction::Drop => { + self.data_stack.pop().expect("drop expects value"); + } + VM_Instruction::Swap => { + let a = self.data_stack.pop().expect("swap expects value"); + let b = self.data_stack.pop().expect("swap expects value"); + self.data_stack.push(a); + self.data_stack.push(b); + } + VM_Instruction::Rot => { + let a = self.data_stack.pop().expect("rot expects value"); + let b = self.data_stack.pop().expect("rot expects value"); + let c = self.data_stack.pop().expect("rot expects value"); + self.data_stack.push(a); + self.data_stack.push(c); + self.data_stack.push(b); + } + VM_Instruction::Add => { + let a = self.data_stack.pop().expect("add expects value"); + let b = self.data_stack.pop().expect("add expects value"); + self.data_stack.push( a + b ); + } + VM_Instruction::Addf => { + let a = self.data_stack.pop().expect("addf expects value"); + let af : f64 = unsafe{ std::mem::transmute(a) }; + + let b = self.data_stack.pop().expect("addf expects value"); + let bf : f64 = unsafe{ std::mem::transmute(a) }; + + let cf = af + bf; + let c : i64 = unsafe{ std::mem::transmute(cf) }; + + self.data_stack.push(c); + } + VM_Instruction::BitwiseNot => { + let a = self.data_stack.pop().expect("bitwise not expects value"); + self.data_stack.push( !a ); + } + } + + true + } +} +