diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 82f00b9..fc43534 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -10,6 +10,7 @@ pub enum Error { FuncDidNotReturn, StackUnderflow, + CallStackEmpty, InvalidStore, @@ -24,6 +25,7 @@ impl Display for Error { Self::StackUnderflow => write!(f, "stack underflow"), Self::ParseError(err) => write!(f, "error parsing module: {:?}", err), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {}", feature), + Self::CallStackEmpty => write!(f, "call stack empty"), Self::Other(message) => write!(f, "unknown error: {}", message), Self::InvalidStore => write!(f, "invalid store"), #[cfg(feature = "std")] diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index f8f8804..1fda75e 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,7 +1,10 @@ use alloc::{format, string::String, string::ToString, vec, vec::Vec}; -use tinywasm_types::{FuncAddr, FuncType, ValType, WasmValue}; +use tinywasm_types::{FuncAddr, FuncType, WasmValue}; -use crate::{runtime::Stack, Error, ModuleInstance, Result, Store}; +use crate::{ + runtime::{CallFrame, Stack}, + Error, ModuleInstance, Result, Store, +}; #[derive(Debug)] pub struct FuncHandle { @@ -12,45 +15,66 @@ pub struct FuncHandle { } impl FuncHandle { /// Call a function + /// See https://webassembly.github.io/spec/core/exec/modules.html#invocation pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { - let func = store + let mut stack = Stack::default(); + + // 1. Assert: funcs[func_addr] exists + // 2. let func_inst be the functiuon instance funcs[func_addr] + let func_inst = store .data .funcs .get(self.addr as usize) .ok_or(Error::Other(format!("function {} not found", self.addr)))?; + // 3. Let func_ty be the function type let func_ty = &self.ty; - // check that params match func_ty params - for (ty, param) in func_ty.params.iter().zip(params) { + // 4. If the length of the provided argument values is different from the number of expected arguments, then fail + if func_ty.params.len() != params.len() { + return Err(Error::Other(format!( + "param count mismatch: expected {}, got {}", + func_ty.params.len(), + params.len() + ))); + } + + // 5. For each value type and the corresponding value, check if types match + for (i, (ty, param)) in func_ty.params.iter().zip(params).enumerate() { if ty != ¶m.val_type() { return Err(Error::Other(format!( - "param type mismatch: expected {:?}, got {:?}", - ty, param + "param type mismatch at index {}: expected {:?}, got {:?}", + i, ty, param ))); } } - let mut local_types: Vec = Vec::new(); - local_types.extend(func_ty.params.iter()); - local_types.extend(func.locals().iter()); + // 6. Let f be the dummy frame + let call_frame = CallFrame::new(self.addr as usize, params, func_inst.locals().iter()); - // let runtime = &mut store.runtime; + // 7. Push the frame f to the call stack + stack.call_stack.push(call_frame); - let mut stack = Stack::default(); - stack.locals.extend(params.iter().cloned()); + // 8. Push the values to the stack (Not needed since the call frame owns the values) - let instrs = func.instructions().iter(); + // 9. Invoke the function instance + let instrs = func_inst.instructions().iter(); store.runtime.exec(&mut stack, instrs)?; - let res = func_ty + // Once the function returns: + let result_m = func_ty.results.len(); + let res = stack.values.pop_n(result_m)?; + func_ty .results .iter() - .map(|_| stack.value_stack.pop()) - .collect::>>() - .ok_or(Error::Other( - "function did not return the correct number of values".into(), - ))?; + .zip(res.iter()) + .try_for_each(|(ty, val)| match ty == &val.val_type() { + true => Ok(()), + false => Err(Error::Other(format!( + "result type mismatch: expected {:?}, got {:?}", + ty, val + ))), + })?; Ok(res) } @@ -103,6 +127,9 @@ impl_into_wasm_value_tuple!(T1, T2); impl_into_wasm_value_tuple!(T1, T2, T3); impl_into_wasm_value_tuple!(T1, T2, T3, T4); impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); macro_rules! impl_from_wasm_value_tuple { ($($T:ident),*) => { @@ -131,3 +158,6 @@ impl_from_wasm_value_tuple!(T1, T2); impl_from_wasm_value_tuple!(T1, T2, T3); impl_from_wasm_value_tuple!(T1, T2, T3, T4); impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5); +impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); +impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7); +impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); diff --git a/crates/tinywasm/src/runtime/mod.rs b/crates/tinywasm/src/runtime/mod.rs index b8aa5aa..f6c0596 100644 --- a/crates/tinywasm/src/runtime/mod.rs +++ b/crates/tinywasm/src/runtime/mod.rs @@ -9,45 +9,63 @@ use crate::{Error, Result}; /// A WebAssembly Runtime. /// See https://webassembly.github.io/spec/core/exec/runtime.html +/// +/// Generic over `CheckTypes` to enable type checking at runtime. +/// This is useful for debugging, but should be disabled if you know +/// that the module is valid. #[derive(Debug, Default)] -pub struct Runtime {} +pub struct Runtime {} -impl Runtime { +impl Runtime { pub(crate) fn exec( &self, stack: &mut Stack, instrs: core::slice::Iter, ) -> Result<()> { - let locals = &mut stack.locals; - let value_stack = &mut stack.value_stack; + let call_frame = stack.call_stack.top_mut()?; for instr in instrs { use tinywasm_types::Instruction::*; match instr { LocalGet(local_index) => { - let val = &locals[*local_index as usize]; + let val = call_frame.get_local(*local_index as usize); debug!("local: {:#?}", val); - value_stack.push(val.clone()); + stack.values.push(val); + } + LocalSet(local_index) => { + let val = stack.values.pop().ok_or(Error::StackUnderflow)?; + call_frame.set_local(*local_index as usize, val); } I64Add => { - let a = value_stack.pop().unwrap(); - let b = value_stack.pop().unwrap(); + let [a, b] = stack.values.pop_n_const::<2>()?; + let (WasmValue::I64(a), WasmValue::I64(b)) = (a, b) else { panic!("Invalid type"); }; let c = WasmValue::I64(a + b); - value_stack.push(c); + stack.values.push(c); } I32Add => { - let a = value_stack.pop().unwrap(); - let b = value_stack.pop().unwrap(); + let [a, b] = stack.values.pop_n_const::<2>()?; + debug!("i64.add: {:?} + {:?}", a, b); + let (WasmValue::I32(a), WasmValue::I32(b)) = (a, b) else { panic!("Invalid type"); }; let c = WasmValue::I32(a + b); - value_stack.push(c); + debug!("i64.add: {:?}", c); + stack.values.push(c); + } + I32Sub => { + let [a, b] = stack.values.pop_n_const::<2>()?; + let (WasmValue::I32(a), WasmValue::I32(b)) = (a, b) else { + panic!("Invalid type"); + }; + let c = WasmValue::I32(a - b); + stack.values.push(c); } End => { + debug!("stack: {:?}", stack); return Ok(()); } _ => todo!(), diff --git a/crates/tinywasm/src/runtime/stack/call.rs b/crates/tinywasm/src/runtime/stack/call.rs index 0d0ffae..4682b16 100644 --- a/crates/tinywasm/src/runtime/stack/call.rs +++ b/crates/tinywasm/src/runtime/stack/call.rs @@ -1,9 +1,48 @@ -use alloc::vec::Vec; +use alloc::boxed::Box; +use tinywasm_types::{ValType, WasmValue}; #[derive(Debug)] pub struct CallFrame { pub instr_ptr: usize, pub func_ptr: usize, - pub local_addrs: Vec, + pub locals: Box<[WasmValue]>, + pub local_count: usize, +} + +impl CallFrame { + pub fn new<'a>( + func_ptr: usize, + params: &[WasmValue], + local_types: impl Iterator, + ) -> Self { + let mut locals = params.to_vec(); + locals.extend(local_types.map(|ty| WasmValue::default_for(*ty))); + let locals = locals.into_boxed_slice(); + + Self { + instr_ptr: 0, + func_ptr, + local_count: locals.len(), + locals, + } + } + + #[inline] + pub(crate) fn set_local(&mut self, local_index: usize, value: WasmValue) { + if local_index >= self.local_count { + panic!("Invalid local index"); + } + + self.locals[local_index] = value; + } + + #[inline] + pub(crate) fn get_local(&self, local_index: usize) -> WasmValue { + if local_index >= self.local_count { + panic!("Invalid local index"); + } + + self.locals[local_index] + } } diff --git a/crates/tinywasm/src/runtime/stack/mod.rs b/crates/tinywasm/src/runtime/stack/mod.rs index 18f68b7..271cdd5 100644 --- a/crates/tinywasm/src/runtime/stack/mod.rs +++ b/crates/tinywasm/src/runtime/stack/mod.rs @@ -4,6 +4,8 @@ mod call; pub use call::CallFrame; use tinywasm_types::WasmValue; +use crate::{Error, Result}; + // minimum stack size pub const STACK_SIZE: usize = 1024; // minimum call stack size @@ -12,28 +14,107 @@ pub const CALL_STACK_SIZE: usize = 1024; /// A WebAssembly Stack #[derive(Debug)] pub struct Stack { - /// Locals - // TODO: maybe store the locals on the stack instead? - pub locals: Vec, - - /// The value stack - // TODO: Split into Vec and Vec for better memory usage? - pub value_stack: Vec, // keeping this typed for now to make it easier to debug - pub value_stack_top: usize, + // keeping this typed for now to make it easier to debug + // TODO: Maybe split into Vec and Vec for better memory usage? + pub(crate) values: ValueStack, /// The call stack - pub call_stack: Vec, - pub call_stack_top: usize, + pub(crate) call_stack: CallStack, +} + +#[derive(Debug)] +pub struct CallStack { + stack: Vec, + top: usize, +} + +#[derive(Debug)] +pub struct ValueStack { + stack: Vec, + top: usize, +} + +impl ValueStack { + #[inline] + pub(crate) fn _extend(&mut self, values: &[WasmValue]) { + self.top += values.len(); + self.stack.extend(values.iter().cloned()); + } + + #[inline] + pub(crate) fn push(&mut self, value: WasmValue) { + self.top += 1; + self.stack.push(value); + } + + #[inline] + pub(crate) fn pop(&mut self) -> Option { + self.top -= 1; + self.stack.pop() + } + + #[inline] + pub(crate) fn pop_n(&mut self, n: usize) -> Result> { + if self.top < n { + return Err(Error::StackUnderflow); + } + self.top -= n; + let res = self.stack.drain(self.top..).rev().collect::>(); + Ok(res) + } + + #[inline] + pub(crate) fn pop_n_const(&mut self) -> Result<[WasmValue; N]> { + if self.top < N { + return Err(Error::StackUnderflow); + } + self.top -= N; + let mut res = [WasmValue::I32(0); N]; + for i in res.iter_mut().rev() { + *i = self.stack.pop().ok_or(Error::InvalidStore)?; + } + + Ok(res) + } +} + +impl CallStack { + #[inline] + pub(crate) fn _top(&self) -> Result<&CallFrame> { + assert!(self.top <= self.stack.len()); + if self.top == 0 { + return Err(Error::CallStackEmpty); + } + Ok(&self.stack[self.top - 1]) + } + + #[inline] + pub(crate) fn top_mut(&mut self) -> Result<&mut CallFrame> { + assert!(self.top <= self.stack.len()); + if self.top == 0 { + return Err(Error::CallStackEmpty); + } + Ok(&mut self.stack[self.top - 1]) + } + + #[inline] + pub(crate) fn push(&mut self, call_frame: CallFrame) { + self.top += 1; + self.stack.push(call_frame); + } } impl Default for Stack { fn default() -> Self { Self { - locals: Vec::new(), - value_stack: Vec::with_capacity(STACK_SIZE), - value_stack_top: 0, - call_stack: Vec::with_capacity(CALL_STACK_SIZE), - call_stack_top: 0, + values: ValueStack { + stack: Vec::with_capacity(STACK_SIZE), + top: 0, + }, + call_stack: CallStack { + stack: Vec::with_capacity(CALL_STACK_SIZE), + top: 0, + }, } } } diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index 0e73ece..85ffdbc 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -24,7 +24,7 @@ pub struct Store { module_instance_count: usize, pub(crate) data: StoreData, - pub(crate) runtime: Runtime, + pub(crate) runtime: Runtime, } impl PartialEq for Store { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 9f37759..6430b03 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -23,16 +23,29 @@ pub struct TinyWasmModule { /// A WebAssembly value. /// See https://webassembly.github.io/spec/core/syntax/types.html#value-types -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Copy)] pub enum WasmValue { // Num types I32(i32), I64(i64), F32(f32), F64(f64), - // Vec types - V128(i128), + // V128(i128), +} + +impl WasmValue { + pub fn default_for(ty: ValType) -> Self { + match ty { + ValType::I32 => Self::I32(0), + ValType::I64 => Self::I64(0), + ValType::F32 => Self::F32(0.0), + ValType::F64 => Self::F64(0.0), + ValType::V128 => unimplemented!("V128 is not yet supported"), + ValType::FuncRef => unimplemented!("FuncRef is not yet supported"), + ValType::ExternRef => unimplemented!("ExternRef is not yet supported"), + } + } } impl From for WasmValue { @@ -59,11 +72,11 @@ impl From for WasmValue { } } -impl From for WasmValue { - fn from(i: i128) -> Self { - Self::V128(i) - } -} +// impl From for WasmValue { +// fn from(i: i128) -> Self { +// Self::V128(i) +// } +// } impl TryFrom for i32 { type Error = (); @@ -109,16 +122,16 @@ impl TryFrom for f64 { } } -impl TryFrom for i128 { - type Error = (); +// impl TryFrom for i128 { +// type Error = (); - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::V128(i) => Ok(i), - _ => Err(()), - } - } -} +// fn try_from(value: WasmValue) -> Result { +// match value { +// WasmValue::V128(i) => Ok(i), +// _ => Err(()), +// } +// } +// } impl Debug for WasmValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -127,7 +140,7 @@ impl Debug for WasmValue { WasmValue::I64(i) => write!(f, "i64({})", i), WasmValue::F32(i) => write!(f, "f32({})", i), WasmValue::F64(i) => write!(f, "f64({})", i), - WasmValue::V128(i) => write!(f, "v128({})", i), + // WasmValue::V128(i) => write!(f, "v128({})", i), } } } @@ -139,7 +152,7 @@ impl WasmValue { Self::I64(_) => ValType::I64, Self::F32(_) => ValType::F32, Self::F64(_) => ValType::F64, - Self::V128(_) => ValType::V128, + // Self::V128(_) => ValType::V128, } } } diff --git a/examples/wasm/add.wat b/examples/wasm/add.wat index b5e1f3a..4976689 100644 --- a/examples/wasm/add.wat +++ b/examples/wasm/add.wat @@ -4,6 +4,11 @@ local.get $b i32.add) + (func $sub (export "sub") (param $a i32) (param $b i32) (result i32) + local.get $a + local.get $b + i32.sub) + (func $add_64 (export "add_64") (param $a i64) (param $b i64) (result i64) local.get $a local.get $b