diff --git a/CHANGELOG.md b/CHANGELOG.md index f6539a1..da8359c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [UNRELEASED] + +### Changed + +- Improved support for WebAssembly 2.0 features +- Simplify and optimize the interpreter loop +- Use a seperate stack and locals for 32, 64 and 128 bit values and references (#21) +- Updated to latest wasmparser version + ## [0.7.0] - 2024-05-15 **All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.6.0...v0.7.0 diff --git a/Cargo.lock b/Cargo.lock index d432d76..7a7412a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,9 +295,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c891175c3fb232128f48de6590095e59198bbeb8620c310be349bfc3afd12c7b" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" [[package]] name = "cfg-if" @@ -768,9 +768,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "enum-iterator" @@ -1999,7 +1999,7 @@ dependencies = [ "tinywasm-parser", "tinywasm-types", "wasm-testsuite", - "wast 211.0.1", + "wast 212.0.0", ] [[package]] @@ -2011,7 +2011,7 @@ dependencies = [ "log", "pretty_env_logger", "tinywasm", - "wast 211.0.1", + "wast 212.0.0", ] [[package]] @@ -2020,7 +2020,7 @@ version = "0.7.0" dependencies = [ "log", "tinywasm-types", - "wasmparser 0.211.1", + "wasmparser 0.212.0", ] [[package]] @@ -2304,9 +2304,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.211.1" +version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e7d931a1120ef357f32b74547646b6fa68ea25e377772b72874b131a9ed70d4" +checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33" dependencies = [ "leb128", ] @@ -2550,9 +2550,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.211.1" +version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3189cc8a91f547390e2f043ca3b3e3fe0892f7d581767fd4e4b7f3dc3fe8e561" +checksum = "8d28bc49ba1e5c5b61ffa7a2eace10820443c4b7d1c0b144109261d14570fdf8" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", @@ -2584,15 +2584,15 @@ dependencies = [ [[package]] name = "wast" -version = "211.0.1" +version = "212.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25506dd82d00da6b14a87436b3d52b1d264083fa79cdb72a0d1b04a8595ccaa" +checksum = "4606a05fb0aae5d11dd7d8280a640d88a63ee019360ba9be552da3d294b8d1f5" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.211.1", + "wasm-encoder 0.212.0", ] [[package]] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 60207a6..bccf998 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,7 +20,7 @@ argh="0.1" color-eyre={version="0.6", default-features=false} log="0.4" pretty_env_logger="0.5" -wast={version="211.0", optional=true} +wast={version="212.0", optional=true} [features] default=["wat"] diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 5b28112..89d2b71 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace=true rust-version.workspace=true [dependencies] -wasmparser={version="0.211", default-features=false, features=["validate"]} +wasmparser={version="0.212", default-features=false, features=["validate"]} log={version="0.4", optional=true} tinywasm-types={version="0.7.0", path="../types", default-features=false} diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 59c023e..f18b570 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -2,7 +2,7 @@ use crate::Result; use crate::{module::Code, visit::process_operators_and_validate}; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use tinywasm_types::*; -use wasmparser::{FuncValidator, OperatorsReader, ValidatorResources}; +use wasmparser::{FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources}; pub(crate) fn convert_module_elements<'a, T: IntoIterator>>>( elements: T, @@ -168,27 +168,45 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result, - validator: &mut FuncValidator, -) -> Result { + mut validator: FuncValidator, +) -> Result<(Code, FuncValidatorAllocations)> { let locals_reader = func.get_locals_reader()?; let count = locals_reader.get_count(); let pos = locals_reader.original_position(); - let locals = { - let mut locals = Vec::new(); - locals.reserve_exact(count as usize); - for (i, local) in locals_reader.into_iter().enumerate() { - let local = local?; - validator.define_locals(pos + i, local.0, local.1)?; - for _ in 0..local.0 { - locals.push(convert_valtype(&local.1)); + // maps a local's address to the index in the type's locals array + let mut local_addr_map = Vec::with_capacity(count as usize); + let mut local_counts = LocalCounts::default(); + + for (i, local) in locals_reader.into_iter().enumerate() { + let local = local?; + validator.define_locals(pos + i, local.0, local.1)?; + } + + for i in 0..validator.len_locals() { + match validator.get_local_type(i) { + Some(wasmparser::ValType::I32) | Some(wasmparser::ValType::F32) => { + local_addr_map.push(local_counts.local_32); + local_counts.local_32 += 1; + } + Some(wasmparser::ValType::I64) | Some(wasmparser::ValType::F64) => { + local_addr_map.push(local_counts.local_64); + local_counts.local_64 += 1; } + Some(wasmparser::ValType::V128) => { + local_addr_map.push(local_counts.local_128); + local_counts.local_128 += 1; + } + Some(wasmparser::ValType::Ref(_)) => { + local_addr_map.push(local_counts.local_ref); + local_counts.local_ref += 1; + } + None => return Err(crate::ParseError::UnsupportedOperator("Unknown local type".to_string())), } - locals.into_boxed_slice() - }; + } - let body = process_operators_and_validate(validator, func)?; - Ok((body, locals)) + let (body, allocations) = process_operators_and_validate(validator, func, local_addr_map)?; + Ok(((body, local_counts), allocations)) } pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 51743c7..0e8f976 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -62,6 +62,7 @@ impl Parser { component_model: false, component_model_nested_names: false, component_model_values: false, + component_model_more_flags: false, exceptions: false, extended_const: false, gc: false, diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 294782c..3ee619f 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -3,12 +3,12 @@ use crate::{conversion, ParseError, Result}; use alloc::string::ToString; use alloc::{boxed::Box, format, vec::Vec}; use tinywasm_types::{ - Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, ValType, + Data, Element, Export, FuncType, Global, Import, Instruction, LocalCounts, MemoryType, TableType, TinyWasmModule, WasmFunction, }; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Box<[Instruction]>, Box<[ValType]>); +pub(crate) type Code = (Box<[Instruction]>, LocalCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -135,9 +135,10 @@ impl ModuleReader { CodeSectionEntry(function) => { debug!("Found code section entry"); let v = validator.code_section_entry(&function)?; - let mut func_validator = v.into_validator(self.func_validator_allocations.take().unwrap_or_default()); - self.code.push(conversion::convert_module_code(function, &mut func_validator)?); - self.func_validator_allocations = Some(func_validator.into_allocations()); + let func_validator = v.into_validator(self.func_validator_allocations.take().unwrap_or_default()); + let (code, allocations) = conversion::convert_module_code(function, func_validator)?; + self.code.push(code); + self.func_validator_allocations = Some(allocations); } ImportSection(reader) => { if !self.imports.is_empty() { diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index f22e05b..4e2fece 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -4,45 +4,43 @@ use crate::conversion::{convert_heaptype, convert_valtype}; use alloc::string::ToString; use alloc::{boxed::Box, vec::Vec}; use tinywasm_types::{Instruction, MemoryArg}; -use wasmparser::{FuncValidator, FunctionBody, VisitOperator, WasmModuleResources}; +use wasmparser::{FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, WasmModuleResources}; -struct ValidateThenVisit<'a, T, U>(T, &'a mut U); +struct ValidateThenVisit<'a, R: WasmModuleResources>(usize, &'a mut FunctionBuilder); macro_rules! validate_then_visit { ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => {$( fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { - self.0.$visit($($($arg.clone()),*)?)?; - self.1.$visit($($($arg),*)?); + self.1.$visit($($($arg.clone()),*)?); + self.1.validator_visitor(self.0).$visit($($($arg),*)?)?; Ok(()) } )*}; } -impl<'a, T, U> VisitOperator<'a> for ValidateThenVisit<'_, T, U> -where - T: VisitOperator<'a, Output = wasmparser::Result<()>>, - U: VisitOperator<'a, Output = ()>, -{ +impl<'a, R: WasmModuleResources> VisitOperator<'a> for ValidateThenVisit<'_, R> { type Output = Result<()>; wasmparser::for_each_operator!(validate_then_visit); } pub(crate) fn process_operators_and_validate( - validator: &mut FuncValidator, + validator: FuncValidator, body: FunctionBody<'_>, -) -> Result> { + local_addr_map: Vec, +) -> Result<(Box<[Instruction]>, FuncValidatorAllocations)> { let mut reader = body.get_operators_reader()?; let remaining = reader.get_binary_reader().bytes_remaining(); - let mut builder = FunctionBuilder::new(remaining); + let mut builder = FunctionBuilder::new(remaining, validator, local_addr_map); + while !reader.eof() { - let validate = validator.visitor(reader.original_position()); - reader.visit_operator(&mut ValidateThenVisit(validate, &mut builder))??; + reader.visit_operator(&mut ValidateThenVisit(reader.original_position(), &mut builder))??; } - validator.finish(reader.original_position())?; + + builder.validator_finish(reader.original_position())?; if !builder.errors.is_empty() { return Err(builder.errors.remove(0)); } - Ok(builder.instructions.into_boxed_slice()) + Ok((builder.instructions.into_boxed_slice(), builder.validator.into_allocations())) } macro_rules! define_operands { @@ -77,15 +75,32 @@ macro_rules! define_mem_operands { )*}; } -pub(crate) struct FunctionBuilder { +pub(crate) struct FunctionBuilder { + validator: FuncValidator, instructions: Vec, label_ptrs: Vec, + local_addr_map: Vec, errors: Vec, } -impl FunctionBuilder { - pub(crate) fn new(instr_capacity: usize) -> Self { +impl FunctionBuilder { + pub(crate) fn validator_visitor( + &mut self, + offset: usize, + ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> { + self.validator.visitor(offset) + } + + pub(crate) fn validator_finish(&mut self, offset: usize) -> Result<(), wasmparser::BinaryReaderError> { + self.validator.finish(offset) + } +} + +impl FunctionBuilder { + pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { Self { + validator, + local_addr_map, instructions: Vec::with_capacity(instr_capacity), label_ptrs: Vec::with_capacity(256), errors: Vec::new(), @@ -115,7 +130,7 @@ macro_rules! impl_visit_operator { }; } -impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { +impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuilder { type Output = (); wasmparser::for_each_operator!(impl_visit_operator); @@ -123,7 +138,6 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { visit_br, Instruction::Br, u32, visit_br_if, Instruction::BrIf, u32, visit_global_get, Instruction::GlobalGet, u32, - visit_global_set, Instruction::GlobalSet, u32, visit_i32_const, Instruction::I32Const, i32, visit_i64_const, Instruction::I64Const, i64, visit_call, Instruction::Call, u32, @@ -161,8 +175,6 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { visit_unreachable, Instruction::Unreachable, visit_nop, Instruction::Nop, visit_return, Instruction::Return, - visit_drop, Instruction::Drop, - visit_select, Instruction::Select(None), visit_i32_eqz, Instruction::I32Eqz, visit_i32_eq, Instruction::I32Eq, visit_i32_ne, Instruction::I32Ne, @@ -305,94 +317,101 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { visit_i64_trunc_sat_f64_u, Instruction::I64TruncSatF64U } - fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { - let arg = MemoryArg { offset: memarg.offset, mem_addr: memarg.memory }; - let i32store = Instruction::I32Store { offset: arg.offset, mem_addr: arg.mem_addr }; - - if self.instructions.len() < 3 || arg.mem_addr > 0xFF || arg.offset > 0xFFFF_FFFF { - return self.instructions.push(i32store); + fn visit_global_set(&mut self, global_index: u32) -> Self::Output { + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match convert_valtype(&t) { + tinywasm_types::ValType::I32 => Instruction::GlobalSet32(global_index), + tinywasm_types::ValType::F32 => Instruction::GlobalSet32(global_index), + tinywasm_types::ValType::I64 => Instruction::GlobalSet64(global_index), + tinywasm_types::ValType::F64 => Instruction::GlobalSet64(global_index), + tinywasm_types::ValType::V128 => Instruction::GlobalSet128(global_index), + tinywasm_types::ValType::RefExtern => Instruction::GlobalSetRef(global_index), + tinywasm_types::ValType::RefFunc => Instruction::GlobalSetRef(global_index), + }), + _ => self.visit_unreachable(), } + } - match self.instructions[self.instructions.len() - 2..] { - [_, Instruction::LocalGet2(a, b)] => { - self.instructions.pop(); - self.instructions.push(Instruction::I32StoreLocal { - local_a: a, - local_b: b, - offset: arg.offset as u32, - mem_addr: arg.mem_addr as u8, - }) - } - [Instruction::LocalGet(a), Instruction::I32Const(b)] => { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32ConstStoreLocal { - local: a, - const_i32: b, - offset: arg.offset as u32, - mem_addr: arg.mem_addr as u8, - }) - } - _ => self.instructions.push(i32store), + fn visit_drop(&mut self) -> Self::Output { + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match convert_valtype(&t) { + tinywasm_types::ValType::I32 => Instruction::Drop32, + tinywasm_types::ValType::F32 => Instruction::Drop32, + tinywasm_types::ValType::I64 => Instruction::Drop64, + tinywasm_types::ValType::F64 => Instruction::Drop64, + tinywasm_types::ValType::V128 => Instruction::Drop128, + tinywasm_types::ValType::RefExtern => Instruction::DropRef, + tinywasm_types::ValType::RefFunc => Instruction::DropRef, + }), + _ => self.visit_unreachable(), + } + } + fn visit_select(&mut self) -> Self::Output { + match self.validator.get_operand_type(1) { + Some(Some(t)) => self.visit_typed_select(t), + _ => self.visit_unreachable(), } } + fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { + let arg = MemoryArg { offset: memarg.offset, mem_addr: memarg.memory }; + let i32store = Instruction::I32Store { offset: arg.offset, mem_addr: arg.mem_addr }; + self.instructions.push(i32store) + } fn visit_local_get(&mut self, idx: u32) -> Self::Output { - let Some(instruction) = self.instructions.last_mut() else { - return self.instructions.push(Instruction::LocalGet(idx)); - }; - - match instruction { - Instruction::LocalGet(a) => *instruction = Instruction::LocalGet2(*a, idx), - Instruction::LocalGet2(a, b) => *instruction = Instruction::LocalGet3(*a, *b, idx), - Instruction::LocalTee(a) => *instruction = Instruction::LocalTeeGet(*a, idx), - _ => self.instructions.push(Instruction::LocalGet(idx)), - }; + let resolved_idx = self.local_addr_map[idx as usize]; + match self.validator.get_local_type(idx) { + Some(t) => self.instructions.push(match convert_valtype(&t) { + tinywasm_types::ValType::I32 => Instruction::LocalGet32(resolved_idx), + tinywasm_types::ValType::F32 => Instruction::LocalGet32(resolved_idx), + tinywasm_types::ValType::I64 => Instruction::LocalGet64(resolved_idx), + tinywasm_types::ValType::F64 => Instruction::LocalGet64(resolved_idx), + tinywasm_types::ValType::V128 => Instruction::LocalGet128(resolved_idx), + tinywasm_types::ValType::RefExtern => Instruction::LocalGetRef(resolved_idx), + tinywasm_types::ValType::RefFunc => Instruction::LocalGetRef(resolved_idx), + }), + _ => self.visit_unreachable(), + } } fn visit_local_set(&mut self, idx: u32) -> Self::Output { - let Some(instruction) = self.instructions.last_mut() else { - return self.instructions.push(Instruction::LocalSet(idx)); - }; - match instruction { - Instruction::LocalGet(a) => *instruction = Instruction::LocalGetSet(*a, idx), - _ => self.instructions.push(Instruction::LocalSet(idx)), - }; + let resolved_idx = self.local_addr_map[idx as usize]; + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match convert_valtype(&t) { + tinywasm_types::ValType::I32 => Instruction::LocalSet32(resolved_idx), + tinywasm_types::ValType::F32 => Instruction::LocalSet32(resolved_idx), + tinywasm_types::ValType::I64 => Instruction::LocalSet64(resolved_idx), + tinywasm_types::ValType::F64 => Instruction::LocalSet64(resolved_idx), + tinywasm_types::ValType::V128 => Instruction::LocalSet128(resolved_idx), + tinywasm_types::ValType::RefExtern => Instruction::LocalSetRef(resolved_idx), + tinywasm_types::ValType::RefFunc => Instruction::LocalSetRef(resolved_idx), + }), + _ => self.visit_unreachable(), + } } fn visit_local_tee(&mut self, idx: u32) -> Self::Output { - self.instructions.push(Instruction::LocalTee(idx)) + let resolved_idx = self.local_addr_map[idx as usize]; + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match convert_valtype(&t) { + tinywasm_types::ValType::I32 => Instruction::LocalTee32(resolved_idx), + tinywasm_types::ValType::F32 => Instruction::LocalTee32(resolved_idx), + tinywasm_types::ValType::I64 => Instruction::LocalTee64(resolved_idx), + tinywasm_types::ValType::F64 => Instruction::LocalTee64(resolved_idx), + tinywasm_types::ValType::V128 => Instruction::LocalTee128(resolved_idx), + tinywasm_types::ValType::RefExtern => Instruction::LocalTeeRef(resolved_idx), + tinywasm_types::ValType::RefFunc => Instruction::LocalTeeRef(resolved_idx), + }), + _ => self.visit_unreachable(), + } } fn visit_i64_rotl(&mut self) -> Self::Output { - let Some([Instruction::I64Xor, Instruction::I64Const(a)]) = self.instructions.last_chunk::<2>() else { - return self.instructions.push(Instruction::I64Rotl); - }; - let a = *a; - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I64XorConstRotl(a)) + self.instructions.push(Instruction::I64Rotl) } fn visit_i32_add(&mut self) -> Self::Output { - let Some(last) = self.instructions.last_chunk::<2>() else { - return self.instructions.push(Instruction::I32Add); - }; - - match *last { - [Instruction::LocalGet(a), Instruction::I32Const(b)] => { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32LocalGetConstAdd(a, b)) - } - [Instruction::LocalGet2(a, b), Instruction::I32Const(c)] => { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::LocalGet(a)); - self.instructions.push(Instruction::I32LocalGetConstAdd(b, c)) - } - _ => self.instructions.push(Instruction::I32Add), - } + self.instructions.push(Instruction::I32Add) } fn visit_block(&mut self, blockty: wasmparser::BlockType) -> Self::Output { @@ -474,7 +493,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { .targets() .map(|t| t.map(Instruction::BrLabel)) .collect::, wasmparser::BinaryReaderError>>() - .expect("BrTable targets are invalid, this should have been caught by the validator"); + .expect("visit_br_table: BrTable targets are invalid, this should have been caught by the validator"); self.instructions.extend(([Instruction::BrTable(def, instrs.len() as u32)].into_iter()).chain(instrs)); } @@ -518,7 +537,15 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { } fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { - self.instructions.push(Instruction::Select(Some(convert_valtype(&ty)))) + self.instructions.push(match convert_valtype(&ty) { + tinywasm_types::ValType::I32 => Instruction::Select32, + tinywasm_types::ValType::F32 => Instruction::Select32, + tinywasm_types::ValType::I64 => Instruction::Select64, + tinywasm_types::ValType::F64 => Instruction::Select64, + tinywasm_types::ValType::V128 => Instruction::Select128, + tinywasm_types::ValType::RefExtern => Instruction::SelectRef, + tinywasm_types::ValType::RefFunc => Instruction::SelectRef, + }) } define_primitive_operands! { diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 491522c..d82d850 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -19,9 +19,19 @@ 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="211.0"} +wast={version="212.0"} owo-colors={version="4.0"} eyre={version="0.6"} serde_json={version="1.0"} diff --git a/crates/tinywasm/src/boxvec.rs b/crates/tinywasm/src/boxvec.rs deleted file mode 100644 index 9c6732d..0000000 --- a/crates/tinywasm/src/boxvec.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::unlikely; -use alloc::{borrow::Cow, boxed::Box, vec::Vec}; -use core::ops::RangeBounds; - -// A Vec-like type that doesn't deallocate memory when popping elements. -#[derive(Debug)] -pub(crate) struct BoxVec { - pub(crate) data: Box<[T]>, - pub(crate) end: usize, -} - -impl BoxVec { - #[inline(always)] - pub(crate) fn with_capacity(capacity: usize) -> Self { - let mut data = Vec::new(); - data.reserve_exact(capacity); - data.resize_with(capacity, T::default); - Self { data: data.into_boxed_slice(), end: 0 } - } - - #[inline(always)] - pub(crate) fn push(&mut self, value: T) { - assert!(self.end <= self.data.len(), "stack overflow"); - self.data[self.end] = value; - self.end += 1; - } - - #[inline(always)] - pub(crate) fn pop(&mut self) -> Option { - assert!(self.end <= self.data.len(), "invalid stack state (should be impossible)"); - if unlikely(self.end == 0) { - None - } else { - self.end -= 1; - Some(self.data[self.end]) - } - } - - #[inline(always)] - pub(crate) fn len(&self) -> usize { - self.end - } - - #[inline(always)] - pub(crate) fn extend_from_slice(&mut self, values: &[T]) { - let new_end = self.end + values.len(); - assert!(new_end <= self.data.len(), "stack overflow"); - self.data[self.end..new_end].copy_from_slice(values); - self.end = new_end; - } - - #[inline(always)] - pub(crate) fn last_mut(&mut self) -> Option<&mut T> { - assert!(self.end <= self.data.len(), "invalid stack state (should be impossible)"); - if unlikely(self.end == 0) { - None - } else { - Some(&mut self.data[self.end - 1]) - } - } - - #[inline(always)] - pub(crate) fn last(&self) -> Option<&T> { - assert!(self.end <= self.data.len(), "invalid stack state (should be impossible)"); - if unlikely(self.end == 0) { - None - } else { - Some(&self.data[self.end - 1]) - } - } - - #[inline(always)] - pub(crate) fn pop_n(&mut self, n: usize) -> Option<&[T]> { - assert!(self.end <= self.data.len(), "invalid stack state (should be impossible)"); - if unlikely(self.end < n) { - None - } else { - let start = self.end - n; - let end = self.end; - self.end = start; - Some(&self.data[start..end]) - } - } - - #[inline(always)] - pub(crate) fn extend(&mut self, iter: impl Iterator) { - let (lower, _) = iter.size_hint(); - let upper = lower; - let new_end = self.end + upper; - assert!(new_end <= self.data.len(), "stack overflow"); - for (i, value) in iter.enumerate() { - self.data[self.end + i] = value; - } - self.end = new_end; - } - - #[inline(always)] - pub(crate) fn drain(&mut self, range: impl RangeBounds) -> Cow<'_, [T]> { - let start = match range.start_bound() { - core::ops::Bound::Included(&start) => start, - core::ops::Bound::Excluded(&start) => start + 1, - core::ops::Bound::Unbounded => 0, - }; - let end = match range.end_bound() { - core::ops::Bound::Included(&end) => end + 1, - core::ops::Bound::Excluded(&end) => end, - core::ops::Bound::Unbounded => self.end, - }; - - assert!(start <= end); - assert!(end <= self.end); - - if end == self.end { - self.end = start; - return Cow::Borrowed(&self.data[start..end]); - } - - let drain = self.data[start..end].to_vec(); - self.data.copy_within(end..self.end, start); - self.end -= end - start; - Cow::Owned(drain) - } -} - -impl core::ops::Index for BoxVec { - type Output = T; - - #[inline(always)] - fn index(&self, index: usize) -> &T { - &self.data[index] - } -} - -impl core::ops::Index> for BoxVec { - type Output = [T]; - - #[inline(always)] - fn index(&self, index: core::ops::Range) -> &[T] { - &self.data[index] - } -} - -impl core::ops::IndexMut for BoxVec { - #[inline(always)] - fn index_mut(&mut self, index: usize) -> &mut T { - &mut self.data[index] - } -} diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index a6e16ad..650f9df 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,5 +1,5 @@ use crate::runtime::{CallFrame, Stack}; -use crate::{log, runtime::RawWasmValue, unlikely, Function}; +use crate::{log, unlikely, Function}; use crate::{Error, FuncContext, Result, Store}; use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; use tinywasm_types::{FuncType, ModuleInstanceAddr, ValType, WasmValue}; @@ -59,8 +59,7 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let call_frame_params = params.iter().map(|v| RawWasmValue::from(*v)).collect::>(); - let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, &call_frame_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) @@ -71,16 +70,16 @@ impl FuncHandle { runtime.exec(store, &mut stack)?; // Once the function returns: - let result_m = func_ty.results.len(); + // let result_m = func_ty.results.len(); // 1. Assert: m values are on the top of the stack (Ensured by validation) - assert!(stack.values.len() >= result_m); + // assert!(stack.values.len() >= result_m); // 2. Pop m values from the stack - let res = stack.values.last_n(result_m)?; + let res = stack.values.pop_results(&func_ty.results)?; // The values are returned as the results of the invocation. - Ok(res.iter().zip(func_ty.results.iter()).map(|(v, ty)| v.attach_type(*ty)).collect()) + Ok(res) } } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 0ab61f8..9c48ee0 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -100,7 +100,6 @@ pub use module::Module; pub use reference::*; pub use store::*; -mod boxvec; mod func; mod imports; mod instance; diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs deleted file mode 100644 index b2042c2..0000000 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Break to a block at the given index (relative to the current frame) -// If there is no block at the given index, return or call the parent function -// -// This is a bit hard to see from the spec, but it's vaild to use breaks to return -// from a function, so we need to check if the label stack is empty -macro_rules! break_to { - ($break_to_relative:expr, $self:expr) => { - if $self.cf.break_to($break_to_relative, &mut $self.stack.values, &mut $self.stack.blocks).is_none() { - return $self.exec_return(); - } - }; -} - -/// 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 -macro_rules! conv { - ($from:ty, $to:ty, $self:expr) => { - $self.stack.values.replace_top(|v| (<$from>::from(v) as $to).into())? - }; -} - -/// 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_trap(|v| { - let (min, max) = float_min_max!($from, $intermediate); - let a: $from = v.into(); - if unlikely(a.is_nan()) { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - if unlikely(a <= min || a >= max) { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - Ok((a as $intermediate as $to).into()) - })? - }; -} - -/// Compare two values on the stack -macro_rules! comp { - ($op:tt, $to:ty, $self:ident) => { - $self.stack.values.calculate(|a, b| { - ((<$to>::from(a) $op <$to>::from(b)) as i32).into() - })? - }; -} - -/// Compare a value on the stack to zero -macro_rules! comp_zero { - ($op:tt, $ty:ty, $self:expr) => { - $self.stack.values.replace_top(|v| ((<$ty>::from(v) $op 0) as i32).into())? - }; -} - -/// Apply an arithmetic method to two values on the stack -macro_rules! arithmetic { - ($op:ident, $to:ty, $self:expr) => { - $self.stack.values.calculate(|a, b| { - (<$to>::from(a).$op(<$to>::from(b)) as $to).into() - })? - }; - - // also allow operators such as +, - - ($op:tt, $ty:ty, $self:expr) => { - $self.stack.values.calculate(|a, b| { - ((<$ty>::from(a) $op <$ty>::from(b)) as $ty).into() - })? - }; -} - -/// Apply an arithmetic method to a single value on the stack -macro_rules! arithmetic_single { - ($op:ident, $ty:ty, $self:expr) => { - arithmetic_single!($op, $ty, $ty, $self) - }; - - ($op:ident, $from:ty, $to:ty, $self:expr) => { - $self.stack.values.replace_top(|v| (<$from>::from(v).$op() as $to).into())? - }; -} - -/// Apply an arithmetic operation to two values on the stack with error checking -macro_rules! checked_int_arithmetic { - ($op:ident, $to:ty, $self:expr) => { - $self.stack.values.calculate_trap(|a, b| { - let a: $to = a.into(); - let b: $to = b.into(); - - if unlikely(b == 0) { - return Err(Error::Trap(crate::Trap::DivisionByZero)); - } - - let result = a.$op(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow))?; - Ok((result).into()) - })? - }; -} - -pub(super) use arithmetic; -pub(super) use arithmetic_single; -pub(super) use break_to; -pub(super) use checked_conv_float; -pub(super) use checked_int_arithmetic; -pub(super) use comp; -pub(super) use comp_zero; -pub(super) use conv; -pub(super) use float_min_max; diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index be3f60a..8028e87 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -1,15 +1,4 @@ -use alloc::{format, rc::Rc, string::ToString}; -use core::ops::{BitAnd, BitOr, BitXor, ControlFlow, Neg}; -use tinywasm_types::{BlockArgs, ElementKind, Instruction, ModuleInstanceAddr, ValType, WasmFunction}; - -use super::stack::{BlockFrame, BlockType}; -use super::{InterpreterRuntime, RawWasmValue, Stack}; -use crate::runtime::CallFrame; -use crate::{cold, unlikely, Error, FuncContext, MemLoadable, MemStorable, ModuleInstance, Result, Store, Trap}; - -mod macros; -mod traits; -use {macros::*, traits::*}; +mod num_helpers; #[cfg(not(feature = "std"))] mod no_std_floats; @@ -18,6 +7,16 @@ mod no_std_floats; #[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() @@ -56,10 +55,18 @@ impl<'store, 'stack> Executor<'store, 'stack> { Nop => self.exec_noop(), Unreachable => self.exec_unreachable()?, - Drop => self.exec_drop()?, - Select(_valtype) => self.exec_select()?, - Call(v) => self.exec_call_direct(*v)?, - CallIndirect(ty, table) => self.exec_call_indirect(*ty, *table)?, + 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)?, @@ -72,18 +79,41 @@ impl<'store, 'stack> Executor<'store, 'stack> { Return => return self.exec_return(), EndBlockFrame => self.exec_end_block()?, - LocalGet(local_index) => self.exec_local_get(*local_index), - LocalSet(local_index) => self.exec_local_set(*local_index)?, - LocalTee(local_index) => self.exec_local_tee(*local_index)?, + 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)?, - GlobalSet(global_index) => self.exec_global_set(*global_index)?, - - I32Const(val) => self.exec_const(*val), - I64Const(val) => self.exec_const(*val), - F32Const(val) => self.exec_const(*val), - F64Const(val) => self.exec_const(*val), - RefFunc(func_idx) => self.exec_const(*func_idx), - RefNull(_) => self.exec_const(-1i64), + 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)?, @@ -97,15 +127,42 @@ impl<'store, 'stack> Executor<'store, 'stack> { ElemDrop(elem_index) => self.exec_elem_drop(*elem_index)?, TableCopy { from, to } => self.exec_table_copy(*from, *to)?, - I32Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - I64Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - F32Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - F64Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - I32Store8 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - I32Store16 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - I64Store8 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - I64Store16 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, - I64Store32 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset)?, + 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)?, @@ -122,145 +179,184 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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 => comp_zero!(==, i64, self), - I32Eqz => comp_zero!(==, i32, self), - - I32Eq => comp!(==, i32, self), - I64Eq => comp!(==, i64, self), - F32Eq => comp!(==, f32, self), - F64Eq => comp!(==, f64, self), - - I32Ne => comp!(!=, i32, self), - I64Ne => comp!(!=, i64, self), - F32Ne => comp!(!=, f32, self), - F64Ne => comp!(!=, f64, self), - - I32LtS => comp!(<, i32, self), - I64LtS => comp!(<, i64, self), - I32LtU => comp!(<, u32, self), - I64LtU => comp!(<, u64, self), - F32Lt => comp!(<, f32, self), - F64Lt => comp!(<, f64, self), - - I32LeS => comp!(<=, i32, self), - I64LeS => comp!(<=, i64, self), - I32LeU => comp!(<=, u32, self), - I64LeU => comp!(<=, u64, self), - F32Le => comp!(<=, f32, self), - F64Le => comp!(<=, f64, self), - - I32GeS => comp!(>=, i32, self), - I64GeS => comp!(>=, i64, self), - I32GeU => comp!(>=, u32, self), - I64GeU => comp!(>=, u64, self), - F32Ge => comp!(>=, f32, self), - F64Ge => comp!(>=, f64, self), - - I32GtS => comp!(>, i32, self), - I64GtS => comp!(>, i64, self), - I32GtU => comp!(>, u32, self), - I64GtU => comp!(>, u64, self), - F32Gt => comp!(>, f32, self), - F64Gt => comp!(>, f64, self), - - I64Add => arithmetic!(wrapping_add, i64, self), - I32Add => arithmetic!(wrapping_add, i32, self), - F32Add => arithmetic!(+, f32, self), - F64Add => arithmetic!(+, f64, self), - - I32Sub => arithmetic!(wrapping_sub, i32, self), - I64Sub => arithmetic!(wrapping_sub, i64, self), - F32Sub => arithmetic!(-, f32, self), - F64Sub => arithmetic!(-, f64, self), - - F32Div => arithmetic!(/, f32, self), - F64Div => arithmetic!(/, f64, self), - - I32Mul => arithmetic!(wrapping_mul, i32, self), - I64Mul => arithmetic!(wrapping_mul, i64, self), - F32Mul => arithmetic!(*, f32, self), - F64Mul => arithmetic!(*, f64, self), + 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 => checked_int_arithmetic!(checked_div, i32, self), - I64DivS => checked_int_arithmetic!(checked_div, i64, self), - I32DivU => checked_int_arithmetic!(checked_div, u32, self), - I64DivU => checked_int_arithmetic!(checked_div, u64, self), - - I32RemS => checked_int_arithmetic!(checked_wrapping_rem, i32, self), - I64RemS => checked_int_arithmetic!(checked_wrapping_rem, i64, self), - I32RemU => checked_int_arithmetic!(checked_wrapping_rem, u32, self), - I64RemU => checked_int_arithmetic!(checked_wrapping_rem, u64, self), - - I32And => arithmetic!(bitand, i32, self), - I64And => arithmetic!(bitand, i64, self), - I32Or => arithmetic!(bitor, i32, self), - I64Or => arithmetic!(bitor, i64, self), - I32Xor => arithmetic!(bitxor, i32, self), - I64Xor => arithmetic!(bitxor, i64, self), - I32Shl => arithmetic!(wasm_shl, i32, self), - I64Shl => arithmetic!(wasm_shl, i64, self), - I32ShrS => arithmetic!(wasm_shr, i32, self), - I64ShrS => arithmetic!(wasm_shr, i64, self), - I32ShrU => arithmetic!(wasm_shr, u32, self), - I64ShrU => arithmetic!(wasm_shr, u64, self), - I32Rotl => arithmetic!(wasm_rotl, i32, self), - I64Rotl => arithmetic!(wasm_rotl, i64, self), - I32Rotr => arithmetic!(wasm_rotr, i32, self), - I64Rotr => arithmetic!(wasm_rotr, i64, self), - - I32Clz => arithmetic_single!(leading_zeros, i32, self), - I64Clz => arithmetic_single!(leading_zeros, i64, self), - I32Ctz => arithmetic_single!(trailing_zeros, i32, self), - I64Ctz => arithmetic_single!(trailing_zeros, i64, self), - I32Popcnt => arithmetic_single!(count_ones, i32, self), - I64Popcnt => arithmetic_single!(count_ones, i64, self), - - F32ConvertI32S => conv!(i32, f32, self), - F32ConvertI64S => conv!(i64, f32, self), - F64ConvertI32S => conv!(i32, f64, self), - F64ConvertI64S => conv!(i64, f64, self), - F32ConvertI32U => conv!(u32, f32, self), - F32ConvertI64U => conv!(u64, f32, self), - F64ConvertI32U => conv!(u32, f64, self), - F64ConvertI64U => conv!(u64, f64, self), - I32Extend8S => conv!(i8, i32, self), - I32Extend16S => conv!(i16, i32, self), - I64Extend8S => conv!(i8, i64, self), - I64Extend16S => conv!(i16, i64, self), - I64Extend32S => conv!(i32, i64, self), - I64ExtendI32U => conv!(u32, i64, self), - I64ExtendI32S => conv!(i32, i64, self), - I32WrapI64 => conv!(i64, i32, self), - - F32DemoteF64 => conv!(f64, f32, self), - F64PromoteF32 => conv!(f32, f64, self), - - F32Abs => arithmetic_single!(abs, f32, self), - F64Abs => arithmetic_single!(abs, f64, self), - F32Neg => arithmetic_single!(neg, f32, self), - F64Neg => arithmetic_single!(neg, f64, self), - F32Ceil => arithmetic_single!(ceil, f32, self), - F64Ceil => arithmetic_single!(ceil, f64, self), - F32Floor => arithmetic_single!(floor, f32, self), - F64Floor => arithmetic_single!(floor, f64, self), - F32Trunc => arithmetic_single!(trunc, f32, self), - F64Trunc => arithmetic_single!(trunc, f64, self), - F32Nearest => arithmetic_single!(tw_nearest, f32, self), - F64Nearest => arithmetic_single!(tw_nearest, f64, self), - F32Sqrt => arithmetic_single!(sqrt, f32, self), - F64Sqrt => arithmetic_single!(sqrt, f64, self), - F32Min => arithmetic!(tw_minimum, f32, self), - F64Min => arithmetic!(tw_minimum, f64, self), - F32Max => arithmetic!(tw_maximum, f32, self), - F64Max => arithmetic!(tw_maximum, f64, self), - F32Copysign => arithmetic!(copysign, f32, self), - F64Copysign => arithmetic!(copysign, f64, self), + 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 => {} - // unsigned versions of these are a bit broken atm I32TruncF32S => checked_conv_float!(f32, i32, self), I32TruncF64S => checked_conv_float!(f64, i32, self), I32TruncF32U => checked_conv_float!(f32, u32, i32, self), @@ -277,31 +373,30 @@ impl<'store, 'stack> Executor<'store, 'stack> { TableGrow(table_idx) => self.exec_table_grow(*table_idx)?, TableFill(table_idx) => self.exec_table_fill(*table_idx)?, - I32TruncSatF32S => arithmetic_single!(trunc, f32, i32, self), - I32TruncSatF32U => arithmetic_single!(trunc, f32, u32, self), - I32TruncSatF64S => arithmetic_single!(trunc, f64, i32, self), - I32TruncSatF64U => arithmetic_single!(trunc, f64, u32, self), - I64TruncSatF32S => arithmetic_single!(trunc, f32, i64, self), - I64TruncSatF32U => arithmetic_single!(trunc, f32, u64, self), - I64TruncSatF64S => arithmetic_single!(trunc, f64, i64, self), - I64TruncSatF64U => arithmetic_single!(trunc, f64, u64, self), - + 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)? - } + // 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.instr_ptr += 1; + self.cf.incr_instr_ptr(); Ok(ControlFlow::Continue(())) } @@ -311,30 +406,16 @@ impl<'store, 'stack> Executor<'store, 'stack> { Err(Error::Trap(Trap::Unreachable)) } - fn exec_drop(&mut self) -> Result<()> { - self.stack.values.pop()?; - Ok(()) - } - fn exec_select(&mut self) -> Result<()> { - let cond: i32 = self.stack.values.pop()?.into(); - let val2 = self.stack.values.pop()?; - - // if cond != 0, we already have the right value on the stack - if cond == 0 { - *self.stack.values.last_mut()? = val2; - } - Ok(()) - } - fn exec_call(&mut self, wasm_func: Rc, owner: ModuleInstanceAddr) -> Result<()> { - let params = self.stack.values.pop_n(wasm_func.ty.params.len())?; - let new_call_frame = CallFrame::new(wasm_func, owner, params, self.stack.blocks.len() as u32); - self.cf.instr_ptr += 1; // skip the call instruction + 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); - self.cf.instr_ptr -= 1; - Ok(()) + Ok(ControlFlow::Continue(())) } - fn exec_call_direct(&mut self, v: u32) -> Result<()> { + 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, @@ -342,17 +423,19 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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_typed(&res); - return Ok(()); + 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<()> { + 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()?.into(); + 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 @@ -377,8 +460,9 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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_typed(&res); - return Ok(()); + self.stack.values.extend_from_wasmvalues(&res); + self.cf.incr_instr_ptr(); + return Ok(ControlFlow::Continue(())); } }; @@ -392,7 +476,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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 i32::from(self.stack.values.pop()?) != 0 { + if self.stack.values.pop::()? != 0 { self.enter_block(self.cf.instr_ptr(), end_offset, BlockType::If, args); return Ok(()); } @@ -414,67 +498,37 @@ impl<'store, 'stack> Executor<'store, 'stack> { Ok(()) } fn enter_block(&mut self, instr_ptr: usize, end_instr_offset: u32, ty: BlockType, args: BlockArgs) { - #[cfg(not(feature = "simd"))] - { - let (params, results) = match args { - BlockArgs::Empty => (0, 0), - BlockArgs::Type(_) => (0, 1), - BlockArgs::FuncType(t) => { - let ty = self.module.func_ty(t); - (ty.params.len() as u8, ty.results.len() as u8) - } - }; - - self.stack.blocks.push(BlockFrame { - instr_ptr, - end_instr_offset, - stack_ptr: self.stack.values.len() as u32 - params as u32, - results, - params, - ty, - }); + 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()) + } }; - #[cfg(feature = "simd")] - { - let (params, results, simd_params, simd_results) = match args { - BlockArgs::Empty => (0, 0, 0, 0), - BlockArgs::Type(t) => match t { - ValType::V128 => (0, 0, 0, 1), - _ => (0, 1, 0, 0), - }, - BlockArgs::FuncType(t) => { - let ty = self.module.func_ty(t); - let simd_params = ty.params.iter().filter(|t| t.is_simd()).count() as u8; - let simd_results = ty.results.iter().filter(|t| t.is_simd()).count() as u8; - let params = ty.params.len() as u8 - simd_params; - let results = ty.results.len() as u8 - simd_results; - (params, results, simd_params, simd_results) - } - }; - - self.stack.blocks.push(BlockFrame { - instr_ptr, - end_instr_offset, - stack_ptr: self.stack.values.len() as u32 - params as u32, - simd_stack_ptr: self.stack.values.simd_len() as u16 - simd_params as u16, - results, - simd_params, - simd_results, - params, - ty, - }); - }; + 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> { - break_to!(to, self); + 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> { - let val: i32 = self.stack.values.pop()?.into(); - if val != 0 { - break_to!(to, self); + 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(())) @@ -486,11 +540,15 @@ impl<'store, 'stack> Executor<'store, 'stack> { return Err(Error::Other(format!("br_table out of bounds: {} >= {}", end, self.cf.instructions().len()))); } - let idx: i32 = self.stack.values.pop()?.into(); - match self.cf.instructions()[start..end].get(idx as usize) { - None => break_to!(default, self), - Some(Instruction::BrLabel(to)) => break_to!(*to, self), + 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(); @@ -512,56 +570,46 @@ impl<'store, 'stack> Executor<'store, 'stack> { } fn exec_end_block(&mut self) -> Result<()> { let block = self.stack.blocks.pop()?; - #[cfg(feature = "simd")] - self.stack.values.truncate_keep_simd(block.simd_stack_ptr, block.simd_results as u32); - self.stack.values.truncate_keep(block.stack_ptr, block.results as u32); + self.stack.values.truncate_keep(&block.stack_ptr, &block.results); Ok(()) } - fn exec_local_get(&mut self, local_index: u32) { - self.stack.values.push(self.cf.get_local(local_index)); - } - fn exec_local_set(&mut self, local_index: u32) -> Result<()> { - self.stack.values.pop().map(|val| self.cf.set_local(local_index, val)) - } - fn exec_local_tee(&mut self, local_index: u32) -> Result<()> { - self.stack.values.last().map(|val| self.cf.set_local(local_index, *val)) - } fn exec_global_get(&mut self, global_index: u32) -> Result<()> { - self.stack.values.push(self.store.get_global_val(self.module.resolve_global_addr(global_index)?)?); + 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<()> { - self.store.set_global_val(self.module.resolve_global_addr(global_index)?, self.stack.values.pop()?) - } - - fn exec_const(&mut self, val: impl Into) { - self.stack.values.push(val.into()); + 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<()> { - self.stack.values.replace_top(|val| ((i32::from(val) == -1) as i32).into()) + 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).into()); + 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.last_mut()?; - *pages_delta = match mem.grow(i32::from(*pages_delta)) { - Some(_) => prev_size.into(), - None => (-1).into(), - }; + 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: i32 = self.stack.values.pop()?.into(); - let src: i32 = self.stack.values.pop()?.into(); - let dst: i32 = self.stack.values.pop()?.into(); + 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(); @@ -576,18 +624,18 @@ impl<'store, 'stack> Executor<'store, 'stack> { Ok(()) } fn exec_memory_fill(&mut self, addr: u32) -> Result<()> { - let size: i32 = self.stack.values.pop()?.into(); - let val: i32 = self.stack.values.pop()?.into(); - let dst: i32 = self.stack.values.pop()?.into(); + 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: i32 = self.stack.values.pop()?.into(); // n - let offset: i32 = self.stack.values.pop()?.into(); // s - let dst: i32 = self.stack.values.pop()?.into(); // d + 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)?)?; @@ -617,9 +665,9 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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()?.into(); - let src: i32 = self.stack.values.pop()?.into(); - let dst: i32 = self.stack.values.pop()?.into(); + 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(); @@ -634,14 +682,14 @@ impl<'store, 'stack> Executor<'store, 'stack> { Ok(()) } - fn exec_mem_load, const LOAD_SIZE: usize, TARGET: Into>( + 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: u64 = self.stack.values.pop()?.into(); + 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 { @@ -650,42 +698,40 @@ impl<'store, 'stack> Executor<'store, 'stack> { max: mem.borrow().max_pages(), })); }; - let val = mem.borrow().load_as::(addr)?; - self.stack.values.push(cast(val).into()); + self.stack.values.push(cast(val)); Ok(()) } - fn exec_mem_store + MemStorable, const N: usize>( + 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: T = self.stack.values.pop()?.into(); let val = val.to_mem_bytes(); - let addr: u64 = self.stack.values.pop()?.into(); + 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: u32 = self.stack.values.pop()?.into(); - let v = table.borrow().get_wasm_val(idx)?; - self.stack.values.push(v.into()); + 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()?.as_reference(); - let idx = self.stack.values.pop()?.into(); + 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(table.borrow().size().into()); + self.stack.values.push_dyn(table.borrow().size().into()); Ok(()) } fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { @@ -694,9 +740,9 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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()?.into(); // n - let offset: i32 = self.stack.values.pop()?.into(); // s - let dst: i32 = self.stack.values.pop()?.into(); // d + 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()); @@ -721,12 +767,12 @@ impl<'store, 'stack> Executor<'store, 'stack> { let table = self.store.get_table(self.module.resolve_table_addr(table_index)?)?; let sz = table.borrow().size(); - let n: i32 = self.stack.values.pop()?.into(); - let val = self.stack.values.pop()?.as_reference(); + 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(sz.into()), - Err(_) => self.stack.values.push((-1_i32).into()), + Ok(_) => self.stack.values.push_dyn(sz.into()), + Err(_) => self.stack.values.push_dyn((-1_i32).into()), } Ok(()) @@ -734,9 +780,9 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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: i32 = self.stack.values.pop()?.into(); - let val = self.stack.values.pop()?.as_reference(); - let i: i32 = self.stack.values.pop()?.into(); + 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 { @@ -753,50 +799,4 @@ impl<'store, 'stack> Executor<'store, 'stack> { table.borrow_mut().fill(self.module.func_addrs(), i as usize, n as usize, val.into())?; Ok(()) } - - // custom instructions - fn exec_i32_const_store_local(&mut self, local: u32, const_i32: i32, offset: u32, mem_addr: u8) -> Result<()> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr as u32)?)?; - let val = const_i32.to_mem_bytes(); - let addr: u64 = self.cf.get_local(local).into(); - mem.borrow_mut().store((offset as u64 + addr) as usize, val.len(), &val)?; - Ok(()) - } - fn exec_i32_store_local(&mut self, local_a: u32, local_b: u32, offset: u32, mem_addr: u8) -> Result<()> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr as u32)?)?; - let addr: u64 = self.cf.get_local(local_a).into(); - let val: i32 = self.cf.get_local(local_b).into(); - let val = val.to_mem_bytes(); - mem.borrow_mut().store((offset as u64 + addr) as usize, val.len(), &val)?; - Ok(()) - } - fn exec_i32_local_get_const_add(&mut self, local: u32, val: i32) { - let local: i32 = self.cf.get_local(local).into(); - self.stack.values.push((local + val).into()); - } - fn exec_i64_xor_const_rotl(&mut self, rotate_by: i64) -> Result<()> { - let val: i64 = self.stack.values.pop()?.into(); - let res = self.stack.values.last_mut()?; - let mask: i64 = (*res).into(); - *res = (val ^ mask).rotate_left(rotate_by as u32).into(); - Ok(()) - } - fn exec_local_get2(&mut self, a: u32, b: u32) { - self.stack.values.extend_from_slice(&[self.cf.get_local(a), self.cf.get_local(b)]); - } - fn exec_local_get3(&mut self, a: u32, b: u32, c: u32) { - self.stack.values.extend_from_slice(&[self.cf.get_local(a), self.cf.get_local(b), self.cf.get_local(c)]); - } - fn exec_local_get_set(&mut self, a: u32, b: u32) { - self.cf.set_local(b, self.cf.get_local(a)) - } - fn exec_local_tee_get(&mut self, a: u32, b: u32) -> Result<()> { - let last = self.stack.values.last()?; - self.cf.set_local(a, *last); - self.stack.values.push(match a == b { - true => *last, - false => self.cf.get_local(b), - }); - Ok(()) - } } diff --git a/crates/tinywasm/src/runtime/interpreter/traits.rs b/crates/tinywasm/src/runtime/interpreter/num_helpers.rs similarity index 66% rename from crates/tinywasm/src/runtime/interpreter/traits.rs rename to crates/tinywasm/src/runtime/interpreter/num_helpers.rs index 7aeb3b7..d0402bc 100644 --- a/crates/tinywasm/src/runtime/interpreter/traits.rs +++ b/crates/tinywasm/src/runtime/interpreter/num_helpers.rs @@ -5,6 +5,50 @@ where 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; diff --git a/crates/tinywasm/src/runtime/mod.rs b/crates/tinywasm/src/runtime/mod.rs index dc4816b..fca58cc 100644 --- a/crates/tinywasm/src/runtime/mod.rs +++ b/crates/tinywasm/src/runtime/mod.rs @@ -1,17 +1,11 @@ mod interpreter; -mod stack; +pub(crate) mod stack; -mod raw; +#[doc(hidden)] +pub use stack::values; +pub use stack::values::*; -#[cfg(all(not(feature = "nightly"), feature = "simd"))] -compile_error!("`simd` feature requires nightly"); - -#[cfg(feature = "simd")] -mod raw_simd; - -pub use raw::RawWasmValue; -pub(crate) use stack::CallFrame; -pub(crate) use stack::Stack; +pub(crate) use stack::{CallFrame, Stack}; /// The main TinyWasm runtime. /// diff --git a/crates/tinywasm/src/runtime/raw.rs b/crates/tinywasm/src/runtime/raw.rs deleted file mode 100644 index a186fd7..0000000 --- a/crates/tinywasm/src/runtime/raw.rs +++ /dev/null @@ -1,123 +0,0 @@ -use core::fmt::Debug; -use tinywasm_types::{ValType, WasmValue}; - -/// A raw wasm value. -/// -/// This is the internal representation of all wasm values -/// -/// See [`WasmValue`] for the public representation. -#[derive(Clone, Copy, Default, PartialEq, Eq)] -pub struct RawWasmValue(pub [u8; 8]); - -impl Debug for RawWasmValue { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "RawWasmValue({})", 0) - } -} - -impl RawWasmValue { - #[inline] - /// Attach a type to the raw value (does not support simd values) - pub fn attach_type(self, ty: ValType) -> WasmValue { - match ty { - ValType::I32 => WasmValue::I32(self.into()), - ValType::I64 => WasmValue::I64(self.into()), - ValType::F32 => WasmValue::F32(self.into()), - ValType::F64 => WasmValue::F64(self.into()), - ValType::V128 => panic!("RawWasmValue cannot be converted to V128"), - ValType::RefExtern => { - self.as_reference().map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)) - } - ValType::RefFunc => { - self.as_reference().map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)) - } - } - } - - #[inline] - pub(crate) fn as_reference(&self) -> Option { - match i64::from(*self) { - v if v < 0 => None, - addr => Some(addr as u32), - } - } -} - -impl From for RawWasmValue { - #[inline] - fn from(v: WasmValue) -> Self { - match v { - WasmValue::I32(i) => Self::from(i), - WasmValue::I64(i) => Self::from(i), - WasmValue::F32(i) => Self::from(i), - WasmValue::F64(i) => Self::from(i), - WasmValue::V128(_) => panic!("RawWasmValue cannot be converted to V128"), - WasmValue::RefExtern(v) => Self::from(v as i64), - WasmValue::RefFunc(v) => Self::from(v as i64), - WasmValue::RefNull(_) => Self::from(-1i64), - } - } -} - -macro_rules! impl_from_raw_wasm_value { - ($type:ty, $to_raw:expr, $from_raw:expr) => { - // Implement From<$type> for RawWasmValue - impl From<$type> for RawWasmValue { - #[inline] - fn from(value: $type) -> Self { - #[allow(clippy::redundant_closure_call)] - Self(u64::to_ne_bytes($to_raw(value))) - } - } - - // Implement From for $type - impl From for $type { - #[inline] - fn from(value: RawWasmValue) -> Self { - #[allow(clippy::redundant_closure_call)] - $from_raw(value.0) - } - } - }; -} - -// This all looks like a lot of extra steps, but the compiler will optimize it all away. -impl_from_raw_wasm_value!(i16, |x| x as u64, |x: [u8; 8]| i16::from_ne_bytes(x[0..2].try_into().unwrap())); -impl_from_raw_wasm_value!(u16, |x| x as u64, |x: [u8; 8]| u16::from_ne_bytes(x[0..2].try_into().unwrap())); -impl_from_raw_wasm_value!(i32, |x| x as u64, |x: [u8; 8]| i32::from_ne_bytes(x[0..4].try_into().unwrap())); -impl_from_raw_wasm_value!(u32, |x| x as u64, |x: [u8; 8]| u32::from_ne_bytes(x[0..4].try_into().unwrap())); -impl_from_raw_wasm_value!(i64, |x| x as u64, |x: [u8; 8]| i64::from_ne_bytes(x[0..8].try_into().unwrap())); -impl_from_raw_wasm_value!(u64, |x| x, |x: [u8; 8]| u64::from_ne_bytes(x[0..8].try_into().unwrap())); -impl_from_raw_wasm_value!(i8, |x| x as u64, |x: [u8; 8]| i8::from_ne_bytes(x[0..1].try_into().unwrap())); -impl_from_raw_wasm_value!(u8, |x| x as u64, |x: [u8; 8]| u8::from_ne_bytes(x[0..1].try_into().unwrap())); - -impl_from_raw_wasm_value!(f32, |x| f32::to_bits(x) as u64, |x: [u8; 8]| f32::from_ne_bytes( - x[0..4].try_into().unwrap() -)); -impl_from_raw_wasm_value!(f64, f64::to_bits, |x: [u8; 8]| f64::from_bits(u64::from_ne_bytes( - x[0..8].try_into().unwrap() -))); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_raw_wasm_value() { - macro_rules! test_macro { - ($( $ty:ty => $val:expr ),*) => { - $( - let raw: RawWasmValue = $val.into(); - let val: $ty = raw.into(); - assert_eq!(val, $val); - )* - }; - } - - test_macro! { - i32 => 0, i64 => 0, u8 => 0, u16 => 0, u32 => 0, u64 => 0, i8 => 0, i16 => 0, f32 => 0.0, f64 => 0.0, - i32 => i32::MIN, i64 => i64::MIN, u8 => u8::MIN, u16 => u16::MIN, u32 => u32::MIN, u64 => u64::MIN, i8 => i8::MIN, i16 => i16::MIN, f32 => f32::MIN, f64 => f64::MIN, - i32 => i32::MAX, i64 => i64::MAX, u8 => u8::MAX, u16 => u16::MAX, u32 => u32::MAX, u64 => u64::MAX, i8 => i8::MAX, i16 => i16::MAX, f32 => f32::MAX, f64 => f64::MAX - } - } -} diff --git a/crates/tinywasm/src/runtime/raw_simd.rs b/crates/tinywasm/src/runtime/raw_simd.rs deleted file mode 100644 index ba7dd62..0000000 --- a/crates/tinywasm/src/runtime/raw_simd.rs +++ /dev/null @@ -1,27 +0,0 @@ -use core::{fmt::Debug, simd::Simd}; - -/// A large raw wasm value, used for 128-bit values. -/// -/// This is the internal representation of vector values. -/// -/// See [`WasmValue`] for the public representation. -#[derive(Clone, Copy, Default, PartialEq, Eq)] -pub struct RawSimdWasmValue(Simd); // wasm has up to 16 8 bit lanes - -impl Debug for RawSimdWasmValue { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "LargeRawWasmValue({})", 0) - } -} - -impl From for RawSimdWasmValue { - fn from(value: u128) -> Self { - Self(value.to_le_bytes().into()) - } -} - -impl From for u128 { - fn from(value: RawSimdWasmValue) -> Self { - u128::from_le_bytes(value.0.into()) - } -} diff --git a/crates/tinywasm/src/runtime/stack/block_stack.rs b/crates/tinywasm/src/runtime/stack/block_stack.rs index c01c9fb..cef536a 100644 --- a/crates/tinywasm/src/runtime/stack/block_stack.rs +++ b/crates/tinywasm/src/runtime/stack/block_stack.rs @@ -1,6 +1,8 @@ use crate::{cold, unlikely, Error, Result}; use alloc::vec::Vec; +use super::values::{StackHeight, StackLocation}; + #[derive(Debug)] pub(crate) struct BlockStack(Vec); @@ -57,16 +59,9 @@ 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: u32, // position of the stack pointer when the block was entered - pub(crate) results: u8, - pub(crate) params: u8, - - #[cfg(feature = "simd")] - pub(crate) simd_stack_ptr: u16, // position of the large stack pointer when the block was entered - #[cfg(feature = "simd")] - pub(crate) simd_results: u8, - #[cfg(feature = "simd")] - pub(crate) simd_params: u8, + pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered + pub(crate) results: StackHeight, + pub(crate) params: StackHeight, pub(crate) ty: BlockType, } diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index 30957be..8e9dbe7 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -1,11 +1,11 @@ +use super::values::{InternalValue, TinyWasmValue, Value128, Value32, Value64, ValueRef}; use super::BlockType; -use crate::runtime::RawWasmValue; 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}; +use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmValue}; pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; @@ -41,7 +41,25 @@ pub(crate) struct CallFrame { pub(crate) block_ptr: u32, pub(crate) func_instance: Rc, pub(crate) module_addr: ModuleInstanceAddr, - pub(crate) locals: Box<[RawWasmValue]>, + 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 { @@ -96,7 +114,7 @@ impl CallFrame { self.instr_ptr = break_to.instr_ptr; // We also want to push the params to the stack - values.break_to_params(break_to); + values.truncate_keep(&break_to.stack_ptr, &break_to.params); // check if we're breaking to the loop if break_to_relative != 0 { @@ -109,7 +127,7 @@ impl CallFrame { 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.break_to_results(break_to); + 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; @@ -126,29 +144,48 @@ impl CallFrame { pub(crate) fn new( wasm_func_inst: Rc, owner: ModuleInstanceAddr, - params: &[RawWasmValue], + params: &[WasmValue], block_ptr: u32, ) -> Self { - let locals = { - let total_size = wasm_func_inst.locals.len() + params.len(); - let mut locals = Vec::new(); - locals.reserve_exact(total_size); - locals.extend(params); - locals.resize_with(total_size, RawWasmValue::default); - locals.into_boxed_slice() - }; - - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + Self::new_raw(wasm_func_inst, owner, params.iter().map(|v| v.into()), block_ptr) } #[inline(always)] - pub(crate) fn set_local(&mut self, local_index: LocalAddr, value: RawWasmValue) { - self.locals[local_index as usize] = value; - } + 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), + } + } - #[inline(always)] - pub(crate) fn get_local(&self, local_index: LocalAddr) -> RawWasmValue { - self.locals[local_index as usize] + 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)] diff --git a/crates/tinywasm/src/runtime/stack/mod.rs b/crates/tinywasm/src/runtime/stack/mod.rs index 06510ab..3902bd2 100644 --- a/crates/tinywasm/src/runtime/stack/mod.rs +++ b/crates/tinywasm/src/runtime/stack/mod.rs @@ -1,6 +1,7 @@ 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}; @@ -16,6 +17,6 @@ pub(crate) struct Stack { impl Stack { pub(crate) fn new(call_frame: CallFrame) -> Self { - Self { values: ValueStack::default(), blocks: BlockStack::default(), call_stack: CallStack::new(call_frame) } + 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 index 159e366..40376f1 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -1,257 +1,174 @@ -use crate::{boxvec::BoxVec, cold, runtime::RawWasmValue, unlikely, Error, Result}; use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; -use super::BlockFrame; - -pub(crate) const VALUE_STACK_SIZE: usize = 1024 * 128; - -#[cfg(feature = "simd")] -pub(crate) const SIMD_VALUE_STACK_SIZE: usize = 1024 * 32; - -#[cfg(feature = "simd")] -use crate::runtime::raw_simd::RawSimdWasmValue; +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: BoxVec, - - #[cfg(feature = "simd")] - simd_stack: BoxVec, + pub(crate) stack_32: Vec, + pub(crate) stack_64: Vec, + pub(crate) stack_128: Vec, + pub(crate) stack_ref: Vec, } -impl Default for ValueStack { - fn default() -> Self { +impl ValueStack { + pub(crate) fn new() -> Self { Self { - stack: BoxVec::with_capacity(VALUE_STACK_SIZE), - - #[cfg(feature = "simd")] - simd_stack: BoxVec::with_capacity(SIMD_VALUE_STACK_SIZE), + 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), } } -} - -impl ValueStack { - #[inline] - pub(crate) fn extend_from_typed(&mut self, values: &[WasmValue]) { - #[cfg(not(feature = "simd"))] - self.stack.extend(values.iter().map(|v| RawWasmValue::from(*v))); - #[cfg(feature = "simd")] - { - values.iter().for_each(|v| match v { - WasmValue::V128(v) => self.simd_stack.push(RawSimdWasmValue::from(*v)), - v => self.stack.push(RawWasmValue::from(*v)), - }); + 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, } } - #[inline(always)] - pub(crate) fn replace_top(&mut self, func: fn(RawWasmValue) -> RawWasmValue) -> Result<()> { - let v = self.last_mut()?; - *v = func(*v); - Ok(()) - } - - #[inline(always)] - pub(crate) fn replace_top_trap(&mut self, func: fn(RawWasmValue) -> Result) -> Result<()> { - let v = self.last_mut()?; - *v = func(*v)?; - Ok(()) - } - - #[inline(always)] - pub(crate) fn calculate(&mut self, func: fn(RawWasmValue, RawWasmValue) -> RawWasmValue) -> Result<()> { - let v2 = self.pop()?; - let v1 = self.last_mut()?; - *v1 = func(*v1, v2); - Ok(()) - } - - #[inline(always)] - pub(crate) fn calculate_trap( - &mut self, - func: fn(RawWasmValue, RawWasmValue) -> Result, - ) -> Result<()> { - let v2 = self.pop()?; - let v1 = self.last_mut()?; - *v1 = func(*v1, v2)?; - Ok(()) + pub(crate) fn peek(&self) -> Result { + T::stack_peek(self) } - #[inline(always)] - pub(crate) fn len(&self) -> usize { - self.stack.len() + pub(crate) fn pop(&mut self) -> Result { + T::stack_pop(self) } - #[cfg(feature = "simd")] - #[inline(always)] - pub(crate) fn simd_len(&self) -> usize { - self.simd_stack.len() + pub(crate) fn push(&mut self, value: T) { + T::stack_push(self, value) } - #[inline] - pub(crate) fn truncate_keep(&mut self, n: u32, end_keep: u32) { - truncate_keep(&mut self.stack, n, end_keep); + pub(crate) fn drop(&mut self) -> Result<()> { + T::stack_pop(self).map(|_| ()) } - #[cfg(feature = "simd")] - #[inline] - pub(crate) fn truncate_keep_simd(&mut self, n: u16, end_keep: u32) { - truncate_keep(&mut self.simd_stack, n as u32, end_keep); + 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(()) } - #[inline(always)] - pub(crate) fn push(&mut self, value: RawWasmValue) { - self.stack.push(value); + 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(()) } - #[inline(always)] - pub(crate) fn extend_from_slice(&mut self, values: &[RawWasmValue]) { - self.stack.extend_from_slice(values); + 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(()) } - #[inline] - pub(crate) fn last_mut(&mut self) -> Result<&mut RawWasmValue> { - match self.stack.last_mut() { - Some(v) => Ok(v), - None => { - cold(); // cold in here instead of the stack makes a huge performance difference - Err(Error::ValueStackUnderflow) - } + 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), } } - #[inline] - pub(crate) fn last(&self) -> Result<&RawWasmValue> { - match self.stack.last() { - Some(v) => Ok(v), - None => { - cold(); // cold in here instead of the stack makes a huge performance difference - Err(Error::ValueStackUnderflow) - } - } + pub(crate) fn pop_params(&mut self, val_types: &[ValType]) -> Result> { + val_types.iter().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>>() } - #[inline(always)] - pub(crate) fn pop(&mut self) -> Result { - match self.stack.pop() { - Some(v) => Ok(v), - None => { - cold(); // cold in here instead of the stack makes a huge performance difference - Err(Error::ValueStackUnderflow) - } - } + 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 + }) } - #[inline] - pub(crate) fn pop_params(&mut self, types: &[ValType]) -> Result> { - #[cfg(not(feature = "simd"))] - return Ok(self.pop_n(types.len())?.iter().zip(types.iter()).map(|(v, ty)| v.attach_type(*ty)).collect()); - - #[cfg(feature = "simd")] - { - let mut values = Vec::with_capacity(types.len()); - for ty in types { - match ty { - ValType::V128 => values.push(WasmValue::V128(self.simd_stack.pop().unwrap().into())), - ty => values.push(self.pop()?.attach_type(*ty)), - } - } - Ok(values) + 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) } - #[inline] - pub(crate) fn break_to_results(&mut self, bf: &BlockFrame) { - let end = self.stack.len() - bf.results as usize; - self.stack.drain(bf.stack_ptr as usize..end); - - #[cfg(feature = "simd")] - let end = self.simd_stack.len() - bf.simd_results as usize; - #[cfg(feature = "simd")] - self.simd_stack.drain(bf.simd_stack_ptr as usize..end); + 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); } - #[inline] - pub(crate) fn break_to_params(&mut self, bf: &BlockFrame) { - let end = self.stack.len() - bf.params as usize; - self.stack.drain(bf.stack_ptr as usize..end); - - #[cfg(feature = "simd")] - let end = self.simd_stack.len() - bf.simd_params as usize; - #[cfg(feature = "simd")] - self.simd_stack.drain(bf.simd_stack_ptr as usize..end); + 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), + } } - #[inline] - pub(crate) fn last_n(&self, n: usize) -> Result<&[RawWasmValue]> { - let len = self.stack.len(); - if unlikely(len < n) { - return Err(Error::ValueStackUnderflow); + 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), + }), } - Ok(&self.stack[len - n..len]) } - #[inline] - pub(crate) fn pop_n(&mut self, n: usize) -> Result<&[RawWasmValue]> { - match self.stack.pop_n(n) { - Some(v) => Ok(v), - None => { - cold(); - Err(Error::ValueStackUnderflow) - } + pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { + for value in values.iter() { + self.push_dyn(value.into()) } } } -#[inline(always)] -fn truncate_keep(data: &mut BoxVec, n: u32, end_keep: u32) { +fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { let total_to_keep = n + end_keep; let len = data.len() as u32; - assert!(len >= total_to_keep, "RawWasmValueotal to keep should be less than or equal to self.top"); + crate::log::error!("truncate_keep: len: {}, total_to_keep: {}, end_keep: {}", len, total_to_keep, end_keep); - if len <= total_to_keep { + if len <= n { return; // No need to truncate if the current size is already less than or equal to total_to_keep } - let items_to_remove = len - total_to_keep; - let remove_start_index = (len - items_to_remove - end_keep) as usize; - let remove_end_index = (len - end_keep) as usize; - data.drain(remove_start_index..remove_end_index); + data.drain((n as usize)..(len - end_keep) as usize); } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_value_stack() { - let mut stack = ValueStack::default(); - stack.push(1.into()); - stack.push(2.into()); - stack.push(3.into()); - assert_eq!(stack.len(), 3); - assert_eq!(i32::from(stack.pop().unwrap()), 3); - assert_eq!(stack.len(), 2); - assert_eq!(i32::from(stack.pop().unwrap()), 2); - assert_eq!(stack.len(), 1); - assert_eq!(i32::from(stack.pop().unwrap()), 1); - assert_eq!(stack.len(), 0); - } - #[test] fn test_truncate_keep() { macro_rules! test_macro { ($( $n:expr, $end_keep:expr, $expected:expr ),*) => { $( - let mut stack = ValueStack::default(); - stack.push(1.into()); - stack.push(2.into()); - stack.push(3.into()); - stack.push(4.into()); - stack.push(5.into()); - stack.truncate_keep($n, $end_keep); + let mut stack = alloc::vec![1,2,3,4,5]; + truncate_keep(&mut stack, $n, $end_keep); assert_eq!(stack.len(), $expected); )* }; diff --git a/crates/tinywasm/src/runtime/stack/values.rs b/crates/tinywasm/src/runtime/stack/values.rs new file mode 100644 index 0000000..606e13a --- /dev/null +++ b/crates/tinywasm/src/runtime/stack/values.rs @@ -0,0 +1,420 @@ +#![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/global.rs b/crates/tinywasm/src/store/global.rs index 6cc778c..a18e43d 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -3,20 +3,20 @@ use core::cell::Cell; use alloc::{format, string::ToString}; use tinywasm_types::*; -use crate::{runtime::RawWasmValue, unlikely, Error, Result}; +use crate::{runtime::TinyWasmValue, unlikely, Error, Result}; /// A WebAssembly Global Instance /// /// See #[derive(Debug)] pub(crate) struct GlobalInstance { - pub(crate) value: Cell, + pub(crate) value: Cell, pub(crate) ty: GlobalType, pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl GlobalInstance { - pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { + pub(crate) fn new(ty: GlobalType, value: TinyWasmValue, owner: ModuleInstanceAddr) -> Self { Self { ty, value: value.into(), _owner: owner } } @@ -50,7 +50,7 @@ mod tests { #[test] fn test_global_instance_get_set() { let global_type = GlobalType { ty: ValType::I32, mutable: true }; - let initial_value = RawWasmValue::from(10i32); + let initial_value = TinyWasmValue::from(10i32); let owner = 0; let mut global_instance = GlobalInstance::new(global_type, initial_value, owner); diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index f72dcab..9ebd82e 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, RawWasmValue}; +use crate::runtime::{self, InterpreterRuntime, TinyWasmValue}; use crate::{Error, Function, ModuleInstance, Result, Trap}; mod data; @@ -160,8 +160,8 @@ impl Store { } /// Get the global at the actual index in the store - #[inline(always)] - pub fn get_global_val(&self, addr: MemAddr) -> Result { + #[doc(hidden)] + pub fn get_global_val(&self, addr: MemAddr) -> Result { self.data .globals .get(addr as usize) @@ -170,8 +170,8 @@ impl Store { } /// Set the global at the actual index in the store - #[inline(always)] - pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: RawWasmValue) -> Result<()> { + #[doc(hidden)] + pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) -> Result<()> { let global = self.data.globals.get(addr as usize).ok_or_else(|| Self::not_found_error("global")); global.map(|global| global.value.set(value)) } @@ -251,13 +251,7 @@ impl Store { let addr = globals.get(*addr as usize).copied().ok_or_else(|| { Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) })?; - let val: i64 = self.data.globals[addr as usize].value.get().into(); - - // check if the global is actually a null reference - match val < 0 { - true => None, - false => Some(val as u32), - } + self.data.globals[addr as usize].value.get().unwrap_ref() } _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {:?}", item))), }; @@ -368,7 +362,7 @@ impl Store { Ok((data_addrs.into_boxed_slice(), None)) } - pub(crate) fn add_global(&mut self, ty: GlobalType, value: RawWasmValue, idx: ModuleInstanceAddr) -> Result { + pub(crate) fn add_global(&mut self, ty: GlobalType, value: TinyWasmValue, idx: ModuleInstanceAddr) -> Result { self.data.globals.push(GlobalInstance::new(ty, value, idx)); Ok(self.data.globals.len() as Addr - 1) } @@ -396,7 +390,7 @@ impl Store { use tinywasm_types::ConstInstruction::*; let val = match const_instr { I32Const(i) => *i, - GlobalGet(addr) => i32::from(self.data.globals[*addr as usize].value.get()), + GlobalGet(addr) => self.data.globals[*addr as usize].value.get().unwrap_32() as i32, _ => return Err(Error::Other("expected i32".to_string())), }; Ok(val) @@ -408,13 +402,13 @@ impl Store { const_instr: &tinywasm_types::ConstInstruction, module_global_addrs: &[Addr], module_func_addrs: &[FuncAddr], - ) -> Result { + ) -> Result { use tinywasm_types::ConstInstruction::*; let val = match const_instr { - F32Const(f) => RawWasmValue::from(*f), - F64Const(f) => RawWasmValue::from(*f), - I32Const(i) => RawWasmValue::from(*i), - I64Const(i) => RawWasmValue::from(*i), + F32Const(f) => (*f).into(), + F64Const(f) => (*f).into(), + I32Const(i) => (*i).into(), + I64Const(i) => (*i).into(), GlobalGet(addr) => { let addr = module_global_addrs.get(*addr as usize).ok_or_else(|| { Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) @@ -424,10 +418,10 @@ impl Store { self.data.globals.get(*addr as usize).expect("global not found. This should be unreachable"); global.value.get() } - RefNull(t) => RawWasmValue::from(t.default_value()), - RefFunc(idx) => RawWasmValue::from(*module_func_addrs.get(*idx as usize).ok_or_else(|| { + RefNull(t) => t.default_value().into(), + RefFunc(idx) => TinyWasmValue::ValueRef(Some(*module_func_addrs.get(*idx as usize).ok_or_else(|| { Error::Other(format!("function {} not found. This should have been caught by the validator", idx)) - })?), + })?)), }; Ok(val) } diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/2.0.csv index 3ad4978..353c879 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/2.0.csv @@ -3,4 +3,4 @@ 0.4.1,27551,335,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.5.0,27551,335,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.6.1,27572,335,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.7.0,27871,48,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":104,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":112,"failed":5},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":50,"failed":0},{"name":"table_init.wast","passed":737,"failed":43},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.7.0,27856,63,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":104,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":112,"failed":5},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":6,"failed":11},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":46,"failed":4},{"name":"table_init.wast","passed":737,"failed":43},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index d67ba83..d19326d 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -87,21 +87,20 @@ pub enum ConstInstruction { #[rustfmt::skip] pub enum Instruction { // > Custom Instructions - BrLabel(LabelAddr), - // LocalGet + I32Const + I32Add - I32LocalGetConstAdd(LocalAddr, i32), - // LocalGet + I32Const + I32Store - I32ConstStoreLocal { local: LocalAddr, const_i32: i32, offset: u32, mem_addr: u8 }, - // LocalGet + LocalGet + I32Store - I32StoreLocal { local_a: LocalAddr, local_b: LocalAddr, offset: u32, mem_addr: u8 }, - // I64Xor + I64Const + I64RotL - // Commonly used by a few crypto libraries - I64XorConstRotl(i64), - // LocalTee + LocalGet - LocalTeeGet(LocalAddr, LocalAddr), - LocalGet2(LocalAddr, LocalAddr), - LocalGet3(LocalAddr, LocalAddr, LocalAddr), - LocalGetSet(LocalAddr, LocalAddr), + // // LocalGet + I32Const + I32Add + // I32LocalGetConstAdd(LocalAddr, i32), + // // LocalGet + I32Const + I32Store + // I32ConstStoreLocal { local: LocalAddr, const_i32: i32, offset: u32, mem_addr: u8 }, + // // LocalGet + LocalGet + I32Store + // I32StoreLocal { local_a: LocalAddr, local_b: LocalAddr, offset: u32, mem_addr: u8 }, + // // I64Xor + I64Const + I64RotL + // // Commonly used by a few crypto libraries + // I64XorConstRotl(i64), + // // LocalTee + LocalGet + // LocalTeeGet(LocalAddr, LocalAddr), + // LocalGet2(LocalAddr, LocalAddr), + // LocalGet3(LocalAddr, LocalAddr, LocalAddr), + // LocalGetSet(LocalAddr, LocalAddr), // > Control Instructions // See @@ -115,22 +114,45 @@ pub enum Instruction { Br(LabelAddr), BrIf(LabelAddr), BrTable(BrTableDefault, BrTableLen), // has to be followed by multiple BrLabel instructions + BrLabel(LabelAddr), Return, Call(FuncAddr), CallIndirect(TypeAddr, TableAddr), - + // > Parametric Instructions // See - Drop, - Select(Option), + Drop32, + Drop64, + Drop128, + DropRef, + + Select32, + Select64, + Select128, + SelectRef, // > Variable Instructions // See - LocalGet(LocalAddr), - LocalSet(LocalAddr), - LocalTee(LocalAddr), + LocalGet32(LocalAddr), + LocalGet64(LocalAddr), + LocalGet128(LocalAddr), + LocalGetRef(LocalAddr), + + LocalSet32(LocalAddr), + LocalSet64(LocalAddr), + LocalSet128(LocalAddr), + LocalSetRef(LocalAddr), + + LocalTee32(LocalAddr), + LocalTee64(LocalAddr), + LocalTee128(LocalAddr), + LocalTeeRef(LocalAddr), + GlobalGet(GlobalAddr), - GlobalSet(GlobalAddr), + GlobalSet32(GlobalAddr), + GlobalSet64(GlobalAddr), + GlobalSet128(GlobalAddr), + GlobalSetRef(GlobalAddr), // > Memory Instructions I32Load { offset: u64, mem_addr: MemAddr }, diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index d447efb..785b13b 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -172,11 +172,20 @@ pub struct FuncType { pub results: Box<[ValType]>, } +#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct LocalCounts { + pub local_32: u32, + pub local_64: u32, + pub local_128: u32, + pub local_ref: u32, +} + #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct WasmFunction { pub instructions: Box<[Instruction]>, - pub locals: Box<[ValType]>, + pub locals: LocalCounts, pub ty: FuncType, }