From 7e8770cc4e418ef1a8cdfd9b1f59ee14b07cc6c9 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 28 Jun 2024 23:19:05 +0200 Subject: chore: restructure runtime Signed-off-by: Henry Gressmann --- crates/parser/src/conversion.rs | 8 - crates/parser/src/visit.rs | 74 +- crates/tinywasm/Cargo.toml | 10 - crates/tinywasm/src/func.rs | 4 +- crates/tinywasm/src/imports.rs | 6 +- crates/tinywasm/src/instance.rs | 38 +- crates/tinywasm/src/interpreter/executor.rs | 817 +++++++++++++++++++++ crates/tinywasm/src/interpreter/mod.rs | 23 + crates/tinywasm/src/interpreter/no_std_floats.rs | 34 + crates/tinywasm/src/interpreter/num_helpers.rs | 163 ++++ .../tinywasm/src/interpreter/stack/block_stack.rs | 75 ++ .../tinywasm/src/interpreter/stack/call_stack.rs | 195 +++++ crates/tinywasm/src/interpreter/stack/mod.rs | 22 + .../tinywasm/src/interpreter/stack/value_stack.rs | 156 ++++ crates/tinywasm/src/interpreter/stack/values.rs | 200 +++++ crates/tinywasm/src/lib.rs | 4 +- crates/tinywasm/src/module.rs | 11 +- crates/tinywasm/src/reference.rs | 49 +- crates/tinywasm/src/runtime/interpreter/mod.rs | 802 -------------------- .../src/runtime/interpreter/no_std_floats.rs | 34 - .../src/runtime/interpreter/num_helpers.rs | 163 ---- crates/tinywasm/src/runtime/mod.rs | 14 - crates/tinywasm/src/runtime/stack/block_stack.rs | 75 -- crates/tinywasm/src/runtime/stack/call_stack.rs | 195 ----- crates/tinywasm/src/runtime/stack/mod.rs | 22 - crates/tinywasm/src/runtime/stack/value_stack.rs | 186 ----- crates/tinywasm/src/runtime/stack/values.rs | 420 ----------- crates/tinywasm/src/store/element.rs | 2 +- crates/tinywasm/src/store/function.rs | 4 +- crates/tinywasm/src/store/global.rs | 56 +- crates/tinywasm/src/store/mod.rs | 6 +- crates/types/src/archive.rs | 6 +- crates/types/src/instructions.rs | 101 +-- crates/types/src/lib.rs | 4 +- crates/types/src/value.rs | 32 +- 35 files changed, 1807 insertions(+), 2204 deletions(-) create mode 100644 crates/tinywasm/src/interpreter/executor.rs create mode 100644 crates/tinywasm/src/interpreter/mod.rs create mode 100644 crates/tinywasm/src/interpreter/no_std_floats.rs create mode 100644 crates/tinywasm/src/interpreter/num_helpers.rs create mode 100644 crates/tinywasm/src/interpreter/stack/block_stack.rs create mode 100644 crates/tinywasm/src/interpreter/stack/call_stack.rs create mode 100644 crates/tinywasm/src/interpreter/stack/mod.rs create mode 100644 crates/tinywasm/src/interpreter/stack/value_stack.rs create mode 100644 crates/tinywasm/src/interpreter/stack/values.rs delete mode 100644 crates/tinywasm/src/runtime/interpreter/mod.rs delete mode 100644 crates/tinywasm/src/runtime/interpreter/no_std_floats.rs delete mode 100644 crates/tinywasm/src/runtime/interpreter/num_helpers.rs delete mode 100644 crates/tinywasm/src/runtime/mod.rs delete mode 100644 crates/tinywasm/src/runtime/stack/block_stack.rs delete mode 100644 crates/tinywasm/src/runtime/stack/call_stack.rs delete mode 100644 crates/tinywasm/src/runtime/stack/mod.rs delete mode 100644 crates/tinywasm/src/runtime/stack/value_stack.rs delete mode 100644 crates/tinywasm/src/runtime/stack/values.rs (limited to 'crates') diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 1d550f3..214487f 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -225,14 +225,6 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result Ok(FuncType { params, results }) } -pub(crate) fn convert_blocktype(blocktype: wasmparser::BlockType) -> BlockArgs { - match blocktype { - wasmparser::BlockType::Empty => BlockArgs::Empty, - wasmparser::BlockType::Type(ty) => BlockArgs::Type(convert_valtype(&ty)), - wasmparser::BlockType::FuncType(ty) => BlockArgs::FuncType(ty), - } -} - pub(crate) fn convert_reftype(reftype: &wasmparser::RefType) -> ValType { match reftype { _ if reftype.is_func_ref() => ValType::RefFunc, diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 1db00ad..e9844aa 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -1,6 +1,6 @@ -use crate::{conversion::convert_blocktype, Result}; +use crate::Result; -use crate::conversion::convert_heaptype; +use crate::conversion::{convert_heaptype, convert_valtype}; use alloc::string::ToString; use alloc::{boxed::Box, vec::Vec}; use tinywasm_types::{Instruction, MemoryArg}; @@ -357,7 +357,13 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_local_get(&mut self, idx: u32) -> Self::Output { - let resolved_idx = self.local_addr_map[idx as usize]; + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); + return; + }; + match self.validator.get_local_type(idx) { Some(t) => self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalGet32(resolved_idx), @@ -372,7 +378,13 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_local_set(&mut self, idx: u32) -> Self::Output { - let resolved_idx = self.local_addr_map[idx as usize]; + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); + return; + }; + match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalSet32(resolved_idx), @@ -387,7 +399,13 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_local_tee(&mut self, idx: u32) -> Self::Output { - let resolved_idx = self.local_addr_map[idx as usize]; + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); + return; + }; + match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), @@ -411,17 +429,29 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild fn visit_block(&mut self, blockty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.instructions.push(Instruction::Block(convert_blocktype(blockty), 0)) + self.instructions.push(match blockty { + wasmparser::BlockType::Empty => Instruction::Block(0), + wasmparser::BlockType::FuncType(idx) => Instruction::BlockWithFuncType(idx, 0), + wasmparser::BlockType::Type(ty) => Instruction::BlockWithType(convert_valtype(&ty), 0), + }) } fn visit_loop(&mut self, ty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.instructions.push(Instruction::Loop(convert_blocktype(ty), 0)) + self.instructions.push(match ty { + wasmparser::BlockType::Empty => Instruction::Loop(0), + wasmparser::BlockType::FuncType(idx) => Instruction::LoopWithFuncType(idx, 0), + wasmparser::BlockType::Type(ty) => Instruction::LoopWithType(convert_valtype(&ty), 0), + }) } fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.instructions.push(Instruction::If(convert_blocktype(ty).into(), 0, 0)) + self.instructions.push(match ty { + wasmparser::BlockType::Empty => Instruction::If(0, 0), + wasmparser::BlockType::FuncType(idx) => Instruction::IfWithFuncType(idx, 0, 0), + wasmparser::BlockType::Type(ty) => Instruction::IfWithType(convert_valtype(&ty), 0, 0), + }) } fn visit_else(&mut self) -> Self::Output { @@ -451,12 +481,18 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild }; let if_instruction = &mut self.instructions[if_label_pointer]; - let Instruction::If(_, else_offset, end_offset) = if_instruction else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - )); - return; + let (else_offset, end_offset) = match if_instruction { + Instruction::If(else_offset, end_offset) + | Instruction::IfWithFuncType(_, else_offset, end_offset) + | Instruction::IfWithType(_, else_offset, end_offset) => (else_offset, end_offset), + _ => { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Expected to end an if block, but the last label was not an if".to_string(), + )); + + return; + } }; *else_offset = (label_pointer - if_label_pointer) @@ -467,9 +503,15 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild .try_into() .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); } - Some(Instruction::Block(_, end_offset)) - | Some(Instruction::Loop(_, end_offset)) - | Some(Instruction::If(_, _, end_offset)) => { + Some(Instruction::Block(end_offset)) + | Some(Instruction::BlockWithType(_, end_offset)) + | Some(Instruction::BlockWithFuncType(_, end_offset)) + | Some(Instruction::Loop(end_offset)) + | Some(Instruction::LoopWithFuncType(_, end_offset)) + | Some(Instruction::LoopWithType(_, end_offset)) + | Some(Instruction::If(_, end_offset)) + | Some(Instruction::IfWithFuncType(_, _, end_offset)) + | Some(Instruction::IfWithType(_, _, end_offset)) => { *end_offset = (current_instr_ptr - label_pointer) .try_into() .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index d82d850..4e15123 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -19,16 +19,6 @@ tinywasm-parser={version="0.7.0", path="../parser", default-features=false, opti tinywasm-types={version="0.7.0", path="../types", default-features=false} libm={version="0.2", default-features=false} -# maybe? -# arrayvec={version="0.7"} instead of the custom implementation -# bumpalo={version="3.16"} -# wide= for simd -# vec1= might be useful? fast .last() and .first() access -# https://github.com/lumol-org/soa-derive could be useful for the memory layout of Stacks - -#https://alic.dev/blog/dense-enums -# https://docs.rs/tagged-pointer/latest/tagged_pointer/ - [dev-dependencies] wasm-testsuite={path="../wasm-testsuite"} wast={version="212.0"} diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 650f9df..3b97d93 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,4 +1,4 @@ -use crate::runtime::{CallFrame, Stack}; +use crate::interpreter::{CallFrame, Stack}; use crate::{log, unlikely, Function}; use crate::{Error, FuncContext, Result, Store}; use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; @@ -59,7 +59,7 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, 0); + let call_frame = CallFrame::new(wasm_func.clone(), func_inst._owner, params, 0); // 7. Push the frame f to the call stack // & 8. Push the values to the stack (Not needed since the call frame owns the values) diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index f24ed73..8c208f0 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -348,7 +348,7 @@ impl Imports { ) -> Result { let mut imports = ResolvedImports::new(); - for import in module.data.imports.iter() { + for import in module.0.imports.iter() { let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?; match val { @@ -368,7 +368,7 @@ impl Imports { } (Extern::Function(extern_func), ImportKind::Function(ty)) => { let import_func_type = module - .data + .0 .func_types .get(*ty as usize) .ok_or_else(|| LinkingError::incompatible_import_type(import))?; @@ -409,7 +409,7 @@ impl Imports { (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { let func = store.get_func(func_addr)?; let import_func_type = module - .data + .0 .func_types .get(*ty as usize) .ok_or_else(|| LinkingError::incompatible_import_type(import))?; diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 4ad3d09..7250cf0 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -64,45 +64,39 @@ impl ModuleInstance { let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?; - let data = module.data; - addrs.funcs.extend(store.init_funcs(data.funcs.into(), idx)?); - addrs.tables.extend(store.init_tables(data.table_types.into(), idx)?); - addrs.memories.extend(store.init_memories(data.memory_types.into(), idx)?); + addrs.funcs.extend(store.init_funcs(module.0.funcs.into(), idx)?); + addrs.tables.extend(store.init_tables(module.0.table_types.into(), idx)?); + addrs.memories.extend(store.init_memories(module.0.memory_types.into(), idx)?); - let global_addrs = store.init_globals(addrs.globals, data.globals.into(), &addrs.funcs, idx)?; + let global_addrs = store.init_globals(addrs.globals, module.0.globals.into(), &addrs.funcs, idx)?; let (elem_addrs, elem_trapped) = - store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &data.elements, idx)?; - let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, data.data.into(), idx)?; + store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?; + let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, module.0.data.into(), idx)?; let instance = ModuleInstanceInner { failed_to_instantiate: elem_trapped.is_some() || data_trapped.is_some(), store_id: store.id(), idx, - types: data.func_types, + types: module.0.func_types, func_addrs: addrs.funcs.into_boxed_slice(), table_addrs: addrs.tables.into_boxed_slice(), mem_addrs: addrs.memories.into_boxed_slice(), global_addrs: global_addrs.into_boxed_slice(), elem_addrs, data_addrs, - func_start: data.start_func, - imports: data.imports, - exports: data.exports, + func_start: module.0.start_func, + imports: module.0.imports, + exports: module.0.exports, }; let instance = ModuleInstance::new(instance); store.add_instance(instance.clone()); - if let Some(trap) = elem_trapped { - return Err(trap.into()); - }; - - if let Some(trap) = data_trapped { - return Err(trap.into()); - }; - - Ok(instance) + match (elem_trapped, data_trapped) { + (Some(trap), _) | (_, Some(trap)) => Err(trap.into()), + _ => Ok(instance), + } } /// Get a export by name @@ -224,13 +218,13 @@ impl ModuleInstance { /// Get a memory by address pub fn memory<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { let mem = store.get_mem(self.resolve_mem_addr(addr)?)?; - Ok(MemoryRef { instance: mem.borrow() }) + Ok(MemoryRef(mem.borrow())) } /// Get a memory by address (mutable) pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { let mem = store.get_mem(self.resolve_mem_addr(addr)?)?; - Ok(MemoryRefMut { instance: mem.borrow_mut() }) + Ok(MemoryRefMut(mem.borrow_mut())) } /// Get the start function of the module diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs new file mode 100644 index 0000000..386099d --- /dev/null +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -0,0 +1,817 @@ +#[cfg(not(feature = "std"))] +mod no_std_floats; + +use interpreter::CallFrame; +#[cfg(not(feature = "std"))] +#[allow(unused_imports)] +use no_std_floats::NoStdFloatExt; + +use alloc::{format, rc::Rc, string::ToString}; +use core::ops::ControlFlow; +use tinywasm_types::*; + +use super::num_helpers::*; +use super::stack::{values::StackHeight, BlockFrame, BlockType, Stack}; +use super::values::*; +use crate::*; + +pub(super) struct Executor<'store, 'stack> { + cf: CallFrame, + module: ModuleInstance, + store: &'store mut Store, + stack: &'stack mut Stack, +} + +impl<'store, 'stack> Executor<'store, 'stack> { + pub(crate) fn new(store: &'store mut Store, stack: &'stack mut Stack) -> Result { + let current_frame = stack.call_stack.pop().ok_or_else(|| Error::CallStackUnderflow)?; + let current_module = store.get_module_instance_raw(current_frame.module_addr()); + Ok(Self { cf: current_frame, module: current_module, stack, store }) + } + + #[inline] + pub(crate) fn run_to_completion(&mut self) -> Result<()> { + loop { + match self.exec_next()? { + ControlFlow::Break(..) => return Ok(()), + ControlFlow::Continue(..) => continue, + }; + } + } + + #[inline(always)] + fn exec_next(&mut self) -> Result> { + use tinywasm_types::Instruction::*; + match self.cf.fetch_instr() { + Nop => self.exec_noop(), + Unreachable => self.exec_unreachable()?, + + Drop32 => self.stack.values.drop::()?, + Drop64 => self.stack.values.drop::()?, + Drop128 => self.stack.values.drop::()?, + DropRef => self.stack.values.drop::()?, + + Select32 => self.stack.values.select::()?, + Select64 => self.stack.values.select::()?, + Select128 => self.stack.values.select::()?, + SelectRef => self.stack.values.select::()?, + + Call(v) => return self.exec_call_direct(*v), + CallIndirect(ty, table) => return self.exec_call_indirect(*ty, *table), + + If(end, el) => self.exec_if(*end, *el, (Default::default(), Default::default()))?, + IfWithType(ty, end, el) => self.exec_if(*end, *el, (Default::default(), (*ty).into()))?, + IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty))?, + Else(end_offset) => self.exec_else(*end_offset)?, + Loop(end) => { + self.enter_block(self.cf.instr_ptr(), *end, BlockType::Loop, (Default::default(), Default::default())) + } + LoopWithType(ty, end) => { + self.enter_block(self.cf.instr_ptr(), *end, BlockType::Loop, (Default::default(), (*ty).into())) + } + LoopWithFuncType(ty, end) => { + self.enter_block(self.cf.instr_ptr(), *end, BlockType::Loop, self.resolve_functype(*ty)) + } + Block(end) => { + self.enter_block(self.cf.instr_ptr(), *end, BlockType::Block, (Default::default(), Default::default())) + } + BlockWithType(ty, end) => { + self.enter_block(self.cf.instr_ptr(), *end, BlockType::Block, (Default::default(), (*ty).into())) + } + BlockWithFuncType(ty, end) => { + self.enter_block(self.cf.instr_ptr(), *end, BlockType::Block, self.resolve_functype(*ty)) + } + Br(v) => return self.exec_br(*v), + BrIf(v) => return self.exec_br_if(*v), + BrTable(default, len) => return self.exec_brtable(*default, *len), + BrLabel(_) => {} + Return => return self.exec_return(), + EndBlockFrame => self.exec_end_block()?, + + LocalGet32(local_index) => { + self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? + } + LocalGet64(local_index) => { + self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? + } + LocalGet128(local_index) => { + self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? + } + LocalGetRef(local_index) => { + self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? + } + + LocalSet32(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, + LocalSet64(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, + LocalSet128(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, + LocalSetRef(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, + + LocalTee32(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, + LocalTee64(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, + LocalTee128(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, + LocalTeeRef(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, + + GlobalGet(global_index) => self.exec_global_get(*global_index)?, + GlobalSet32(global_index) => self.exec_global_set::(*global_index)?, + GlobalSet64(global_index) => self.exec_global_set::(*global_index)?, + GlobalSet128(global_index) => self.exec_global_set::(*global_index)?, + GlobalSetRef(global_index) => self.exec_global_set::(*global_index)?, + + I32Const(val) => self.stack.values.push(*val), + I64Const(val) => self.stack.values.push(*val), + F32Const(val) => self.stack.values.push::(val.to_bits() as i32), + F64Const(val) => self.stack.values.push(val.to_bits() as i64), + RefFunc(func_idx) => self.stack.values.push(Some(*func_idx)), // do we need to resolve the function index? + RefNull(_) => self.stack.values.push(None), + RefIsNull => self.exec_ref_is_null()?, + + MemorySize(addr) => self.exec_memory_size(*addr)?, + MemoryGrow(addr) => self.exec_memory_grow(*addr)?, + + // Bulk memory operations + MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, + MemoryFill(addr) => self.exec_memory_fill(*addr)?, + MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, + DataDrop(data_index) => self.exec_data_drop(*data_index)?, + ElemDrop(elem_index) => self.exec_elem_drop(*elem_index)?, + TableCopy { from, to } => self.exec_table_copy(*from, *to)?, + + I32Store { mem_addr, offset } => { + let v = self.stack.values.pop::()?; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + I64Store { mem_addr, offset } => { + let v = self.stack.values.pop::()?; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + F32Store { mem_addr, offset } => { + let v = self.stack.values.pop::()?; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + F64Store { mem_addr, offset } => { + let v = self.stack.values.pop::()?; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + I32Store8 { mem_addr, offset } => { + let v = self.stack.values.pop::()? as i8; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + I32Store16 { mem_addr, offset } => { + let v = self.stack.values.pop::()? as i16; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + I64Store8 { mem_addr, offset } => { + let v = self.stack.values.pop::()? as i8; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + I64Store16 { mem_addr, offset } => { + let v = self.stack.values.pop::()? as i16; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + I64Store32 { mem_addr, offset } => { + let v = self.stack.values.pop::()? as i32; + self.exec_mem_store::(v, *mem_addr, *offset)? + } + + I32Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, + I64Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, + F32Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, + F64Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, + I32Load8S { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, + I32Load8U { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, + I32Load16S { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, + I32Load16U { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, + I64Load8S { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, + I64Load8U { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, + I64Load16S { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, + I64Load16U { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, + I64Load32S { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, + I64Load32U { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, + + I64Eqz => self.stack.values.replace_top::(|v| Ok((v == 0) as i32))?, + I32Eqz => self.stack.values.replace_top::(|v| Ok((v == 0) as i32))?, + I32Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, + I64Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, + F32Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, + F64Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, + + I32Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, + I64Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, + F32Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, + F64Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, + + I32LtS => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, + I64LtS => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, + I32LtU => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, + I64LtU => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, + F32Lt => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, + F64Lt => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, + + I32LeS => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, + I64LeS => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, + I32LeU => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, + I64LeU => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, + F32Le => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, + F64Le => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, + + I32GeS => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, + I64GeS => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, + I32GeU => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, + I64GeU => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, + F32Ge => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, + F64Ge => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, + + I32GtS => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, + I64GtS => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, + I32GtU => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, + I64GtU => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, + F32Gt => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, + F64Gt => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, + + I32Add => self.stack.values.calculate::(|a, b| Ok(a.wrapping_add(b)))?, + I64Add => self.stack.values.calculate::(|a, b| Ok(a.wrapping_add(b)))?, + F32Add => self.stack.values.calculate::(|a, b| Ok(a + b))?, + F64Add => self.stack.values.calculate::(|a, b| Ok(a + b))?, + + I32Sub => self.stack.values.calculate::(|a, b| Ok(a.wrapping_sub(b)))?, + I64Sub => self.stack.values.calculate::(|a, b| Ok(a.wrapping_sub(b)))?, + F32Sub => self.stack.values.calculate::(|a, b| Ok(a - b))?, + F64Sub => self.stack.values.calculate::(|a, b| Ok(a - b))?, + + F32Div => self.stack.values.calculate::(|a, b| Ok(a / b))?, + F64Div => self.stack.values.calculate::(|a, b| Ok(a / b))?, + + I32Mul => self.stack.values.calculate::(|a, b| Ok(a.wrapping_mul(b)))?, + I64Mul => self.stack.values.calculate::(|a, b| Ok(a.wrapping_mul(b)))?, + F32Mul => self.stack.values.calculate::(|a, b| Ok(a * b))?, + F64Mul => self.stack.values.calculate::(|a, b| Ok(a * b))?, + + // these can trap + I32DivS => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + I64DivS => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + I32DivU => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + I64DivU => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + + I32RemS => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + I64RemS => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + I32RemU => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + I64RemU => self.stack.values.calculate::(|a, b| { + if unlikely(b == 0) { + return Err(Error::Trap(Trap::DivisionByZero)); + } + a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + })?, + + I32And => self.stack.values.calculate::(|a, b| Ok(a & b))?, + I64And => self.stack.values.calculate::(|a, b| Ok(a & b))?, + I32Or => self.stack.values.calculate::(|a, b| Ok(a | b))?, + I64Or => self.stack.values.calculate::(|a, b| Ok(a | b))?, + I32Xor => self.stack.values.calculate::(|a, b| Ok(a ^ b))?, + I64Xor => self.stack.values.calculate::(|a, b| Ok(a ^ b))?, + I32Shl => self.stack.values.calculate::(|a, b| Ok(a.wasm_shl(b)))?, + I64Shl => self.stack.values.calculate::(|a, b| Ok(a.wasm_shl(b)))?, + I32ShrS => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, + I64ShrS => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, + I32ShrU => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, + I64ShrU => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, + I32Rotl => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotl(b)))?, + I64Rotl => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotl(b)))?, + I32Rotr => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotr(b)))?, + I64Rotr => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotr(b)))?, + + I32Clz => self.stack.values.replace_top::(|v| Ok(v.leading_zeros() as i32))?, + I64Clz => self.stack.values.replace_top::(|v| Ok(v.leading_zeros() as i64))?, + I32Ctz => self.stack.values.replace_top::(|v| Ok(v.trailing_zeros() as i32))?, + I64Ctz => self.stack.values.replace_top::(|v| Ok(v.trailing_zeros() as i64))?, + I32Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones() as i32))?, + I64Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones() as i64))?, + + F32ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f32))?, + F32ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f32))?, + F64ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f64))?, + F64ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f64))?, + F32ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f32))?, + F32ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f32))?, + F64ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f64))?, + F64ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f64))?, + + I32Extend8S => self.stack.values.replace_top::(|v| Ok((v as i8) as i32))?, + I32Extend16S => self.stack.values.replace_top::(|v| Ok((v as i16) as i32))?, + I64Extend8S => self.stack.values.replace_top::(|v| Ok((v as i8) as i64))?, + I64Extend16S => self.stack.values.replace_top::(|v| Ok((v as i16) as i64))?, + I64Extend32S => self.stack.values.replace_top::(|v| Ok((v as i32) as i64))?, + I64ExtendI32U => self.stack.values.replace_top::(|v| Ok(v as i64))?, + I64ExtendI32S => self.stack.values.replace_top::(|v| Ok(v as i64))?, + I32WrapI64 => self.stack.values.replace_top::(|v| Ok(v as i32))?, + + F32DemoteF64 => self.stack.values.replace_top::(|v| Ok(v as f32))?, + F64PromoteF32 => self.stack.values.replace_top::(|v| Ok(v as f64))?, + + F32Abs => self.stack.values.replace_top::(|v| Ok(v.abs()))?, + F64Abs => self.stack.values.replace_top::(|v| Ok(v.abs()))?, + F32Neg => self.stack.values.replace_top::(|v| Ok(-v))?, + F64Neg => self.stack.values.replace_top::(|v| Ok(-v))?, + F32Ceil => self.stack.values.replace_top::(|v| Ok(v.ceil()))?, + F64Ceil => self.stack.values.replace_top::(|v| Ok(v.ceil()))?, + F32Floor => self.stack.values.replace_top::(|v| Ok(v.floor()))?, + F64Floor => self.stack.values.replace_top::(|v| Ok(v.floor()))?, + F32Trunc => self.stack.values.replace_top::(|v| Ok(v.trunc()))?, + F64Trunc => self.stack.values.replace_top::(|v| Ok(v.trunc()))?, + F32Nearest => self.stack.values.replace_top::(|v| Ok(v.tw_nearest()))?, + F64Nearest => self.stack.values.replace_top::(|v| Ok(v.tw_nearest()))?, + F32Sqrt => self.stack.values.replace_top::(|v| Ok(v.sqrt()))?, + F64Sqrt => self.stack.values.replace_top::(|v| Ok(v.sqrt()))?, + F32Min => self.stack.values.calculate::(|a, b| Ok(a.tw_minimum(b)))?, + F64Min => self.stack.values.calculate::(|a, b| Ok(a.tw_minimum(b)))?, + F32Max => self.stack.values.calculate::(|a, b| Ok(a.tw_maximum(b)))?, + F64Max => self.stack.values.calculate::(|a, b| Ok(a.tw_maximum(b)))?, + F32Copysign => self.stack.values.calculate::(|a, b| Ok(a.copysign(b)))?, + F64Copysign => self.stack.values.calculate::(|a, b| Ok(a.copysign(b)))?, + + // no-op instructions since types are erased at runtime + I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} + + I32TruncF32S => checked_conv_float!(f32, i32, self), + I32TruncF64S => checked_conv_float!(f64, i32, self), + I32TruncF32U => checked_conv_float!(f32, u32, i32, self), + I32TruncF64U => checked_conv_float!(f64, u32, i32, self), + I64TruncF32S => checked_conv_float!(f32, i64, self), + I64TruncF64S => checked_conv_float!(f64, i64, self), + I64TruncF32U => checked_conv_float!(f32, u64, i64, self), + I64TruncF64U => checked_conv_float!(f64, u64, i64, self), + + TableGet(table_idx) => self.exec_table_get(*table_idx)?, + TableSet(table_idx) => self.exec_table_set(*table_idx)?, + TableSize(table_idx) => self.exec_table_size(*table_idx)?, + TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx)?, + TableGrow(table_idx) => self.exec_table_grow(*table_idx)?, + TableFill(table_idx) => self.exec_table_fill(*table_idx)?, + + I32TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32))?, + I32TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32))?, + I32TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32))?, + I32TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32))?, + I64TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64))?, + I64TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64))?, + I64TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64))?, + I64TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64))?, + // custom instructions + // LocalGet2(a, b) => self.exec_local_get2(*a, *b), + // LocalGet3(a, b, c) => self.exec_local_get3(*a, *b, *c), + // LocalTeeGet(a, b) => self.exec_local_tee_get(*a, *b)?, + // LocalGetSet(a, b) => self.exec_local_get_set(*a, *b), + // I64XorConstRotl(rotate_by) => self.exec_i64_xor_const_rotl(*rotate_by)?, + // I32LocalGetConstAdd(local, val) => self.exec_i32_local_get_const_add(*local, *val), + // I32ConstStoreLocal { local, const_i32, offset, mem_addr } => { + // self.exec_i32_const_store_local(*local, *const_i32, *offset, *mem_addr)? + // } + // I32StoreLocal { local_a, local_b, offset, mem_addr } => { + // self.exec_i32_store_local(*local_a, *local_b, *offset, *mem_addr)? + // } + }; + + self.cf.incr_instr_ptr(); + Ok(ControlFlow::Continue(())) + } + + fn exec_noop(&self) {} + #[cold] + fn exec_unreachable(&self) -> Result<()> { + Err(Error::Trap(Trap::Unreachable)) + } + + fn exec_call(&mut self, wasm_func: Rc, owner: ModuleInstanceAddr) -> Result> { + let params = self.stack.values.pop_many_raw(&wasm_func.ty.params)?; + let new_call_frame = + CallFrame::new_raw(wasm_func, owner, params.into_iter().rev(), self.stack.blocks.len() as u32); + self.cf.incr_instr_ptr(); // skip the call instruction + self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; + self.module.swap_with(self.cf.module_addr(), self.store); + Ok(ControlFlow::Continue(())) + } + fn exec_call_direct(&mut self, v: u32) -> Result> { + let func_inst = self.store.get_func(self.module.resolve_func_addr(v)?)?; + let wasm_func = match &func_inst.func { + crate::Function::Wasm(wasm_func) => wasm_func, + crate::Function::Host(host_func) => { + let func = &host_func.clone(); + let params = self.stack.values.pop_params(&host_func.ty.params)?; + let res = (func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms)?; + self.stack.values.extend_from_wasmvalues(&res); + self.cf.incr_instr_ptr(); + return Ok(ControlFlow::Continue(())); + } + }; + + self.exec_call(wasm_func.clone(), func_inst._owner) + } + fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> Result> { + // verify that the table is of the right type, this should be validated by the parser already + let func_ref = { + let table = self.store.get_table(self.module.resolve_table_addr(table_addr)?)?; + let table_idx: u32 = self.stack.values.pop::()? as u32; + let table = table.borrow(); + assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); + table + .get(table_idx) + .map_err(|_| Error::Trap(Trap::UndefinedElement { index: table_idx as usize }))? + .addr() + .ok_or(Trap::UninitializedElement { index: table_idx as usize })? + }; + + let func_inst = self.store.get_func(func_ref)?; + let call_ty = self.module.func_ty(type_addr); + let wasm_func = match &func_inst.func { + crate::Function::Wasm(f) => f, + crate::Function::Host(host_func) => { + if unlikely(host_func.ty != *call_ty) { + return Err(Error::Trap(Trap::IndirectCallTypeMismatch { + actual: host_func.ty.clone(), + expected: call_ty.clone(), + })); + } + + let host_func = host_func.clone(); + let params = self.stack.values.pop_params(&host_func.ty.params)?; + let res = (host_func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms)?; + self.stack.values.extend_from_wasmvalues(&res); + self.cf.incr_instr_ptr(); + return Ok(ControlFlow::Continue(())); + } + }; + + if wasm_func.ty == *call_ty { + return self.exec_call(wasm_func.clone(), func_inst._owner); + } + + cold(); + Err(Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into()) + } + + fn exec_if( + &mut self, + else_offset: u32, + end_offset: u32, + (params, results): (StackHeight, StackHeight), + ) -> Result<()> { + // truthy value is on the top of the stack, so enter the then block + if self.stack.values.pop::()? != 0 { + self.enter_block(self.cf.instr_ptr(), end_offset, BlockType::If, (params, results)); + return Ok(()); + } + + // falsy value is on the top of the stack + if else_offset == 0 { + *self.cf.instr_ptr_mut() += end_offset as usize; + return Ok(()); + } + + let old = self.cf.instr_ptr(); + *self.cf.instr_ptr_mut() += else_offset as usize; + self.enter_block(old + else_offset as usize, end_offset - else_offset, BlockType::Else, (params, results)); + Ok(()) + } + fn exec_else(&mut self, end_offset: u32) -> Result<()> { + self.exec_end_block()?; + *self.cf.instr_ptr_mut() += end_offset as usize; + Ok(()) + } + fn resolve_functype(&self, idx: u32) -> (StackHeight, StackHeight) { + let ty = self.module.func_ty(idx); + ((&*ty.params).into(), (&*ty.results).into()) + } + fn enter_block( + &mut self, + instr_ptr: usize, + end_instr_offset: u32, + ty: BlockType, + (params, results): (StackHeight, StackHeight), + ) { + self.stack.blocks.push(BlockFrame { + instr_ptr, + end_instr_offset, + stack_ptr: self.stack.values.height(), + results, + params, + ty, + }); + } + fn exec_br(&mut self, to: u32) -> Result> { + if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { + return self.exec_return(); + } + + self.cf.incr_instr_ptr(); + Ok(ControlFlow::Continue(())) + } + fn exec_br_if(&mut self, to: u32) -> Result> { + if self.stack.values.pop::()? != 0 + && self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() + { + return self.exec_return(); + } + self.cf.incr_instr_ptr(); + Ok(ControlFlow::Continue(())) + } + fn exec_brtable(&mut self, default: u32, len: u32) -> Result> { + let start = self.cf.instr_ptr() + 1; + let end = start + len as usize; + if end > self.cf.instructions().len() { + return Err(Error::Other(format!("br_table out of bounds: {} >= {}", end, self.cf.instructions().len()))); + } + + let idx = self.stack.values.pop::()?; + let to = match self.cf.instructions()[start..end].get(idx as usize) { + None => default, + Some(Instruction::BrLabel(to)) => *to, + _ => return Err(Error::Other("br_table with invalid label".to_string())), + }; + + if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { + return self.exec_return(); + } + + self.cf.incr_instr_ptr(); + Ok(ControlFlow::Continue(())) + } + fn exec_return(&mut self) -> Result> { + let old = self.cf.block_ptr(); + match self.stack.call_stack.pop() { + None => return Ok(ControlFlow::Break(())), + Some(cf) => self.cf = cf, + } + + if old > self.cf.block_ptr() { + self.stack.blocks.truncate(old); + } + + self.module.swap_with(self.cf.module_addr(), self.store); + Ok(ControlFlow::Continue(())) + } + fn exec_end_block(&mut self) -> Result<()> { + let block = self.stack.blocks.pop()?; + self.stack.values.truncate_keep(&block.stack_ptr, &block.results); + Ok(()) + } + + fn exec_global_get(&mut self, global_index: u32) -> Result<()> { + self.stack.values.push_dyn(self.store.get_global_val(self.module.resolve_global_addr(global_index)?)?); + Ok(()) + } + fn exec_global_set(&mut self, global_index: u32) -> Result<()> + where + TinyWasmValue: From, + { + self.store.set_global_val(self.module.resolve_global_addr(global_index)?, self.stack.values.pop::()?.into()) + } + fn exec_ref_is_null(&mut self) -> Result<()> { + let is_null = self.stack.values.pop::()?.is_none() as i32; + self.stack.values.push::(is_null); + Ok(()) + } + + fn exec_memory_size(&mut self, addr: u32) -> Result<()> { + let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)?)?; + self.stack.values.push::(mem.borrow().page_count() as i32); + Ok(()) + } + fn exec_memory_grow(&mut self, addr: u32) -> Result<()> { + let mut mem = self.store.get_mem(self.module.resolve_mem_addr(addr)?)?.borrow_mut(); + let prev_size = mem.page_count() as i32; + let pages_delta = self.stack.values.pop::()?; + self.stack.values.push::(match mem.grow(pages_delta) { + Some(_) => prev_size, + None => -1, + }); + Ok(()) + } + + fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { + let size = self.stack.values.pop::()?; + let src = self.stack.values.pop::()?; + let dst = self.stack.values.pop::()?; + + if from == to { + let mut mem_from = self.store.get_mem(self.module.resolve_mem_addr(from)?)?.borrow_mut(); + // copy within the same memory + mem_from.copy_within(dst as usize, src as usize, size as usize)?; + } else { + // copy between two memories + let mem_from = self.store.get_mem(self.module.resolve_mem_addr(from)?)?.borrow(); + let mut mem_to = self.store.get_mem(self.module.resolve_mem_addr(to)?)?.borrow_mut(); + mem_to.copy_from_slice(dst as usize, mem_from.load(src as usize, size as usize)?)?; + } + Ok(()) + } + fn exec_memory_fill(&mut self, addr: u32) -> Result<()> { + let size = self.stack.values.pop::()?; + let val = self.stack.values.pop::()?; + let dst = self.stack.values.pop::()?; + + let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)?)?; + mem.borrow_mut().fill(dst as usize, size as usize, val as u8)?; + Ok(()) + } + fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { + let size = self.stack.values.pop::()?; // n + let offset = self.stack.values.pop::()?; // s + let dst = self.stack.values.pop::()?; // d + + let data = self.store.get_data(self.module.resolve_data_addr(data_index)?)?; + let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_index)?)?; + + let data_len = data.data.as_ref().map(|d| d.len()).unwrap_or(0); + + if unlikely(((size + offset) as usize > data_len) || ((dst + size) as usize > mem.borrow().len())) { + return Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len }.into()); + } + + if size == 0 { + return Ok(()); + } + + let data = match &data.data { + Some(data) => data, + None => return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()), + }; + + mem.borrow_mut().store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)])?; + Ok(()) + } + fn exec_data_drop(&mut self, data_index: u32) -> Result<()> { + self.store.get_data_mut(self.module.resolve_data_addr(data_index)?).map(|d| d.drop()) + } + fn exec_elem_drop(&mut self, elem_index: u32) -> Result<()> { + self.store.get_elem_mut(self.module.resolve_elem_addr(elem_index)?).map(|e| e.drop()) + } + fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { + let size: i32 = self.stack.values.pop::()?; + let src: i32 = self.stack.values.pop::()?; + let dst: i32 = self.stack.values.pop::()?; + + if from == to { + let mut table_from = self.store.get_table(self.module.resolve_table_addr(from)?)?.borrow_mut(); + // copy within the same memory + table_from.copy_within(dst as usize, src as usize, size as usize)?; + } else { + // copy between two memories + let table_from = self.store.get_table(self.module.resolve_table_addr(from)?)?.borrow(); + let mut table_to = self.store.get_table(self.module.resolve_table_addr(to)?)?.borrow_mut(); + table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?)?; + } + Ok(()) + } + + fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( + &mut self, + cast: fn(LOAD) -> TARGET, + mem_addr: tinywasm_types::MemAddr, + offset: u64, + ) -> Result<()> { + let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)?)?; + let val = self.stack.values.pop::()? as u64; + let Some(Ok(addr)) = offset.checked_add(val).map(|a| a.try_into()) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: offset as usize, + len: LOAD_SIZE, + max: mem.borrow().max_pages(), + })); + }; + let val = mem.borrow().load_as::(addr)?; + self.stack.values.push(cast(val)); + Ok(()) + } + fn exec_mem_store, const N: usize>( + &mut self, + val: T, + mem_addr: tinywasm_types::MemAddr, + offset: u64, + ) -> Result<()> { + let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)?)?; + let val = val.to_mem_bytes(); + let addr = self.stack.values.pop::()? as u64; + mem.borrow_mut().store((offset + addr) as usize, val.len(), &val)?; + Ok(()) + } + + fn exec_table_get(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; + let idx: i32 = self.stack.values.pop::()?; + let v = table.borrow().get_wasm_val(idx as u32)?; + self.stack.values.push_dyn(v.into()); + Ok(()) + } + fn exec_table_set(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; + let val = self.stack.values.pop::()?; + let idx = self.stack.values.pop::()? as u32; + table.borrow_mut().set(idx, val.into())?; + Ok(()) + } + fn exec_table_size(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; + self.stack.values.push_dyn(table.borrow().size().into()); + Ok(()) + } + fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; + let table_len = table.borrow().size(); + let elem = self.store.get_elem(self.module.resolve_elem_addr(elem_index)?)?; + let elem_len = elem.items.as_ref().map(|items| items.len()).unwrap_or(0); + + let size: i32 = self.stack.values.pop::()?; // n + let offset: i32 = self.stack.values.pop::()?; // s + let dst: i32 = self.stack.values.pop::()?; // d + + if unlikely(((size + offset) as usize > elem_len) || ((dst + size) > table_len)) { + return Err(Trap::TableOutOfBounds { offset: offset as usize, len: size as usize, max: elem_len }.into()); + } + + if size == 0 { + return Ok(()); + } + + if let ElementKind::Active { .. } = elem.kind { + return Err(Error::Other("table.init with active element".to_string())); + } + + let Some(items) = elem.items.as_ref() else { + return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); + }; + + table.borrow_mut().init(self.module.func_addrs(), dst, &items[offset as usize..(offset + size) as usize])?; + Ok(()) + } + fn exec_table_grow(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; + let sz = table.borrow().size(); + + let n = self.stack.values.pop::()?; + let val = self.stack.values.pop::()?; + + match table.borrow_mut().grow(n, val.into()) { + Ok(_) => self.stack.values.push_dyn(sz.into()), + Err(_) => self.stack.values.push_dyn((-1_i32).into()), + } + + Ok(()) + } + fn exec_table_fill(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; + + let n = self.stack.values.pop::()?; + let val = self.stack.values.pop::()?; + let i = self.stack.values.pop::()?; + + if unlikely(i + n > table.borrow().size()) { + return Err(Error::Trap(Trap::TableOutOfBounds { + offset: i as usize, + len: n as usize, + max: table.borrow().size() as usize, + })); + } + + if n == 0 { + return Ok(()); + } + + table.borrow_mut().fill(self.module.func_addrs(), i as usize, n as usize, val.into())?; + Ok(()) + } +} diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs new file mode 100644 index 0000000..f9096f8 --- /dev/null +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -0,0 +1,23 @@ +mod executor; +mod num_helpers; +pub(crate) mod stack; + +#[doc(hidden)] +pub use stack::values; +pub use stack::values::*; + +pub(crate) use stack::{CallFrame, Stack}; + +use crate::{Result, Store}; + +/// The main TinyWasm runtime. +/// +/// This is the default runtime used by TinyWasm. +#[derive(Debug, Default)] +pub struct InterpreterRuntime {} + +impl InterpreterRuntime { + pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack) -> Result<()> { + executor::Executor::new(store, stack)?.run_to_completion() + } +} diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs new file mode 100644 index 0000000..5b9471e --- /dev/null +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -0,0 +1,34 @@ +pub(super) trait NoStdFloatExt { + fn round(self) -> Self; + fn abs(self) -> Self; + fn signum(self) -> Self; + fn ceil(self) -> Self; + fn floor(self) -> Self; + fn trunc(self) -> Self; + fn sqrt(self) -> Self; + fn copysign(self, other: Self) -> Self; +} + +#[rustfmt::skip] +impl NoStdFloatExt for f64 { + #[inline] fn round(self) -> Self { libm::round(self) } + #[inline] fn abs(self) -> Self { libm::fabs(self) } + #[inline] fn signum(self) -> Self { libm::copysign(1.0, self) } + #[inline] fn ceil(self) -> Self { libm::ceil(self) } + #[inline] fn floor(self) -> Self { libm::floor(self) } + #[inline] fn trunc(self) -> Self { libm::trunc(self) } + #[inline] fn sqrt(self) -> Self { libm::sqrt(self) } + #[inline] fn copysign(self, other: Self) -> Self { libm::copysign(self, other) } +} + +#[rustfmt::skip] +impl NoStdFloatExt for f32 { + #[inline] fn round(self) -> Self { libm::roundf(self) } + #[inline] fn abs(self) -> Self { libm::fabsf(self) } + #[inline] fn signum(self) -> Self { libm::copysignf(1.0, self) } + #[inline] fn ceil(self) -> Self { libm::ceilf(self) } + #[inline] fn floor(self) -> Self { libm::floorf(self) } + #[inline] fn trunc(self) -> Self { libm::truncf(self) } + #[inline] fn sqrt(self) -> Self { libm::sqrtf(self) } + #[inline] fn copysign(self, other: Self) -> Self { libm::copysignf(self, other) } +} diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs new file mode 100644 index 0000000..88a5e9e --- /dev/null +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -0,0 +1,163 @@ +pub(crate) trait CheckedWrappingRem +where + Self: Sized, +{ + fn checked_wrapping_rem(self, rhs: Self) -> Option; +} + +/// Doing the actual conversion from float to int is a bit tricky, because +/// we need to check for overflow. This macro generates the min/max values +/// for a specific conversion, which are then used in the actual conversion. +/// Rust sadly doesn't have wrapping casts for floats yet, maybe never. +/// Alternatively, https://crates.io/crates/az could be used for this but +/// it's not worth the dependency. +#[rustfmt::skip] +macro_rules! float_min_max { + (f32, i32) => {(-2147483904.0_f32, 2147483648.0_f32)}; + (f64, i32) => {(-2147483649.0_f64, 2147483648.0_f64)}; + (f32, u32) => {(-1.0_f32, 4294967296.0_f32)}; // 2^32 + (f64, u32) => {(-1.0_f64, 4294967296.0_f64)}; // 2^32 + (f32, i64) => {(-9223373136366403584.0_f32, 9223372036854775808.0_f32)}; // 2^63 + 2^40 | 2^63 + (f64, i64) => {(-9223372036854777856.0_f64, 9223372036854775808.0_f64)}; // 2^63 + 2^40 | 2^63 + (f32, u64) => {(-1.0_f32, 18446744073709551616.0_f32)}; // 2^64 + (f64, u64) => {(-1.0_f64, 18446744073709551616.0_f64)}; // 2^64 + // other conversions are not allowed + ($from:ty, $to:ty) => {compile_error!("invalid float conversion")}; +} + +/// Convert a value on the stack with error checking +macro_rules! checked_conv_float { + // Direct conversion with error checking (two types) + ($from:tt, $to:tt, $self:expr) => { + checked_conv_float!($from, $to, $to, $self) + }; + // Conversion with an intermediate unsigned type and error checking (three types) + ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { + $self.stack.values.replace_top::<$from, $to>(|v| { + let (min, max) = float_min_max!($from, $intermediate); + if unlikely(v.is_nan()) { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + if unlikely(v <= min || v >= max) { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + Ok((v as $intermediate as $to).into()) + })? + }; +} + +pub(crate) use checked_conv_float; +pub(crate) use float_min_max; + +pub(crate) trait TinywasmFloatExt { + fn tw_minimum(self, other: Self) -> Self; + fn tw_maximum(self, other: Self) -> Self; + fn tw_nearest(self) -> Self; +} + +#[cfg(not(feature = "std"))] +use super::no_std_floats::NoStdFloatExt; + +macro_rules! impl_wasm_float_ops { + ($($t:ty)*) => ($( + impl TinywasmFloatExt for $t { + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest + fn tw_nearest(self) -> Self { + match self { + x if x.is_nan() => x, // preserve NaN + x if x.is_infinite() || x == 0.0 => x, // preserve infinities and zeros + x if (0.0..=0.5).contains(&x) => 0.0, + x if (-0.5..0.0).contains(&x) => -0.0, + x => { + // Handle normal and halfway cases + let rounded = x.round(); + let diff = (x - rounded).abs(); + if diff != 0.5 || rounded % 2.0 == 0.0 { + return rounded + } + + rounded - x.signum() // Make even + } + } + } + + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmin + // Based on f32::minimum (which is not yet stable) + #[inline] + fn tw_minimum(self, other: Self) -> Self { + match self.partial_cmp(&other) { + Some(core::cmp::Ordering::Less) => self, + Some(core::cmp::Ordering::Greater) => other, + Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { self } else { other }, + None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + } + } + + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax + // Based on f32::maximum (which is not yet stable) + #[inline] + fn tw_maximum(self, other: Self) -> Self { + match self.partial_cmp(&other) { + Some(core::cmp::Ordering::Greater) => self, + Some(core::cmp::Ordering::Less) => other, + Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { other } else { self }, + None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + } + } + } + )*) +} + +impl_wasm_float_ops! { f32 f64 } + +pub(crate) trait WasmIntOps { + fn wasm_shl(self, rhs: Self) -> Self; + fn wasm_shr(self, rhs: Self) -> Self; + fn wasm_rotl(self, rhs: Self) -> Self; + fn wasm_rotr(self, rhs: Self) -> Self; +} + +macro_rules! impl_wrapping_self_sh { + ($($t:ty)*) => ($( + impl WasmIntOps for $t { + #[inline] + fn wasm_shl(self, rhs: Self) -> Self { + self.wrapping_shl(rhs as u32) + } + + #[inline] + fn wasm_shr(self, rhs: Self) -> Self { + self.wrapping_shr(rhs as u32) + } + + #[inline] + fn wasm_rotl(self, rhs: Self) -> Self { + self.rotate_left(rhs as u32) + } + + #[inline] + fn wasm_rotr(self, rhs: Self) -> Self { + self.rotate_right(rhs as u32) + } + } + )*) +} + +impl_wrapping_self_sh! { i32 i64 u32 u64 } + +macro_rules! impl_checked_wrapping_rem { + ($($t:ty)*) => ($( + impl CheckedWrappingRem for $t { + #[inline] + fn checked_wrapping_rem(self, rhs: Self) -> Option { + if rhs == 0 { + None + } else { + Some(self.wrapping_rem(rhs)) + } + } + } + )*) +} + +impl_checked_wrapping_rem! { i32 i64 u32 u64 } diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs new file mode 100644 index 0000000..cef536a --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -0,0 +1,75 @@ +use crate::{cold, unlikely, Error, Result}; +use alloc::vec::Vec; + +use super::values::{StackHeight, StackLocation}; + +#[derive(Debug)] +pub(crate) struct BlockStack(Vec); + +impl Default for BlockStack { + fn default() -> Self { + Self(Vec::with_capacity(128)) + } +} + +impl BlockStack { + #[inline(always)] + pub(crate) fn len(&self) -> usize { + self.0.len() + } + + #[inline(always)] + pub(crate) fn push(&mut self, block: BlockFrame) { + self.0.push(block); + } + + #[inline] + /// get the label at the given index, where 0 is the top of the stack + pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { + let len = (self.0.len() as u32) - offset; + + // the vast majority of wasm functions don't use break to return + if unlikely(index >= len) { + return None; + } + + Some(&self.0[self.0.len() - index as usize - 1]) + } + + #[inline(always)] + pub(crate) fn pop(&mut self) -> Result { + match self.0.pop() { + Some(frame) => Ok(frame), + None => { + cold(); + Err(Error::BlockStackUnderflow) + } + } + } + + /// keep the top `len` blocks and discard the rest + #[inline(always)] + pub(crate) fn truncate(&mut self, len: u32) { + self.0.truncate(len as usize); + } +} + +#[derive(Debug)] +pub(crate) struct BlockFrame { + pub(crate) instr_ptr: usize, // position of the instruction pointer when the block was entered + pub(crate) end_instr_offset: u32, // position of the end instruction of the block + + pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered + pub(crate) results: StackHeight, + pub(crate) params: StackHeight, + + pub(crate) ty: BlockType, +} + +#[derive(Debug)] +pub(crate) enum BlockType { + Loop, + If, + Else, + Block, +} diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs new file mode 100644 index 0000000..8e9dbe7 --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -0,0 +1,195 @@ +use super::values::{InternalValue, TinyWasmValue, Value128, Value32, Value64, ValueRef}; +use super::BlockType; +use crate::unlikely; +use crate::{Result, Trap}; + +use alloc::boxed::Box; +use alloc::{rc::Rc, vec, vec::Vec}; +use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmValue}; + +pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; + +#[derive(Debug)] +pub(crate) struct CallStack { + stack: Vec, +} + +impl CallStack { + #[inline] + pub(crate) fn new(initial_frame: CallFrame) -> Self { + Self { stack: vec![initial_frame] } + } + + #[inline(always)] + pub(crate) fn pop(&mut self) -> Option { + self.stack.pop() + } + + #[inline(always)] + pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { + if unlikely((self.stack.len() + 1) >= MAX_CALL_STACK_SIZE) { + return Err(Trap::CallStackOverflow.into()); + } + self.stack.push(call_frame); + Ok(()) + } +} + +#[derive(Debug)] +pub(crate) struct CallFrame { + pub(crate) instr_ptr: usize, + pub(crate) block_ptr: u32, + pub(crate) func_instance: Rc, + pub(crate) module_addr: ModuleInstanceAddr, + pub(crate) locals: Locals, +} + +#[derive(Debug)] +pub(crate) struct Locals { + pub(crate) locals_32: Box<[Value32]>, + pub(crate) locals_64: Box<[Value64]>, + pub(crate) locals_128: Box<[Value128]>, + pub(crate) locals_ref: Box<[ValueRef]>, +} + +impl Locals { + pub(crate) fn get(&self, local_index: LocalAddr) -> Result { + T::local_get(self, local_index) + } + + pub(crate) fn set(&mut self, local_index: LocalAddr, value: T) -> Result<()> { + T::local_set(self, local_index, value) + } +} + +impl CallFrame { + #[inline(always)] + pub(crate) fn instr_ptr(&self) -> usize { + self.instr_ptr + } + + #[inline(always)] + pub(crate) fn instr_ptr_mut(&mut self) -> &mut usize { + &mut self.instr_ptr + } + + #[inline(always)] + pub(crate) fn incr_instr_ptr(&mut self) { + self.instr_ptr += 1; + } + + #[inline(always)] + pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { + self.module_addr + } + + #[inline(always)] + pub(crate) fn block_ptr(&self) -> u32 { + self.block_ptr + } + + #[inline(always)] + pub(crate) fn fetch_instr(&self) -> &Instruction { + match self.func_instance.instructions.get(self.instr_ptr) { + Some(instr) => instr, + None => unreachable!("Instruction pointer out of bounds"), + } + } + + /// Break to a block at the given index (relative to the current frame) + /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) + pub(crate) fn break_to( + &mut self, + break_to_relative: u32, + values: &mut super::ValueStack, + blocks: &mut super::BlockStack, + ) -> Option<()> { + let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?; + + // instr_ptr points to the label instruction, but the next step + // will increment it by 1 since we're changing the "current" instr_ptr + match break_to.ty { + BlockType::Loop => { + // this is a loop, so we want to jump back to the start of the loop + self.instr_ptr = break_to.instr_ptr; + + // We also want to push the params to the stack + values.truncate_keep(&break_to.stack_ptr, &break_to.params); + + // check if we're breaking to the loop + if break_to_relative != 0 { + // we also want to trim the label stack to the loop (but not including the loop) + blocks.truncate(blocks.len() as u32 - break_to_relative); + return Some(()); + } + } + + BlockType::Block | BlockType::If | BlockType::Else => { + // this is a block, so we want to jump to the next instruction after the block ends + // We also want to push the block's results to the stack + values.truncate_keep(&break_to.stack_ptr, &break_to.results); + + // (the inst_ptr will be incremented by 1 before the next instruction is executed) + self.instr_ptr = break_to.instr_ptr + break_to.end_instr_offset as usize; + + // we also want to trim the label stack, including the block + blocks.truncate(blocks.len() as u32 - (break_to_relative + 1)); + } + } + + Some(()) + } + + #[inline(always)] + pub(crate) fn new( + wasm_func_inst: Rc, + owner: ModuleInstanceAddr, + params: &[WasmValue], + block_ptr: u32, + ) -> Self { + Self::new_raw(wasm_func_inst, owner, params.iter().map(|v| v.into()), block_ptr) + } + + #[inline(always)] + pub(crate) fn new_raw( + wasm_func_inst: Rc, + owner: ModuleInstanceAddr, + params: impl ExactSizeIterator, + block_ptr: u32, + ) -> Self { + let locals = { + let mut locals_32 = Vec::new(); + let mut locals_64 = Vec::new(); + let mut locals_128 = Vec::new(); + let mut locals_ref = Vec::new(); + + for p in params { + match p { + TinyWasmValue::Value32(v) => locals_32.push(v), + TinyWasmValue::Value64(v) => locals_64.push(v), + TinyWasmValue::Value128(v) => locals_128.push(v), + TinyWasmValue::ValueRef(v) => locals_ref.push(v), + } + } + + locals_32.resize_with(wasm_func_inst.locals.local_32 as usize, Default::default); + locals_64.resize_with(wasm_func_inst.locals.local_64 as usize, Default::default); + locals_128.resize_with(wasm_func_inst.locals.local_128 as usize, Default::default); + locals_ref.resize_with(wasm_func_inst.locals.local_ref as usize, Default::default); + + Locals { + locals_32: locals_32.into_boxed_slice(), + locals_64: locals_64.into_boxed_slice(), + locals_128: locals_128.into_boxed_slice(), + locals_ref: locals_ref.into_boxed_slice(), + } + }; + + Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + } + + #[inline(always)] + pub(crate) fn instructions(&self) -> &[Instruction] { + &self.func_instance.instructions + } +} diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs new file mode 100644 index 0000000..3902bd2 --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -0,0 +1,22 @@ +mod block_stack; +mod call_stack; +mod value_stack; +pub mod values; + +pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; +pub(crate) use call_stack::{CallFrame, CallStack}; +pub(crate) use value_stack::ValueStack; + +/// A WebAssembly Stack +#[derive(Debug)] +pub(crate) struct Stack { + pub(crate) values: ValueStack, + pub(crate) blocks: BlockStack, + pub(crate) call_stack: CallStack, +} + +impl Stack { + pub(crate) fn new(call_frame: CallFrame) -> Self { + Self { values: ValueStack::new(), blocks: BlockStack::default(), call_stack: CallStack::new(call_frame) } + } +} diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs new file mode 100644 index 0000000..fc23b48 --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -0,0 +1,156 @@ +use alloc::vec::Vec; +use tinywasm_types::{ValType, WasmValue}; + +use super::values::*; +use crate::Result; +pub(crate) const STACK_32_SIZE: usize = 1024 * 128; +pub(crate) const STACK_64_SIZE: usize = 1024 * 128; +pub(crate) const STACK_128_SIZE: usize = 1024 * 128; +pub(crate) const STACK_REF_SIZE: usize = 1024; + +#[derive(Debug)] +pub(crate) struct ValueStack { + pub(crate) stack_32: Vec, + pub(crate) stack_64: Vec, + pub(crate) stack_128: Vec, + pub(crate) stack_ref: Vec, +} + +impl ValueStack { + pub(crate) fn new() -> Self { + Self { + stack_32: Vec::with_capacity(STACK_32_SIZE), + stack_64: Vec::with_capacity(STACK_64_SIZE), + stack_128: Vec::with_capacity(STACK_128_SIZE), + stack_ref: Vec::with_capacity(STACK_REF_SIZE), + } + } + + pub(crate) fn height(&self) -> StackLocation { + StackLocation { + s32: self.stack_32.len() as u32, + s64: self.stack_64.len() as u32, + s128: self.stack_128.len() as u32, + sref: self.stack_ref.len() as u32, + } + } + + pub(crate) fn peek(&self) -> Result { + T::stack_peek(self) + } + + pub(crate) fn pop(&mut self) -> Result { + T::stack_pop(self) + } + + pub(crate) fn push(&mut self, value: T) { + T::stack_push(self, value) + } + + pub(crate) fn drop(&mut self) -> Result<()> { + T::stack_pop(self).map(|_| ()) + } + + pub(crate) fn select(&mut self) -> Result<()> { + let cond: i32 = self.pop()?; + let val2: T = self.pop()?; + if cond == 0 { + self.drop::()?; + self.push(val2); + } + Ok(()) + } + + pub(crate) fn calculate(&mut self, func: fn(T, T) -> Result) -> Result<()> { + let v2 = T::stack_pop(self)?; + let v1 = T::stack_pop(self)?; + U::stack_push(self, func(v1, v2)?); + Ok(()) + } + + pub(crate) fn replace_top(&mut self, func: fn(T) -> Result) -> Result<()> { + let v1 = T::stack_pop(self)?; + U::stack_push(self, func(v1)?); + Ok(()) + } + + pub(crate) fn pop_dyn(&mut self, val_type: ValType) -> Result { + match val_type { + ValType::I32 => self.pop().map(TinyWasmValue::Value32), + ValType::I64 => self.pop().map(TinyWasmValue::Value64), + ValType::V128 => self.pop().map(TinyWasmValue::Value128), + ValType::RefExtern => self.pop().map(TinyWasmValue::ValueRef), + ValType::RefFunc => self.pop().map(TinyWasmValue::ValueRef), + ValType::F32 => self.pop().map(TinyWasmValue::Value32), + ValType::F64 => self.pop().map(TinyWasmValue::Value64), + } + } + + pub(crate) fn pop_params(&mut self, val_types: &[ValType]) -> Result> { + val_types.iter().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>>() + } + + pub(crate) fn pop_results(&mut self, val_types: &[ValType]) -> Result> { + val_types.iter().rev().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>>().map(|mut v| { + v.reverse(); + v + }) + } + + pub(crate) fn pop_many_raw(&mut self, val_types: &[ValType]) -> Result> { + let mut values = Vec::with_capacity(val_types.len()); + for val_type in val_types.iter() { + values.push(self.pop_dyn(*val_type)?); + } + Ok(values) + } + + pub(crate) fn truncate_keep(&mut self, to: &StackLocation, keep: &StackHeight) { + #[inline(always)] + fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { + let len = data.len() as u32; + if len <= n { + return; // No need to truncate if the current size is already less than or equal to total_to_keep + } + data.drain((n as usize)..(len - end_keep) as usize); + } + + truncate_keep(&mut self.stack_32, to.s32, keep.s32 as u32); + truncate_keep(&mut self.stack_64, to.s64, keep.s64 as u32); + truncate_keep(&mut self.stack_128, to.s128, keep.s128 as u32); + truncate_keep(&mut self.stack_ref, to.sref, keep.sref as u32); + } + + pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) { + match value { + TinyWasmValue::Value32(v) => self.stack_32.push(v), + TinyWasmValue::Value64(v) => self.stack_64.push(v), + TinyWasmValue::Value128(v) => self.stack_128.push(v), + TinyWasmValue::ValueRef(v) => self.stack_ref.push(v), + } + } + + pub(crate) fn pop_wasmvalue(&mut self, val_type: ValType) -> Result { + match val_type { + ValType::I32 => self.pop().map(WasmValue::I32), + ValType::I64 => self.pop().map(WasmValue::I64), + ValType::V128 => self.pop().map(WasmValue::V128), + ValType::F32 => self.pop().map(WasmValue::F32), + ValType::F64 => self.pop().map(WasmValue::F64), + ValType::RefExtern => self.pop().map(|v| match v { + Some(v) => WasmValue::RefExtern(v), + None => WasmValue::RefNull(ValType::RefExtern), + }), + ValType::RefFunc => self.pop().map(|v| match v { + Some(v) => WasmValue::RefFunc(v), + None => WasmValue::RefNull(ValType::RefFunc), + }), + } + } + + pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { + for value in values.iter() { + self.push_dyn(value.into()) + } + } +} diff --git a/crates/tinywasm/src/interpreter/stack/values.rs b/crates/tinywasm/src/interpreter/stack/values.rs new file mode 100644 index 0000000..6e8274f --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/values.rs @@ -0,0 +1,200 @@ +#![allow(missing_docs)] +use super::{call_stack::Locals, ValueStack}; +use crate::{Error, Result}; +use tinywasm_types::{LocalAddr, ValType, WasmValue}; + +pub(crate) type Value32 = u32; +pub(crate) type Value64 = u64; +pub(crate) type Value128 = u128; +pub(crate) type ValueRef = Option; + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A untyped WebAssembly value +pub enum TinyWasmValue { + Value32(Value32), + Value64(Value64), + Value128(Value128), + ValueRef(ValueRef), +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct StackLocation { + pub(crate) s32: u32, + pub(crate) s64: u32, + pub(crate) s128: u32, + pub(crate) sref: u32, +} + +#[derive(Debug, Clone, Copy, Default)] +pub(crate) struct StackHeight { + pub(crate) s32: u16, + pub(crate) s64: u16, + pub(crate) s128: u16, + pub(crate) sref: u16, +} + +impl From for StackHeight { + fn from(value: ValType) -> Self { + match value { + ValType::I32 | ValType::F32 => Self { s32: 1, ..Default::default() }, + ValType::I64 | ValType::F64 => Self { s64: 1, ..Default::default() }, + ValType::V128 => Self { s128: 1, ..Default::default() }, + ValType::RefExtern | ValType::RefFunc => Self { sref: 1, ..Default::default() }, + } + } +} + +impl From<&[ValType]> for StackHeight { + fn from(value: &[ValType]) -> Self { + let mut s32 = 0; + let mut s64 = 0; + let mut s128 = 0; + let mut sref = 0; + for val_type in value.iter() { + match val_type { + ValType::I32 | ValType::F32 => s32 += 1, + ValType::I64 | ValType::F64 => s64 += 1, + ValType::V128 => s128 += 1, + ValType::RefExtern | ValType::RefFunc => sref += 1, + } + } + Self { s32, s64, s128, sref } + } +} + +impl TinyWasmValue { + pub fn unwrap_32(&self) -> Value32 { + match self { + TinyWasmValue::Value32(v) => *v, + _ => unreachable!("Expected Value32"), + } + } + + pub fn unwrap_64(&self) -> Value64 { + match self { + TinyWasmValue::Value64(v) => *v, + _ => unreachable!("Expected Value64"), + } + } + + pub fn unwrap_128(&self) -> Value128 { + match self { + TinyWasmValue::Value128(v) => *v, + _ => unreachable!("Expected Value128"), + } + } + + pub fn unwrap_ref(&self) -> ValueRef { + match self { + TinyWasmValue::ValueRef(v) => *v, + _ => unreachable!("Expected ValueRef"), + } + } + + pub fn attach_type(&self, ty: ValType) -> WasmValue { + match ty { + ValType::I32 => WasmValue::I32(self.unwrap_32() as i32), + ValType::I64 => WasmValue::I64(self.unwrap_64() as i64), + ValType::F32 => WasmValue::F32(f32::from_bits(self.unwrap_32())), + ValType::F64 => WasmValue::F64(f64::from_bits(self.unwrap_64())), + ValType::V128 => WasmValue::V128(self.unwrap_128()), + ValType::RefExtern => match self.unwrap_ref() { + Some(v) => WasmValue::RefExtern(v), + None => WasmValue::RefNull(ValType::RefExtern), + }, + ValType::RefFunc => match self.unwrap_ref() { + Some(v) => WasmValue::RefFunc(v), + None => WasmValue::RefNull(ValType::RefFunc), + }, + } + } +} + +impl From<&WasmValue> for TinyWasmValue { + fn from(value: &WasmValue) -> Self { + match value { + WasmValue::I32(v) => TinyWasmValue::Value32(*v as u32), + WasmValue::I64(v) => TinyWasmValue::Value64(*v as u64), + WasmValue::V128(v) => TinyWasmValue::Value128(*v), + WasmValue::F32(v) => TinyWasmValue::Value32(v.to_bits()), + WasmValue::F64(v) => TinyWasmValue::Value64(v.to_bits()), + WasmValue::RefFunc(v) => TinyWasmValue::ValueRef(Some(*v)), + WasmValue::RefExtern(v) => TinyWasmValue::ValueRef(Some(*v)), + WasmValue::RefNull(_) => TinyWasmValue::ValueRef(None), + } + } +} + +impl From for TinyWasmValue { + fn from(value: WasmValue) -> Self { + TinyWasmValue::from(&value) + } +} + +mod sealed { + #[allow(unreachable_pub)] + pub trait Sealed {} +} + +pub(crate) trait InternalValue: sealed::Sealed { + fn stack_push(stack: &mut ValueStack, value: Self); + fn stack_pop(stack: &mut ValueStack) -> Result + where + Self: Sized; + fn stack_peek(stack: &ValueStack) -> Result + where + Self: Sized; + fn local_get(locals: &Locals, index: LocalAddr) -> Result + where + Self: Sized; + fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) -> Result<()>; +} + +macro_rules! impl_internalvalue { + ($( $variant:ident, $stack:ident, $locals:ident, $internal:ty, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { + $( + impl sealed::Sealed for $outer {} + + impl From<$outer> for TinyWasmValue { + fn from(value: $outer) -> Self { + TinyWasmValue::$variant($to_internal(value)) + } + } + + impl InternalValue for $outer { + #[inline] + fn stack_push(stack: &mut ValueStack, value: Self) { + stack.$stack.push($to_internal(value)); + } + #[inline] + fn stack_pop(stack: &mut ValueStack) -> Result { + stack.$stack.pop().ok_or(Error::ValueStackUnderflow).map($to_outer) + } + #[inline] + fn stack_peek(stack: &ValueStack) -> Result { + stack.$stack.last().copied().ok_or(Error::ValueStackUnderflow).map($to_outer) + } + #[inline] + fn local_get(locals: &Locals, index: LocalAddr) -> Result { + Ok($to_outer(locals.$locals[index as usize])) + } + #[inline] + fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) -> Result<()> { + locals.$locals[index as usize] = $to_internal(value); + Ok(()) + } + } + )* + }; +} + +impl_internalvalue! { + Value32, stack_32, locals_32, u32, u32, |v| v, |v| v + Value64, stack_64, locals_64, u64, u64, |v| v, |v| v + Value32, stack_32, locals_32, u32, i32, |v| v as u32, |v: u32| v as i32 + Value64, stack_64, locals_64, u64, i64, |v| v as u64, |v| v as i64 + Value32, stack_32, locals_32, u32, f32, f32::to_bits, f32::from_bits + Value64, stack_64, locals_64, u64, f64, f64::to_bits, f64::from_bits + Value128, stack_128, locals_128, Value128, Value128, |v| v, |v| v + ValueRef, stack_ref, locals_ref, ValueRef, ValueRef, |v| v, |v| v +} diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 9c48ee0..c1c2549 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -108,8 +108,8 @@ mod reference; mod store; /// Runtime for executing WebAssembly modules. -pub mod runtime; -pub use runtime::InterpreterRuntime; +pub mod interpreter; +pub use interpreter::InterpreterRuntime; #[cfg(feature = "parser")] /// Re-export of [`tinywasm_parser`]. Requires `parser` feature. diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 5b5f0c6..f6e8e04 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -1,24 +1,21 @@ use crate::{Imports, ModuleInstance, Result, Store}; use tinywasm_types::TinyWasmModule; -#[derive(Debug)] /// A WebAssembly Module /// /// See -#[derive(Clone)] -pub struct Module { - pub(crate) data: TinyWasmModule, -} +#[derive(Debug, Clone)] +pub struct Module(pub(crate) TinyWasmModule); impl From<&TinyWasmModule> for Module { fn from(data: &TinyWasmModule) -> Self { - Self { data: data.clone() } + Self(data.clone()) } } impl From for Module { fn from(data: TinyWasmModule) -> Self { - Self { data } + Self(data) } } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index f3acc49..870de48 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,45 +1,40 @@ -use core::cell::{Ref, RefCell, RefMut}; +use core::cell::{Ref, RefMut}; use core::ffi::CStr; use alloc::ffi::CString; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use crate::{GlobalInstance, MemoryInstance, Result}; -use tinywasm_types::WasmValue; +use crate::{MemoryInstance, Result}; // This module essentially contains the public APIs to interact with the data stored in the store /// A reference to a memory instance #[derive(Debug)] -pub struct MemoryRef<'a> { - pub(crate) instance: Ref<'a, MemoryInstance>, -} +pub struct MemoryRef<'a>(pub(crate) Ref<'a, MemoryInstance>); /// A borrowed reference to a memory instance #[derive(Debug)] -pub struct MemoryRefMut<'a> { - pub(crate) instance: RefMut<'a, MemoryInstance>, -} +pub struct MemoryRefMut<'a>(pub(crate) RefMut<'a, MemoryInstance>); impl<'a> MemoryRefLoad for MemoryRef<'a> { /// Load a slice of memory fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, len) + self.0.load(offset, len) } } impl<'a> MemoryRefLoad for MemoryRefMut<'a> { /// Load a slice of memory fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, len) + self.0.load(offset, len) } } impl MemoryRef<'_> { /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, len) + self.0.load(offset, len) } /// Load a slice of memory as a vector @@ -51,7 +46,7 @@ impl MemoryRef<'_> { impl MemoryRefMut<'_> { /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, len) + self.0.load(offset, len) } /// Load a slice of memory as a vector @@ -61,27 +56,27 @@ impl MemoryRefMut<'_> { /// Grow the memory by the given number of pages pub fn grow(&mut self, delta_pages: i32) -> Option { - self.instance.grow(delta_pages) + self.0.grow(delta_pages) } /// Get the current size of the memory in pages pub fn page_count(&mut self) -> usize { - self.instance.page_count() + self.0.page_count() } /// Copy a slice of memory to another place in memory pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> { - self.instance.copy_within(src, dst, len) + self.0.copy_within(src, dst, len) } /// Fill a slice of memory with a value pub fn fill(&mut self, offset: usize, len: usize, val: u8) -> Result<()> { - self.instance.fill(offset, len, val) + self.0.fill(offset, len, val) } /// Store a slice of memory pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> { - self.instance.store(offset, len, data) + self.0.store(offset, len, data) } } @@ -139,21 +134,3 @@ pub trait MemoryStringExt: MemoryRefLoad { impl MemoryStringExt for MemoryRef<'_> {} impl MemoryStringExt for MemoryRefMut<'_> {} - -/// A reference to a global instance -#[derive(Debug)] -pub struct GlobalRef { - pub(crate) instance: RefCell, -} - -impl GlobalRef { - /// Get the value of the global - pub fn get(&self) -> WasmValue { - self.instance.borrow().get() - } - - /// Set the value of the global - pub fn set(&self, val: WasmValue) -> Result<()> { - self.instance.borrow_mut().set(val) - } -} diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs deleted file mode 100644 index 8028e87..0000000 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ /dev/null @@ -1,802 +0,0 @@ -mod num_helpers; - -#[cfg(not(feature = "std"))] -mod no_std_floats; - -#[cfg(not(feature = "std"))] -#[allow(unused_imports)] -use no_std_floats::NoStdFloatExt; - -use alloc::{format, rc::Rc, string::ToString}; -use core::ops::ControlFlow; -use num_helpers::*; -use tinywasm_types::*; - -use super::stack::{values::StackHeight, BlockFrame, BlockType}; -use super::{values::*, InterpreterRuntime, Stack}; -use crate::runtime::CallFrame; -use crate::*; - -impl InterpreterRuntime { - pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack) -> Result<()> { - Executor::new(store, stack)?.run_to_completion() - } -} - -struct Executor<'store, 'stack> { - store: &'store mut Store, - stack: &'stack mut Stack, - - cf: CallFrame, - module: ModuleInstance, -} - -impl<'store, 'stack> Executor<'store, 'stack> { - pub(crate) fn new(store: &'store mut Store, stack: &'stack mut Stack) -> Result { - let current_frame = stack.call_stack.pop().ok_or_else(|| Error::CallStackUnderflow)?; - let current_module = store.get_module_instance_raw(current_frame.module_addr()); - Ok(Self { cf: current_frame, module: current_module, stack, store }) - } - - #[inline] - pub(crate) fn run_to_completion(&mut self) -> Result<()> { - loop { - match self.exec_next()? { - ControlFlow::Break(..) => return Ok(()), - ControlFlow::Continue(..) => continue, - }; - } - } - - #[inline(always)] - fn exec_next(&mut self) -> Result> { - use tinywasm_types::Instruction::*; - match self.cf.fetch_instr() { - Nop => self.exec_noop(), - Unreachable => self.exec_unreachable()?, - - Drop32 => self.stack.values.drop::()?, - Drop64 => self.stack.values.drop::()?, - Drop128 => self.stack.values.drop::()?, - DropRef => self.stack.values.drop::()?, - - Select32 => self.stack.values.select::()?, - Select64 => self.stack.values.select::()?, - Select128 => self.stack.values.select::()?, - SelectRef => self.stack.values.select::()?, - - Call(v) => return self.exec_call_direct(*v), - CallIndirect(ty, table) => return self.exec_call_indirect(*ty, *table), - - If(args, el, end) => self.exec_if((*args).into(), *el, *end)?, - Else(end_offset) => self.exec_else(*end_offset)?, - Loop(args, end) => self.enter_block(self.cf.instr_ptr(), *end, BlockType::Loop, *args), - Block(args, end) => self.enter_block(self.cf.instr_ptr(), *end, BlockType::Block, *args), - Br(v) => return self.exec_br(*v), - BrIf(v) => return self.exec_br_if(*v), - BrTable(default, len) => return self.exec_brtable(*default, *len), - BrLabel(_) => {} - Return => return self.exec_return(), - EndBlockFrame => self.exec_end_block()?, - - LocalGet32(local_index) => { - self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? - } - LocalGet64(local_index) => { - self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? - } - LocalGet128(local_index) => { - self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? - } - LocalGetRef(local_index) => { - self.cf.locals.get::(*local_index).map(|v| self.stack.values.push(v))? - } - - LocalSet32(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, - LocalSet64(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, - LocalSet128(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, - LocalSetRef(local_index) => self.cf.locals.set(*local_index, self.stack.values.pop::()?)?, - - LocalTee32(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, - LocalTee64(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, - LocalTee128(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, - LocalTeeRef(local_index) => self.cf.locals.set(*local_index, self.stack.values.peek::()?)?, - - GlobalGet(global_index) => self.exec_global_get(*global_index)?, - GlobalSet32(global_index) => self.exec_global_set::(*global_index)?, - GlobalSet64(global_index) => self.exec_global_set::(*global_index)?, - GlobalSet128(global_index) => self.exec_global_set::(*global_index)?, - GlobalSetRef(global_index) => self.exec_global_set::(*global_index)?, - - I32Const(val) => self.stack.values.push(*val), - I64Const(val) => self.stack.values.push(*val), - F32Const(val) => self.stack.values.push::(val.to_bits() as i32), - F64Const(val) => self.stack.values.push(val.to_bits() as i64), - RefFunc(func_idx) => self.stack.values.push(Some(*func_idx)), // do we need to resolve the function index? - RefNull(_) => self.stack.values.push(None), - RefIsNull => self.exec_ref_is_null()?, - - MemorySize(addr) => self.exec_memory_size(*addr)?, - MemoryGrow(addr) => self.exec_memory_grow(*addr)?, - - // Bulk memory operations - MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, - MemoryFill(addr) => self.exec_memory_fill(*addr)?, - MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, - DataDrop(data_index) => self.exec_data_drop(*data_index)?, - ElemDrop(elem_index) => self.exec_elem_drop(*elem_index)?, - TableCopy { from, to } => self.exec_table_copy(*from, *to)?, - - I32Store { mem_addr, offset } => { - let v = self.stack.values.pop::()?; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - I64Store { mem_addr, offset } => { - let v = self.stack.values.pop::()?; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - F32Store { mem_addr, offset } => { - let v = self.stack.values.pop::()?; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - F64Store { mem_addr, offset } => { - let v = self.stack.values.pop::()?; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - I32Store8 { mem_addr, offset } => { - let v = self.stack.values.pop::()? as i8; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - I32Store16 { mem_addr, offset } => { - let v = self.stack.values.pop::()? as i16; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - I64Store8 { mem_addr, offset } => { - let v = self.stack.values.pop::()? as i8; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - I64Store16 { mem_addr, offset } => { - let v = self.stack.values.pop::()? as i16; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - I64Store32 { mem_addr, offset } => { - let v = self.stack.values.pop::()? as i32; - self.exec_mem_store::(v, *mem_addr, *offset)? - } - - I32Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, - I64Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, - F32Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, - F64Load { mem_addr, offset } => self.exec_mem_load::(|v| v, *mem_addr, *offset)?, - I32Load8S { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, - I32Load8U { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, - I32Load16S { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, - I32Load16U { mem_addr, offset } => self.exec_mem_load::(|v| v as i32, *mem_addr, *offset)?, - I64Load8S { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, - I64Load8U { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, - I64Load16S { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, - I64Load16U { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, - I64Load32S { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, - I64Load32U { mem_addr, offset } => self.exec_mem_load::(|v| v as i64, *mem_addr, *offset)?, - - I64Eqz => self.stack.values.replace_top::(|v| Ok((v == 0) as i32))?, - I32Eqz => self.stack.values.replace_top::(|v| Ok((v == 0) as i32))?, - I32Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, - I64Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, - F32Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, - F64Eq => self.stack.values.calculate::(|a, b| Ok((a == b) as i32))?, - - I32Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, - I64Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, - F32Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, - F64Ne => self.stack.values.calculate::(|a, b| Ok((a != b) as i32))?, - - I32LtS => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, - I64LtS => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, - I32LtU => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, - I64LtU => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, - F32Lt => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, - F64Lt => self.stack.values.calculate::(|a, b| Ok((a < b) as i32))?, - - I32LeS => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, - I64LeS => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, - I32LeU => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, - I64LeU => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, - F32Le => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, - F64Le => self.stack.values.calculate::(|a, b| Ok((a <= b) as i32))?, - - I32GeS => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, - I64GeS => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, - I32GeU => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, - I64GeU => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, - F32Ge => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, - F64Ge => self.stack.values.calculate::(|a, b| Ok((a >= b) as i32))?, - - I32GtS => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, - I64GtS => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, - I32GtU => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, - I64GtU => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, - F32Gt => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, - F64Gt => self.stack.values.calculate::(|a, b| Ok((a > b) as i32))?, - - I32Add => self.stack.values.calculate::(|a, b| Ok(a.wrapping_add(b)))?, - I64Add => self.stack.values.calculate::(|a, b| Ok(a.wrapping_add(b)))?, - F32Add => self.stack.values.calculate::(|a, b| Ok(a + b))?, - F64Add => self.stack.values.calculate::(|a, b| Ok(a + b))?, - - I32Sub => self.stack.values.calculate::(|a, b| Ok(a.wrapping_sub(b)))?, - I64Sub => self.stack.values.calculate::(|a, b| Ok(a.wrapping_sub(b)))?, - F32Sub => self.stack.values.calculate::(|a, b| Ok(a - b))?, - F64Sub => self.stack.values.calculate::(|a, b| Ok(a - b))?, - - F32Div => self.stack.values.calculate::(|a, b| Ok(a / b))?, - F64Div => self.stack.values.calculate::(|a, b| Ok(a / b))?, - - I32Mul => self.stack.values.calculate::(|a, b| Ok(a.wrapping_mul(b)))?, - I64Mul => self.stack.values.calculate::(|a, b| Ok(a.wrapping_mul(b)))?, - F32Mul => self.stack.values.calculate::(|a, b| Ok(a * b))?, - F64Mul => self.stack.values.calculate::(|a, b| Ok(a * b))?, - - // these can trap - I32DivS => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - I64DivS => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - I32DivU => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - I64DivU => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_div(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - - I32RemS => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - I64RemS => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - I32RemU => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - I64RemU => self.stack.values.calculate::(|a, b| { - if unlikely(b == 0) { - return Err(Error::Trap(Trap::DivisionByZero)); - } - a.checked_wrapping_rem(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) - })?, - - I32And => self.stack.values.calculate::(|a, b| Ok(a & b))?, - I64And => self.stack.values.calculate::(|a, b| Ok(a & b))?, - I32Or => self.stack.values.calculate::(|a, b| Ok(a | b))?, - I64Or => self.stack.values.calculate::(|a, b| Ok(a | b))?, - I32Xor => self.stack.values.calculate::(|a, b| Ok(a ^ b))?, - I64Xor => self.stack.values.calculate::(|a, b| Ok(a ^ b))?, - I32Shl => self.stack.values.calculate::(|a, b| Ok(a.wasm_shl(b)))?, - I64Shl => self.stack.values.calculate::(|a, b| Ok(a.wasm_shl(b)))?, - I32ShrS => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, - I64ShrS => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, - I32ShrU => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, - I64ShrU => self.stack.values.calculate::(|a, b| Ok(a.wasm_shr(b)))?, - I32Rotl => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotl(b)))?, - I64Rotl => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotl(b)))?, - I32Rotr => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotr(b)))?, - I64Rotr => self.stack.values.calculate::(|a, b| Ok(a.wasm_rotr(b)))?, - - I32Clz => self.stack.values.replace_top::(|v| Ok(v.leading_zeros() as i32))?, - I64Clz => self.stack.values.replace_top::(|v| Ok(v.leading_zeros() as i64))?, - I32Ctz => self.stack.values.replace_top::(|v| Ok(v.trailing_zeros() as i32))?, - I64Ctz => self.stack.values.replace_top::(|v| Ok(v.trailing_zeros() as i64))?, - I32Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones() as i32))?, - I64Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones() as i64))?, - - F32ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f32))?, - F32ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f32))?, - F64ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f64))?, - F64ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f64))?, - F32ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f32))?, - F32ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f32))?, - F64ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f64))?, - F64ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f64))?, - - I32Extend8S => self.stack.values.replace_top::(|v| Ok((v as i8) as i32))?, - I32Extend16S => self.stack.values.replace_top::(|v| Ok((v as i16) as i32))?, - I64Extend8S => self.stack.values.replace_top::(|v| Ok((v as i8) as i64))?, - I64Extend16S => self.stack.values.replace_top::(|v| Ok((v as i16) as i64))?, - I64Extend32S => self.stack.values.replace_top::(|v| Ok((v as i32) as i64))?, - I64ExtendI32U => self.stack.values.replace_top::(|v| Ok(v as i64))?, - I64ExtendI32S => self.stack.values.replace_top::(|v| Ok(v as i64))?, - I32WrapI64 => self.stack.values.replace_top::(|v| Ok(v as i32))?, - - F32DemoteF64 => self.stack.values.replace_top::(|v| Ok(v as f32))?, - F64PromoteF32 => self.stack.values.replace_top::(|v| Ok(v as f64))?, - - F32Abs => self.stack.values.replace_top::(|v| Ok(v.abs()))?, - F64Abs => self.stack.values.replace_top::(|v| Ok(v.abs()))?, - F32Neg => self.stack.values.replace_top::(|v| Ok(-v))?, - F64Neg => self.stack.values.replace_top::(|v| Ok(-v))?, - F32Ceil => self.stack.values.replace_top::(|v| Ok(v.ceil()))?, - F64Ceil => self.stack.values.replace_top::(|v| Ok(v.ceil()))?, - F32Floor => self.stack.values.replace_top::(|v| Ok(v.floor()))?, - F64Floor => self.stack.values.replace_top::(|v| Ok(v.floor()))?, - F32Trunc => self.stack.values.replace_top::(|v| Ok(v.trunc()))?, - F64Trunc => self.stack.values.replace_top::(|v| Ok(v.trunc()))?, - F32Nearest => self.stack.values.replace_top::(|v| Ok(v.tw_nearest()))?, - F64Nearest => self.stack.values.replace_top::(|v| Ok(v.tw_nearest()))?, - F32Sqrt => self.stack.values.replace_top::(|v| Ok(v.sqrt()))?, - F64Sqrt => self.stack.values.replace_top::(|v| Ok(v.sqrt()))?, - F32Min => self.stack.values.calculate::(|a, b| Ok(a.tw_minimum(b)))?, - F64Min => self.stack.values.calculate::(|a, b| Ok(a.tw_minimum(b)))?, - F32Max => self.stack.values.calculate::(|a, b| Ok(a.tw_maximum(b)))?, - F64Max => self.stack.values.calculate::(|a, b| Ok(a.tw_maximum(b)))?, - F32Copysign => self.stack.values.calculate::(|a, b| Ok(a.copysign(b)))?, - F64Copysign => self.stack.values.calculate::(|a, b| Ok(a.copysign(b)))?, - - // no-op instructions since types are erased at runtime - I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} - - I32TruncF32S => checked_conv_float!(f32, i32, self), - I32TruncF64S => checked_conv_float!(f64, i32, self), - I32TruncF32U => checked_conv_float!(f32, u32, i32, self), - I32TruncF64U => checked_conv_float!(f64, u32, i32, self), - I64TruncF32S => checked_conv_float!(f32, i64, self), - I64TruncF64S => checked_conv_float!(f64, i64, self), - I64TruncF32U => checked_conv_float!(f32, u64, i64, self), - I64TruncF64U => checked_conv_float!(f64, u64, i64, self), - - TableGet(table_idx) => self.exec_table_get(*table_idx)?, - TableSet(table_idx) => self.exec_table_set(*table_idx)?, - TableSize(table_idx) => self.exec_table_size(*table_idx)?, - TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx)?, - TableGrow(table_idx) => self.exec_table_grow(*table_idx)?, - TableFill(table_idx) => self.exec_table_fill(*table_idx)?, - - I32TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32))?, - I32TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32))?, - I32TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32))?, - I32TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32))?, - I64TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64))?, - I64TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64))?, - I64TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64))?, - I64TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64))?, - // custom instructions - // LocalGet2(a, b) => self.exec_local_get2(*a, *b), - // LocalGet3(a, b, c) => self.exec_local_get3(*a, *b, *c), - // LocalTeeGet(a, b) => self.exec_local_tee_get(*a, *b)?, - // LocalGetSet(a, b) => self.exec_local_get_set(*a, *b), - // I64XorConstRotl(rotate_by) => self.exec_i64_xor_const_rotl(*rotate_by)?, - // I32LocalGetConstAdd(local, val) => self.exec_i32_local_get_const_add(*local, *val), - // I32ConstStoreLocal { local, const_i32, offset, mem_addr } => { - // self.exec_i32_const_store_local(*local, *const_i32, *offset, *mem_addr)? - // } - // I32StoreLocal { local_a, local_b, offset, mem_addr } => { - // self.exec_i32_store_local(*local_a, *local_b, *offset, *mem_addr)? - // } - }; - - self.cf.incr_instr_ptr(); - Ok(ControlFlow::Continue(())) - } - - fn exec_noop(&self) {} - #[cold] - fn exec_unreachable(&self) -> Result<()> { - Err(Error::Trap(Trap::Unreachable)) - } - - fn exec_call(&mut self, wasm_func: Rc, owner: ModuleInstanceAddr) -> Result> { - let params = self.stack.values.pop_many_raw(&wasm_func.ty.params)?; - let new_call_frame = - CallFrame::new_raw(wasm_func, owner, params.into_iter().rev(), self.stack.blocks.len() as u32); - self.cf.incr_instr_ptr(); // skip the call instruction - self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; - self.module.swap_with(self.cf.module_addr(), self.store); - Ok(ControlFlow::Continue(())) - } - fn exec_call_direct(&mut self, v: u32) -> Result> { - let func_inst = self.store.get_func(self.module.resolve_func_addr(v)?)?; - let wasm_func = match &func_inst.func { - crate::Function::Wasm(wasm_func) => wasm_func, - crate::Function::Host(host_func) => { - let func = &host_func.clone(); - let params = self.stack.values.pop_params(&host_func.ty.params)?; - let res = (func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms)?; - self.stack.values.extend_from_wasmvalues(&res); - self.cf.incr_instr_ptr(); - return Ok(ControlFlow::Continue(())); - } - }; - - self.exec_call(wasm_func.clone(), func_inst.owner) - } - fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> Result> { - // verify that the table is of the right type, this should be validated by the parser already - let func_ref = { - let table = self.store.get_table(self.module.resolve_table_addr(table_addr)?)?; - let table_idx: u32 = self.stack.values.pop::()? as u32; - let table = table.borrow(); - assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); - table - .get(table_idx) - .map_err(|_| Error::Trap(Trap::UndefinedElement { index: table_idx as usize }))? - .addr() - .ok_or(Trap::UninitializedElement { index: table_idx as usize })? - }; - - let func_inst = self.store.get_func(func_ref)?; - let call_ty = self.module.func_ty(type_addr); - let wasm_func = match &func_inst.func { - crate::Function::Wasm(f) => f, - crate::Function::Host(host_func) => { - if unlikely(host_func.ty != *call_ty) { - return Err(Error::Trap(Trap::IndirectCallTypeMismatch { - actual: host_func.ty.clone(), - expected: call_ty.clone(), - })); - } - - let host_func = host_func.clone(); - let params = self.stack.values.pop_params(&host_func.ty.params)?; - let res = (host_func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms)?; - self.stack.values.extend_from_wasmvalues(&res); - self.cf.incr_instr_ptr(); - return Ok(ControlFlow::Continue(())); - } - }; - - if wasm_func.ty == *call_ty { - return self.exec_call(wasm_func.clone(), func_inst.owner); - } - - cold(); - Err(Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into()) - } - - fn exec_if(&mut self, args: BlockArgs, else_offset: u32, end_offset: u32) -> Result<()> { - // truthy value is on the top of the stack, so enter the then block - if self.stack.values.pop::()? != 0 { - self.enter_block(self.cf.instr_ptr(), end_offset, BlockType::If, args); - return Ok(()); - } - - // falsy value is on the top of the stack - if else_offset == 0 { - *self.cf.instr_ptr_mut() += end_offset as usize; - return Ok(()); - } - - let old = self.cf.instr_ptr(); - *self.cf.instr_ptr_mut() += else_offset as usize; - self.enter_block(old + else_offset as usize, end_offset - else_offset, BlockType::Else, args); - Ok(()) - } - fn exec_else(&mut self, end_offset: u32) -> Result<()> { - self.exec_end_block()?; - *self.cf.instr_ptr_mut() += end_offset as usize; - Ok(()) - } - fn enter_block(&mut self, instr_ptr: usize, end_instr_offset: u32, ty: BlockType, args: BlockArgs) { - let (params, results) = match args { - BlockArgs::Empty => (StackHeight::default(), StackHeight::default()), - BlockArgs::Type(t) => (StackHeight::default(), t.into()), - BlockArgs::FuncType(t) => { - let ty = self.module.func_ty(t); - ((&*ty.params).into(), (&*ty.results).into()) - } - }; - - self.stack.blocks.push(BlockFrame { - instr_ptr, - end_instr_offset, - stack_ptr: self.stack.values.height(), - results, - params, - ty, - }); - } - fn exec_br(&mut self, to: u32) -> Result> { - if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { - return self.exec_return(); - } - - self.cf.incr_instr_ptr(); - Ok(ControlFlow::Continue(())) - } - fn exec_br_if(&mut self, to: u32) -> Result> { - if self.stack.values.pop::()? != 0 - && self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() - { - return self.exec_return(); - } - self.cf.incr_instr_ptr(); - Ok(ControlFlow::Continue(())) - } - fn exec_brtable(&mut self, default: u32, len: u32) -> Result> { - let start = self.cf.instr_ptr() + 1; - let end = start + len as usize; - if end > self.cf.instructions().len() { - return Err(Error::Other(format!("br_table out of bounds: {} >= {}", end, self.cf.instructions().len()))); - } - - let idx = self.stack.values.pop::()?; - let to = match self.cf.instructions()[start..end].get(idx as usize) { - None => default, - Some(Instruction::BrLabel(to)) => *to, - _ => return Err(Error::Other("br_table with invalid label".to_string())), - }; - - if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { - return self.exec_return(); - } - - self.cf.incr_instr_ptr(); - Ok(ControlFlow::Continue(())) - } - fn exec_return(&mut self) -> Result> { - let old = self.cf.block_ptr(); - match self.stack.call_stack.pop() { - None => return Ok(ControlFlow::Break(())), - Some(cf) => self.cf = cf, - } - - if old > self.cf.block_ptr() { - self.stack.blocks.truncate(old); - } - - self.module.swap_with(self.cf.module_addr(), self.store); - Ok(ControlFlow::Continue(())) - } - fn exec_end_block(&mut self) -> Result<()> { - let block = self.stack.blocks.pop()?; - self.stack.values.truncate_keep(&block.stack_ptr, &block.results); - Ok(()) - } - - fn exec_global_get(&mut self, global_index: u32) -> Result<()> { - self.stack.values.push_dyn(self.store.get_global_val(self.module.resolve_global_addr(global_index)?)?); - Ok(()) - } - fn exec_global_set(&mut self, global_index: u32) -> Result<()> - where - TinyWasmValue: From, - { - self.store.set_global_val(self.module.resolve_global_addr(global_index)?, self.stack.values.pop::()?.into()) - } - fn exec_ref_is_null(&mut self) -> Result<()> { - let is_null = self.stack.values.pop::()?.is_none() as i32; - self.stack.values.push::(is_null); - Ok(()) - } - - fn exec_memory_size(&mut self, addr: u32) -> Result<()> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)?)?; - self.stack.values.push::(mem.borrow().page_count() as i32); - Ok(()) - } - fn exec_memory_grow(&mut self, addr: u32) -> Result<()> { - let mut mem = self.store.get_mem(self.module.resolve_mem_addr(addr)?)?.borrow_mut(); - let prev_size = mem.page_count() as i32; - let pages_delta = self.stack.values.pop::()?; - self.stack.values.push::(match mem.grow(pages_delta) { - Some(_) => prev_size, - None => -1, - }); - Ok(()) - } - - fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { - let size = self.stack.values.pop::()?; - let src = self.stack.values.pop::()?; - let dst = self.stack.values.pop::()?; - - if from == to { - let mut mem_from = self.store.get_mem(self.module.resolve_mem_addr(from)?)?.borrow_mut(); - // copy within the same memory - mem_from.copy_within(dst as usize, src as usize, size as usize)?; - } else { - // copy between two memories - let mem_from = self.store.get_mem(self.module.resolve_mem_addr(from)?)?.borrow(); - let mut mem_to = self.store.get_mem(self.module.resolve_mem_addr(to)?)?.borrow_mut(); - mem_to.copy_from_slice(dst as usize, mem_from.load(src as usize, size as usize)?)?; - } - Ok(()) - } - fn exec_memory_fill(&mut self, addr: u32) -> Result<()> { - let size = self.stack.values.pop::()?; - let val = self.stack.values.pop::()?; - let dst = self.stack.values.pop::()?; - - let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)?)?; - mem.borrow_mut().fill(dst as usize, size as usize, val as u8)?; - Ok(()) - } - fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { - let size = self.stack.values.pop::()?; // n - let offset = self.stack.values.pop::()?; // s - let dst = self.stack.values.pop::()?; // d - - let data = self.store.get_data(self.module.resolve_data_addr(data_index)?)?; - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_index)?)?; - - let data_len = data.data.as_ref().map(|d| d.len()).unwrap_or(0); - - if unlikely(((size + offset) as usize > data_len) || ((dst + size) as usize > mem.borrow().len())) { - return Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len }.into()); - } - - if size == 0 { - return Ok(()); - } - - let data = match &data.data { - Some(data) => data, - None => return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()), - }; - - mem.borrow_mut().store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)])?; - Ok(()) - } - fn exec_data_drop(&mut self, data_index: u32) -> Result<()> { - self.store.get_data_mut(self.module.resolve_data_addr(data_index)?).map(|d| d.drop()) - } - fn exec_elem_drop(&mut self, elem_index: u32) -> Result<()> { - self.store.get_elem_mut(self.module.resolve_elem_addr(elem_index)?).map(|e| e.drop()) - } - fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { - let size: i32 = self.stack.values.pop::()?; - let src: i32 = self.stack.values.pop::()?; - let dst: i32 = self.stack.values.pop::()?; - - if from == to { - let mut table_from = self.store.get_table(self.module.resolve_table_addr(from)?)?.borrow_mut(); - // copy within the same memory - table_from.copy_within(dst as usize, src as usize, size as usize)?; - } else { - // copy between two memories - let table_from = self.store.get_table(self.module.resolve_table_addr(from)?)?.borrow(); - let mut table_to = self.store.get_table(self.module.resolve_table_addr(to)?)?.borrow_mut(); - table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?)?; - } - Ok(()) - } - - fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( - &mut self, - cast: fn(LOAD) -> TARGET, - mem_addr: tinywasm_types::MemAddr, - offset: u64, - ) -> Result<()> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)?)?; - let val = self.stack.values.pop::()? as u64; - let Some(Ok(addr)) = offset.checked_add(val).map(|a| a.try_into()) else { - cold(); - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: offset as usize, - len: LOAD_SIZE, - max: mem.borrow().max_pages(), - })); - }; - let val = mem.borrow().load_as::(addr)?; - self.stack.values.push(cast(val)); - Ok(()) - } - fn exec_mem_store, const N: usize>( - &mut self, - val: T, - mem_addr: tinywasm_types::MemAddr, - offset: u64, - ) -> Result<()> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)?)?; - let val = val.to_mem_bytes(); - let addr = self.stack.values.pop::()? as u64; - mem.borrow_mut().store((offset + addr) as usize, val.len(), &val)?; - Ok(()) - } - - fn exec_table_get(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; - let idx: i32 = self.stack.values.pop::()?; - let v = table.borrow().get_wasm_val(idx as u32)?; - self.stack.values.push_dyn(v.into()); - Ok(()) - } - fn exec_table_set(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; - let val = self.stack.values.pop::()?; - let idx = self.stack.values.pop::()? as u32; - table.borrow_mut().set(idx, val.into())?; - Ok(()) - } - fn exec_table_size(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; - self.stack.values.push_dyn(table.borrow().size().into()); - Ok(()) - } - fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; - let table_len = table.borrow().size(); - let elem = self.store.get_elem(self.module.resolve_elem_addr(elem_index)?)?; - let elem_len = elem.items.as_ref().map(|items| items.len()).unwrap_or(0); - - let size: i32 = self.stack.values.pop::()?; // n - let offset: i32 = self.stack.values.pop::()?; // s - let dst: i32 = self.stack.values.pop::()?; // d - - if unlikely(((size + offset) as usize > elem_len) || ((dst + size) > table_len)) { - return Err(Trap::TableOutOfBounds { offset: offset as usize, len: size as usize, max: elem_len }.into()); - } - - if size == 0 { - return Ok(()); - } - - if let ElementKind::Active { .. } = elem.kind { - return Err(Error::Other("table.init with active element".to_string())); - } - - let Some(items) = elem.items.as_ref() else { - return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); - }; - - table.borrow_mut().init(self.module.func_addrs(), dst, &items[offset as usize..(offset + size) as usize])?; - Ok(()) - } - fn exec_table_grow(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; - let sz = table.borrow().size(); - - let n = self.stack.values.pop::()?; - let val = self.stack.values.pop::()?; - - match table.borrow_mut().grow(n, val.into()) { - Ok(_) => self.stack.values.push_dyn(sz.into()), - Err(_) => self.stack.values.push_dyn((-1_i32).into()), - } - - Ok(()) - } - fn exec_table_fill(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; - - let n = self.stack.values.pop::()?; - let val = self.stack.values.pop::()?; - let i = self.stack.values.pop::()?; - - if unlikely(i + n > table.borrow().size()) { - return Err(Error::Trap(Trap::TableOutOfBounds { - offset: i as usize, - len: n as usize, - max: table.borrow().size() as usize, - })); - } - - if n == 0 { - return Ok(()); - } - - table.borrow_mut().fill(self.module.func_addrs(), i as usize, n as usize, val.into())?; - Ok(()) - } -} diff --git a/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs b/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs deleted file mode 100644 index 5b9471e..0000000 --- a/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs +++ /dev/null @@ -1,34 +0,0 @@ -pub(super) trait NoStdFloatExt { - fn round(self) -> Self; - fn abs(self) -> Self; - fn signum(self) -> Self; - fn ceil(self) -> Self; - fn floor(self) -> Self; - fn trunc(self) -> Self; - fn sqrt(self) -> Self; - fn copysign(self, other: Self) -> Self; -} - -#[rustfmt::skip] -impl NoStdFloatExt for f64 { - #[inline] fn round(self) -> Self { libm::round(self) } - #[inline] fn abs(self) -> Self { libm::fabs(self) } - #[inline] fn signum(self) -> Self { libm::copysign(1.0, self) } - #[inline] fn ceil(self) -> Self { libm::ceil(self) } - #[inline] fn floor(self) -> Self { libm::floor(self) } - #[inline] fn trunc(self) -> Self { libm::trunc(self) } - #[inline] fn sqrt(self) -> Self { libm::sqrt(self) } - #[inline] fn copysign(self, other: Self) -> Self { libm::copysign(self, other) } -} - -#[rustfmt::skip] -impl NoStdFloatExt for f32 { - #[inline] fn round(self) -> Self { libm::roundf(self) } - #[inline] fn abs(self) -> Self { libm::fabsf(self) } - #[inline] fn signum(self) -> Self { libm::copysignf(1.0, self) } - #[inline] fn ceil(self) -> Self { libm::ceilf(self) } - #[inline] fn floor(self) -> Self { libm::floorf(self) } - #[inline] fn trunc(self) -> Self { libm::truncf(self) } - #[inline] fn sqrt(self) -> Self { libm::sqrtf(self) } - #[inline] fn copysign(self, other: Self) -> Self { libm::copysignf(self, other) } -} diff --git a/crates/tinywasm/src/runtime/interpreter/num_helpers.rs b/crates/tinywasm/src/runtime/interpreter/num_helpers.rs deleted file mode 100644 index d0402bc..0000000 --- a/crates/tinywasm/src/runtime/interpreter/num_helpers.rs +++ /dev/null @@ -1,163 +0,0 @@ -pub(crate) trait CheckedWrappingRem -where - Self: Sized, -{ - fn checked_wrapping_rem(self, rhs: Self) -> Option; -} - -/// Doing the actual conversion from float to int is a bit tricky, because -/// we need to check for overflow. This macro generates the min/max values -/// for a specific conversion, which are then used in the actual conversion. -/// Rust sadly doesn't have wrapping casts for floats yet, maybe never. -/// Alternatively, https://crates.io/crates/az could be used for this but -/// it's not worth the dependency. -#[rustfmt::skip] -macro_rules! float_min_max { - (f32, i32) => {(-2147483904.0_f32, 2147483648.0_f32)}; - (f64, i32) => {(-2147483649.0_f64, 2147483648.0_f64)}; - (f32, u32) => {(-1.0_f32, 4294967296.0_f32)}; // 2^32 - (f64, u32) => {(-1.0_f64, 4294967296.0_f64)}; // 2^32 - (f32, i64) => {(-9223373136366403584.0_f32, 9223372036854775808.0_f32)}; // 2^63 + 2^40 | 2^63 - (f64, i64) => {(-9223372036854777856.0_f64, 9223372036854775808.0_f64)}; // 2^63 + 2^40 | 2^63 - (f32, u64) => {(-1.0_f32, 18446744073709551616.0_f32)}; // 2^64 - (f64, u64) => {(-1.0_f64, 18446744073709551616.0_f64)}; // 2^64 - // other conversions are not allowed - ($from:ty, $to:ty) => {compile_error!("invalid float conversion")}; -} - -/// Convert a value on the stack with error checking -macro_rules! checked_conv_float { - // Direct conversion with error checking (two types) - ($from:tt, $to:tt, $self:expr) => { - checked_conv_float!($from, $to, $to, $self) - }; - // Conversion with an intermediate unsigned type and error checking (three types) - ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { - $self.stack.values.replace_top::<$from, $to>(|v| { - let (min, max) = float_min_max!($from, $intermediate); - if unlikely(v.is_nan()) { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - if unlikely(v <= min || v >= max) { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - Ok((v as $intermediate as $to).into()) - })? - }; -} - -pub(crate) use checked_conv_float; -pub(crate) use float_min_max; - -pub(crate) trait TinywasmFloatExt { - fn tw_minimum(self, other: Self) -> Self; - fn tw_maximum(self, other: Self) -> Self; - fn tw_nearest(self) -> Self; -} - -#[cfg(not(feature = "std"))] -use super::no_std_floats::NoStdFloatExt; - -macro_rules! impl_wasm_float_ops { - ($($t:ty)*) => ($( - impl TinywasmFloatExt for $t { - // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest - fn tw_nearest(self) -> Self { - match self { - x if x.is_nan() => x, // preserve NaN - x if x.is_infinite() || x == 0.0 => x, // preserve infinities and zeros - x if (0.0..=0.5).contains(&x) => 0.0, - x if (-0.5..0.0).contains(&x) => -0.0, - x => { - // Handle normal and halfway cases - let rounded = x.round(); - let diff = (x - rounded).abs(); - if diff != 0.5 || rounded % 2.0 == 0.0 { - return rounded - } - - rounded - x.signum() // Make even - } - } - } - - // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmin - // Based on f32::minimum (which is not yet stable) - #[inline] - fn tw_minimum(self, other: Self) -> Self { - match self.partial_cmp(&other) { - Some(core::cmp::Ordering::Less) => self, - Some(core::cmp::Ordering::Greater) => other, - Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { self } else { other }, - None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. - } - } - - // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax - // Based on f32::maximum (which is not yet stable) - #[inline] - fn tw_maximum(self, other: Self) -> Self { - match self.partial_cmp(&other) { - Some(core::cmp::Ordering::Greater) => self, - Some(core::cmp::Ordering::Less) => other, - Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { other } else { self }, - None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. - } - } - } - )*) -} - -impl_wasm_float_ops! { f32 f64 } - -pub(crate) trait WasmIntOps { - fn wasm_shl(self, rhs: Self) -> Self; - fn wasm_shr(self, rhs: Self) -> Self; - fn wasm_rotl(self, rhs: Self) -> Self; - fn wasm_rotr(self, rhs: Self) -> Self; -} - -macro_rules! impl_wrapping_self_sh { - ($($t:ty)*) => ($( - impl WasmIntOps for $t { - #[inline] - fn wasm_shl(self, rhs: Self) -> Self { - self.wrapping_shl(rhs as u32) - } - - #[inline] - fn wasm_shr(self, rhs: Self) -> Self { - self.wrapping_shr(rhs as u32) - } - - #[inline] - fn wasm_rotl(self, rhs: Self) -> Self { - self.rotate_left(rhs as u32) - } - - #[inline] - fn wasm_rotr(self, rhs: Self) -> Self { - self.rotate_right(rhs as u32) - } - } - )*) -} - -impl_wrapping_self_sh! { i32 i64 u32 u64 } - -macro_rules! impl_checked_wrapping_rem { - ($($t:ty)*) => ($( - impl CheckedWrappingRem for $t { - #[inline] - fn checked_wrapping_rem(self, rhs: Self) -> Option { - if rhs == 0 { - None - } else { - Some(self.wrapping_rem(rhs)) - } - } - } - )*) -} - -impl_checked_wrapping_rem! { i32 i64 u32 u64 } diff --git a/crates/tinywasm/src/runtime/mod.rs b/crates/tinywasm/src/runtime/mod.rs deleted file mode 100644 index fca58cc..0000000 --- a/crates/tinywasm/src/runtime/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod interpreter; -pub(crate) mod stack; - -#[doc(hidden)] -pub use stack::values; -pub use stack::values::*; - -pub(crate) use stack::{CallFrame, Stack}; - -/// The main TinyWasm runtime. -/// -/// This is the default runtime used by TinyWasm. -#[derive(Debug, Default)] -pub struct InterpreterRuntime {} diff --git a/crates/tinywasm/src/runtime/stack/block_stack.rs b/crates/tinywasm/src/runtime/stack/block_stack.rs deleted file mode 100644 index cef536a..0000000 --- a/crates/tinywasm/src/runtime/stack/block_stack.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::{cold, unlikely, Error, Result}; -use alloc::vec::Vec; - -use super::values::{StackHeight, StackLocation}; - -#[derive(Debug)] -pub(crate) struct BlockStack(Vec); - -impl Default for BlockStack { - fn default() -> Self { - Self(Vec::with_capacity(128)) - } -} - -impl BlockStack { - #[inline(always)] - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - #[inline(always)] - pub(crate) fn push(&mut self, block: BlockFrame) { - self.0.push(block); - } - - #[inline] - /// get the label at the given index, where 0 is the top of the stack - pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { - let len = (self.0.len() as u32) - offset; - - // the vast majority of wasm functions don't use break to return - if unlikely(index >= len) { - return None; - } - - Some(&self.0[self.0.len() - index as usize - 1]) - } - - #[inline(always)] - pub(crate) fn pop(&mut self) -> Result { - match self.0.pop() { - Some(frame) => Ok(frame), - None => { - cold(); - Err(Error::BlockStackUnderflow) - } - } - } - - /// keep the top `len` blocks and discard the rest - #[inline(always)] - pub(crate) fn truncate(&mut self, len: u32) { - self.0.truncate(len as usize); - } -} - -#[derive(Debug)] -pub(crate) struct BlockFrame { - pub(crate) instr_ptr: usize, // position of the instruction pointer when the block was entered - pub(crate) end_instr_offset: u32, // position of the end instruction of the block - - pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered - pub(crate) results: StackHeight, - pub(crate) params: StackHeight, - - pub(crate) ty: BlockType, -} - -#[derive(Debug)] -pub(crate) enum BlockType { - Loop, - If, - Else, - Block, -} diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs deleted file mode 100644 index 8e9dbe7..0000000 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ /dev/null @@ -1,195 +0,0 @@ -use super::values::{InternalValue, TinyWasmValue, Value128, Value32, Value64, ValueRef}; -use super::BlockType; -use crate::unlikely; -use crate::{Result, Trap}; - -use alloc::boxed::Box; -use alloc::{rc::Rc, vec, vec::Vec}; -use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmValue}; - -pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; - -#[derive(Debug)] -pub(crate) struct CallStack { - stack: Vec, -} - -impl CallStack { - #[inline] - pub(crate) fn new(initial_frame: CallFrame) -> Self { - Self { stack: vec![initial_frame] } - } - - #[inline(always)] - pub(crate) fn pop(&mut self) -> Option { - self.stack.pop() - } - - #[inline(always)] - pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - if unlikely((self.stack.len() + 1) >= MAX_CALL_STACK_SIZE) { - return Err(Trap::CallStackOverflow.into()); - } - self.stack.push(call_frame); - Ok(()) - } -} - -#[derive(Debug)] -pub(crate) struct CallFrame { - pub(crate) instr_ptr: usize, - pub(crate) block_ptr: u32, - pub(crate) func_instance: Rc, - pub(crate) module_addr: ModuleInstanceAddr, - pub(crate) locals: Locals, -} - -#[derive(Debug)] -pub(crate) struct Locals { - pub(crate) locals_32: Box<[Value32]>, - pub(crate) locals_64: Box<[Value64]>, - pub(crate) locals_128: Box<[Value128]>, - pub(crate) locals_ref: Box<[ValueRef]>, -} - -impl Locals { - pub(crate) fn get(&self, local_index: LocalAddr) -> Result { - T::local_get(self, local_index) - } - - pub(crate) fn set(&mut self, local_index: LocalAddr, value: T) -> Result<()> { - T::local_set(self, local_index, value) - } -} - -impl CallFrame { - #[inline(always)] - pub(crate) fn instr_ptr(&self) -> usize { - self.instr_ptr - } - - #[inline(always)] - pub(crate) fn instr_ptr_mut(&mut self) -> &mut usize { - &mut self.instr_ptr - } - - #[inline(always)] - pub(crate) fn incr_instr_ptr(&mut self) { - self.instr_ptr += 1; - } - - #[inline(always)] - pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { - self.module_addr - } - - #[inline(always)] - pub(crate) fn block_ptr(&self) -> u32 { - self.block_ptr - } - - #[inline(always)] - pub(crate) fn fetch_instr(&self) -> &Instruction { - match self.func_instance.instructions.get(self.instr_ptr) { - Some(instr) => instr, - None => unreachable!("Instruction pointer out of bounds"), - } - } - - /// Break to a block at the given index (relative to the current frame) - /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) - pub(crate) fn break_to( - &mut self, - break_to_relative: u32, - values: &mut super::ValueStack, - blocks: &mut super::BlockStack, - ) -> Option<()> { - let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?; - - // instr_ptr points to the label instruction, but the next step - // will increment it by 1 since we're changing the "current" instr_ptr - match break_to.ty { - BlockType::Loop => { - // this is a loop, so we want to jump back to the start of the loop - self.instr_ptr = break_to.instr_ptr; - - // We also want to push the params to the stack - values.truncate_keep(&break_to.stack_ptr, &break_to.params); - - // check if we're breaking to the loop - if break_to_relative != 0 { - // we also want to trim the label stack to the loop (but not including the loop) - blocks.truncate(blocks.len() as u32 - break_to_relative); - return Some(()); - } - } - - BlockType::Block | BlockType::If | BlockType::Else => { - // this is a block, so we want to jump to the next instruction after the block ends - // We also want to push the block's results to the stack - values.truncate_keep(&break_to.stack_ptr, &break_to.results); - - // (the inst_ptr will be incremented by 1 before the next instruction is executed) - self.instr_ptr = break_to.instr_ptr + break_to.end_instr_offset as usize; - - // we also want to trim the label stack, including the block - blocks.truncate(blocks.len() as u32 - (break_to_relative + 1)); - } - } - - Some(()) - } - - #[inline(always)] - pub(crate) fn new( - wasm_func_inst: Rc, - owner: ModuleInstanceAddr, - params: &[WasmValue], - block_ptr: u32, - ) -> Self { - Self::new_raw(wasm_func_inst, owner, params.iter().map(|v| v.into()), block_ptr) - } - - #[inline(always)] - pub(crate) fn new_raw( - wasm_func_inst: Rc, - owner: ModuleInstanceAddr, - params: impl ExactSizeIterator, - block_ptr: u32, - ) -> Self { - let locals = { - let mut locals_32 = Vec::new(); - let mut locals_64 = Vec::new(); - let mut locals_128 = Vec::new(); - let mut locals_ref = Vec::new(); - - for p in params { - match p { - TinyWasmValue::Value32(v) => locals_32.push(v), - TinyWasmValue::Value64(v) => locals_64.push(v), - TinyWasmValue::Value128(v) => locals_128.push(v), - TinyWasmValue::ValueRef(v) => locals_ref.push(v), - } - } - - locals_32.resize_with(wasm_func_inst.locals.local_32 as usize, Default::default); - locals_64.resize_with(wasm_func_inst.locals.local_64 as usize, Default::default); - locals_128.resize_with(wasm_func_inst.locals.local_128 as usize, Default::default); - locals_ref.resize_with(wasm_func_inst.locals.local_ref as usize, Default::default); - - Locals { - locals_32: locals_32.into_boxed_slice(), - locals_64: locals_64.into_boxed_slice(), - locals_128: locals_128.into_boxed_slice(), - locals_ref: locals_ref.into_boxed_slice(), - } - }; - - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } - } - - #[inline(always)] - pub(crate) fn instructions(&self) -> &[Instruction] { - &self.func_instance.instructions - } -} diff --git a/crates/tinywasm/src/runtime/stack/mod.rs b/crates/tinywasm/src/runtime/stack/mod.rs deleted file mode 100644 index 3902bd2..0000000 --- a/crates/tinywasm/src/runtime/stack/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -mod block_stack; -mod call_stack; -mod value_stack; -pub mod values; - -pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; -pub(crate) use call_stack::{CallFrame, CallStack}; -pub(crate) use value_stack::ValueStack; - -/// A WebAssembly Stack -#[derive(Debug)] -pub(crate) struct Stack { - pub(crate) values: ValueStack, - pub(crate) blocks: BlockStack, - pub(crate) call_stack: CallStack, -} - -impl Stack { - pub(crate) fn new(call_frame: CallFrame) -> Self { - Self { values: ValueStack::new(), blocks: BlockStack::default(), call_stack: CallStack::new(call_frame) } - } -} diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs deleted file mode 100644 index 40376f1..0000000 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ /dev/null @@ -1,186 +0,0 @@ -use alloc::vec::Vec; -use tinywasm_types::{ValType, WasmValue}; - -use super::values::*; -use crate::Result; -pub(crate) const STACK_32_SIZE: usize = 1024 * 128; -pub(crate) const STACK_64_SIZE: usize = 1024 * 128; -pub(crate) const STACK_128_SIZE: usize = 1024 * 128; -pub(crate) const STACK_REF_SIZE: usize = 1024; - -#[derive(Debug)] -pub(crate) struct ValueStack { - pub(crate) stack_32: Vec, - pub(crate) stack_64: Vec, - pub(crate) stack_128: Vec, - pub(crate) stack_ref: Vec, -} - -impl ValueStack { - pub(crate) fn new() -> Self { - Self { - stack_32: Vec::with_capacity(STACK_32_SIZE), - stack_64: Vec::with_capacity(STACK_64_SIZE), - stack_128: Vec::with_capacity(STACK_128_SIZE), - stack_ref: Vec::with_capacity(STACK_REF_SIZE), - } - } - - pub(crate) fn height(&self) -> StackLocation { - StackLocation { - s32: self.stack_32.len() as u32, - s64: self.stack_64.len() as u32, - s128: self.stack_128.len() as u32, - sref: self.stack_ref.len() as u32, - } - } - - pub(crate) fn peek(&self) -> Result { - T::stack_peek(self) - } - - pub(crate) fn pop(&mut self) -> Result { - T::stack_pop(self) - } - - pub(crate) fn push(&mut self, value: T) { - T::stack_push(self, value) - } - - pub(crate) fn drop(&mut self) -> Result<()> { - T::stack_pop(self).map(|_| ()) - } - - pub(crate) fn select(&mut self) -> Result<()> { - let cond: i32 = self.pop()?; - let val2: T = self.pop()?; - if cond == 0 { - self.drop::()?; - self.push(val2); - } - Ok(()) - } - - pub(crate) fn calculate(&mut self, func: fn(T, T) -> Result) -> Result<()> { - let v2 = T::stack_pop(self)?; - let v1 = T::stack_pop(self)?; - U::stack_push(self, func(v1, v2)?); - Ok(()) - } - - pub(crate) fn replace_top(&mut self, func: fn(T) -> Result) -> Result<()> { - let v1 = T::stack_pop(self)?; - U::stack_push(self, func(v1)?); - Ok(()) - } - - pub(crate) fn pop_dyn(&mut self, val_type: ValType) -> Result { - match val_type { - ValType::I32 => self.pop().map(TinyWasmValue::Value32), - ValType::I64 => self.pop().map(TinyWasmValue::Value64), - ValType::V128 => self.pop().map(TinyWasmValue::Value128), - ValType::RefExtern => self.pop().map(TinyWasmValue::ValueRef), - ValType::RefFunc => self.pop().map(TinyWasmValue::ValueRef), - ValType::F32 => self.pop().map(TinyWasmValue::Value32), - ValType::F64 => self.pop().map(TinyWasmValue::Value64), - } - } - - pub(crate) fn pop_params(&mut self, val_types: &[ValType]) -> Result> { - val_types.iter().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>>() - } - - pub(crate) fn pop_results(&mut self, val_types: &[ValType]) -> Result> { - val_types.iter().rev().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>>().map(|mut v| { - v.reverse(); - v - }) - } - - pub(crate) fn pop_many_raw(&mut self, val_types: &[ValType]) -> Result> { - let mut values = Vec::with_capacity(val_types.len()); - for val_type in val_types.iter() { - values.push(self.pop_dyn(*val_type)?); - } - Ok(values) - } - - pub(crate) fn truncate_keep(&mut self, to: &StackLocation, keep: &StackHeight) { - truncate_keep(&mut self.stack_32, to.s32, keep.s32); - truncate_keep(&mut self.stack_64, to.s64, keep.s64); - truncate_keep(&mut self.stack_128, to.s128, keep.s128); - truncate_keep(&mut self.stack_ref, to.sref, keep.sref); - } - - pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) { - match value { - TinyWasmValue::Value32(v) => self.stack_32.push(v), - TinyWasmValue::Value64(v) => self.stack_64.push(v), - TinyWasmValue::Value128(v) => self.stack_128.push(v), - TinyWasmValue::ValueRef(v) => self.stack_ref.push(v), - } - } - - pub(crate) fn pop_wasmvalue(&mut self, val_type: ValType) -> Result { - match val_type { - ValType::I32 => self.pop().map(WasmValue::I32), - ValType::I64 => self.pop().map(WasmValue::I64), - ValType::V128 => self.pop().map(WasmValue::V128), - ValType::F32 => self.pop().map(WasmValue::F32), - ValType::F64 => self.pop().map(WasmValue::F64), - ValType::RefExtern => self.pop().map(|v| match v { - Some(v) => WasmValue::RefExtern(v), - None => WasmValue::RefNull(ValType::RefExtern), - }), - ValType::RefFunc => self.pop().map(|v| match v { - Some(v) => WasmValue::RefFunc(v), - None => WasmValue::RefNull(ValType::RefFunc), - }), - } - } - - pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { - for value in values.iter() { - self.push_dyn(value.into()) - } - } -} - -fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { - let total_to_keep = n + end_keep; - let len = data.len() as u32; - crate::log::error!("truncate_keep: len: {}, total_to_keep: {}, end_keep: {}", len, total_to_keep, end_keep); - - if len <= n { - return; // No need to truncate if the current size is already less than or equal to total_to_keep - } - - data.drain((n as usize)..(len - end_keep) as usize); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_truncate_keep() { - macro_rules! test_macro { - ($( $n:expr, $end_keep:expr, $expected:expr ),*) => { - $( - let mut stack = alloc::vec![1,2,3,4,5]; - truncate_keep(&mut stack, $n, $end_keep); - assert_eq!(stack.len(), $expected); - )* - }; - } - - test_macro! { - 0, 0, 0, - 1, 0, 1, - 0, 1, 1, - 1, 1, 2, - 2, 1, 3, - 2, 2, 4 - } - } -} diff --git a/crates/tinywasm/src/runtime/stack/values.rs b/crates/tinywasm/src/runtime/stack/values.rs deleted file mode 100644 index 606e13a..0000000 --- a/crates/tinywasm/src/runtime/stack/values.rs +++ /dev/null @@ -1,420 +0,0 @@ -#![allow(missing_docs)] -use tinywasm_types::{ValType, WasmValue}; - -use crate::{Error, Result}; - -use super::{call_stack::Locals, ValueStack}; - -pub type Value32 = u32; -pub type Value64 = u64; -pub type Value128 = u128; -pub type ValueRef = Option; - -#[derive(Debug, Clone, Copy)] -pub(crate) struct StackLocation { - pub(crate) s32: u32, - pub(crate) s64: u32, - pub(crate) s128: u32, - pub(crate) sref: u32, -} - -#[derive(Debug, Clone, Copy, Default)] -pub(crate) struct StackHeight { - pub(crate) s32: u32, - pub(crate) s64: u32, - pub(crate) s128: u32, - pub(crate) sref: u32, -} - -impl From for StackHeight { - fn from(value: ValType) -> Self { - match value { - ValType::I32 | ValType::F32 => Self { s32: 1, ..Default::default() }, - ValType::I64 | ValType::F64 => Self { s64: 1, ..Default::default() }, - ValType::V128 => Self { s128: 1, ..Default::default() }, - ValType::RefExtern | ValType::RefFunc => Self { sref: 1, ..Default::default() }, - } - } -} - -impl From<&[ValType]> for StackHeight { - fn from(value: &[ValType]) -> Self { - let mut s32 = 0; - let mut s64 = 0; - let mut s128 = 0; - let mut sref = 0; - for val_type in value.iter() { - match val_type { - ValType::I32 | ValType::F32 => s32 += 1, - ValType::I64 | ValType::F64 => s64 += 1, - ValType::V128 => s128 += 1, - ValType::RefExtern | ValType::RefFunc => sref += 1, - } - } - Self { s32, s64, s128, sref } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum TinyWasmValue { - Value32(Value32), - Value64(Value64), - Value128(Value128), - ValueRef(ValueRef), -} - -impl TinyWasmValue { - pub fn unwrap_32(&self) -> Value32 { - match self { - TinyWasmValue::Value32(v) => *v, - _ => unreachable!("Expected Value32"), - } - } - - pub fn unwrap_64(&self) -> Value64 { - match self { - TinyWasmValue::Value64(v) => *v, - _ => unreachable!("Expected Value64"), - } - } - - pub fn unwrap_128(&self) -> Value128 { - match self { - TinyWasmValue::Value128(v) => *v, - _ => unreachable!("Expected Value128"), - } - } - - pub fn unwrap_ref(&self) -> ValueRef { - match self { - TinyWasmValue::ValueRef(v) => *v, - _ => unreachable!("Expected ValueRef"), - } - } - - pub fn attach_type(&self, ty: ValType) -> WasmValue { - match ty { - ValType::I32 => WasmValue::I32(self.unwrap_32() as i32), - ValType::I64 => WasmValue::I64(self.unwrap_64() as i64), - ValType::F32 => WasmValue::F32(f32::from_bits(self.unwrap_32())), - ValType::F64 => WasmValue::F64(f64::from_bits(self.unwrap_64())), - ValType::V128 => WasmValue::V128(self.unwrap_128()), - ValType::RefExtern => match self.unwrap_ref() { - Some(v) => WasmValue::RefExtern(v), - None => WasmValue::RefNull(ValType::RefExtern), - }, - ValType::RefFunc => match self.unwrap_ref() { - Some(v) => WasmValue::RefFunc(v), - None => WasmValue::RefNull(ValType::RefFunc), - }, - } - } -} - -impl Default for TinyWasmValue { - fn default() -> Self { - TinyWasmValue::Value32(0) - } -} - -impl From for TinyWasmValue { - fn from(value: WasmValue) -> Self { - match value { - WasmValue::I32(v) => TinyWasmValue::Value32(v as u32), - WasmValue::I64(v) => TinyWasmValue::Value64(v as u64), - WasmValue::V128(v) => TinyWasmValue::Value128(v), - WasmValue::F32(v) => TinyWasmValue::Value32(v.to_bits()), - WasmValue::F64(v) => TinyWasmValue::Value64(v.to_bits()), - WasmValue::RefFunc(v) => TinyWasmValue::ValueRef(Some(v)), - WasmValue::RefExtern(v) => TinyWasmValue::ValueRef(Some(v)), - WasmValue::RefNull(_) => TinyWasmValue::ValueRef(None), - } - } -} - -impl From<&WasmValue> for TinyWasmValue { - fn from(value: &WasmValue) -> Self { - match value { - WasmValue::I32(v) => TinyWasmValue::Value32(*v as u32), - WasmValue::I64(v) => TinyWasmValue::Value64(*v as u64), - WasmValue::V128(v) => TinyWasmValue::Value128(*v), - WasmValue::F32(v) => TinyWasmValue::Value32(v.to_bits()), - WasmValue::F64(v) => TinyWasmValue::Value64(v.to_bits()), - WasmValue::RefFunc(v) => TinyWasmValue::ValueRef(Some(*v)), - WasmValue::RefExtern(v) => TinyWasmValue::ValueRef(Some(*v)), - WasmValue::RefNull(_) => TinyWasmValue::ValueRef(None), - } - } -} - -impl From for TinyWasmValue { - fn from(value: f32) -> Self { - TinyWasmValue::Value32(value.to_bits()) - } -} - -impl From for TinyWasmValue { - fn from(value: f64) -> Self { - TinyWasmValue::Value64(value.to_bits()) - } -} - -impl From for TinyWasmValue { - fn from(value: i32) -> Self { - TinyWasmValue::Value32(value as u32) - } -} - -impl From for TinyWasmValue { - fn from(value: u32) -> Self { - TinyWasmValue::Value32(value) - } -} - -impl From for TinyWasmValue { - fn from(value: i64) -> Self { - TinyWasmValue::Value64(value as u64) - } -} - -impl From for TinyWasmValue { - fn from(value: u64) -> Self { - TinyWasmValue::Value64(value) - } -} - -impl From for TinyWasmValue { - fn from(value: Value128) -> Self { - TinyWasmValue::Value128(value) - } -} - -impl From for TinyWasmValue { - fn from(value: ValueRef) -> Self { - TinyWasmValue::ValueRef(value) - } -} - -// TODO: this can be made a bit more maintainable by using a macro - -mod sealed { - #[allow(unreachable_pub)] - pub trait Sealed {} -} - -impl sealed::Sealed for i32 {} -impl sealed::Sealed for f32 {} -impl sealed::Sealed for i64 {} -impl sealed::Sealed for u64 {} -impl sealed::Sealed for f64 {} -impl sealed::Sealed for u32 {} -impl sealed::Sealed for Value128 {} -impl sealed::Sealed for ValueRef {} - -pub(crate) trait InternalValue: sealed::Sealed { - fn stack_push(stack: &mut ValueStack, value: Self); - fn stack_pop(stack: &mut ValueStack) -> Result - where - Self: Sized; - fn stack_peek(stack: &ValueStack) -> Result - where - Self: Sized; - - fn local_get(locals: &Locals, index: u32) -> Result - where - Self: Sized; - - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()>; -} - -impl InternalValue for i32 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_32.push(value as u32); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_32.pop().ok_or(Error::ValueStackUnderflow).map(|v| v as i32) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_32.last().ok_or(Error::ValueStackUnderflow).map(|v| *v as i32) - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(locals.locals_32[index as usize] as i32) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_32[index as usize] = value as u32; - Ok(()) - } -} - -impl InternalValue for f32 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_32.push(value.to_bits()); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_32.pop().ok_or(Error::ValueStackUnderflow).map(f32::from_bits) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_32.last().ok_or(Error::ValueStackUnderflow).map(|v| f32::from_bits(*v)) - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(f32::from_bits(locals.locals_32[index as usize])) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_32[index as usize] = value.to_bits(); - Ok(()) - } -} - -impl InternalValue for i64 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_64.push(value as u64); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_64.pop().ok_or(Error::ValueStackUnderflow).map(|v| v as i64) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_64.last().ok_or(Error::ValueStackUnderflow).map(|v| *v as i64) - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(locals.locals_64[index as usize] as i64) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_64[index as usize] = value as u64; - Ok(()) - } -} - -impl InternalValue for u64 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_64.push(value); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_64.pop().ok_or(Error::ValueStackUnderflow) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_64.last().ok_or(Error::ValueStackUnderflow).copied() - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(locals.locals_64[index as usize]) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_64[index as usize] = value; - Ok(()) - } -} - -impl InternalValue for f64 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_64.push(value.to_bits()); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_64.pop().ok_or(Error::ValueStackUnderflow).map(f64::from_bits) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_64.last().ok_or(Error::ValueStackUnderflow).map(|v| f64::from_bits(*v)) - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(f64::from_bits(locals.locals_64[index as usize])) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_64[index as usize] = value.to_bits(); - Ok(()) - } -} - -impl InternalValue for u32 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_32.push(value); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_32.pop().ok_or(Error::ValueStackUnderflow) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_32.last().ok_or(Error::ValueStackUnderflow).copied() - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(locals.locals_32[index as usize]) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_32[index as usize] = value; - Ok(()) - } -} - -impl InternalValue for Value128 { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_128.push(value); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_128.pop().ok_or(Error::ValueStackUnderflow) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_128.last().ok_or(Error::ValueStackUnderflow).copied() - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(locals.locals_128[index as usize]) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_128[index as usize] = value; - Ok(()) - } -} - -impl InternalValue for ValueRef { - #[inline] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.stack_ref.push(value); - } - #[inline] - fn stack_pop(stack: &mut ValueStack) -> Result { - stack.stack_ref.pop().ok_or(Error::ValueStackUnderflow) - } - #[inline] - fn stack_peek(stack: &ValueStack) -> Result { - stack.stack_ref.last().ok_or(Error::ValueStackUnderflow).copied() - } - #[inline] - fn local_get(locals: &Locals, index: u32) -> Result { - Ok(locals.locals_ref[index as usize]) - } - #[inline] - fn local_set(locals: &mut Locals, index: u32, value: Self) -> Result<()> { - locals.locals_ref[index as usize] = value; - Ok(()) - } -} diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs index 6da73c7..40301a5 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -9,7 +9,7 @@ use tinywasm_types::*; pub(crate) struct ElementInstance { pub(crate) kind: ElementKind, pub(crate) items: Option>, // none is the element was dropped - _owner: ModuleInstanceAddr, // index into store.module_instances + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl ElementInstance { diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index ef370c2..f3f1df0 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -8,11 +8,11 @@ use tinywasm_types::*; /// See pub(crate) struct FunctionInstance { pub(crate) func: Function, - pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions } impl FunctionInstance { pub(crate) fn new_wasm(func: WasmFunction, owner: ModuleInstanceAddr) -> Self { - Self { func: Function::Wasm(Rc::new(func)), owner } + Self { func: Function::Wasm(Rc::new(func)), _owner: owner } } } diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index a18e43d..b7a47d6 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -1,10 +1,7 @@ +use crate::interpreter::TinyWasmValue; use core::cell::Cell; - -use alloc::{format, string::ToString}; use tinywasm_types::*; -use crate::{runtime::TinyWasmValue, unlikely, Error, Result}; - /// A WebAssembly Global Instance /// /// See @@ -19,55 +16,4 @@ impl GlobalInstance { pub(crate) fn new(ty: GlobalType, value: TinyWasmValue, owner: ModuleInstanceAddr) -> Self { Self { ty, value: value.into(), _owner: owner } } - - #[inline] - pub(crate) fn get(&self) -> WasmValue { - self.value.get().attach_type(self.ty.ty) - } - - pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { - if unlikely(val.val_type() != self.ty.ty) { - return Err(Error::Other(format!( - "global type mismatch: expected {:?}, got {:?}", - self.ty.ty, - val.val_type() - ))); - } - - if unlikely(!self.ty.mutable) { - return Err(Error::Other("global is immutable".to_string())); - } - - self.value.set(val.into()); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_global_instance_get_set() { - let global_type = GlobalType { ty: ValType::I32, mutable: true }; - let initial_value = TinyWasmValue::from(10i32); - let owner = 0; - - let mut global_instance = GlobalInstance::new(global_type, initial_value, owner); - - // Test `get` - assert_eq!(global_instance.get(), WasmValue::I32(10), "global value should be 10"); - - // Test `set` with correct type - assert!(global_instance.set(WasmValue::I32(20)).is_ok(), "set should succeed"); - assert_eq!(global_instance.get(), WasmValue::I32(20), "global value should be 20"); - - // Test `set` with incorrect type - assert!(matches!(global_instance.set(WasmValue::F32(1.0)), Err(Error::Other(_))), "set should fail"); - - // Test `set` on immutable global - let immutable_global_type = GlobalType { ty: ValType::I32, mutable: false }; - let mut immutable_global_instance = GlobalInstance::new(immutable_global_type, initial_value, owner); - assert!(matches!(immutable_global_instance.set(WasmValue::I32(30)), Err(Error::Other(_)))); - } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 9ebd82e..b427497 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -3,7 +3,7 @@ use core::cell::RefCell; use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; -use crate::runtime::{self, InterpreterRuntime, TinyWasmValue}; +use crate::interpreter::{self, InterpreterRuntime, TinyWasmValue}; use crate::{Error, Function, ModuleInstance, Result, Trap}; mod data; @@ -57,7 +57,7 @@ impl Store { } /// Create a new store with the given runtime - pub(crate) fn runtime(&self) -> runtime::InterpreterRuntime { + pub(crate) fn runtime(&self) -> interpreter::InterpreterRuntime { match self.runtime { Runtime::Default => InterpreterRuntime::default(), } @@ -381,7 +381,7 @@ impl Store { } pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { - self.data.funcs.push(FunctionInstance { func, owner: idx }); + self.data.funcs.push(FunctionInstance { func, _owner: idx }); Ok(self.data.funcs.len() as FuncAddr - 1) } diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 52146e7..0a57f4c 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -57,11 +57,7 @@ impl TinyWasmModule { /// Creates a TinyWasmModule from a slice of bytes. pub fn from_twasm(wasm: &[u8]) -> Result { let len = validate_magic(wasm)?; - let root = check_archived_root::(&wasm[len..]).map_err(|_e| { - crate::log::error!("Invalid archive: {}", _e); - TwasmError::InvalidArchive - })?; - + let root = check_archived_root::(&wasm[len..]).map_err(|_e| TwasmError::InvalidArchive)?; Ok(root.deserialize(&mut rkyv::Infallible).unwrap()) } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index d19326d..dd201ad 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,50 +1,6 @@ use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; use crate::{DataAddr, ElemAddr, MemAddr}; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] -pub enum BlockArgs { - Empty, - Type(ValType), - FuncType(u32), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] -/// A packed representation of BlockArgs -/// This is needed to keep the size of the Instruction enum small. -/// Sadly, using #[repr(u8)] on BlockArgs itself is not possible because of the FuncType variant. -pub struct BlockArgsPacked([u8; 5]); // Modifying this directly can cause runtime errors, but no UB - -impl From for BlockArgsPacked { - fn from(args: BlockArgs) -> Self { - let mut packed = [0; 5]; - match args { - BlockArgs::Empty => packed[0] = 0, - BlockArgs::Type(t) => { - packed[0] = 1; - packed[1] = t.to_byte(); - } - BlockArgs::FuncType(t) => { - packed[0] = 2; - packed[1..].copy_from_slice(&t.to_le_bytes()); - } - } - Self(packed) - } -} - -impl From for BlockArgs { - fn from(packed: BlockArgsPacked) -> Self { - match packed.0[0] { - 0 => BlockArgs::Empty, - 1 => BlockArgs::Type(ValType::from_byte(packed.0[1]).unwrap()), - 2 => BlockArgs::FuncType(u32::from_le_bytes(packed.0[1..].try_into().unwrap())), - _ => unreachable!(), - } - } -} - /// Represents a memory immediate in a WebAssembly memory instruction. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] @@ -106,9 +62,19 @@ pub enum Instruction { // See Unreachable, Nop, - Block(BlockArgs, EndOffset), - Loop(BlockArgs, EndOffset), - If(BlockArgsPacked, ElseOffset, EndOffset), // If else offset is 0 if there is no else block + + Block(EndOffset), + BlockWithType(ValType, EndOffset), + BlockWithFuncType(TypeAddr, EndOffset), + + Loop(EndOffset), + LoopWithType(ValType, EndOffset), + LoopWithFuncType(TypeAddr, EndOffset), + + If(ElseOffset, EndOffset), + IfWithType(ValType, ElseOffset, EndOffset), + IfWithFuncType(TypeAddr, ElseOffset, EndOffset), + Else(EndOffset), EndBlockFrame, Br(LabelAddr), @@ -233,44 +199,3 @@ pub enum Instruction { DataDrop(DataAddr), ElemDrop(ElemAddr), } - -#[cfg(test)] -mod test_blockargs_packed { - use super::*; - - #[test] - fn test_empty() { - let packed: BlockArgsPacked = BlockArgs::Empty.into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Empty); - } - - #[test] - fn test_val_type_i32() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::I32).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::I32)); - } - - #[test] - fn test_val_type_i64() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::I64).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::I64)); - } - - #[test] - fn test_val_type_f32() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::F32).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::F32)); - } - - #[test] - fn test_val_type_f64() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::F64).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::F64)); - } - - #[test] - fn test_func_type() { - let packed: BlockArgsPacked = BlockArgs::FuncType(0x12345678).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::FuncType(0x12345678)); - } -} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 785b13b..cdee7e6 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -14,7 +14,7 @@ use core::{fmt::Debug, ops::Range}; // log for logging (optional). #[cfg(feature = "logging")] -#[allow(clippy::single_component_path_imports)] +#[allow(clippy::single_component_path_imports, unused)] use log; #[cfg(not(feature = "logging"))] @@ -125,7 +125,7 @@ pub type ExternAddr = Addr; // additional internal addresses pub type TypeAddr = Addr; -pub type LocalAddr = Addr; +pub type LocalAddr = u16; // there can't be more than 50.000 locals in a function pub type LabelAddr = Addr; pub type ModuleInstanceAddr = Addr; diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 6c422d0..c418338 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -146,31 +146,6 @@ impl ValType { pub fn is_simd(&self) -> bool { matches!(self, ValType::V128) } - - pub(crate) fn to_byte(self) -> u8 { - match self { - ValType::I32 => 0x7F, - ValType::I64 => 0x7E, - ValType::F32 => 0x7D, - ValType::F64 => 0x7C, - ValType::V128 => 0x7B, - ValType::RefFunc => 0x70, - ValType::RefExtern => 0x6F, - } - } - - pub(crate) fn from_byte(byte: u8) -> Option { - match byte { - 0x7F => Some(ValType::I32), - 0x7E => Some(ValType::I64), - 0x7D => Some(ValType::F32), - 0x7C => Some(ValType::F64), - 0x7B => Some(ValType::V128), - 0x70 => Some(ValType::RefFunc), - 0x6F => Some(ValType::RefExtern), - _ => None, - } - } } macro_rules! impl_conversion_for_wasmvalue { @@ -202,9 +177,4 @@ macro_rules! impl_conversion_for_wasmvalue { } } -impl_conversion_for_wasmvalue! { - i32 => I32, - i64 => I64, - f32 => F32, - f64 => F64 -} +impl_conversion_for_wasmvalue! { i32 => I32, i64 => I64, f32 => F32, f64 => F64, u128 => V128 } -- cgit v1.3.1