├── src ├── export.rs ├── memory.rs ├── lib.rs ├── module.rs ├── bin │ └── main.rs ├── error.rs ├── function.rs ├── value.rs ├── vm.rs ├── stack.rs └── compiler.rs ├── .gitignore ├── for_loop.wasm ├── fib.wasm ├── for_loop_64.wasm ├── bare-metal-wasm ├── bare_metal_wasm.wasm ├── Cargo.toml ├── build_wasm_opt.sh └── src │ └── lib.rs ├── fib.wat ├── Cargo.toml ├── for_loop.wat ├── for_loop_64.wat ├── LICENSE ├── Ideas.md └── README.md /src/export.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.wasm 4 | *.wast 5 | -------------------------------------------------------------------------------- /for_loop.wasm: -------------------------------------------------------------------------------- 1 | asm` for_loop 2 | &$@ AH !@ Aj" A*j -------------------------------------------------------------------------------- /fib.wasm: -------------------------------------------------------------------------------- 1 | asm`fib 2 |  AH@A Ak Akj namefibn -------------------------------------------------------------------------------- /for_loop_64.wasm: -------------------------------------------------------------------------------- 1 | asm`~~ for_loop 2 | (&~@ BS !@ B|"PE B*| -------------------------------------------------------------------------------- /bare-metal-wasm/bare_metal_wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Neopallium/s1vm/HEAD/bare-metal-wasm/bare_metal_wasm.wasm -------------------------------------------------------------------------------- /bare-metal-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bare-metal-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /bare-metal-wasm/build_wasm_opt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | TARGET=wasm32-unknown-unknown 6 | BINARY=target/$TARGET/release/bare_metal_wasm.wasm 7 | OUT=bare_metal_wasm.wasm 8 | 9 | cargo build --target $TARGET --release 10 | wasm-strip $BINARY 11 | wasm-opt -o $OUT -Oz $BINARY 12 | ls -lh $OUT 13 | -------------------------------------------------------------------------------- /fib.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (func $fib (type 0) (param $n i32) (result i32) 4 | local.get $n 5 | i32.const 2 6 | i32.lt_s 7 | if ;; label = @1 8 | i32.const 1 9 | return 10 | end 11 | local.get $n 12 | i32.const 2 13 | i32.sub 14 | call $fib 15 | local.get $n 16 | i32.const 1 17 | i32.sub 18 | call $fib 19 | i32.add 20 | return) 21 | (export "fib" (func $fib))) 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | pub mod error; 4 | pub use error::{Error, Result}; 5 | 6 | // VM 7 | mod memory; 8 | mod export; 9 | mod stack; 10 | pub use stack::*; 11 | mod value; 12 | pub use value::*; 13 | mod compiler; 14 | mod vm; 15 | pub use vm::*; 16 | 17 | // Module 18 | mod function; 19 | pub use function::*; 20 | mod module; 21 | pub use module::*; 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | #[test] 26 | fn it_works() { 27 | assert_eq!(2 + 2, 4); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "s1vm" 3 | version = "0.1.1" 4 | authors = ["Robert G. Jakabosky "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "Fast WebAssembly interpreter" 8 | keywords = ["wasm", "webassembly", "interpreter"] 9 | repository = "https://github.com/Neopallium/s1vm" 10 | homepage = "https://github.com/Neopallium/s1vm" 11 | 12 | [dependencies] 13 | bwasm = "0.1.1" 14 | parity-wasm = "0.41" 15 | 16 | # uncomment these to do profiling. 17 | [profile.dev] 18 | opt-level = 1 19 | 20 | [profile.release] 21 | #debug = true 22 | #lto = "fat" 23 | #codegen-units = 1 24 | -------------------------------------------------------------------------------- /for_loop.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i32) (result i32))) 3 | (export "for_loop" (func $for_loop)) 4 | (func $for_loop (type 0) (param $0 i32) (result i32) 5 | (local $1 i32) 6 | (block $label$0 7 | (br_if $label$0 8 | (i32.lt_s 9 | (get_local $0) 10 | (i32.const 1) 11 | ) 12 | ) 13 | (set_local $1 14 | (get_local $0) 15 | ) 16 | (loop $label$1 17 | (br_if $label$1 18 | (tee_local $1 19 | (i32.add 20 | (get_local $1) 21 | (i32.const -1) 22 | ) 23 | ) 24 | ) 25 | ) 26 | ) 27 | (return 28 | (i32.add 29 | (get_local $0) 30 | (i32.const 42) 31 | ) 32 | ) 33 | ) 34 | ) 35 | -------------------------------------------------------------------------------- /bare-metal-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[panic_handler] 4 | fn handle_panic(_: &core::panic::PanicInfo) -> ! { 5 | loop {} 6 | } 7 | 8 | #[inline(never)] 9 | #[no_mangle] 10 | pub extern fn inner_func(rdi: usize, rsi: usize, rdx: usize, rcx: usize, r8: usize) -> usize { 11 | rdi + rsi + rdx + rcx + r8 12 | } 13 | 14 | #[inline(never)] 15 | #[no_mangle] 16 | pub extern fn do_for_loop(rdi: usize, rsi: usize, rdx: usize, rcx: usize, r8: usize) -> usize { 17 | for idx in 0..rdi { 18 | if inner_func(idx, rsi, rdx, rcx, r8) == 0 { 19 | return 0; 20 | } 21 | } 22 | rdi 23 | } 24 | 25 | #[no_mangle] 26 | pub extern fn for_loop(count: usize) -> usize { 27 | do_for_loop(count, count+1, count+2, count+3, count+4) 28 | } 29 | -------------------------------------------------------------------------------- /for_loop_64.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func (param i64) (result i64))) 3 | (export "for_loop" (func $for_loop)) 4 | (func $for_loop (type 0) (param $0 i64) (result i64) 5 | (local $1 i64) 6 | (block $label$0 7 | (br_if $label$0 8 | (i64.lt_s 9 | (get_local $0) 10 | (i64.const 1) 11 | ) 12 | ) 13 | (set_local $1 14 | (get_local $0) 15 | ) 16 | (loop $label$1 17 | (br_if $label$1 18 | (i32.eqz 19 | (i64.eqz 20 | (tee_local $1 21 | (i64.add 22 | (get_local $1) 23 | (i64.const -1) 24 | ) 25 | ) 26 | ) 27 | ) 28 | ) 29 | ) 30 | ) 31 | (return 32 | (i64.add 33 | (get_local $0) 34 | (i64.const 42) 35 | ) 36 | ) 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /src/module.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::*; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct ModuleInstance { 7 | //types: Vec, 8 | funcs: Vec, 9 | //globals: Vec, 10 | exports: HashMap, 11 | } 12 | 13 | impl ModuleInstance { 14 | pub fn new() -> ModuleInstance { 15 | Default::default() 16 | } 17 | 18 | // Map function idx to address 19 | pub fn add_function(&mut self, addr: FuncAddr) { 20 | self.funcs.push(addr); 21 | } 22 | 23 | // Export a function 24 | pub fn add_export(&mut self, name: &str, idx: FuncIdx) -> Result<()> { 25 | let name = name.to_string(); 26 | if self.exports.contains_key(&name) { 27 | Err(Error::FuncExists) 28 | } else { 29 | self.exports.insert(name, idx); 30 | Ok(()) 31 | } 32 | } 33 | 34 | pub fn find_function(&self, name: &str) -> Result { 35 | if let Some(idx) = self.exports.get(&name.to_string()) { 36 | if let Some(func) = self.funcs.get(*idx as usize) { 37 | return Ok(*func); 38 | } 39 | } 40 | Err(Error::FuncNotFound) 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Robert G. Jakabosky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | use s1vm::*; 4 | 5 | fn main() -> Result<(), Error> { 6 | let mut args = std::env::args(); 7 | args.next(); // skip program name. 8 | let file = args.next().expect("missing file name"); 9 | let func = args.next().expect("missing function name"); 10 | let params: Vec = args.map(|x| { 11 | match x.parse::() { 12 | Ok(v) => Value::I64(v), 13 | Err(e) => { 14 | eprintln!("failed to parse '{}': {}", x, e); 15 | Value::I64(0) 16 | }, 17 | } 18 | }).collect(); 19 | 20 | //println!("Type sizes:"); 21 | //println!("isa::Instruction = {}", std::mem::size_of::()); 22 | //println!("bwasm::Instruction = {}", std::mem::size_of::()); 23 | 24 | // Create VM. 25 | let mut vm = VM::new(); 26 | 27 | // Load wasm file 28 | //println!("--- Loading module: {}", file); 29 | vm.load_file("main", &file)?; 30 | 31 | // Call module function 32 | //println!("Calling: {}({:?})", func, params); 33 | let ret = vm.call("main", &func, ¶ms)?; 34 | if let Some(ret) = ret { 35 | println!("{}", ret); 36 | } else { 37 | println!("ret = "); 38 | } 39 | 40 | Ok(()) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq)] 4 | pub enum TrapKind { 5 | InvalidFunctionAddr, 6 | 7 | Unreachable, 8 | MemoryAccessOutOfBounds, 9 | TableAccessOutOfBounds, 10 | ElemUninitialized, 11 | DivisionByZero, 12 | InvalidConversionToInt, 13 | StackOverflow, 14 | UnexpectedSignature, 15 | } 16 | pub type Trap = std::result::Result; 17 | 18 | #[derive(Debug, Clone)] 19 | pub enum Error { 20 | FuncNotFound, 21 | FuncExists, 22 | 23 | ModuleNotFound, 24 | ModuleExists, 25 | 26 | ParseError(parity_wasm::SerializationError), 27 | ValidationError(String), 28 | 29 | RuntimeError(TrapKind), 30 | } 31 | 32 | pub type Result = std::result::Result; 33 | 34 | impl fmt::Display for Error { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | match self { 37 | Error::FuncNotFound => write!(f, "function not found"), 38 | Error::FuncExists => write!(f, "function already exists"), 39 | Error::ModuleNotFound => write!(f, "module not found"), 40 | Error::ModuleExists => write!(f, "module already exists"), 41 | Error::ParseError(e) => write!(f, "failed to parse wasm: {}", e), 42 | Error::ValidationError(e) => write!(f, "failed to validate wasm: {}", e), 43 | Error::RuntimeError(trap) => write!(f, "runtime trap: {:?}", trap), 44 | } 45 | } 46 | } 47 | 48 | impl std::error::Error for Error { 49 | } 50 | 51 | impl From for Error { 52 | fn from(trap: TrapKind) -> Self { 53 | Error::RuntimeError(trap) 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(error: parity_wasm::SerializationError) -> Self { 59 | Error::ParseError(error) 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(error: bwasm::LoadError) -> Self { 65 | match error { 66 | bwasm::LoadError::SerializationError(error) => Error::ParseError(error), 67 | bwasm::LoadError::ValidationError(error) => Error::ValidationError(format!("{}", error)), 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/function.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::*; 3 | use crate::error::*; 4 | 5 | type CompiledFunc = Box Trap>>; 6 | 7 | pub struct CompiledFunction { 8 | pub local_types: Vec, 9 | pub run: CompiledFunc, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct HostFunction { 14 | pub mod_idx: usize, 15 | pub func_idx: usize, 16 | } 17 | 18 | pub enum FunctionBody { 19 | Compiled(CompiledFunction), 20 | Host(HostFunction), 21 | } 22 | 23 | pub struct Function { 24 | pub name: String, 25 | pub func_type: FunctionType, 26 | pub body: FunctionBody, 27 | } 28 | 29 | impl Function { 30 | pub fn new(func: &bwasm::Function, run: CompiledFunc) -> Function { 31 | Function { 32 | name: func.name().to_string(), 33 | func_type: FunctionType::from(func.func_type()), 34 | body: FunctionBody::Compiled(CompiledFunction{ 35 | local_types: ValueType::from_slice(func.locals()), 36 | run, 37 | }), 38 | } 39 | } 40 | 41 | pub fn param_count(&self) -> usize { 42 | self.func_type.param_count() 43 | } 44 | 45 | pub fn ret_type(&self) -> Option { 46 | self.func_type.ret_type 47 | } 48 | 49 | pub fn call(&self, state: &State, store: &mut Store, l0: &mut StackValue) -> Trap> { 50 | match self.body { 51 | FunctionBody::Compiled(ref body) => { 52 | let p_count = self.param_count(); 53 | let l_count = body.local_types.len(); 54 | let ret = if p_count <= 1 && l_count == 0 { 55 | // Fast function call. 56 | (body.run)(state, store, l0)? 57 | } else { 58 | // Setup stack frame for function. 59 | let old_frame = store.stack.push_frame(p_count, l_count)?; 60 | 61 | // run function 62 | let ret = (body.run)(state, store, l0)?; 63 | 64 | // cleanup stack frame. 65 | store.stack.pop_frame(old_frame); 66 | ret 67 | }; 68 | Ok(ret) 69 | }, 70 | FunctionBody::Host(_) => { 71 | todo!(""); 72 | }, 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub enum ValueType { 5 | I32, 6 | I64, 7 | F32, 8 | F64, 9 | } 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq)] 12 | pub enum Value { 13 | I32(i32), 14 | I64(i64), 15 | F32(f32), 16 | F64(f64), 17 | } 18 | 19 | impl fmt::Display for Value { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | match self { 22 | Value::I32(v) => write!(f, "{}", v), 23 | Value::I64(v) => write!(f, "{}", v), 24 | Value::F32(v) => write!(f, "{}", v), 25 | Value::F64(v) => write!(f, "{}", v), 26 | } 27 | } 28 | } 29 | 30 | impl From for ValueType { 31 | fn from(val_type: bwasm::ValueType) -> Self { 32 | match val_type { 33 | bwasm::ValueType::I32 => ValueType::I32, 34 | bwasm::ValueType::I64 => ValueType::I64, 35 | bwasm::ValueType::F32 => ValueType::F32, 36 | bwasm::ValueType::F64 => ValueType::F64, 37 | } 38 | } 39 | } 40 | 41 | impl From<&bwasm::ValueType> for ValueType { 42 | fn from(val_type: &bwasm::ValueType) -> Self { 43 | ValueType::from(*val_type) 44 | } 45 | } 46 | 47 | impl ValueType { 48 | pub fn from_slice(val_types: &[bwasm::ValueType]) -> Vec { 49 | val_types.iter().map(ValueType::from).collect() 50 | } 51 | } 52 | 53 | pub type RetValue = Option; 54 | 55 | pub type ConstI32 = i32; 56 | pub type ConstI64 = i64; 57 | pub type ConstF32 = f32; 58 | pub type ConstF64 = f64; 59 | 60 | pub type TypeIdx = u32; 61 | pub type FuncIdx = u32; 62 | pub type TableIdx = u32; 63 | pub type MemIdx = u32; 64 | pub type GlobalIdx = u32; 65 | pub type LocalIdx = u32; 66 | pub type LabelIdx = u32; 67 | 68 | pub type ModuleInstanceAddr = u32; 69 | pub type FuncAddr = u32; 70 | pub type TableAddr = u32; 71 | pub type MemAddr = u32; 72 | pub type GlobalAddr = u32; 73 | 74 | #[derive(Debug, Clone, Default)] 75 | pub struct FunctionType { 76 | pub params: Vec, 77 | pub ret_type: Option, 78 | } 79 | 80 | impl FunctionType { 81 | pub fn new() -> FunctionType { 82 | Default::default() 83 | } 84 | 85 | pub fn param_count(&self) -> usize { 86 | self.params.len() 87 | } 88 | } 89 | 90 | impl From for FunctionType { 91 | fn from(func_type: bwasm::FunctionType) -> Self { 92 | FunctionType::from(&func_type) 93 | } 94 | } 95 | 96 | impl From<&bwasm::FunctionType> for FunctionType { 97 | fn from(func_type: &bwasm::FunctionType) -> Self { 98 | FunctionType { 99 | params: ValueType::from_slice(func_type.params()), 100 | ret_type: func_type.return_type().map(ValueType::from), 101 | } 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /Ideas.md: -------------------------------------------------------------------------------- 1 | # Ideas 2 | 3 | This is to document some of my crazy ideas for this project. 4 | 5 | ## Threaded interpreter 6 | 7 | The normal ways (computed gotos, tail-calls) to optimize VM opcode execution 8 | [do not work in Rust](https://pliniker.github.io/post/dispatchers/). 9 | 10 | Crazy ideas: 11 | 1. Each opcode as a closure/function. Still using a loop to call each opcode. 12 | 2. Each opcode as a closure that also has a reference to the next opcode. 13 | 3. Compile opcodes to nested closures. All opcodes have a static number of inputs and outputs. 14 | 15 | ### Compile opcodes into closures 16 | 17 | A closure can be made for each opcode of a function to capture the opcode's parameters 18 | (jump target, local/global idx, mem offset, etc..). 19 | 20 | It could also allow merging multiple opcodes into a single closure. 21 | 22 | Convert (2 push, 2 pop, 1 push): 23 | * GetLocal(0) - push local onto stack. 24 | * I64Const(1234) - push constant '1234' onto stack. 25 | * I64Add - pop two i64 values, push results (a + b) 26 | 27 | ```rust 28 | /// opcode functions for i64 opcodes. 29 | mod i64_ops { 30 | fn op_add_local_const(store: &mut Store, local: u32, const_val: i64) -> Trap<()> { 31 | let left = store.get_local(local); 32 | let right = const_val; 33 | let res = left.wrapping_add(right); 34 | store.push(res); 35 | Ok(()) 36 | } 37 | /// many more "merged" opcode functions..... 38 | } 39 | ``` 40 | 41 | The compiler would make a closure: 42 | ```rust 43 | let local_idx = 0; // decoded from 'GetLocal(0)' op 44 | let const_val = 1234; // decoded from 'I64Const(1234)' op 45 | let merged_op = move |_state: &State, store: &mut Store| -> Trap<()> { 46 | i64_ops::op_add_local_const(store, local_idx, const_val); 47 | }; 48 | ``` 49 | 50 | ## Structure 51 | 52 | * Module - immutable 53 | * Function - immutable 54 | * CodeBlock - immutable 55 | * Instruction - immutable 56 | * VM - mutable 57 | 58 | ### Module 59 | An Module defines the static code for a program/script. It contains a list of Functions. 60 | Can have an optional `start` Function that is executed before any exported functions can 61 | be called. 62 | 63 | Layout: 64 | * Meta data{version, total size, # globals, # functions, min size required to start execution} 65 | * Export/globals 66 | * One or more Functions. Allow stream execution. 67 | 68 | ### Function 69 | * Type 70 | * Locals 71 | * Body - Intructions. 72 | 73 | ### CodeBlock 74 | * Meta{# locals, # instructions} 75 | 76 | ### Instruction 77 | 78 | * ... many standard ops: Add,Sub,Mul,Load,Store,etc... 79 | * Call - Call another Function inside the Module. 80 | * TailCall - Optimized version of `Call`. 81 | * RustCall - Call non-async Rust function. Easy to use for API bindings. 82 | * AsyncCall - Async call. 83 | 84 | #### Call/TailCall 85 | These are optimizations to avoid creating a boxed (heap allocated) future. 86 | 87 | #### RustCall 88 | For simple library functions. Don't allow it to call functions in the Module. 89 | 90 | #### AsyncCall 91 | Need to used a `BoxFuture` to avoid recursive async calls. 92 | 93 | ```rust 94 | use futures::future::{BoxFuture, FutureExt}; 95 | 96 | fn recursive() -> BoxFuture<'static, ()> { 97 | async move { 98 | recursive().await; 99 | recursive().await; 100 | }.boxed() 101 | } 102 | ``` 103 | 104 | ### CallStack 105 | Rust's async support could be used to make the VM stackless without the need for 106 | an internal callstack or C-style coroutine (task/stacklet/etc...). 107 | 108 | Stack of: 109 | * FunctionCtx - VM Function context(function idx, pc, locals) 110 | * Future - Rust Future 111 | 112 | ### VM 113 | The execution context for a Module. The VM holds an immutable reference to the Module. 114 | 115 | ### Stack free execution 116 | Loading/executing the Module in a VM should be async. 117 | 118 | Load: 119 | * Create a Module by compiling/loading it from a file. Should support async I/O. 120 | * Create a VM from the Module. (VM is mutable, Module is immutable). doesn't block. 121 | 122 | Call Function: Rust -> VM 123 | * VM.call(name). async. Can yield from 124 | 125 | -------------------------------------------------------------------------------- /src/vm.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::*; 4 | use crate::compiler::Compiler; 5 | use crate::error::*; 6 | 7 | /// VM Store - Mutable data 8 | #[derive(Default)] 9 | pub struct Store { 10 | pub mem: Vec, 11 | pub stack: Stack, 12 | } 13 | 14 | /// VM State - Immutable, only changes when loading a module. 15 | #[derive(Default)] 16 | pub struct State { 17 | funcs: Vec, 18 | // Loaded modules 19 | module_instances: Vec, 20 | modules: HashMap, 21 | } 22 | 23 | impl State { 24 | pub fn new() -> State { 25 | State { 26 | ..Default::default() 27 | } 28 | } 29 | 30 | pub fn load_file(&mut self, name: &str, file: &str) -> Result { 31 | if self.modules.contains_key(name) { 32 | return Err(Error::ModuleExists) 33 | } 34 | // load new module from file. 35 | let module = bwasm::Module::from_file(file)?; 36 | 37 | self.compile_module(name, &module) 38 | } 39 | 40 | fn compile_module(&mut self, name: &str, module: &bwasm::Module) -> Result { 41 | let mod_addr = self.module_instances.len() as ModuleInstanceAddr; 42 | // create new module instance. 43 | let mut mod_inst = ModuleInstance::new(); 44 | // compile functions 45 | let compiler = Compiler::new(module); 46 | self.funcs = compiler.compile()?; 47 | for addr in 0..self.funcs.len() { 48 | mod_inst.add_function(addr as u32); 49 | } 50 | // load exports 51 | for export in module.exports().into_iter() { 52 | match export.internal() { 53 | bwasm::Internal::Function(idx) => { 54 | //eprintln!("-Export function '{}' at {}", export.field(), *idx); 55 | mod_inst.add_export(export.field(), *idx)?; 56 | }, 57 | _ => { 58 | println!("Unhandled export: {:?}", export); 59 | }, 60 | } 61 | } 62 | // Finished compile module. 63 | self.module_instances.push(mod_inst); 64 | self.modules.insert(name.to_string(), mod_addr); 65 | Ok(mod_addr) 66 | } 67 | 68 | pub fn get_function(&self, addr: FuncAddr) -> Trap<&Function> { 69 | self.funcs.get(addr as usize).ok_or(TrapKind::InvalidFunctionAddr) 70 | } 71 | 72 | fn get_module_instance(&self, module: &str) -> Result<&ModuleInstance> { 73 | if let Some(addr) = self.modules.get(module) { 74 | if let Some(inst) = self.module_instances.get(*addr as usize) { 75 | return Ok(inst) 76 | } 77 | } 78 | Err(Error::ModuleNotFound) 79 | } 80 | 81 | pub fn get_exported(&self, module: &str, name: &str) -> Result { 82 | let mod_inst = self.get_module_instance(module)?; 83 | mod_inst.find_function(name) 84 | } 85 | 86 | pub fn invoke_function(&self, store: &mut Store, func_addr: FuncAddr, l0: &mut StackValue) -> Trap> { 87 | let func = self.get_function(func_addr)?; 88 | func.call(self, store, l0) 89 | } 90 | 91 | pub fn call(&self, store: &mut Store, func_addr: FuncAddr, params: &[Value]) -> Result { 92 | store.stack.push_params(params)?; 93 | let func = self.get_function(func_addr)?; 94 | let mut l0 = StackValue::from(params[0]); 95 | let ret = func.call(self, store, &mut l0)?; 96 | if let Some(ret) = ret { 97 | if let Some(ret_type) = func.ret_type() { 98 | Ok(Some(match ret_type { 99 | ValueType::I32 => Value::I32(ret.0 as _), 100 | ValueType::I64 => Value::I64(ret.0 as _), 101 | ValueType::F32 => Value::F32(f32::from_bits(ret.0 as _)), 102 | ValueType::F64 => Value::F64(f64::from_bits(ret.0 as _)), 103 | })) 104 | } else { 105 | Err(Error::RuntimeError(TrapKind::UnexpectedSignature)) 106 | } 107 | } else { 108 | Ok(None) 109 | } 110 | } 111 | } 112 | 113 | #[derive(Default)] 114 | pub struct VM { 115 | // Mutable store 116 | store: Store, 117 | 118 | /// Immutable state. Only mutable when loading modules. 119 | state: State, 120 | } 121 | 122 | impl VM { 123 | pub fn new() -> VM { 124 | VM { 125 | ..Default::default() 126 | } 127 | } 128 | 129 | pub fn load_file(&mut self, name: &str, file: &str) -> Result { 130 | self.state.load_file(name, file) 131 | } 132 | 133 | pub fn get_exported(&self, module: &str, name: &str) -> Result { 134 | self.state.get_exported(module, name) 135 | } 136 | 137 | pub fn call(&mut self, module: &str, name: &str, params: &[Value]) -> Result { 138 | let func_addr = self.state.get_exported(module, name)?; 139 | self.state.call(&mut self.store, func_addr, params) 140 | } 141 | } 142 | 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # s1vm 2 | 3 | A fast WebAssembly (wasm) interpreter written in 100% safe Rust. 4 | 5 | This project started with the idea to port [WASM3](https://github.com/wasm3/wasm3)'s 6 | VM design to safe Rust. 7 | 8 | Right now it is a Proof of Concept for trying out some ideas for making a fast VM interpreter in safe Rust (no JIT, no unsafe). 9 | 10 | See [Ideas.md](./Ideas.md) for some crazy ideas that might be used. 11 | 12 | ## VM Design 13 | 14 | `s1vm` gets most of it's speed from not using a loop to execute each opcode. Instead it "compiles" each WASM function using a nest of closure calls. 15 | 16 | ### Common loop based interpreter 17 | 18 | A simple interpreter VM would use a loop and a large switch (match in Rust) to execute opcodes. That requires multiple jumps (one unpredictable and one predictable jump) for each executed opcode, the unpredictable jump will stall the CPU slowing down execution. 19 | 20 | This design also doesn't allow for merging common opcode patterns (GetLocal, I32Const, I32Add). Merging opcodes can help eliminate alot of stack push/pop operations. 21 | 22 | ```rust 23 | fn execute_function(func: &Function, state: &State, store: &mut Store) -> Trap> { 24 | let code = &func.code; // a `Vec` to hold the current function's opcodes. 25 | let mut pc = 0usize; // PC = program counter. Index to the next opcode to execute. 26 | let pc_end = pc + code.len() - 1; 27 | 28 | // loop over the function's opcodes until the function returns or we reach the end of the opcodes. 29 | loop { 30 | use crate::isa::Instruction::*; // import opcodes: Br, Return, Call, etc.. 31 | let op = code[pc]; 32 | match op { // The CPU can't predict this jump 33 | Br(jump_pc) => { // Simple branch opcode (i.e. goto) 34 | pc = jump_pc as usize; 35 | continue; 36 | }, 37 | /* .. other conditional branch opcodes .. */ 38 | Return => { 39 | if func.ret_type().is_some() { 40 | // this function returns a value. Pop it from the stack. 41 | return Ok(Some(store.stack.pop_val()?)); 42 | } else { 43 | // no return value. 44 | return Ok(None); 45 | } 46 | }, 47 | Call(func_idx) => { 48 | // `func_idx` is the unique index for the function that this opcode wants to call. 49 | if let Some(ret) = state.invoke_function(store, func_idx)? { 50 | // If the function returns a value, push it onto the stack. 51 | store.stack.push_val(ret)?; 52 | } 53 | }, 54 | // in WASM locals are at fixed-offsets from the top of the stack. 55 | // They also include the parameters passed on the stack to a function. 56 | GetLocal(local_idx) => { 57 | store.stack.get_local(local_idx)?; 58 | }, 59 | SetLocal(local_idx) => { 60 | store.stack.set_local(local_idx)?; 61 | }, 62 | /* .. Get/Set global opcodes. */ 63 | /* .. Load/Store opcodes for reading/writing to memory. */ 64 | /* .. CurrentMemory & GrowMemory opcodes for allocating more memory. */ 65 | // opcodes for pushing i32/i64/f32/f64 constants onto the stack. 66 | I32Const(val) => store.stack.push(val)?, 67 | I64Const(val) => store.stack.push(val)?, 68 | F32Const(val) => store.stack.push(val)?, 69 | F64Const(val) => store.stack.push(val)?, 70 | 71 | I64Add => { 72 | let (left, right) = store.stack.pop_pair()? as (i64, i64); // pop 2 values from stack 73 | let res = left + right; // add the values. 74 | store.stck.push(res)?; 75 | }, 76 | /* .. lots of basic i32/i64/f32/f64 opcodes for add/sub/mul/div operations. */ 77 | } 78 | if pc == pc_end { 79 | break; // end of function's opcodes. 80 | } 81 | pc = pc + 1; // goto next opcode. 82 | } 83 | Ok(None) 84 | } 85 | ``` 86 | 87 | ### s1vm compiler 88 | 89 | The compiler needs to reconstruct the control-flow of each function into one or more Blocks, this will eliminate the need for the `pc` counter during runtime. 90 | 91 | When compiling opcodes the compiler keeps a stack of inputs (the outputs of earlier opcodes) that can be consumed by opcodes that pop values from the runtime stack. 92 | 93 | As the compiler is processing opcodes, it pushes `Input` values onto the inputs stack. Opcodes that need inputs will pop them from the inputs stack. This eliminates a lot of runtime stack push/pops. 94 | 95 | Take for example the opcodes for an `add` function: 96 | ``` 97 | local.get 0 -- Push `Input::Local(0)` 98 | local.get 1 -- Push `Input::Local(1)` 99 | i32.add -- Pop 2 inputs, generate closure for add opcode, push `Input::Op(closure)` 100 | return -- Pop 1 input, generate closure for return opcode and append it to the block. 101 | ``` 102 | 103 | This design allows the compiler to use more specialized code for each opcode based on the types of inputs and eliminated a large amount of runtime stack push/pops. 104 | 105 | Compiler types: 106 | ```rust 107 | // block type. 108 | enum BlockKind { 109 | Block, 110 | Loop, 111 | If, 112 | Else, 113 | } 114 | 115 | // Action is used for control-flow inside a function's blocks. 116 | enum Action { 117 | Return(Option), // return from the current function. 118 | End, // sub-block finish, continue the parent block. 119 | Branch(u32 /*block depth*/), // return to parent block with the same depth 120 | } 121 | 122 | // EvalFunc is for a blocks compiled opcodes. 123 | type EvalFunc = Box Trap>; 124 | 125 | // OpFunc is for compiled opcodes that have produce a value to be consume as an input. 126 | type OpFunc = Box Trap>; 127 | 128 | enum Input { 129 | Local(u32 /* local_idx */), // The input is a function local. 130 | Const(StackValue), // The input is a constant value. 131 | Op(OpFunc), // The input is the value produced by a compiled opcode. 132 | } 133 | 134 | struct Block { 135 | kind: BlockKind, // block type. Only used while compiling. 136 | depth: u32, // this block's depth. 137 | eval: Vec, // vector of compiled sub-blocks and opcodes. 138 | } 139 | 140 | ``` 141 | 142 | ### s1vm structures 143 | 144 | - immutable 145 | * `State` - Top-level struct that holds the loaded modules. 146 | * `Module` - Each module has a list of functions, some of those functions are exported to allow other modules or the host to call them. 147 | * `Function` - Hold the compiled code or bytecode for a function. 148 | - mutable 149 | * `Store` - Top-level mutable struct that hold the `Memory` and `Stack`. The `State` can be shared between multiple isolated instanace of the same WASM script. 150 | * Memory - Just an array of bytes `Vec`. 151 | * `Stack` - Holds a stack of values for opcodes that push/pop and for parameter passing when calling a function. Also helps track the call stack frames. 152 | * `VM` - Just wraps a `State` and `Store` instance. 153 | - other types 154 | * `Instruction` - a WASM opcode 155 | * `StackValue` - wraps a `u64` 156 | * `Trap` - A specialized `Result` type to handle normal function returns and VM errors (i.e. WASM runtime errors). 157 | 158 | ## Goals 159 | 160 | 1. Only use safe Rust. Crate marked `#![forbid(unsafe_code)]` 161 | 2. Support pause/resume. Either by using `async/await` or stack unwinding/rewinding. 162 | 3. Resource limits (CPU/Memory). Limiting or throttling CPU usage is useful for running sandboxed code. 163 | 164 | ## Benchmark 165 | 166 | Benchmark of `s1vm` against other wasm interpreters: 167 | - [WASM3](https://github.com/wasm3/wasm3) - C 168 | - [wasmi](https://github.com/paritytech/wasmi) - Rust 169 | 170 | - fib.wasm 35 171 | - wasm3 - 0.66 seconds 172 | - s1vm - 1.29 seconds 173 | - wasmi - 3.31 seconds 174 | 175 | - fib.wasm 41 176 | - wasm3 - 9.8 seconds 177 | - s1vm - 22.5 seconds 178 | - wasmi - 57.6 seconds 179 | 180 | ## TODOs 181 | 182 | - [ ] - Support calling host functions. 183 | - [ ] src/compiler.rs - Implement missing compiler opcodes. 184 | -------------------------------------------------------------------------------- /src/stack.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::error::*; 3 | use crate::value::*; 4 | 5 | pub const DEFAULT_STACK_LIMIT: usize = 1024 * 1024; 6 | pub const INIT_STACK_SIZE: usize = 1024; 7 | 8 | #[derive(Debug)] 9 | pub struct StackPtr(pub usize); 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct StackValue(pub u64); 13 | 14 | #[derive(Debug, Default, Clone, Copy)] 15 | pub struct Frame { 16 | /// Base Pointer - for params/locals. 17 | pub bp: usize, 18 | /// Stack Base Pointer - Base for push/pop, to make sure the function doesn't pop locals 19 | pub sbp: usize, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct Stack { 24 | stack: Vec, 25 | /// Current Frame 26 | frame: Frame, 27 | /// Maximum stack size. 28 | limit: usize, 29 | } 30 | 31 | impl Stack { 32 | pub fn new() -> Stack { 33 | Self::new_with_limit(DEFAULT_STACK_LIMIT) 34 | } 35 | 36 | pub fn new_with_limit(limit: usize) -> Stack { 37 | Stack { 38 | stack: Vec::with_capacity(INIT_STACK_SIZE), 39 | frame: Default::default(), 40 | limit, 41 | } 42 | } 43 | 44 | #[inline] 45 | pub fn len(&self) -> usize { 46 | self.stack.len() 47 | } 48 | 49 | /// How many values are on the current frame 50 | pub fn frame_size(&self) -> usize { 51 | self.len() - self.frame.sbp 52 | } 53 | 54 | fn check_overflow(&mut self, need: usize) -> Trap { 55 | let len = self.len(); 56 | let space = self.limit - len; 57 | if space < need { 58 | eprintln!("StackOverflow: limit={}, len={}", self.limit, len); 59 | return Err(TrapKind::StackOverflow); 60 | } 61 | 62 | // Return the current stack size. 63 | Ok(len) 64 | } 65 | 66 | /// Start a new stack frame by saving the current base pointer. 67 | pub fn push_frame(&mut self, params: usize, locals: usize) -> Trap { 68 | // Check if there are enough values on the current stack frame for 69 | // the new function's parameters. 70 | let cur_size = self.frame_size(); 71 | if cur_size < params { 72 | eprintln!("StackOverflow: frame size={}, needed params={}", cur_size, params); 73 | return Err(TrapKind::StackOverflow); 74 | } 75 | 76 | // save old frame 77 | let old_frame = self.frame; 78 | 79 | // Include the params in the new stack frame. 80 | let bp = self.len() - params; 81 | self.frame = Frame{ 82 | bp, 83 | sbp: bp + locals, 84 | }; 85 | 86 | if locals > 0 { 87 | self.reserve_locals(locals); 88 | } 89 | 90 | Ok(old_frame) 91 | } 92 | 93 | pub fn reserve_locals(&mut self, locals: usize) { 94 | //eprintln!("reserve_locals: len={}, locals={}", self.stack.len(), locals); 95 | // Push initial value for locals. 96 | // TODO: Try improving initialization of locals. 97 | for _idx in 0..locals { 98 | //eprintln!("Push local({})", _idx); 99 | self.stack.push(StackValue(0)); 100 | } 101 | //eprintln!("reserve_locals: end len={}, locals={}", self.stack.len(), locals); 102 | } 103 | 104 | /// Remove current stack frame and restore previous frame. 105 | pub fn pop_frame(&mut self, old_frame: Frame) { 106 | // Drop current stack frame values. 107 | self.stack.truncate(self.frame.bp); 108 | // Restore old frame 109 | self.frame = old_frame; 110 | } 111 | 112 | pub fn push_params(&mut self, params: &[Value]) -> Trap { 113 | // Check for stackoverflow and get current stack size. 114 | let len = self.check_overflow(params.len())?; 115 | 116 | for val in params { 117 | self.stack.push(StackValue::from(*val)); 118 | } 119 | // return original stack size. 120 | Ok(len) 121 | } 122 | 123 | pub fn drop_values(&mut self, count: u32) -> Trap<()> { 124 | let len = self.len(); 125 | let new_len = len 126 | .checked_sub(count as usize) 127 | .ok_or(TrapKind::StackOverflow)?; 128 | self.stack.truncate(new_len); 129 | Ok(()) 130 | } 131 | 132 | #[inline] 133 | pub fn tee_local(&mut self, local: LocalIdx) -> Trap<()> { 134 | // Copy value from stack 135 | let val = self.top_val()?; 136 | 137 | // save to local 138 | let idx = self.frame.bp + local as usize; 139 | self.stack[idx] = val; 140 | Ok(()) 141 | } 142 | 143 | #[inline] 144 | pub fn set_local(&mut self, local: LocalIdx) -> Trap<()> { 145 | // pop value from stack 146 | let val = self.pop_val()?; 147 | 148 | // save to local 149 | let idx = self.frame.bp + local as usize; 150 | self.stack[idx] = val; 151 | Ok(()) 152 | } 153 | 154 | #[inline] 155 | pub fn get_local(&mut self, local: LocalIdx) -> Trap<()> { 156 | let idx = self.frame.bp + local as usize; 157 | 158 | self.push_val(self.stack[idx]) 159 | } 160 | 161 | #[inline] 162 | pub fn set_local_val(&mut self, local: LocalIdx, val: StackValue, l0: &mut StackValue) { 163 | if local == 0 { 164 | *l0 = val; 165 | return; 166 | } 167 | let idx = self.frame.bp + local as usize; 168 | 169 | self.stack[idx] = val; 170 | } 171 | 172 | #[inline] 173 | pub fn get_local_val(&mut self, local: LocalIdx, l0: &mut StackValue) -> StackValue { 174 | //eprintln!("get_local_val: idx={}, len={}", local, self.stack.len()); 175 | if local == 0 { 176 | return *l0; 177 | } 178 | let idx = self.frame.bp + local as usize; 179 | 180 | self.stack[idx] 181 | } 182 | 183 | #[inline] 184 | pub fn push_val(&mut self, val: StackValue) -> Trap<()> { 185 | if self.len() >= self.limit { 186 | eprintln!("StackOverflow: limit={}, len={}", self.limit, self.len()); 187 | return Err(TrapKind::StackOverflow); 188 | } 189 | //eprintln!("-- Push: {:?}", val); 190 | self.stack.push(val); 191 | Ok(()) 192 | } 193 | 194 | #[inline] 195 | pub fn push_values(&mut self, vals: &[StackValue]) -> Trap<()> { 196 | // Check for stack overflow. 197 | self.len().checked_add(vals.len()) 198 | .filter(|l| l < &self.limit) 199 | .ok_or_else(|| { 200 | eprintln!("StackOverflow: limit={}, len={}", self.limit, self.len()); 201 | TrapKind::StackOverflow 202 | })?; 203 | //eprintln!("-- Push: {:?}", val); 204 | self.stack.extend_from_slice(vals); 205 | Ok(()) 206 | } 207 | 208 | pub fn pop_typed(&mut self, val_type: ValueType) -> Trap { 209 | match val_type { 210 | ValueType::I32 => self.pop().map(Value::I32), 211 | ValueType::I64 => self.pop().map(Value::I64), 212 | ValueType::F32 => self.pop().map(Value::F32), 213 | ValueType::F64 => self.pop().map(Value::F64), 214 | } 215 | } 216 | 217 | #[inline] 218 | pub fn pop_val(&mut self) -> Trap { 219 | self.stack.pop().ok_or(TrapKind::StackOverflow) 220 | } 221 | 222 | #[inline] 223 | pub fn top_val(&mut self) -> Trap { 224 | self.stack.last().map(|x| *x) 225 | .ok_or(TrapKind::StackOverflow) 226 | } 227 | 228 | /// Apply a 'unop' to top value, replacing it with the results. 229 | #[inline] 230 | pub fn unop(&mut self, op: F) -> Trap<()> 231 | where F: FnOnce(&mut StackValue) -> Trap<()> 232 | { 233 | let mut val = self.stack.last_mut() 234 | .ok_or(TrapKind::StackOverflow)?; 235 | op(&mut val) 236 | } 237 | 238 | /// Apply a `binop` to the top two values, replacing them with the results. 239 | #[inline] 240 | pub fn binop(&mut self, op: F) -> Trap<()> 241 | where F: FnOnce(&mut StackValue, StackValue) -> Trap<()> 242 | { 243 | let right = self.pop_val()?; 244 | let mut left = self.stack.last_mut() 245 | .ok_or(TrapKind::StackOverflow)?; 246 | op(&mut left, right) 247 | } 248 | } 249 | 250 | impl Default for Stack { 251 | fn default() -> Stack { 252 | Self::new() 253 | } 254 | } 255 | 256 | pub trait FromValue 257 | where 258 | Self: Sized, 259 | { 260 | fn from_value(val: StackValue) -> Self; 261 | } 262 | 263 | pub trait FromStack: Sized { 264 | fn push(&mut self, val: T) -> Trap<()>; 265 | fn pop(&mut self) -> Trap; 266 | 267 | fn pop_pair(&mut self) -> Trap<(T, T)>; 268 | } 269 | 270 | macro_rules! impl_stack_value { 271 | ($($t:ty),*) => { 272 | $( 273 | impl FromValue for $t { 274 | fn from_value(StackValue(val): StackValue) -> Self { 275 | val as _ 276 | } 277 | } 278 | 279 | impl From<$t> for StackValue { 280 | fn from(other: $t) -> StackValue { 281 | StackValue(other as _) 282 | } 283 | } 284 | 285 | impl FromStack<$t> for Stack { 286 | #[inline] 287 | fn push(&mut self, val: $t) -> Trap<()> { 288 | self.push_val(StackValue(val as _)) 289 | } 290 | 291 | #[inline] 292 | fn pop(&mut self) -> Trap<$t> { 293 | self.pop_val().map(|x| x.0 as _) 294 | } 295 | 296 | fn pop_pair(&mut self) -> Trap<($t, $t)> { 297 | let right = self.pop()?; 298 | let left = self.pop()?; 299 | Ok((left, right)) 300 | } 301 | } 302 | )* 303 | }; 304 | } 305 | 306 | impl_stack_value!(i8, u8, i16, u16, i32, u32, i64, u64); 307 | 308 | macro_rules! impl_stack_value_float { 309 | ($($t:ty),*) => { 310 | $( 311 | impl FromValue for $t { 312 | fn from_value(StackValue(val): StackValue) -> Self { 313 | <$t>::from_bits(val as _) 314 | } 315 | } 316 | 317 | impl From<$t> for StackValue { 318 | fn from(other: $t) -> Self { 319 | StackValue(other.to_bits() as _) 320 | } 321 | } 322 | 323 | impl FromStack<$t> for Stack { 324 | #[inline] 325 | fn push(&mut self, val: $t) -> Trap<()> { 326 | self.push_val(StackValue(val.to_bits() as _)) 327 | } 328 | 329 | #[inline] 330 | fn pop(&mut self) -> Trap<$t> { 331 | self.pop_val().map(|x| <$t>::from_bits(x.0 as _)) 332 | } 333 | 334 | fn pop_pair(&mut self) -> Trap<($t, $t)> { 335 | let right = self.pop()?; 336 | let left = self.pop()?; 337 | Ok((left, right)) 338 | } 339 | } 340 | )* 341 | }; 342 | } 343 | 344 | impl_stack_value_float!(f32, f64); 345 | 346 | impl From for StackValue { 347 | fn from(val: Value) -> StackValue { 348 | match val { 349 | Value::I32(v) => StackValue(v as _), 350 | Value::I64(v) => StackValue(v as _), 351 | Value::F32(v) => StackValue(v.to_bits() as _), 352 | Value::F64(v) => StackValue(v.to_bits() as _), 353 | } 354 | } 355 | } 356 | 357 | -------------------------------------------------------------------------------- /src/compiler.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::*; 3 | use crate::function::*; 4 | use crate::error::*; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq)] 7 | enum BlockKind { 8 | Block, 9 | Loop, 10 | If, 11 | Else, 12 | } 13 | 14 | #[derive(Debug, Clone)] 15 | pub enum Action { 16 | Return(Option), 17 | End, 18 | Branch(u32), 19 | } 20 | 21 | type Local = u32; 22 | 23 | type EvalFunc = Box Trap>; 24 | 25 | type OpFunc = Box Trap>; 26 | 27 | enum Input { 28 | Local(Local), 29 | Const(StackValue), 30 | Op(OpFunc), 31 | } 32 | 33 | impl Input { 34 | pub fn resolv(&self, state: &vm::State, store: &mut Store, l0: &mut StackValue) -> Trap { 35 | match self { 36 | Input::Local(0) => { 37 | Ok(*l0) 38 | }, 39 | Input::Local(local_idx) => { 40 | Ok(store.stack.get_local_val(*local_idx, l0)) 41 | }, 42 | Input::Const(const_val) => { 43 | Ok(*const_val) 44 | }, 45 | Input::Op(closure) => closure(state, store, l0), 46 | } 47 | } 48 | } 49 | 50 | pub trait Eval { 51 | fn eval(&self, state: &vm::State, store: &mut Store, l0: &mut StackValue) -> Trap; 52 | } 53 | 54 | impl Eval for Local { 55 | fn eval(&self, _state: &vm::State, store: &mut Store, l0: &mut StackValue) -> Trap { 56 | Ok(store.stack.get_local_val(*self, l0)) 57 | } 58 | } 59 | 60 | impl Eval for StackValue { 61 | fn eval(&self, _state: &vm::State, _store: &mut Store, _l0: &mut StackValue) -> Trap { 62 | Ok(*self) 63 | } 64 | } 65 | 66 | impl Eval for OpFunc { 67 | fn eval(&self, state: &vm::State, store: &mut Store, l0: &mut StackValue) -> Trap { 68 | self(state, store, l0) 69 | } 70 | } 71 | 72 | struct Block 73 | { 74 | kind: BlockKind, 75 | depth: u32, 76 | eval: Vec, 77 | } 78 | 79 | impl Block { 80 | pub fn new(kind: BlockKind, depth: u32) -> Self { 81 | Self { 82 | kind, 83 | depth, 84 | eval: vec![], 85 | } 86 | } 87 | 88 | pub fn depth(&self) -> u32 { 89 | self.depth 90 | } 91 | 92 | pub fn push(&mut self, f: EvalFunc) { 93 | self.eval.push(f); 94 | } 95 | 96 | pub fn run(&self, state: &vm::State, store: &mut Store, _l0: &mut StackValue) -> Trap { 97 | //eprintln!("---- run block: {:?}, len={}, depth={}", self.kind, self.eval.len(), self.depth); 98 | 'repeat: loop { 99 | for f in self.eval.iter() { 100 | let ret = f(state, store, _l0)?; 101 | //eprintln!("---- evaled: ret = {:?}", ret); 102 | match ret { 103 | Action::Return(_) => { 104 | // Keep passing return value up, until we get to the function block. 105 | return Ok(ret); 106 | }, 107 | Action::End => { 108 | // sub-block finished, continue this block. 109 | continue; 110 | }, 111 | Action::Branch(depth) => { 112 | //eprintln!("---- Branch({})", depth); 113 | if depth > 0 { 114 | // keep passing action lower. 115 | return Ok(Action::Branch(depth-1)); 116 | } else { 117 | // handle Branch here. 118 | if self.kind == BlockKind::Loop { 119 | // Repeat loop block. 120 | continue 'repeat; 121 | } else { 122 | // Normal block, If, or Else. Just exit on branch. 123 | return Ok(Action::End); 124 | } 125 | } 126 | } 127 | } 128 | } 129 | // End of block. 130 | return Ok(Action::End); 131 | } 132 | } 133 | } 134 | 135 | pub struct State { 136 | values: Vec, 137 | pub depth: u32, 138 | pub pc: usize, 139 | } 140 | 141 | impl State { 142 | pub fn new() -> Self { 143 | Self { 144 | values: vec![], 145 | depth: 0, 146 | pc: 0, 147 | } 148 | } 149 | 150 | fn pop(&mut self) -> Result { 151 | self.values.pop() 152 | .ok_or_else(|| { 153 | Error::ValidationError(format!("Value stack empty")) 154 | }) 155 | } 156 | 157 | fn pop_n(&mut self, n: usize) -> Result> { 158 | let at = self.values.len().checked_sub(n) 159 | .ok_or_else(|| { 160 | Error::ValidationError(format!("Value stack empty")) 161 | })?; 162 | Ok(self.values.split_off(at)) 163 | } 164 | 165 | fn push(&mut self, input: Input) { 166 | self.values.push(input); 167 | } 168 | 169 | fn len(&self) -> usize { 170 | self.values.len() 171 | } 172 | } 173 | 174 | macro_rules! impl_ops_match_input { 175 | ($name:ident, $op:expr) => { 176 | { 177 | match $name { 178 | Input::Local($name) => { 179 | $op 180 | }, 181 | Input::Const($name) => { 182 | $op 183 | }, 184 | Input::Op($name) => { 185 | $op 186 | } 187 | } 188 | } 189 | }; 190 | } 191 | 192 | macro_rules! impl_unops_match_input { 193 | ($state:ident, $left:ident, $op:expr) => { 194 | { 195 | let left = $state.pop()?; 196 | $state.push(Input::Op(impl_ops_match_input!(left, { 197 | Box::new(move |state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 198 | let $left = left.eval(state, store, l0)?.0; 199 | let res = $op; 200 | Ok(StackValue(res as _)) 201 | }) 202 | }))); 203 | } 204 | }; 205 | ($state:ident, $vm_state:ident, $store:ident, $l0:ident, $left:ident, $op:expr) => { 206 | { 207 | let left = $state.pop()?; 208 | $state.push(Input::Op(impl_ops_match_input!(left, { 209 | Box::new(move |$vm_state: &vm::State, $store: &mut Store, $l0: &mut StackValue| -> Trap { 210 | let $left = left.eval($vm_state, $store, $l0)?.0; 211 | let res = $op; 212 | Ok(StackValue(res as _)) 213 | }) 214 | }))); 215 | } 216 | }; 217 | } 218 | 219 | macro_rules! impl_binops_match_input { 220 | ($state:ident, $left:ident, $right:ident, $op:expr) => { 221 | { 222 | let right = $state.pop()?; 223 | let left = $state.pop()?; 224 | $state.push(Input::Op(impl_ops_match_input!(left, { 225 | impl_ops_match_input!(right, { 226 | Box::new(move |state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 227 | let $left = left.eval(state, store, l0)?.0; 228 | let $right = right.eval(state, store, l0)?.0; 229 | let res = $op; 230 | Ok(StackValue(res as _)) 231 | }) 232 | }) 233 | }))); 234 | } 235 | }; 236 | } 237 | 238 | pub struct Compiler { 239 | module: bwasm::Module, 240 | compiled: Vec, 241 | 242 | func_idx: u32, 243 | ret_type: Option, 244 | code: Vec, 245 | pc_end: usize, 246 | } 247 | 248 | impl Compiler { 249 | pub fn new(module: &bwasm::Module) -> Self { 250 | Self { 251 | module: module.clone(), 252 | compiled: vec![], 253 | 254 | func_idx: 0, 255 | ret_type: None, 256 | code: vec![], 257 | pc_end: 0, 258 | } 259 | } 260 | 261 | pub fn compile(mut self) -> Result> { 262 | let len = self.module.functions().len() as u32; 263 | for idx in 0..len { 264 | self.compile_function(idx)?; 265 | } 266 | Ok(self.compiled) 267 | } 268 | 269 | fn compile_function(&mut self, func_idx: u32) -> Result<()> { 270 | self.func_idx = func_idx; 271 | let func = self.module.get_func(func_idx) 272 | .ok_or(Error::FuncNotFound)?; 273 | 274 | if func.is_imported() { 275 | return Ok(()); 276 | } 277 | // Compile function into a closure 278 | self.code = func.instructions().to_vec(); 279 | self.ret_type = func.return_type().map(ValueType::from); 280 | self.pc_end = self.code.len(); 281 | 282 | let mut state = State::new(); 283 | let block = self.compile_block(&mut state, BlockKind::Block)?; 284 | 285 | self.compiled.push(Function::new(func, 286 | Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap> 287 | { 288 | match block.run(state, store, _l0)? { 289 | Action::Return(ret_value) => { 290 | //eprintln!("--- Function return: {:?}", ret_value); 291 | return Ok(ret_value); 292 | }, 293 | _ => { 294 | unreachable!("Compiled function missing 'Return' action."); 295 | }, 296 | } 297 | }))); 298 | 299 | //eprintln!("---------- depth = {}, values = {}", state.depth, state.len()); 300 | Ok(()) 301 | } 302 | 303 | fn compile_block(&self, state: &mut State, kind: BlockKind) -> Result { 304 | let mut block = Block::new(kind, state.depth); 305 | //eprintln!("compile block: depth: {} {:?}, stack: {}", block.depth(), kind, state.len()); 306 | state.depth += 1; 307 | if state.depth > 4 { 308 | panic!("compile overflow, increase the max depth if needed"); 309 | } 310 | // compile function opcodes. 311 | loop { 312 | use parity_wasm::elements::Instruction::*; 313 | if state.pc > self.pc_end { 314 | break; 315 | } 316 | let pc = state.pc; 317 | let op = &self.code[pc]; 318 | //eprintln!("compile {}: {:?}", pc, op); 319 | match op { 320 | Block(_) => { 321 | state.pc += 1; 322 | let sub_block = self.compile_block(state, BlockKind::Block)?; 323 | block.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap { 324 | sub_block.run(state, store, _l0) 325 | })); 326 | }, 327 | Loop(_) => { 328 | state.pc += 1; 329 | let loop_block = self.compile_loop(state)?; 330 | block.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap { 331 | loop_block.run(state, store, _l0) 332 | })); 333 | }, 334 | If(_) => { 335 | state.pc += 1; 336 | self.compile_if(&mut block, state)?; 337 | }, 338 | Else => { 339 | match kind { 340 | BlockKind::If => { 341 | break; 342 | }, 343 | _ => { 344 | return Err(Error::ValidationError(format!("invalid 'else' block, missing 'if'"))); 345 | }, 346 | } 347 | }, 348 | End => { 349 | if block.depth() == 0 && state.len() > 0 { 350 | self.emit_return(state, &mut block)?; 351 | } 352 | break; 353 | }, 354 | Return => { 355 | self.emit_return(state, &mut block)?; 356 | }, 357 | Br(block_depth) => { 358 | self.compile_br(&mut block, *block_depth)?; 359 | }, 360 | BrIf(block_depth) => { 361 | self.compile_br_if(&mut block, state, *block_depth)?; 362 | }, 363 | BrTable(ref _br_table) => { 364 | todo!(""); 365 | }, 366 | 367 | Call(func_idx) => { 368 | let func_idx = *func_idx; 369 | let func = self.module.get_func(func_idx) 370 | .ok_or(Error::FuncNotFound)?; 371 | let count = func.param_count(); 372 | //eprintln!("Call: params={}", count); 373 | if count > 1 { 374 | let params = state.pop_n(count as usize - 1)?; 375 | impl_unops_match_input!(state, vm_state, store, l0, val, { 376 | let mut val = StackValue(val); 377 | store.stack.push_val(val)?; 378 | // Resolve inputs and .. 379 | let params = params.iter().map(|p| { 380 | p.resolv(vm_state, store, l0) 381 | }).collect::, _>>()?; 382 | // .. push the values onto the stack. 383 | store.stack.push_values(¶ms[..])?; 384 | if let Some(ret) = vm_state.invoke_function(store, func_idx, &mut val)? { 385 | ret.0 386 | } else { 387 | 0 388 | } 389 | }); 390 | } else { 391 | impl_unops_match_input!(state, vm_state, store, l0, val, { 392 | let mut val = StackValue(val); 393 | if let Some(ret) = vm_state.invoke_function(store, func_idx, &mut val)? { 394 | ret.0 395 | } else { 396 | 0 397 | } 398 | }); 399 | } 400 | }, 401 | 402 | GetLocal(local_idx) => { 403 | state.push(Input::Local(*local_idx)); 404 | }, 405 | SetLocal(set_idx) => { 406 | let set_idx = *set_idx; 407 | let val = state.pop()?; 408 | if set_idx == 0 { 409 | match val { 410 | Input::Local(0) => { 411 | // noop. Get local 0 and set local 0. 412 | }, 413 | Input::Local(local_idx) => { 414 | block.push(Box::new(move |_state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 415 | let val = store.stack.get_local_val(local_idx, l0); 416 | *l0 = val; 417 | Ok(Action::End) 418 | })); 419 | }, 420 | Input::Const(const_val) => { 421 | block.push(Box::new(move |_state: &vm::State, _store: &mut Store, l0: &mut StackValue| -> Trap { 422 | *l0 = const_val; 423 | Ok(Action::End) 424 | })); 425 | }, 426 | Input::Op(closure) => { 427 | block.push(Box::new(move |state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 428 | *l0 = closure(state, store, l0)?; 429 | Ok(Action::End) 430 | })); 431 | }, 432 | } 433 | } else { 434 | block.push(match val { 435 | Input::Local(0) => { 436 | Box::new(move |_state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 437 | let val = *l0; 438 | store.stack.set_local_val(set_idx, val, l0); 439 | Ok(Action::End) 440 | }) 441 | }, 442 | Input::Local(local_idx) => { 443 | Box::new(move |_state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 444 | let val = store.stack.get_local_val(local_idx, l0); 445 | store.stack.set_local_val(set_idx, val, l0); 446 | Ok(Action::End) 447 | }) 448 | }, 449 | Input::Const(const_val) => { 450 | Box::new(move |_state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 451 | let val = const_val; 452 | store.stack.set_local_val(set_idx, val, l0); 453 | Ok(Action::End) 454 | }) 455 | }, 456 | Input::Op(closure) => { 457 | Box::new(move |state: &vm::State, store: &mut Store, l0: &mut StackValue| -> Trap { 458 | let val = closure(state, store, l0)?; 459 | store.stack.set_local_val(set_idx, val, l0); 460 | Ok(Action::End) 461 | }) 462 | }, 463 | }); 464 | } 465 | }, 466 | TeeLocal(set_idx) => { 467 | let set_idx = *set_idx; 468 | impl_unops_match_input!(state, vm_state, store, l0, val, { 469 | store.stack.set_local_val(set_idx, StackValue(val), l0); 470 | val 471 | }); 472 | }, 473 | I32Const(val) => { 474 | state.push(Input::Const(StackValue(*val as _))); 475 | }, 476 | I64Const(val) => { 477 | state.push(Input::Const(StackValue(*val as _))); 478 | }, 479 | 480 | I32Add => i32_ops::add(state)?, 481 | I32Sub => i32_ops::sub(state)?, 482 | I32LtS => i32_ops::lt_s(state)?, 483 | I32Eq => i32_ops::eq(state)?, 484 | I32Eqz => i32_ops::eqz(state)?, 485 | 486 | I64Add => i64_ops::add(state)?, 487 | I64Sub => i64_ops::sub(state)?, 488 | I64LtS => i64_ops::lt_s(state)?, 489 | I64Eq => i64_ops::eq(state)?, 490 | I64Eqz => i64_ops::eqz(state)?, 491 | op => todo!("implment opcode: {:?}", op), 492 | }; 493 | state.pc += 1; 494 | } 495 | 496 | state.depth -= 1; 497 | //eprintln!("end block: depth: {} {:?}, stack: {}", block.depth(), kind, state.len()); 498 | Ok(block) 499 | } 500 | 501 | fn emit_return(&self, state: &mut State, block: &mut Block) -> Result<()> { 502 | if self.ret_type.is_some() { 503 | let ret = state.pop()?; 504 | match ret { 505 | Input::Local(local_idx) => { 506 | block.push(Box::new(move |_state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap { 507 | let ret = store.stack.get_local_val(local_idx, _l0); 508 | Ok(Action::Return(Some(StackValue(ret.0 as _)))) 509 | })); 510 | }, 511 | Input::Const(const_val) => { 512 | block.push(Box::new(move |_state: &vm::State, _store: &mut Store, _l0: &mut StackValue| -> Trap { 513 | let ret = const_val; 514 | Ok(Action::Return(Some(StackValue(ret.0 as _)))) 515 | })); 516 | }, 517 | Input::Op(closure) => { 518 | block.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap { 519 | let ret = closure(state, store, _l0)?; 520 | Ok(Action::Return(Some(StackValue(ret.0 as _)))) 521 | })); 522 | }, 523 | } 524 | } else { 525 | block.push(Box::new(move |_state: &vm::State, _store: &mut Store, _l0: &mut StackValue| -> Trap { 526 | //eprintln!("--- run compiled RETURN: no value"); 527 | Ok(Action::Return(None)) 528 | })); 529 | } 530 | Ok(()) 531 | } 532 | 533 | fn compile_loop(&self, state: &mut State) -> Result { 534 | self.compile_block(state, BlockKind::Loop) 535 | } 536 | 537 | fn compile_br(&self, block: &mut Block, block_depth: u32) -> Result<()> { 538 | //eprintln!("emit br: {:?}", block_depth); 539 | block.push(Box::new(move |_state: &vm::State, _store: &mut Store, _l0: &mut StackValue| -> Trap { 540 | Ok(Action::Branch(block_depth)) 541 | })); 542 | Ok(()) 543 | } 544 | 545 | fn compile_br_if(&self, block: &mut Block, state: &mut State, block_depth: u32) -> Result<()> { 546 | //eprintln!("emit br_if: {:?}", block_depth); 547 | // pop condition value. 548 | let val = state.pop()?; 549 | match val { 550 | Input::Op(closure) => { 551 | block.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap 552 | { 553 | let val = closure(state, store, _l0)?; 554 | if val.0 != 0 { 555 | //eprintln!("branch: {:?}", val); 556 | Ok(Action::Branch(block_depth)) 557 | } else { 558 | //eprintln!("continue: {:?}", val); 559 | Ok(Action::End) 560 | } 561 | })); 562 | }, 563 | _ => { 564 | block.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap 565 | { 566 | let val = val.resolv(state, store, _l0)?; 567 | if val.0 != 0 { 568 | //eprintln!("branch: {:?}", val); 569 | Ok(Action::Branch(block_depth)) 570 | } else { 571 | //eprintln!("continue: {:?}", val); 572 | Ok(Action::End) 573 | } 574 | })); 575 | }, 576 | } 577 | Ok(()) 578 | } 579 | 580 | fn compile_if(&self, parent: &mut Block, state: &mut State) -> Result<()> { 581 | // pop condition value. 582 | let val = state.pop()?; 583 | 584 | // compile 'If' block. 585 | let if_block = self.compile_block(state, BlockKind::If)?; 586 | 587 | // Check for Else block 588 | use parity_wasm::elements::Instruction::*; 589 | let else_block = match &self.code[state.pc] { 590 | Else => { 591 | Some(self.compile_else(state)?) 592 | }, 593 | End => { 594 | None 595 | }, 596 | _ => { 597 | unreachable!("missing end of 'If' block"); 598 | } 599 | }; 600 | 601 | // Build closure. 602 | if let Some(else_block) = else_block { 603 | match val { 604 | Input::Op(closure) => { 605 | parent.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap 606 | { 607 | let val = closure(state, store, _l0)?; 608 | if val.0 == 0 { 609 | else_block.run(state, store, _l0) 610 | } else { 611 | if_block.run(state, store, _l0) 612 | } 613 | })); 614 | }, 615 | _ => { 616 | parent.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap 617 | { 618 | let val = val.resolv(state, store, _l0)?; 619 | if val.0 == 0 { 620 | else_block.run(state, store, _l0) 621 | } else { 622 | if_block.run(state, store, _l0) 623 | } 624 | })); 625 | }, 626 | } 627 | } else { 628 | match val { 629 | Input::Op(closure) => { 630 | parent.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap 631 | { 632 | let val = closure(state, store, _l0)?; 633 | if val.0 == 0 { 634 | Ok(Action::End) 635 | } else { 636 | if_block.run(state, store, _l0) 637 | } 638 | })); 639 | }, 640 | _ => { 641 | parent.push(Box::new(move |state: &vm::State, store: &mut Store, _l0: &mut StackValue| -> Trap 642 | { 643 | let val = val.resolv(state, store, _l0)?; 644 | if val.0 == 0 { 645 | Ok(Action::End) 646 | } else { 647 | if_block.run(state, store, _l0) 648 | } 649 | })); 650 | }, 651 | } 652 | } 653 | Ok(()) 654 | } 655 | 656 | fn compile_else(&self, state: &mut State) -> Result { 657 | self.compile_block(state, BlockKind::Else) 658 | } 659 | } 660 | 661 | macro_rules! impl_int_binops { 662 | ($name: ident, $type: ty, $op: ident) => { 663 | pub fn $name(state: &mut State) -> Result<()> { 664 | impl_binops_match_input!(state, left, right, { 665 | (left as $type).$op(right as $type) 666 | }); 667 | Ok(()) 668 | } 669 | }; 670 | ($name: ident, $type: ty, $op: ident, $as_type: ty) => { 671 | pub fn $name(state: &mut State) -> Result<()> { 672 | impl_binops_match_input!(state, left, right, { 673 | (left as $type).$op(right as $type) as $as_type 674 | }); 675 | Ok(()) 676 | } 677 | }; 678 | ($name: ident, $type: ty, $type2: ty, $op: ident, $as_type: ty) => { 679 | pub fn $name(state: &mut State) -> Result<()> { 680 | impl_binops_match_input!(state, left, right, { 681 | (left as $type).$op(right as $type2) as $as_type 682 | }); 683 | Ok(()) 684 | } 685 | }; 686 | ($name: ident, $type: ty, $op: ident, $as_type: ty, $mask: expr) => { 687 | pub fn $name(state: &mut State) -> Result<()> { 688 | impl_binops_match_input!(state, left, right, { 689 | let right = (right as $type) & $mask; 690 | (left as $type).$op(right as u32) as $as_type 691 | }); 692 | Ok(()) 693 | } 694 | }; 695 | } 696 | 697 | macro_rules! impl_int_binops_div { 698 | ($name: ident, $type: ty, $op: ident, $as_type: ty) => { 699 | pub fn $name(state: &mut State) -> Result<()> { 700 | impl_binops_match_input!(state, left, right, { 701 | let res = (left as $type).$op(right as $type) 702 | .ok_or_else(|| { 703 | if (right as $type) == 0 { 704 | TrapKind::DivisionByZero 705 | } else { 706 | TrapKind::InvalidConversionToInt 707 | } 708 | })?; 709 | (res as $as_type) 710 | }); 711 | Ok(()) 712 | } 713 | }; 714 | } 715 | 716 | macro_rules! impl_int_relops { 717 | ($name: ident, $type: ty, $relop: expr) => { 718 | pub fn $name(state: &mut State) -> Result<()> { 719 | impl_unops_match_input!(state, left, { 720 | $relop(left as $type) 721 | }); 722 | Ok(()) 723 | } 724 | }; 725 | ($name: ident, $type: ty, $type2: ty, $relop: expr) => { 726 | pub fn $name(state: &mut State) -> Result<()> { 727 | impl_binops_match_input!(state, left, right, { 728 | $relop(left as $type, right as $type2) 729 | }); 730 | Ok(()) 731 | } 732 | }; 733 | } 734 | 735 | macro_rules! impl_numeric_ops { 736 | ($op_mod: ident, $type: ty, $type_u: ty) => { 737 | #[allow(dead_code)] 738 | mod $op_mod { 739 | use std::ops::*; 740 | use super::*; 741 | 742 | pub fn load(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 743 | todo!(); 744 | } 745 | pub fn load8_s(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 746 | todo!(); 747 | } 748 | pub fn load8_u(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 749 | todo!(); 750 | } 751 | pub fn load16_s(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 752 | todo!(); 753 | } 754 | pub fn load16_u(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 755 | todo!(); 756 | } 757 | pub fn load32_s(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 758 | todo!(); 759 | } 760 | pub fn load32_u(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 761 | todo!(); 762 | } 763 | 764 | pub fn store(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 765 | todo!(); 766 | } 767 | pub fn store8(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 768 | todo!(); 769 | } 770 | pub fn store16(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 771 | todo!(); 772 | } 773 | pub fn store32(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 774 | todo!(); 775 | } 776 | 777 | impl_int_relops!(clz, $type, |val: $type| { 778 | val.leading_zeros() 779 | }); 780 | impl_int_relops!(ctz, $type, |val: $type| { 781 | val.trailing_zeros() 782 | }); 783 | impl_int_relops!(popcnt, $type, |val: $type| { 784 | val.count_ones() 785 | }); 786 | 787 | impl_int_binops!(add, $type, wrapping_add); 788 | impl_int_binops!(sub, $type, wrapping_sub); 789 | 790 | impl_int_binops!(mul, $type, wrapping_mul); 791 | 792 | impl_int_binops_div!(div_s, $type, checked_div, i64); 793 | impl_int_binops_div!(div_u, $type, checked_div, u64); 794 | impl_int_binops_div!(rem_s, $type, checked_rem, i64); 795 | impl_int_binops_div!(rem_u, $type, checked_rem, u64); 796 | 797 | impl_int_binops!(and, $type, bitand); 798 | impl_int_binops!(or, $type, bitor); 799 | impl_int_binops!(xor, $type, bitxor); 800 | impl_int_binops!(shl, $type, wrapping_shl, $type_u, 0x1F); 801 | impl_int_binops!(shr_s, $type, wrapping_shr, $type_u, 0x1F); 802 | impl_int_binops!(shr_u, $type, wrapping_shr, $type_u, 0x1F); 803 | impl_int_binops!(rotl, $type, u32, rotate_left, u64); 804 | impl_int_binops!(rotr, $type, u32, rotate_right, u64); 805 | 806 | impl_int_relops!(eqz, $type, |val| { 807 | val == Default::default() 808 | }); 809 | impl_int_relops!(eq, $type, $type, |left, right| { 810 | left == right 811 | }); 812 | impl_int_relops!(ne, $type, $type, |left, right| { 813 | left != right 814 | }); 815 | impl_int_relops!(lt_s, $type, $type, |left, right| { 816 | left < right 817 | }); 818 | impl_int_relops!(lt_u, $type_u, $type_u, |left, right| { 819 | left < right 820 | }); 821 | impl_int_relops!(gt_s, $type, $type, |left, right| { 822 | left > right 823 | }); 824 | impl_int_relops!(gt_u, $type_u, $type_u, |left, right| { 825 | left > right 826 | }); 827 | impl_int_relops!(le_s, $type, $type, |left, right| { 828 | left <= right 829 | }); 830 | impl_int_relops!(le_u, $type_u, $type_u, |left, right| { 831 | left <= right 832 | }); 833 | impl_int_relops!(ge_s, $type, $type, |left, right| { 834 | left >= right 835 | }); 836 | impl_int_relops!(ge_u, $type_u, $type_u, |left, right| { 837 | left >= right 838 | }); 839 | 840 | pub fn trunc_s_f32(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 841 | todo!(); 842 | } 843 | pub fn trunc_u_f32(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 844 | todo!(); 845 | } 846 | pub fn trunc_s_f64(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 847 | todo!(); 848 | } 849 | pub fn trunc_u_f64(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 850 | todo!(); 851 | } 852 | 853 | } 854 | }; 855 | } 856 | 857 | impl_numeric_ops!(i32_ops, i32, u32); 858 | impl_numeric_ops!(i64_ops, i64, u64); 859 | 860 | macro_rules! impl_float_numeric_ops { 861 | ($op_mod: ident, $type: ty) => { 862 | #[allow(dead_code)] 863 | mod $op_mod { 864 | 865 | use super::*; 866 | 867 | pub fn load(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 868 | todo!(); 869 | } 870 | 871 | pub fn store(_store: &mut Store, _l0: &mut StackValue, _offset: u32) -> Trap<()> { 872 | todo!(); 873 | } 874 | 875 | pub fn abs(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 876 | todo!(); 877 | } 878 | pub fn neg(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 879 | todo!(); 880 | } 881 | pub fn ceil(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 882 | todo!(); 883 | } 884 | pub fn floor(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 885 | todo!(); 886 | } 887 | pub fn trunc(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 888 | todo!(); 889 | } 890 | pub fn nearest(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 891 | todo!(); 892 | } 893 | pub fn sqrt(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 894 | todo!(); 895 | } 896 | 897 | pub fn add(store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 898 | let (left, right) = store.stack.pop_pair()? as ($type, $type); 899 | let res = left + right; 900 | store.stack.push(res)?; 901 | Ok(()) 902 | } 903 | 904 | pub fn sub(store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 905 | let (left, right) = store.stack.pop_pair()? as ($type, $type); 906 | let res = left - right; 907 | store.stack.push(res)?; 908 | Ok(()) 909 | } 910 | 911 | pub fn mul(store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 912 | let (left, right) = store.stack.pop_pair()? as ($type, $type); 913 | let res = left * right; 914 | store.stack.push(res)?; 915 | Ok(()) 916 | } 917 | pub fn div(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 918 | todo!(); 919 | } 920 | pub fn min(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 921 | todo!(); 922 | } 923 | pub fn max(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 924 | todo!(); 925 | } 926 | pub fn copysign(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 927 | todo!(); 928 | } 929 | 930 | pub fn eq(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 931 | todo!(); 932 | } 933 | pub fn ne(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 934 | todo!(); 935 | } 936 | pub fn lt(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 937 | todo!(); 938 | } 939 | pub fn gt(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 940 | todo!(); 941 | } 942 | pub fn le(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 943 | todo!(); 944 | } 945 | pub fn ge(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 946 | todo!(); 947 | } 948 | pub fn convert_s_i32(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 949 | todo!(); 950 | } 951 | pub fn convert_u_i32(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 952 | todo!(); 953 | } 954 | pub fn convert_s_i64(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 955 | todo!(); 956 | } 957 | pub fn convert_u_i64(_store: &mut Store, _l0: &mut StackValue) -> Trap<()> { 958 | todo!(); 959 | } 960 | } 961 | }; 962 | } 963 | 964 | impl_float_numeric_ops!(f32_ops, f32); 965 | impl_float_numeric_ops!(f64_ops, f64); 966 | 967 | --------------------------------------------------------------------------------