diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index b0a1889..b812f2c 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -16,13 +16,13 @@ jobs: toolchain: nightly components: clippy - - run: cargo run -- -s examples/hello.cirru - - run: cargo run -- -s examples/sum.cirru - - run: cargo run -- -s examples/assert.cirru - - run: cargo run -- -s examples/nested.cirru - - run: cargo run -- -s examples/named.cirru - - run: cargo run -- examples/recur.cirru - - run: cargo run -- --emit-binary target/a.calx examples/named.cirru && cargo run -- --eval-binary target/a.calx + - run: cargo run -- -s demos/hello.cirru + - run: cargo run -- -s demos/sum.cirru + - run: cargo run -- -s demos/assert.cirru + - run: cargo run -- -s demos/nested.cirru + - run: cargo run -- -s demos/named.cirru + - run: cargo run -- demos/recur.cirru + - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx # - run: cargo test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d71f498..79802d8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,13 +24,13 @@ jobs: toolchain: nightly components: clippy - - run: cargo run -- -s examples/hello.cirru - - run: cargo run -- -s examples/sum.cirru - - run: cargo run -- -s examples/assert.cirru - - run: cargo run -- -s examples/nested.cirru - - run: cargo run -- -s examples/named.cirru - - run: cargo run -- examples/recur.cirru - - run: cargo run -- --emit-binary target/a.calx examples/named.cirru && cargo run -- --eval-binary target/a.calx + - run: cargo run -- -s demos/hello.cirru + - run: cargo run -- -s demos/sum.cirru + - run: cargo run -- -s demos/assert.cirru + - run: cargo run -- -s demos/nested.cirru + - run: cargo run -- -s demos/named.cirru + - run: cargo run -- demos/recur.cirru + - run: cargo run -- --emit-binary target/a.calx demos/named.cirru && cargo run -- --eval-binary target/a.calx - uses: giraffate/clippy-action@v1 with: diff --git a/Cargo.toml b/Cargo.toml index 0f75a7b..ad855e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calx_vm" -version = "0.1.5" +version = "0.1.6" authors = ["jiyinyiyong "] edition = "2021" license = "MIT" @@ -9,9 +9,7 @@ homepage = "https://github.com/calcit-lang/calx-vm" documentation = "https://docs.rs/crate/calx_vm/" repository = "https://github.com/calcit-lang/calx-vm.rs" readme = "README.md" -exclude = [ - "examples/*", -] +exclude = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index 55682eb..57bcd2f 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ Calx Binary Edition `0.1`: | `return` | | TODO | | `fn $types $body` | | Global definition | | `assert` | `quit(1)` if not `true` | for testing | +| `inspect | println inspection information | | For `$types`, it can be `($t1 $t2 -> $t3 $t4)`, where supported types are: @@ -146,6 +147,17 @@ For `$types`, it can be `($t1 $t2 -> $t3 $t4)`, where supported types are: - list _TODO_ - link _TODO_ +### Preprocess + +Before Calx running the instructions, Calx performs preprocessing to them. There are several tasks: + +- `block` and `loop` are expanded since there are `block-end` instructions +- `br` and `br-if` also expanded to `jmp` and `jmp-if` instructions, internally +- stack size is checked to ensure it's consistent among branches, and tidied up at function end +- local variables are renamed to indexes + +these steps simplies debugging, although it's sure that's good features. + ### License MIT diff --git a/examples/assert.cirru b/demos/assert.cirru similarity index 100% rename from examples/assert.cirru rename to demos/assert.cirru diff --git a/demos/fibonacci.cirru b/demos/fibonacci.cirru new file mode 100644 index 0000000..9d6603f --- /dev/null +++ b/demos/fibonacci.cirru @@ -0,0 +1,29 @@ + +fn main () + call fibo + const 34 + echo + +fn fibo (($x i64) -> i64) + block (->) + local.get $x + const 3 + i.lt + br-if 0 + + local.get $x + const -1 + i.add + call fibo + + local.get $x + const -2 + i.add + call fibo + + i.add + + return + + const 1 + return diff --git a/examples/hello.cirru b/demos/hello.cirru similarity index 100% rename from examples/hello.cirru rename to demos/hello.cirru diff --git a/examples/named.cirru b/demos/named.cirru similarity index 91% rename from examples/named.cirru rename to demos/named.cirru index 2787a24..09b99da 100644 --- a/examples/named.cirru +++ b/demos/named.cirru @@ -9,7 +9,11 @@ fn f-add (($a i64) ($b i64) -> i64) local.get $b local.get $c dup + + inspect + echo + return fn main () const 1 diff --git a/examples/nested.cirru b/demos/nested.cirru similarity index 100% rename from examples/nested.cirru rename to demos/nested.cirru diff --git a/examples/recur.cirru b/demos/recur.cirru similarity index 100% rename from examples/recur.cirru rename to demos/recur.cirru diff --git a/examples/sum.cirru b/demos/sum.cirru similarity index 77% rename from examples/sum.cirru rename to demos/sum.cirru index 36f8629..b98c99e 100644 --- a/examples/sum.cirru +++ b/demos/sum.cirru @@ -13,43 +13,48 @@ fn blocks (-> i64) block (->) (br 0) (const 1.) (const 2.) (neg) (add) (echo) , (const "|demo of string") (echo) const 0 + return fn sum (-> i64) - local.new - block (-> i64) - const 0 - local.set 0 - const 0 - loop (i64) + local.new $sum + const 0 + local.set $sum + + const 0 + block (i64 -> i64) + loop (i64 -> i64) ;; "echo inspect i" ;; const |inspect + ;; local.get $c ;; echo - ;; dup - ;; echo i - ;; "i += 1" + ;; "c += 1" const 1 i.add - ;; "acc += i" dup - local.get 0 + + ;; "acc += c" + local.get $sum i.add - local.set 0 + local.set $sum - ;; "if >= 1M" dup + + ;; "if >= 1M" const 1000000 i.ge br-if 1 - br 0 + drop - const "|check sum" + echo - local.get 0 + const "|check sum" + local.get $sum dup echo + return fn echos (-> i64) const "|loading program" diff --git a/examples/fibo.rs b/examples/fibo.rs new file mode 100644 index 0000000..f261e51 --- /dev/null +++ b/examples/fibo.rs @@ -0,0 +1,14 @@ +//! fibonacci for comparisom +//! still orders in magnitude faster than Calx + +fn fibo(n: i64) -> i64 { + if n < 3 { + 1 + } else { + fibo(n - 1) + fibo(n - 2) + } +} + +fn main() { + println!("Result {}", fibo(40)) +} diff --git a/src/bin/calx.rs b/src/bin/calx.rs index 3f5e128..f571e67 100644 --- a/src/bin/calx.rs +++ b/src/bin/calx.rs @@ -11,7 +11,7 @@ use calx_vm::{log_calx_value, parse_function, Calx, CalxBinaryProgram, CalxFunc, #[derive(Parser, Debug)] #[command(name = "Calx VM")] #[command(author = "Jon Chen ")] -#[command(version = "0.1.4")] +#[command(version = "0.1.6")] #[command(about = "A toy VM", long_about = None)] struct Args { #[arg(short, long, value_name = "SHOW_CODE")] @@ -20,6 +20,8 @@ struct Args { disable_pre: bool, #[arg(short, long, value_name = "EMIT_BINARY")] emit_binary: Option, + #[arg(short, long, value_name = "VERBOSE")] + verbose: bool, #[arg(long, value_name = "EVAL_BINARY")] eval_binary: bool, #[arg(value_name = "SOURCE")] @@ -38,7 +40,7 @@ fn main() -> Result<(), String> { let mut fns: Vec = vec![]; if eval_binary { - let code = fs::read(source).expect("read binar from source file"); + let code = fs::read(source).expect("read binary from source file"); let program: CalxBinaryProgram = bincode::decode_from_slice(&code, bincode::config::standard()) .expect("decode functions from binary") .0; @@ -66,26 +68,15 @@ fn main() -> Result<(), String> { } if emit_binary.is_some() { - let mut slice = [0u8; 10000]; let program = CalxBinaryProgram { edition: CALX_BINARY_EDITION.to_string(), fns, }; - let length = match bincode::encode_into_slice(&program, &mut slice, bincode::config::standard()) { - Ok(l) => { - println!("encoded binary length: {}", l); - l - } - Err(e) => panic!("failed on default length of 10000: {}", e), - }; - let slice = &slice[..length]; + let buf = bincode::encode_to_vec(program, bincode::config::standard()).map_err(|e| e.to_string())?; let target_file = &emit_binary.unwrap(); - match fs::write(target_file, slice) { - Ok(_) => println!("wrote binary to {}", target_file), - Err(e) => panic!("failed to write binary to {}: {}", target_file, e), - }; + fs::write(target_file, buf).map_err(|e| e.to_string())?; + println!("wrote binary to {}", target_file); return Ok(()); - // println!("Bytes written: {:?}", slice); } let mut imports: CalxImportsDict = HashMap::new(); @@ -103,7 +94,8 @@ fn main() -> Result<(), String> { let now = Instant::now(); if !disable_pre { - vm.preprocess()?; + println!("[calx] start preprocessing"); + vm.preprocess(args.verbose)?; } else { println!("Preprocess disabled.") } @@ -114,11 +106,12 @@ fn main() -> Result<(), String> { } } + println!("[calx] start running"); match vm.run(vec![Calx::I64(1)]) { Ok(ret) => { let elapsed = now.elapsed(); - println!("Took {:.3?}: {:?}", elapsed, ret); + println!("[calx] took {:.3?}: {:?}", elapsed, ret); Ok(()) } Err(e) => { diff --git a/src/parser.rs b/src/parser.rs index 68f10c0..6aa16c1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,6 +4,8 @@ mod locals; +use std::rc::Rc; + use lazy_static::lazy_static; use regex::Regex; @@ -61,11 +63,11 @@ pub fn parse_function(nodes: &[Cirru]) -> Result { } Ok(CalxFunc { - name: name.to_string(), - params_types, - ret_types, - local_names: locals_collector.locals, - instrs: body, + name: Rc::new(name.to_string()), + params_types: params_types.into(), + ret_types: Rc::new(ret_types), + local_names: Rc::new(locals_collector.locals), + instrs: Rc::new(body), }) } @@ -257,6 +259,7 @@ pub fn parse_instr(ptr_base: usize, node: &Cirru, collector: &mut LocalsCollecto Ok(vec![CalxInstr::Assert((*message).to_owned())]) } + "inspect" => Ok(vec![CalxInstr::Inspect]), _ => Err(format!("unknown instruction: {}", name)), }, } @@ -345,8 +348,8 @@ pub fn parse_block(ptr_base: usize, xs: &[Cirru], looped: bool, collector: &mut looped, from: ptr_base + 1, to: p, - params_types, - ret_types, + params_types: Rc::new(params_types), + ret_types: Rc::new(ret_types), }, ); Ok(chunk) diff --git a/src/primes.rs b/src/primes.rs index 4f0e60b..984c419 100644 --- a/src/primes.rs +++ b/src/primes.rs @@ -4,7 +4,7 @@ */ use bincode::{Decode, Encode}; -use std::fmt; +use std::{fmt, rc::Rc}; /// Simplied from Calcit, but trying to be basic and mutable #[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] @@ -73,21 +73,21 @@ impl fmt::Display for Calx { #[derive(Debug, Clone, PartialEq, PartialOrd, Encode, Decode)] pub struct CalxFunc { - pub name: String, - pub params_types: Vec, - pub ret_types: Vec, - pub instrs: Vec, - pub local_names: Vec, + pub name: Rc, + pub params_types: Rc>, + pub ret_types: Rc>, + pub instrs: Rc>, + pub local_names: Rc>, } impl fmt::Display for CalxFunc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CalxFunc {} (", self.name)?; - for p in &self.params_types { + for p in &*self.params_types { write!(f, "{:?} ", p)?; } f.write_str("-> ")?; - for p in &self.ret_types { + for p in &*self.ret_types { write!(f, "{:?} ", p)?; } f.write_str(")")?; @@ -161,8 +161,8 @@ pub enum CalxInstr { Jmp(usize), // internal JmpIf(usize), // internal Block { - params_types: Vec, - ret_types: Vec, + params_types: Rc>, + ret_types: Rc>, /// bool to indicate loop looped: bool, from: usize, @@ -182,6 +182,72 @@ pub enum CalxInstr { Return, /// TODO might also be a foreign function instead Assert(String), + /// inspecting stack + Inspect, +} + +impl CalxInstr { + /// notice that some of the instrs are special and need to handle manually + pub fn stack_arity(&self) -> (usize, usize) { + match self { + CalxInstr::LocalSet(_) => (1, 0), + CalxInstr::LocalTee(_) => (1, 1), // TODO need check + CalxInstr::LocalGet(_) => (0, 1), + CalxInstr::LocalNew => (0, 0), + CalxInstr::GlobalSet(_) => (1, 0), + CalxInstr::GlobalGet(_) => (0, 1), + CalxInstr::GlobalNew => (0, 0), + CalxInstr::Const(_) => (0, 1), + CalxInstr::Dup => (1, 2), + CalxInstr::Drop => (1, 0), + CalxInstr::IntAdd => (2, 1), + CalxInstr::IntMul => (2, 1), + CalxInstr::IntDiv => (2, 1), + CalxInstr::IntRem => (2, 1), + CalxInstr::IntNeg => (1, 1), + CalxInstr::IntShr => (2, 1), + CalxInstr::IntShl => (2, 1), + CalxInstr::IntEq => (2, 1), + CalxInstr::IntNe => (2, 1), + CalxInstr::IntLt => (2, 1), + CalxInstr::IntLe => (2, 1), + CalxInstr::IntGt => (2, 1), + CalxInstr::IntGe => (2, 1), + CalxInstr::Add => (2, 1), + CalxInstr::Mul => (2, 1), + CalxInstr::Div => (2, 1), + CalxInstr::Neg => (1, 1), + // string operations + // list operations + CalxInstr::NewList => (0, 1), + CalxInstr::ListGet => (2, 1), + CalxInstr::ListSet => (3, 0), + // Link + CalxInstr::NewLink => (0, 1), + // bool operations + CalxInstr::And => (2, 1), + CalxInstr::Or => (2, 1), + CalxInstr::Not => (1, 1), + // control stuctures + CalxInstr::Br(_) => (0, 0), + CalxInstr::BrIf(_) => (1, 0), + CalxInstr::Jmp(_) => (0, 0), + CalxInstr::JmpIf(_) => (1, 0), + CalxInstr::Block { .. } => (0, 0), + CalxInstr::BlockEnd(_) => (0, 0), + CalxInstr::Echo => (1, 0), + CalxInstr::Call(_) => (0, 0), // TODO + CalxInstr::ReturnCall(_) => (0, 0), // TODO + CalxInstr::CallImport(_) => (0, 0), // import + CalxInstr::Unreachable => (0, 0), // TODO + CalxInstr::Nop => (0, 0), + CalxInstr::Quit(_) => (0, 0), + CalxInstr::Return => (0, 0), // TODO + CalxInstr::Assert(_) => (1, 0), + // debug + CalxInstr::Inspect => (0, 0), + } + } } #[derive(Debug, Clone, PartialEq, PartialOrd)] @@ -204,7 +270,7 @@ impl CalxError { CalxError { message: s, stack: vec![], - top_frame: CalxFrame::new_empty(), + top_frame: CalxFrame::default(), blocks: vec![], globals: vec![], } @@ -214,31 +280,42 @@ impl CalxError { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)] pub struct BlockData { pub looped: bool, - pub ret_types: Vec, + pub params_types: Rc>, + pub ret_types: Rc>, pub from: usize, pub to: usize, pub initial_stack_size: usize, } +impl BlockData { + // size of stack after block finished or breaked + pub fn expected_finish_size(&self) -> usize { + self.initial_stack_size - self.params_types.len() + self.ret_types.len() + } +} + #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct CalxFrame { + pub name: Rc, pub locals: Vec, // params + added locals - pub instrs: Vec, + /** store return values */ + pub instrs: Rc>, pub pointer: usize, pub initial_stack_size: usize, pub blocks_track: Vec, - pub ret_types: Vec, + pub ret_types: Rc>, } -impl CalxFrame { - pub fn new_empty() -> Self { +impl Default for CalxFrame { + fn default() -> Self { CalxFrame { + name: String::from("").into(), locals: vec![], - instrs: vec![], + instrs: Rc::new(vec![]), pointer: 0, initial_stack_size: 0, blocks_track: vec![], - ret_types: vec![], + ret_types: Rc::new(vec![]), } } } @@ -247,7 +324,7 @@ impl fmt::Display for CalxFrame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("CalxFrame ")?; write!(f, "_{} (", self.initial_stack_size)?; - for p in &self.ret_types { + for p in &*self.ret_types { write!(f, "{:?} ", p)?; } write!(f, ") @{}", self.pointer)?; diff --git a/src/vm.rs b/src/vm.rs index 43367bc..fbc544e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,7 +1,8 @@ use crate::primes::{BlockData, Calx, CalxError, CalxFrame, CalxFunc, CalxInstr, CalxType}; use std::collections::hash_map::HashMap; -use std::fmt; use std::ops::Rem; +use std::rc::Rc; +use std::{fmt, vec}; pub type CalxImportsDict = HashMap) -> Result, usize)>; @@ -25,12 +26,13 @@ impl CalxVM { pub fn new(fns: Vec, globals: Vec, imports: CalxImportsDict) -> Self { let main_func = find_func(&fns, "main").expect("main function is required"); let main_frame = CalxFrame { + name: main_func.name.to_owned(), initial_stack_size: 0, blocks_track: vec![], - instrs: main_func.instrs, + instrs: main_func.instrs.clone(), pointer: 0, locals: vec![], - ret_types: main_func.ret_types, + ret_types: main_func.ret_types.clone(), }; CalxVM { stack: vec![], @@ -62,20 +64,21 @@ impl CalxVM { self.top_frame.pointer += 1; continue; } - match self.top_frame.instrs[self.top_frame.pointer].to_owned() { + let instrs = self.top_frame.instrs.to_owned(); + match &instrs[self.top_frame.pointer] { CalxInstr::Jmp(line) => { - self.top_frame.pointer = line; + self.top_frame.pointer = line.to_owned(); continue; // point reset, goto next loop } CalxInstr::JmpIf(line) => { let v = self.stack_pop()?; if v == Calx::Bool(true) || v == Calx::I64(1) { - self.top_frame.pointer = line; + self.top_frame.pointer = line.to_owned(); continue; // point reset, goto next loop } } CalxInstr::Br(size) => { - self.shrink_blocks_by(size)?; + self.shrink_blocks_by(*size)?; let last_idx = self.top_frame.blocks_track.len() - 1; if self.top_frame.blocks_track[last_idx].looped { @@ -89,7 +92,7 @@ impl CalxVM { CalxInstr::BrIf(size) => { let last_idx = self.stack.len() - 1; if self.stack[last_idx] == Calx::Bool(true) || self.stack[last_idx] == Calx::I64(1) { - self.shrink_blocks_by(size)?; + self.shrink_blocks_by(*size)?; let last_idx = self.top_frame.blocks_track.len() - 1; if self.top_frame.blocks_track[last_idx].looped { @@ -112,16 +115,17 @@ impl CalxVM { return Err(self.gen_err(format!("no enough data on stack {:?} for {:?}", self.stack, params_types))); } self.top_frame.blocks_track.push(BlockData { - looped, - ret_types, - from, - to, + looped: looped.to_owned(), + params_types: params_types.to_owned(), + ret_types: ret_types.to_owned(), + from: from.to_owned(), + to: to.to_owned(), initial_stack_size: self.stack.len() - params_types.len(), }); - self.check_stack_for_block(¶ms_types)?; + self.check_stack_for_block(params_types)?; } CalxInstr::BlockEnd(looped) => { - if looped { + if *looped { return Err(self.gen_err(String::from("loop end expected to be branched"))); } let last_block = self.top_frame.blocks_track.pop().unwrap(); @@ -138,52 +142,67 @@ impl CalxVM { } CalxInstr::LocalSet(idx) => { let v = self.stack_pop()?; - if idx >= self.top_frame.locals.len() { + if *idx >= self.top_frame.locals.len() { return Err(self.gen_err(format!("out of bound in local.set {} for {:?}", idx, self.top_frame.locals))); } else { - self.top_frame.locals[idx] = v + self.top_frame.locals[*idx] = v } } CalxInstr::LocalTee(idx) => { let v = self.stack_pop()?; - if idx >= self.top_frame.locals.len() { + if *idx >= self.top_frame.locals.len() { return Err(self.gen_err(format!("out of bound in local.tee {}", idx))); } else { - self.top_frame.locals[idx] = v.to_owned() + self.top_frame.locals[*idx] = v.to_owned() } self.stack_push(v); } CalxInstr::LocalGet(idx) => { - if idx < self.top_frame.locals.len() { - self.stack_push(self.top_frame.locals[idx].to_owned()) + if *idx < self.top_frame.locals.len() { + self.stack_push(self.top_frame.locals[*idx].to_owned()) } else { return Err(self.gen_err(format!("invalid index for local.get {}", idx))); } } CalxInstr::Return => { + // return values are moved to a temp space + let mut ret_stack: Vec = vec![]; + + let ret_size = self.top_frame.ret_types.len(); + for _ in 0..ret_size { + let v = self.stack_pop()?; + ret_stack.insert(0, v); + } + self.check_func_return()?; + if self.frames.is_empty() { - return match self.stack.pop() { - Some(x) => Ok(x), + // top frame return, just return value + return match ret_stack.first() { + Some(x) => Ok(x.to_owned()), None => Err(self.gen_err("return without value".to_owned())), }; } else { // let prev_frame = self.top_frame; self.top_frame = self.frames.pop().unwrap(); + // push return values back + for v in ret_stack { + self.stack_push(v); + } } } CalxInstr::LocalNew => self.top_frame.locals.push(Calx::Nil), CalxInstr::GlobalSet(idx) => { let v = self.stack_pop()?; - if self.globals.len() >= idx { + if self.globals.len() >= *idx { return Err(self.gen_err(format!("out of bound in global.set {}", idx))); } else { - self.globals[idx] = v + self.globals[*idx] = v } } CalxInstr::GlobalGet(idx) => { - if idx < self.globals.len() { - self.stack_push(self.globals[idx].to_owned()) + if *idx < self.globals.len() { + self.stack_push(self.globals[*idx].to_owned()) } else { return Err(self.gen_err(format!("invalid index for global.get {}", idx))); } @@ -377,8 +396,11 @@ impl CalxVM { } CalxInstr::Call(f_name) => { // println!("frame size: {}", self.frames.len()); - match find_func(&self.funcs, &f_name) { + match find_func(&self.funcs, f_name) { Some(f) => { + let instrs = f.instrs.to_owned(); + let ret_types = f.ret_types.to_owned(); + let f_name = f.name.to_owned(); let mut locals: Vec = vec![]; for _ in 0..f.params_types.len() { let v = self.stack_pop()?; @@ -386,12 +408,13 @@ impl CalxVM { } self.frames.push(self.top_frame.to_owned()); self.top_frame = CalxFrame { + name: f_name, blocks_track: vec![], initial_stack_size: self.stack.len(), locals, pointer: 0, - instrs: f.instrs, - ret_types: f.ret_types, + instrs, + ret_types, }; // start in new frame @@ -402,15 +425,18 @@ impl CalxVM { } CalxInstr::ReturnCall(f_name) => { // println!("frame size: {}", self.frames.len()); - match find_func(&self.funcs, &f_name) { + match find_func(&self.funcs, f_name) { Some(f) => { // println!("examine stack: {:?}", self.stack); + let instrs = f.instrs.to_owned(); + let ret_types = f.ret_types.to_owned(); + let f_name = f.name.to_owned(); let mut locals: Vec = vec![]; for _ in 0..f.params_types.len() { let v = self.stack_pop()?; locals.insert(0, v); } - let prev_frame = self.top_frame.to_owned(); + let prev_frame = &self.top_frame; if prev_frame.initial_stack_size != self.stack.len() { return Err(self.gen_err(format!( "expected constant initial stack size: {}, got: {}", @@ -419,12 +445,13 @@ impl CalxVM { ))); } self.top_frame = CalxFrame { + name: f_name, blocks_track: vec![], initial_stack_size: self.stack.len(), locals, pointer: 0, - instrs: f.instrs, - ret_types: f.ret_types, + instrs, + ret_types, }; // start in new frame @@ -433,7 +460,7 @@ impl CalxVM { None => return Err(self.gen_err(format!("cannot find function named: {}", f_name))), } } - CalxInstr::CallImport(f_name) => match self.imports.to_owned().get(&*f_name) { + CalxInstr::CallImport(f_name) => match self.imports.to_owned().get(f_name) { None => return Err(self.gen_err(format!("missing imported function {}", f_name))), Some((f, size)) => { if self.stack.len() < *size { @@ -459,7 +486,7 @@ impl CalxVM { CalxInstr::Nop => { // Noop } - CalxInstr::Quit(code) => std::process::exit(code as i32), + CalxInstr::Quit(code) => std::process::exit(*code as i32), CalxInstr::Echo => { let v = self.stack_pop()?; println!("{}", v); @@ -472,23 +499,53 @@ impl CalxVM { return Err(self.gen_err(format!("Failed assertion: {}", message))); } } + CalxInstr::Inspect => { + println!("[ ----------------"); + println!( + " Internal frames: {:?}", + self.frames.iter().map(|x| x.name.to_owned()).collect::>() + ); + println!(" Top frame: {}", self.top_frame.name); + println!(" Locals: {:?}", self.top_frame.locals); + println!(" Blocks: {:?}", self.top_frame.blocks_track); + println!(" Stack({}): {:?}", self.stack.len(), self.stack); + println!( + " Sizes: {} + {}", + self.top_frame.initial_stack_size, + self.top_frame.ret_types.len() + ); + println!(" Pointer: {}", self.top_frame.pointer); + println!(" -------------- ]"); + } } self.top_frame.pointer += 1; } } - pub fn preprocess(&mut self) -> Result<(), String> { + pub fn preprocess(&mut self, verbose: bool) -> Result<(), String> { for i in 0..self.funcs.len() { let mut stack_size = 0; let mut ops: Vec = vec![]; let mut blocks_track: Vec = vec![]; - // println!("\nFUNC {} {}", self.funcs[i].name, stack_size); + let f = &self.funcs[i]; + + if verbose { + println!( + "\nFUNC {}\n initial stack size: {}\n ret_size {}", + f.name, + stack_size, + f.ret_types.len() + ); + } for j in 0..self.funcs[i].instrs.len() { - // println!("{} * {:?}", stack_size, self.funcs[i].instrs[j].to_owned()); - match self.funcs[i].instrs[j].to_owned() { + if verbose { + println!("{} * {:?}", stack_size, self.funcs[i].instrs[j].to_owned()); + } + let instrs = &self.funcs[i].instrs; + match &instrs[j] { CalxInstr::Block { looped, params_types, @@ -500,20 +557,26 @@ impl CalxVM { return Err(format!("insufficient params {} for block: {:?}", stack_size, params_types)); } blocks_track.push(BlockData { - looped, - ret_types, - from, - to, + looped: looped.to_owned(), + params_types: params_types.to_owned(), + ret_types: ret_types.to_owned(), + from: from.to_owned(), + to: to.to_owned(), initial_stack_size: stack_size, }); ops.push(CalxInstr::Nop); } CalxInstr::Br(size) => { - if size > blocks_track.len() { + if *size > blocks_track.len() { return Err(format!("br {} too large", size)); } let target_block = blocks_track[blocks_track.len() - size - 1].to_owned(); + let expected_size = target_block.expected_finish_size(); + if stack_size != expected_size { + return Err(format!("br({size}) expected size {expected_size}, got {stack_size}")); + } + if target_block.looped { // not checking ops.push(CalxInstr::Jmp(target_block.from)) @@ -525,7 +588,7 @@ impl CalxVM { if blocks_track.is_empty() { return Err(format!("cannot branch with no blocks, {}", size)); } - if size > blocks_track.len() { + if *size > blocks_track.len() { return Err(format!("br {} too large", size)); } @@ -536,7 +599,12 @@ impl CalxVM { } else { ops.push(CalxInstr::JmpIf(target_block.to)) } - stack_size -= 1 + stack_size -= 1; + + let expected_size = target_block.expected_finish_size(); + if stack_size != expected_size { + return Err(format!("brIf({size}) expected size {expected_size}, got {stack_size}")); + } } CalxInstr::BlockEnd(looped) => { // println!("checking: {:?}", blocks_track); @@ -545,51 +613,54 @@ impl CalxVM { } let prev_block = blocks_track.pop().unwrap(); - if looped { + if *looped { // nothing, branched during runtime - } else if stack_size != prev_block.initial_stack_size + prev_block.ret_types.len() { + } else if stack_size != prev_block.initial_stack_size + prev_block.ret_types.len() - prev_block.params_types.len() { + let block_kind = if prev_block.looped { "loop" } else { "block" }; return Err(format!( - "not match {} and {} + {:?} at block end", - stack_size, prev_block.initial_stack_size, prev_block.ret_types + "stack size is {stack_size}, initial size is {}, return types is {:?} at {block_kind} end", + prev_block.initial_stack_size, prev_block.ret_types )); } ops.push(CalxInstr::Nop) } - CalxInstr::Call(f_name) => match find_func(&self.funcs, &f_name) { + CalxInstr::Call(f_name) => match find_func(&self.funcs, f_name) { Some(f) => { if stack_size < f.params_types.len() { return Err(format!("insufficient size to call: {} {:?}", stack_size, f.params_types)); } stack_size = stack_size - f.params_types.len() + f.ret_types.len(); - ops.push(CalxInstr::Call(f_name)) + ops.push(CalxInstr::Call(f_name.to_owned())) } None => return Err(format!("cannot find function named: {}", f_name)), }, - CalxInstr::ReturnCall(f_name) => match find_func(&self.funcs, &f_name) { + CalxInstr::ReturnCall(f_name) => match find_func(&self.funcs, f_name) { Some(f) => { if stack_size < f.params_types.len() { return Err(format!("insufficient size to call: {} {:?}", stack_size, f.params_types)); } stack_size = stack_size - f.params_types.len() + f.ret_types.len(); - ops.push(CalxInstr::ReturnCall(f_name)) + ops.push(CalxInstr::ReturnCall(f_name.to_owned())) } None => return Err(format!("cannot find function named: {}", f_name)), }, - CalxInstr::CallImport(f_name) => match &self.imports.get(&*f_name) { + CalxInstr::CallImport(f_name) => match &self.imports.get(f_name) { Some((_f, size)) => { if stack_size < *size { return Err(format!("insufficient size to call import: {} {:?}", stack_size, size)); } stack_size = stack_size - size + 1; - ops.push(CalxInstr::CallImport(f_name)) + ops.push(CalxInstr::CallImport(f_name.to_owned())) } None => return Err(format!("missing imported function {}", f_name)), }, CalxInstr::Return => { - if stack_size != self.funcs[i].ret_types.len() { + let ret_size = self.funcs[i].ret_types.len(); + stack_size -= ret_size; + if stack_size != 0 { return Err(format!( - "invalid return size {} of {:?} in {}", + "invalid return size {} for {:?} in {}", stack_size, self.funcs[i].ret_types, self.funcs[i].name )); } @@ -597,7 +668,7 @@ impl CalxVM { } a => { // checks - let (params_size, ret_size) = instr_stack_arity(&a); + let (params_size, ret_size) = a.stack_arity(); if stack_size < params_size { return Err(format!("insufficient stack {} to call {:?} of {}", stack_size, a, params_size)); } @@ -610,14 +681,14 @@ impl CalxVM { } } } - if stack_size != self.funcs[i].ret_types.len() { + if stack_size != 0 { return Err(format!( - "invalid return size {} of {:?} in {}", + "invalid final size {} of {:?} in {}", stack_size, self.funcs[i].ret_types, self.funcs[i].name )); } - self.funcs[i].instrs = ops; + self.funcs[i].instrs = Rc::new(ops); } Ok(()) } @@ -637,8 +708,8 @@ impl CalxVM { } #[inline(always)] - fn check_func_return(&mut self) -> Result<(), CalxError> { - if self.stack.len() != self.top_frame.initial_stack_size + self.top_frame.ret_types.len() { + fn check_func_return(&self) -> Result<(), CalxError> { + if self.stack.len() != self.top_frame.initial_stack_size { return Err(self.gen_err(format!( "stack size {} does not fit initial size {} plus {:?}", self.stack.len(), @@ -698,71 +769,6 @@ impl CalxVM { } } -pub fn find_func(funcs: &[CalxFunc], name: &str) -> Option { - for x in funcs { - if &*x.name == name { - return Some(x.to_owned()); - } - } - None -} - -/// notice that some of the instrs are special and need to handle manually -pub fn instr_stack_arity(op: &CalxInstr) -> (usize, usize) { - match op { - CalxInstr::LocalSet(_) => (1, 0), - CalxInstr::LocalTee(_) => (1, 1), // TODO need check - CalxInstr::LocalGet(_) => (0, 1), - CalxInstr::LocalNew => (0, 0), - CalxInstr::GlobalSet(_) => (1, 0), - CalxInstr::GlobalGet(_) => (0, 1), - CalxInstr::GlobalNew => (0, 0), - CalxInstr::Const(_) => (0, 1), - CalxInstr::Dup => (1, 2), - CalxInstr::Drop => (1, 0), - CalxInstr::IntAdd => (2, 1), - CalxInstr::IntMul => (2, 1), - CalxInstr::IntDiv => (2, 1), - CalxInstr::IntRem => (2, 1), - CalxInstr::IntNeg => (1, 1), - CalxInstr::IntShr => (2, 1), - CalxInstr::IntShl => (2, 1), - CalxInstr::IntEq => (2, 1), - CalxInstr::IntNe => (2, 1), - CalxInstr::IntLt => (2, 1), - CalxInstr::IntLe => (2, 1), - CalxInstr::IntGt => (2, 1), - CalxInstr::IntGe => (2, 1), - CalxInstr::Add => (2, 1), - CalxInstr::Mul => (2, 1), - CalxInstr::Div => (2, 1), - CalxInstr::Neg => (1, 1), - // string operations - // list operations - CalxInstr::NewList => (0, 1), - CalxInstr::ListGet => (2, 1), - CalxInstr::ListSet => (3, 0), - // Link - CalxInstr::NewLink => (0, 1), - // bool operations - CalxInstr::And => (2, 1), - CalxInstr::Or => (2, 1), - CalxInstr::Not => (1, 1), - // control stuctures - CalxInstr::Br(_) => (0, 0), - CalxInstr::BrIf(_) => (1, 0), - CalxInstr::Jmp(_) => (0, 0), - CalxInstr::JmpIf(_) => (1, 0), - CalxInstr::Block { .. } => (0, 0), - CalxInstr::BlockEnd(_) => (0, 0), - CalxInstr::Echo => (1, 0), - CalxInstr::Call(_) => (0, 0), // TODO - CalxInstr::ReturnCall(_) => (0, 0), // TODO - CalxInstr::CallImport(_) => (0, 0), // import - CalxInstr::Unreachable => (0, 0), // TODO - CalxInstr::Nop => (0, 0), - CalxInstr::Quit(_) => (0, 0), - CalxInstr::Return => (0, 0), // TODO - CalxInstr::Assert(_) => (1, 0), - } +pub fn find_func<'a>(funcs: &'a [CalxFunc], name: &str) -> Option<&'a CalxFunc> { + funcs.iter().find(|x| *x.name == name) }