diff --git a/.gitignore b/.gitignore index 1b26c78..eb86e63 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ #/target Cargo.lock + + +profile.json diff --git a/Cargo.toml b/Cargo.toml index 541b6ca..035a63d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calx_vm" -version = "0.2.0-a1" +version = "0.2.0" authors = ["jiyinyiyong "] edition = "2021" license = "MIT" diff --git a/README.md b/README.md index 6b402bc..d89243a 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,55 @@ it starts with a `main` function: ```cirru fn main () - const "|hello world" + const 1 + call demo + const 0 + call demo + +fn demo (($a i64) ->) + local.get $a + + if (->) + do + const 11 + echo + do + const 20 + echo + const 3 echo ``` -`-S` to show instructions: +`-s` to show instructions: ```bash -calx -S hello.cirru -``` - -`-D` to disable preprocess: - -```bash -calx -D hello.cirru +$ calx demos/if.cirru -s +[calx] start preprocessing +loaded fn: CalxFunc main (-> ) + 00 Const(I64(1)) + 01 Call("demo") + 02 Const(I64(0)) + 03 Call("demo") + +loaded fn: CalxFunc demo (I64 -> ) + local_names: 0_$a . + 00 LocalGet(0) + 01 JmpIf(5) + 02 Const(I64(20)) + 03 Echo + 04 Jmp(8) + 05 Const(I64(11)) + 06 Echo + 07 Jmp(8) + 08 Const(I64(3)) + 09 Echo + +[calx] start running +11 +3 +20 +3 +[calx] took 67.250µs: Nil ``` `--emit-binary filename` for encode functions into a binary file. @@ -73,82 +108,13 @@ fn main () ### Instructions -> _TODO_ update to `0.2.x`... +Find docs on https://docs.rs/calx_vm/ . Highly inspired by: - WASM https://github.com/WebAssembly/design/blob/main/Semantics.md - Lox https://github.com/Darksecond/lox/blob/master/lox-vm/src/bettervm/vm.rs -For binary op, top value puts on right. - -Calx Binary Edition `0.1`: - -| Code | Usage | Note | -| -------------------- | -------------------------------------------------------- | -------------------------------------- | -| `local.set $idx` | pop from stack, set value at `$idx` | | -| `local.tee $idx` | set value at `$idx`, and also load it | | -| `local.get $idx` | get value at `$idx` load on stack | | -| `local.new $name` | increase size of array of locals | name is optional, defaults to `$` | -| `global.set $idx` | set global value at `$idx` | | -| `global.get $idx` | get global value at `$idx` | | -| `global.new` | increase size of array of globals | | -| `const $v` | push value `$v` on stack | | -| `dup` | duplicate top value on stack | | -| `drop` | drop top value from stack | | -| `i.add` | add two i64 numbers on stack into one | | -| `i.mul` | multiply two i64 numbers on stack into one | | -| `i.div` | divide two i64 numbers on stack into one | | -| `i.rem` | calculate reminder two i64 numbers on stack into one | | -| `i.neg` | negate i64 numbers on top of stack | | -| `i.shr $bits` | call SHR `$bits` bits on i64 numbers on top of stack | | -| `i.shl $bits` | call SHL `$bits` bits on i64 numbers on top of stack | | -| `i.eq` | detects if two i64 numbers on stack equal | | -| `i.ne` | detects if two i64 numbers on stack not equal | | -| `i.lt` | litter than, compares two i64 numbers on stack | | -| `i.gt` | greater than, compares two i64 numbers on stack | | -| `i.le` | litter/equal than, compares two i64 numbers on stack | | -| `i.ge` | greater/equal than, compares two i64 numbers on stack | | -| `add` | add two f64 numbers on stack into one | | -| `mul` | multiply two f64 numbers on stack into one | | -| `div` | divide two f64 numbers on stack into one | | -| `neg` | negate f64 numbers on top of stack | | -| `list.new` | | TODO | -| `list.get` | | TODO | -| `list.set` | | TODO | -| `link.new` | | TODO | -| `and` | | TODO | -| `or` | | TODO | -| `not` | | TODO | -| `br $n` | branch `$n` level of block, 0 means end of current block | | -| `br-if $n` | like `br $n` but detects top value on stack first | Internal | -| (JMP `$l`) | jump to line `$l` | Internal | -| (JMP_IF `$l`) | conditional jump to `$l` | -| `block $types $body` | declare a block | | -| `loop $types $body` | declare a loop block | | -| (BlockEnd) | internal mark for ending a block | Internal | -| `echo` | pop value from stack and print | | -| `call $f` | call function `$f` | | -| `return-call $f` | tail call function `$f` | | -| `call-import $f` | call imported function `$f` | | -| `unreachable` | throw unreachable panic | | -| `nop` | No op | | -| `quit $code` | quit program and return exit code `$code` | | -| `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: - -- nil -- i64 -- f64 -- bool _TODO_ -- str _TODO_ -- list _TODO_ -- link _TODO_ - ### Preprocess Before Calx running the instructions, Calx performs preprocessing to them. There are several tasks: @@ -158,7 +124,7 @@ Before Calx running the instructions, Calx performs preprocessing to them. There - 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. +The codebase would be updated as I'm learning more about WASM. ### License diff --git a/src/calx.rs b/src/calx.rs index eeab805..2d0ff3b 100644 --- a/src/calx.rs +++ b/src/calx.rs @@ -11,11 +11,17 @@ pub use types::CalxType; /// Simplied from Calcit, but trying to be basic and mutable #[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] pub enum Calx { + /// TODO Nil, + /// TODO Bool(bool), + /// `i64` I64(i64), + /// `f64` F64(f64), + // TODO Str(String), + /// TODO List(Vec), // to simultate linked structures // Link(Box, Box, Box), diff --git a/src/calx/types.rs b/src/calx/types.rs index d0638e3..f05f151 100644 --- a/src/calx/types.rs +++ b/src/calx/types.rs @@ -1,14 +1,22 @@ use bincode::{Decode, Encode}; use std::str::FromStr; +/// syntax like `(i64 -> i64)` can be used to types of functions and blocks #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Decode, Encode)] pub enum CalxType { + /// TODO Nil, + /// TODO Bool, + /// i64 value I64, + /// f64 value F64, + /// TODO Str, + /// TODO List, + /// TODO Link, } diff --git a/src/lib.rs b/src/lib.rs index 482f5a2..745f208 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +//! Calx VM is a toy VM for learning WebAssembly. +//! It is a stack machine, and it is dynamically typed. Being an experiment, for Calcit project. + mod calx; mod parser; mod syntax; @@ -6,5 +9,6 @@ mod vm; pub use calx::{Calx, CalxType}; pub use parser::{extract_nested, parse_function}; +pub use syntax::CalxSyntax; pub use util::log_calx_value; -pub use vm::{func::CalxFunc, instr::CALX_INSTR_EDITION, CalxImportsDict, CalxVM}; +pub use vm::{func::CalxFunc, instr::CalxInstr, instr::CALX_INSTR_EDITION, CalxImportsDict, CalxVM}; diff --git a/src/syntax.rs b/src/syntax.rs index 68d551d..9677a5b 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -7,87 +7,122 @@ use crate::{Calx, CalxType}; /// learning from WASM but for dynamic data #[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] pub enum CalxSyntax { - // Param, // load variable from parameter + /// `local.set`, pop from stack, set value at position LocalSet(usize), - LocalTee(usize), // set and also load to stack + /// `local.tee`, set value at position, and also load to stack + LocalTee(usize), + /// `local.get`, get value at position load on stack LocalGet(usize), + /// `local.new`, increase size of array of locals LocalNew, + /// `global.set`, set global value at position GlobalSet(usize), + /// `global.get`, get global value from position GlobalGet(usize), + /// `global.new`, increase size of array of globals GlobalNew, + /// `const`, push value to stack Const(Calx), + /// `dup`, duplicate value on stack Dup, + /// `drop`, drop top value from stack Drop, - // number operations + /// `i.add`, add two i64 numbers on stack into a i64 IntAdd, + /// `i.mul`, multiply two i64 numbers on stack into a i64 IntMul, + /// `i.div`, divide two i64 numbers on stack into a i64 IntDiv, + /// `i.rem`, remainder of two i64 numbers on stack into a i64 IntRem, + /// `i.neg`, negate a i64 number on stack IntNeg, + /// `i.shr`, shift right a i64 number on stack IntShr, + /// `i.shl`, shift left a i64 number on stack IntShl, - /// equal + /// `i.eq`, equal of two i64 numbers on stack into a bool IntEq, - /// not equal + /// `i.ne`, not equal of two i64 numbers on stack into a bool IntNe, - /// littler than + /// `i.lt`, littler than, compares two i64 numbers on stack IntLt, - /// littler than, or equal + /// `i.le`, littler than, or equal, compares two i64 numbers on stack IntLe, - /// greater than + /// `i.gt`, greater than, compares two i64 numbers on stack IntGt, - /// greater than, or equal + /// `i.ge`, greater than, or equal, compares two i64 numbers on stack IntGe, + /// `add`, add two f64 numbers on stack into a f64 Add, + /// `mul`, multiply two f64 numbers on stack into a f64 Mul, + /// `div`, divide two f64 numbers on stack into a f64 Div, + /// `neg`, negate a f64 number on stack Neg, - // string operations - // list operations + /// TODO list operations NewList, + /// TODO ListGet, + /// TODO ListSet, - // Link + /// TODO Link NewLink, - // bool operations + /// TODO And, + /// TODO Or, + /// TODO Not, - // control stuctures - Br(usize), - BrIf(usize), + /// `block`, creates block, for `block` and `loop` Block { - params_types: Rc>, - ret_types: Rc>, /// bool to indicate loop looped: bool, + params_types: Rc>, + ret_types: Rc>, + /// index of `end` instruction from: usize, + /// index of `end` instruction to: usize, }, + /// `br`, break from block, level `0` indicates the innermost block + Br(usize), + /// `br-if`, break from block conditionally, level `0` indicates the innermost block + BrIf(usize), + /// (parsed) end of block, for `block` and `loop` BlockEnd(bool), - /// just a list of instructions nested + /// `do`, just a list of instructions nested, used inside `if` area Do(Vec), - /// pop and println current value + /// `echo`, pop and println current value Echo, - /// TODO use function name at first, during running, index can be faster + /// `call`, call function + /// TODO optimize with index Call(String), - /// for tail recursion + /// `return-call`, tail recursion call function name ReturnCall(String), + /// `call-import`, call import function CallImport(String), + /// `unreachable`, unreachable panic Unreachable, + /// `nop`, no operation placeholder Nop, - Quit(usize), // quit and return value + /// `quit`, quit and return value + Quit(usize), + /// `return`, return from function Return, - /// TODO might also be a foreign function instead + /// `assert`, TODO might also be a foreign function instead Assert(String), - /// inspecting stack + /// `inspect`, inspecting stack Inspect, - /// if takes 1 value from stack, returns values as ret_types + /// `if`, takes 1 value from stack, returns values as ret_types If { ret_types: Rc>, else_at: usize, to: usize, }, + /// (parsed) end of then instructions ThenEnd, + /// (parsed) end of else instructions ElseEnd, } diff --git a/src/vm.rs b/src/vm.rs index 6d06a2e..a3d95b6 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -8,7 +8,7 @@ use std::ops::Rem; use std::rc::Rc; use std::{fmt, vec}; -use crate::calx::{Calx, CalxType}; +use crate::calx::Calx; use crate::syntax::CalxSyntax; use crate::vm::block_data::BlockStack; @@ -19,6 +19,13 @@ use self::instr::CalxInstr; pub type CalxImportsDict = HashMap) -> Result, usize)>; +/// Virtual Machine for Calx +/// code is evaluated in a several steps: +/// 1. parse into `CalxSyntax` +/// 2. preprocess `CalxSyntax` into instructions(`CalxInstr`) +/// 3. run instructions +/// +/// `CalxSyntax` contains some richer info than `CalxInstr`. #[derive(Clone)] pub struct CalxVM { pub stack: Vec, @@ -542,13 +549,6 @@ impl CalxVM { println!("[ ----------------{}", self.inspect_display(2)); println!(" -------------- ]"); } - If { ret_types, .. } => { - // TODO - self.check_stack_for_block(ret_types)?; - } - EndIf => { - unreachable!("End if is internal instruction during preprocessing") - } } Ok(false) @@ -775,20 +775,6 @@ impl CalxVM { Ok(()) } - /// checks is given parameters on stack top - fn check_stack_for_block(&self, params: &[CalxType]) -> Result<(), CalxError> { - if self.stack.len() < params.len() { - return Err(self.gen_err(format!("stack size does not match given params: {:?} {:?}", self.stack, params))); - } - for (idx, t) in params.iter().enumerate() { - if self.stack[self.stack.len() - params.len() - 1 + idx].typed_as(t.to_owned()) { - continue; - } - return Err(self.gen_err(format!("stack type does not match given params: {:?} {:?}", self.stack, params))); - } - Ok(()) - } - #[inline(always)] fn check_func_return(&self) -> Result<(), CalxError> { if self.stack.len() != self.top_frame.initial_stack_size { diff --git a/src/vm/instr.rs b/src/vm/instr.rs index 6539eec..8093cff 100644 --- a/src/vm/instr.rs +++ b/src/vm/instr.rs @@ -1,89 +1,107 @@ -use std::rc::Rc; - use bincode::{Decode, Encode}; -use crate::{ - calx::{Calx, CalxType}, - syntax::CalxSyntax, -}; +use crate::{calx::Calx, syntax::CalxSyntax}; /// learning from WASM but for dynamic data #[derive(Debug, Clone, PartialEq, PartialOrd, Decode, Encode)] pub enum CalxInstr { - // Param, // load variable from parameter + /// pop from stack, set value at position LocalSet(usize), - LocalTee(usize), // set and also load to stack + /// set and also load to stack + LocalTee(usize), + /// get value at position load on stack LocalGet(usize), + /// increase size of array of locals LocalNew, + /// set global value at position GlobalSet(usize), + /// get global value from position GlobalGet(usize), + /// increase size of array of globals GlobalNew, + /// push value to stack Const(Calx), + /// duplicate value on stack Dup, + /// drop top value from stack Drop, - // number operations + /// add two i64 numbers on stack into a i64 IntAdd, + /// multiply two i64 numbers on stack into a i64 IntMul, + /// divide two i64 numbers on stack into a i64 IntDiv, + /// remainder of two i64 numbers on stack into a i64 IntRem, + /// negate a i64 number on stack IntNeg, + /// shift right a i64 number on stack IntShr, + /// shift left a i64 number on stack IntShl, - /// equal + /// equal of two i64 numbers on stack into a bool IntEq, - /// not equal + /// not equal of two i64 numbers on stack into a bool IntNe, - /// littler than + /// littler than, compares two i64 numbers on stack IntLt, - /// littler than, or equal + /// littler than, or equal, compares two i64 numbers on stack IntLe, - /// greater than + /// greater than, compares two i64 numbers on stack IntGt, - /// greater than, or equal + /// greater than, or equal, compares two i64 numbers on stack IntGe, + /// add two f64 numbers on stack into a f64 Add, + /// multiply two f64 numbers on stack into a f64 Mul, + /// divide two f64 numbers on stack into a f64 Div, + /// negate a f64 number on stack Neg, - // string operations - // list operations + /// TODO NewList, + /// TODO ListGet, + /// TODO ListSet, - // Link + /// TODO NewLink, - // bool operations + /// TODO And, + /// TODO Or, + /// TODO Not, - // control stuctures - Jmp(usize), // internal - JmpOffset(i32), // internal - JmpIf(usize), // internal - JmpOffsetIf(i32), // internal + /// Jump to index + Jmp(usize), + /// Jump by offset + JmpOffset(i32), + /// Jump to index if top value is true + JmpIf(usize), + /// Jump by offset if top value is true + JmpOffsetIf(i32), /// pop and println current value Echo, - /// TODO use function name at first, during running, index can be faster + /// call function + /// TODO optimize with index Call(String), - /// for tail recursion + /// tail recursion ReturnCall(String), + /// call import CallImport(String), + /// unreachable panic Unreachable, + /// no operation placeholder Nop, - Quit(usize), // quit and return value + /// quit and return error code + Quit(usize), + /// return from function Return, /// TODO might also be a foreign function instead Assert(String), /// inspecting stack Inspect, - /// if takes 1 value from stack, returns values as ret_types - If { - ret_types: Rc>, - then_to: usize, - else_to: usize, - to: usize, - }, - EndIf, } impl TryFrom<&CalxSyntax> for CalxInstr { @@ -211,8 +229,6 @@ impl CalxInstr { CalxInstr::Assert(_) => (1, 0), // debug CalxInstr::Inspect => (0, 0), - CalxInstr::If { ret_types, .. } => (1, ret_types.len()), - CalxInstr::EndIf => (0, 0), } } }