From d9b20bcd0d6006eb365224b0509d44ef38611d46 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 2 Aug 2026 19:59:10 +0200 Subject: [PATCH 01/15] feat: add validate feature Signed-off-by: Henry --- CHANGELOG.md | 1 + README.md | 5 ++- crates/cli/Cargo.toml | 1 + crates/parser/Cargo.toml | 5 ++- crates/parser/README.md | 3 +- crates/parser/src/conversion.rs | 77 ++++++++++++++++++++++++++------- crates/parser/src/lib.rs | 70 +++++++++++++++++++----------- crates/parser/src/macros.rs | 22 +++++----- crates/parser/src/module.rs | 66 +++++++++++++++++++++++----- crates/parser/src/parallel.rs | 11 ++++- crates/parser/src/validation.rs | 15 +++++++ crates/parser/src/visit.rs | 58 ++++++++++++++++++++----- crates/tinywasm/Cargo.toml | 7 ++- crates/tinywasm/src/lib.rs | 3 +- examples/rust/Cargo.toml | 3 +- 15 files changed, 262 insertions(+), 85 deletions(-) create mode 100644 crates/parser/src/validation.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 79fb6b0..b58768f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for the WebAssembly function-references proposal - Added `WasmValue::ty` and `WasmValue::matches_type` +- Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules. ### Changed diff --git a/README.md b/README.md index 77ade90..6ded672 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,11 @@ See the [examples](./examples) directory and [documentation](https://docs.rs/tin - **`std`:** Enables `std` and parsing from files and streams. Enabled by default. - **`log`:** Enables integration with the `log` crate. Enabled by default. - **`parser`:** Enables `tinywasm-parser` and top-level parse helpers. Enabled by default. +- **`validate`:** Enables WebAssembly validation while parsing. Enabled by default and configurable through `ParserOptions`. - **`archive`:** Enables serialization and deserialization of the internal `twasm` format. Enabled by default. - **`canonicalize-nans`:** Canonicalizes NaN values. Enabled by default. - **`debug`:** Derives `Debug` for runtime types. Enabled by default. -- **`parallel-parser`:** Parallelizes function parsing and validation when `std` is enabled. Enabled by default. +- **`parallel-parser`:** Parallelizes function parsing when `std` is enabled. Enabled by default. - **`guest-debug`:** Exposes module-internal by-index inspection APIs (`*_by_index`). - **`simd-x86`:** Enables x86-specific SIMD intrinsics and uses `unsafe` internally. @@ -65,7 +66,7 @@ TinyWasm also has its own internal bytecode format, `twasm`. WebAssembly modules ## Safety -TinyWasm only uses safe Rust by default. The optional `simd-x86` feature enables x86-specific SIMD intrinsics and uses `unsafe` internally. WebAssembly input is validated by default and runs inside a sandbox: untrusted Wasm should not be able to access host memory, escape the sandbox, or cause undefined behavior in the runtime. Validation should only be disabled for trusted input. +TinyWasm only uses safe Rust by default. The optional `simd-x86` feature enables x86-specific SIMD intrinsics and uses `unsafe` internally. WebAssembly input is validated by default through the `validate` feature. Disabling validation should not let Wasm access host memory or escape the sandbox, but malformed input may panic or otherwise crash the process, so only disable it for trusted input. The internal `twasm` bytecode format is not currently validated as an untrusted input format. Malformed `twasm` may panic, but should not compromise memory safety or allow sandbox escape. Only run trusted `twasm` bytecode, or generate it through TinyWasm from Wasm input. diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 7dbd4d2..17b56e6 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -37,6 +37,7 @@ tinywasm = { workspace = true, features = [ "parallel-parser", "parser", "std", + "validate", ] } wast = { workspace = true, optional = true } wat = { workspace = true, optional = true } diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 3e71403..561ac45 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -13,10 +13,11 @@ categories.workspace = true [dependencies] log = { workspace = true, optional = true } tinywasm-types = { workspace = true } -wasmparser = { workspace = true, features = ["features", "simd", "validate"] } +wasmparser = { workspace = true, features = ["simd"] } [features] -default = ["log", "parallel", "std"] +default = ["log", "parallel", "std", "validate"] log = ["dep:log"] parallel = ["std"] std = ["tinywasm-types/std", "wasmparser/std"] +validate = ["wasmparser/features", "wasmparser/validate"] diff --git a/crates/parser/README.md b/crates/parser/README.md index 91145f1..05c2af2 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -6,7 +6,8 @@ This crate provides the parser and lowering pipeline that converts WebAssembly b - `std`: Enables the use of `std` and `std::io` for parsing from files and streams. - `log`: Enables logging of the parsing process using the `log` crate. -- `parallel`: Enables multithreaded parsing and validation when `std` is available. +- `parallel`: Enables multithreaded function parsing. Requires `std`. +- `validate`: Enables `wasmparser` validation. Enabled by default and configurable through `ParserOptions`. ## Usage diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index db8d2aa..600976a 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -1,10 +1,10 @@ -use crate::{Result, module::FunctionCode, visit::process_operators_and_validate}; +use crate::validation::{FuncValidator, FuncValidatorAllocations, ValidatorResources}; +#[cfg(feature = "validate")] +use crate::visit::process_operators_and_validate; +use crate::{Result, module::FunctionCode, visit::process_operators}; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use tinywasm_types::*; -use wasmparser::{ - CompositeInnerType, FuncValidator, FuncValidatorAllocations, OperatorsReader, OperatorsReaderAllocations, - ValidatorResources, -}; +use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations}; pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result { let kind = match element.kind { @@ -116,28 +116,48 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result, + count: u32, + ty: wasmparser::ValType, +) -> Result<()> { + let size = crate::visit::OperandSize::from(ty); + let count = + usize::try_from(count).map_err(|_| crate::ParseError::Other("local declaration count is too large".into()))?; + local_types.reserve(count); + local_types.extend(core::iter::repeat_n(size, count)); + Ok(()) +} + pub(crate) fn convert_module_code( func: wasmparser::FunctionBody<'_>, - mut validator: Option>, + validator: Option>, reader_allocs: OperatorsReaderAllocations, metadata: &crate::visit::ModuleMetadata, ty_idx: u32, ) -> Result<(FunctionCode, Option, OperatorsReaderAllocations)> { let locals_reader = func.get_locals_reader()?; - let pos = locals_reader.original_position(); + #[cfg(feature = "validate")] + let locals_position = locals_reader.original_position(); let signature = metadata.signature(ty_idx)?.clone(); let mut local_types = signature.params.clone(); - for (i, local) in locals_reader.into_iter().enumerate() { + #[cfg(feature = "validate")] + let mut validator = validator; + + #[cfg(feature = "validate")] + for (local_index, local) in locals_reader.into_iter().enumerate() { let local = local?; if let Some(validator) = validator.as_mut() { - validator.define_locals(pos + i, local.0, local.1)?; + validator.define_locals(locals_position + local_index, local.0, local.1)?; } - let size = crate::visit::OperandSize::from(local.1); - let count = usize::try_from(local.0) - .map_err(|_| crate::ParseError::Other("local declaration count is too large".into()))?; - local_types.reserve(count); - local_types.extend(core::iter::repeat_n(size, count)); + extend_local_types(&mut local_types, local.0, local.1)?; + } + + #[cfg(not(feature = "validate"))] + for local in locals_reader { + let local = local?; + extend_local_types(&mut local_types, local.0, local.1)?; } // maps a local's address to the index in the type's locals array @@ -154,8 +174,33 @@ pub(crate) fn convert_module_code( *count = count.checked_add(1).ok_or_else(|| crate::ParseError::Other(error.into()))?; } - let (body, data, validator_allocs, reader_allocs) = - process_operators_and_validate(validator, func, local_types, local_addr_map, metadata, ty_idx, reader_allocs)?; + #[cfg(feature = "validate")] + let (body, data, validator_allocs, reader_allocs) = match validator { + Some(validator) => { + let (body, data, validator_allocs, reader_allocs) = process_operators_and_validate( + validator, + func, + local_types, + local_addr_map, + metadata, + ty_idx, + reader_allocs, + )?; + (body, data, Some(validator_allocs), reader_allocs) + } + None => { + let (body, data, reader_allocs) = + process_operators(func, local_types, local_addr_map, metadata, ty_idx, reader_allocs)?; + (body, data, None, reader_allocs) + } + }; + #[cfg(not(feature = "validate"))] + let (body, data, validator_allocs, reader_allocs) = { + let _ = validator; + let (body, data, reader_allocs) = + process_operators(func, local_types, local_addr_map, metadata, ty_idx, reader_allocs)?; + (body, data, None, reader_allocs) + }; Ok(( FunctionCode { instructions: body, data, locals: local_counts, uses_local_memory: false }, validator_allocs, diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index aa5e0b0..6efc112 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -34,6 +34,7 @@ mod error; mod macros; mod module; mod optimize; +mod validation; mod visit; #[cfg(parallel_parser)] @@ -41,7 +42,10 @@ mod parallel; pub use error::*; use module::ModuleReader; -use wasmparser::{Validator, WasmFeatures}; +use validation::Validator; + +#[cfg(feature = "validate")] +use wasmparser::WasmFeatures; pub use tinywasm_types::Module; @@ -49,7 +53,10 @@ pub use tinywasm_types::Module; #[non_exhaustive] #[derive(Debug, Clone)] pub struct ParserOptions { - /// Whether to validate modules while parsing. Enabled by default. + /// Whether to validate modules while parsing. Enabled by default when the + /// `validate` feature is enabled. + /// + /// Requires the `validate` feature to have any effect. /// /// Disable this only for trusted input. Parsing without validation may produce /// a module that violates runtime assumptions. @@ -73,7 +80,7 @@ pub struct ParserOptions { impl Default for ParserOptions { fn default() -> Self { Self { - validation: true, + validation: cfg!(feature = "validate"), optimize_local_memory_allocation: true, optimize_rewrite: true, #[cfg(parallel_parser)] @@ -85,9 +92,12 @@ impl Default for ParserOptions { impl ParserOptions { /// Enable or disable WebAssembly validation. /// + /// Requires the `validate` feature to have any effect. + /// /// Disable this only for trusted input. Parsing without validation may produce /// a module that violates runtime assumptions. pub const fn with_validation(mut self, enabled: bool) -> Self { + assert!(!enabled || cfg!(feature = "validate"), "validation requires the `validate` feature"); self.validation = enabled; self } @@ -157,27 +167,35 @@ impl Parser { &self.options } - fn create_validator(_options: ParserOptions) -> Validator { - let features = WasmFeatures::CALL_INDIRECT_OVERLONG - | WasmFeatures::BULK_MEMORY_OPT - | WasmFeatures::RELAXED_SIMD - | WasmFeatures::GC_TYPES - | WasmFeatures::REFERENCE_TYPES - | WasmFeatures::MUTABLE_GLOBAL - | WasmFeatures::MULTI_VALUE - | WasmFeatures::FLOATS - | WasmFeatures::BULK_MEMORY - | WasmFeatures::SATURATING_FLOAT_TO_INT - | WasmFeatures::SIGN_EXTENSION - | WasmFeatures::EXTENDED_CONST - | WasmFeatures::FUNCTION_REFERENCES - | WasmFeatures::TAIL_CALL - | WasmFeatures::MULTI_MEMORY - | WasmFeatures::SIMD - | WasmFeatures::MEMORY64 - | WasmFeatures::CUSTOM_PAGE_SIZES - | WasmFeatures::WIDE_ARITHMETIC; - Validator::new_with_features(features) + fn validator(&self) -> Option { + #[cfg(feature = "validate")] + { + let features = WasmFeatures::CALL_INDIRECT_OVERLONG + | WasmFeatures::BULK_MEMORY_OPT + | WasmFeatures::RELAXED_SIMD + | WasmFeatures::GC_TYPES + | WasmFeatures::REFERENCE_TYPES + | WasmFeatures::MUTABLE_GLOBAL + | WasmFeatures::MULTI_VALUE + | WasmFeatures::FLOATS + | WasmFeatures::BULK_MEMORY + | WasmFeatures::SATURATING_FLOAT_TO_INT + | WasmFeatures::SIGN_EXTENSION + | WasmFeatures::EXTENDED_CONST + | WasmFeatures::FUNCTION_REFERENCES + | WasmFeatures::TAIL_CALL + | WasmFeatures::MULTI_MEMORY + | WasmFeatures::SIMD + | WasmFeatures::MEMORY64 + | WasmFeatures::CUSTOM_PAGE_SIZES + | WasmFeatures::WIDE_ARITHMETIC; + self.options.validation().then(|| Validator::new_with_features(features)) + } + #[cfg(not(feature = "validate"))] + { + assert!(!self.options.validation(), "validation requires the `validate` feature"); + None + } } #[cfg(feature = "std")] @@ -194,7 +212,7 @@ impl Parser { /// Parse a [`Module`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); - let mut validator = self.options.validation().then(|| Self::create_validator(self.options.clone())); + let mut validator = self.validator(); let mut reader = ModuleReader::default(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { @@ -228,7 +246,7 @@ impl Parser { #[cfg(feature = "std")] /// Parse a [`Module`] from a stream. Requires `std` feature. pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { - let mut validator = self.options.validation().then(|| Self::create_validator(self.options.clone())); + let mut validator = self.validator(); let mut reader = ModuleReader::default(); let mut buffer = alloc::vec::Vec::new(); let mut parser = wasmparser::Parser::new(0); diff --git a/crates/parser/src/macros.rs b/crates/parser/src/macros.rs index 723f691..4ce23bb 100644 --- a/crates/parser/src/macros.rs +++ b/crates/parser/src/macros.rs @@ -1,26 +1,24 @@ pub(crate) mod visit { + #[cfg(feature = "validate")] macro_rules! validate_then_visit { ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {$( fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { - if let Some(validator) = self.validator.as_mut() { - if let Err(e) = validator.visitor(self.position).$visit($($($arg.clone()),*)?) { - core::hint::cold_path(); - return Err(crate::ParseError::ParseError { message: e.to_string(), offset: self.position }); - } + if let Err(e) = self.validator.visitor(self.position).$visit($($($arg.clone()),*)?) { + core::hint::cold_path(); + return Err(crate::ParseError::ParseError { message: e.to_string(), offset: self.position }); } self.builder.$visit($($($arg),*)?) } )*}; } + #[cfg(feature = "validate")] macro_rules! validate_then_visit_simd { ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {$( fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { - if let Some(validator) = self.validator.as_mut() { - if let Err(e) = validator.simd_visitor(self.position).$visit($($($arg.clone()),*)?) { - core::hint::cold_path(); - return Err(crate::ParseError::ParseError { message: e.to_string(), offset: self.position }); - } + if let Err(e) = self.validator.simd_visitor(self.position).$visit($($($arg.clone()),*)?) { + core::hint::cold_path(); + return Err(crate::ParseError::ParseError { message: e.to_string(), offset: self.position }); } self.builder.$visit($($($arg),*)?) } @@ -138,7 +136,9 @@ pub(crate) mod visit { }; } - pub(crate) use {impl_visit_operator, lowering_ops, validate_then_visit, validate_then_visit_simd}; + pub(crate) use {impl_visit_operator, lowering_ops}; + #[cfg(feature = "validate")] + pub(crate) use {validate_then_visit, validate_then_visit_simd}; } pub(crate) mod optimize { diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index c2199f3..a269141 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,10 +1,13 @@ use crate::log::debug; +#[cfg(parallel_parser)] +use crate::validation::{FuncToValidate, ValidatorResources}; +use crate::validation::{FuncValidatorAllocations, Validator}; use crate::{ParseError, ParserOptions, Result, conversion::*, optimize}; use alloc::{boxed::Box, format, string::ToString, sync::Arc, vec::Vec}; use core::marker::PhantomData; use core::ops::Range; use tinywasm_types::*; -use wasmparser::{FuncValidatorAllocations, OperatorsReaderAllocations, Payload, Validator}; +use wasmparser::{OperatorsReaderAllocations, Payload}; pub(crate) struct FunctionCode { pub instructions: Vec, @@ -79,11 +82,11 @@ impl<'a> ModuleReader<'a> { self.translation_metadata.as_deref().unwrap() } - pub(crate) fn process_payload( - &mut self, - payload: Payload<'_>, - mut validator: Option<&mut Validator>, - ) -> Result<()> { + pub(crate) fn process_payload(&mut self, payload: Payload<'_>, validator: Option<&mut Validator>) -> Result<()> { + #[cfg(feature = "validate")] + let mut validator = validator; + #[cfg(not(feature = "validate"))] + let _ = validator; fn check_section(section: &str, duplicate: bool) -> Result<()> { debug!("found {section} section"); if duplicate { @@ -94,9 +97,12 @@ impl<'a> ModuleReader<'a> { match payload { Payload::Version { num, encoding, range } => { + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.version(num, encoding, &range)?; } + #[cfg(not(feature = "validate"))] + let _ = range; self.version = Some(num); if let wasmparser::Encoding::Component = encoding { return Err(ParseError::InvalidEncoding(encoding)); @@ -104,13 +110,17 @@ impl<'a> ModuleReader<'a> { } Payload::StartSection { func, range } => { check_section("start", self.start_func.is_some())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.start_section(func, &range)?; } + #[cfg(not(feature = "validate"))] + let _ = range; self.start_func = Some(func); } Payload::TypeSection(reader) => { check_section("type", !self.func_types.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.type_section(&reader)?; } @@ -118,6 +128,7 @@ impl<'a> ModuleReader<'a> { } Payload::GlobalSection(reader) => { check_section("global", !self.globals.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.global_section(&reader)?; } @@ -125,6 +136,7 @@ impl<'a> ModuleReader<'a> { } Payload::TableSection(reader) => { check_section("table", !self.tables.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.table_section(&reader)?; } @@ -149,6 +161,7 @@ impl<'a> ModuleReader<'a> { } Payload::MemorySection(reader) => { check_section("memory", !self.memory_types.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.memory_section(&reader)?; } @@ -157,6 +170,7 @@ impl<'a> ModuleReader<'a> { } Payload::ElementSection(reader) => { debug!("Found element section"); + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.element_section(&reader)?; } @@ -165,6 +179,7 @@ impl<'a> ModuleReader<'a> { } Payload::DataSection(reader) => { check_section("data", !self.data.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.data_section(&reader)?; } @@ -175,12 +190,16 @@ impl<'a> ModuleReader<'a> { if !self.data.is_empty() { return Err(ParseError::UnsupportedSection("Data count section after data section".into())); } + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.data_count_section(count, &range)?; } + #[cfg(not(feature = "validate"))] + let _ = (count, range); } Payload::FunctionSection(reader) => { check_section("function", !self.code_type_addrs.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.function_section(&reader)?; } @@ -200,6 +219,7 @@ impl<'a> ModuleReader<'a> { } Payload::ImportSection(reader) => { check_section("import", !self.imports.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.import_section(&reader)?; } @@ -217,6 +237,7 @@ impl<'a> ModuleReader<'a> { } Payload::ExportSection(reader) => { check_section("export", !self.exports.is_empty())?; + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.export_section(&reader)?; } @@ -228,9 +249,12 @@ impl<'a> ModuleReader<'a> { return Err(ParseError::DuplicateSection("End section".into())); } + #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.end(offset)?; } + #[cfg(not(feature = "validate"))] + let _ = offset; self.end_reached = true; } Payload::CustomSection(_reader) => { @@ -262,9 +286,12 @@ impl<'a> ModuleReader<'a> { self.has_code_section = true; self.code.reserve(count as usize); + #[cfg(feature = "validate")] if let Some(validator) = validator { validator.code_section_start(&range)?; } + #[cfg(not(feature = "validate"))] + let _ = (range, validator); #[cfg(parallel_parser)] { @@ -294,10 +321,16 @@ impl<'a> ModuleReader<'a> { let func_validator_allocs = self.func_validator_allocations.take(); let operators_reader_allocs = self.operators_reader_allocations.take().unwrap_or_default(); + #[cfg(feature = "validate")] let func_validator = validator .map(|validator| validator.code_section_entry(&function)) .transpose()? .map(|func| func.into_validator(func_validator_allocs.unwrap_or_default())); + #[cfg(not(feature = "validate"))] + let func_validator = { + let _ = (validator, func_validator_allocs); + None + }; let ordinal = self.code.len(); let ty_idx = *self @@ -332,7 +365,10 @@ impl<'a> ModuleReader<'a> { #[cfg(parallel_parser)] if self.pending_functions.is_some() { + #[cfg(feature = "validate")] let func_to_validate = validator.map(|validator| validator.code_section_entry(&function)).transpose()?; + #[cfg(not(feature = "validate"))] + let func_to_validate = None; return self.queue_function(crate::parallel::FunctionBodyInput::Borrowed(function), func_to_validate); } @@ -345,15 +381,23 @@ impl<'a> ModuleReader<'a> { count: u32, body_offset: usize, section_bytes: Arc<[u8]>, - mut validator: Option<&mut Validator>, + validator: Option<&mut Validator>, ) -> Result<()> { + #[cfg(feature = "validate")] + let mut validator = validator; + #[cfg(not(feature = "validate"))] + let _ = validator; let mut reader = wasmparser::BinaryReader::new(§ion_bytes, body_offset); for _ in 0..count { let body_reader = reader.read_reader()?; let body_range = body_reader.range(); - let function = wasmparser::FunctionBody::new(body_reader); - let func_to_validate = - validator.as_mut().map(|validator| validator.code_section_entry(&function)).transpose()?; + #[cfg(feature = "validate")] + let func_to_validate = { + let function = wasmparser::FunctionBody::new(body_reader); + validator.as_mut().map(|validator| validator.code_section_entry(&function)).transpose()? + }; + #[cfg(not(feature = "validate"))] + let func_to_validate = None; self.queue_function( crate::parallel::FunctionBodyInput::Owned(crate::parallel::OwnedFunctionBody { section_bytes: section_bytes.clone(), @@ -378,7 +422,7 @@ impl<'a> ModuleReader<'a> { fn queue_function( &mut self, body: crate::parallel::FunctionBodyInput<'a>, - func_to_validate: Option>, + func_to_validate: Option>, ) -> Result<()> { let ordinal = self.code.len() + self.pending_functions.as_ref().map_or(0, Vec::len); let results = *self diff --git a/crates/parser/src/parallel.rs b/crates/parser/src/parallel.rs index 88d1035..e0e3858 100644 --- a/crates/parser/src/parallel.rs +++ b/crates/parser/src/parallel.rs @@ -1,10 +1,11 @@ use crate::module::{FunctionCode, optimize_function_code}; +use crate::validation::{FuncToValidate, FuncValidatorAllocations, ValidatorResources}; use crate::{ParseError, ParserOptions, Result, conversion}; use alloc::sync::Arc; use alloc::vec::Vec; use core::ops::Range; use tinywasm_types::ValueCounts; -use wasmparser::{FuncValidatorAllocations, OperatorsReaderAllocations, ValidatorResources}; +use wasmparser::OperatorsReaderAllocations; pub(crate) enum FunctionBodyInput<'a> { Borrowed(wasmparser::FunctionBody<'a>), @@ -22,7 +23,7 @@ pub(crate) struct OwnedFunctionBody { pub(crate) struct PendingFunction<'a> { pub ordinal: usize, pub results: ValueCounts, - pub func_to_validate: Option>, + pub func_to_validate: Option>, pub ty_idx: u32, pub body: FunctionBodyInput<'a>, } @@ -64,7 +65,13 @@ fn process_function_job( validator_allocs: Option, reader_allocs: OperatorsReaderAllocations, ) -> Result<(FunctionCode, Option, OperatorsReaderAllocations)> { + #[cfg(feature = "validate")] let validator = job.func_to_validate.map(|func| func.into_validator(validator_allocs.unwrap_or_default())); + #[cfg(not(feature = "validate"))] + let validator = { + let _ = (job.func_to_validate, validator_allocs); + None + }; let (code, validator_allocs, reader_allocs) = match job.body { FunctionBodyInput::Borrowed(func) => { conversion::convert_module_code(func, validator, reader_allocs, metadata, job.ty_idx)? diff --git a/crates/parser/src/validation.rs b/crates/parser/src/validation.rs new file mode 100644 index 0000000..b2bb084 --- /dev/null +++ b/crates/parser/src/validation.rs @@ -0,0 +1,15 @@ +#[cfg(all(feature = "validate", parallel_parser))] +pub(crate) use wasmparser::FuncToValidate; +#[cfg(feature = "validate")] +pub(crate) use wasmparser::{FuncValidator, FuncValidatorAllocations, Validator, ValidatorResources}; + +#[cfg(all(not(feature = "validate"), parallel_parser))] +pub(crate) type FuncToValidate = core::marker::PhantomData; +#[cfg(not(feature = "validate"))] +pub(crate) type FuncValidator = core::marker::PhantomData; +#[cfg(not(feature = "validate"))] +pub(crate) type FuncValidatorAllocations = (); +#[cfg(not(feature = "validate"))] +pub(crate) type Validator = (); +#[cfg(not(feature = "validate"))] +pub(crate) type ValidatorResources = (); diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index d0e970b..67651f9 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -5,10 +5,10 @@ use tinywasm_types::{ FuncType, Global, Import, ImportKind, Instruction, MemoryArch, MemoryArg, MemoryType, TableDefinition, ValueCounts, WasmFunctionData, WasmType, }; -use wasmparser::{ - FuncValidator, FuncValidatorAllocations, FunctionBody, OperatorsReader, OperatorsReaderAllocations, - ValidatorResources, VisitOperator, VisitSimdOperator, -}; +use wasmparser::{FunctionBody, OperatorsReader, OperatorsReaderAllocations, VisitSimdOperator}; + +#[cfg(feature = "validate")] +use wasmparser::{FuncValidator, FuncValidatorAllocations, ValidatorResources, VisitOperator}; #[derive(Debug, Clone, Copy)] enum BlockKind { @@ -142,8 +142,9 @@ impl<'a> FunctionBuilder<'a> { } } +#[cfg(feature = "validate")] struct ValidateThenVisit<'a, 'm> { - validator: Option<&'a mut FuncValidator>, + validator: &'a mut FuncValidator, builder: &'a mut FunctionBuilder<'m>, position: usize, } @@ -220,6 +221,7 @@ impl ModuleMetadata { } } +#[cfg(feature = "validate")] impl<'a> VisitOperator<'a> for ValidateThenVisit<'_, '_> { type Output = Result<()>; @@ -230,19 +232,19 @@ impl<'a> VisitOperator<'a> for ValidateThenVisit<'_, '_> { } } +#[cfg(feature = "validate")] impl VisitSimdOperator<'_> for ValidateThenVisit<'_, '_> { wasmparser::for_each_visit_simd_operator!(validate_then_visit_simd); } -pub(crate) fn process_operators_and_validate( - mut validator: Option>, +pub(crate) fn process_operators( body: FunctionBody<'_>, local_types: Vec, local_addr_map: Vec, metadata: &ModuleMetadata, ty_idx: u32, allocs: OperatorsReaderAllocations, -) -> Result<(Vec, WasmFunctionData, Option, OperatorsReaderAllocations)> { +) -> Result<(Vec, WasmFunctionData, OperatorsReaderAllocations)> { let body_size = body.as_bytes().len(); let reader = body.get_binary_reader_for_operators()?; let mut reader = OperatorsReader::new_with_allocs(reader, allocs); @@ -252,7 +254,7 @@ pub(crate) fn process_operators_and_validate( while !reader.eof() { let position = reader.original_position(); let res = reader - .visit_operator(&mut ValidateThenVisit { validator: validator.as_mut(), builder: &mut builder, position }) + .visit_operator(&mut builder) .map_err(|e| crate::ParseError::ParseError { message: e.to_string(), offset: position }); if let Err(e) = res.flatten() { @@ -262,13 +264,47 @@ pub(crate) fn process_operators_and_validate( } reader.finish()?; + let data = WasmFunctionData { + v128_constants: builder.data.v128_constants.into_boxed_slice(), + branch_table_targets: builder.data.branch_table_targets.into_boxed_slice(), + }; + Ok((builder.instructions, data, reader.into_allocations())) +} - let validator_allocations = validator.map(FuncValidator::into_allocations); +#[cfg(feature = "validate")] +pub(crate) fn process_operators_and_validate( + mut validator: FuncValidator, + body: FunctionBody<'_>, + local_types: Vec, + local_addr_map: Vec, + metadata: &ModuleMetadata, + ty_idx: u32, + allocs: OperatorsReaderAllocations, +) -> Result<(Vec, WasmFunctionData, FuncValidatorAllocations, OperatorsReaderAllocations)> { + let body_size = body.as_bytes().len(); + let reader = body.get_binary_reader_for_operators()?; + let mut reader = OperatorsReader::new_with_allocs(reader, allocs); + let signature = metadata.signature(ty_idx)?.clone(); + let mut builder = FunctionBuilder::new(metadata, signature, local_types, local_addr_map, body_size); + + while !reader.eof() { + let position = reader.original_position(); + let res = reader + .visit_operator(&mut ValidateThenVisit { validator: &mut validator, builder: &mut builder, position }) + .map_err(|e| crate::ParseError::ParseError { message: e.to_string(), offset: position }); + + if let Err(e) = res.flatten() { + core::hint::cold_path(); + return Err(e); + } + } + + reader.finish()?; let data = WasmFunctionData { v128_constants: builder.data.v128_constants.into_boxed_slice(), branch_table_targets: builder.data.branch_table_targets.into_boxed_slice(), }; - Ok((builder.instructions, data, validator_allocations, reader.into_allocations())) + Ok((builder.instructions, data, validator.into_allocations(), reader.into_allocations())) } impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index aecebeb..d1c387b 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -13,6 +13,7 @@ categories.workspace = true [package.metadata.docs.rs] features = [ "std", + "validate", "parser", "archive", "log", @@ -160,7 +161,8 @@ default = [ "log", "parallel-parser", "parser", - "std" + "std", + "validate" ] log = ["dep:log", "tinywasm-parser?/log", "tinywasm-types/log"] @@ -169,6 +171,9 @@ std = ["tinywasm-parser?/std", "tinywasm-types/std"] # support for parsing WebAssembly parser = ["dep:tinywasm-parser"] +# validate WebAssembly while parsing +validate = ["parser", "tinywasm-parser/validate"] + # parallelize function parsing/validation across threads (requires std) parallel-parser = ["parser", "tinywasm-parser?/parallel"] diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 908f33b..1c2bcb4 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -64,10 +64,11 @@ //! - **`std`:** Enables `std` and parsing from files and streams. Enabled by default. //! - **`log`:** Enables integration with the `log` crate. Enabled by default. //! - **`parser`:** Enables `tinywasm-parser` and top-level parse helpers. Enabled by default. +//! - **`validate`:** Enables WebAssembly validation while parsing. Enabled by default and configurable through `ParserOptions`. //! - **`archive`:** Enables serialization and deserialization of the internal `twasm` format. Enabled by default. //! - **`canonicalize-nans`:** Canonicalizes NaN values. Enabled by default. //! - **`debug`:** Derives `Debug` for runtime types. Enabled by default. -//! - **`parallel-parser`:** Parallelizes function parsing and validation when `std` is enabled. Enabled by default. +//! - **`parallel-parser`:** Parallelizes function parsing when `std` is enabled. Enabled by default. //! - **`guest-debug`:** Exposes module-internal by-index inspection APIs (`*_by_index`). //! - **`simd-x86`:** Enables x86-specific SIMD intrinsics and uses `unsafe` internally. //! diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index efce27a..0ae646c 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -39,7 +39,8 @@ argon2 = { version = "0.5" } dlmalloc = { version = "0.2", features = ["global"] } tinywasm = { path = "../../crates/tinywasm", default-features = false, features = [ "archive", - "parser" + "parser", + "validate" ] } [features] From 87f1da2b7bfbbd0f499743871a04c84d6f1bf958 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 2 Aug 2026 20:00:50 +0200 Subject: [PATCH 02/15] docs: update readme Signed-off-by: Henry --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6ded672..77aaf93 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +> [!WARNING] +> This is the `next` branch and contains unreleased changes. See [`v0.10.0`](https://github.com/explodingcamera/tinywasm/tree/v0.10.0) for the latest released version. + # `tinywasm` [![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust&style=flat-square)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust&style=flat-square)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg?style=flat-square)](./LICENSE-APACHE) From d0c74255f9d6c57a243cf34037af0e8f371504b3 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 8 Aug 2026 15:43:26 +0200 Subject: [PATCH 03/15] feat: initial support for the webassembly gc proposal (#50) Signed-off-by: Henry --- CHANGELOG.md | 3 +- Cargo.lock | 32 +- Cargo.toml | 2 - README.md | 48 +- crates/cli/src/wast_runner.rs | 55 +- crates/parser/src/conversion.rs | 142 +++++- crates/parser/src/lib.rs | 1 + crates/parser/src/macros.rs | 32 ++ crates/parser/src/module.rs | 43 +- crates/parser/src/optimize.rs | 1 + crates/parser/src/visit.rs | 184 ++++++- crates/tinywasm/Cargo.toml | 2 - crates/tinywasm/src/engine.rs | 10 + crates/tinywasm/src/error.rs | 25 + crates/tinywasm/src/func.rs | 64 ++- crates/tinywasm/src/imports.rs | 35 +- crates/tinywasm/src/instance.rs | 6 +- crates/tinywasm/src/interpreter/executor.rs | 332 ++++++++++++- .../src/interpreter/stack/value_stack.rs | 28 +- crates/tinywasm/src/interpreter/values.rs | 54 +- crates/tinywasm/src/reference.rs | 50 +- crates/tinywasm/src/store/element.rs | 6 +- crates/tinywasm/src/store/function.rs | 8 + crates/tinywasm/src/store/gc/arena.rs | 263 ++++++++++ crates/tinywasm/src/store/gc/mod.rs | 92 ++++ crates/tinywasm/src/store/gc/object.rs | 218 ++++++++ crates/tinywasm/src/store/mod.rs | 470 +++++++----------- crates/tinywasm/src/store/state.rs | 385 ++++++++++++++ crates/tinywasm/src/store/table.rs | 104 +--- crates/tinywasm/src/store/types.rs | 204 ++++++++ crates/tinywasm/tests/gc_refs.rs | 120 +++++ crates/tinywasm/tests/generated/wasm-2.csv | 2 +- crates/tinywasm/tests/generated/wasm-3.csv | 2 +- .../tests/generated/wasm-extended-const.csv | 2 +- .../generated/wasm-function-references.csv | 2 +- crates/tinywasm/tests/generated/wasm-gc.csv | 1 + .../tinywasm/tests/generated/wasm-latest.csv | 1 + .../tinywasm/tests/generated/wasm-threads.csv | 1 + crates/tinywasm/tests/internal_refs.rs | 9 +- crates/types/src/archive.rs | 2 +- crates/types/src/instructions.rs | 37 ++ crates/types/src/lib.rs | 96 +--- crates/types/src/reference.rs | 109 ++-- crates/types/src/types.rs | 158 ++++++ crates/types/src/value.rs | 34 +- 45 files changed, 2770 insertions(+), 705 deletions(-) create mode 100644 crates/tinywasm/src/store/gc/arena.rs create mode 100644 crates/tinywasm/src/store/gc/mod.rs create mode 100644 crates/tinywasm/src/store/gc/object.rs create mode 100644 crates/tinywasm/src/store/state.rs create mode 100644 crates/tinywasm/src/store/types.rs create mode 100644 crates/tinywasm/tests/gc_refs.rs create mode 100644 crates/types/src/types.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b58768f..38d4dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for the WebAssembly function-references proposal +- Added support for the WebAssembly garbage-collection proposal - Added `WasmValue::ty` and `WasmValue::matches_type` - Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules. ### Changed -- Function types are now stored separately and resolved through `Function::ty(&Store)`. +- Module types now use one dense recursive type space, while function types are resolved through `Function::ty(&Store)`. ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 680b8fb..df0e601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -171,9 +171,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -181,9 +181,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -193,9 +193,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.8" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f84a88507dbd05c695f2cb5e8558e747179134005e9893882dec964190ed89" +checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19" dependencies = [ "clap", ] @@ -690,9 +690,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -982,9 +982,9 @@ dependencies = [ [[package]] name = "wasm-testsuite" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd62746cfcc437a1bdadbe75bf29df08d5a3ae67336e4ac6cea37e0194fce47" +checksum = "9d451d78ea8b19bbed461065802e8797edf21cfb6aa2e9cbeea0aede114f8fb4" dependencies = [ "include_dir", "wast", @@ -1071,18 +1071,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e57b6ea..ca80b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,10 @@ categories = ["compilers", "embedded", "no-std", "virtualization", "wasm"] [workspace.dependencies] tinywasm = { path = "crates/tinywasm", version = "0.11.0-pre.0", default-features = false } -tinywasm-cli = { path = "crates/cli", version = "0.11.0-pre.0", default-features = false } tinywasm-parser = { path = "crates/parser", version = "0.11.0-pre.0", default-features = false } tinywasm-types = { path = "crates/types", version = "0.11.0-pre.0", default-features = false } eyre = "0.6" -indexmap = "2.14" log = "0.4" owo-colors = { version = "4.3" } pretty_env_logger = "0.5" diff --git a/README.md b/README.md index 77aaf93..484dbaf 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ See the [examples](./examples) directory and [documentation](https://docs.rs/tin With default features disabled, `tinywasm` depends only on `core`, `alloc`, and `libm`[^libm], making it usable in `no_std + alloc` environments. -Use `Engine` and `engine::Config` when you need non-default runtime settings such as fuel accounting, stack sizing, memory backend selection, or trap-on-OOM behavior. +Use `Engine` and `engine::Config` when you need non-default runtime settings such as fuel accounting, stack sizing, memory backend selection, the GC collection threshold, or trap-on-OOM behavior. [^libm]: [rust-lang/rust#137578](https://github.com/rust-lang/rust/issues/137578) — tracking issue for floating-point math support in `no_std`. @@ -75,27 +75,31 @@ The internal `twasm` bytecode format is not currently validated as an untrusted ## Supported Proposals -| Proposal | Status | `tinywasm` Version | -| --------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------ | -| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | 🟢 | 0.2.0 | -| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | 🟢 | 0.2.0 | -| [**Non-trapping float-to-int Conversion**](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) | 🟢 | 0.2.0 | -| [**Sign-extension operators**](https://github.com/WebAssembly/sign-extension-ops) | 🟢 | 0.2.0 | -| [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) | 🟢 | 0.4.0 | -| [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | 🟢 | 0.7.0 | -| [**Multi-memory**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 | -| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | 0.9.0 | -| [**Extended Const**](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md) | 🟢 | 0.9.0 | -| [**Fixed-Width SIMD**](https://github.com/WebAssembly/simd/blob/main/proposals/simd/Overview.md) | 🟢 | 0.9.0 | -| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | 0.9.0 | -| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | 0.9.0 | -| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🟢 | 0.9.0 | -| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🟢 | 0.9.0 | -| [**Typed Function References**](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md) | 🚧 | `next` | -| [**Exception Handling**](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) | 🌑 | - | -| [**Garbage Collection**](https://github.com/WebAssembly/gc/blob/main/proposals/gc/Overview.md) | 🌑 | - | -| [**Stack Switching**](https://github.com/WebAssembly/stack-switching/blob/main/proposals/stack-switching/Explainer.md) | 🌑 | - | -| [**Threads**](https://github.com/WebAssembly/threads/blob/main-legacy/proposals/threads/Overview.md) | 🌑 | - | +TinyWasm targets non-JavaScript core proposals through [phase 3](https://github.com/WebAssembly/proposals). JavaScript integrations and optional embedding or tooling APIs are not included here. + +| Proposal | Status | `tinywasm` Version | +| ---------------------------------------------------------------------------------------------------------------- | ------ | ------------------ | +| [**Import/Export of Mutable Globals**](https://github.com/WebAssembly/mutable-global) | 🟢 | 0.2.0 | +| [**Multi-value**](https://github.com/WebAssembly/multi-value) | 🟢 | 0.2.0 | +| [**Non-trapping Float-to-int Conversions**](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) | 🟢 | 0.2.0 | +| [**Sign-extension Operators**](https://github.com/WebAssembly/sign-extension-ops) | 🟢 | 0.2.0 | +| [**Bulk Memory Operations**](https://github.com/WebAssembly/bulk-memory-operations) | 🟢 | 0.4.0 | +| [**Reference Types**](https://github.com/WebAssembly/reference-types) | 🟢 | 0.7.0 | +| [**Fixed-width SIMD**](https://github.com/WebAssembly/simd) | 🟢 | 0.9.0 | +| [**Tail Calls**](https://github.com/WebAssembly/tail-call) | 🟢 | 0.9.0 | +| [**Extended Constant Expressions**](https://github.com/WebAssembly/extended-const) | 🟢 | 0.9.0 | +| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory) | 🟢 | 0.8.0 | +| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd) | 🟢 | 0.9.0 | +| [**Custom Annotation Syntax**](https://github.com/WebAssembly/annotations) | 🟢 | 0.8.0 | +| [**Memory64**](https://github.com/WebAssembly/memory64) | 🟢 | 0.9.0 | +| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic) | 🟢 | 0.9.0 | +| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes) | 🟢 | 0.9.0 | +| [**Typed Function References**](https://github.com/WebAssembly/function-references) | 🟢 | `next` | +| [**Garbage Collection**](https://github.com/WebAssembly/gc) | 🟢 | `next` | +| [**Exception Handling**](https://github.com/WebAssembly/exception-handling) | 🌑 | - | +| [**Stack Switching**](https://github.com/WebAssembly/stack-switching) | 🌑 | - | +| [**Compact Import Section**](https://github.com/WebAssembly/compact-import-section) | 🌑 | - | +| [**Threads**](https://github.com/WebAssembly/threads) | 🌑 | - | **Legend**\ 🌑 -- not available\ diff --git a/crates/cli/src/wast_runner.rs b/crates/cli/src/wast_runner.rs index c773663..ad1742e 100644 --- a/crates/cli/src/wast_runner.rs +++ b/crates/cli/src/wast_runner.rs @@ -7,7 +7,10 @@ use std::time::Duration; use eyre::{Context, Result, bail, eyre}; use log::{debug, error}; -use tinywasm::types::{ExternRef, FuncRef, MemoryType, RefType, RefValue, TableType, WasmType, WasmValue}; +use tinywasm::types::{ + AbstractHeapType as TinyAbstractHeapType, AnyRef, ExternRef, FuncRef, MemoryType, RefType, RefValue, TableType, + WasmType, WasmValue, +}; use tinywasm::{ExecProgress, Global, HostFunction, Imports, Memory, Module, ModuleInstance, Store, Table}; use wast::{QuoteWat, core::AbstractHeapType}; @@ -459,7 +462,7 @@ impl WastRunner { let expected = expected_alternatives .iter() .filter_map(|alts| alts.first()) - .find(|exp| exp.matches(&module_global)); + .find(|exp| exp.matches(&module_global, &store)); if expected.is_none() { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), @@ -508,7 +511,10 @@ impl WastRunner { } if expected_alternatives.iter().any(|expected| { expected.len() == outcomes.len() - && outcomes.iter().zip(expected.iter()).all(|(outcome, exp)| exp.matches(outcome)) + && outcomes + .iter() + .zip(expected.iter()) + .all(|(outcome, exp)| exp.matches(outcome, &store)) }) { Ok(()) } else { @@ -755,16 +761,18 @@ fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { I32(i) => WasmValue::I32(i), I64(i) => WasmValue::I64(i), V128(i) => WasmValue::V128(i.to_le_bytes()), - RefExtern(v) => ExternRef::new(v).into(), + RefExtern(v) => ExternRef::try_new(v).ok_or_else(|| eyre!("external reference address is too large"))?.into(), RefNull(t) => match t { wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func } => RefValue::Null.into(), - wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern } => RefValue::Null.into(), + wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern | AbstractHeapType::Any } => { + RefValue::Null.into() + } _ => { bail!("unsupported arg type: refnull: {:?}", t); } }, - RefHost(_) => { - bail!("unsupported arg type: RefHost"); + RefHost(value) => { + RefValue::Any(AnyRef::from_host(value).ok_or_else(|| eyre!("host reference address is too large"))?).into() } }) } @@ -791,15 +799,29 @@ enum ExpectedValue { RefNull, RefFunc, RefExtern, + RefAny, + RefEq, + RefI31, + RefStruct, + RefArray, } impl ExpectedValue { - fn matches(&self, value: &WasmValue) -> bool { + fn matches(&self, value: &WasmValue, store: &Store) -> bool { match self { Self::Exact(expected) => value.eq_loose(expected), Self::RefNull => matches!(value, WasmValue::Ref(RefValue::Null)), Self::RefFunc => matches!(value, WasmValue::Ref(RefValue::Func(_))), Self::RefExtern => matches!(value, WasmValue::Ref(RefValue::Extern(_))), + Self::RefAny => matches!(value, WasmValue::Ref(RefValue::Any(_))), + Self::RefEq => { + store.value_matches_type(*value, WasmType::Ref(RefType::new_abstract(false, TinyAbstractHeapType::Eq))) + } + Self::RefI31 => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()), + Self::RefStruct => store + .value_matches_type(*value, WasmType::Ref(RefType::new_abstract(false, TinyAbstractHeapType::Struct))), + Self::RefArray => store + .value_matches_type(*value, WasmType::Ref(RefType::new_abstract(false, TinyAbstractHeapType::Array))), } } } @@ -817,7 +839,10 @@ fn wastret2tinywasmvalues(ret: wast::WastRet) -> Result> { } fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result { - use wast::core::WastRetCore::{F32, F64, I32, I64, RefExtern, RefFunc, RefNull, V128}; + use wast::core::WastRetCore::{ + F32, F64, I32, I64, RefAny, RefArray, RefEq, RefExtern, RefFunc, RefHost, RefI31, RefI31Shared, RefNull, + RefStruct, V128, + }; Ok(match ret { F32(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?), F64(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?), @@ -825,13 +850,23 @@ fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result ExpectedValue::Exact(WasmValue::I64(i)), V128(i) => ExpectedValue::Exact(WasmValue::V128(wast_v128_to_bytes(i))), RefNull(_) => ExpectedValue::RefNull, - RefExtern(Some(v)) => ExpectedValue::Exact(ExternRef::new(v).into()), + RefExtern(Some(v)) => ExpectedValue::Exact( + ExternRef::try_new(v).ok_or_else(|| eyre!("external reference address is too large"))?.into(), + ), RefExtern(None) => ExpectedValue::RefExtern, RefFunc(Some(wast::token::Index::Num(n, _))) => ExpectedValue::Exact(FuncRef::new(n).into()), RefFunc(None) => ExpectedValue::RefFunc, RefFunc(v) => { bail!("unsupported arg type: reffunc: {:?}", v); } + RefAny => ExpectedValue::RefAny, + RefEq => ExpectedValue::RefEq, + RefI31 | RefI31Shared => ExpectedValue::RefI31, + RefStruct => ExpectedValue::RefStruct, + RefArray => ExpectedValue::RefArray, + RefHost(value) => ExpectedValue::Exact( + RefValue::Any(AnyRef::from_host(value).ok_or_else(|| eyre!("host reference address is too large"))?).into(), + ), a => { bail!("unsupported arg type {:?}", a); } diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 600976a..8bc7df1 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -2,9 +2,9 @@ use crate::validation::{FuncValidator, FuncValidatorAllocations, ValidatorResour #[cfg(feature = "validate")] use crate::visit::process_operators_and_validate; use crate::{Result, module::FunctionCode, visit::process_operators}; -use alloc::{boxed::Box, format, string::ToString, vec::Vec}; +use alloc::{boxed::Box, format, vec::Vec}; use tinywasm_types::*; -use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations}; +use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations, UnpackedIndex}; pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result { let kind = match element.kind { @@ -208,25 +208,77 @@ pub(crate) fn convert_module_code( )) } -pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { - let mut types = ty.types(); - // TODO(wasm3): Preserve recursive groups and non-function composite types instead of flattening singleton funcs. - if types.len() != 1 { - return Err(crate::ParseError::UnsupportedOperator( - "Expected exactly one type in the type section".to_string(), - )); +pub(crate) fn convert_rec_group(ty: wasmparser::RecGroup, group_start: u32, types: &mut Vec) -> Result { + let group_len = u32::try_from(ty.types().len()) + .map_err(|_| crate::ParseError::Other("recursive type group is too large".into()))?; + types.reserve(group_len as usize); + for ty in ty.into_types() { + let composite = &ty.composite_type; + if composite.shared { + return Err(crate::ParseError::UnsupportedOperator("shared composite types are unsupported".into())); + } + if composite.descriptor_idx.is_some() || composite.describes_idx.is_some() { + return Err(crate::ParseError::UnsupportedOperator("descriptor types are unsupported".into())); + } + + let supertype = + ty.supertype_idx.map(|idx| convert_type_index(idx.unpack(), group_start, group_len)).transpose()?; + let composite = match &composite.inner { + CompositeInnerType::Func(ty) => { + let params = ty + .params() + .iter() + .map(|ty| convert_valtype_in_group(ty, group_start, group_len)) + .collect::>>()?; + let results = ty + .results() + .iter() + .map(|ty| convert_valtype_in_group(ty, group_start, group_len)) + .collect::>>()?; + CompositeType::Func(FuncType::new(¶ms, &results)) + } + CompositeInnerType::Struct(ty) => CompositeType::Struct(StructType { + fields: ty + .fields + .iter() + .map(|field| convert_field_type(field, group_start, group_len)) + .collect::>()?, + }), + CompositeInnerType::Array(ty) => { + CompositeType::Array(ArrayType { field: convert_field_type(&ty.0, group_start, group_len)? }) + } + CompositeInnerType::Cont(_) => { + return Err(crate::ParseError::UnsupportedOperator("continuation types are unsupported".into())); + } + }; + types.push(SubType { is_final: ty.is_final, supertype, composite }); + } + Ok(group_len) +} + +fn convert_type_index(index: UnpackedIndex, group_start: u32, group_len: u32) -> Result { + match index { + UnpackedIndex::Module(index) => Ok(index), + UnpackedIndex::RecGroup(index) if index < group_len => { + group_start.checked_add(index).ok_or_else(|| crate::ParseError::Other("type index is too large".into())) + } + UnpackedIndex::RecGroup(index) => { + Err(crate::ParseError::Other(format!("recursive group type index out of bounds: {index}"))) + } + #[cfg(feature = "validate")] + UnpackedIndex::Id(_) => { + Err(crate::ParseError::UnsupportedOperator(format!("unsupported canonical type index: {index}"))) + } } +} - let ty = types.next().unwrap(); - let CompositeInnerType::Func(ty) = &ty.composite_type.inner else { - return Err(crate::ParseError::UnsupportedOperator(format!( - "Unsupported non-function type in type section: {}", - ty.composite_type - ))); +fn convert_field_type(field: &wasmparser::FieldType, group_start: u32, group_len: u32) -> Result { + let storage = match &field.element_type { + wasmparser::StorageType::I8 => StorageType::I8, + wasmparser::StorageType::I16 => StorageType::I16, + wasmparser::StorageType::Val(ty) => StorageType::Value(convert_valtype_in_group(ty, group_start, group_len)?), }; - let params = ty.params().iter().map(convert_valtype).collect::>>()?; - let results = ty.results().iter().map(convert_valtype).collect::>>()?; - Ok(FuncType::new(¶ms, &results)) + Ok(FieldType { storage, mutable: field.mutable }) } pub(crate) fn convert_ref_type(ty: wasmparser::RefType) -> Result { @@ -234,13 +286,23 @@ pub(crate) fn convert_ref_type(ty: wasmparser::RefType) -> Result { } pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> Result { + convert_valtype_with_group(valtype, None) +} + +fn convert_valtype_in_group(valtype: &wasmparser::ValType, group_start: u32, group_len: u32) -> Result { + convert_valtype_with_group(valtype, Some((group_start, group_len))) +} + +fn convert_valtype_with_group(valtype: &wasmparser::ValType, group: Option<(u32, u32)>) -> Result { match valtype { wasmparser::ValType::I32 => Ok(WasmType::I32), wasmparser::ValType::I64 => Ok(WasmType::I64), wasmparser::ValType::F32 => Ok(WasmType::F32), wasmparser::ValType::F64 => Ok(WasmType::F64), wasmparser::ValType::V128 => Ok(WasmType::V128), - wasmparser::ValType::Ref(r) => Ok(WasmType::Ref(convert_ref_type(*r)?)), + wasmparser::ValType::Ref(r) => { + Ok(WasmType::Ref(convert_heap_type_with_group(r.heap_type(), r.is_nullable(), group)?)) + } } } @@ -265,6 +327,20 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result { ConstInstruction::Ref(RefValue::Func(FuncRef::new(function_index))) } + wasmparser::Operator::RefI31 => ConstInstruction::RefI31, + wasmparser::Operator::AnyConvertExtern => ConstInstruction::AnyConvertExtern, + wasmparser::Operator::ExternConvertAny => ConstInstruction::ExternConvertAny, + wasmparser::Operator::StructNew { struct_type_index } => ConstInstruction::StructNew(struct_type_index), + wasmparser::Operator::StructNewDefault { struct_type_index } => { + ConstInstruction::StructNewDefault(struct_type_index) + } + wasmparser::Operator::ArrayNew { array_type_index } => ConstInstruction::ArrayNew(array_type_index), + wasmparser::Operator::ArrayNewDefault { array_type_index } => { + ConstInstruction::ArrayNewDefault(array_type_index) + } + wasmparser::Operator::ArrayNewFixed { array_type_index, array_size } => { + ConstInstruction::ArrayNewFixed(array_type_index, array_size) + } wasmparser::Operator::I32Const { value } => ConstInstruction::I32Const(value), wasmparser::Operator::I64Const { value } => ConstInstruction::I64Const(value), wasmparser::Operator::F32Const { value } => ConstInstruction::F32Const(f32::from_bits(value.bits())), @@ -294,6 +370,14 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result Result { + convert_heap_type_with_group(heap, nullable, None) +} + +fn convert_heap_type_with_group( + heap: wasmparser::HeapType, + nullable: bool, + group: Option<(u32, u32)>, +) -> Result { match heap { wasmparser::HeapType::Abstract { shared: false, ty } => Ok(RefType::new_abstract( nullable, @@ -316,9 +400,23 @@ pub(crate) fn convert_heap_type(heap: wasmparser::HeapType, nullable: bool) -> R }, )), wasmparser::HeapType::Concrete(index) => { - let index = index.as_module_index().ok_or_else(|| { - crate::ParseError::UnsupportedOperator(format!("Unsupported non-module heap type index: {index:?}")) - })?; + let index = match index { + UnpackedIndex::Module(index) => index, + index @ UnpackedIndex::RecGroup(_) => { + let (group_start, group_len) = group.ok_or_else(|| { + crate::ParseError::UnsupportedOperator(format!( + "recursive-group heap type outside a type group: {index}" + )) + })?; + convert_type_index(index, group_start, group_len)? + } + #[cfg(feature = "validate")] + index @ UnpackedIndex::Id(_) => { + return Err(crate::ParseError::UnsupportedOperator(format!( + "unsupported canonical heap type index: {index}" + ))); + } + }; RefType::new_concrete(nullable, index) .ok_or_else(|| crate::ParseError::Other(format!("heap type index is too large: {index}"))) } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 6efc112..5439558 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -174,6 +174,7 @@ impl Parser { | WasmFeatures::BULK_MEMORY_OPT | WasmFeatures::RELAXED_SIMD | WasmFeatures::GC_TYPES + | WasmFeatures::GC | WasmFeatures::REFERENCE_TYPES | WasmFeatures::MUTABLE_GLOBAL | WasmFeatures::MULTI_VALUE diff --git a/crates/parser/src/macros.rs b/crates/parser/src/macros.rs index 4ce23bb..553f439 100644 --- a/crates/parser/src/macros.rs +++ b/crates/parser/src/macros.rs @@ -37,6 +37,27 @@ pub(crate) mod visit { $(lowering_ops!(@effect $inputs => $outputs $visit);)* lowering_ops!($($rest)*); }; + (unsupported $args:tt { $($visit:ident),* $(,)? } $($rest:tt)*) => { + $(lowering_ops!(@unsupported $args $visit);)* + lowering_ops!($($rest)*); + }; + (heap $nullable:literal $inputs:tt => $outputs:tt { + $($visit:ident => $instr:ident),* $(,)? + } $($rest:tt)*) => { + $( + fn $visit(&mut self, heap_type: wasmparser::HeapType) -> Self::Output { + let ty = convert_heap_type(heap_type, $nullable)?; + lowering_ops!(@emit self fixed $inputs => $outputs Instruction::$instr(ty)) + } + )* + lowering_ops!($($rest)*); + }; + + (@unsupported [$($argty:ty),*] $visit:ident) => { + fn $visit(&mut self $(, _: $argty)*) -> Self::Output { + Err(crate::ParseError::UnsupportedOperator(stringify!($visit).to_string())) + } + }; (@fixed [$($input:ident),*] => [$($output:ident),*] $visit:ident $(($($arg:ident: $ty:ty),+))? => $instr:ident @@ -64,6 +85,15 @@ pub(crate) mod visit { (@table $inputs:tt => $outputs:tt $($operator:tt)*) => { lowering_ops!(@resolved table_size $inputs => $outputs $($operator)*); }; + (@array_field [$($input:ident),*] => [$($output:ident),*] + $visit:ident($type_index:ident: $type_ty:ty $(, $arg:ident: $arg_ty:ty)*) => $instr:ident + ) => { + fn $visit(&mut self, $type_index: $type_ty $(, $arg: $arg_ty)*) -> Self::Output { + let size = ModuleMetadata::storage_size(self.metadata.array_field($type_index)?.storage); + lowering_ops!(@emit self address(size) [$($input),*] => [$($output),*] + Instruction::$instr($type_index $(, $arg)*).into()) + } + }; (@resolved $resolver:ident [$($input:ident),*] => [$($output:ident),*] $visit:ident($index:ident: $ty:ty) => $instr:ident ) => { @@ -110,6 +140,7 @@ pub(crate) mod visit { }; (@size Addr, $address:ident) => { $address }; + (@size Field, $address:ident) => { $address }; (@size $size:ident $(, $address:ident)?) => { OperandSize::$size }; } @@ -128,6 +159,7 @@ pub(crate) mod visit { (@@relaxed_simd $($rest:tt)* ) => {}; (@@tail_call $($rest:tt)* ) => {}; (@@function_references $($rest:tt)* ) => {}; + (@@gc $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { fn $visit(&mut self $($(,_: $argty)*)?) -> Self::Output { diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index a269141..237478c 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -44,11 +44,12 @@ pub(crate) struct ModuleReader<'a> { translation_metadata: Option>, has_code_section: bool, + has_type_section: bool, marker: PhantomData<&'a [u8]>, pub(crate) version: Option, pub(crate) start_func: Option, - pub(crate) func_types: Box<[FuncType]>, + pub(crate) types: TypeSection, pub(crate) code_type_addrs: Box<[u32]>, code_results: Box<[ValueCounts]>, pub(crate) exports: Arc<[Export]>, @@ -71,7 +72,7 @@ impl<'a> ModuleReader<'a> { fn translation_metadata(&mut self) -> &crate::visit::ModuleMetadata { if self.translation_metadata.is_none() { self.translation_metadata = Some(Arc::new(crate::visit::ModuleMetadata::new( - &self.func_types, + &self.types, &self.code_type_addrs, &self.imports, &self.globals, @@ -119,12 +120,25 @@ impl<'a> ModuleReader<'a> { self.start_func = Some(func); } Payload::TypeSection(reader) => { - check_section("type", !self.func_types.is_empty())?; + check_section("type", self.has_type_section)?; + self.has_type_section = true; #[cfg(feature = "validate")] if let Some(validator) = validator.as_mut() { validator.type_section(&reader)?; } - self.func_types = reader.into_iter().map(|t| convert_module_type(t?)).collect::>()?; + let mut types = Vec::with_capacity(reader.count() as usize); + let mut rec_group_lengths = Vec::with_capacity(reader.count() as usize); + for group in reader { + let group = group?; + let group_start = u32::try_from(types.len()) + .map_err(|_| ParseError::Other("type section is too large".into()))?; + let group_len = convert_rec_group(group, group_start, &mut types)?; + rec_group_lengths.push(group_len); + } + self.types = TypeSection { + types: types.into_boxed_slice(), + rec_group_lengths: rec_group_lengths.into_boxed_slice(), + }; } Payload::GlobalSection(reader) => { check_section("global", !self.globals.is_empty())?; @@ -207,10 +221,9 @@ impl<'a> ModuleReader<'a> { let mut results = Vec::with_capacity(reader.count() as usize); for ty_idx in reader { let ty_idx = ty_idx?; - let ty = self - .func_types - .get(ty_idx as usize) - .ok_or_else(|| ParseError::Other(format!("function type index out of bounds: {ty_idx}")))?; + let ty = self.types.get(ty_idx).and_then(SubType::as_func).ok_or_else(|| { + ParseError::Other(format!("function type index does not reference a function: {ty_idx}")) + })?; type_addrs.push(ty_idx); results.push(ValueCounts::from_iter(ty.results())); } @@ -227,7 +240,14 @@ impl<'a> ModuleReader<'a> { for import in reader.into_imports() { let import = convert_module_import(import?)?; match import.kind { - ImportKind::Function(_) => self.imported_func_count += 1, + ImportKind::Function(type_idx) => { + if self.types.get(type_idx).and_then(SubType::as_func).is_none() { + return Err(ParseError::Other(format!( + "function import type index does not reference a function: {type_idx}" + ))); + } + self.imported_func_count += 1; + } ImportKind::Memory(_) => self.imported_memory_count += 1, _ => {} } @@ -504,7 +524,8 @@ impl<'a> ModuleReader<'a> { .zip(self.code_type_addrs) .zip(self.code_results) .map(|((code, ty_idx), results)| { - let ty = self.func_types.get(ty_idx as usize).expect("function type was checked while parsing").clone(); + let ty = + self.types.get(ty_idx).and_then(SubType::as_func).expect("function type was checked while parsing"); let params = ValueCounts::from_iter(ty.params()); if code.uses_local_memory { local_memory_allocation = LocalMemoryAllocation::Eager; @@ -522,7 +543,7 @@ impl<'a> ModuleReader<'a> { Ok(ModuleInner { funcs, - func_types: self.func_types, + types: self.types, func_type_idxs, globals: self.globals, tables: self.tables, diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index dcb7568..5c2d7d5 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -858,6 +858,7 @@ fn instruction_target_mut(instr: &mut Instruction) -> Option<&mut u32> { | Instruction::JumpIfNonZero64(ip) | Instruction::JumpIfRefNull(ip) | Instruction::JumpIfRefNonNull(ip) + | Instruction::BrOnCast(ip, _, _) | Instruction::JumpCmpStackConst32 { target_ip: ip, .. } | Instruction::JumpCmpStackConst64 { target_ip: ip, .. } | Instruction::JumpIfLocalZero32 { target_ip: ip, .. } diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 67651f9..4437242 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -2,8 +2,8 @@ use crate::{Result, conversion::convert_heap_type, macros::visit::*}; use alloc::string::ToString; use alloc::vec::Vec; use tinywasm_types::{ - FuncType, Global, Import, ImportKind, Instruction, MemoryArch, MemoryArg, MemoryType, TableDefinition, ValueCounts, - WasmFunctionData, WasmType, + Global, Import, ImportKind, Instruction, MemoryArch, MemoryArg, MemoryType, StorageType, SubType, TableDefinition, + TypeSection, ValueCounts, WasmFunctionData, WasmType, }; use wasmparser::{FunctionBody, OperatorsReader, OperatorsReaderAllocations, VisitSimdOperator}; @@ -85,11 +85,12 @@ pub(crate) struct Signature { } pub(crate) struct ModuleMetadata { - signatures: Vec, + signatures: Vec>, functions: Vec, globals: Vec, memories: Vec, tables: Vec, + types: Vec, } #[derive(Default)] @@ -140,6 +141,16 @@ impl<'a> FunctionBuilder<'a> { lane_counts: ValueCounts::default(), } } + + fn visit_struct_get_impl( + &mut self, + type_index: u32, + field_index: u32, + instruction: fn(u32, u32) -> Instruction, + ) -> Result<()> { + let size = ModuleMetadata::storage_size(self.metadata.struct_field(type_index, field_index)?.storage); + self.emit(&[OperandSize::S32], &[size], instruction(type_index, field_index)) + } } #[cfg(feature = "validate")] @@ -151,7 +162,7 @@ struct ValidateThenVisit<'a, 'm> { impl ModuleMetadata { pub(crate) fn new( - types: &[FuncType], + types: &TypeSection, code_type_addrs: &[u32], imports: &[Import], globals: &[Global], @@ -178,19 +189,30 @@ impl ModuleMetadata { table_sizes.extend(tables.iter().map(|table| OperandSize::from(table.ty.arch()))); let signatures = types + .types .iter() - .map(|ty| Signature { - params: ty.params().iter().map(OperandSize::from).collect(), - results: ty.results().iter().map(OperandSize::from).collect(), + .map(|ty| { + ty.as_func().map(|ty| Signature { + params: ty.params().iter().map(OperandSize::from).collect(), + results: ty.results().iter().map(OperandSize::from).collect(), + }) }) .collect(); - Self { signatures, functions, globals: global_sizes, memories: memory_sizes, tables: table_sizes } + Self { + signatures, + functions, + globals: global_sizes, + memories: memory_sizes, + tables: table_sizes, + types: types.types.to_vec(), + } } pub(crate) fn signature(&self, idx: u32) -> Result<&Signature> { self.signatures .get(idx as usize) - .ok_or_else(|| crate::ParseError::Other(alloc::format!("type index out of bounds: {idx}"))) + .and_then(Option::as_ref) + .ok_or_else(|| crate::ParseError::Other(alloc::format!("type index is not a function type: {idx}"))) } fn function_signature(&self, idx: u32) -> Result<&Signature> { @@ -219,6 +241,36 @@ impl ModuleMetadata { .copied() .ok_or_else(|| crate::ParseError::Other(alloc::format!("{entity} index out of bounds: {idx}"))) } + + fn storage_size(storage: StorageType) -> OperandSize { + match storage { + StorageType::I8 | StorageType::I16 => OperandSize::S32, + StorageType::Value(ref ty) => OperandSize::from(ty), + } + } + + fn struct_fields(&self, idx: u32) -> Result<&[tinywasm_types::FieldType]> { + self.types + .get(idx as usize) + .and_then(SubType::as_struct) + .map(|ty| ty.fields.as_ref()) + .ok_or_else(|| crate::ParseError::Other(alloc::format!("type index is not a struct type: {idx}"))) + } + + fn struct_field(&self, type_index: u32, field_index: u32) -> Result { + self.struct_fields(type_index)? + .get(field_index as usize) + .copied() + .ok_or_else(|| crate::ParseError::Other("struct field index out of bounds".into())) + } + + fn array_field(&self, idx: u32) -> Result { + self.types + .get(idx as usize) + .and_then(SubType::as_array) + .map(|ty| ty.field) + .ok_or_else(|| crate::ParseError::Other(alloc::format!("type index is not an array type: {idx}"))) + } } #[cfg(feature = "validate")] @@ -339,8 +391,17 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fixed [] => [] { visit_data_drop(segment: u32) => DataDrop, visit_elem_drop(segment: u32) => ElemDrop } fixed [] => [S32] { visit_i32_const(value: i32) => Const32, visit_ref_func(function: u32) => RefFunc } fixed [] => [S64] { visit_i64_const(value: i64) => Const64 } + heap false [] => [S32] { visit_ref_null => RefNull } + heap false [S32] => [S32] { + visit_ref_test_non_null => RefTest, visit_ref_cast_non_null => RefCast, + } + heap true [S32] => [S32] { + visit_ref_test_nullable => RefTest, visit_ref_cast_nullable => RefCast, + } fixed [S32] => [S32] { - visit_i32_eqz => I32Eqz, visit_ref_is_null => RefIsNull, visit_i32_clz => I32Clz, + visit_ref_is_null => RefIsNull, visit_ref_as_non_null => RefAsNonNull, visit_ref_i31 => RefI31, + visit_i31_get_s => I31GetS, visit_i31_get_u => I31GetU, + visit_i32_eqz => I32Eqz, visit_i32_clz => I32Clz, visit_i32_ctz => I32Ctz, visit_i32_popcnt => I32Popcnt, visit_i32_extend8_s => I32Extend8S, visit_i32_extend16_s => I32Extend16S, visit_i32_trunc_f32_s => I32TruncF32S, visit_i32_trunc_f32_u => I32TruncF32U, visit_f32_convert_i32_s => F32ConvertI32S, @@ -349,6 +410,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { visit_f32_ceil => F32Ceil, visit_f32_floor => F32Floor, visit_f32_trunc => F32Trunc, visit_f32_nearest => F32Nearest, visit_f32_sqrt => F32Sqrt, } + effect [S32] => [S32] { visit_any_convert_extern, visit_extern_convert_any } fixed [S64] => [S64] { visit_i64_clz => I64Clz, visit_i64_ctz => I64Ctz, visit_i64_popcnt => I64Popcnt, visit_i64_extend8_s => I64Extend8S, visit_i64_extend16_s => I64Extend16S, @@ -373,7 +435,8 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { visit_i64_trunc_sat_f32_u => I64TruncSatF32U, } fixed [S32, S32] => [S32] { - visit_i32_eq => I32Eq, visit_i32_ne => I32Ne, visit_i32_lt_s => I32LtS, visit_i32_lt_u => I32LtU, + visit_ref_eq => RefEq, visit_i32_eq => I32Eq, visit_i32_ne => I32Ne, + visit_i32_lt_s => I32LtS, visit_i32_lt_u => I32LtU, visit_i32_gt_s => I32GtS, visit_i32_gt_u => I32GtU, visit_i32_le_s => I32LeS, visit_i32_le_u => I32LeU, visit_i32_ge_s => I32GeS, visit_i32_ge_u => I32GeU, visit_f32_eq => F32Eq, visit_f32_ne => F32Ne, visit_f32_lt => F32Lt, visit_f32_gt => F32Gt, @@ -420,6 +483,65 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { table [S32, Addr] => [Addr] { visit_table_grow(table: u32) => TableGrow } table [Addr, S32, Addr] => [] { visit_table_fill(table: u32) => TableFill } table [Addr, S32, S32] => [] { visit_table_init(elem_index: u32, table: u32) => TableInit } + fixed [] => [S32] { visit_struct_new_default(type_index: u32) => StructNewDefault } + fixed [S32] => [S32] { + visit_array_new_default(type_index: u32) => ArrayNewDefault, visit_array_len => ArrayLen, + } + fixed [S32, S32] => [S32] { + visit_array_new_data(type_index: u32, data_index: u32) => ArrayNewData, + visit_array_new_elem(type_index: u32, elem_index: u32) => ArrayNewElem, + } + fixed [S32, S32, S32, S32] => [] { + visit_array_init_data(type_index: u32, data_index: u32) => ArrayInitData, + visit_array_init_elem(type_index: u32, elem_index: u32) => ArrayInitElem, + } + fixed [S32, S32, S32, S32, S32] => [] { + visit_array_copy(type_index_dst: u32, type_index_src: u32) => ArrayCopy, + } + array_field [Field, S32] => [S32] { visit_array_new(type_index: u32) => ArrayNew } + array_field [S32, S32] => [Field] { + visit_array_get(type_index: u32) => ArrayGet, visit_array_get_s(type_index: u32) => ArrayGetS, + visit_array_get_u(type_index: u32) => ArrayGetU, + } + array_field [S32, S32, Field] => [] { visit_array_set(type_index: u32) => ArraySet } + array_field [S32, S32, Field, S32] => [] { visit_array_fill(type_index: u32) => ArrayFill } + } + + fn visit_struct_new(&mut self, type_index: u32) -> Self::Output { + let inputs = self + .metadata + .struct_fields(type_index)? + .iter() + .map(|field| ModuleMetadata::storage_size(field.storage)) + .collect::>(); + self.emit(&inputs, &[OperandSize::S32], Instruction::StructNew(type_index)) + } + + fn visit_struct_get(&mut self, type_index: u32, field_index: u32) -> Self::Output { + self.visit_struct_get_impl(type_index, field_index, Instruction::StructGet) + } + + fn visit_struct_get_s(&mut self, type_index: u32, field_index: u32) -> Self::Output { + self.visit_struct_get_impl(type_index, field_index, Instruction::StructGetS) + } + + fn visit_struct_get_u(&mut self, type_index: u32, field_index: u32) -> Self::Output { + self.visit_struct_get_impl(type_index, field_index, Instruction::StructGetU) + } + + fn visit_struct_set(&mut self, type_index: u32, field_index: u32) -> Self::Output { + let size = ModuleMetadata::storage_size(self.metadata.struct_field(type_index, field_index)?.storage); + self.emit(&[OperandSize::S32, size], &[], Instruction::StructSet(type_index, field_index)) + } + + fn visit_array_new_fixed(&mut self, type_index: u32, array_size: u32) -> Self::Output { + let size = ModuleMetadata::storage_size(self.metadata.array_field(type_index)?.storage); + for _ in 0..array_size { + self.pop_expect(size)?; + } + self.push_sizes(&[OperandSize::S32])?; + self.instructions.push(Instruction::ArrayNewFixed(type_index, array_size)); + Ok(()) } fn visit_call(&mut self, function_index: u32) -> Self::Output { @@ -718,14 +840,22 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { self.emit(&[dst, src, len], &[], Instruction::MemoryCopy { dst_mem, src_mem }) } - // Reference Types - fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output { - let instruction = Instruction::RefNull(convert_heap_type(ty, false)?); - self.emit(&[], &[OperandSize::S32], instruction) + fn visit_br_on_cast( + &mut self, + relative_depth: u32, + _from_ref_type: wasmparser::RefType, + to_ref_type: wasmparser::RefType, + ) -> Self::Output { + self.emit_cast_branch(relative_depth, to_ref_type, false) } - fn visit_ref_as_non_null(&mut self) -> Self::Output { - self.emit(&[OperandSize::S32], &[OperandSize::S32], Instruction::RefAsNonNull) + fn visit_br_on_cast_fail( + &mut self, + relative_depth: u32, + _from_ref_type: wasmparser::RefType, + to_ref_type: wasmparser::RefType, + ) -> Self::Output { + self.emit_cast_branch(relative_depth, to_ref_type, true) } fn visit_br_on_null(&mut self, relative_depth: u32) -> Self::Output { @@ -971,6 +1101,23 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuilder<'_> { } impl FunctionBuilder<'_> { + fn emit_cast_branch( + &mut self, + relative_depth: u32, + target: wasmparser::RefType, + branch_on_fail: bool, + ) -> Result<()> { + self.pop_expect(OperandSize::S32)?; + let target = convert_heap_type(target.heap_type(), target.is_nullable())?; + let conditional_ip = self.instructions.len(); + self.instructions.push(Instruction::BrOnCast(0, target, branch_on_fail)); + self.push_sizes(&[OperandSize::S32])?; + self.emit_dropkeep_to_label(relative_depth)?; + self.emit_branch_jump_or_return(relative_depth)?; + self.patch_jump(conditional_ip, self.instructions.len()); + Ok(()) + } + fn is_unreachable(&self) -> bool { self.control_stack.last().is_none_or(|frame| frame.unreachable) } @@ -1108,7 +1255,8 @@ impl FunctionBuilder<'_> { | Instruction::JumpIfZero32(ip) | Instruction::JumpIfNonZero32(ip) | Instruction::JumpIfRefNull(ip) - | Instruction::JumpIfRefNonNull(ip) => { + | Instruction::JumpIfRefNonNull(ip) + | Instruction::BrOnCast(ip, _, _) => { *ip = target as u32; } _ => {} diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index d1c387b..6cf9b17 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -70,7 +70,6 @@ name = "test-wasm-custom-page-sizes" [[test]] harness = false name = "test-wasm-function-references" -test = false [[test]] harness = false @@ -79,7 +78,6 @@ name = "test-wasm-custom" [[test]] harness = false name = "test-wasm-gc" -test = false [[test]] harness = false diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index 521eb80..64ef6be 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -123,6 +123,9 @@ pub struct Config { /// Whether memory and stack allocation failures should trap instead of degrading into normal operation failure modes. /// Defaults to `false`. pub trap_on_oom: bool, + /// Initial number of GC heap bytes that triggers collection. + /// Defaults to 1 MiB. + pub gc_collection_threshold: usize, } impl Config { @@ -181,6 +184,12 @@ impl Config { self } + /// Set the initial GC heap collection threshold in bytes. + pub fn with_gc_collection_threshold(mut self, threshold: usize) -> Self { + self.gc_collection_threshold = threshold; + self + } + /// Get the current fuel policy pub fn fuel_policy(&self) -> FuelPolicy { self.fuel_policy @@ -206,6 +215,7 @@ impl Default for Config { fuel_policy: FuelPolicy::default(), memory_backend: MemoryBackend::default(), trap_on_oom: false, + gc_collection_threshold: 1024 * 1024, } } } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index b5885ca..ebd0470 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -133,6 +133,9 @@ pub enum Trap { max: usize, }, + /// An out-of-bounds GC array access occurred. + ArrayOutOfBounds, + /// A division by zero occurred DivisionByZero, @@ -169,9 +172,21 @@ pub enum Trap { /// A null reference was used where a non-null reference was required. NullReference, + /// A null struct reference was dereferenced. + NullStructReference, + + /// A null array reference was dereferenced. + NullArrayReference, + /// A null function reference was called. NullFunctionReference, + /// A null i31 reference was unwrapped. + NullI31Reference, + + /// A reference cast failed. + CastFailure, + /// Indirect call type mismatch IndirectCallTypeMismatch { /// The expected type @@ -191,6 +206,7 @@ impl Trap { Self::Unreachable => "unreachable", Self::MemoryOutOfBounds { .. } => "out of bounds memory access", Self::TableOutOfBounds { .. } => "out of bounds table access", + Self::ArrayOutOfBounds => "out of bounds array access", Self::DivisionByZero => "integer divide by zero", Self::InvalidConversionToInt => "invalid conversion to integer", Self::IntegerOverflow => "integer overflow", @@ -200,7 +216,11 @@ impl Trap { Self::UndefinedElement { .. } => "undefined element", Self::UninitializedElement { .. } => "uninitialized element", Self::NullReference => "null reference", + Self::NullStructReference => "null structure reference", + Self::NullArrayReference => "null array reference", Self::NullFunctionReference => "null function reference", + Self::NullI31Reference => "null i31 reference", + Self::CastFailure => "cast failure", Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch", Self::HostFunction(_) => "host function trap", Self::InvalidStore => "invalid store", @@ -291,6 +311,7 @@ impl Display for Trap { Self::TableOutOfBounds { offset, len, max } => { write!(f, "out of bounds table access: offset={offset}, len={len}, max={max}") } + Self::ArrayOutOfBounds => write!(f, "out of bounds array access"), Self::DivisionByZero => write!(f, "integer divide by zero"), Self::InvalidConversionToInt => write!(f, "invalid conversion to integer"), Self::IntegerOverflow => write!(f, "integer overflow"), @@ -302,7 +323,11 @@ impl Display for Trap { write!(f, "uninitialized element: index={index}") } Self::NullReference => write!(f, "null reference"), + Self::NullStructReference => write!(f, "null structure reference"), + Self::NullArrayReference => write!(f, "null array reference"), Self::NullFunctionReference => write!(f, "null function reference"), + Self::NullI31Reference => write!(f, "null i31 reference"), + Self::CastFailure => write!(f, "cast failure"), Self::InvalidStore => write!(f, "invalid store"), #[cfg(feature = "debug")] Self::IndirectCallTypeMismatch { expected, actual } => { diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index b6b7b14..732cafc 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -44,16 +44,25 @@ impl Function { // Execute until completion and then collect result values from the stack. InterpreterRuntime::exec(store, callframe, 0)?; - collect_call_results(&mut store.value_stack, store.state.get_type(func_instance.type_addr)) + collect_call_results( + &mut store.value_stack, + &store.state, + store.state.get_canonical_func_type(func_instance.type_addr), + ) } self.item.validate_store(store)?; validate_call_params(&store.state, store.state.get_func_type(self.addr()), params)?; + let results_may_gc = store.state.get_func(self.addr()).gc.results; store.enter_execution()?; let result = call_inner(self, store, params); store.exit_execution(); + if results_may_gc && let Ok(values) = &result { + store.state.pin_host_values(values); + } + result } @@ -97,12 +106,17 @@ impl Function { self.item.validate_store(store)?; validate_call_params(&store.state, store.state.get_func_type(self.addr()), params)?; + let results_may_gc = store.state.get_func(self.addr()).gc.results; store.enter_execution()?; let result = call_resumable_inner(self, store, params); store.exit_execution(); - Ok(FuncExecution { store, state: result? }) + let state = result?; + if results_may_gc && let FuncExecutionState::Completed { result: Some(values) } = &state { + store.state.pin_host_values(values); + } + Ok(FuncExecution { store, state }) } } @@ -188,6 +202,7 @@ impl HostFunction { let type_addr = store.register_host_type(ty); let addr = store.add_func(FunctionInstance { type_addr, + gc: store.state.func_gc_metadata(type_addr), kind: crate::store::FunctionKind::Host(Rc::new(Self { func: Box::new(func) })), }); Function { item: crate::StoreItem::new(store.id(), addr), module_id: 0 } @@ -231,6 +246,7 @@ impl HostFunction { let type_addr = store.register_host_type(&ty); let addr = store.add_func(FunctionInstance { type_addr, + gc: store.state.func_gc_metadata(type_addr), kind: crate::store::FunctionKind::Host(Rc::new(Self { func: Box::new(inner_func) })), }); Function { item: crate::StoreItem::new(store.id(), addr), module_id: 0 } @@ -328,7 +344,7 @@ impl FuncContext<'_> { validate_call_params(&self.store.state, self.store.state.get_func_type(func.addr()), args)?; let func_instance = self.store.state.get_func(func.addr()).clone(); - match func_instance.kind { + let result = match func_instance.kind { crate::store::FunctionKind::Host(host_func) => { let result = host_func.call(FuncContext { store: &mut *self.store, module_id: func.module_id }, args)?; @@ -354,9 +370,19 @@ impl FuncContext<'_> { self.store.value_stack.truncate_to_base(value_stack_base); })?; - collect_call_results(&mut self.store.value_stack, self.store.state.get_type(func_instance.type_addr)) + collect_call_results( + &mut self.store.value_stack, + &self.store.state, + self.store.state.get_canonical_func_type(func_instance.type_addr), + ) } + }; + if func_instance.gc.results + && let Ok(values) = &result + { + self.store.state.pin_host_values(values); } + result } /// Call a typed function from within the current host-function invocation. @@ -441,12 +467,19 @@ impl<'store> FuncExecution<'store> { match result? { crate::interpreter::ExecState::Completed => { - let result_ty = self.store.state.get_func(root_func_addr).type_addr; + let func = self.store.state.get_func(root_func_addr); + let result_ty = func.type_addr; + let results_may_gc = func.gc.results; self.state = FuncExecutionState::Completed { result: None }; - Ok(ExecProgress::Completed(collect_call_results( + let values = collect_call_results( &mut self.store.value_stack, - self.store.state.get_type(result_ty), - )?)) + &self.store.state, + self.store.state.get_canonical_func_type(result_ty), + )?; + if results_may_gc { + self.store.state.pin_host_values(&values); + } + Ok(ExecProgress::Completed(values)) } crate::interpreter::ExecState::Suspended(callframe) => { let FuncExecutionState::Running { exec_state, .. } = &mut self.state else { @@ -517,7 +550,7 @@ pub(crate) fn validate_host_results( type_addr: TypeAddr, result: Vec, ) -> Result> { - let expected = store.state.get_type(type_addr); + let expected = store.state.get_canonical_func_type(type_addr); if result.len() == expected.results().len() && result.iter().zip(expected.results()).all(|(&value, &ty)| store.state.value_matches_type(value, ty)) { @@ -527,9 +560,18 @@ pub(crate) fn validate_host_results( Err(Error::InvalidHostFnReturn { expected: Box::new(expected.clone()), actual: result }) } -fn collect_call_results(value_stack: &mut ValueStack, func_ty: &FuncType) -> Result> { +fn collect_call_results( + value_stack: &mut ValueStack, + state: &crate::store::State, + func_ty: &FuncType, +) -> Result> { debug_assert!(value_stack.len() >= func_ty.results().len()); // m values are on the top of the stack (Ensured by validation) - let mut res: Vec<_> = value_stack.pop_types(func_ty.results().iter().rev()).collect(); // pop in reverse order since the stack is LIFO + let mut res: Vec<_> = func_ty + .results() + .iter() + .rev() + .map(|&ty| state.attach_value(value_stack.pop_tinyvalue(ty), ty).expect("validated function result")) + .collect(); res.reverse(); // reverse to get the original order Ok(res) } diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 887d107..3a7c06d 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -157,35 +157,9 @@ impl Imports { Ok(()) } - fn ref_subtype(actual: RefType, expected: RefType) -> bool { - if actual.is_nullable() && !expected.is_nullable() { - return false; - } - match (actual.type_index(), expected.type_index()) { - (Some(actual), Some(expected)) => actual == expected, - (Some(_), None) => matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Func)), - (None, Some(_)) => matches!(actual.abstract_heap_type(), Some(AbstractHeapType::NoFunc)), - (None, None) => { - actual.abstract_heap_type() == expected.abstract_heap_type() - || actual.is_func() && matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Func)) - || actual.is_extern() && matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Extern)) - || actual.is_exn() && matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Exn)) - } - } - } - - fn value_subtype(actual: WasmType, expected: WasmType) -> bool { - match (actual, expected) { - (WasmType::Ref(actual), WasmType::Ref(expected)) => Self::ref_subtype(actual, expected), - _ => actual == expected, - } - } - fn compare_table_types(import: &Import, actual: &TableType, expected: &TableType) -> Result<()> { Self::compare_types(import, &actual.arch(), &expected.arch())?; - if !Self::ref_subtype(actual.element_type, expected.element_type) - || !Self::ref_subtype(expected.element_type, actual.element_type) - { + if actual.element_type != expected.element_type { return Err(LinkingError::incompatible_import_type(import).into()); } if actual.size_initial < expected.size_initial { @@ -275,8 +249,8 @@ impl Imports { let global = store.state.get_global(global_addr); let expected = ty.with_ty(crate::store::canonicalize_value_type(ty.ty, type_addrs)); let compatible = global.ty.mutable == ty.mutable - && Self::value_subtype(global.ty.ty, expected.ty) - && (!ty.mutable || Self::value_subtype(expected.ty, global.ty.ty)); + && store.state.value_type_is_subtype(global.ty.ty, expected.ty) + && (!ty.mutable || store.state.value_type_is_subtype(expected.ty, global.ty.ty)); if !compatible { cold_path(); return Err(LinkingError::incompatible_import_type(import).into()); @@ -306,7 +280,8 @@ impl Imports { if let Some(func) = &func_handle { func.item.validate_store(store)?; } - if store.state.get_func(func_addr).type_addr != *expected_type_addr { + if !store.state.type_addr_is_subtype(store.state.get_func(func_addr).type_addr, *expected_type_addr) + { cold_path(); return Err(LinkingError::incompatible_import_type(import).into()); } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index ecfbacb..be38419 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -151,7 +151,7 @@ impl ModuleInstance { /// /// See pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option) -> Result { - let type_addrs = store.register_module_types(&module.func_types); + let type_addrs = store.register_module_types(&module.types); let id = store.next_module_instance_id(); let mut addrs = imports.unwrap_or_default().link(store, module, &type_addrs)?; let local_type_addrs = module.func_type_idxs[addrs.funcs.len()..] @@ -175,9 +175,9 @@ impl ModuleInstance { store.init_globals(&mut addrs.globals, &module.globals, &addrs.funcs, &type_addrs)?; addrs.tables.extend(store.init_tables(&module.tables, &addrs.globals, &addrs.funcs, &type_addrs)?); let (elem_addrs, elem_trapped) = - store.init_elements(&addrs.tables, &addrs.funcs, &addrs.globals, &module.elements)?; + store.init_elements(&addrs.tables, &addrs.funcs, &addrs.globals, &module.elements, &type_addrs)?; let (data_addrs, data_trapped) = - store.init_data(&addrs.memories, &addrs.globals, &addrs.funcs, &module.data)?; + store.init_data(&addrs.memories, &addrs.globals, &addrs.funcs, &module.data, &type_addrs)?; let instance = ModuleInstanceInner { store_id: store.id(), diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 5e78dae..fb8fdec 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -237,6 +237,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { JumpIfNonZero64(ip) => if self.exec_jump_non_zero_64(*ip) { return Ok(None) }, JumpIfRefNull(ip) => if self.exec_jump_ref_null(*ip) { return Ok(None) }, JumpIfRefNonNull(ip) => if self.exec_jump_ref_non_null(*ip) { return Ok(None) }, + BrOnCast(ip, ty, on_fail) => if self.exec_ref_matches(*ty) == *on_fail { self.cf.instr_ptr = *ip as usize; return Ok(None); }, JumpIfLocalZero32 { target_ip, local } => if self.exec_jump_local_zero_32(*target_ip, *local) { return Ok(None) }, JumpIfLocalNonZero32 { target_ip, local } => if self.exec_jump_local_non_zero_32(*target_ip, *local) { return Ok(None) }, JumpIfLocalZero64 { target_ip, local } => if self.exec_jump_local_zero_64(*target_ip, *local) { return Ok(None) }, @@ -444,10 +445,46 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), // Reference types - RefFunc(func_idx) => self.exec_const(ValueRef::from_addr(Some(self.module.resolve_func_addr(*func_idx))))?, + RefFunc(func_idx) => self.exec_const(ValueRef::from_category_addr(self.module.resolve_func_addr(*func_idx)))?, RefNull(_) => self.exec_const(ValueRef::NULL)?, RefIsNull => self.exec_ref_is_null()?, RefAsNonNull => self.exec_ref_as_non_null()?, + RefI31 => stack_op!(unary i32 => ValueRef, |v| ValueRef::from_i31(v)), + I31GetS => self.exec_i31_get(true)?, + I31GetU => self.exec_i31_get(false)?, + RefEq => stack_op!(binary ValueRef => i32, |a, b| i32::from(a == b)), + RefTest(ty) => { + let value = ValueRef::stack_pop(&mut self.store.value_stack); + let ty = self.canonical_ref_type(*ty); + self.store.value_stack.push(i32::from(self.store.state.value_ref_matches(value, ty)))?; + } + RefCast(ty) => { + if !self.exec_ref_matches(*ty) { + cold_path(); + return Err(Trap::CastFailure); + } + } + // GC objects + StructNew(ty) => self.exec_struct_new(*ty, false)?, + StructNewDefault(ty) => self.exec_struct_new(*ty, true)?, + StructGet(ty, field) => self.exec_struct_get(*ty, *field, None)?, + StructGetS(ty, field) => self.exec_struct_get(*ty, *field, Some(true))?, + StructGetU(ty, field) => self.exec_struct_get(*ty, *field, Some(false))?, + StructSet(ty, field) => self.exec_struct_set(*ty, *field)?, + ArrayNew(ty) => self.exec_array_new(*ty, false)?, + ArrayNewDefault(ty) => self.exec_array_new(*ty, true)?, + ArrayNewFixed(ty, len) => self.exec_array_new_fixed(*ty, *len)?, + ArrayNewData(ty, data) => self.exec_array_new_data(*ty, *data)?, + ArrayNewElem(ty, elem) => self.exec_array_new_elem(*ty, *elem)?, + ArrayGet(ty) => self.exec_array_get(*ty, None)?, + ArrayGetS(ty) => self.exec_array_get(*ty, Some(true))?, + ArrayGetU(ty) => self.exec_array_get(*ty, Some(false))?, + ArraySet(ty) => self.exec_array_set(*ty)?, + ArrayLen => self.exec_array_len()?, + ArrayFill(ty) => self.exec_array_fill(*ty)?, + ArrayCopy(dst, src) => self.exec_array_copy(*dst, *src)?, + ArrayInitData(ty, data) => self.exec_array_init_data(*ty, *data)?, + ArrayInitElem(ty, elem) => self.exec_array_init_elem(*ty, *elem)?, MemorySize(addr) => self.exec_memory_size(*addr)?, MemoryGrow(addr) => self.exec_memory_grow(*addr)?, @@ -987,10 +1024,19 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { &mut self, host_func: Rc, type_addr: TypeAddr, + params_may_gc: bool, ) -> Result { - let ty = self.store.state.get_type(type_addr); - let mut params = self.store.value_stack.pop_types(ty.params().iter().rev()).collect::>(); + let param_types = self.store.state.get_canonical_func_type(type_addr).params(); + let mut params = Vec::new(); + params.try_reserve_exact(param_types.len()).map_err(|_| Trap::OutOfMemory)?; + for &ty in param_types.iter().rev() { + let value = self.store.value_stack.pop_tinyvalue(ty); + params.push(self.store.state.attach_value(value, ty).expect("validated host argument")); + } params.reverse(); + if params_may_gc { + self.store.state.pin_host_values(¶ms); + } let result = host_func.call(FuncContext { store: self.store, module_id: self.module.id() }, ¶ms); let res = match result.and_then(|result| crate::func::validate_host_results(self.store, type_addr, result)) { Ok(res) => res, @@ -1016,7 +1062,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { match func.kind { crate::store::FunctionKind::Wasm(wasm_func) => self.exec_call(wasm_func, addr), crate::store::FunctionKind::Host(host_func) => { - self.exec_call_host::(host_func, func.type_addr)?; + self.exec_call_host::(host_func, func.type_addr, func.gc.params)?; Ok(()) } } @@ -1031,7 +1077,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.exec_return_call(wasm_func, addr)?; Ok(false) } - crate::store::FunctionKind::Host(host_func) => self.exec_call_host::(host_func, func.type_addr), + crate::store::FunctionKind::Host(host_func) => { + self.exec_call_host::(host_func, func.type_addr, func.gc.params) + } } } @@ -1071,7 +1119,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let table_addr = self.module.resolve_table_addr(table_addr); let table_idx = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; let table = self.store.state.get_table(table_addr); - debug_assert!(table.kind.element_type.is_func(), "table is not of type funcref"); let Ok(table) = table.get(table_idx) else { cold_path(); @@ -1092,11 +1139,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { expected_type_addr: TypeAddr, ) -> Result { let func = self.store.state.get_func(func_addr).clone(); - if func.type_addr != expected_type_addr { + if !self.store.state.type_addr_is_subtype(func.type_addr, expected_type_addr) { cold_path(); return Err(Trap::IndirectCallTypeMismatch { - actual: Box::new(self.store.state.get_type(func.type_addr).clone()), - expected: Box::new(self.store.state.get_type(expected_type_addr).clone()), + actual: Box::new(self.store.state.get_canonical_func_type(func.type_addr).clone()), + expected: Box::new(self.store.state.get_canonical_func_type(expected_type_addr).clone()), }); } match func.kind { @@ -1105,7 +1152,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { false => self.exec_call(wasm_func, func_addr), }, crate::store::FunctionKind::Host(host_func) => { - return self.exec_call_host::(host_func, func.type_addr); + return self.exec_call_host::(host_func, func.type_addr, func.gc.params); } }?; Ok(false) @@ -1327,6 +1374,261 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } + fn canonical_ref_type(&self, ty: RefType) -> RefType { + let Some(type_index) = ty.type_index() else { return ty }; + RefType::new_concrete(ty.is_nullable(), self.module.resolve_type_addr(type_index)) + .expect("canonical type address fits in a reference") + } + + fn exec_ref_matches(&self, ty: RefType) -> bool { + let value = ValueRef::stack_peek(&self.store.value_stack); + self.store.state.value_ref_matches(value, self.canonical_ref_type(ty)) + } + + fn exec_i31_get(&mut self, signed: bool) -> Result<(), Trap> { + let value = ValueRef::stack_pop(&mut self.store.value_stack); + if value.is_null() { + cold_path(); + return Err(Trap::NullI31Reference); + } + let value = if signed { + value.i31_s().expect("validated i31.get operand") + } else { + value.i31_u().expect("validated i31.get operand") as i32 + }; + self.store.value_stack.push(value) + } + + fn exec_struct_new(&mut self, type_index: TypeAddr, default: bool) -> Result<(), Trap> { + let type_addr = self.module.resolve_type_addr(type_index); + let fields = &self.store.state.get_type(type_addr).as_struct().expect("validated struct.new type").fields; + let mut values = Vec::new(); + values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; + if default { + values.extend(fields.iter().map(|field| default_value(field.storage))); + } else { + for field in fields.iter().rev() { + values.push(pop_value(&mut self.store.value_stack, field.storage)); + } + values.reverse(); + } + let value = self.store.state.alloc_gc_object( + type_addr, + values, + (&self.store.value_stack.stack_32).into_iter().copied(), + )?; + self.store.value_stack.push(value) + } + + fn exec_struct_get(&mut self, type_index: TypeAddr, field_index: u32, signed: Option) -> Result<(), Trap> { + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_struct().expect("validated struct.get type").fields + [field_index as usize] + .storage; + let object = self.store.state.gc_object(reference, type_addr)?; + let value = *object.values.get(field_index as usize).expect("validated struct field index"); + push_value(&mut self.store.value_stack, value, storage, signed) + } + + fn exec_struct_set(&mut self, type_index: TypeAddr, field_index: u32) -> Result<(), Trap> { + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_struct().expect("validated struct.set type").fields + [field_index as usize] + .storage; + let value = pop_value(&mut self.store.value_stack, storage); + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + self.store.state.gc_object(reference, type_addr)?; + self.store.state.gc.set(reference, field_index as usize, value).expect("live struct field"); + Ok(()) + } + + fn exec_array_new(&mut self, type_index: TypeAddr, default: bool) -> Result<(), Trap> { + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_array().expect("validated array.new type").field.storage; + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let value = if default { default_value(storage) } else { pop_value(&mut self.store.value_stack, storage) }; + let mut values = Vec::new(); + values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + values.resize(len, value); + let reference = self.store.state.alloc_gc_object( + type_addr, + values, + (&self.store.value_stack.stack_32).into_iter().copied(), + )?; + self.store.value_stack.push(reference) + } + + fn exec_array_new_fixed(&mut self, type_index: TypeAddr, len: u32) -> Result<(), Trap> { + let type_addr = self.module.resolve_type_addr(type_index); + let storage = + self.store.state.get_type(type_addr).as_array().expect("validated array.new_fixed type").field.storage; + let len = len as usize; + let mut values = Vec::new(); + values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + for _ in 0..len { + values.push(pop_value(&mut self.store.value_stack, storage)); + } + values.reverse(); + let reference = self.store.state.alloc_gc_object( + type_addr, + values, + (&self.store.value_stack.stack_32).into_iter().copied(), + )?; + self.store.value_stack.push(reference) + } + + fn exec_array_get(&mut self, type_index: TypeAddr, signed: Option) -> Result<(), Trap> { + let index = u32::stack_pop(&mut self.store.value_stack) as usize; + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_array().expect("validated array.get type").field.storage; + let object = self.store.state.gc_object(reference, type_addr)?; + let value = *object.values.get(index).ok_or(Trap::ArrayOutOfBounds)?; + push_value(&mut self.store.value_stack, value, storage, signed) + } + + fn exec_array_set(&mut self, type_index: TypeAddr) -> Result<(), Trap> { + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_array().expect("validated array.set type").field.storage; + let value = pop_value(&mut self.store.value_stack, storage); + let index = u32::stack_pop(&mut self.store.value_stack) as usize; + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + let object = self.store.state.gc_object(reference, type_addr)?; + if index >= object.values.len() { + return Err(Trap::ArrayOutOfBounds); + } + self.store.state.gc.set(reference, index, value).expect("live array element"); + Ok(()) + } + + fn exec_array_len(&mut self) -> Result<(), Trap> { + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + if reference.is_null() { + return Err(Trap::NullArrayReference); + } + let object = self.store.state.gc.get(reference).ok_or(Trap::Other("invalid GC reference"))?; + if self.store.state.get_type(object.type_addr).as_array().is_none() { + return Err(Trap::Other("GC reference is not an array")); + } + self.store.value_stack.push(object.values.len() as i32) + } + + fn exec_array_fill(&mut self, type_index: TypeAddr) -> Result<(), Trap> { + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_array().expect("validated array.fill type").field.storage; + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let value = pop_value(&mut self.store.value_stack, storage); + let index = u32::stack_pop(&mut self.store.value_stack) as usize; + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + let object = self.store.state.gc_object(reference, type_addr)?; + let end = index.checked_add(len).filter(|end| *end <= object.values.len()).ok_or(Trap::ArrayOutOfBounds)?; + self.store.state.gc.fill(reference, index..end, value).expect("live array range"); + Ok(()) + } + + fn exec_array_copy(&mut self, dst_type: TypeAddr, src_type: TypeAddr) -> Result<(), Trap> { + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let src_index = u32::stack_pop(&mut self.store.value_stack) as usize; + let src = ValueRef::stack_pop(&mut self.store.value_stack); + let dst_index = u32::stack_pop(&mut self.store.value_stack) as usize; + let dst = ValueRef::stack_pop(&mut self.store.value_stack); + let dst_type = self.module.resolve_type_addr(dst_type); + let src_type = self.module.resolve_type_addr(src_type); + let dst_len = self.store.state.gc_object(dst, dst_type)?.values.len(); + let src_object = self.store.state.gc_object(src, src_type)?; + let src_end = + src_index.checked_add(len).filter(|end| *end <= src_object.values.len()).ok_or(Trap::ArrayOutOfBounds)?; + dst_index.checked_add(len).filter(|end| *end <= dst_len).ok_or(Trap::ArrayOutOfBounds)?; + if src == dst { + self.store.state.gc.copy_within(dst, src_index..src_end, dst_index).expect("live array range"); + return Ok(()); + } + let mut values = Vec::new(); + values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + values.extend_from_slice(&src_object.values[src_index..src_end]); + self.store.state.gc.set_slice(dst, dst_index, &values).expect("live array range"); + Ok(()) + } + + fn exec_array_new_data(&mut self, type_index: TypeAddr, data_index: DataAddr) -> Result<(), Trap> { + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let src = u32::stack_pop(&mut self.store.value_stack) as usize; + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_array().expect("validated array type").field.storage; + let data_addr = self.module.resolve_data_addr(data_index); + let data = self.store.state.data[data_addr as usize].data.as_deref().unwrap_or(&[]); + let values = decode_data(storage, data, src, len)?; + let reference = self.store.state.alloc_gc_object( + type_addr, + values, + (&self.store.value_stack.stack_32).into_iter().copied(), + )?; + self.store.value_stack.push(reference) + } + + fn exec_array_new_elem(&mut self, type_index: TypeAddr, elem_index: ElemAddr) -> Result<(), Trap> { + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let src = u32::stack_pop(&mut self.store.value_stack) as usize; + let elem_addr = self.module.resolve_elem_addr(elem_index); + let items = self.store.state.elements[elem_addr as usize].items.as_deref().unwrap_or(&[]); + let end = src.checked_add(len).filter(|end| *end <= items.len()).ok_or_else(|| Trap::TableOutOfBounds { + offset: src, + len, + max: items.len(), + })?; + let mut values = Vec::new(); + values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + values.extend(items[src..end].iter().copied().map(TinyWasmValue::ValueRef)); + let type_addr = self.module.resolve_type_addr(type_index); + let reference = self.store.state.alloc_gc_object( + type_addr, + values, + (&self.store.value_stack.stack_32).into_iter().copied(), + )?; + self.store.value_stack.push(reference) + } + + fn exec_array_init_data(&mut self, type_index: TypeAddr, data_index: DataAddr) -> Result<(), Trap> { + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let src = u32::stack_pop(&mut self.store.value_stack) as usize; + let dst = u32::stack_pop(&mut self.store.value_stack) as usize; + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + let type_addr = self.module.resolve_type_addr(type_index); + let storage = self.store.state.get_type(type_addr).as_array().expect("validated array type").field.storage; + let object_len = self.store.state.gc_object(reference, type_addr)?.values.len(); + dst.checked_add(len).filter(|end| *end <= object_len).ok_or(Trap::ArrayOutOfBounds)?; + let data = + self.store.state.data[self.module.resolve_data_addr(data_index) as usize].data.as_deref().unwrap_or(&[]); + let values = decode_data(storage, data, src, len)?; + self.store.state.gc.set_slice(reference, dst, &values).expect("live array range"); + Ok(()) + } + + fn exec_array_init_elem(&mut self, type_index: TypeAddr, elem_index: ElemAddr) -> Result<(), Trap> { + let len = u32::stack_pop(&mut self.store.value_stack) as usize; + let src = u32::stack_pop(&mut self.store.value_stack) as usize; + let dst = u32::stack_pop(&mut self.store.value_stack) as usize; + let reference = ValueRef::stack_pop(&mut self.store.value_stack); + let type_addr = self.module.resolve_type_addr(type_index); + let object_len = self.store.state.gc_object(reference, type_addr)?.values.len(); + dst.checked_add(len).filter(|end| *end <= object_len).ok_or(Trap::ArrayOutOfBounds)?; + let items = self.store.state.elements[self.module.resolve_elem_addr(elem_index) as usize] + .items + .as_deref() + .unwrap_or(&[]); + let end = src.checked_add(len).filter(|end| *end <= items.len()).ok_or_else(|| Trap::TableOutOfBounds { + offset: src, + len, + max: items.len(), + })?; + let mut values = Vec::new(); + values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + values.extend(items[src..end].iter().copied().map(TinyWasmValue::ValueRef)); + self.store.state.gc.set_slice(reference, dst, &values).expect("live array range"); + Ok(()) + } + fn exec_memory_size(&mut self, addr: u32) -> Result<(), Trap> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(addr)); match mem.is_64bit() { @@ -1531,8 +1833,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_table_get(&mut self, table_index: u32) -> Result<(), Trap> { let table_addr = self.module.resolve_table_addr(table_index); let idx = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; - let v = self.store.state.get_table(table_addr).get_wasm_val(idx)?; - self.store.value_stack.push_dyn(v.into()) + let value = *self.store.state.get_table(table_addr).get(idx)?; + self.store.value_stack.push(value) } fn exec_table_set(&mut self, table_index: u32) -> Result<(), Trap> { @@ -1540,7 +1842,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let table_addr = self.module.resolve_table_addr(table_index); let idx = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; let table = self.store.state.get_table_mut(table_addr); - table.set(idx, val.addr().into()) + table.set(idx, val) } fn exec_table_size(&mut self, table_index: u32) -> Result<(), Trap> { @@ -1580,7 +1882,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let val = ::stack_pop(&mut self.store.value_stack); let table = self.store.state.get_table_mut(table_addr); let sz = table.size(); - let result = table.grow(n, val.addr().into()); + let result = table.grow(n, val); match (arch, result) { (MemoryArch::I32, Ok(())) => self.store.value_stack.push(sz as i32), (MemoryArch::I32, Err(_)) => self.store.value_stack.push(-1_i32), @@ -1595,7 +1897,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let n = self.pop_table_operand(arch)?; let val = ::stack_pop(&mut self.store.value_stack); let i = self.pop_table_operand(arch)?; - self.store.state.get_table_mut(table_addr).fill(i, n, val.addr().into()) + self.store.state.get_table_mut(table_addr).fill(i, n, val) } fn pop_table_operand(&mut self, arch: MemoryArch) -> Result { diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 853c5e4..378dc7f 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -165,6 +165,15 @@ impl Stack { self.data.truncate(len - count); } } + +impl<'a, T: Copy + Default> IntoIterator for &'a Stack { + type Item = &'a T; + type IntoIter = core::slice::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.data.iter() + } +} impl ValueStack { pub(crate) fn new(config: &Config) -> Self { Self { @@ -207,13 +216,6 @@ impl ValueStack { self.stack_128.select_many(counts.c128 as usize, condition); } - pub(crate) fn pop_types<'a>( - &'a mut self, - val_types: impl IntoIterator, - ) -> impl core::iter::Iterator { - val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) - } - #[inline(always)] pub(crate) fn enter_locals(&mut self, params: &ValueCounts, locals: &ValueCounts) -> Result { let locals_base32 = self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)?; @@ -246,14 +248,12 @@ impl ValueStack { Ok(()) } - pub(crate) fn pop_wasmvalue(&mut self, val_type: WasmType) -> WasmValue { + pub(crate) fn pop_tinyvalue(&mut self, val_type: WasmType) -> TinyWasmValue { match val_type { - WasmType::I32 => WasmValue::I32(i32::stack_pop(self)), - WasmType::I64 => WasmValue::I64(i64::stack_pop(self)), - WasmType::F32 => WasmValue::F32(f32::stack_pop(self)), - WasmType::F64 => WasmValue::F64(f64::stack_pop(self)), - WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::stack_pop(self)).attach_type(val_type).unwrap(), - WasmType::V128 => WasmValue::V128(Value128::stack_pop(self).0), + WasmType::I32 | WasmType::F32 => TinyWasmValue::Value32(Value32::stack_pop(self)), + WasmType::I64 | WasmType::F64 => TinyWasmValue::Value64(Value64::stack_pop(self)), + WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::stack_pop(self)), + WasmType::V128 => TinyWasmValue::Value128(Value128::stack_pop(self)), } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index be2e0a1..f034906 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -8,8 +8,6 @@ pub(crate) type Value64 = u64; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct ValueRef(u32); -// TODO(wasm3): GC references need a tagged representation; this raw address is only sufficient for current refs. - impl Default for ValueRef { fn default() -> Self { Self::NULL @@ -17,7 +15,8 @@ impl Default for ValueRef { } impl ValueRef { - pub(crate) const NULL: Self = Self(u32::MAX); + const HOST_ANY_TAG: u32 = 1 << 30; + pub(crate) const NULL: Self = Self(0); #[inline] pub(crate) const fn from_raw(raw: u32) -> Self { @@ -25,16 +24,24 @@ impl ValueRef { } #[inline] - pub(crate) const fn from_addr(addr: Option) -> Self { - match addr { - Some(addr) => Self(addr), - None => Self::NULL, - } + pub(crate) const fn from_category_addr(addr: u32) -> Self { + let Some(raw) = addr.checked_add(1) else { panic!("reference address does not fit in the runtime encoding") }; + let Some(raw) = raw.checked_mul(2) else { panic!("reference address does not fit in the runtime encoding") }; + Self(raw) + } + + #[inline] + pub(crate) const fn from_i31(value: i32) -> Self { + Self(((value as u32) << 1) | 1) + } + + pub(crate) const fn is_host_any(self) -> bool { + matches!(self.addr(), Some(addr) if addr & Self::HOST_ANY_TAG != 0) } #[inline] pub(crate) const fn addr(self) -> Option { - if self.is_null() { None } else { Some(self.0) } + if self.is_null() || self.is_i31() { None } else { Some(self.0 / 2 - 1) } } #[inline] @@ -42,6 +49,21 @@ impl ValueRef { self.0 == Self::NULL.0 } + #[inline] + pub(crate) const fn is_i31(self) -> bool { + self.0 & 1 != 0 + } + + #[inline] + pub(crate) const fn i31_s(self) -> Option { + if self.is_i31() { Some((self.0 as i32) >> 1) } else { None } + } + + #[inline] + pub(crate) const fn i31_u(self) -> Option { + if self.is_i31() { Some(self.0 >> 1) } else { None } + } + #[inline] pub(crate) const fn raw(self) -> u32 { self.0 @@ -96,7 +118,7 @@ impl TinyWasmValue { } } - /// Attaches a type to the value (panics if the size of the value is not the same as the type) + /// Converts this internal value to the requested public WebAssembly type. pub fn attach_type(self, ty: WasmType) -> Option { match (self, ty) { (Self::Value32(v), WasmType::I32) => Some(WasmValue::I32(v as i32)), @@ -105,13 +127,13 @@ impl TinyWasmValue { (Self::Value64(v), WasmType::F64) => Some(WasmValue::F64(f64::from_bits(v))), (Self::ValueRef(v), WasmType::Ref(_)) if v.is_null() => Some(RefValue::Null.into()), (Self::ValueRef(v), WasmType::Ref(ty)) if ty.is_func() => { - Some(WasmValue::Ref(RefValue::Func(FuncRef::new(v.raw())))) + Some(WasmValue::Ref(RefValue::Func(FuncRef::new(v.addr()?)))) } (Self::ValueRef(v), WasmType::Ref(ty)) if ty.is_extern() => { - Some(WasmValue::Ref(RefValue::Extern(ExternRef::new(v.raw())))) + Some(WasmValue::Ref(RefValue::Extern(ExternRef::from_raw(v.raw())))) } (Self::ValueRef(v), WasmType::Ref(ty)) if ty.is_exn() => { - Some(WasmValue::Ref(RefValue::Exn(ExnRef::new(v.raw())))) + Some(WasmValue::Ref(RefValue::Exn(ExnRef::new(v.addr()?)))) } (Self::ValueRef(v), WasmType::Ref(_)) => Some(WasmValue::Ref(RefValue::Any(AnyRef::from_raw(v.raw())))), (Self::Value128(v), WasmType::V128) => Some(WasmValue::V128(v.0)), @@ -130,7 +152,11 @@ impl From<&WasmValue> for TinyWasmValue { WasmValue::I64(v) => Self::Value64(*v as u64), WasmValue::F32(v) => Self::Value32(v.to_bits()), WasmValue::F64(v) => Self::Value64(v.to_bits()), - WasmValue::Ref(value) => Self::ValueRef(ValueRef::from_addr(value.raw())), + WasmValue::Ref(RefValue::Null) => Self::ValueRef(ValueRef::NULL), + WasmValue::Ref(RefValue::Func(value)) => Self::ValueRef(ValueRef::from_category_addr(value.addr())), + WasmValue::Ref(RefValue::Extern(value)) => Self::ValueRef(ValueRef::from_raw(value.raw())), + WasmValue::Ref(RefValue::Exn(value)) => Self::ValueRef(ValueRef::from_category_addr(value.addr())), + WasmValue::Ref(RefValue::Any(value)) => Self::ValueRef(ValueRef::from_raw(value.raw())), WasmValue::V128(v) => Self::Value128((*v).into()), } } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index dc54736..d8f9669 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -5,7 +5,8 @@ use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use crate::store::{GlobalInstance, TableElement, TableInstance}; +use crate::interpreter::TinyWasmValue; +use crate::store::{GlobalInstance, TableInstance}; use crate::{Error, MemoryInstance, Result, Store, Trap}; use tinywasm_types::{Addr, GlobalAddr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue}; @@ -346,12 +347,13 @@ fn table_value_to_element( state: &crate::store::State, element_type: tinywasm_types::RefType, value: WasmValue, -) -> Result { +) -> Result { if !state.value_matches_type(value, WasmType::Ref(element_type)) { return Err(Trap::Other("invalid table value type")); } let WasmValue::Ref(value) = value else { unreachable!() }; - Ok(TableElement::from(value.raw())) + let TinyWasmValue::ValueRef(value) = TinyWasmValue::from(WasmValue::Ref(value)) else { unreachable!() }; + Ok(value) } impl Table { @@ -390,20 +392,37 @@ impl Table { /// Get a table element as a wasm reference value. pub fn get(&self, store: &Store, index: TableAddr) -> Result { - Ok(self.instance(store)?.get_wasm_val(index as usize)?) + let table = self.instance(store)?; + let value = store + .state + .attach_value(TinyWasmValue::ValueRef(*table.get(index as usize)?), WasmType::Ref(table.kind.element_type)) + .expect("table value matches its element type"); + if let WasmValue::Ref(value) = value { + store.state.pin_host_ref(value); + } + Ok(value) } /// Load a range of table elements and iterate over wasm reference values. - pub fn load(&self, store: &Store, offset: usize, len: usize) -> Result> { + pub fn load<'a>( + &self, + store: &'a Store, + offset: usize, + len: usize, + ) -> Result + 'a> { let table = self.instance(store)?; let element_type = table.kind.element_type; let elements = table.load(offset, len)?; - Ok(elements - .iter() - .copied() - .map(move |element| element.to_wasm_value(element_type)) - .collect::>() - .into_iter()) + let may_contain_gc = store.state.type_may_contain_gc(WasmType::Ref(element_type)); + Ok(elements.iter().copied().map(move |value| { + if may_contain_gc { + store.state.gc.pin(value); + } + store + .state + .attach_value(TinyWasmValue::ValueRef(value), WasmType::Ref(element_type)) + .expect("table value matches its element type") + })) } /// Set a table element. @@ -460,8 +479,13 @@ impl Global { /// Get the current value of the global. pub fn get(&self, store: &Store) -> Result { let global = self.instance(store)?; - let value = global.value.attach_type(global.ty.ty); - Ok(value.unwrap_or_else(|| unreachable!("Global value type does not match global type, this is a bug"))) + let value = store.state.attach_value(global.value, global.ty.ty); + let value = + value.unwrap_or_else(|| unreachable!("Global value type does not match global type, this is a bug")); + if let WasmValue::Ref(value) = value { + store.state.pin_host_ref(value); + } + Ok(value) } /// Set the current value of the global. diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs index f3a8602..4437bd4 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -1,12 +1,14 @@ -use crate::TableElement; +use crate::interpreter::ValueRef; use alloc::vec::Vec; +use tinywasm_types::RefType; /// A WebAssembly Element Instance /// /// See #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ElementInstance { - pub(crate) items: Option>, // none is the element was dropped + pub(crate) items: Option>, // none is the element was dropped + pub(crate) ty: RefType, } impl ElementInstance { diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index f0e3ed9..367fadb 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -10,9 +10,17 @@ use crate::func::HostFunction; #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct FunctionInstance { pub(crate) type_addr: TypeAddr, + pub(crate) gc: FunctionGcMetadata, pub(crate) kind: FunctionKind, } +#[derive(Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct FunctionGcMetadata { + pub(crate) params: bool, + pub(crate) results: bool, +} + #[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) enum FunctionKind { diff --git a/crates/tinywasm/src/store/gc/arena.rs b/crates/tinywasm/src/store/gc/arena.rs new file mode 100644 index 0000000..b8f5961 --- /dev/null +++ b/crates/tinywasm/src/store/gc/arena.rs @@ -0,0 +1,263 @@ +//! Mark-and-sweep storage with stable handles for WebAssembly GC objects. + +use alloc::vec::Vec; +use core::cell::Cell; +use core::mem::size_of; +use core::num::NonZeroU32; + +/// A stable reference to an arena slot. +/// +/// Reclaimed slots increment their generation so stale handles cannot access a +/// new object allocated in the same slot. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Handle { + index: u32, + generation: NonZeroU32, +} + +/// Adds the handles referenced by an arena object to the mark worklist. +pub(crate) trait Trace { + /// Calls `mark` for every arena object referenced by this object. + fn trace(&self, mark: &mut impl FnMut(Handle)); +} + +/// An arena allocation or capacity error. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct AllocError; + +impl core::fmt::Display for AllocError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("GC arena capacity exhausted") + } +} + +impl core::error::Error for AllocError {} + +#[inline] +fn mark(slots: &[Slot], worklist: &mut Vec, handle: Handle) { + let Some(slot) = slots.get(handle.index as usize) else { + return; + }; + if slot.generation != handle.generation || !matches!(slot.state, SlotState::Occupied { .. }) || slot.marked.get() { + return; + } + slot.marked.set(true); + worklist.push(handle.index); +} + +struct Slot { + generation: NonZeroU32, + marked: Cell, + state: SlotState, +} + +enum SlotState { + Occupied { value: T, bytes: usize }, + Free { next: Option }, + Retired, +} + +/// A mark-and-sweep arena with stable generational handles. +pub(crate) struct Arena { + slots: Vec>, + free_head: Option, + worklist: Vec, + len: usize, + allocated_bytes: usize, + collection_threshold: usize, + next_collection: usize, +} + +impl Arena { + /// Creates an empty arena with the given initial collection threshold. + pub(crate) const fn new(collection_threshold: usize) -> Self { + Self { + slots: Vec::new(), + free_head: None, + worklist: Vec::new(), + len: 0, + allocated_bytes: 0, + collection_threshold, + next_collection: collection_threshold, + } + } + + /// Allocates a value with `out_of_line_bytes` of storage owned outside its slot. + /// + /// The byte count must remain valid while the value is in the arena. + pub(crate) fn alloc(&mut self, value: T, out_of_line_bytes: usize) -> Result { + let bytes = size_of::>().checked_add(out_of_line_bytes).ok_or(AllocError)?; + let allocated_bytes = self.allocated_bytes.checked_add(bytes).ok_or(AllocError)?; + + let handle = if let Some(index) = self.free_head { + let slot = &mut self.slots[index as usize]; + let SlotState::Free { next } = slot.state else { unreachable!("free list points to an occupied slot") }; + self.free_head = next; + slot.marked.set(false); + slot.state = SlotState::Occupied { value, bytes }; + Handle { index, generation: slot.generation } + } else { + if self.slots.len() >= u32::MAX as usize { + return Err(AllocError); + } + let index = self.slots.len() as u32; + self.slots.try_reserve(1).map_err(|_| AllocError)?; + self.slots.push(Slot { + generation: NonZeroU32::MIN, + marked: Cell::new(false), + state: SlotState::Occupied { value, bytes }, + }); + Handle { index, generation: NonZeroU32::MIN } + }; + + self.len += 1; + self.allocated_bytes = allocated_bytes; + Ok(handle) + } + + /// Returns a shared reference if the handle is live and current. + pub(crate) fn get(&self, handle: Handle) -> Option<&T> { + let slot = self.slots.get(handle.index as usize)?; + if slot.generation != handle.generation { + return None; + } + match &slot.state { + SlotState::Occupied { value, .. } => Some(value), + SlotState::Free { .. } | SlotState::Retired => None, + } + } + + /// Returns an exclusive reference if the handle is live and current. + pub(crate) fn get_mut(&mut self, handle: Handle) -> Option<&mut T> { + let slot = self.slots.get_mut(handle.index as usize)?; + if slot.generation != handle.generation { + return None; + } + match &mut slot.state { + SlotState::Occupied { value, .. } => Some(value), + SlotState::Free { .. } | SlotState::Retired => None, + } + } + + /// Returns whether an allocation of this size should trigger collection. + pub(crate) fn should_collect(&self, out_of_line_bytes: usize) -> bool { + size_of::>() + .checked_add(out_of_line_bytes) + .and_then(|bytes| self.allocated_bytes.checked_add(bytes)) + .is_none_or(|bytes| bytes >= self.next_collection) + } +} + +impl Arena { + /// Collects objects that cannot be reached from `roots`. + pub(crate) fn collect(&mut self, roots: impl IntoIterator) -> Result<(), AllocError> { + self.worklist.clear(); + self.worklist.try_reserve(self.len).map_err(|_| AllocError)?; + + for root in roots { + mark(&self.slots, &mut self.worklist, root); + } + + while let Some(index) = self.worklist.pop() { + let slots = &self.slots; + let worklist = &mut self.worklist; + let SlotState::Occupied { value, .. } = &slots[index as usize].state else { + unreachable!("marked slots are occupied") + }; + value.trace(&mut |handle| mark(slots, worklist, handle)); + } + + let mut live_objects = 0; + let mut live_bytes = 0; + for (index, slot) in self.slots.iter_mut().enumerate() { + let SlotState::Occupied { bytes, .. } = &slot.state else { + continue; + }; + if slot.marked.replace(false) { + live_objects += 1; + live_bytes += bytes; + continue; + } + + if let Some(generation) = slot.generation.checked_add(1) { + slot.generation = generation; + slot.state = SlotState::Free { next: self.free_head }; + self.free_head = Some(index as u32); + } else { + slot.state = SlotState::Retired; + } + } + + self.len = live_objects; + self.allocated_bytes = live_bytes; + self.next_collection = live_bytes.saturating_mul(2).max(self.collection_threshold); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + struct Object(Vec); + + impl Trace for Object { + fn trace(&self, mark: &mut impl FnMut(Handle)) { + for &handle in &self.0 { + mark(handle); + } + } + } + + #[test] + fn reuses_slots_and_rejects_stale_handles() { + let mut arena = Arena::new(1024); + let stale = arena.alloc(Object(vec![]), 0).unwrap(); + + arena.collect([]).unwrap(); + let current = arena.alloc(Object(vec![]), 0).unwrap(); + + assert_eq!(stale.index, current.index); + assert_ne!(stale.generation, current.generation); + assert!(arena.get(stale).is_none()); + assert!(arena.get(current).is_some()); + } + + #[test] + fn keeps_reachable_cycles() { + let mut arena = Arena::new(1024); + let first = arena.alloc(Object(Vec::with_capacity(1)), size_of::()).unwrap(); + let second = arena.alloc(Object(vec![first]), size_of::()).unwrap(); + arena.get_mut(first).unwrap().0.push(second); + + arena.collect([first]).unwrap(); + + assert_eq!(arena.len, 2); + assert_eq!(arena.allocated_bytes, size_of::>() * 2 + size_of::() * 2); + assert_eq!(arena.get(second).unwrap().0, [first]); + } + + #[test] + fn reclaims_unreachable_cycles() { + let mut arena = Arena::new(1024); + let first = arena.alloc(Object(Vec::with_capacity(1)), size_of::()).unwrap(); + let second = arena.alloc(Object(vec![first]), size_of::()).unwrap(); + arena.get_mut(first).unwrap().0.push(second); + + arena.collect([]).unwrap(); + + assert_eq!(arena.len, 0); + assert_eq!(arena.allocated_bytes, 0); + } + + #[test] + fn accounts_for_object_storage() { + let mut arena = Arena::new(1024); + let root = arena.alloc(Object(vec![]), 24).unwrap(); + + arena.collect([root]).unwrap(); + + assert_eq!(arena.allocated_bytes, size_of::>() + 24); + } +} diff --git a/crates/tinywasm/src/store/gc/mod.rs b/crates/tinywasm/src/store/gc/mod.rs new file mode 100644 index 0000000..2a87a5f --- /dev/null +++ b/crates/tinywasm/src/store/gc/mod.rs @@ -0,0 +1,92 @@ +mod arena; +mod object; + +use alloc::vec::Vec; +use tinywasm_types::{StorageType, WasmType}; + +use crate::Trap; +use crate::interpreter::stack::ValueStack; +use crate::interpreter::{InternalValue, TinyWasmValue, Value128, ValueRef}; + +pub(crate) use arena::{AllocError, Arena, Handle, Trace}; +pub(crate) use object::{GcHeap, GcObject}; + +/// Returns the zero value for a GC field or array element. +pub(crate) fn default_value(storage: StorageType) -> TinyWasmValue { + match storage { + StorageType::I8 | StorageType::I16 | StorageType::Value(WasmType::I32 | WasmType::F32) => { + TinyWasmValue::Value32(0) + } + StorageType::Value(WasmType::I64 | WasmType::F64) => TinyWasmValue::Value64(0), + StorageType::Value(WasmType::V128) => TinyWasmValue::Value128(Value128::from([0; 16])), + StorageType::Value(WasmType::Ref(_)) => TinyWasmValue::ValueRef(ValueRef::NULL), + } +} + +/// Pops and packs a GC field or array element from the operand stack. +pub(crate) fn pop_value(stack: &mut ValueStack, storage: StorageType) -> TinyWasmValue { + match storage { + StorageType::I8 => TinyWasmValue::Value32(i32::stack_pop(stack) as u8 as u32), + StorageType::I16 => TinyWasmValue::Value32(i32::stack_pop(stack) as u16 as u32), + StorageType::Value(WasmType::I32 | WasmType::F32) => TinyWasmValue::Value32(u32::stack_pop(stack)), + StorageType::Value(WasmType::I64 | WasmType::F64) => TinyWasmValue::Value64(u64::stack_pop(stack)), + StorageType::Value(WasmType::V128) => TinyWasmValue::Value128(Value128::stack_pop(stack)), + StorageType::Value(WasmType::Ref(_)) => TinyWasmValue::ValueRef(ValueRef::stack_pop(stack)), + } +} + +/// Extends a packed value and pushes it onto the operand stack. +pub(crate) fn push_value( + stack: &mut ValueStack, + value: TinyWasmValue, + storage: StorageType, + signed: Option, +) -> Result<(), Trap> { + let value = match (value, storage, signed) { + (TinyWasmValue::Value32(value), StorageType::I8, Some(true)) => { + TinyWasmValue::Value32(value as i8 as i32 as u32) + } + (TinyWasmValue::Value32(value), StorageType::I16, Some(true)) => { + TinyWasmValue::Value32(value as i16 as i32 as u32) + } + (TinyWasmValue::Value32(value), StorageType::I8, Some(false)) => TinyWasmValue::Value32(value & u8::MAX as u32), + (TinyWasmValue::Value32(value), StorageType::I16, Some(false)) => { + TinyWasmValue::Value32(value & u16::MAX as u32) + } + (value, _, None) => value, + _ => unreachable!("validated packed field access"), + }; + stack.push_dyn(value) +} + +/// Decodes numeric array elements from a data segment. +pub(crate) fn decode_data( + storage: StorageType, + data: &[u8], + src: usize, + len: usize, +) -> Result, Trap> { + let width = match storage { + StorageType::I8 => 1, + StorageType::I16 => 2, + StorageType::Value(WasmType::I32 | WasmType::F32) => 4, + StorageType::Value(WasmType::I64 | WasmType::F64) => 8, + StorageType::Value(WasmType::V128) => 16, + StorageType::Value(WasmType::Ref(_)) => unreachable!("array.new_data reference element"), + }; + let Some(end) = len.checked_mul(width).and_then(|bytes| src.checked_add(bytes)).filter(|&end| end <= data.len()) + else { + return Err(Trap::MemoryOutOfBounds { offset: src, len: len.saturating_mul(width), max: data.len() }); + }; + let mut values = Vec::new(); + values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + values.extend(data[src..end].chunks_exact(width).map(|bytes| match width { + 1 => TinyWasmValue::Value32(u32::from(bytes[0])), + 2 => TinyWasmValue::Value32(u32::from(u16::from_le_bytes(bytes.try_into().unwrap()))), + 4 => TinyWasmValue::Value32(u32::from_le_bytes(bytes.try_into().unwrap())), + 8 => TinyWasmValue::Value64(u64::from_le_bytes(bytes.try_into().unwrap())), + 16 => TinyWasmValue::Value128(Value128::from(<[u8; 16]>::try_from(bytes).unwrap())), + _ => unreachable!(), + })); + Ok(values) +} diff --git a/crates/tinywasm/src/store/gc/object.rs b/crates/tinywasm/src/store/gc/object.rs new file mode 100644 index 0000000..a3c5440 --- /dev/null +++ b/crates/tinywasm/src/store/gc/object.rs @@ -0,0 +1,218 @@ +use alloc::{boxed::Box, vec::Vec}; +use core::cell::RefCell; +use core::mem::size_of; +use core::sync::atomic::{AtomicU32, Ordering}; + +use tinywasm_types::TypeAddr; + +use crate::interpreter::{TinyWasmValue, ValueRef}; + +use super::{AllocError, Arena, Handle, Trace}; + +static NEXT_GC_REF: AtomicU32 = AtomicU32::new(0); + +pub(crate) struct GcObject { + pub(crate) type_addr: TypeAddr, + pub(crate) values: Box<[TinyWasmValue]>, + references: Option]>>, +} + +impl Trace for GcObject { + fn trace(&self, mark: &mut impl FnMut(Handle)) { + if let Some(references) = &self.references { + references.iter().flatten().copied().for_each(mark); + } + } +} + +pub(crate) struct GcHeap { + objects: Arena, + directory: Vec<(u32, Handle)>, + pinned: RefCell>, +} + +impl Default for GcHeap { + fn default() -> Self { + Self::new(1024 * 1024) + } +} + +impl GcHeap { + /// Creates a heap with the configured allocation threshold. + pub(crate) const fn new(collection_threshold: usize) -> Self { + Self { objects: Arena::new(collection_threshold), directory: Vec::new(), pinned: RefCell::new(Vec::new()) } + } + + #[inline] + /// Resolves a compact reference to its generation-checked arena handle. + pub(crate) fn handle(&self, value: ValueRef) -> Option { + let key = value.addr()?; + let index = self.directory.binary_search_by_key(&key, |entry| entry.0).ok()?; + Some(self.directory[index].1) + } + + #[inline] + pub(crate) fn get(&self, value: ValueRef) -> Option<&GcObject> { + self.objects.get(self.handle(value)?) + } + + #[inline] + pub(crate) fn get_mut(&mut self, value: ValueRef) -> Option<&mut GcObject> { + self.objects.get_mut(self.handle(value)?) + } + + /// Allocates an object and returns its compact runtime reference. + pub(crate) fn alloc( + &mut self, + type_addr: TypeAddr, + values: Vec, + trace_references: bool, + ) -> Result { + let key = + NEXT_GC_REF.try_update(Ordering::Relaxed, Ordering::Relaxed, |key| (key < (1 << 30)).then_some(key + 1)); + let Ok(key) = key else { + return Err(AllocError); + }; + let references = if trace_references { + let mut references = Vec::new(); + references.try_reserve_exact(values.len()).map_err(|_| AllocError)?; + references.extend(values.iter().map(|value| match value { + TinyWasmValue::ValueRef(value) => self.handle(*value), + _ => None, + })); + Some(references.into_boxed_slice()) + } else { + None + }; + let element_size = size_of::() + if trace_references { size_of::>() } else { 0 }; + let out_of_line_bytes = values.len().checked_mul(element_size).ok_or(AllocError)?; + let object = GcObject { type_addr, values: values.into_boxed_slice(), references }; + self.directory.try_reserve(1).map_err(|_| AllocError)?; + let handle = self.objects.alloc(object, out_of_line_bytes)?; + self.directory.push((key, handle)); + Ok(ValueRef::from_category_addr(key)) + } + + pub(crate) fn set(&mut self, object: ValueRef, index: usize, value: TinyWasmValue) -> Option<()> { + let reference = value.as_ref().and_then(|value| self.handle(value)); + let object = self.get_mut(object)?; + *object.values.get_mut(index)? = value; + if let Some(references) = &mut object.references { + references[index] = reference; + } + Some(()) + } + + /// Replaces a contiguous range and updates its traced references. + pub(crate) fn set_slice(&mut self, object: ValueRef, index: usize, values: &[TinyWasmValue]) -> Option<()> { + let object_handle = self.handle(object)?; + let end = index.checked_add(values.len())?; + let directory = &self.directory; + let object = self.objects.get_mut(object_handle)?; + object.values.get_mut(index..end)?.copy_from_slice(values); + if let Some(references) = &mut object.references { + for (reference, value) in references[index..end].iter_mut().zip(values) { + *reference = value.as_ref().and_then(|value| { + let key = value.addr()?; + let index = directory.binary_search_by_key(&key, |entry| entry.0).ok()?; + Some(directory[index].1) + }); + } + } + Some(()) + } + + /// Fills a contiguous range and updates its traced references. + pub(crate) fn fill( + &mut self, + object: ValueRef, + range: core::ops::Range, + value: TinyWasmValue, + ) -> Option<()> { + let reference = value.as_ref().and_then(|value| self.handle(value)); + let object = self.get_mut(object)?; + object.values.get_mut(range.clone())?.fill(value); + if let Some(references) = &mut object.references { + references[range].fill(reference); + } + Some(()) + } + + pub(crate) fn should_collect(&self, value_count: usize, trace_references: bool) -> bool { + let element_size = size_of::() + if trace_references { size_of::>() } else { 0 }; + let bytes = value_count.saturating_mul(element_size); + self.objects.should_collect(bytes) + } + + /// Reclaims objects unreachable from runtime and permanent host roots. + pub(crate) fn collect(&mut self, roots: impl IntoIterator) -> Result<(), AllocError> { + let pinned = self.pinned.borrow(); + let directory = &self.directory; + let root_handles = roots.into_iter().filter_map(|value| { + let key = value.addr()?; + Some(directory.get(directory.binary_search_by_key(&key, |entry| entry.0).ok()?)?.1) + }); + self.objects.collect(pinned.iter().copied().chain(root_handles))?; + drop(pinned); + self.directory.retain(|(_, handle)| self.objects.get(*handle).is_some()); + Ok(()) + } + + /// Permanently roots a managed reference exposed through the copyable host API. + pub(crate) fn pin(&self, value: ValueRef) { + if let Some(handle) = self.handle(value) { + let mut pinned = self.pinned.borrow_mut(); + if let Err(index) = pinned.binary_search(&handle) { + pinned.insert(index, handle); + } + } + } + + pub(crate) fn copy_within(&mut self, object: ValueRef, src: core::ops::Range, dst: usize) -> Option<()> { + let object = self.get_mut(object)?; + object.values.copy_within(src.clone(), dst); + if let Some(references) = &mut object.references { + references.copy_within(src, dst); + } + Some(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compact_keys_are_not_reused_after_collection() { + let mut heap = GcHeap::default(); + let stale = heap.alloc(0, Vec::new(), false).unwrap(); + heap.collect([]).unwrap(); + let current = heap.alloc(0, Vec::new(), false).unwrap(); + + assert_ne!(stale, current); + assert!(heap.get(stale).is_none()); + assert!(heap.get(current).is_some()); + } + + #[test] + fn compact_keys_do_not_resolve_in_another_heap() { + let mut first = GcHeap::default(); + let second = GcHeap::default(); + let value = first.alloc(0, Vec::new(), false).unwrap(); + + assert!(second.get(value).is_none()); + } + + #[test] + fn collection_reclaims_object_cycles() { + let mut heap = GcHeap::default(); + let first = heap.alloc(0, alloc::vec![TinyWasmValue::ValueRef(ValueRef::NULL)], true).unwrap(); + let second = heap.alloc(0, alloc::vec![TinyWasmValue::ValueRef(first)], true).unwrap(); + heap.set(first, 0, TinyWasmValue::ValueRef(second)).unwrap(); + + heap.collect([]).unwrap(); + + assert!(heap.get(first).is_none()); + assert!(heap.get(second).is_none()); + } +} diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 517a561..84c4033 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,4 +1,3 @@ -use alloc::sync::Arc; use alloc::{boxed::Box, format, vec::Vec}; use core::hint::cold_path; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -11,36 +10,29 @@ use crate::{Engine, Error, ModuleInstance, Result, Trap}; mod data; mod element; mod function; +mod gc; mod global; mod memory; +mod state; mod table; +mod types; +pub(crate) use gc::{decode_data, default_value, pop_value, push_value}; pub use memory::{LazyLinearMemory, LinearMemory, MemoryBackend, PagedMemory, VecMemory}; pub(crate) use memory::{MemValue, MemoryInstance}; +pub(crate) use state::State; +pub(crate) use types::{canonicalize_ref_type, canonicalize_value_type}; pub(crate) use {data::*, element::*, function::*, global::*, table::*}; // global store id counter static STORE_ID: AtomicUsize = AtomicUsize::new(0); -pub(crate) fn canonicalize_ref_type(ty: RefType, type_addrs: &[TypeAddr]) -> RefType { - let Some(type_addr) = ty.type_index() else { return ty }; - let canonical = - *type_addrs.get(type_addr as usize).unwrap_or_else(|| unreachable!("invalid type address: {type_addr}")); - RefType::new_concrete(ty.is_nullable(), canonical).unwrap() -} - -pub(crate) fn canonicalize_value_type(ty: WasmType, type_addrs: &[TypeAddr]) -> WasmType { - match ty { - WasmType::Ref(ty) => WasmType::Ref(canonicalize_ref_type(ty, type_addrs)), - ty => ty, - } -} - /// Global state that can be manipulated by WebAssembly programs /// -/// Note that the state doesn't do any garbage collection - so it will grow -/// indefinitely if you keep adding modules to it. When calling temporary -/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler). +/// Managed WebAssembly GC objects are collected automatically. Other Store +/// instances, such as modules, functions, memories, and tables, live until the +/// Store is dropped. GC references exposed through the copyable host value API +/// are retained for the Store's lifetime. /// /// ## Example /// ```rust @@ -80,10 +72,11 @@ impl Store { /// Create a new store pub fn new(engine: Engine) -> Self { let id = STORE_ID.fetch_add(1, Ordering::Relaxed); + let state = State { gc: gc::GcHeap::new(engine.config().gc_collection_threshold), ..State::default() }; Self { id, module_instances: Vec::new(), - state: State::default(), + state, call_stack: CallStack::new(engine.config()), value_stack: ValueStack::new(engine.config()), engine, @@ -130,140 +123,6 @@ impl Default for Store { } } -#[derive(Default)] -/// Global state that can be manipulated by WebAssembly programs -/// -/// Data should only be addressable by the module that owns it -/// See -pub(crate) struct State { - // Concrete type indexes in store instances address this canonical type space. - canonical_types: Vec, - pub(crate) funcs: Vec, - pub(crate) tables: Vec, - pub(crate) memories: Vec, - pub(crate) globals: Vec, - pub(crate) elements: Vec, - pub(crate) data: Vec, -} - -impl State { - pub(crate) fn value_matches_type(&self, value: WasmValue, expected: WasmType) -> bool { - match (value, expected) { - (WasmValue::Ref(RefValue::Null), WasmType::Ref(expected)) => expected.is_nullable(), - (WasmValue::Ref(RefValue::Func(func)), WasmType::Ref(expected)) => { - self.funcs.get(func.addr() as usize).is_some_and(|func| match expected.type_index() { - Some(expected) => func.type_addr == expected, - None => matches!(expected.abstract_heap_type(), Some(AbstractHeapType::Func)), - }) - } - (_, WasmType::Ref(expected)) if expected.is_concrete() => false, - _ => value.matches_type(expected), - } - } - - #[inline] - pub(crate) fn get_func_type(&self, addr: FuncAddr) -> &FuncType { - let type_addr = self.get_func(addr).type_addr; - Self::get(&self.canonical_types, type_addr, "canonical type") - } - - #[inline] - pub(crate) fn get_type(&self, addr: TypeAddr) -> &FuncType { - Self::get(&self.canonical_types, addr, "canonical type") - } - fn get<'a, T>(items: &'a [T], addr: Addr, kind: &str) -> &'a T { - items.get(addr as usize).unwrap_or_else(|| unreachable!("invalid {kind} address: {addr}")) - } - - fn get_mut<'a, T>(items: &'a mut [T], addr: Addr, kind: &str) -> &'a mut T { - items.get_mut(addr as usize).unwrap_or_else(|| unreachable!("invalid {kind} address: {addr}")) - } - - fn get_disjoint_mut<'a, T>(items: &'a mut [T], addr: Addr, addr2: Addr, kind: &str) -> (&'a mut T, &'a mut T) { - let [item_a, item_b] = items - .get_disjoint_mut([addr as usize, addr2 as usize]) - .unwrap_or_else(|_| unreachable!("invalid {kind} addresses: {addr}, {addr2}")); - (item_a, item_b) - } - - /// Get the function at the actual index in the store - pub(crate) fn get_func(&self, addr: FuncAddr) -> &FunctionInstance { - Self::get(&self.funcs, addr, "function") - } - - /// Get a wasm function at the actual index in the store, panicking if it's a host function (which should be guaranteed by the validator) - pub(crate) fn get_wasm_func(&self, addr: FuncAddr) -> &WasmFunctionInstance { - match self.funcs.get(addr as usize).map(|func| &func.kind) { - Some(FunctionKind::Wasm(wasm_func)) => wasm_func, - _ => unreachable!("invalid wasm function address: {addr}"), - } - } - - /// Get the memory at the actual index in the store - pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { - Self::get(&self.memories, addr, "memory") - } - - /// Get the memory at the actual index in the store - pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { - Self::get_mut(&mut self.memories, addr, "memory") - } - - /// Get the memory at the actual index in the store - pub(crate) fn get_mems_mut(&mut self, addr: MemAddr, addr2: MemAddr) -> (&mut MemoryInstance, &mut MemoryInstance) { - Self::get_disjoint_mut(&mut self.memories, addr, addr2, "memory") - } - - /// Get the table at the actual index in the store - pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { - Self::get(&self.tables, addr, "table") - } - - /// Get the table at the actual index in the store - pub(crate) fn get_table_mut(&mut self, addr: TableAddr) -> &mut TableInstance { - Self::get_mut(&mut self.tables, addr, "table") - } - - /// Get two mutable tables at the actual index in the store - pub(crate) fn get_tables_mut( - &mut self, - addr: TableAddr, - addr2: TableAddr, - ) -> (&mut TableInstance, &mut TableInstance) { - Self::get_disjoint_mut(&mut self.tables, addr, addr2, "table") - } - - /// Get the data at the actual index in the store - pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { - Self::get_mut(&mut self.data, addr, "data") - } - - /// Get the element at the actual index in the store - pub(crate) fn get_elem_mut(&mut self, addr: ElemAddr) -> &mut ElementInstance { - Self::get_mut(&mut self.elements, addr, "element") - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { - Self::get(&self.globals, addr, "global") - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global_mut(&mut self, addr: GlobalAddr) -> &mut GlobalInstance { - Self::get_mut(&mut self.globals, addr, "global") - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { - self.get_global(addr).value - } - - /// Set the global at the actual index in the store - pub(crate) fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { - self.get_global_mut(addr).value = value; - } -} - impl Store { /// Get the store's ID (unique per process) pub fn id(&self) -> usize { @@ -290,71 +149,31 @@ impl Store { pub fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { self.state.set_global_val(addr, value); } -} - -// Linking related functions -impl Store { - pub(crate) fn register_module_types(&mut self, types: &[FuncType]) -> Box<[TypeAddr]> { - let mut type_addrs = Vec::with_capacity(types.len()); - for (local_addr, ty) in types.iter().enumerate() { - if let Some(addr) = self - .state - .canonical_types - .iter() - .position(|registered| ty.equivalent(types, registered, &self.state.canonical_types)) - { - type_addrs.push(addr as TypeAddr); - continue; - } - let addr = self.state.canonical_types.len(); - assert!(addr <= ((1 << 30) - 1), "too many canonical function types"); - type_addrs.push(addr as TypeAddr); - let canonicalize = |ty: WasmType| match ty { - WasmType::Ref(ty) if ty.is_concrete() => { - let module_ref = ty.type_index().unwrap() as usize; - // A singleton recursive group can refer to the type currently being registered. - let canonical = if module_ref == local_addr { - addr as TypeAddr - } else { - *type_addrs - .get(module_ref) - .unwrap_or_else(|| unreachable!("invalid forward type reference: {module_ref}")) - }; - WasmType::Ref(RefType::new_concrete(ty.is_nullable(), canonical).unwrap()) - } - ty => ty, - }; - let params = ty.params().iter().copied().map(canonicalize).collect::>(); - let results = ty.results().iter().copied().map(canonicalize).collect::>(); - self.state.canonical_types.push(FuncType::new(¶ms, &results)); - } - type_addrs.into_boxed_slice() - } - - pub(crate) fn register_host_type(&mut self, ty: &FuncType) -> TypeAddr { - if let Some(addr) = self.state.canonical_types.iter().position(|registered| ty == registered) { - return addr as TypeAddr; - } - let addr = self.state.canonical_types.len(); - assert!(addr <= ((1 << 30) - 1), "too many canonical function types"); - self.state.canonical_types.push(ty.clone()); - addr as TypeAddr + /// Returns whether a public value has the requested runtime type. + #[doc(hidden)] + pub fn value_matches_type(&self, value: WasmValue, ty: WasmType) -> bool { + self.state.value_matches_type(value, ty) } +} +impl Store { /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs( &mut self, - funcs: &[Arc], + funcs: &[alloc::sync::Arc], owner: ModuleInstanceId, type_addrs: &[TypeAddr], ) -> impl ExactSizeIterator { let start = self.state.funcs.len() as FuncAddr; debug_assert_eq!(funcs.len(), type_addrs.len()); - self.state.funcs.extend(funcs.iter().zip(type_addrs).map(|(func, &type_addr)| FunctionInstance { - type_addr, - kind: FunctionKind::Wasm(WasmFunctionInstance { func: func.clone(), owner }), - })); + for (func, &type_addr) in funcs.iter().zip(type_addrs) { + self.state.funcs.push(FunctionInstance { + type_addr, + gc: self.state.func_gc_metadata(type_addr), + kind: FunctionKind::Wasm(WasmFunctionInstance { func: func.clone(), owner }), + }); + } start..start + funcs.len() as FuncAddr } @@ -370,11 +189,11 @@ impl Store { self.state.tables.reserve_exact(tables.len()); for table in tables { let init = match &table.init { - Some(expr) => match self.eval_const(expr, global_addrs, func_addrs)? { - TinyWasmValue::ValueRef(value) => TableElement::from(value.addr()), + Some(expr) => match self.eval_const(expr, global_addrs, func_addrs, type_addrs)? { + TinyWasmValue::ValueRef(value) => value, _ => return Err(Error::other("table initializer is not a reference value")), }, - None => TableElement::Uninitialized, + None => ValueRef::NULL, }; let element_type = canonicalize_ref_type(table.ty.element_type, type_addrs); let ty = match table.ty.arch() { @@ -417,7 +236,7 @@ impl Store { out.extend(start..start + globals.len() as Addr); for global in globals { - let value = match self.eval_const(&global.init, out, func_addrs) { + let value = match self.eval_const(&global.init, out, func_addrs, type_addrs) { Ok(val) => val, Err(e) => { cold_path(); @@ -431,17 +250,23 @@ impl Store { Ok(()) } - fn elem_addr(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result> { + fn elem_value( + &mut self, + item: &ElementItem, + globals: &[Addr], + funcs: &[FuncAddr], + type_addrs: &[TypeAddr], + ) -> Result { match item { - ElementItem::Expr(expr) => match self.eval_const(expr, globals, funcs)? { - TinyWasmValue::ValueRef(v) => Ok(v.addr()), + ElementItem::Expr(expr) => match self.eval_const(expr, globals, funcs, type_addrs)? { + TinyWasmValue::ValueRef(value) => Ok(value), other => { cold_path(); Err(Error::Other(format!("expected ref type, got {other:?}"))) } }, ElementItem::Func(addr) => match funcs.get(*addr as usize) { - Some(func_addr) => Ok(Some(*func_addr)), + Some(func_addr) => Ok(ValueRef::from_category_addr(*func_addr)), None => { cold_path(); Err(Error::Other(format!( @@ -460,26 +285,31 @@ impl Store { func_addrs: &[FuncAddr], global_addrs: &[Addr], elements: &[Element], + type_addrs: &[TypeAddr], ) -> Result<(Box<[Addr]>, Option)> { let elem_count = self.state.elements.len(); - let mut elem_addrs = Vec::with_capacity(elem_count); + let mut elem_addrs = Vec::with_capacity(elements.len()); for (i, element) in elements.iter().enumerate() { - let init = element - .items - .iter() - .map(|item| Ok(TableElement::from(self.elem_addr(item, global_addrs, func_addrs)?))) - .collect::>>()?; + let elem_addr = self.state.elements.len(); + self.state.elements.push(ElementInstance { + items: Some(Vec::with_capacity(element.items.len())), + ty: canonicalize_ref_type(element.ty, type_addrs), + }); + for item in &element.items { + let value = self.elem_value(item, global_addrs, func_addrs, type_addrs)?; + self.state.elements[elem_addr].items.as_mut().unwrap().push(value); + } - let items = match &element.kind { + match &element.kind { // doesn't need to be initialized, can be initialized lazily using the `table.init` instruction - ElementKind::Passive => Some(init), + ElementKind::Passive => {} // this one is not available to the runtime but needs to be initialized to declare references - ElementKind::Declared => None, // a. Execute the instruction elm.drop i + ElementKind::Declared => self.state.elements[elem_addr].drop(), // this one is active, so we need to initialize it (essentially a `table.init` instruction) ElementKind::Active { offset, table } => { - let offset = self.eval_size_const(offset, global_addrs, func_addrs)?; + let offset = self.eval_size_const(offset, global_addrs, func_addrs, type_addrs)?; let table_addr = table_addrs .get(*table as usize) .copied() @@ -497,20 +327,26 @@ impl Store { let Ok(offset) = usize::try_from(offset) else { return Ok(( elem_addrs.into_boxed_slice(), - Some(Trap::TableOutOfBounds { offset: usize::MAX, len: init.len(), max: table.size() }), + Some(Trap::TableOutOfBounds { + offset: usize::MAX, + len: self.state.elements[elem_addr].items.as_ref().unwrap().len(), + max: table.size(), + }), )); }; - if let Err(trap) = table.init(offset, &init) { + let State { elements, tables, .. } = &mut self.state; + let init = elements[elem_addr].items.as_deref().unwrap(); + let table = &mut tables[table_addr as usize]; + if let Err(trap) = table.init(offset, init) { return Ok((elem_addrs.into_boxed_slice(), Some(trap))); } // f. Execute the instruction elm.drop i - None + elements[elem_addr].drop(); } - }; + } - self.state.elements.push(ElementInstance { items }); elem_addrs.push((i + elem_count) as Addr); } @@ -525,6 +361,7 @@ impl Store { global_addrs: &[Addr], func_addrs: &[FuncAddr], data: &[Data], + type_addrs: &[TypeAddr], ) -> Result<(Box<[Addr]>, Option)> { let data_count = self.state.data.len(); let mut data_addrs = Vec::with_capacity(data_count); @@ -535,7 +372,7 @@ impl Store { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; - let offset = self.eval_size_const(offset, global_addrs, func_addrs)?; + let offset = self.eval_size_const(offset, global_addrs, func_addrs, type_addrs)?; let Some(mem) = self.state.memories.get_mut(*mem_addr as usize) else { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; @@ -573,12 +410,13 @@ impl Store { /// Evaluate a constant expression that's either a i32 or a i64 as a global or a const instruction fn eval_size_const( - &self, + &mut self, const_instrs: &[tinywasm_types::ConstInstruction], module_global_addrs: &[Addr], module_func_addrs: &[FuncAddr], + module_type_addrs: &[TypeAddr], ) -> Result { - let value = self.eval_const(const_instrs, module_global_addrs, module_func_addrs)?; + let value = self.eval_const(const_instrs, module_global_addrs, module_func_addrs, module_type_addrs)?; match value { TinyWasmValue::Value32(i) => Ok(u64::from(i)), TinyWasmValue::Value64(i) => Ok(i), @@ -589,61 +427,56 @@ impl Store { /// Evaluate a constant expression #[inline] fn eval_const( - &self, + &mut self, const_instrs: &[tinywasm_types::ConstInstruction], module_global_addrs: &[Addr], module_func_addrs: &[FuncAddr], + module_type_addrs: &[TypeAddr], ) -> Result { use tinywasm_types::ConstInstruction::*; - let resolve_global = |idx: u32| -> Result { - let Some(addr) = module_global_addrs.get(idx as usize) else { - cold_path(); - return Err(Error::Other(format!( - "global {idx} not found. This should have been caught by the validator" - ))); - }; - - let Some(global) = self.state.globals.get(*addr as usize) else { - cold_path(); - return Err(Error::Other(format!("global {addr} not found"))); - }; - - Ok(global.value) + let global_value = |state: &State, index: u32| -> Result { + let addr = *module_global_addrs + .get(index as usize) + .ok_or_else(|| Error::Other(format!("global {index} not found")))?; + Ok(state.globals.get(addr as usize).ok_or_else(|| Error::Other(format!("global {addr} not found")))?.value) }; - - let resolve_func = |idx: u32| -> Result { - match module_func_addrs.get(idx as usize) { - Some(func_addr) => Ok(*func_addr), - None => { - cold_path(); - Err(Error::Other(format!( - "function {idx} not found. This should have been caught by the validator" - ))) - } + let func_ref = |index: u32| -> Result { + let addr = *module_func_addrs + .get(index as usize) + .ok_or_else(|| Error::Other(format!("function {index} not found")))?; + Ok(ValueRef::from_category_addr(addr)) + }; + let type_addr = |index: TypeAddr| -> Result { + module_type_addrs.get(index as usize).copied().ok_or_else(|| Error::other("GC constant type not found")) + }; + let pop_value = |stack: &mut Vec, storage: StorageType| -> Result { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + match (storage, value) { + (StorageType::I8, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u8 as u32)), + (StorageType::I16, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u16 as u32)), + (StorageType::Value(WasmType::I32 | WasmType::F32), value @ TinyWasmValue::Value32(_)) + | (StorageType::Value(WasmType::I64 | WasmType::F64), value @ TinyWasmValue::Value64(_)) + | (StorageType::Value(WasmType::V128), value @ TinyWasmValue::Value128(_)) + | (StorageType::Value(WasmType::Ref(_)), value @ TinyWasmValue::ValueRef(_)) => Ok(value), + _ => Err(Error::other("type mismatch in GC constant")), } }; - if const_instrs.len() == 1 { - let val = match &const_instrs[0] { - F32Const(f) => (*f).into(), - F64Const(f) => (*f).into(), - I32Const(i) => (*i).into(), - I64Const(i) => (*i).into(), - V128Const(i) => (*i).into(), - GlobalGet(addr) => resolve_global(*addr)?, - Ref(tinywasm_types::RefValue::Null) => TinyWasmValue::ValueRef(ValueRef::NULL), + if let [instr] = const_instrs { + match instr { + I32Const(value) => return Ok(TinyWasmValue::Value32(*value as u32)), + I64Const(value) => return Ok(TinyWasmValue::Value64(*value as u64)), + F32Const(value) => return Ok(TinyWasmValue::Value32(value.to_bits())), + F64Const(value) => return Ok(TinyWasmValue::Value64(value.to_bits())), + V128Const(value) => return Ok(TinyWasmValue::Value128((*value).into())), + GlobalGet(index) => return global_value(&self.state, *index), + Ref(tinywasm_types::RefValue::Null) => return Ok(TinyWasmValue::ValueRef(ValueRef::NULL)), Ref(tinywasm_types::RefValue::Func(func)) => { - TinyWasmValue::ValueRef(ValueRef::from_raw(resolve_func(func.addr())?)) - } - Ref(_) => return Err(Error::other("unsupported reference constant")), - _ => { - cold_path(); - return Err(Error::other("unsupported const instruction")); + return Ok(TinyWasmValue::ValueRef(func_ref(func.addr())?)); } - }; - - return Ok(val); + _ => {} + } } let mut stack = Vec::new(); @@ -654,15 +487,96 @@ impl Store { F32Const(f) => stack.push(TinyWasmValue::Value32(f.to_bits())), F64Const(f) => stack.push(TinyWasmValue::Value64(f.to_bits())), V128Const(i) => stack.push(TinyWasmValue::Value128((*i).into())), - GlobalGet(addr) => stack.push(resolve_global(*addr)?), + GlobalGet(index) => stack.push(global_value(&self.state, *index)?), Ref(tinywasm_types::RefValue::Null) => stack.push(TinyWasmValue::ValueRef(ValueRef::NULL)), Ref(tinywasm_types::RefValue::Func(func)) => { - stack.push(TinyWasmValue::ValueRef(ValueRef::from_raw(resolve_func(func.addr())?))) + stack.push(TinyWasmValue::ValueRef(func_ref(func.addr())?)) } Ref(_) => { cold_path(); return Err(Error::other("unsupported reference constant")); } + RefI31 => { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + let TinyWasmValue::Value32(value) = value else { + return Err(Error::other("type mismatch in const ref.i31")); + }; + stack.push(TinyWasmValue::ValueRef(ValueRef::from_i31(value as i32))); + } + AnyConvertExtern | ExternConvertAny => { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + if !matches!(value, TinyWasmValue::ValueRef(_)) { + return Err(Error::other("type mismatch in const reference conversion")); + } + stack.push(value); + } + StructNew(type_index) | StructNewDefault(type_index) => { + let type_addr = type_addr(*type_index)?; + let fields = self + .state + .get_type(type_addr) + .as_struct() + .ok_or_else(|| Error::other("GC constant type is not a struct"))? + .fields + .clone(); + let default = matches!(instr, StructNewDefault(_)); + let mut values = Vec::new(); + values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; + if default { + values.extend(fields.iter().map(|field| default_value(field.storage))); + } else { + for field in fields.iter().rev() { + values.push(pop_value(&mut stack, field.storage)?); + } + values.reverse(); + } + let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); + let reference = self.state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + } + ArrayNew(type_index) | ArrayNewDefault(type_index) => { + let type_addr = type_addr(*type_index)?; + let storage = self + .state + .get_type(type_addr) + .as_array() + .ok_or_else(|| Error::other("GC constant type is not an array"))? + .field + .storage; + let Some(TinyWasmValue::Value32(len)) = stack.pop() else { + return Err(Error::other("type mismatch in const array length")); + }; + let value = if matches!(instr, ArrayNewDefault(_)) { + default_value(storage) + } else { + pop_value(&mut stack, storage)? + }; + let mut values = Vec::new(); + values.try_reserve_exact(len as usize).map_err(|_| Trap::OutOfMemory)?; + values.resize(len as usize, value); + let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); + let reference = self.state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + } + ArrayNewFixed(type_index, len) => { + let type_addr = type_addr(*type_index)?; + let storage = self + .state + .get_type(type_addr) + .as_array() + .ok_or_else(|| Error::other("GC constant type is not an array"))? + .field + .storage; + let mut values = Vec::new(); + values.try_reserve_exact(*len as usize).map_err(|_| Trap::OutOfMemory)?; + for _ in 0..*len { + values.push(pop_value(&mut stack, storage)?); + } + values.reverse(); + let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); + let reference = self.state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + } I32Add | I32Sub | I32Mul => { let rhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; let lhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs new file mode 100644 index 0000000..125cc30 --- /dev/null +++ b/crates/tinywasm/src/store/state.rs @@ -0,0 +1,385 @@ +use alloc::vec::Vec; + +use super::*; + +#[derive(Default)] +/// Global state that can be manipulated by WebAssembly programs +/// +/// Data should only be addressable by the module that owns it +/// See +pub(crate) struct State { + // Concrete type indexes in store instances address this canonical type space. + pub(crate) canonical_types: Vec, + pub(crate) canonical_rec_group_lengths: Vec, + pub(crate) funcs: Vec, + pub(crate) tables: Vec, + pub(crate) memories: Vec, + pub(crate) globals: Vec, + pub(crate) elements: Vec, + pub(crate) data: Vec, + pub(crate) gc: gc::GcHeap, +} + +impl State { + /// Returns whether values of this type can contain a managed GC object. + pub(crate) fn type_may_contain_gc(&self, ty: WasmType) -> bool { + Self::type_may_contain_gc_in(&self.canonical_types, ty) + } + + fn type_may_contain_gc_in(types: &[SubType], ty: WasmType) -> bool { + let WasmType::Ref(ty) = ty else { return false }; + if let Some(type_addr) = ty.type_index() { + return matches!(types[type_addr as usize].composite, CompositeType::Struct(_) | CompositeType::Array(_)); + } + matches!( + ty.abstract_heap_type(), + Some( + AbstractHeapType::Any + | AbstractHeapType::Eq + | AbstractHeapType::Struct + | AbstractHeapType::Array + | AbstractHeapType::Extern + ) + ) + } + + /// Precomputes whether a canonical function signature can carry GC objects. + pub(crate) fn func_gc_metadata(&self, type_addr: TypeAddr) -> FunctionGcMetadata { + let ty = self.get_canonical_func_type(type_addr); + FunctionGcMetadata { + params: ty.params().iter().copied().any(|ty| self.type_may_contain_gc(ty)), + results: ty.results().iter().copied().any(|ty| self.type_may_contain_gc(ty)), + } + } + + /// Allocates an object, collecting from all runtime roots when needed. + pub(crate) fn alloc_gc_object( + &mut self, + type_addr: TypeAddr, + values: Vec, + stack_32: impl IntoIterator, + ) -> Result { + let trace_references = match &self.get_type(type_addr).composite { + CompositeType::Struct(ty) => { + ty.fields.iter().any(|field| matches!(field.storage, StorageType::Value(WasmType::Ref(_)))) + } + CompositeType::Array(ty) => matches!(ty.field.storage, StorageType::Value(WasmType::Ref(_))), + CompositeType::Func(_) => unreachable!("GC object type is not a function"), + }; + if self.gc.should_collect(values.len(), trace_references) { + let canonical_types = &self.canonical_types; + let roots = stack_32 + .into_iter() + .map(ValueRef::from_raw) + .chain( + self.globals + .iter() + .filter(|global| Self::type_may_contain_gc_in(canonical_types, global.ty.ty)) + .filter_map(|global| global.value.as_ref()), + ) + .chain( + self.tables + .iter() + .filter(|table| { + Self::type_may_contain_gc_in(canonical_types, WasmType::Ref(table.kind.element_type)) + }) + .flat_map(|table| table.elements.iter().copied()), + ) + .chain( + self.elements + .iter() + .filter(|element| Self::type_may_contain_gc_in(canonical_types, WasmType::Ref(element.ty))) + .flat_map(|element| element.items.iter().flatten().copied()), + ) + .chain(values.iter().filter_map(|value| value.as_ref())); + self.gc.collect(roots).map_err(|_| Trap::OutOfMemory)?; + } + self.gc.alloc(type_addr, values, trace_references).map_err(|_| Trap::OutOfMemory) + } + + /// Pins a host-visible reference when it resolves to a managed GC object. + pub(crate) fn pin_host_ref(&self, value: RefValue) { + let raw = match value { + RefValue::Any(value) => value.raw(), + RefValue::Extern(value) => value.raw(), + RefValue::Null | RefValue::Func(_) | RefValue::Exn(_) => return, + }; + self.gc.pin(ValueRef::from_raw(raw)); + } + + /// Pins GC references that have crossed into host-visible values. + pub(crate) fn pin_host_values(&self, values: &[WasmValue]) { + for &value in values { + if let WasmValue::Ref(value) = value { + self.pin_host_ref(value); + } + } + } + + /// Resolves a non-null object of the expected canonical type. + pub(crate) fn gc_object(&self, reference: ValueRef, expected_type: TypeAddr) -> Result<&gc::GcObject, Trap> { + if reference.is_null() { + return Err(if self.get_type(expected_type).as_array().is_some() { + Trap::NullArrayReference + } else { + Trap::NullStructReference + }); + } + let object = self.gc.get(reference).ok_or(Trap::Other("invalid GC reference"))?; + if !self.type_addr_is_subtype(object.type_addr, expected_type) { + return Err(Trap::Other("GC reference type mismatch")); + } + Ok(object) + } + + /// Converts an internal value using canonical reference type information. + pub(crate) fn attach_value(&self, value: TinyWasmValue, ty: WasmType) -> Option { + if let (TinyWasmValue::ValueRef(value), WasmType::Ref(reference_type)) = (value, ty) + && let Some(type_addr) = reference_type.type_index() + { + if value.is_null() { + return Some(RefValue::Null.into()); + } + return match &self.get_type(type_addr).composite { + CompositeType::Func(_) => Some(RefValue::Func(FuncRef::new(value.addr()?)).into()), + CompositeType::Struct(_) | CompositeType::Array(_) => { + Some(RefValue::Any(AnyRef::from_raw(value.raw())).into()) + } + }; + } + value.attach_type(ty) + } + + /// Returns whether one canonical type is a subtype of another. + pub(crate) fn type_addr_is_subtype(&self, mut actual: TypeAddr, expected: TypeAddr) -> bool { + loop { + if actual == expected { + return true; + } + let Some(supertype) = self.get_type(actual).supertype else { return false }; + actual = supertype; + } + } + + /// Returns whether one reference type is a subtype of another. + pub(crate) fn ref_type_is_subtype(&self, actual: RefType, expected: RefType) -> bool { + if actual.is_nullable() && !expected.is_nullable() { + return false; + } + self.heap_type_is_subtype(actual, expected) + } + + /// Returns whether one value type is a subtype of another. + pub(crate) fn value_type_is_subtype(&self, actual: WasmType, expected: WasmType) -> bool { + match (actual, expected) { + (WasmType::Ref(actual), WasmType::Ref(expected)) => self.ref_type_is_subtype(actual, expected), + _ => actual == expected, + } + } + + fn heap_type_is_subtype(&self, actual: RefType, expected: RefType) -> bool { + if let Some(expected_addr) = expected.type_index() { + let Some(actual_addr) = actual.type_index() else { + return matches!( + (actual.abstract_heap_type(), &self.get_type(expected_addr).composite), + (Some(AbstractHeapType::NoFunc), CompositeType::Func(_)) + | (Some(AbstractHeapType::None), CompositeType::Struct(_) | CompositeType::Array(_)) + ); + }; + return self.type_addr_is_subtype(actual_addr, expected_addr); + } + + let expected = expected.abstract_heap_type().expect("abstract reference type"); + if let Some(actual_addr) = actual.type_index() { + return match &self.get_type(actual_addr).composite { + CompositeType::Func(_) => expected == AbstractHeapType::Func, + CompositeType::Struct(_) => { + matches!(expected, AbstractHeapType::Struct | AbstractHeapType::Eq | AbstractHeapType::Any) + } + CompositeType::Array(_) => { + matches!(expected, AbstractHeapType::Array | AbstractHeapType::Eq | AbstractHeapType::Any) + } + }; + } + + let actual = actual.abstract_heap_type().expect("abstract reference type"); + actual == expected + || match actual { + AbstractHeapType::None => matches!( + expected, + AbstractHeapType::I31 + | AbstractHeapType::Struct + | AbstractHeapType::Array + | AbstractHeapType::Eq + | AbstractHeapType::Any + ), + AbstractHeapType::I31 | AbstractHeapType::Struct | AbstractHeapType::Array => { + matches!(expected, AbstractHeapType::Eq | AbstractHeapType::Any) + } + AbstractHeapType::Eq => expected == AbstractHeapType::Any, + AbstractHeapType::NoFunc => expected == AbstractHeapType::Func, + AbstractHeapType::NoExtern => expected == AbstractHeapType::Extern, + AbstractHeapType::NoExn => expected == AbstractHeapType::Exn, + _ => false, + } + } + + /// Returns whether a runtime reference has the expected type. + pub(crate) fn value_ref_matches(&self, value: ValueRef, expected: RefType) -> bool { + if value.is_null() { + return expected.is_nullable(); + } + if expected.abstract_heap_type() == Some(AbstractHeapType::Extern) { + return true; + } + let expected_func = expected.type_index().is_some_and(|addr| self.get_type(addr).as_func().is_some()) + || expected.abstract_heap_type() == Some(AbstractHeapType::Func); + if expected_func { + let Some(func_addr) = value.addr() else { return false }; + let Some(func) = self.funcs.get(func_addr as usize) else { return false }; + let actual = RefType::new_concrete(false, func.type_addr).expect("canonical type fits"); + return self.ref_type_is_subtype(actual, expected); + } + if value.is_i31() { + let actual = RefType::new_abstract(false, AbstractHeapType::I31); + return self.ref_type_is_subtype(actual, expected); + } + if value.is_host_any() { + let actual = RefType::new_abstract(false, AbstractHeapType::Any); + return self.ref_type_is_subtype(actual, expected); + } + + let Some(object) = self.gc.get(value) else { return false }; + let actual = RefType::new_concrete(false, object.type_addr).expect("canonical type fits"); + self.ref_type_is_subtype(actual, expected) + } + + #[inline] + pub(crate) fn get_func_type(&self, addr: FuncAddr) -> &FuncType { + self.get_canonical_func_type(self.get_func(addr).type_addr) + } + + #[inline] + pub(crate) fn get_type(&self, addr: TypeAddr) -> &SubType { + Self::get(&self.canonical_types, addr, "canonical type") + } + + #[inline] + pub(crate) fn get_canonical_func_type(&self, addr: TypeAddr) -> &FuncType { + self.get_type(addr).as_func().expect("validated function address references a function type") + } + + pub(crate) fn value_matches_type(&self, value: WasmValue, expected: WasmType) -> bool { + match (value, expected) { + (WasmValue::Ref(RefValue::Null), WasmType::Ref(expected)) => expected.is_nullable(), + (WasmValue::Ref(RefValue::Func(func)), WasmType::Ref(expected)) => { + self.funcs.get(func.addr() as usize).is_some_and(|func| { + let actual = RefType::new_concrete(false, func.type_addr).expect("canonical type fits"); + self.ref_type_is_subtype(actual, expected) + }) + } + (WasmValue::Ref(RefValue::Any(_)), WasmType::Ref(expected)) + if expected.is_func() || expected.is_extern() || expected.is_exn() => + { + false + } + (WasmValue::Ref(RefValue::Any(value)), WasmType::Ref(expected)) => { + self.value_ref_matches(ValueRef::from_raw(value.raw()), expected) + } + (_, WasmType::Ref(expected)) if expected.is_concrete() => false, + _ => value.matches_type(expected), + } + } + + pub(super) fn get<'a, T>(items: &'a [T], addr: Addr, kind: &str) -> &'a T { + items.get(addr as usize).unwrap_or_else(|| unreachable!("invalid {kind} address: {addr}")) + } + + fn get_mut<'a, T>(items: &'a mut [T], addr: Addr, kind: &str) -> &'a mut T { + items.get_mut(addr as usize).unwrap_or_else(|| unreachable!("invalid {kind} address: {addr}")) + } + + fn get_disjoint_mut<'a, T>(items: &'a mut [T], addr: Addr, addr2: Addr, kind: &str) -> (&'a mut T, &'a mut T) { + let [item_a, item_b] = items + .get_disjoint_mut([addr as usize, addr2 as usize]) + .unwrap_or_else(|_| unreachable!("invalid {kind} addresses: {addr}, {addr2}")); + (item_a, item_b) + } + + /// Get the function at the actual index in the store + pub(crate) fn get_func(&self, addr: FuncAddr) -> &FunctionInstance { + Self::get(&self.funcs, addr, "function") + } + + /// Get a wasm function at the actual index in the store, panicking if it's a host function (which should be guaranteed by the validator) + pub(crate) fn get_wasm_func(&self, addr: FuncAddr) -> &WasmFunctionInstance { + match self.funcs.get(addr as usize).map(|func| &func.kind) { + Some(FunctionKind::Wasm(wasm_func)) => wasm_func, + _ => unreachable!("invalid wasm function address: {addr}"), + } + } + + /// Get the memory at the actual index in the store + pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { + Self::get(&self.memories, addr, "memory") + } + + /// Get the memory at the actual index in the store + pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { + Self::get_mut(&mut self.memories, addr, "memory") + } + + /// Get the memory at the actual index in the store + pub(crate) fn get_mems_mut(&mut self, addr: MemAddr, addr2: MemAddr) -> (&mut MemoryInstance, &mut MemoryInstance) { + Self::get_disjoint_mut(&mut self.memories, addr, addr2, "memory") + } + + /// Get the table at the actual index in the store + pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { + Self::get(&self.tables, addr, "table") + } + + /// Get the table at the actual index in the store + pub(crate) fn get_table_mut(&mut self, addr: TableAddr) -> &mut TableInstance { + Self::get_mut(&mut self.tables, addr, "table") + } + + /// Get two mutable tables at the actual index in the store + pub(crate) fn get_tables_mut( + &mut self, + addr: TableAddr, + addr2: TableAddr, + ) -> (&mut TableInstance, &mut TableInstance) { + Self::get_disjoint_mut(&mut self.tables, addr, addr2, "table") + } + + /// Get the data at the actual index in the store + pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { + Self::get_mut(&mut self.data, addr, "data") + } + + /// Get the element at the actual index in the store + pub(crate) fn get_elem_mut(&mut self, addr: ElemAddr) -> &mut ElementInstance { + Self::get_mut(&mut self.elements, addr, "element") + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { + Self::get(&self.globals, addr, "global") + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global_mut(&mut self, addr: GlobalAddr) -> &mut GlobalInstance { + Self::get_mut(&mut self.globals, addr, "global") + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { + self.get_global(addr).value + } + + /// Set the global at the actual index in the store + pub(crate) fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { + self.get_global_mut(addr).value = value; + } +} diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index 68ed99f..ece0001 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,4 +1,4 @@ -use crate::{Result, Trap}; +use crate::{Result, Trap, interpreter::ValueRef}; use alloc::vec::Vec; use core::ops::Range; use tinywasm_types::*; @@ -10,17 +10,17 @@ const MAX_TABLE_SIZE: usize = 10_000_000; /// See #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct TableInstance { - pub(crate) elements: Vec, + pub(crate) elements: Vec, pub(crate) kind: TableType, } impl TableInstance { #[cfg(test)] pub(crate) fn new(kind: TableType) -> Result { - Self::new_with_init(kind, TableElement::Uninitialized) + Self::new_with_init(kind, ValueRef::NULL) } - pub(crate) fn new_with_init(kind: TableType, init: TableElement) -> Result { + pub(crate) fn new_with_init(kind: TableType, init: ValueRef) -> Result { let size = usize::try_from(kind.size_initial).map_err(|_| Trap::OutOfMemory)?; if size > MAX_TABLE_SIZE { return Err(Trap::OutOfMemory.into()); @@ -45,27 +45,23 @@ impl TableInstance { Ok(addr..end) } - pub(crate) fn get_wasm_val(&self, addr: usize) -> Result { - Ok(self.get(addr)?.to_wasm_value(self.kind.element_type)) - } - - pub(crate) fn fill(&mut self, addr: usize, len: usize, val: TableElement) -> Result<(), Trap> { + pub(crate) fn fill(&mut self, addr: usize, len: usize, val: ValueRef) -> Result<(), Trap> { let range = self.checked_range(addr, len)?; self.elements[range].fill(val); Ok(()) } - pub(crate) fn get(&self, addr: usize) -> Result<&TableElement, Trap> { + pub(crate) fn get(&self, addr: usize) -> Result<&ValueRef, Trap> { self.elements.get(addr).ok_or_else(|| self.trap_oob(addr, 1)) } - pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[TableElement]) -> Result<(), Trap> { + pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[ValueRef]) -> Result<(), Trap> { let range = self.checked_range(dst, src.len())?; self.elements[range].copy_from_slice(src); Ok(()) } - pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[TableElement], Trap> { + pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[ValueRef], Trap> { Ok(&self.elements[self.checked_range(addr, len)?]) } @@ -76,13 +72,13 @@ impl TableInstance { Ok(()) } - pub(crate) fn set(&mut self, table_idx: usize, value: TableElement) -> Result<(), Trap> { + pub(crate) fn set(&mut self, table_idx: usize, value: ValueRef) -> Result<(), Trap> { let range = self.checked_range(table_idx, 1)?; self.elements[range.start] = value; Ok(()) } - pub(crate) fn grow(&mut self, n: usize, init: TableElement) -> Result<(), Trap> { + pub(crate) fn grow(&mut self, n: usize, init: ValueRef) -> Result<(), Trap> { let len = n.checked_add(self.elements.len()).ok_or(Trap::OutOfMemory)?; let declared_max = self.kind.size_max.and_then(|max| usize::try_from(max).ok()).unwrap_or(usize::MAX); let max = declared_max.min(MAX_TABLE_SIZE); @@ -99,52 +95,13 @@ impl TableInstance { self.elements.len() } - pub(crate) fn init(&mut self, offset: usize, init: &[TableElement]) -> Result<(), Trap> { + pub(crate) fn init(&mut self, offset: usize, init: &[ValueRef]) -> Result<(), Trap> { let range = self.checked_range(offset, init.len())?; self.elements[range].copy_from_slice(init); Ok(()) } } -#[derive(Clone, Copy)] -#[cfg_attr(feature = "debug", derive(Debug))] -pub(crate) enum TableElement { - Uninitialized, - Initialized(TableAddr), -} - -impl From> for TableElement { - fn from(addr: Option) -> Self { - match addr { - None => Self::Uninitialized, - Some(addr) => Self::Initialized(addr), - } - } -} - -impl TableElement { - pub(crate) fn addr(&self) -> Option { - match self { - Self::Uninitialized => None, - Self::Initialized(addr) => Some(*addr), - } - } - - pub(crate) fn to_wasm_value(self, ty: RefType) -> WasmValue { - let Some(addr) = self.addr() else { return RefValue::Null.into() }; - let value = if ty.is_func() { - RefValue::Func(FuncRef::new(addr)) - } else if ty.is_extern() { - RefValue::Extern(ExternRef::new(addr)) - } else if ty.is_exn() { - RefValue::Exn(ExnRef::new(addr)) - } else { - RefValue::Any(AnyRef::from_raw(addr)) - }; - value.into() - } -} - #[cfg(test)] mod tests { use super::*; @@ -162,43 +119,17 @@ mod tests { assert_eq!(table_instance.size() as u64, kind.size_initial, "Table instance creation failed: size mismatch"); } - #[test] - fn test_get_wasm_val() { - let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind).unwrap(); - - table_instance.set(0, TableElement::Initialized(0)).expect("Setting table element failed"); - table_instance.set(1, TableElement::Uninitialized).expect("Setting table element failed"); - - match table_instance.get_wasm_val(0) { - Ok(WasmValue::Ref(RefValue::Func(_))) => {} - _ => panic!("get_wasm_val failed to return the correct WasmValue"), - } - - match table_instance.get_wasm_val(1) { - Ok(WasmValue::Ref(RefValue::Null)) => {} - _ => panic!("get_wasm_val failed to return the correct WasmValue"), - } - - match table_instance.get_wasm_val(999) { - Err(Trap::TableOutOfBounds { .. }) => {} - _ => panic!("get_wasm_val failed to handle undefined element correctly"), - } - } - #[test] fn test_set_and_get() { let kind = dummy_table_type(); let mut table_instance = TableInstance::new(kind).unwrap(); - let result = table_instance.set(0, TableElement::Initialized(1)); + let value = ValueRef::from_raw(2); + let result = table_instance.set(0, value); assert!(result.is_ok(), "Setting table element failed"); let elem = table_instance.get(0); - assert!( - elem.is_ok() && matches!(elem.unwrap(), &TableElement::Initialized(1)), - "Getting table element failed or returned incorrect value" - ); + assert!(elem.is_ok() && elem.unwrap() == &value, "Getting table element failed or returned incorrect value"); } #[test] @@ -206,17 +137,14 @@ mod tests { let kind = dummy_table_type(); let mut table_instance = TableInstance::new(kind).unwrap(); - let init_elements = vec![TableElement::Initialized(0); 5]; + let init_elements = vec![ValueRef::from_raw(2); 5]; let result = table_instance.init(0, &init_elements); assert!(result.is_ok(), "Initializing table with elements failed"); for i in 0..5 { let elem = table_instance.get(i); - assert!( - elem.is_ok() && matches!(elem.unwrap(), &TableElement::Initialized(_)), - "Element not initialized correctly at index {i}" - ); + assert!(elem.is_ok() && !elem.unwrap().is_null(), "Element not initialized correctly at index {i}"); } } } diff --git a/crates/tinywasm/src/store/types.rs b/crates/tinywasm/src/store/types.rs new file mode 100644 index 0000000..7cee56b --- /dev/null +++ b/crates/tinywasm/src/store/types.rs @@ -0,0 +1,204 @@ +use alloc::{boxed::Box, vec::Vec}; +use tinywasm_types::*; + +use super::Store; + +pub(crate) fn canonicalize_ref_type(ty: RefType, type_addrs: &[TypeAddr]) -> RefType { + let Some(type_addr) = ty.type_index() else { return ty }; + let canonical = *type_addrs.get(type_addr as usize).expect("validated type address should exist"); + RefType::new_concrete(ty.is_nullable(), canonical).expect("canonical type addresses fit in references") +} + +pub(crate) fn canonicalize_value_type(ty: WasmType, type_addrs: &[TypeAddr]) -> WasmType { + match ty { + WasmType::Ref(ty) => WasmType::Ref(canonicalize_ref_type(ty, type_addrs)), + ty => ty, + } +} + +fn map_value_type(ty: WasmType, resolve: &mut impl FnMut(TypeAddr) -> TypeAddr) -> WasmType { + match ty { + WasmType::Ref(ty) if ty.is_concrete() => { + let addr = resolve(ty.type_index().expect("concrete reference has a type index")); + WasmType::Ref( + RefType::new_concrete(ty.is_nullable(), addr).expect("canonical type addresses fit in references"), + ) + } + ty => ty, + } +} + +fn map_field_type(field: FieldType, resolve: &mut impl FnMut(TypeAddr) -> TypeAddr) -> FieldType { + let storage = match field.storage { + StorageType::Value(ty) => StorageType::Value(map_value_type(ty, resolve)), + storage => storage, + }; + FieldType { storage, mutable: field.mutable } +} + +fn map_subtype(ty: &SubType, mut resolve: impl FnMut(TypeAddr) -> TypeAddr) -> SubType { + let supertype = ty.supertype.map(&mut resolve); + let composite = match &ty.composite { + CompositeType::Func(ty) => { + let params = ty.params().iter().copied().map(|ty| map_value_type(ty, &mut resolve)).collect::>(); + let results = ty.results().iter().copied().map(|ty| map_value_type(ty, &mut resolve)).collect::>(); + CompositeType::Func(FuncType::new(¶ms, &results)) + } + CompositeType::Struct(ty) => CompositeType::Struct(StructType { + fields: ty.fields.iter().copied().map(|field| map_field_type(field, &mut resolve)).collect(), + }), + CompositeType::Array(ty) => CompositeType::Array(ArrayType { field: map_field_type(ty.field, &mut resolve) }), + }; + SubType { is_final: ty.is_final, supertype, composite } +} + +fn subtypes_equal( + ty: &SubType, + registered: &SubType, + module_group: core::ops::Range, + canonical_group: core::ops::Range, + resolve: impl Fn(TypeAddr) -> TypeAddr + Copy, +) -> bool { + let type_addrs_equal = |ty: TypeAddr, registered: TypeAddr| { + module_group.contains(&(ty as usize)) == canonical_group.contains(&(registered as usize)) + && resolve(ty) == registered + }; + let values_equal = |ty: WasmType, registered: WasmType| match (ty, registered) { + (WasmType::Ref(ty), WasmType::Ref(registered)) if ty.is_concrete() => { + let ty_addr = ty.type_index().expect("concrete reference"); + let Some(registered_addr) = registered.type_index() else { return false }; + ty.is_nullable() == registered.is_nullable() && type_addrs_equal(ty_addr, registered_addr) + } + _ => ty == registered, + }; + let fields_equal = |ty: FieldType, registered: FieldType| { + ty.mutable == registered.mutable + && match (ty.storage, registered.storage) { + (StorageType::Value(ty), StorageType::Value(registered)) => values_equal(ty, registered), + (ty, registered) => ty == registered, + } + }; + + let supertypes_equal = match (ty.supertype, registered.supertype) { + (Some(ty), Some(registered)) => type_addrs_equal(ty, registered), + (None, None) => true, + _ => false, + }; + ty.is_final == registered.is_final + && supertypes_equal + && match (&ty.composite, ®istered.composite) { + (CompositeType::Func(ty), CompositeType::Func(registered)) => { + ty.params().len() == registered.params().len() + && ty.results().len() == registered.results().len() + && ty + .params() + .iter() + .chain(ty.results()) + .copied() + .zip(registered.params().iter().chain(registered.results()).copied()) + .all(|(ty, registered)| values_equal(ty, registered)) + } + (CompositeType::Struct(ty), CompositeType::Struct(registered)) => { + ty.fields.len() == registered.fields.len() + && ty + .fields + .iter() + .copied() + .zip(registered.fields.iter().copied()) + .all(|(ty, registered)| fields_equal(ty, registered)) + } + (CompositeType::Array(ty), CompositeType::Array(registered)) => fields_equal(ty.field, registered.field), + _ => false, + } +} + +impl Store { + pub(crate) fn register_module_types(&mut self, section: &TypeSection) -> Box<[TypeAddr]> { + let mut type_addrs = Vec::with_capacity(section.types.len()); + let mut module_group_start = 0usize; + + for &group_len in §ion.rec_group_lengths { + let group_len = group_len as usize; + let module_group_end = module_group_start.checked_add(group_len).expect("type group is too large"); + let group = section + .types + .get(module_group_start..module_group_end) + .expect("validated recursive group length fits the type section"); + + let resolve = |module_addr: TypeAddr, canonical_group_start: usize| { + let module_addr = module_addr as usize; + if (module_group_start..module_group_end).contains(&module_addr) { + (canonical_group_start + module_addr - module_group_start) as TypeAddr + } else { + *type_addrs + .get(module_addr) + .expect("validated type reference targets the current or a prior recursive group") + } + }; + + let mut canonical_group_start = 0; + let mut matching_group = None; + for &canonical_group_len in &self.state.canonical_rec_group_lengths { + let canonical_group_len = canonical_group_len as usize; + if canonical_group_len == group_len + && group.iter().zip(&self.state.canonical_types[canonical_group_start..]).all(|(ty, registered)| { + subtypes_equal( + ty, + registered, + module_group_start..module_group_end, + canonical_group_start..canonical_group_start + canonical_group_len, + |addr| resolve(addr, canonical_group_start), + ) + }) + { + matching_group = Some(canonical_group_start); + break; + } + canonical_group_start += canonical_group_len; + } + + let canonical_group_start = match matching_group { + Some(start) => start, + None => { + let start = self.state.canonical_types.len(); + assert!( + start.checked_add(group_len).is_some_and(|end| end <= (1 << 30)), + "too many canonical types" + ); + self.state + .canonical_types + .extend(group.iter().map(|ty| map_subtype(ty, |addr| resolve(addr, start)))); + self.state.canonical_rec_group_lengths.push(group_len as u32); + start + } + }; + type_addrs.extend((canonical_group_start..canonical_group_start + group_len).map(|addr| addr as TypeAddr)); + module_group_start = module_group_end; + } + debug_assert_eq!(module_group_start, section.types.len()); + type_addrs.into_boxed_slice() + } + + pub(crate) fn register_host_type(&mut self, ty: &FuncType) -> TypeAddr { + let mut group_start = 0usize; + for &group_len in &self.state.canonical_rec_group_lengths { + if group_len == 1 + && self.state.canonical_types[group_start].is_final + && self.state.canonical_types[group_start].supertype.is_none() + && self.state.canonical_types[group_start].as_func() == Some(ty) + { + return group_start as TypeAddr; + } + group_start += group_len as usize; + } + let addr = self.state.canonical_types.len(); + assert!(addr < (1 << 30), "too many canonical types"); + self.state.canonical_types.push(SubType { + is_final: true, + supertype: None, + composite: CompositeType::Func(ty.clone()), + }); + self.state.canonical_rec_group_lengths.push(1); + addr as TypeAddr + } +} diff --git a/crates/tinywasm/tests/gc_refs.rs b/crates/tinywasm/tests/gc_refs.rs new file mode 100644 index 0000000..d3f2994 --- /dev/null +++ b/crates/tinywasm/tests/gc_refs.rs @@ -0,0 +1,120 @@ +use tinywasm::types::{ExternRef, RefValue, WasmValue}; +use tinywasm::{Engine, ExecProgress, ModuleInstance, Store, engine::Config}; + +const MODULE: &str = r#" + (module + (type $node (struct (field i32))) + (type $bytes (array (mut i8))) + (func (export "new") (result anyref) + (struct.new $node (i32.const 42))) + (func (export "new-extern") (result externref) + (extern.convert_any (struct.new $node (i32.const 42)))) + (func (export "read") (param anyref) (result i32) + (struct.get $node 0 (ref.cast (ref $node) (local.get 0)))) + (func (export "read-extern") (param externref) (result i32) + (struct.get $node 0 + (ref.cast (ref $node) (any.convert_extern (local.get 0))))) + (func (export "churn") + (local $i i32) + (loop $loop + (drop (array.new_default $bytes (i32.const 16))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br_if $loop (i32.lt_u (local.get $i) (i32.const 100))))) + ) +"#; + +fn store() -> Store { + Store::new(Engine::new(Config::new().with_gc_collection_threshold(1))) +} + +#[test] +fn host_result_is_pinned_and_rejected_by_another_store() { + let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap(); + let mut first_store = store(); + let first = ModuleInstance::instantiate(&mut first_store, &module, None).unwrap(); + let new = first.func_untyped(&first_store, "new").unwrap(); + let read = first.func_untyped(&first_store, "read").unwrap(); + let churn = first.func_untyped(&first_store, "churn").unwrap(); + + let value = new.call(&mut first_store, &[]).unwrap().pop().unwrap(); + assert!(matches!(value, WasmValue::Ref(RefValue::Any(_)))); + churn.call(&mut first_store, &[]).unwrap(); + assert_eq!(read.call(&mut first_store, &[value]).unwrap(), [WasmValue::I32(42)]); + + let mut second_store = store(); + let second = ModuleInstance::instantiate(&mut second_store, &module, None).unwrap(); + let other_read = second.func_untyped(&second_store, "read").unwrap(); + assert!(other_read.call(&mut second_store, &[value]).is_err()); +} + +#[test] +fn externalized_gc_result_is_pinned() { + let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap(); + let mut store = store(); + let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap(); + let new = instance.func_untyped(&store, "new-extern").unwrap(); + let read = instance.func_untyped(&store, "read-extern").unwrap(); + let churn = instance.func_untyped(&store, "churn").unwrap(); + + let value = new.call(&mut store, &[]).unwrap().pop().unwrap(); + assert!(matches!(value, WasmValue::Ref(RefValue::Extern(_)))); + churn.call(&mut store, &[]).unwrap(); + assert_eq!(read.call(&mut store, &[value]).unwrap(), [WasmValue::I32(42)]); +} + +#[test] +fn host_externref_does_not_alias_a_gc_object() { + let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap(); + let mut store = store(); + let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap(); + let new = instance.func_untyped(&store, "new").unwrap(); + let read = instance.func_untyped(&store, "read-extern").unwrap(); + + new.call(&mut store, &[]).unwrap(); + let host_ref = WasmValue::Ref(RefValue::Extern(ExternRef::new(0))); + assert!(read.call(&mut store, &[host_ref]).is_err()); +} + +#[test] +fn resumable_gc_result_is_pinned() { + let module = tinywasm::parse_bytes(&wat::parse_str(MODULE).unwrap()).unwrap(); + let mut store = store(); + let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap(); + let new = instance.func_untyped(&store, "new").unwrap(); + let read = instance.func_untyped(&store, "read").unwrap(); + let churn = instance.func_untyped(&store, "churn").unwrap(); + + let mut execution = new.call_resumable(&mut store, &[]).unwrap(); + let value = match execution.resume_with_fuel(1000).unwrap() { + ExecProgress::Completed(mut values) => values.pop().unwrap(), + ExecProgress::Suspended => panic!("constructor unexpectedly suspended"), + }; + drop(execution); + + churn.call(&mut store, &[]).unwrap(); + assert_eq!(read.call(&mut store, &[value]).unwrap(), [WasmValue::I32(42)]); +} + +#[test] +fn element_initializers_root_previous_gc_values() { + let module = tinywasm::parse_bytes( + &wat::parse_str( + r#" + (module + (type $node (struct (field i32))) + (table 2 (ref null $node)) + (elem (i32.const 0) (ref $node) + (struct.new $node (i32.const 1)) + (struct.new $node (i32.const 2))) + (func (export "first") (result i32) + (struct.get $node 0 (table.get 0 (i32.const 0))))) + "#, + ) + .unwrap(), + ) + .unwrap(); + let mut store = store(); + let instance = ModuleInstance::instantiate(&mut store, &module, None).unwrap(); + + assert_eq!(instance.func_untyped(&store, "first").unwrap().call(&mut store, &[]).unwrap(), [WasmValue::I32(1)]); +} diff --git a/crates/tinywasm/tests/generated/wasm-2.csv b/crates/tinywasm/tests/generated/wasm-2.csv index c2472a7..645f0d5 100644 --- a/crates/tinywasm/tests/generated/wasm-2.csv +++ b/crates/tinywasm/tests/generated/wasm-2.csv @@ -13,4 +13,4 @@ 0.9.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":104,"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":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"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.9.1,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":104,"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":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"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.10.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":104,"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":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"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.11.0-pre.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":104,"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":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"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.11.0-pre.0,28002,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":59,"failed":0},{"name":"elem.wast","passed":96,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":108,"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":178,"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":104,"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":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"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/tinywasm/tests/generated/wasm-3.csv b/crates/tinywasm/tests/generated/wasm-3.csv index 9403d27..7d3961c 100644 --- a/crates/tinywasm/tests/generated/wasm-3.csv +++ b/crates/tinywasm/tests/generated/wasm-3.csv @@ -1,3 +1,3 @@ 0.9.0,20776,452,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":37,"failed":9},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.10.0,20778,450,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":38,"failed":8},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.11.0-pre.0,21107,121,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":19,"failed":13},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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.11.0-pre.0,21151,77,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":65,"failed":0},{"name":"elem.wast","passed":151,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":124,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":2,"failed":0},{"name":"type-equivalence.wast","passed":32,"failed":0},{"name":"type-rec.wast","passed":27,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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/tinywasm/tests/generated/wasm-extended-const.csv b/crates/tinywasm/tests/generated/wasm-extended-const.csv index 8677772..1e4e851 100644 --- a/crates/tinywasm/tests/generated/wasm-extended-const.csv +++ b/crates/tinywasm/tests/generated/wasm-extended-const.csv @@ -2,4 +2,4 @@ 0.9.0,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] 0.9.1,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] 0.10.0,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] -0.11.0-pre.0,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] +0.11.0-pre.0,284,0,[{"name":"data.wast","passed":63,"failed":0},{"name":"elem.wast","passed":109,"failed":0},{"name":"global.wast","passed":112,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-function-references.csv b/crates/tinywasm/tests/generated/wasm-function-references.csv index 3e59535..9d6dd64 100644 --- a/crates/tinywasm/tests/generated/wasm-function-references.csv +++ b/crates/tinywasm/tests/generated/wasm-function-references.csv @@ -1,3 +1,3 @@ 0.9.0,1536,331,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":0,"failed":9},{"name":"br_on_null.wast","passed":0,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_ref.wast","passed":3,"failed":31},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":146,"failed":21},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":4},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":10,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":35,"failed":8},{"name":"type-equivalence.wast","passed":7,"failed":7},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":10}] 0.10.0,1536,331,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":0,"failed":9},{"name":"br_on_null.wast","passed":0,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_ref.wast","passed":3,"failed":31},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":146,"failed":21},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":4},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":10,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":35,"failed":8},{"name":"type-equivalence.wast","passed":7,"failed":7},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":10}] -0.11.0-pre.0,1867,0,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":9,"failed":0},{"name":"br_on_null.wast","passed":9,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call_ref.wast","passed":34,"failed":0},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":167,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":4,"failed":0},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":50,"failed":0},{"name":"select.wast","passed":157,"failed":0},{"name":"table-sub.wast","passed":3,"failed":0},{"name":"table.wast","passed":43,"failed":0},{"name":"type-equivalence.wast","passed":14,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":12,"failed":0}] +0.11.0-pre.0,1866,0,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":9,"failed":0},{"name":"br_on_null.wast","passed":9,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call_ref.wast","passed":34,"failed":0},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":167,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":4,"failed":0},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":50,"failed":0},{"name":"select.wast","passed":157,"failed":0},{"name":"table-sub.wast","passed":3,"failed":0},{"name":"table.wast","passed":43,"failed":0},{"name":"type-equivalence.wast","passed":13,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":12,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-gc.csv b/crates/tinywasm/tests/generated/wasm-gc.csv index 5b2995f..ea39c56 100644 --- a/crates/tinywasm/tests/generated/wasm-gc.csv +++ b/crates/tinywasm/tests/generated/wasm-gc.csv @@ -1,2 +1,3 @@ 0.9.0,80,703,[{"name":"array.wast","passed":6,"failed":48},{"name":"array_copy.wast","passed":4,"failed":31},{"name":"array_fill.wast","passed":3,"failed":27},{"name":"array_init_data.wast","passed":2,"failed":44},{"name":"array_init_elem.wast","passed":3,"failed":33},{"name":"array_new_data.wast","passed":0,"failed":28},{"name":"array_new_elem.wast","passed":0,"failed":24},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"br_on_cast.wast","passed":6,"failed":31},{"name":"br_on_cast_fail.wast","passed":6,"failed":31},{"name":"extern.wast","passed":0,"failed":18},{"name":"i31.wast","passed":2,"failed":71},{"name":"ref_cast.wast","passed":0,"failed":45},{"name":"ref_eq.wast","passed":6,"failed":83},{"name":"ref_test.wast","passed":0,"failed":71},{"name":"struct.wast","passed":5,"failed":25},{"name":"type-subtyping.wast","passed":36,"failed":93}] 0.10.0,80,704,[{"name":"array.wast","passed":6,"failed":48},{"name":"array_copy.wast","passed":4,"failed":31},{"name":"array_fill.wast","passed":3,"failed":27},{"name":"array_init_data.wast","passed":2,"failed":44},{"name":"array_init_elem.wast","passed":3,"failed":33},{"name":"array_new_data.wast","passed":0,"failed":28},{"name":"array_new_elem.wast","passed":0,"failed":24},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"br_on_cast.wast","passed":6,"failed":31},{"name":"br_on_cast_fail.wast","passed":6,"failed":31},{"name":"extern.wast","passed":0,"failed":18},{"name":"i31.wast","passed":2,"failed":71},{"name":"ref_cast.wast","passed":0,"failed":45},{"name":"ref_eq.wast","passed":6,"failed":83},{"name":"ref_test.wast","passed":0,"failed":71},{"name":"struct.wast","passed":5,"failed":25},{"name":"type-subtyping.wast","passed":36,"failed":94}] +0.11.0-pre.0,784,0,[{"name":"array.wast","passed":54,"failed":0},{"name":"array_copy.wast","passed":35,"failed":0},{"name":"array_fill.wast","passed":30,"failed":0},{"name":"array_init_data.wast","passed":46,"failed":0},{"name":"array_init_elem.wast","passed":36,"failed":0},{"name":"array_new_data.wast","passed":28,"failed":0},{"name":"array_new_elem.wast","passed":24,"failed":0},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"br_on_cast.wast","passed":37,"failed":0},{"name":"br_on_cast_fail.wast","passed":37,"failed":0},{"name":"extern.wast","passed":18,"failed":0},{"name":"i31.wast","passed":73,"failed":0},{"name":"ref_cast.wast","passed":45,"failed":0},{"name":"ref_eq.wast","passed":89,"failed":0},{"name":"ref_test.wast","passed":71,"failed":0},{"name":"struct.wast","passed":30,"failed":0},{"name":"type-subtyping.wast","passed":130,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-latest.csv b/crates/tinywasm/tests/generated/wasm-latest.csv index ef7e1a2..94a8b58 100644 --- a/crates/tinywasm/tests/generated/wasm-latest.csv +++ b/crates/tinywasm/tests/generated/wasm-latest.csv @@ -1,2 +1,3 @@ 0.9.0,20777,452,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":37,"failed":9},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.10.0,20697,536,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":14,"failed":35},{"name":"return_call_indirect.wast","passed":30,"failed":51},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":38,"failed":8},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.11.0-pre.0,21156,77,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":65,"failed":0},{"name":"elem.wast","passed":151,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":124,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":49,"failed":0},{"name":"return_call_indirect.wast","passed":81,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":2,"failed":0},{"name":"type-equivalence.wast","passed":32,"failed":0},{"name":"type-rec.wast","passed":27,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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/tinywasm/tests/generated/wasm-threads.csv b/crates/tinywasm/tests/generated/wasm-threads.csv index f588c66..25d09b7 100644 --- a/crates/tinywasm/tests/generated/wasm-threads.csv +++ b/crates/tinywasm/tests/generated/wasm-threads.csv @@ -1,2 +1,3 @@ 0.9.0,357,262,[{"name":"atomic.wast","passed":48,"failed":249},{"name":"exports.wast","passed":82,"failed":6},{"name":"imports.wast","passed":147,"failed":5},{"name":"memory.wast","passed":80,"failed":2}] 0.10.0,357,262,[{"name":"atomic.wast","passed":48,"failed":249},{"name":"exports.wast","passed":82,"failed":6},{"name":"imports.wast","passed":147,"failed":5},{"name":"memory.wast","passed":80,"failed":2}] +0.11.0-pre.0,357,262,[{"name":"atomic.wast","passed":48,"failed":249},{"name":"exports.wast","passed":82,"failed":6},{"name":"imports.wast","passed":147,"failed":5},{"name":"memory.wast","passed":80,"failed":2}] diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index 7a68f01..04279cc 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -149,8 +149,13 @@ fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<() let module = tinywasm::parse_bytes(&wasm)?; let export = module.exports.iter().find(|export| export.name.as_ref() == "f").expect("export f not found"); - let old_lookup_ty = module.func_types.get(export.index as usize).expect("old lookup type missing"); - let func_ty = &module.func_types[module.func_type_idxs[export.index as usize] as usize]; + let old_lookup_ty = module.types.get(export.index).and_then(|ty| ty.as_func()).expect("old lookup type missing"); + let func_ty = module + .types + .get(module.func_type_idxs[export.index as usize]) + .expect("export function type index should exist") + .as_func() + .expect("export function should reference a function type"); assert_eq!(old_lookup_ty.params(), &[tinywasm::types::WasmType::I64]); assert_eq!(func_ty.params(), &[]); diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 99238ab..9904a7d 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -7,7 +7,7 @@ use crate::Module; #[rustfmt::skip] const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TWASM_MAGIC_PREFIX[2], TWASM_MAGIC_PREFIX[3], TWASM_VERSION[0], TWASM_VERSION[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; -const TWASM_VERSION: &[u8; 2] = b"04"; +const TWASM_VERSION: &[u8; 2] = b"05"; fn validate_magic(wasm: &[u8]) -> Result { if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index a53576e..1160fa5 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -54,6 +54,14 @@ pub enum ConstInstruction { V128Const([u8; 16]), GlobalGet(GlobalAddr), Ref(RefValue), + RefI31, + AnyConvertExtern, + ExternConvertAny, + StructNew(TypeAddr), + StructNewDefault(TypeAddr), + ArrayNew(TypeAddr), + ArrayNewDefault(TypeAddr), + ArrayNewFixed(TypeAddr, u32), I32Add, I32Sub, I32Mul, @@ -272,6 +280,35 @@ pub enum Instruction { RefFunc(FuncAddr), RefIsNull, RefAsNonNull, + RefI31, + I31GetS, + I31GetU, + RefEq, + RefTest(RefType), + RefCast(RefType), + BrOnCast(u32, RefType, bool), + + // > GC Objects + StructNew(TypeAddr), + StructNewDefault(TypeAddr), + StructGet(TypeAddr, u32), + StructGetS(TypeAddr, u32), + StructGetU(TypeAddr, u32), + StructSet(TypeAddr, u32), + ArrayNew(TypeAddr), + ArrayNewDefault(TypeAddr), + ArrayNewFixed(TypeAddr, u32), + ArrayNewData(TypeAddr, DataAddr), + ArrayNewElem(TypeAddr, ElemAddr), + ArrayGet(TypeAddr), + ArrayGetS(TypeAddr), + ArrayGetU(TypeAddr), + ArraySet(TypeAddr), + ArrayLen, + ArrayFill(TypeAddr), + ArrayCopy(TypeAddr, TypeAddr), + ArrayInitData(TypeAddr, DataAddr), + ArrayInitElem(TypeAddr, ElemAddr), // > Numeric Instructions // See diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 0b15927..c461296 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -22,9 +22,11 @@ const fn max_page_count(page_size: u64) -> u64 { mod instructions; mod reference; +mod types; mod value; pub use instructions::*; pub use reference::*; +pub use types::*; pub use value::*; #[cfg(feature = "archive")] @@ -81,10 +83,10 @@ pub struct ModuleInner { /// Contains data from to the `code`, `func`, and `type` sections of the original WebAssembly module. pub funcs: Box<[Arc]>, - /// A vector of type definitions, indexed by `TypeAddr` + /// The dense type definitions, indexed by `TypeAddr`. /// /// Corresponds to the `type` section of the original WebAssembly module. - pub func_types: Box<[FuncType]>, + pub types: TypeSection, /// Function index to type index mapping in module index space, including imports. pub func_type_idxs: Box<[TypeAddr]>, @@ -135,7 +137,7 @@ impl Module { pub fn imports(&self) -> impl Iterator> { self.0.imports.iter().filter_map(|import| { let ty = match &import.kind { - ImportKind::Function(type_idx) => Some(ImportType::Func(self.0.func_types.get(*type_idx as usize)?)), + ImportKind::Function(type_idx) => Some(ImportType::Func(self.0.types.get(*type_idx)?.as_func()?)), ImportKind::Table(table_ty) => Some(ImportType::Table(table_ty)), ImportKind::Memory(memory_ty) => Some(ImportType::Memory(memory_ty)), ImportKind::Global(global_ty) => Some(ImportType::Global(global_ty)), @@ -178,7 +180,7 @@ impl Module { let import = imports.nth(index)?; match &import.kind { - ImportKind::Function(type_idx) => Some(ExportType::Func(module.func_types.get(*type_idx as usize)?)), + ImportKind::Function(type_idx) => Some(ExportType::Func(module.types.get(*type_idx)?.as_func()?)), ImportKind::Table(table_ty) => Some(ExportType::Table(table_ty)), ImportKind::Memory(memory_ty) => Some(ExportType::Memory(memory_ty)), ImportKind::Global(global_ty) => Some(ExportType::Global(global_ty)), @@ -194,7 +196,7 @@ impl Module { imported_type(&self.0, ExternalKind::Func, idx)? } else { let type_idx = *self.0.func_type_idxs.get(idx)?; - ExportType::Func(self.0.func_types.get(type_idx as usize)?) + ExportType::Func(self.0.types.get(type_idx)?.as_func()?) } } ExternalKind::Table => { @@ -360,90 +362,6 @@ impl ExternVal { } } -/// The type of a WebAssembly Function. -/// -/// See -#[derive(Clone, PartialEq, Eq, Default)] -#[cfg_attr(feature = "debug", derive(Debug))] -#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -pub struct FuncType { - data: Box<[WasmType]>, - param_count: u16, -} - -impl FuncType { - /// Create a new function type. - pub fn new(params: &[WasmType], results: &[WasmType]) -> Self { - let data: Box<[WasmType]> = params.iter().cloned().chain(results.iter().cloned()).collect(); - Self { data, param_count: params.len() as u16 } - } - - /// Get the parameter types of this function type. - pub fn params(&self) -> &[WasmType] { - &self.data[..self.param_count as usize] - } - - /// Get the result types of this function type. - pub fn results(&self) -> &[WasmType] { - &self.data[self.param_count as usize..] - } - - /// Compare function types while resolving concrete references in their respective type spaces. - pub fn equivalent(&self, types: &[FuncType], other: &Self, other_types: &[FuncType]) -> bool { - fn refs_equal( - left_types: &[FuncType], - left: RefType, - right_types: &[FuncType], - right: RefType, - visited: &mut alloc::vec::Vec<(u32, u32)>, - ) -> bool { - if left.is_nullable() != right.is_nullable() { - return false; - } - match (left.type_index(), right.type_index()) { - (Some(left_idx), Some(right_idx)) => { - if visited.contains(&(left_idx, right_idx)) { - return true; - } - let (Some(left), Some(right)) = - (left_types.get(left_idx as usize), right_types.get(right_idx as usize)) - else { - return false; - }; - visited.push((left_idx, right_idx)); - funcs_equal(left, left_types, right, right_types, visited) - } - (None, None) => left.abstract_heap_type() == right.abstract_heap_type(), - _ => false, - } - } - - fn funcs_equal( - left: &FuncType, - left_types: &[FuncType], - right: &FuncType, - right_types: &[FuncType], - visited: &mut alloc::vec::Vec<(u32, u32)>, - ) -> bool { - left.params().len() == right.params().len() - && left.results().len() == right.results().len() - && left.params().iter().chain(left.results()).zip(right.params().iter().chain(right.results())).all( - |(&left, &right)| match (left, right) { - (WasmType::Ref(left), WasmType::Ref(right)) => { - refs_equal(left_types, left, right_types, right, visited) - } - _ => left == right, - }, - ) - } - - if core::ptr::eq(types, other_types) && self == other { - return true; - } - funcs_equal(self, types, other, other_types, &mut alloc::vec::Vec::new()) - } -} - #[derive(Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/types/src/reference.rs b/crates/types/src/reference.rs index ee66965..884d330 100644 --- a/crates/types/src/reference.rs +++ b/crates/types/src/reference.rs @@ -1,3 +1,12 @@ +const HOST_REF_TAG: u32 = 1 << 30; + +const fn encode_host_ref(addr: u32) -> Option { + if addr >= HOST_REF_TAG - 1 { + return None; + } + Some((addr | HOST_REF_TAG).wrapping_add(1).wrapping_mul(2)) +} + /// An abstract WebAssembly heap type. /// /// This contains exactly the abstract heap types in core Wasm 3.0. @@ -31,7 +40,8 @@ pub enum AbstractHeapType { /// [nullable:1 concrete:1 payload:30] /// ``` /// -/// For concrete types, `payload` is a module type index. +/// For concrete types, `payload` is a module type index before instantiation +/// and a canonical store type address at runtime. /// Otherwise, it is an [`AbstractHeapType`]. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -116,9 +126,7 @@ impl RefType { #[inline] pub const fn is_func(self) -> bool { - // TODO(wasm3): Classify concrete refs from the module type definition once GC types are represented. - self.is_concrete() - || matches!(self.abstract_heap_type(), Some(AbstractHeapType::Func | AbstractHeapType::NoFunc)) + matches!(self.abstract_heap_type(), Some(AbstractHeapType::Func | AbstractHeapType::NoFunc)) } #[inline] @@ -132,6 +140,7 @@ impl RefType { } } +/// A host-facing WebAssembly reference value. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -143,19 +152,9 @@ pub enum RefValue { Exn(ExnRef), } -impl RefValue { - /// Return the reference's raw representation, or `None` for null. - pub const fn raw(self) -> Option { - match self { - Self::Null => None, - Self::Func(value) => Some(value.addr()), - Self::Extern(value) => Some(value.addr()), - Self::Any(value) => Some(value.raw()), - Self::Exn(value) => Some(value.addr()), - } - } -} - +/// A reference to a function in a store. +/// +/// The payload is the function's store-local address. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -173,6 +172,11 @@ impl FuncRef { } } +/// An opaque external reference. +/// +/// Packed as `[payload:31 i31:1]`. Host addresses use the upper payload +/// category, Store-managed objects use the lower category, and odd values +/// contain an externalized i31. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -181,15 +185,35 @@ pub struct ExternRef(u32); impl ExternRef { #[inline] pub const fn new(addr: u32) -> Self { - Self(addr) + let Some(value) = Self::try_new(addr) else { panic!("external reference address is too large") }; + value } + /// Creates an external reference when `addr` fits the runtime encoding. #[inline] - pub const fn addr(self) -> u32 { + pub const fn try_new(addr: u32) -> Option { + match encode_host_ref(addr) { + Some(raw) => Some(Self(raw)), + None => None, + } + } + + #[doc(hidden)] + #[inline] + pub const fn from_raw(raw: u32) -> Self { + Self(raw) + } + + #[doc(hidden)] + #[inline] + pub const fn raw(self) -> u32 { self.0 } } +/// A reference to an exception in a store. +/// +/// The payload is the exception's store-local address. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -207,14 +231,30 @@ impl ExnRef { } } +/// A WebAssembly `anyref` value. +/// +/// Packed as: +/// +/// ```text +/// [payload:31 i31:1] +/// ``` +/// +/// Odd values contain an inline signed i31. Non-zero even values are reserved +/// for store-managed references, and zero is reserved for null by the runtime. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct AnyRef(u32); impl AnyRef { - // Odd values are inline i31s. - // Even values are GC object handles. + /// Creates a host reference when `addr` fits the runtime encoding. + #[inline] + pub const fn from_host(addr: u32) -> Option { + match encode_host_ref(addr) { + Some(raw) => Some(Self(raw)), + None => None, + } + } #[doc(hidden)] #[inline] @@ -234,22 +274,21 @@ impl AnyRef { if self.0 & 1 == 1 { Some((self.0 as i32) >> 1) } else { None } } - pub const fn from_gc_addr(addr: u32) -> Option { - match addr.checked_add(1) { - Some(raw) => match raw.checked_mul(2) { - Some(raw) => Some(Self(raw)), - None => None, - }, - None => None, - } - } - - pub const fn gc_addr(self) -> Option { - if self.0 != 0 && self.0 & 1 == 0 { Some(self.0 / 2 - 1) } else { None } - } - #[inline] pub const fn raw(self) -> u32 { self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn host_reference_encoding_is_checked_and_unique() { + assert_ne!(AnyRef::from_host(0), AnyRef::from_host(1)); + assert!(AnyRef::from_host(HOST_REF_TAG - 2).is_some()); + assert!(AnyRef::from_host(HOST_REF_TAG - 1).is_none()); + assert!(ExternRef::try_new(u32::MAX).is_none()); + } +} diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs new file mode 100644 index 0000000..214ada6 --- /dev/null +++ b/crates/types/src/types.rs @@ -0,0 +1,158 @@ +use alloc::boxed::Box; + +use crate::{TypeAddr, WasmType}; + +/// The dense type index space of a WebAssembly module. +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct TypeSection { + /// Types in module index order. + pub types: Box<[SubType]>, + /// Number of types in each recursive group, in section order. + pub rec_group_lengths: Box<[u32]>, +} + +impl TypeSection { + #[inline] + pub fn get(&self, index: TypeAddr) -> Option<&SubType> { + self.types.get(index as usize) + } + + #[inline] + pub fn len(&self) -> usize { + self.types.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.types.is_empty() + } +} + +/// A type with optional declared subtyping. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct SubType { + pub is_final: bool, + pub supertype: Option, + pub composite: CompositeType, +} + +impl SubType { + #[inline] + pub const fn as_func(&self) -> Option<&FuncType> { + self.composite.as_func() + } + + #[inline] + pub const fn as_struct(&self) -> Option<&StructType> { + self.composite.as_struct() + } + + #[inline] + pub const fn as_array(&self) -> Option<&ArrayType> { + self.composite.as_array() + } +} + +/// A function, struct, or array type. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub enum CompositeType { + Func(FuncType), + Struct(StructType), + Array(ArrayType), +} + +impl CompositeType { + #[inline] + pub const fn as_func(&self) -> Option<&FuncType> { + match self { + Self::Func(ty) => Some(ty), + _ => None, + } + } + + #[inline] + pub const fn as_struct(&self) -> Option<&StructType> { + match self { + Self::Struct(ty) => Some(ty), + _ => None, + } + } + + #[inline] + pub const fn as_array(&self) -> Option<&ArrayType> { + match self { + Self::Array(ty) => Some(ty), + _ => None, + } + } +} + +/// The type of a WebAssembly function. +/// +/// See +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct FuncType { + data: Box<[WasmType]>, + param_count: u16, +} + +impl FuncType { + /// Create a new function type. + pub fn new(params: &[WasmType], results: &[WasmType]) -> Self { + let data: Box<[WasmType]> = params.iter().cloned().chain(results.iter().cloned()).collect(); + Self { data, param_count: params.len() as u16 } + } + + /// Get the parameter types of this function type. + pub fn params(&self) -> &[WasmType] { + &self.data[..self.param_count as usize] + } + + /// Get the result types of this function type. + pub fn results(&self) -> &[WasmType] { + &self.data[self.param_count as usize..] + } +} + +/// A WebAssembly struct type. +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct StructType { + pub fields: Box<[FieldType]>, +} + +/// A WebAssembly array type. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct ArrayType { + pub field: FieldType, +} + +/// A struct field or array element type. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct FieldType { + pub storage: StorageType, + pub mutable: bool, +} + +/// A field's packed or unpacked storage type. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub enum StorageType { + I8, + I16, + Value(WasmType), +} diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 92c660b..f0a576b 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -54,7 +54,7 @@ impl WasmValue { | (Self::V128(_), WasmType::V128) => true, (Self::Ref(RefValue::Null), WasmType::Ref(ty)) => ty.is_nullable(), (Self::Ref(RefValue::Func(_)), WasmType::Ref(ty)) => { - ty.is_concrete() || matches!(ty.abstract_heap_type(), Some(crate::AbstractHeapType::Func)) + matches!(ty.abstract_heap_type(), Some(crate::AbstractHeapType::Func)) } (Self::Ref(RefValue::Extern(_)), WasmType::Ref(ty)) => { matches!(ty.abstract_heap_type(), Some(crate::AbstractHeapType::Extern)) @@ -62,12 +62,42 @@ impl WasmValue { (Self::Ref(RefValue::Exn(_)), WasmType::Ref(ty)) => { matches!(ty.abstract_heap_type(), Some(crate::AbstractHeapType::Exn)) } - (Self::Ref(RefValue::Any(_)), WasmType::Ref(ty)) => !ty.is_func() && !ty.is_extern() && !ty.is_exn(), + (Self::Ref(RefValue::Any(value)), WasmType::Ref(ty)) => matches!( + (value.as_i31(), ty.abstract_heap_type()), + ( + Some(_), + Some(crate::AbstractHeapType::I31 | crate::AbstractHeapType::Eq | crate::AbstractHeapType::Any) + ) | (None, Some(crate::AbstractHeapType::Any)) + ), _ => false, } } } +#[cfg(test)] +mod tests { + use super::*; + use crate::{AbstractHeapType, AnyRef, FuncRef, RefType}; + + #[test] + fn concrete_references_require_store_type_information() { + let concrete = WasmType::Ref(RefType::new_concrete(false, 0).unwrap()); + + assert!(!WasmValue::from(FuncRef::new(0)).matches_type(concrete)); + assert!(!WasmValue::from(AnyRef::from_host(0).unwrap()).matches_type(concrete)); + } + + #[test] + fn i31_only_matches_its_store_independent_supertypes() { + let value = WasmValue::from(AnyRef::from_i31(0).unwrap()); + + for ty in [AbstractHeapType::I31, AbstractHeapType::Eq, AbstractHeapType::Any] { + assert!(value.matches_type(WasmType::Ref(RefType::new_abstract(false, ty)))); + } + assert!(!value.matches_type(WasmType::Ref(RefType::new_abstract(false, AbstractHeapType::Struct)))); + } +} + impl Debug for WasmValue { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { From 676be92cde66d7c20bfe11d64bfc3f5762c4c46e Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Aug 2026 18:30:29 +0200 Subject: [PATCH 04/15] chore: store globals in different lanes based on size Signed-off-by: Henry --- CHANGELOG.md | 5 +- crates/parser/src/conversion.rs | 22 +- crates/parser/src/macros.rs | 2 +- crates/parser/src/optimize.rs | 8 +- crates/parser/src/visit.rs | 248 ++++++++---------- crates/tinywasm/src/imports.rs | 23 +- crates/tinywasm/src/interpreter/executor.rs | 50 ++-- .../src/interpreter/stack/value_stack.rs | 4 + crates/tinywasm/src/interpreter/values.rs | 40 ++- crates/tinywasm/src/reference.rs | 34 +-- crates/tinywasm/src/store/global.rs | 165 +++++++++++- crates/tinywasm/src/store/mod.rs | 18 +- crates/tinywasm/src/store/state.rs | 64 +++-- crates/tinywasm/tests/store_ownership.rs | 18 +- crates/tinywasm/tests/typed_globals.rs | 93 +++++++ crates/types/src/instructions.rs | 7 +- crates/types/src/lib.rs | 41 ++- 17 files changed, 564 insertions(+), 278 deletions(-) create mode 100644 crates/tinywasm/tests/typed_globals.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d4dd8..7ef6fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,16 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added support for the WebAssembly function-references proposal -- Added support for the WebAssembly garbage-collection proposal +- Added basic support for the WebAssembly garbage-collection proposal - Added `WasmValue::ty` and `WasmValue::matches_type` +- Added `ValueLane` for mapping WebAssembly value types to their physical 32-bit, 64-bit, or 128-bit storage lane. - Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules. ### Changed - Module types now use one dense recursive type space, while function types are resolved through `Function::ty(&Store)`. +- Globals are stored in separate 32-bit, 64-bit, and 128-bit value lanes, avoiding tagged value conversion during guest execution. ### Fixed +- Directly defined imports now reject handles from a different `Store`. - Tail calls to host functions now return directly to the caller frame. ### Breaking Changes diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 8bc7df1..4073a8e 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -6,6 +6,14 @@ use alloc::{boxed::Box, format, vec::Vec}; use tinywasm_types::*; use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations, UnpackedIndex}; +pub(crate) fn value_lane(ty: wasmparser::ValType) -> ValueLane { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 | wasmparser::ValType::Ref(_) => ValueLane::S32, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => ValueLane::S64, + wasmparser::ValType::V128 => ValueLane::S128, + } +} + pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result { let kind = match element.kind { wasmparser::ElementKind::Active { table_index, offset_expr } => tinywasm_types::ElementKind::Active { @@ -116,12 +124,8 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result, - count: u32, - ty: wasmparser::ValType, -) -> Result<()> { - let size = crate::visit::OperandSize::from(ty); +fn extend_local_types(local_types: &mut Vec, count: u32, ty: wasmparser::ValType) -> Result<()> { + let size = value_lane(ty); let count = usize::try_from(count).map_err(|_| crate::ParseError::Other("local declaration count is too large".into()))?; local_types.reserve(count); @@ -166,9 +170,9 @@ pub(crate) fn convert_module_code( for ty in &local_types { let (count, error) = match ty { - crate::visit::OperandSize::S32 => (&mut local_counts.c32, "too many 32-bit locals"), - crate::visit::OperandSize::S64 => (&mut local_counts.c64, "too many 64-bit locals"), - crate::visit::OperandSize::S128 => (&mut local_counts.c128, "too many 128-bit locals"), + ValueLane::S32 => (&mut local_counts.c32, "too many 32-bit locals"), + ValueLane::S64 => (&mut local_counts.c64, "too many 64-bit locals"), + ValueLane::S128 => (&mut local_counts.c128, "too many 128-bit locals"), }; local_addr_map.push(*count); *count = count.checked_add(1).ok_or_else(|| crate::ParseError::Other(error.into()))?; diff --git a/crates/parser/src/macros.rs b/crates/parser/src/macros.rs index 553f439..db1402d 100644 --- a/crates/parser/src/macros.rs +++ b/crates/parser/src/macros.rs @@ -141,7 +141,7 @@ pub(crate) mod visit { (@size Addr, $address:ident) => { $address }; (@size Field, $address:ident) => { $address }; - (@size $size:ident $(, $address:ident)?) => { OperandSize::$size }; + (@size $size:ident $(, $address:ident)?) => { ValueLane::$size }; } macro_rules! impl_visit_operator { diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index 5c2d7d5..74a9fc0 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -93,7 +93,7 @@ fn rewrite( rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); rewrite!(instrs, i, [Const32(c), LocalGet32(local)] => BinOpLocalConst32(op, local, c)); - rewrite!(instrs, i, [GlobalGet(global)] => BinOpStackGlobal32(op, global)); + rewrite!(instrs, i, [GlobalGet32(global)] => BinOpStackGlobal32(op, global)); if matches!(op, BinOp::IAdd) { rewrite!(instrs, i, [Const32(c)] => AddConst32(c)); rewrite!(instrs, i, [I32Add] => I32Add3); @@ -103,7 +103,7 @@ fn rewrite( let Some(op) = int_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); - rewrite!(instrs, i, [GlobalGet(global)] => BinOpStackGlobal32(op, global)); + rewrite!(instrs, i, [GlobalGet32(global)] => BinOpStackGlobal32(op, global)); if matches!(op, BinOp::IShrS) { rewrite!(instrs, i, [BinOpLocalConst32(BinOp::IShl, local, 8), Const32(8)] => [LocalGet32(local), I32Extend8S]); rewrite!(instrs, i, [BinOpLocalConst32(BinOp::IShl, local, 16), Const32(16)] => [LocalGet32(local), I32Extend16S]); @@ -114,7 +114,7 @@ fn rewrite( rewrite!(instrs, i, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b)); rewrite!(instrs, i, [LocalGet64(local), Const64(c)] => BinOpLocalConst64(op, local, c)); rewrite!(instrs, i, [Const64(c), LocalGet64(local)] => BinOpLocalConst64(op, local, c)); - rewrite!(instrs, i, [GlobalGet(global)] => BinOpStackGlobal64(op, global)); + rewrite!(instrs, i, [GlobalGet64(global)] => BinOpStackGlobal64(op, global)); if matches!(op, BinOp::IAdd) { rewrite!(instrs, i, [Const64(c)] => AddConst64(c)); rewrite!(instrs, i, [I64Add] => I64Add3); @@ -124,7 +124,7 @@ fn rewrite( let Some(op) = int_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b)); rewrite!(instrs, i, [LocalGet64(local), Const64(c)] => BinOpLocalConst64(op, local, c)); - rewrite!(instrs, i, [GlobalGet(global)] => BinOpStackGlobal64(op, global)); + rewrite!(instrs, i, [GlobalGet64(global)] => BinOpStackGlobal64(op, global)); if matches!(op, BinOp::IShrS) { rewrite!(instrs, i, [BinOpLocalConst64(BinOp::IShl, local, 8), Const64(8)] => [LocalGet64(local), I64Extend8S]); rewrite!(instrs, i, [BinOpLocalConst64(BinOp::IShl, local, 16), Const64(16)] => [LocalGet64(local), I64Extend16S]); diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 4437242..46d1f9f 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -1,9 +1,13 @@ -use crate::{Result, conversion::convert_heap_type, macros::visit::*}; +use crate::{ + Result, + conversion::{convert_heap_type, value_lane}, + macros::visit::*, +}; use alloc::string::ToString; use alloc::vec::Vec; use tinywasm_types::{ - Global, Import, ImportKind, Instruction, MemoryArch, MemoryArg, MemoryType, StorageType, SubType, TableDefinition, - TypeSection, ValueCounts, WasmFunctionData, WasmType, + Global, Import, ImportKind, Instruction, MemoryArg, MemoryType, StorageType, SubType, TableDefinition, TypeSection, + ValueCounts, ValueLane, WasmFunctionData, }; use wasmparser::{FunctionBody, OperatorsReader, OperatorsReaderAllocations, VisitSimdOperator}; @@ -18,52 +22,6 @@ enum BlockKind { If, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum OperandSize { - S32, - S64, - S128, -} - -impl OperandSize { - fn choose(self, s32: T, s64: T, s128: T) -> T { - match self { - Self::S32 => s32, - Self::S64 => s64, - Self::S128 => s128, - } - } -} - -impl From for OperandSize { - fn from(ty: wasmparser::ValType) -> Self { - match ty { - wasmparser::ValType::I32 | wasmparser::ValType::F32 | wasmparser::ValType::Ref(_) => Self::S32, - wasmparser::ValType::I64 | wasmparser::ValType::F64 => Self::S64, - wasmparser::ValType::V128 => Self::S128, - } - } -} - -impl From<&WasmType> for OperandSize { - fn from(ty: &WasmType) -> Self { - match ty { - WasmType::I32 | WasmType::F32 | WasmType::Ref(_) => Self::S32, - WasmType::I64 | WasmType::F64 => Self::S64, - WasmType::V128 => Self::S128, - } - } -} - -impl From for OperandSize { - fn from(arch: MemoryArch) -> Self { - match arch { - MemoryArch::I32 => Self::S32, - MemoryArch::I64 => Self::S64, - } - } -} - struct ControlFrame { kind: BlockKind, has_else: bool, @@ -71,8 +29,8 @@ struct ControlFrame { branch_jumps: Vec, height: usize, base: ValueCounts, - params: Vec, - results: Vec, + params: Vec, + results: Vec, unreachable: bool, entry_unreachable: bool, end_reachable: bool, @@ -80,16 +38,16 @@ struct ControlFrame { #[derive(Clone)] pub(crate) struct Signature { - pub params: Vec, - results: Vec, + pub params: Vec, + results: Vec, } pub(crate) struct ModuleMetadata { signatures: Vec>, functions: Vec, - globals: Vec, - memories: Vec, - tables: Vec, + globals: Vec, + memories: Vec, + tables: Vec, types: Vec, } @@ -103,10 +61,10 @@ pub(crate) struct FunctionBuilder<'a> { instructions: Vec, data: FunctionDataBuilder, control_stack: Vec, - operand_stack: Vec, + operand_stack: Vec, lane_counts: ValueCounts, metadata: &'a ModuleMetadata, - local_types: Vec, + local_types: Vec, local_addr_map: Vec, } @@ -114,7 +72,7 @@ impl<'a> FunctionBuilder<'a> { pub(crate) fn new( metadata: &'a ModuleMetadata, signature: Signature, - local_types: Vec, + local_types: Vec, local_addr_map: Vec, body_size: usize, ) -> Self { @@ -149,7 +107,7 @@ impl<'a> FunctionBuilder<'a> { instruction: fn(u32, u32) -> Instruction, ) -> Result<()> { let size = ModuleMetadata::storage_size(self.metadata.struct_field(type_index, field_index)?.storage); - self.emit(&[OperandSize::S32], &[size], instruction(type_index, field_index)) + self.emit(&[ValueLane::S32], &[size], instruction(type_index, field_index)) } } @@ -161,6 +119,13 @@ struct ValidateThenVisit<'a, 'm> { } impl ModuleMetadata { + fn address_lane(arch: tinywasm_types::MemoryArch) -> ValueLane { + match arch { + tinywasm_types::MemoryArch::I32 => ValueLane::S32, + tinywasm_types::MemoryArch::I64 => ValueLane::S64, + } + } + pub(crate) fn new( types: &TypeSection, code_type_addrs: &[u32], @@ -177,24 +142,24 @@ impl ModuleMetadata { for import in imports { match &import.kind { ImportKind::Function(ty) => functions.push(*ty), - ImportKind::Global(ty) => global_sizes.push(OperandSize::from(&ty.ty)), - ImportKind::Memory(ty) => memory_sizes.push(OperandSize::from(ty.arch())), - ImportKind::Table(ty) => table_sizes.push(OperandSize::from(ty.arch())), + ImportKind::Global(ty) => global_sizes.push(ValueLane::from(&ty.ty)), + ImportKind::Memory(ty) => memory_sizes.push(Self::address_lane(ty.arch())), + ImportKind::Table(ty) => table_sizes.push(Self::address_lane(ty.arch())), } } functions.extend_from_slice(code_type_addrs); - global_sizes.extend(globals.iter().map(|global| OperandSize::from(&global.ty.ty))); - memory_sizes.extend(memories.iter().map(|ty| OperandSize::from(ty.arch()))); - table_sizes.extend(tables.iter().map(|table| OperandSize::from(table.ty.arch()))); + global_sizes.extend(globals.iter().map(|global| ValueLane::from(&global.ty.ty))); + memory_sizes.extend(memories.iter().map(|ty| Self::address_lane(ty.arch()))); + table_sizes.extend(tables.iter().map(|table| Self::address_lane(table.ty.arch()))); let signatures = types .types .iter() .map(|ty| { ty.as_func().map(|ty| Signature { - params: ty.params().iter().map(OperandSize::from).collect(), - results: ty.results().iter().map(OperandSize::from).collect(), + params: ty.params().iter().map(ValueLane::from).collect(), + results: ty.results().iter().map(ValueLane::from).collect(), }) }) .collect(); @@ -223,29 +188,29 @@ impl ModuleMetadata { self.signature(ty) } - fn global_size(&self, idx: u32) -> Result { + fn global_size(&self, idx: u32) -> Result { Self::indexed_size(&self.globals, "global", idx) } - fn memory_size(&self, idx: u32) -> Result { + fn memory_size(&self, idx: u32) -> Result { Self::indexed_size(&self.memories, "memory", idx) } - fn table_size(&self, idx: u32) -> Result { + fn table_size(&self, idx: u32) -> Result { Self::indexed_size(&self.tables, "table", idx) } - fn indexed_size(sizes: &[OperandSize], entity: &str, idx: u32) -> Result { + fn indexed_size(sizes: &[ValueLane], entity: &str, idx: u32) -> Result { sizes .get(idx as usize) .copied() .ok_or_else(|| crate::ParseError::Other(alloc::format!("{entity} index out of bounds: {idx}"))) } - fn storage_size(storage: StorageType) -> OperandSize { + fn storage_size(storage: StorageType) -> ValueLane { match storage { - StorageType::I8 | StorageType::I16 => OperandSize::S32, - StorageType::Value(ref ty) => OperandSize::from(ty), + StorageType::I8 | StorageType::I16 => ValueLane::S32, + StorageType::Value(ref ty) => ValueLane::from(ty), } } @@ -291,7 +256,7 @@ impl VisitSimdOperator<'_> for ValidateThenVisit<'_, '_> { pub(crate) fn process_operators( body: FunctionBody<'_>, - local_types: Vec, + local_types: Vec, local_addr_map: Vec, metadata: &ModuleMetadata, ty_idx: u32, @@ -327,7 +292,7 @@ pub(crate) fn process_operators( pub(crate) fn process_operators_and_validate( mut validator: FuncValidator, body: FunctionBody<'_>, - local_types: Vec, + local_types: Vec, local_addr_map: Vec, metadata: &ModuleMetadata, ty_idx: u32, @@ -470,7 +435,6 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { effect [S32] => [S32] { visit_f32_reinterpret_i32, visit_i32_reinterpret_f32 } effect [S64] => [S64] { visit_f64_reinterpret_i64, visit_i64_reinterpret_f64 } terminating [] => [] { visit_unreachable => Unreachable, visit_return => Return } - global [] => [Addr] { visit_global_get(global_index: u32) => GlobalGet } memory_index [] => [Addr] { visit_memory_size(memory: u32) => MemorySize } memory_index [Addr] => [Addr] { visit_memory_grow(memory: u32) => MemoryGrow } memory_index [Addr, S32, Addr] => [] { @@ -514,7 +478,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { .iter() .map(|field| ModuleMetadata::storage_size(field.storage)) .collect::>(); - self.emit(&inputs, &[OperandSize::S32], Instruction::StructNew(type_index)) + self.emit(&inputs, &[ValueLane::S32], Instruction::StructNew(type_index)) } fn visit_struct_get(&mut self, type_index: u32, field_index: u32) -> Self::Output { @@ -531,7 +495,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_struct_set(&mut self, type_index: u32, field_index: u32) -> Self::Output { let size = ModuleMetadata::storage_size(self.metadata.struct_field(type_index, field_index)?.storage); - self.emit(&[OperandSize::S32, size], &[], Instruction::StructSet(type_index, field_index)) + self.emit(&[ValueLane::S32, size], &[], Instruction::StructSet(type_index, field_index)) } fn visit_array_new_fixed(&mut self, type_index: u32, array_size: u32) -> Self::Output { @@ -539,7 +503,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { for _ in 0..array_size { self.pop_expect(size)?; } - self.push_sizes(&[OperandSize::S32])?; + self.push_sizes(&[ValueLane::S32])?; self.instructions.push(Instruction::ArrayNewFixed(type_index, array_size)); Ok(()) } @@ -559,7 +523,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_call_ref(&mut self, type_index: u32) -> Self::Output { let signature = self.metadata.signature(type_index)?.clone(); let mut inputs = signature.params; - inputs.push(OperandSize::S32); + inputs.push(ValueLane::S32); self.emit(&inputs, &signature.results, Instruction::CallRef(type_index)) } @@ -584,7 +548,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_return_call_ref(&mut self, type_index: u32) -> Self::Output { let signature = self.metadata.signature(type_index)?.clone(); let mut inputs = signature.params; - inputs.push(OperandSize::S32); + inputs.push(ValueLane::S32); self.apply_effect(&inputs, &[])?; self.mark_unreachable(); self.instructions.push(Instruction::ReturnCallRef(type_index)); @@ -593,7 +557,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_global_set(&mut self, global_index: u32) -> Self::Output { let size = self.metadata.global_size(global_index)?; - let instruction = size.choose( + let instruction = size.select( Instruction::GlobalSet32(global_index), Instruction::GlobalSet64(global_index), Instruction::GlobalSet128(global_index), @@ -601,21 +565,31 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { self.emit(&[size], &[], instruction) } + fn visit_global_get(&mut self, global_index: u32) -> Self::Output { + let size = self.metadata.global_size(global_index)?; + let instruction = size.select( + Instruction::GlobalGet32(global_index), + Instruction::GlobalGet64(global_index), + Instruction::GlobalGet128(global_index), + ); + self.emit(&[], &[size], instruction) + } + fn visit_drop(&mut self) -> Self::Output { - let size = self.operand_stack.last().copied().unwrap_or(OperandSize::S32); - let instruction = size.choose(Instruction::Drop32, Instruction::Drop64, Instruction::Drop128); + let size = self.operand_stack.last().copied().unwrap_or(ValueLane::S32); + let instruction = size.select(Instruction::Drop32, Instruction::Drop64, Instruction::Drop128); self.emit(&[size], &[], instruction) } fn visit_select(&mut self) -> Self::Output { - let size = self.operand_stack.iter().rev().nth(1).copied().unwrap_or(OperandSize::S32); - let instruction = size.choose(Instruction::Select32, Instruction::Select64, Instruction::Select128); - self.emit(&[size, size, OperandSize::S32], &[size], instruction) + let size = self.operand_stack.iter().rev().nth(1).copied().unwrap_or(ValueLane::S32); + let instruction = size.select(Instruction::Select32, Instruction::Select64, Instruction::Select128); + self.emit(&[size, size, ValueLane::S32], &[size], instruction) } fn visit_local_get(&mut self, idx: u32) -> Self::Output { let (size, local_idx) = self.local(idx)?; - let instruction = size.choose( + let instruction = size.select( Instruction::LocalGet32(local_idx), Instruction::LocalGet64(local_idx), Instruction::LocalGet128(local_idx), @@ -625,7 +599,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_local_set(&mut self, idx: u32) -> Self::Output { let (size, local_idx) = self.local(idx)?; - let instruction = size.choose( + let instruction = size.select( Instruction::LocalSet32(local_idx), Instruction::LocalSet64(local_idx), Instruction::LocalSet128(local_idx), @@ -637,21 +611,21 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { let (size, local_idx) = self.local(idx)?; self.apply_effect(&[size], &[size])?; let src = match (size, self.instructions.last()) { - (OperandSize::S32, Some(Instruction::LocalGet32(src))) => Some(*src), - (OperandSize::S64, Some(Instruction::LocalGet64(src))) => Some(*src), - (OperandSize::S128, Some(Instruction::LocalGet128(src))) => Some(*src), + (ValueLane::S32, Some(Instruction::LocalGet32(src))) => Some(*src), + (ValueLane::S64, Some(Instruction::LocalGet64(src))) => Some(*src), + (ValueLane::S128, Some(Instruction::LocalGet128(src))) => Some(*src), _ => None, }; if let Some(src) = src { self.instructions.pop(); let instructions = match size { - OperandSize::S32 => [Instruction::LocalCopy32(src, local_idx), Instruction::LocalGet32(local_idx)], - OperandSize::S64 => [Instruction::LocalCopy64(src, local_idx), Instruction::LocalGet64(local_idx)], - OperandSize::S128 => [Instruction::LocalCopy128(src, local_idx), Instruction::LocalGet128(local_idx)], + ValueLane::S32 => [Instruction::LocalCopy32(src, local_idx), Instruction::LocalGet32(local_idx)], + ValueLane::S64 => [Instruction::LocalCopy64(src, local_idx), Instruction::LocalGet64(local_idx)], + ValueLane::S128 => [Instruction::LocalCopy128(src, local_idx), Instruction::LocalGet128(local_idx)], }; self.instructions.extend(instructions); } else { - self.instructions.push(size.choose( + self.instructions.push(size.select( Instruction::LocalTee32(local_idx), Instruction::LocalTee64(local_idx), Instruction::LocalTee128(local_idx), @@ -669,7 +643,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; self.instructions.push(Instruction::JumpIfZero32(0)); self.push_control(BlockKind::If, ty, Some(self.instructions.len() - 1)) } @@ -721,7 +695,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_br_if(&mut self, depth: u32) -> Self::Output { - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; let cond_jump_ip = self.instructions.len(); self.instructions.push(Instruction::JumpIfZero32(0)); @@ -745,7 +719,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { let ts = targets.targets().collect::, wasmparser::Error>>()?; - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; let default_depth = targets.default(); let len = ts.len() as u32; @@ -819,24 +793,24 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_f32_const(&mut self, val: wasmparser::Ieee32) -> Self::Output { - self.emit(&[], &[OperandSize::S32], Instruction::Const32(val.bits() as i32)) + self.emit(&[], &[ValueLane::S32], Instruction::Const32(val.bits() as i32)) } fn visit_f64_const(&mut self, val: wasmparser::Ieee64) -> Self::Output { - self.emit(&[], &[OperandSize::S64], Instruction::Const64(val.bits() as i64)) + self.emit(&[], &[ValueLane::S64], Instruction::Const64(val.bits() as i64)) } fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { let dst = self.metadata.table_size(dst_table)?; let src = self.metadata.table_size(src_table)?; - let len = if dst == OperandSize::S32 || src == OperandSize::S32 { OperandSize::S32 } else { OperandSize::S64 }; + let len = if dst == ValueLane::S32 || src == ValueLane::S32 { ValueLane::S32 } else { ValueLane::S64 }; self.emit(&[dst, src, len], &[], Instruction::TableCopy { dst_table, src_table }) } fn visit_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Self::Output { let dst = self.metadata.memory_size(dst_mem)?; let src = self.metadata.memory_size(src_mem)?; - let len = if dst == OperandSize::S32 || src == OperandSize::S32 { OperandSize::S32 } else { OperandSize::S64 }; + let len = if dst == ValueLane::S32 || src == ValueLane::S32 { ValueLane::S32 } else { ValueLane::S64 }; self.emit(&[dst, src, len], &[], Instruction::MemoryCopy { dst_mem, src_mem }) } @@ -859,41 +833,41 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_br_on_null(&mut self, relative_depth: u32) -> Self::Output { - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; let fallthrough_jump = self.instructions.len(); self.instructions.push(Instruction::JumpIfRefNonNull(0)); self.emit_dropkeep_to_label(relative_depth)?; self.emit_branch_jump_or_return(relative_depth)?; self.patch_jump(fallthrough_jump, self.instructions.len()); - self.push_sizes(&[OperandSize::S32]) + self.push_sizes(&[ValueLane::S32]) } fn visit_br_on_non_null(&mut self, relative_depth: u32) -> Self::Output { - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; let fallthrough_jump = self.instructions.len(); self.instructions.push(Instruction::JumpIfRefNull(0)); - self.push_sizes(&[OperandSize::S32])?; + self.push_sizes(&[ValueLane::S32])?; self.emit_dropkeep_to_label(relative_depth)?; - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; self.emit_branch_jump_or_return(relative_depth)?; self.patch_jump(fallthrough_jump, self.instructions.len()); Ok(()) } fn visit_typed_select_multi(&mut self, tys: Vec) -> Self::Output { - let sizes: Vec<_> = tys.into_iter().map(OperandSize::from).collect(); + let sizes: Vec<_> = tys.into_iter().map(value_lane).collect(); let counts = Self::value_counts(&sizes); self.emit( - &[sizes.as_slice(), sizes.as_slice(), &[OperandSize::S32]].concat(), + &[sizes.as_slice(), sizes.as_slice(), &[ValueLane::S32]].concat(), &sizes, Instruction::SelectMulti(counts), ) } fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { - let size = OperandSize::from(ty); - let instruction = size.choose(Instruction::Select32, Instruction::Select64, Instruction::Select128); - self.emit(&[size, size, OperandSize::S32], &[size], instruction) + let size = value_lane(ty); + let instruction = size.select(Instruction::Select32, Instruction::Select64, Instruction::Select128); + self.emit(&[size, size, ValueLane::S32], &[size], instruction) } } @@ -1085,8 +1059,8 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuilder<'_> { fn visit_i8x16_shuffle(&mut self, lanes: [u8; 16]) -> Self::Output { self.emit( - &[OperandSize::S128, OperandSize::S128], - &[OperandSize::S128], + &[ValueLane::S128, ValueLane::S128], + &[ValueLane::S128], Instruction::I8x16Shuffle(self.data.v128_constants.len() as u32), )?; self.data.v128_constants.push(lanes); @@ -1094,7 +1068,7 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuilder<'_> { } fn visit_v128_const(&mut self, value: wasmparser::V128) -> Self::Output { - self.emit(&[], &[OperandSize::S128], Instruction::Const128(self.data.v128_constants.len() as u32))?; + self.emit(&[], &[ValueLane::S128], Instruction::Const128(self.data.v128_constants.len() as u32))?; self.data.v128_constants.push(*value.bytes()); Ok(()) } @@ -1107,11 +1081,11 @@ impl FunctionBuilder<'_> { target: wasmparser::RefType, branch_on_fail: bool, ) -> Result<()> { - self.pop_expect(OperandSize::S32)?; + self.pop_expect(ValueLane::S32)?; let target = convert_heap_type(target.heap_type(), target.is_nullable())?; let conditional_ip = self.instructions.len(); self.instructions.push(Instruction::BrOnCast(0, target, branch_on_fail)); - self.push_sizes(&[OperandSize::S32])?; + self.push_sizes(&[ValueLane::S32])?; self.emit_dropkeep_to_label(relative_depth)?; self.emit_branch_jump_or_return(relative_depth)?; self.patch_jump(conditional_ip, self.instructions.len()); @@ -1129,7 +1103,7 @@ impl FunctionBuilder<'_> { .ok_or_else(|| crate::ParseError::Other(alloc::format!("branch depth out of bounds: {depth}"))) } - fn local(&self, idx: u32) -> Result<(OperandSize, u16)> { + fn local(&self, idx: u32) -> Result<(ValueLane, u16)> { let size = *self .local_types .get(idx as usize) @@ -1142,12 +1116,12 @@ impl FunctionBuilder<'_> { } /// Pushes logical operands while maintaining the lane counts used by `DropKeep`. - fn push_sizes(&mut self, sizes: &[OperandSize]) -> Result<()> { + fn push_sizes(&mut self, sizes: &[ValueLane]) -> Result<()> { for &size in sizes { let count = match size { - OperandSize::S32 => &mut self.lane_counts.c32, - OperandSize::S64 => &mut self.lane_counts.c64, - OperandSize::S128 => &mut self.lane_counts.c128, + ValueLane::S32 => &mut self.lane_counts.c32, + ValueLane::S64 => &mut self.lane_counts.c64, + ValueLane::S128 => &mut self.lane_counts.c128, }; *count = count .checked_add(1) @@ -1158,7 +1132,7 @@ impl FunctionBuilder<'_> { } /// Pops an operand, allowing a polymorphic value at an unreachable frame base. - fn pop_expect(&mut self, expected: OperandSize) -> Result<()> { + fn pop_expect(&mut self, expected: ValueLane) -> Result<()> { let frame_height = self.control_stack.last().map_or(0, |frame| frame.height); if self.operand_stack.len() == frame_height && self.is_unreachable() { return Ok(()); @@ -1171,22 +1145,22 @@ impl FunctionBuilder<'_> { return Err(crate::ParseError::Other("logical operand width mismatch".into())); } match actual { - OperandSize::S32 => self.lane_counts.c32 -= 1, - OperandSize::S64 => self.lane_counts.c64 -= 1, - OperandSize::S128 => self.lane_counts.c128 -= 1, + ValueLane::S32 => self.lane_counts.c32 -= 1, + ValueLane::S64 => self.lane_counts.c64 -= 1, + ValueLane::S128 => self.lane_counts.c128 -= 1, } Ok(()) } /// Applies a declared logical stack effect in WebAssembly operand order. - fn apply_effect(&mut self, inputs: &[OperandSize], outputs: &[OperandSize]) -> Result<()> { + fn apply_effect(&mut self, inputs: &[ValueLane], outputs: &[ValueLane]) -> Result<()> { inputs.iter().rev().try_for_each(|&size| self.pop_expect(size))?; self.push_sizes(outputs)?; Ok(()) } /// Applies an instruction's stack effect before adding it to the bytecode. - fn emit(&mut self, inputs: &[OperandSize], outputs: &[OperandSize], instruction: Instruction) -> Result<()> { + fn emit(&mut self, inputs: &[ValueLane], outputs: &[ValueLane], instruction: Instruction) -> Result<()> { self.apply_effect(inputs, outputs)?; self.instructions.push(instruction); Ok(()) @@ -1212,9 +1186,7 @@ impl FunctionBuilder<'_> { fn push_control(&mut self, kind: BlockKind, ty: wasmparser::BlockType, initial_jump: Option) -> Result<()> { let signature = match ty { wasmparser::BlockType::Empty => Signature { params: Vec::new(), results: Vec::new() }, - wasmparser::BlockType::Type(ty) => { - Signature { params: Vec::new(), results: alloc::vec![OperandSize::from(ty)] } - } + wasmparser::BlockType::Type(ty) => Signature { params: Vec::new(), results: alloc::vec![value_lane(ty)] }, wasmparser::BlockType::FuncType(idx) => self.metadata.signature(idx)?.clone(), }; for &size in signature.params.iter().rev() { @@ -1263,13 +1235,13 @@ impl FunctionBuilder<'_> { } } - fn value_counts(sizes: &[OperandSize]) -> ValueCounts { + fn value_counts(sizes: &[ValueLane]) -> ValueCounts { let mut counts = ValueCounts::default(); for size in sizes { match size { - OperandSize::S32 => counts.c32 += 1, - OperandSize::S64 => counts.c64 += 1, - OperandSize::S128 => counts.c128 += 1, + ValueLane::S32 => counts.c32 += 1, + ValueLane::S64 => counts.c64 += 1, + ValueLane::S128 => counts.c128 += 1, } } counts diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 3a7c06d..6a399ee 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -224,9 +224,18 @@ impl Imports { for import in &*module.imports { let (val, func_handle) = if let Some(defined) = self.take_defined(import) { match defined { - Extern::Global(global) => (ExternVal::Global(global.0.addr), None), - Extern::Table(table) => (ExternVal::Table(table.0.addr), None), - Extern::Memory(memory) => (ExternVal::Memory(memory.0.addr), None), + Extern::Global(global) => { + global.0.validate_store(store)?; + (ExternVal::Global(global.0.addr), None) + } + Extern::Table(table) => { + table.0.validate_store(store)?; + (ExternVal::Table(table.0.addr), None) + } + Extern::Memory(memory) => { + memory.0.validate_store(store)?; + (ExternVal::Memory(memory.0.addr), None) + } Extern::Function(func) => (ExternVal::Func(func.addr()), Some(func)), } } else { @@ -246,11 +255,11 @@ impl Imports { match (val, &import.kind) { (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { - let global = store.state.get_global(global_addr); + let global_ty = store.state.globals.ty(global_addr); let expected = ty.with_ty(crate::store::canonicalize_value_type(ty.ty, type_addrs)); - let compatible = global.ty.mutable == ty.mutable - && store.state.value_type_is_subtype(global.ty.ty, expected.ty) - && (!ty.mutable || store.state.value_type_is_subtype(expected.ty, global.ty.ty)); + let compatible = global_ty.mutable == ty.mutable + && store.state.value_type_is_subtype(global_ty.ty, expected.ty) + && (!ty.mutable || store.state.value_type_is_subtype(expected.ty, global_ty.ty)); if !compatible { cold_path(); return Err(LinkingError::incompatible_import_type(import).into()); diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index fb8fdec..f864972 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -123,6 +123,16 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let val = <$ty>::stack_peek(&self.store.value_stack); <$ty>::local_set(&mut self.store.value_stack, &self.cf, *$local_index, val); }}; + (global_get $ty:ty, $global_index:expr) => {{ + let addr = self.module.resolve_global_addr(*$global_index); + let value = <$ty>::global_get(&self.store.state.globals, addr); + <$ty>::stack_push(&mut self.store.value_stack, value)?; + }}; + (global_set $ty:ty, $global_index:expr) => {{ + let addr = self.module.resolve_global_addr(*$global_index); + let value = <$ty>::stack_pop(&mut self.store.value_stack); + <$ty>::global_set(&mut self.store.state.globals, addr, value); + }}; } macro_rules! exec_binop { @@ -200,12 +210,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); self.store.value_stack.push(value)?; }}; - (stack_global $vt:ident, $width:tt, $op:ident, $global:ident) => {{ - let TinyWasmValue::$vt(global_val) = - self.store.state.get_global_val(self.module.resolve_global_addr(*$global)) - else { - unreachable!("expected global to be Value32") - }; + (stack_global $vt:ty, $width:tt, $op:ident, $global:ident) => {{ + let global_val = + <$vt>::global_get(&self.store.state.globals, self.module.resolve_global_addr(*$global)); let stack_val = <$vt>::stack_pop(&mut self.store.value_stack); self.store.value_stack.push(exec_binop!($width, *$op, stack_val, global_val))?; }}; @@ -339,10 +346,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { LocalTee32(local_index) => stack_op!(local_tee Value32, local_index), LocalTee64(local_index) => stack_op!(local_tee Value64, local_index), LocalTee128(local_index) => stack_op!(local_tee Value128, local_index), - GlobalGet(global_index) => self.exec_global_get(*global_index)?, - GlobalSet32(global_index) => self.exec_global_set_32(*global_index), - GlobalSet64(global_index) => self.exec_global_set::(*global_index), - GlobalSet128(global_index) => self.exec_global_set::(*global_index), + GlobalGet32(global_index) => stack_op!(global_get Value32, global_index), + GlobalGet64(global_index) => stack_op!(global_get Value64, global_index), + GlobalGet128(global_index) => stack_op!(global_get Value128, global_index), + GlobalSet32(global_index) => stack_op!(global_set Value32, global_index), + GlobalSet64(global_index) => stack_op!(global_set Value64, global_index), + GlobalSet128(global_index) => stack_op!(global_set Value128, global_index), Const32(val) => self.exec_const(*val)?, Const64(val) => self.exec_const(*val)?, I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), @@ -1336,27 +1345,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } - fn exec_global_get(&mut self, global_index: u32) -> Result<(), Trap> { - self.store.value_stack.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) - } - - fn exec_global_set(&mut self, global_index: u32) { - let global_addr = self.module.resolve_global_addr(global_index); - let value = ::stack_pop(&mut self.store.value_stack).into(); - self.store.state.set_global_val(global_addr, value); - } - - fn exec_global_set_32(&mut self, global_index: u32) { - let global_addr = self.module.resolve_global_addr(global_index); - let raw = ::stack_pop(&mut self.store.value_stack); - let value = match self.store.state.get_global(global_addr).ty.ty { - WasmType::I32 | WasmType::F32 => TinyWasmValue::Value32(raw), - WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::from_raw(raw)), - WasmType::I64 | WasmType::F64 | WasmType::V128 => unreachable!("invalid global.set.32 target type"), - }; - self.store.state.set_global_val(global_addr, value); - } - fn exec_const(&mut self, val: T) -> Result<(), Trap> { self.store.value_stack.push(val) } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 378dc7f..3b5dfe2 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -8,6 +8,10 @@ use crate::interpreter::*; use crate::{Result, Trap}; #[cfg_attr(feature = "debug", derive(Debug))] +/// Physical value lanes used by the interpreter. +/// +/// Guest values should normally be accessed through +/// [`InternalValue`] so their stack and global representation stays consistent. pub(crate) struct ValueStack { pub(crate) stack_32: Stack, pub(crate) stack_64: Stack, diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index f034906..fae7876 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,6 +1,7 @@ use super::stack::{CallFrame, ValueStack}; +use crate::store::Globals; use crate::{Result, interpreter::simd::Value128}; -use tinywasm_types::{AnyRef, ExnRef, ExternRef, FuncRef, LocalAddr, RefValue, WasmType, WasmValue}; +use tinywasm_types::{AnyRef, ExnRef, ExternRef, FuncRef, GlobalAddr, LocalAddr, RefValue, WasmType, WasmValue}; pub(crate) type Value32 = u32; pub(crate) type Value64 = u64; @@ -179,7 +180,8 @@ mod sealed { pub trait Sealed {} } -pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + Default { +/// Typed access to values in their physical [`ValueStack`] and [`Globals`] lanes. +pub(crate) trait InternalValue: sealed::Sealed + Copy + Default { fn stack_push(stack: &mut ValueStack, value: Self) -> Result<(), crate::Trap>; fn stack_pop(stack: &mut ValueStack) -> Self; fn stack_peek(stack: &ValueStack) -> Self; @@ -188,12 +190,16 @@ pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + De fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, f: impl FnOnce(Self) -> Self); fn local_copy(stack: &mut ValueStack, frame: &CallFrame, from: LocalAddr, to: LocalAddr); + /// Gets a value from its physical global lane. + fn global_get(globals: &Globals, addr: GlobalAddr) -> Self; + /// Sets a value in its physical global lane. + fn global_set(globals: &mut Globals, addr: GlobalAddr, value: Self); } macro_rules! impl_internalvalue { ( $( - $variant:ident, $stack:ident, $stack_base:ident, $outer:ty, + $variant:ident, $stack:ident, $stack_base:ident, $global_get:ident, $global_set:ident, $outer:ty, |$to_value_v:ident| $to_value:expr, |$to_stack_v:ident| $to_stack:expr, |$from_stack_v:ident| $from_stack:expr @@ -248,6 +254,18 @@ macro_rules! impl_internalvalue { stack.$stack.copy(base + from as usize, base + to as usize); } + #[inline(always)] + fn global_get(globals: &Globals, addr: GlobalAddr) -> Self { + let $from_stack_v = globals.$global_get(addr); + $from_stack + } + + #[inline(always)] + fn global_set(globals: &mut Globals, addr: GlobalAddr, value: Self) { + let $to_stack_v = value; + globals.$global_set(addr, $to_stack); + } + #[inline(always)] fn stack_pop(stack: &mut ValueStack) -> Self { let $from_stack_v = stack.$stack.pop(); @@ -278,12 +296,12 @@ macro_rules! impl_internalvalue { } impl_internalvalue! { - Value32, stack_32, s32, u32, |v| v, |v| v, |v| v - Value64, stack_64, s64, u64, |v| v, |v| v, |v| v - Value32, stack_32, s32, i32, |v| v as u32, |v| v as u32, |v| v as i32 - Value64, stack_64, s64, i64, |v| v as u64, |v| v as u64, |v| v as i64 - Value32, stack_32, s32, f32, |v| f32::to_bits(v), |v| f32::to_bits(v), |v| f32::from_bits(v) - Value64, stack_64, s64, f64, |v| f64::to_bits(v), |v| f64::to_bits(v), |v| f64::from_bits(v) - ValueRef, stack_32, s32, ValueRef, |v| v, |v| v.raw(), |v| ValueRef(v) - Value128, stack_128, s128, Value128, |v| v, |v| v, |v| v + Value32, stack_32, s32, get_32, set_32, u32, |v| v, |v| v, |v| v + Value64, stack_64, s64, get_64, set_64, u64, |v| v, |v| v, |v| v + Value32, stack_32, s32, get_32, set_32, i32, |v| v as u32, |v| v as u32, |v| v as i32 + Value64, stack_64, s64, get_64, set_64, i64, |v| v as u64, |v| v as u64, |v| v as i64 + Value32, stack_32, s32, get_32, set_32, f32, |v| f32::to_bits(v), |v| f32::to_bits(v), |v| f32::from_bits(v) + Value64, stack_64, s64, get_64, set_64, f64, |v| f64::to_bits(v), |v| f64::to_bits(v), |v| f64::from_bits(v) + ValueRef, stack_32, s32, get_32, set_32, ValueRef, |v| v, |v| v.raw(), |v| ValueRef(v) + Value128, stack_128, s128, get_128, set_128, Value128, |v| v, |v| v, |v| v } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index d8f9669..ad9cfd9 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -6,9 +6,9 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use crate::interpreter::TinyWasmValue; -use crate::store::{GlobalInstance, TableInstance}; +use crate::store::TableInstance; use crate::{Error, MemoryInstance, Result, Store, Trap}; -use tinywasm_types::{Addr, GlobalAddr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue}; +use tinywasm_types::{Addr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue}; #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] @@ -460,28 +460,20 @@ impl Global { cold_path(); return Err(Error::Other("invalid global value type".to_string())); } - let addr = store.state.globals.len() as GlobalAddr; - store.state.globals.push(GlobalInstance::new(ty, value.into())); + let addr = store.state.globals.push(ty, value.into()); Ok(Self(StoreItem::new(store.id(), addr))) } - #[inline] - fn instance<'a>(&self, store: &'a Store) -> Result<&'a GlobalInstance> { - self.0.validate_store(store)?; - Ok(store.state.get_global(self.0.addr)) - } - /// Get the type of the global. pub fn ty(&self, store: &Store) -> Result { - Ok(self.instance(store)?.ty) + self.0.validate_store(store)?; + Ok(store.state.globals.ty(self.0.addr)) } /// Get the current value of the global. pub fn get(&self, store: &Store) -> Result { - let global = self.instance(store)?; - let value = store.state.attach_value(global.value, global.ty.ty); - let value = - value.unwrap_or_else(|| unreachable!("Global value type does not match global type, this is a bug")); + self.0.validate_store(store)?; + let value = store.state.global_wasm_value(self.0.addr); if let WasmValue::Ref(value) = value { store.state.pin_host_ref(value); } @@ -491,16 +483,6 @@ impl Global { /// Set the current value of the global. pub fn set(&self, store: &mut Store, value: WasmValue) -> Result<()> { self.0.validate_store(store)?; - let global = store.state.get_global(self.0.addr); - if !global.ty.mutable { - cold_path(); - return Err(Error::Other("global is immutable".to_string())); - } - if !store.state.value_matches_type(value, global.ty.ty) { - cold_path(); - return Err(Error::Other("invalid global value type".to_string())); - } - store.state.get_global_mut(self.0.addr).value = value.into(); - Ok(()) + store.state.set_global_wasm_value(self.0.addr, value) } } diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index 9280494..c685d89 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -1,17 +1,162 @@ -use crate::interpreter::TinyWasmValue; +use alloc::vec::Vec; use tinywasm_types::*; -/// A WebAssembly Global Instance +use crate::interpreter::{TinyWasmValue, Value32, Value64, Value128, ValueRef}; + +struct GlobalLane { + values: Vec, + types: Vec, +} + +impl Default for GlobalLane { + fn default() -> Self { + Self { values: Vec::new(), types: Vec::new() } + } +} + +impl GlobalLane { + fn push(&mut self, ty: GlobalType, value: T) -> usize { + debug_assert_eq!(self.values.len(), self.types.len()); + let index = self.values.len(); + self.values.push(value); + self.types.push(ty); + index + } + + #[inline(always)] + fn get(&self, index: usize, addr: GlobalAddr) -> T { + *self.values.get(index).unwrap_or_else(|| unreachable!("invalid global address: {addr}")) + } + + #[inline(always)] + fn set(&mut self, index: usize, addr: GlobalAddr, value: T) { + *self.values.get_mut(index).unwrap_or_else(|| unreachable!("invalid global address: {addr}")) = value; + } + + fn ty(&self, index: usize, addr: GlobalAddr) -> GlobalType { + *self.types.get(index).unwrap_or_else(|| unreachable!("invalid global address: {addr}")) + } +} + +/// Global instances split into their physical value lanes. /// -/// See -#[cfg_attr(feature = "debug", derive(Debug))] -pub(crate) struct GlobalInstance { - pub(crate) value: TinyWasmValue, - pub(crate) ty: GlobalType, +/// Guest values should normally be accessed through +/// [`InternalValue`](crate::interpreter::InternalValue). Dynamic access is +/// reserved for initialization and host boundaries. +#[derive(Default)] +pub(crate) struct Globals { + globals_32: GlobalLane, + globals_64: GlobalLane, + globals_128: GlobalLane, } -impl GlobalInstance { - pub(crate) fn new(ty: GlobalType, value: TinyWasmValue) -> Self { - Self { ty, value } +impl Globals { + const LANE_SHIFT: u32 = 30; + const INDEX_MASK: u32 = (1 << Self::LANE_SHIFT) - 1; + const LANE_32: u32 = 0; + const LANE_64: u32 = 1 << Self::LANE_SHIFT; + const LANE_128: u32 = 2 << Self::LANE_SHIFT; + + fn addr(lane: u32, index: usize) -> GlobalAddr { + assert!(index <= Self::INDEX_MASK as usize, "too many globals in one value lane"); + lane | index as u32 + } + + #[inline(always)] + fn index(addr: GlobalAddr, lane: u32) -> usize { + debug_assert_eq!(addr & !Self::INDEX_MASK, lane, "global address has the wrong value lane"); + (addr & Self::INDEX_MASK) as usize + } + + /// Returns a global's logical type and mutability. + pub(crate) fn ty(&self, addr: GlobalAddr) -> GlobalType { + match addr & !Self::INDEX_MASK { + Self::LANE_32 => self.globals_32.ty(Self::index(addr, Self::LANE_32), addr), + Self::LANE_64 => self.globals_64.ty(Self::index(addr, Self::LANE_64), addr), + Self::LANE_128 => self.globals_128.ty(Self::index(addr, Self::LANE_128), addr), + _ => unreachable!("invalid global address: {addr}"), + } + } + + /// Adds a global and returns its packed store address. + pub(crate) fn push(&mut self, ty: GlobalType, value: TinyWasmValue) -> GlobalAddr { + match (ty.ty, value) { + (WasmType::I32 | WasmType::F32, TinyWasmValue::Value32(value)) => { + Self::addr(Self::LANE_32, self.globals_32.push(ty, value)) + } + (WasmType::Ref(_), TinyWasmValue::ValueRef(value)) => { + Self::addr(Self::LANE_32, self.globals_32.push(ty, value.raw())) + } + (WasmType::I64 | WasmType::F64, TinyWasmValue::Value64(value)) => { + Self::addr(Self::LANE_64, self.globals_64.push(ty, value)) + } + (WasmType::V128, TinyWasmValue::Value128(value)) => { + Self::addr(Self::LANE_128, self.globals_128.push(ty, value)) + } + _ => unreachable!("global value does not match its declared type"), + } + } + + /// Returns a dynamically represented global value. + pub(crate) fn get(&self, addr: GlobalAddr) -> TinyWasmValue { + let ty = self.ty(addr); + match ty.ty { + WasmType::I32 | WasmType::F32 => TinyWasmValue::Value32(self.get_32(addr)), + WasmType::I64 | WasmType::F64 => TinyWasmValue::Value64(self.get_64(addr)), + WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::from_raw(self.get_32(addr))), + WasmType::V128 => TinyWasmValue::Value128(self.get_128(addr)), + } + } + + /// Sets a global from a dynamically represented value. + pub(crate) fn set(&mut self, addr: GlobalAddr, value: TinyWasmValue) { + match (self.ty(addr).ty, value) { + (WasmType::I32 | WasmType::F32, TinyWasmValue::Value32(value)) => self.set_32(addr, value), + (WasmType::Ref(_), TinyWasmValue::ValueRef(value)) => self.set_32(addr, value.raw()), + (WasmType::I64 | WasmType::F64, TinyWasmValue::Value64(value)) => self.set_64(addr, value), + (WasmType::V128, TinyWasmValue::Value128(value)) => self.set_128(addr, value), + _ => unreachable!("global value does not match its declared type"), + } + } + + /// Returns a raw value from the 32-bit lane. + #[inline(always)] + pub(crate) fn get_32(&self, addr: GlobalAddr) -> Value32 { + self.globals_32.get(Self::index(addr, Self::LANE_32), addr) + } + + /// Returns a raw value from the 64-bit lane. + #[inline(always)] + pub(crate) fn get_64(&self, addr: GlobalAddr) -> Value64 { + self.globals_64.get(Self::index(addr, Self::LANE_64), addr) + } + + /// Returns a raw value from the 128-bit lane. + #[inline(always)] + pub(crate) fn get_128(&self, addr: GlobalAddr) -> Value128 { + self.globals_128.get(Self::index(addr, Self::LANE_128), addr) + } + + /// Sets a raw value in the 32-bit lane. + #[inline(always)] + pub(crate) fn set_32(&mut self, addr: GlobalAddr, value: Value32) { + self.globals_32.set(Self::index(addr, Self::LANE_32), addr, value); + } + + /// Sets a raw value in the 64-bit lane. + #[inline(always)] + pub(crate) fn set_64(&mut self, addr: GlobalAddr, value: Value64) { + self.globals_64.set(Self::index(addr, Self::LANE_64), addr, value); + } + + /// Sets a raw value in the 128-bit lane. + #[inline(always)] + pub(crate) fn set_128(&mut self, addr: GlobalAddr, value: Value128) { + self.globals_128.set(Self::index(addr, Self::LANE_128), addr, value); + } + + /// Iterates over globals in the 32-bit lane for root tracing. + pub(crate) fn globals_32(&self) -> impl Iterator + '_ { + self.globals_32.values.iter().copied().zip(self.globals_32.types.iter().copied()) } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 84c4033..3c80e91 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -72,7 +72,8 @@ impl Store { /// Create a new store pub fn new(engine: Engine) -> Self { let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - let state = State { gc: gc::GcHeap::new(engine.config().gc_collection_threshold), ..State::default() }; + let state = + State { gc: Box::new(gc::GcHeap::new(engine.config().gc_collection_threshold)), ..State::default() }; Self { id, module_instances: Vec::new(), @@ -138,16 +139,16 @@ impl Store { self.module_instances.push(instance); } - /// Get the global at the actual index in the store + /// Gets a global by its opaque store address. #[doc(hidden)] pub fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { - self.state.get_global_val(addr) + self.state.globals.get(addr) } - /// Set the global at the actual index in the store + /// Sets a global by its opaque store address. #[doc(hidden)] pub fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { - self.state.set_global_val(addr, value); + self.state.globals.set(addr, value); } /// Returns whether a public value has the requested runtime type. @@ -232,9 +233,6 @@ impl Store { func_addrs: &[FuncAddr], type_addrs: &[TypeAddr], ) -> Result<()> { - let start = self.state.globals.len() as Addr; - out.extend(start..start + globals.len() as Addr); - for global in globals { let value = match self.eval_const(&global.init, out, func_addrs, type_addrs) { Ok(val) => val, @@ -244,7 +242,7 @@ impl Store { } }; let ty = global.ty.with_ty(canonicalize_value_type(global.ty.ty, type_addrs)); - self.state.globals.push(GlobalInstance::new(ty, value)); + out.push(self.state.globals.push(ty, value)); } Ok(()) @@ -439,7 +437,7 @@ impl Store { let addr = *module_global_addrs .get(index as usize) .ok_or_else(|| Error::Other(format!("global {index} not found")))?; - Ok(state.globals.get(addr as usize).ok_or_else(|| Error::Other(format!("global {addr} not found")))?.value) + Ok(state.globals.get(addr)) }; let func_ref = |index: u32| -> Result { let addr = *module_func_addrs diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index 125cc30..392035f 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -14,10 +14,10 @@ pub(crate) struct State { pub(crate) funcs: Vec, pub(crate) tables: Vec, pub(crate) memories: Vec, - pub(crate) globals: Vec, + pub(crate) globals: Globals, pub(crate) elements: Vec, pub(crate) data: Vec, - pub(crate) gc: gc::GcHeap, + pub(crate) gc: Box, } impl State { @@ -73,9 +73,9 @@ impl State { .map(ValueRef::from_raw) .chain( self.globals - .iter() - .filter(|global| Self::type_may_contain_gc_in(canonical_types, global.ty.ty)) - .filter_map(|global| global.value.as_ref()), + .globals_32() + .filter(|(_, ty)| Self::type_may_contain_gc_in(canonical_types, ty.ty)) + .map(|(value, _)| ValueRef::from_raw(value)), ) .chain( self.tables @@ -363,23 +363,45 @@ impl State { Self::get_mut(&mut self.elements, addr, "element") } - /// Get the global at the actual index in the store - pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { - Self::get(&self.globals, addr, "global") - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global_mut(&mut self, addr: GlobalAddr) -> &mut GlobalInstance { - Self::get_mut(&mut self.globals, addr, "global") - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { - self.get_global(addr).value + /// Converts a global directly to its public value representation. + pub(crate) fn global_wasm_value(&self, addr: GlobalAddr) -> WasmValue { + let ty = self.globals.ty(addr).ty; + match ty { + WasmType::I32 => WasmValue::I32(self.globals.get_32(addr) as i32), + WasmType::I64 => WasmValue::I64(self.globals.get_64(addr) as i64), + WasmType::F32 => WasmValue::F32(f32::from_bits(self.globals.get_32(addr))), + WasmType::F64 => WasmValue::F64(f64::from_bits(self.globals.get_64(addr))), + WasmType::Ref(_) => self + .attach_value(TinyWasmValue::ValueRef(ValueRef::from_raw(self.globals.get_32(addr))), ty) + .unwrap_or_else(|| unreachable!("global value does not match its type")), + WasmType::V128 => WasmValue::V128(self.globals.get_128(addr).to_le_bytes()), + } } - /// Set the global at the actual index in the store - pub(crate) fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { - self.get_global_mut(addr).value = value; + /// Validates and sets a global from its public value representation. + pub(crate) fn set_global_wasm_value(&mut self, addr: GlobalAddr, value: WasmValue) -> Result<()> { + let ty = self.globals.ty(addr); + if !ty.mutable { + cold_path(); + return Err(Error::other("global is immutable")); + } + if !self.value_matches_type(value, ty.ty) { + cold_path(); + return Err(Error::other("invalid global value type")); + } + match value { + WasmValue::I32(value) => self.globals.set_32(addr, value as u32), + WasmValue::I64(value) => self.globals.set_64(addr, value as u64), + WasmValue::F32(value) => self.globals.set_32(addr, value.to_bits()), + WasmValue::F64(value) => self.globals.set_64(addr, value.to_bits()), + WasmValue::Ref(value) => { + let TinyWasmValue::ValueRef(value) = TinyWasmValue::from(WasmValue::Ref(value)) else { + unreachable!("reference value did not convert to an internal reference") + }; + self.globals.set_32(addr, value.raw()); + } + WasmValue::V128(value) => self.globals.set_128(addr, value.into()), + } + Ok(()) } } diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs index 8577d73..6cae984 100644 --- a/crates/tinywasm/tests/store_ownership.rs +++ b/crates/tinywasm/tests/store_ownership.rs @@ -1,5 +1,6 @@ use eyre::Result; -use tinywasm::{ModuleInstance, Store}; +use tinywasm::types::{GlobalType, WasmType}; +use tinywasm::{Global, Imports, ModuleInstance, Store}; const MODULE_WAT: &str = r#" (module @@ -86,3 +87,18 @@ fn table_grow_rejects_wrong_store_with_invalid_store_error() -> Result<()> { Ok(()) } + +#[test] +fn global_import_rejects_wrong_store() -> Result<()> { + let wasm = wat::parse_str(r#"(module (import "env" "g" (global i32)))"#)?; + let module = tinywasm::parse_bytes(&wasm)?; + let mut owner_store = Store::default(); + let global = Global::new(&mut owner_store, GlobalType::new(WasmType::I32, false), 1.into())?; + let mut imports = Imports::default(); + imports.define("env", "g", global); + + let mut other_store = Store::default(); + let err = ModuleInstance::instantiate(&mut other_store, &module, Some(imports)).unwrap_err(); + assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore)); + Ok(()) +} diff --git a/crates/tinywasm/tests/typed_globals.rs b/crates/tinywasm/tests/typed_globals.rs new file mode 100644 index 0000000..80494e0 --- /dev/null +++ b/crates/tinywasm/tests/typed_globals.rs @@ -0,0 +1,93 @@ +use eyre::Result; +use tinywasm::types::{GlobalType, Instruction, RefValue, WasmType, WasmValue}; +use tinywasm::{Global, Imports, ModuleInstance, Store}; + +#[test] +fn globals_use_typed_instructions_and_roundtrip_values() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (global (mut i32) (i32.const 1)) + (global (mut f32) (f32.const 2)) + (global (mut i64) (i64.const 3)) + (global (mut f64) (f64.const 4)) + (global (mut v128) (v128.const i32x4 1 2 3 4)) + (global (mut funcref) (ref.null func)) + + (func (export "i32") (param i32) (result i32) + local.get 0 global.set 0 global.get 0) + (func (export "f32") (param f32) (result f32) + local.get 0 global.set 1 global.get 1) + (func (export "i64") (param i64) (result i64) + local.get 0 global.set 2 global.get 2) + (func (export "f64") (param f64) (result f64) + local.get 0 global.set 3 global.get 3) + (func (export "v128") (param v128) (result v128) + local.get 0 global.set 4 global.get 4) + (func (export "ref") (param funcref) (result funcref) + local.get 0 global.set 5 global.get 5) + (func (export "add-i32") (param i32) (result i32) + local.get 0 global.get 0 i32.add) + (func (export "add-i64") (param i64) (result i64) + local.get 0 global.get 2 i64.add) + ) + "#, + )?; + + let module = tinywasm::parse_bytes(&wasm)?; + let instructions = module.funcs.iter().flat_map(|func| func.instructions.iter()); + let (mut get32, mut get64, mut get128, mut fused32, mut fused64) = (0, 0, 0, 0, 0); + for instruction in instructions { + match instruction { + Instruction::GlobalGet32(_) => get32 += 1, + Instruction::GlobalGet64(_) => get64 += 1, + Instruction::GlobalGet128(_) => get128 += 1, + Instruction::BinOpStackGlobal32(..) => fused32 += 1, + Instruction::BinOpStackGlobal64(..) => fused64 += 1, + _ => {} + } + } + assert_eq!((get32, get64, get128), (3, 2, 1)); + assert_eq!((fused32, fused64), (1, 1)); + + let mut store = Store::default(); + let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + let cases = [ + ("i32", WasmValue::I32(11)), + ("f32", WasmValue::F32(12.5)), + ("i64", WasmValue::I64(13)), + ("f64", WasmValue::F64(14.5)), + ("v128", WasmValue::V128([15; 16])), + ("ref", WasmValue::Ref(RefValue::Null)), + ]; + for (name, value) in cases { + assert_eq!(instance.func_untyped(&store, name)?.call(&mut store, &[value])?, vec![value]); + } + assert_eq!(instance.func::(&store, "add-i32")?.call(&mut store, 2)?, 13); + assert_eq!(instance.func::(&store, "add-i64")?.call(&mut store, 2)?, 15); + + Ok(()) +} + +#[test] +fn imported_global_keeps_its_typed_store_address() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (import "env" "g" (global (mut i64))) + (func (export "roundtrip") (param i64) (result i64) + local.get 0 global.set 0 global.get 0) + ) + "#, + )?; + let module = tinywasm::parse_bytes(&wasm)?; + let mut store = Store::default(); + let global = Global::new(&mut store, GlobalType::new(WasmType::I64, true), WasmValue::I64(1))?; + let mut imports = Imports::default(); + imports.define("env", "g", global); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + + assert_eq!(instance.func::(&store, "roundtrip")?.call(&mut store, 42)?, 42); + assert_eq!(global.get(&store)?, WasmValue::I64(42)); + Ok(()) +} diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 1160fa5..b920ac7 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -239,10 +239,9 @@ pub enum Instruction { // > Variable Instructions // See - GlobalGet(GlobalAddr), - LocalGet32(LocalAddr), LocalSet32(LocalAddr), LocalTee32(LocalAddr), GlobalSet32(GlobalAddr), - LocalGet64(LocalAddr), LocalSet64(LocalAddr), LocalTee64(LocalAddr), GlobalSet64(GlobalAddr), - LocalGet128(LocalAddr), LocalSet128(LocalAddr), LocalTee128(LocalAddr), GlobalSet128(GlobalAddr), + GlobalGet32(GlobalAddr), LocalGet32(LocalAddr), LocalSet32(LocalAddr), LocalTee32(LocalAddr), GlobalSet32(GlobalAddr), + GlobalGet64(GlobalAddr), LocalGet64(LocalAddr), LocalSet64(LocalAddr), LocalTee64(LocalAddr), GlobalSet64(GlobalAddr), + GlobalGet128(GlobalAddr), LocalGet128(LocalAddr), LocalSet128(LocalAddr), LocalTee128(LocalAddr), GlobalSet128(GlobalAddr), // > Memory Instructions I32Load(MemoryArg), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index c461296..0fd4e81 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -362,6 +362,39 @@ impl ExternVal { } } +/// The physical storage lane used by a [`WasmType`]. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub enum ValueLane { + /// A 32-bit value or reference. + S32, + /// A 64-bit value. + S64, + /// A 128-bit SIMD value. + S128, +} + +impl ValueLane { + /// Selects one of three values based on this lane. + pub fn select(self, s32: T, s64: T, s128: T) -> T { + match self { + Self::S32 => s32, + Self::S64 => s64, + Self::S128 => s128, + } + } +} + +impl From<&WasmType> for ValueLane { + fn from(ty: &WasmType) -> Self { + match ty { + WasmType::I32 | WasmType::F32 | WasmType::Ref(_) => Self::S32, + WasmType::I64 | WasmType::F64 => Self::S64, + WasmType::V128 => Self::S128, + } + } +} + #[derive(Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -384,10 +417,10 @@ impl<'a> FromIterator<&'a WasmType> for ValueCounts { let mut counts = Self::default(); for ty in iter { - match ty { - WasmType::I32 | WasmType::F32 | WasmType::Ref(_) => counts.c32 += 1, - WasmType::I64 | WasmType::F64 => counts.c64 += 1, - WasmType::V128 => counts.c128 += 1, + match ValueLane::from(ty) { + ValueLane::S32 => counts.c32 += 1, + ValueLane::S64 => counts.c64 += 1, + ValueLane::S128 => counts.c128 += 1, } } counts From 8ca9c3580990bc973218d944f2262a7d98a8380e Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 8 Aug 2026 23:45:00 +0200 Subject: [PATCH 05/15] chore: general cleanup, fix performance regressions Signed-off-by: Henry --- crates/tinywasm/benches/tinywasm.rs | 2 +- crates/tinywasm/src/func.rs | 13 ++-- crates/tinywasm/src/interpreter/executor.rs | 31 +++++++--- crates/tinywasm/src/interpreter/simd/mod.rs | 7 ++- .../src/interpreter/stack/value_stack.rs | 21 ++++--- crates/tinywasm/src/interpreter/values.rs | 52 ++++++---------- crates/tinywasm/src/reference.rs | 35 +++++------ crates/tinywasm/src/store/memory/mod.rs | 40 ++++++++++-- crates/tinywasm/src/store/memory/vec.rs | 4 +- crates/tinywasm/src/store/mod.rs | 13 ++-- crates/tinywasm/src/store/state.rs | 61 ++++++++++--------- crates/tinywasm/src/store/types.rs | 9 +++ crates/types/src/types.rs | 6 +- 13 files changed, 160 insertions(+), 134 deletions(-) diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 9a4de80..67db886 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -23,7 +23,7 @@ fn tinywasm_from_twasm(twasm: &[u8]) -> Result { } fn tinywasm_run(module: &Module) -> Result<()> { - let engine = Engine::new(Config::default().with_memory_backend(MemoryBackend::paged(64 * 1024))); + let engine = Engine::new(Config::default()); let mut store = Store::new(engine); let mut imports = Imports::default(); imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(()))); diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 732cafc..c4409cd 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -278,6 +278,7 @@ impl FuncContext<'_> { self.store .get_module_instance(self.module_id) .unwrap_or_else(|| unreachable!("invalid module instance id in host function context: {}", self.module_id)) + .clone() } /// Get a memory export. @@ -566,13 +567,11 @@ fn collect_call_results( func_ty: &FuncType, ) -> Result> { debug_assert!(value_stack.len() >= func_ty.results().len()); // m values are on the top of the stack (Ensured by validation) - let mut res: Vec<_> = func_ty - .results() - .iter() - .rev() - .map(|&ty| state.attach_value(value_stack.pop_tinyvalue(ty), ty).expect("validated function result")) - .collect(); - res.reverse(); // reverse to get the original order + let results = func_ty.results(); + let mut res = vec![WasmValue::I32(0); results.len()]; + for (i, &ty) in results.iter().enumerate().rev() { + res[i] = value_stack.pop_wasmvalue(state, ty); + } Ok(res) } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index f864972..1c80dac 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -46,7 +46,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { pub(crate) fn new(store: &'store mut Store, cf: CallFrame, call_stack_base: u32) -> Self { let wasm_func = store.state.get_wasm_func(cf.func_addr); - let module = store.get_module_instance_internal(wasm_func.owner); + let module = store + .get_module_instance(wasm_func.owner) + .unwrap_or_else(|| unreachable!("invalid module instance")) + .clone(); Self { module, cf, func: wasm_func.func.clone(), store, call_stack_base } } @@ -1004,7 +1007,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.call_stack.push(self.cf)?; self.cf = CallFrame::new(func_addr, locals_base, wasm_func.func.locals); if wasm_func.owner != self.module.id() { - self.module = self.store.get_module_instance_internal(wasm_func.owner); + self.module = self + .store + .get_module_instance(wasm_func.owner) + .unwrap_or_else(|| unreachable!("invalid module instance")) + .clone(); } Ok(()) @@ -1023,7 +1030,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }; self.cf = CallFrame::new(func_addr, locals_base, wasm_func.func.locals); if wasm_func.owner != self.module.id() { - self.module = self.store.get_module_instance_internal(wasm_func.owner); + self.module = self + .store + .get_module_instance(wasm_func.owner) + .unwrap_or_else(|| unreachable!("invalid module instance")) + .clone(); } Ok(()) @@ -1039,8 +1050,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mut params = Vec::new(); params.try_reserve_exact(param_types.len()).map_err(|_| Trap::OutOfMemory)?; for &ty in param_types.iter().rev() { - let value = self.store.value_stack.pop_tinyvalue(ty); - params.push(self.store.state.attach_value(value, ty).expect("validated host argument")); + params.push(self.store.value_stack.pop_wasmvalue(&self.store.state, ty)); } params.reverse(); if params_may_gc { @@ -1094,13 +1104,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_call_self(&mut self) -> Result<(), Trap> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); - - self.store.call_stack.push(self.cf)?; let Ok(locals_base) = self.store.value_stack.enter_locals(&self.func.params, &self.func.locals) else { cold_path(); return Err(Trap::CallStackOverflow); }; - self.cf = CallFrame::new(self.cf.func_addr, locals_base, self.func.locals); + let new = CallFrame::new(self.cf.func_addr, locals_base, self.func.locals); + self.store.call_stack.push(core::mem::replace(&mut self.cf, new))?; Ok(()) } @@ -1195,7 +1204,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let wasm_func = self.store.state.get_wasm_func(caller.func_addr); self.func = wasm_func.func.clone(); if wasm_func.owner != self.module.id() { - self.module = self.store.get_module_instance_internal(wasm_func.owner); + self.module = self + .store + .get_module_instance(wasm_func.owner) + .unwrap_or_else(|| unreachable!("invalid module instance")) + .clone(); } self.cf = caller; false diff --git a/crates/tinywasm/src/interpreter/simd/mod.rs b/crates/tinywasm/src/interpreter/simd/mod.rs index b05769c..d54268f 100644 --- a/crates/tinywasm/src/interpreter/simd/mod.rs +++ b/crates/tinywasm/src/interpreter/simd/mod.rs @@ -13,8 +13,9 @@ use core::arch::wasm32 as wasm; use core::arch::wasm64 as wasm; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] /// A 128-bit SIMD value -pub struct Value128(pub(super) [u8; 16]); +pub struct Value128(pub(crate) [u8; 16]); impl From<[u8; 16]> for Value128 { fn from(bytes: [u8; 16]) -> Self { @@ -24,12 +25,12 @@ impl From<[u8; 16]> for Value128 { impl Value128 { #[inline(always)] - pub fn from_le_bytes(bytes: [u8; 16]) -> Self { + pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { Self(bytes) } #[inline(always)] - pub fn to_le_bytes(self) -> [u8; 16] { + pub const fn to_le_bytes(self) -> [u8; 16] { self.0 } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 3b5dfe2..852618d 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -244,20 +244,21 @@ impl ValueStack { pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<(), Trap> { 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_32.push(v.raw())?, + 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_32.push(v.raw()), } - Ok(()) } - pub(crate) fn pop_tinyvalue(&mut self, val_type: WasmType) -> TinyWasmValue { + pub(crate) fn pop_wasmvalue(&mut self, state: &crate::store::State, val_type: WasmType) -> WasmValue { match val_type { - WasmType::I32 | WasmType::F32 => TinyWasmValue::Value32(Value32::stack_pop(self)), - WasmType::I64 | WasmType::F64 => TinyWasmValue::Value64(Value64::stack_pop(self)), - WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::stack_pop(self)), - WasmType::V128 => TinyWasmValue::Value128(Value128::stack_pop(self)), + WasmType::I32 => WasmValue::I32(self.stack_32.pop() as i32), + WasmType::I64 => WasmValue::I64(self.stack_64.pop() as i64), + WasmType::F32 => WasmValue::F32(f32::from_bits(self.stack_32.pop())), + WasmType::F64 => WasmValue::F64(f64::from_bits(self.stack_64.pop())), + WasmType::Ref(ty) => WasmValue::Ref(state.to_ref_value(ValueRef::from_raw(self.stack_32.pop()), ty)), + WasmType::V128 => WasmValue::V128(self.stack_128.pop().0), } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index fae7876..6913605 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,12 +1,16 @@ use super::stack::{CallFrame, ValueStack}; use crate::store::Globals; use crate::{Result, interpreter::simd::Value128}; -use tinywasm_types::{AnyRef, ExnRef, ExternRef, FuncRef, GlobalAddr, LocalAddr, RefValue, WasmType, WasmValue}; +use tinywasm_types::{GlobalAddr, LocalAddr, RefValue, WasmValue}; pub(crate) type Value32 = u32; pub(crate) type Value64 = u64; #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Packed internal representation of a WebAssembly reference. +/// +/// Unlike the public [`RefValue`], this stores no explicit reference category. +/// Converting it back therefore requires the value's canonical reference type. pub(crate) struct ValueRef(u32); impl Default for ValueRef { @@ -71,6 +75,18 @@ impl ValueRef { } } +impl From for ValueRef { + fn from(value: RefValue) -> Self { + match value { + RefValue::Null => Self::NULL, + RefValue::Func(value) => Self::from_category_addr(value.addr()), + RefValue::Extern(value) => Self::from_raw(value.raw()), + RefValue::Exn(value) => Self::from_category_addr(value.addr()), + RefValue::Any(value) => Self::from_raw(value.raw()), + } + } +} + #[allow(private_interfaces)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// A untyped WebAssembly value @@ -118,32 +134,6 @@ impl TinyWasmValue { _ => None, } } - - /// Converts this internal value to the requested public WebAssembly type. - pub fn attach_type(self, ty: WasmType) -> Option { - match (self, ty) { - (Self::Value32(v), WasmType::I32) => Some(WasmValue::I32(v as i32)), - (Self::Value64(v), WasmType::I64) => Some(WasmValue::I64(v as i64)), - (Self::Value32(v), WasmType::F32) => Some(WasmValue::F32(f32::from_bits(v))), - (Self::Value64(v), WasmType::F64) => Some(WasmValue::F64(f64::from_bits(v))), - (Self::ValueRef(v), WasmType::Ref(_)) if v.is_null() => Some(RefValue::Null.into()), - (Self::ValueRef(v), WasmType::Ref(ty)) if ty.is_func() => { - Some(WasmValue::Ref(RefValue::Func(FuncRef::new(v.addr()?)))) - } - (Self::ValueRef(v), WasmType::Ref(ty)) if ty.is_extern() => { - Some(WasmValue::Ref(RefValue::Extern(ExternRef::from_raw(v.raw())))) - } - (Self::ValueRef(v), WasmType::Ref(ty)) if ty.is_exn() => { - Some(WasmValue::Ref(RefValue::Exn(ExnRef::new(v.addr()?)))) - } - (Self::ValueRef(v), WasmType::Ref(_)) => Some(WasmValue::Ref(RefValue::Any(AnyRef::from_raw(v.raw())))), - (Self::Value128(v), WasmType::V128) => Some(WasmValue::V128(v.0)), - (_, WasmType::I32 | WasmType::F32) => None, - (_, WasmType::I64 | WasmType::F64) => None, - (_, WasmType::Ref(_)) => None, - (_, WasmType::V128) => None, - } - } } impl From<&WasmValue> for TinyWasmValue { @@ -153,11 +143,7 @@ impl From<&WasmValue> for TinyWasmValue { WasmValue::I64(v) => Self::Value64(*v as u64), WasmValue::F32(v) => Self::Value32(v.to_bits()), WasmValue::F64(v) => Self::Value64(v.to_bits()), - WasmValue::Ref(RefValue::Null) => Self::ValueRef(ValueRef::NULL), - WasmValue::Ref(RefValue::Func(value)) => Self::ValueRef(ValueRef::from_category_addr(value.addr())), - WasmValue::Ref(RefValue::Extern(value)) => Self::ValueRef(ValueRef::from_raw(value.raw())), - WasmValue::Ref(RefValue::Exn(value)) => Self::ValueRef(ValueRef::from_category_addr(value.addr())), - WasmValue::Ref(RefValue::Any(value)) => Self::ValueRef(ValueRef::from_raw(value.raw())), + WasmValue::Ref(value) => Self::ValueRef((*value).into()), WasmValue::V128(v) => Self::Value128((*v).into()), } } @@ -190,9 +176,7 @@ pub(crate) trait InternalValue: sealed::Sealed + Copy + Default { fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, f: impl FnOnce(Self) -> Self); fn local_copy(stack: &mut ValueStack, frame: &CallFrame, from: LocalAddr, to: LocalAddr); - /// Gets a value from its physical global lane. fn global_get(globals: &Globals, addr: GlobalAddr) -> Self; - /// Sets a value in its physical global lane. fn global_set(globals: &mut Globals, addr: GlobalAddr, value: Self); } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index ad9cfd9..00f966e 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -5,7 +5,7 @@ use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use crate::interpreter::TinyWasmValue; +use crate::interpreter::ValueRef; use crate::store::TableInstance; use crate::{Error, MemoryInstance, Result, Store, Trap}; use tinywasm_types::{Addr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue}; @@ -347,13 +347,14 @@ fn table_value_to_element( state: &crate::store::State, element_type: tinywasm_types::RefType, value: WasmValue, -) -> Result { - if !state.value_matches_type(value, WasmType::Ref(element_type)) { +) -> Result { + let WasmValue::Ref(value) = value else { + return Err(Trap::Other("invalid table value type")); + }; + if !state.value_matches_type(WasmValue::Ref(value), WasmType::Ref(element_type)) { return Err(Trap::Other("invalid table value type")); } - let WasmValue::Ref(value) = value else { unreachable!() }; - let TinyWasmValue::ValueRef(value) = TinyWasmValue::from(WasmValue::Ref(value)) else { unreachable!() }; - Ok(value) + Ok(value.into()) } impl Table { @@ -393,14 +394,9 @@ impl Table { /// Get a table element as a wasm reference value. pub fn get(&self, store: &Store, index: TableAddr) -> Result { let table = self.instance(store)?; - let value = store - .state - .attach_value(TinyWasmValue::ValueRef(*table.get(index as usize)?), WasmType::Ref(table.kind.element_type)) - .expect("table value matches its element type"); - if let WasmValue::Ref(value) = value { - store.state.pin_host_ref(value); - } - Ok(value) + let value = store.state.to_ref_value(*table.get(index as usize)?, table.kind.element_type); + store.state.pin_host_ref(value); + Ok(WasmValue::Ref(value)) } /// Load a range of table elements and iterate over wasm reference values. @@ -413,15 +409,12 @@ impl Table { let table = self.instance(store)?; let element_type = table.kind.element_type; let elements = table.load(offset, len)?; - let may_contain_gc = store.state.type_may_contain_gc(WasmType::Ref(element_type)); + let may_contain_gc = store.state.type_may_contain_gc(&WasmType::Ref(element_type)); Ok(elements.iter().copied().map(move |value| { if may_contain_gc { store.state.gc.pin(value); } - store - .state - .attach_value(TinyWasmValue::ValueRef(value), WasmType::Ref(element_type)) - .expect("table value matches its element type") + WasmValue::Ref(store.state.to_ref_value(value, element_type)) })) } @@ -473,7 +466,7 @@ impl Global { /// Get the current value of the global. pub fn get(&self, store: &Store) -> Result { self.0.validate_store(store)?; - let value = store.state.global_wasm_value(self.0.addr); + let value = store.state.get_global_wasmvalue(self.0.addr); if let WasmValue::Ref(value) = value { store.state.pin_host_ref(value); } @@ -483,6 +476,6 @@ impl Global { /// Set the current value of the global. pub fn set(&self, store: &mut Store, value: WasmValue) -> Result<()> { self.0.validate_store(store)?; - store.state.set_global_wasm_value(self.0.addr, value) + store.state.set_global_wasmvalue(self.0.addr, value) } } diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs index a3f0053..5805c86 100644 --- a/crates/tinywasm/src/store/memory/mod.rs +++ b/crates/tinywasm/src/store/memory/mod.rs @@ -377,13 +377,13 @@ macro_rules! impl_mem_traits { #[inline(always)] fn load_at(mem: &dyn LinearMemory, addr: usize) -> core::result::Result { - Ok(Self::from_le_bytes(match mem.$read(addr) { - Ok(bytes) => bytes, + match mem.$read(addr) { + Ok(bytes) => Ok(Self::from_le_bytes(bytes)), Err(trap) => { cold_path(); - return Err(trap); + Err(trap) } - })) + } } #[inline(always)] @@ -402,10 +402,38 @@ macro_rules! impl_mem_traits { impl_mem_traits!( u8, 1, read_8, write_8, i8, 1, read_8, write_8, u16, 2, read_16, write_16, i16, 2, read_16, write_16, u32, 4, read_32, write_32, i32, 4, read_32, write_32, f32, 4, read_32, write_32, u64, 8, read_64, write_64, i64, 8, - read_64, write_64, f64, 8, read_64, write_64, Value128, 16, read_128, write_128 + read_64, write_64, f64, 8, read_64, write_64 ); -fn memory_oob(offset: usize, len: usize, max: usize) -> crate::Trap { +impl MemValue<16> for Value128 { + #[inline(always)] + fn to_mem_bytes(self) -> [u8; 16] { + self.0 + } + + #[inline(always)] + fn from_mem_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } + + #[inline(always)] + fn load_at(mem: &dyn LinearMemory, addr: usize) -> core::result::Result { + match mem.read_128(addr) { + Ok(bytes) => Ok(Self(bytes)), + Err(trap) => { + cold_path(); + Err(trap) + } + } + } + + #[inline(always)] + fn store_at(self, mem: &mut dyn LinearMemory, addr: usize) -> core::result::Result<(), crate::Trap> { + mem.write_128(addr, &self.0) + } +} + +const fn memory_oob(offset: usize, len: usize, max: usize) -> crate::Trap { crate::Trap::MemoryOutOfBounds { offset, len, max } } diff --git a/crates/tinywasm/src/store/memory/vec.rs b/crates/tinywasm/src/store/memory/vec.rs index 2737159..aa468ec 100644 --- a/crates/tinywasm/src/store/memory/vec.rs +++ b/crates/tinywasm/src/store/memory/vec.rs @@ -35,7 +35,9 @@ impl VecMemory { #[inline(always)] fn read_fixed(&self, addr: usize) -> Result<[u8; N], crate::Trap> { self.check_fixed_addr::(addr)?; - Ok(self.data[addr..addr + N].try_into().unwrap_or_else(|_| unreachable!("slice length should be {N}"))) + let mut bytes = [0u8; N]; + bytes.copy_from_slice(&self.data[addr..addr + N]); + Ok(bytes) } #[inline(always)] diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 3c80e91..9d5622a 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -87,13 +87,8 @@ impl Store { } /// Get a module instance by the internal id - pub fn get_module_instance(&self, id: ModuleInstanceId) -> Option { - self.module_instances.get(id as usize).cloned() - } - - #[inline] - pub(crate) fn get_module_instance_internal(&self, id: ModuleInstanceId) -> ModuleInstance { - self.module_instances.get(id as usize).unwrap_or_else(|| unreachable!("invalid module instance: {id}")).clone() + pub fn get_module_instance(&self, id: ModuleInstanceId) -> Option<&ModuleInstance> { + self.module_instances.get(id as usize) } pub(crate) fn enter_execution(&mut self) -> Result<()> { @@ -168,11 +163,11 @@ impl Store { ) -> impl ExactSizeIterator { let start = self.state.funcs.len() as FuncAddr; debug_assert_eq!(funcs.len(), type_addrs.len()); - for (func, &type_addr) in funcs.iter().zip(type_addrs) { + for (func, &type_addr) in funcs.iter().cloned().zip(type_addrs) { self.state.funcs.push(FunctionInstance { type_addr, gc: self.state.func_gc_metadata(type_addr), - kind: FunctionKind::Wasm(WasmFunctionInstance { func: func.clone(), owner }), + kind: FunctionKind::Wasm(WasmFunctionInstance { func, owner }), }); } start..start + funcs.len() as FuncAddr diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index 392035f..5cb561a 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -22,11 +22,11 @@ pub(crate) struct State { impl State { /// Returns whether values of this type can contain a managed GC object. - pub(crate) fn type_may_contain_gc(&self, ty: WasmType) -> bool { + pub(crate) fn type_may_contain_gc(&self, ty: &WasmType) -> bool { Self::type_may_contain_gc_in(&self.canonical_types, ty) } - fn type_may_contain_gc_in(types: &[SubType], ty: WasmType) -> bool { + fn type_may_contain_gc_in(types: &[SubType], ty: &WasmType) -> bool { let WasmType::Ref(ty) = ty else { return false }; if let Some(type_addr) = ty.type_index() { return matches!(types[type_addr as usize].composite, CompositeType::Struct(_) | CompositeType::Array(_)); @@ -47,8 +47,8 @@ impl State { pub(crate) fn func_gc_metadata(&self, type_addr: TypeAddr) -> FunctionGcMetadata { let ty = self.get_canonical_func_type(type_addr); FunctionGcMetadata { - params: ty.params().iter().copied().any(|ty| self.type_may_contain_gc(ty)), - results: ty.results().iter().copied().any(|ty| self.type_may_contain_gc(ty)), + params: ty.params().iter().any(|ty| self.type_may_contain_gc(ty)), + results: ty.results().iter().any(|ty| self.type_may_contain_gc(ty)), } } @@ -74,21 +74,21 @@ impl State { .chain( self.globals .globals_32() - .filter(|(_, ty)| Self::type_may_contain_gc_in(canonical_types, ty.ty)) + .filter(|(_, ty)| Self::type_may_contain_gc_in(canonical_types, &ty.ty)) .map(|(value, _)| ValueRef::from_raw(value)), ) .chain( self.tables .iter() .filter(|table| { - Self::type_may_contain_gc_in(canonical_types, WasmType::Ref(table.kind.element_type)) + Self::type_may_contain_gc_in(canonical_types, &WasmType::Ref(table.kind.element_type)) }) .flat_map(|table| table.elements.iter().copied()), ) .chain( self.elements .iter() - .filter(|element| Self::type_may_contain_gc_in(canonical_types, WasmType::Ref(element.ty))) + .filter(|element| Self::type_may_contain_gc_in(canonical_types, &WasmType::Ref(element.ty))) .flat_map(|element| element.items.iter().flatten().copied()), ) .chain(values.iter().filter_map(|value| value.as_ref())); @@ -132,22 +132,30 @@ impl State { Ok(object) } - /// Converts an internal value using canonical reference type information. - pub(crate) fn attach_value(&self, value: TinyWasmValue, ty: WasmType) -> Option { - if let (TinyWasmValue::ValueRef(value), WasmType::Ref(reference_type)) = (value, ty) - && let Some(type_addr) = reference_type.type_index() - { - if value.is_null() { - return Some(RefValue::Null.into()); - } + /// Converts an internal reference using canonical heap type information. + pub(crate) fn to_ref_value(&self, value: ValueRef, ty: RefType) -> RefValue { + if value.is_null() { + return RefValue::Null; + } + + if let Some(type_addr) = ty.type_index() { return match &self.get_type(type_addr).composite { - CompositeType::Func(_) => Some(RefValue::Func(FuncRef::new(value.addr()?)).into()), - CompositeType::Struct(_) | CompositeType::Array(_) => { - Some(RefValue::Any(AnyRef::from_raw(value.raw())).into()) + CompositeType::Func(_) => { + RefValue::Func(FuncRef::new(value.addr().expect("non-null reference has an address"))) } + CompositeType::Struct(_) | CompositeType::Array(_) => RefValue::Any(AnyRef::from_raw(value.raw())), }; } - value.attach_type(ty) + if ty.is_func() { + return RefValue::Func(FuncRef::new(value.addr().expect("non-null reference has an address"))); + } + if ty.is_extern() { + return RefValue::Extern(ExternRef::from_raw(value.raw())); + } + if ty.is_exn() { + return RefValue::Exn(ExnRef::new(value.addr().expect("non-null reference has an address"))); + } + RefValue::Any(AnyRef::from_raw(value.raw())) } /// Returns whether one canonical type is a subtype of another. @@ -364,22 +372,20 @@ impl State { } /// Converts a global directly to its public value representation. - pub(crate) fn global_wasm_value(&self, addr: GlobalAddr) -> WasmValue { + pub(crate) fn get_global_wasmvalue(&self, addr: GlobalAddr) -> WasmValue { let ty = self.globals.ty(addr).ty; match ty { WasmType::I32 => WasmValue::I32(self.globals.get_32(addr) as i32), WasmType::I64 => WasmValue::I64(self.globals.get_64(addr) as i64), WasmType::F32 => WasmValue::F32(f32::from_bits(self.globals.get_32(addr))), WasmType::F64 => WasmValue::F64(f64::from_bits(self.globals.get_64(addr))), - WasmType::Ref(_) => self - .attach_value(TinyWasmValue::ValueRef(ValueRef::from_raw(self.globals.get_32(addr))), ty) - .unwrap_or_else(|| unreachable!("global value does not match its type")), + WasmType::Ref(ty) => WasmValue::Ref(self.to_ref_value(ValueRef::from_raw(self.globals.get_32(addr)), ty)), WasmType::V128 => WasmValue::V128(self.globals.get_128(addr).to_le_bytes()), } } /// Validates and sets a global from its public value representation. - pub(crate) fn set_global_wasm_value(&mut self, addr: GlobalAddr, value: WasmValue) -> Result<()> { + pub(crate) fn set_global_wasmvalue(&mut self, addr: GlobalAddr, value: WasmValue) -> Result<()> { let ty = self.globals.ty(addr); if !ty.mutable { cold_path(); @@ -394,12 +400,7 @@ impl State { WasmValue::I64(value) => self.globals.set_64(addr, value as u64), WasmValue::F32(value) => self.globals.set_32(addr, value.to_bits()), WasmValue::F64(value) => self.globals.set_64(addr, value.to_bits()), - WasmValue::Ref(value) => { - let TinyWasmValue::ValueRef(value) = TinyWasmValue::from(WasmValue::Ref(value)) else { - unreachable!("reference value did not convert to an internal reference") - }; - self.globals.set_32(addr, value.raw()); - } + WasmValue::Ref(value) => self.globals.set_32(addr, ValueRef::from(value).raw()), WasmValue::V128(value) => self.globals.set_128(addr, value.into()), } Ok(()) diff --git a/crates/tinywasm/src/store/types.rs b/crates/tinywasm/src/store/types.rs index 7cee56b..4954bba 100644 --- a/crates/tinywasm/src/store/types.rs +++ b/crates/tinywasm/src/store/types.rs @@ -39,6 +39,15 @@ fn map_field_type(field: FieldType, resolve: &mut impl FnMut(TypeAddr) -> TypeAd fn map_subtype(ty: &SubType, mut resolve: impl FnMut(TypeAddr) -> TypeAddr) -> SubType { let supertype = ty.supertype.map(&mut resolve); let composite = match &ty.composite { + CompositeType::Func(ty) + if !ty + .params() + .iter() + .chain(ty.results()) + .any(|ty| matches!(ty, WasmType::Ref(ty) if ty.is_concrete())) => + { + CompositeType::Func(ty.clone()) + } CompositeType::Func(ty) => { let params = ty.params().iter().copied().map(|ty| map_value_type(ty, &mut resolve)).collect::>(); let results = ty.results().iter().copied().map(|ty| map_value_type(ty, &mut resolve)).collect::>(); diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index 214ada6..0801849 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -1,4 +1,4 @@ -use alloc::boxed::Box; +use alloc::{boxed::Box, sync::Arc}; use crate::{TypeAddr, WasmType}; @@ -100,7 +100,7 @@ impl CompositeType { #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct FuncType { - data: Box<[WasmType]>, + data: Arc<[WasmType]>, param_count: u16, } @@ -108,7 +108,7 @@ impl FuncType { /// Create a new function type. pub fn new(params: &[WasmType], results: &[WasmType]) -> Self { let data: Box<[WasmType]> = params.iter().cloned().chain(results.iter().cloned()).collect(); - Self { data, param_count: params.len() as u16 } + Self { data: data.into(), param_count: params.len() as u16 } } /// Get the parameter types of this function type. From 9893a51d2712f82a2ee57316a66bc26500eb3fdd Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Aug 2026 16:11:57 +0200 Subject: [PATCH 06/15] feat: add reusable host imports and simplify function calls - make host definitions reusable across stores with gc-aware import linking - reorganize function calls, resumable execution, and typed value handling - expand typed tuples to arity 20 and update parser and store APIs Signed-off-by: Henry --- CHANGELOG.md | 9 +- Cargo.lock | 16 +- crates/cli/src/wast_runner.rs | 20 +- crates/parser/README.md | 6 +- crates/parser/src/lib.rs | 20 +- crates/tinywasm/benches/argon2id.rs | 2 +- crates/tinywasm/benches/fibonacci.rs | 2 +- crates/tinywasm/benches/tinywasm.rs | 8 +- crates/tinywasm/benches/tinywasm_modes.rs | 6 +- crates/tinywasm/src/func.rs | 892 ------------------ crates/tinywasm/src/func/context.rs | 151 +++ crates/tinywasm/src/func/host.rs | 259 +++++ crates/tinywasm/src/func/mod.rs | 201 ++++ crates/tinywasm/src/func/resume.rs | 265 ++++++ crates/tinywasm/src/func/values.rs | 201 ++++ crates/tinywasm/src/imports.rs | 113 ++- crates/tinywasm/src/instance.rs | 31 +- crates/tinywasm/src/interpreter/executor.rs | 20 +- .../src/interpreter/stack/value_stack.rs | 116 ++- crates/tinywasm/src/lib.rs | 6 +- crates/tinywasm/src/reference.rs | 22 +- crates/tinywasm/src/store/const_expr.rs | 194 ++++ crates/tinywasm/src/store/function.rs | 4 +- crates/tinywasm/src/store/global.rs | 19 + crates/tinywasm/src/store/mod.rs | 407 +++----- crates/tinywasm/src/store/state.rs | 21 +- .../tests/host_func_signature_check.rs | 83 +- .../tinywasm/tests/host_import_arg_order.rs | 17 +- crates/tinywasm/tests/import_linking.rs | 4 +- crates/tinywasm/tests/imported_table_init.rs | 4 +- crates/tinywasm/tests/internal_refs.rs | 11 +- crates/tinywasm/tests/memory_backends.rs | 2 +- crates/tinywasm/tests/store_ownership.rs | 2 +- crates/tinywasm/tests/typed_globals.rs | 2 +- examples/doom/src/runtime.rs | 22 +- examples/funcref_callbacks.rs | 21 +- examples/linking.rs | 2 +- examples/reentrance.rs | 4 +- examples/rust/src/tinywasm.rs | 4 +- examples/rust/src/tinywasm_no_std.rs | 6 +- examples/wasm-rust.rs | 20 +- 41 files changed, 1812 insertions(+), 1403 deletions(-) delete mode 100644 crates/tinywasm/src/func.rs create mode 100644 crates/tinywasm/src/func/context.rs create mode 100644 crates/tinywasm/src/func/host.rs create mode 100644 crates/tinywasm/src/func/mod.rs create mode 100644 crates/tinywasm/src/func/resume.rs create mode 100644 crates/tinywasm/src/func/values.rs create mode 100644 crates/tinywasm/src/store/const_expr.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef6fb5..4ae416e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- `HostFunction` is now a reusable definition, and module instantiation borrows `Imports` so host imports can be shared across stores. +- Host function callbacks now require `Send + Sync` so the same definition can be used safely with multiple stores. +- Typed function tuples now support up to 20 parameters or results. `WasmTupleChain` is deprecated. Use untyped functions for larger signatures. - Module types now use one dense recursive type space, while function types are resolved through `Function::ty(&Store)`. - Globals are stored in separate 32-bit, 64-bit, and 128-bit value lanes, avoiding tagged value conversion during guest execution. @@ -27,7 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes -- Renamed `ModuleInstanceAddr` to `ModuleInstanceId` for consistency. +- `HostFunction::from` and `HostFunction::from_untyped` no longer take a `Store` and now return reusable `HostFunction` definitions +- `ModuleInstance::instantiate` and `instantiate_no_start` now borrow `Imports`. +- `Store::id` now returns `u32` instead of `usize`. +- `Parser::new` now takes `ParserOptions`, use `Parser::default()` for default settings. `Parser::with_options` was removed. +- Renamed `ModuleInstanceAddr` to `ModuleInstanceId`. - Removed `HostFunction::ty` and `WasmFunction::ty`. Use `Function::ty(&Store)` for runtime function types. - Changed `TableType::element_type` and `Element::ty` from `WasmType` to `RefType`, and replaced module `table_types` with `TableDefinition { ty, init }`. diff --git a/Cargo.lock b/Cargo.lock index df0e601..5312e7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "shlex", @@ -369,9 +369,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "float-cmp" @@ -840,18 +840,18 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", diff --git a/crates/cli/src/wast_runner.rs b/crates/cli/src/wast_runner.rs index ad1742e..2ca622d 100644 --- a/crates/cli/src/wast_runner.rs +++ b/crates/cli/src/wast_runner.rs @@ -177,20 +177,20 @@ impl WastRunner { .define("spectest", "global_i64", global_i64) .define("spectest", "global_f32", global_f32) .define("spectest", "global_f64", global_f64) - .define("spectest", "print", HostFunction::from(store, |_ctx: tinywasm::FuncContext, (): ()| Ok(()))) - .define("spectest", "print_i32", HostFunction::from(store, |_ctx: tinywasm::FuncContext, _arg: i32| Ok(()))) - .define("spectest", "print_i64", HostFunction::from(store, |_ctx: tinywasm::FuncContext, _arg: i64| Ok(()))) - .define("spectest", "print_f32", HostFunction::from(store, |_ctx: tinywasm::FuncContext, _arg: f32| Ok(()))) - .define("spectest", "print_f64", HostFunction::from(store, |_ctx: tinywasm::FuncContext, _arg: f64| Ok(()))) + .define("spectest", "print", HostFunction::from(|_ctx: tinywasm::FuncContext, (): ()| Ok(()))) + .define("spectest", "print_i32", HostFunction::from(|_ctx: tinywasm::FuncContext, _arg: i32| Ok(()))) + .define("spectest", "print_i64", HostFunction::from(|_ctx: tinywasm::FuncContext, _arg: i64| Ok(()))) + .define("spectest", "print_f32", HostFunction::from(|_ctx: tinywasm::FuncContext, _arg: f32| Ok(()))) + .define("spectest", "print_f64", HostFunction::from(|_ctx: tinywasm::FuncContext, _arg: f64| Ok(()))) .define( "spectest", "print_i32_f32", - HostFunction::from(store, |_ctx: tinywasm::FuncContext, _args: (i32, f32)| Ok(())), + HostFunction::from(|_ctx: tinywasm::FuncContext, _args: (i32, f32)| Ok(())), ) .define( "spectest", "print_f64_f64", - HostFunction::from(store, |_ctx: tinywasm::FuncContext, _args: (f64, f64)| Ok(())), + HostFunction::from(|_ctx: tinywasm::FuncContext, _args: (f64, f64)| Ok(())), ); for (name, module) in modules { @@ -235,7 +235,7 @@ impl WastRunner { let (name, bytes) = encode_quote_wat(module); let module = parse_module_bytes(&bytes).expect("failed to parse module bytes"); let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); - let module_instance = ModuleInstance::instantiate(&mut store, &module, Some(imports)) + let module_instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports)) .expect("failed to instantiate module"); (name, module_instance) }) @@ -331,7 +331,7 @@ impl WastRunner { let module = parse_module_bytes(&wat.encode().expect("failed to encode module")) .expect("failed to parse module"); let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); - ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; return Ok(()); } wast::WastExecute::Get { .. } => panic!("get not supported"), @@ -376,7 +376,7 @@ impl WastRunner { let module = parse_module_bytes(&module.encode().expect("failed to encode module")) .expect("failed to parse module"); let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); - ModuleInstance::instantiate(&mut store, &module, Some(imports)) + ModuleInstance::instantiate(&mut store, &module, Some(&imports)) }); match res { Err(err) => test_group.add_result( diff --git a/crates/parser/README.md b/crates/parser/README.md index 05c2af2..cf1a6c4 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -16,10 +16,10 @@ use tinywasm_parser::{Parser, ParserOptions}; let bytes = include_bytes!("./file.wasm"); -let parser = Parser::new(); +let parser = Parser::default(); let module = parser.parse_module_bytes(bytes)?; -let parser = Parser::with_options(ParserOptions::default().with_rewrite_optimization(false)); +let parser = Parser::new(ParserOptions::default().with_rewrite_optimization(false)); let module = parser.parse_module_bytes(bytes)?; let module = parser.parse_module_file("path/to/file.wasm")?; @@ -27,4 +27,4 @@ let mut stream = std::fs::File::open("path/to/file.wasm")?; let module = parser.parse_module_stream(&mut stream)?; ``` -If you just want the default configuration, the top-level `parse_bytes`, `parse_file`, and `parse_stream` helpers are thin wrappers around `Parser::new()`. +If you just want the default configuration, the top-level `parse_bytes`, `parse_file`, and `parse_stream` helpers are thin wrappers around `Parser::default()`. diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 5439558..fe968d8 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -90,6 +90,11 @@ impl Default for ParserOptions { } impl ParserOptions { + /// Create parser options with default settings. + pub fn new() -> Self { + Self::default() + } + /// Enable or disable WebAssembly validation. /// /// Requires the `validate` feature to have any effect. @@ -152,13 +157,8 @@ pub struct Parser { } impl Parser { - /// Create a new parser instance - pub fn new() -> Self { - Self::default() - } - - /// Create a new parser with explicit options. - pub fn with_options(options: ParserOptions) -> Self { + /// Create a parser with the given options. + pub const fn new(options: ParserOptions) -> Self { Self { options } } @@ -352,17 +352,17 @@ impl TryFrom> for Module { /// Parse a module from bytes pub fn parse_bytes(wasm: &[u8]) -> Result { - Parser::new().parse_module_bytes(wasm) + Parser::default().parse_module_bytes(wasm) } #[cfg(feature = "std")] /// Parse a module from a file. Requires the `std` feature. pub fn parse_file(path: impl AsRef + Clone) -> Result { - Parser::new().parse_module_file(path) + Parser::default().parse_module_file(path) } #[cfg(feature = "std")] /// Parse a module from a stream. Requires the `std` feature. pub fn parse_stream(stream: impl crate::std::io::Read) -> Result { - Parser::new().parse_module_stream(stream) + Parser::default().parse_module_stream(stream) } diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index dbd86e5..d2845c5 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -6,7 +6,7 @@ use types::Module; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.wasm"); fn argon2id_parse() -> Result { - let parser = tinywasm_parser::Parser::new(); + let parser = tinywasm_parser::Parser::default(); let data = parser.parse_module_bytes(WASM)?; Ok(data) } diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index e0737da..1bf0791 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -6,7 +6,7 @@ use types::Module; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); fn fibonacci_parse() -> Result { - let parser = tinywasm_parser::Parser::new(); + let parser = tinywasm_parser::Parser::default(); let data = parser.parse_module_bytes(WASM)?; Ok(data) } diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 67db886..3560d3c 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -1,13 +1,13 @@ use criterion::{Criterion, criterion_group, criterion_main}; use eyre::Result; use tinywasm::engine::Config; -use tinywasm::{Engine, FuncContext, HostFunction, Imports, MemoryBackend, ModuleInstance, Store, types}; +use tinywasm::{Engine, FuncContext, HostFunction, Imports, ModuleInstance, Store, types}; use types::Module; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); fn tinywasm_parse() -> Result { - let parser = tinywasm_parser::Parser::new(); + let parser = tinywasm_parser::Parser::default(); let data = parser.parse_module_bytes(WASM)?; Ok(data) } @@ -26,8 +26,8 @@ fn tinywasm_run(module: &Module) -> Result<()> { let engine = Engine::new(Config::default()); let mut store = Store::new(engine); let mut imports = Imports::default(); - imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(()))); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); + imports.define("env", "printi32", HostFunction::from(|_: FuncContext<'_>, _: i32| Ok(()))); + let instance = ModuleInstance::instantiate(&mut store, module, Some(&imports)).expect("instantiate"); let hello = instance.func::<(), ()>(&store, "hello").expect("func_typed"); hello.call(&mut store, ()).expect("call"); Ok(()) diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs index ed89b7b..6c1badf 100644 --- a/crates/tinywasm/benches/tinywasm_modes.rs +++ b/crates/tinywasm/benches/tinywasm_modes.rs @@ -10,7 +10,7 @@ const TIME_BUDGET_PER_ROUND: core::time::Duration = core::time::Duration::from_m const BENCH_MEASUREMENT_TIME: core::time::Duration = core::time::Duration::from_secs(10); fn tinywasm_parse() -> Result { - let parser = tinywasm_parser::Parser::new(); + let parser = tinywasm_parser::Parser::default(); Ok(parser.parse_module_bytes(WASM)?) } @@ -21,9 +21,9 @@ fn setup_typed_func(module: &Module, engine: Option) -> Result<(Store, F }; let mut imports = Imports::default(); - imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _: i32| Ok(()))); + imports.define("env", "printi32", HostFunction::from(|_: FuncContext<'_>, _: i32| Ok(()))); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, module, Some(&imports))?; let func = instance.func::<(), ()>(&store, "hello")?; Ok((store, func)) } diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs deleted file mode 100644 index c4409cd..0000000 --- a/crates/tinywasm/src/func.rs +++ /dev/null @@ -1,892 +0,0 @@ -use crate::interpreter::stack::{CallFrame, ValueStack}; -use crate::reference::StoreItem; -use crate::{Error, FunctionInstance, InterpreterRuntime, Result, Store, Trap}; -use alloc::{borrow::Cow, boxed::Box, format, rc::Rc, vec, vec::Vec}; -use core::hint::cold_path; -use tinywasm_types::{ExternRef, FuncAddr, FuncRef, FuncType, ModuleInstanceId, TypeAddr, WasmType, WasmValue}; - -impl Function { - #[inline] - pub(crate) const fn addr(&self) -> FuncAddr { - self.item.addr - } - - /// Get this function's canonical type from its store. - /// - /// Concrete reference types are only meaningful in this store. - pub fn ty<'a>(&self, store: &'a Store) -> Result<&'a FuncType> { - self.item.validate_store(store)?; - Ok(store.state.get_func_type(self.addr())) - } - - /// Call a function (Invocation) - /// - /// See - #[inline] - pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { - #[inline] - fn call_inner(func: &Function, store: &mut Store, params: &[WasmValue]) -> Result> { - let func_instance = store.state.get_func(func.addr()).clone(); - let wasm_func = match &func_instance.kind { - crate::store::FunctionKind::Host(host_func) => { - let result = host_func.clone().call(FuncContext { store, module_id: func.module_id }, params)?; - return validate_host_results(store, func_instance.type_addr, result); - } - crate::store::FunctionKind::Wasm(wasm_func) => wasm_func, - }; - - // Reset stack, push args, allocate locals, create entry frame. - store.call_stack.clear(); - store.value_stack.clear(); - store.value_stack.extend_from_wasmvalues(params)?; - let locals_base = store.value_stack.enter_locals(&wasm_func.func.params, &wasm_func.func.locals)?; - let callframe = CallFrame::new(func.addr(), locals_base, wasm_func.func.locals); - - // Execute until completion and then collect result values from the stack. - InterpreterRuntime::exec(store, callframe, 0)?; - collect_call_results( - &mut store.value_stack, - &store.state, - store.state.get_canonical_func_type(func_instance.type_addr), - ) - } - - self.item.validate_store(store)?; - validate_call_params(&store.state, store.state.get_func_type(self.addr()), params)?; - let results_may_gc = store.state.get_func(self.addr()).gc.results; - - store.enter_execution()?; - let result = call_inner(self, store, params); - store.exit_execution(); - - if results_may_gc && let Ok(values) = &result { - store.state.pin_host_values(values); - } - - result - } - - /// Call a function and return a resumable execution handle. - /// - /// The returned handle keeps a mutable borrow of the [`Store`] until it - /// completes. Use [`FuncExecution::resume_with_fuel`] (or - /// `resume_with_time_budget` with `std`) to continue. - pub fn call_resumable<'store>( - &self, - store: &'store mut Store, - params: &[WasmValue], - ) -> Result> { - #[inline] - fn call_resumable_inner( - func: &Function, - store: &mut Store, - params: &[WasmValue], - ) -> Result { - let func_instance = store.state.get_func(func.addr()).clone(); - match &func_instance.kind { - crate::store::FunctionKind::Host(host_func) => { - let result = host_func.clone().call(FuncContext { store, module_id: func.module_id }, params)?; - let result = validate_host_results(store, func_instance.type_addr, result)?; - Ok(FuncExecutionState::Completed { result: Some(result) }) - } - crate::store::FunctionKind::Wasm(wasm_func) => { - store.call_stack.clear(); - store.value_stack.clear(); - store.value_stack.extend_from_wasmvalues(params)?; - let locals_base = store.value_stack.enter_locals(&wasm_func.func.params, &wasm_func.func.locals)?; - let callframe = CallFrame::new(func.addr(), locals_base, wasm_func.func.locals); - - Ok(FuncExecutionState::Running { - exec_state: ExecutionState { callframe }, - root_func_addr: func.addr(), - }) - } - } - } - - self.item.validate_store(store)?; - validate_call_params(&store.state, store.state.get_func_type(self.addr()), params)?; - let results_may_gc = store.state.get_func(self.addr()).gc.results; - - store.enter_execution()?; - let result = call_resumable_inner(self, store, params); - store.exit_execution(); - - let state = result?; - if results_may_gc && let FuncExecutionState::Completed { result: Some(values) } = &state { - store.state.pin_host_values(values); - } - Ok(FuncExecution { store, state }) - } -} - -#[derive(Clone, PartialEq, Eq)] -/// Progress for fuel-limited function execution. -pub enum ExecProgress { - /// Execution completed and produced a result. - Completed(T), - /// Execution suspended after exhausting fuel or time budget. - Suspended, -} - -#[derive(Clone)] -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -pub(crate) struct ExecutionState { - pub(crate) callframe: CallFrame, -} - -/// A function handle -#[derive(Clone)] -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -pub struct Function { - pub(crate) item: StoreItem, - pub(crate) module_id: ModuleInstanceId, -} - -/// A typed function handle -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -pub struct FunctionTyped { - /// The underlying function handle - pub func: Function, - pub(crate) marker: core::marker::PhantomData<(P, R)>, -} - -/// A host function -pub struct HostFunction { - pub(crate) func: HostFuncInner, -} - -impl HostFunction { - /// Call the function - pub fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result> { - (self.func)(ctx, args) - } - - /// Create a new untyped host function import. - /// - /// ## Example - /// ```rust - /// # fn main() -> tinywasm::Result<()> { - /// # use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; - /// # use tinywasm::types::{FuncType, WasmType, WasmValue}; - /// # let wasm = wat::parse_str(r#" - /// # (module - /// # (import "host" "add_one" (func $add_one (param i32) (result i32))) - /// # (func (export "call") (param i32) (result i32) - /// # local.get 0 - /// # call $add_one)) - /// # "#).expect("valid wat"); - /// # let module = tinywasm::parse_bytes(&wasm)?; - /// let mut store = Store::default(); - /// let ty = FuncType::new(&[WasmType::I32], &[WasmType::I32]); - /// let add_one = HostFunction::from_untyped(&mut store, &ty, |_ctx: FuncContext<'_>, args| { - /// let WasmValue::I32(value) = args[0] else { - /// return Err(tinywasm::Error::Other("expected i32".into())); - /// }; - /// Ok(vec![WasmValue::I32(value + 1)]) - /// }); - /// - /// let mut imports = Imports::new(); - /// imports.define("host", "add_one", add_one); - /// # let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; - /// # let call = instance.func::(&store, "call")?; - /// # assert_eq!(call.call(&mut store, 41)?, 42); - /// # Ok(()) - /// # } - /// ``` - pub fn from_untyped( - store: &mut Store, - ty: &FuncType, - func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static, - ) -> Function { - let type_addr = store.register_host_type(ty); - let addr = store.add_func(FunctionInstance { - type_addr, - gc: store.state.func_gc_metadata(type_addr), - kind: crate::store::FunctionKind::Host(Rc::new(Self { func: Box::new(func) })), - }); - Function { item: crate::StoreItem::new(store.id(), addr), module_id: 0 } - } - - /// Create a new typed host function import. - /// - /// ## Example - /// ```rust - /// # fn main() -> tinywasm::Result<()> { - /// # use tinywasm::{HostFunction, Imports, ModuleInstance, Store}; - /// # let wasm = wat::parse_str(r#" - /// # (module - /// # (import "host" "add_one" (func $add_one (param i32) (result i32))) - /// # (func (export "call") (param i32) (result i32) - /// # local.get 0 - /// # call $add_one)) - /// # "#).expect("valid wat"); - /// # let module = tinywasm::parse_bytes(&wasm)?; - /// let mut store = Store::default(); - /// let add_one = HostFunction::from(&mut store, |_ctx, value: i32| Ok(value + 1)); - /// - /// let mut imports = Imports::new(); - /// imports.define("host", "add_one", add_one); - /// # let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; - /// # let call = instance.func::(&store, "call")?; - /// # assert_eq!(call.call(&mut store, 41)?, 42); - /// # Ok(()) - /// # } - /// ``` - pub fn from(store: &mut Store, func: impl Fn(FuncContext<'_>, P) -> Result + 'static) -> Function - where - P: FromWasmValues + ToWasmTypes, - R: IntoWasmValues + ToWasmTypes, - { - let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { - Ok(func(ctx, P::from_wasm_values(args)?)?.into_wasm_values()) - }; - - let ty = tinywasm_types::FuncType::new(&P::wasm_types(), &R::wasm_types()); - let type_addr = store.register_host_type(&ty); - let addr = store.add_func(FunctionInstance { - type_addr, - gc: store.state.func_gc_metadata(type_addr), - kind: crate::store::FunctionKind::Host(Rc::new(Self { func: Box::new(inner_func) })), - }); - Function { item: crate::StoreItem::new(store.id(), addr), module_id: 0 } - } -} - -pub(crate) type HostFuncInner = Box, &[WasmValue]) -> Result>>; - -/// The context of a host-function call -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -pub struct FuncContext<'a> { - pub(crate) store: &'a mut crate::Store, - pub(crate) module_id: ModuleInstanceId, -} - -impl FuncContext<'_> { - /// Get the store. - pub fn store(&self) -> &crate::Store { - self.store - } - - /// Get mutable access to the store. - pub fn store_mut(&mut self) -> &mut crate::Store { - self.store - } - - /// Get the module instance. - pub fn module(&self) -> crate::ModuleInstance { - self.store - .get_module_instance(self.module_id) - .unwrap_or_else(|| unreachable!("invalid module instance id in host function context: {}", self.module_id)) - .clone() - } - - /// Get a memory export. - pub fn memory(&self, name: &str) -> Result { - self.module().memory(name) - } - - /// Get any exported extern value by name. - pub fn extern_item(&self, name: &str) -> Result { - self.module().extern_item(name) - } - - /// Get a table export. - pub fn table(&self, name: &str) -> Result { - self.module().table(name) - } - - /// Get the value of a global export. - pub fn global_get(&self, name: &str) -> Result { - self.module().global_get(self.store, name) - } - - /// Get a global export. - pub fn global(&self, name: &str) -> Result { - self.module().global(name) - } - - /// Set the value of a mutable global export. - pub fn global_set(&mut self, name: &str, value: WasmValue) -> Result<()> { - self.module().global_set(self.store, name, value) - } - - /// Charge additional fuel from the currently running resumable invocation. - /// - /// This is a no-op when the current invocation is not using fuel-based - /// resumption. - pub fn charge_fuel(&mut self, fuel: u32) { - self.store.execution_fuel = self.store.execution_fuel.saturating_sub(fuel); - } - - /// Get remaining fuel for the current invocation. - /// - /// Returns `0` when fuel-based resumption is not active. - pub fn remaining_fuel(&self) -> u32 { - self.store.execution_fuel - } - - /// Call a function from within the current host-function invocation. - /// - /// This is the safe way for host functions to perform blocking reentrant - /// calls into Wasm. Unlike [`Function::call`], it preserves the active - /// invocation's stacks and resumes the host caller after the nested call - /// completes. - /// - /// Nested calls are currently blocking only. If the surrounding invocation - /// is resumed with fuel or a time budget, this method does not suspend and - /// later continue the host function in the middle of the nested call. - pub fn call_untyped(&mut self, func: &Function, args: &[WasmValue]) -> Result> { - if !self.store.execution_active { - return Err(Error::other("FuncContext::call requires an active host-function invocation")); - } - - func.item.validate_store(self.store)?; - validate_call_params(&self.store.state, self.store.state.get_func_type(func.addr()), args)?; - - let func_instance = self.store.state.get_func(func.addr()).clone(); - let result = match func_instance.kind { - crate::store::FunctionKind::Host(host_func) => { - let result = - host_func.call(FuncContext { store: &mut *self.store, module_id: func.module_id }, args)?; - validate_host_results(self.store, func_instance.type_addr, result) - } - crate::store::FunctionKind::Wasm(wasm_func) => { - let call_stack_base = self.store.call_stack.len(); - let value_stack_base = self.store.value_stack.base(); - - self.store.value_stack.extend_from_wasmvalues(args).inspect_err(|_| { - self.store.value_stack.truncate_to_base(value_stack_base); - })?; - - let locals_base = self - .store - .value_stack - .enter_locals(&wasm_func.func.params, &wasm_func.func.locals) - .inspect_err(|_| self.store.value_stack.truncate_to_base(value_stack_base))?; - - let callframe = CallFrame::new(func.addr(), locals_base, wasm_func.func.locals); - InterpreterRuntime::exec(self.store, callframe, call_stack_base).inspect_err(|_| { - self.store.call_stack.truncate_to(call_stack_base); - self.store.value_stack.truncate_to_base(value_stack_base); - })?; - - collect_call_results( - &mut self.store.value_stack, - &self.store.state, - self.store.state.get_canonical_func_type(func_instance.type_addr), - ) - } - }; - if func_instance.gc.results - && let Ok(values) = &result - { - self.store.state.pin_host_values(values); - } - result - } - - /// Call a typed function from within the current host-function invocation. - /// - /// See [`Self::call_untyped`] for reentrancy and resumable-execution - /// limitations. - pub fn call(&mut self, func: &FunctionTyped, params: P) -> Result - where - P: IntoWasmValues, - R: FromWasmValues, - { - R::from_wasm_values(&self.call_untyped(&func.func, ¶ms.into_wasm_values())?) - } -} - -impl core::ops::Deref for FuncContext<'_> { - type Target = crate::Store; - - fn deref(&self) -> &Self::Target { - self.store - } -} - -impl core::ops::DerefMut for FuncContext<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.store - } -} - -impl<'a> FuncContext<'a> { - /// Create a new host function context. - pub const fn new(store: &'a mut crate::Store, module_id: ModuleInstanceId) -> Self { - Self { store, module_id } - } -} - -#[cfg(feature = "debug")] -impl core::fmt::Debug for HostFunction { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("HostFunction").field("func", &"...").finish() - } -} - -/// Resumable execution for an untyped function call. -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -pub struct FuncExecution<'store> { - store: &'store mut Store, - state: FuncExecutionState, -} - -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -enum FuncExecutionState { - Running { exec_state: ExecutionState, root_func_addr: u32 }, - Completed { result: Option> }, -} - -/// Resumable execution for a typed function call. -#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] -pub struct FuncExecutionTyped<'store, R> { - execution: FuncExecution<'store>, - marker: core::marker::PhantomData, -} - -impl<'store> FuncExecution<'store> { - fn resume( - &mut self, - run: impl FnOnce(&mut Store, CallFrame) -> Result, - ) -> Result>> { - let (callframe, root_func_addr) = match &mut self.state { - FuncExecutionState::Running { exec_state, root_func_addr } => (exec_state.callframe, *root_func_addr), - FuncExecutionState::Completed { result } => { - return match result.take() { - Some(res) => Ok(ExecProgress::Completed(res)), - None => Err(Error::other("execution already completed")), - }; - } - }; - - self.store.enter_execution()?; - let result = run(self.store, callframe); - self.store.exit_execution(); - - match result? { - crate::interpreter::ExecState::Completed => { - let func = self.store.state.get_func(root_func_addr); - let result_ty = func.type_addr; - let results_may_gc = func.gc.results; - self.state = FuncExecutionState::Completed { result: None }; - let values = collect_call_results( - &mut self.store.value_stack, - &self.store.state, - self.store.state.get_canonical_func_type(result_ty), - )?; - if results_may_gc { - self.store.state.pin_host_values(&values); - } - Ok(ExecProgress::Completed(values)) - } - crate::interpreter::ExecState::Suspended(callframe) => { - let FuncExecutionState::Running { exec_state, .. } = &mut self.state else { - unreachable!("invalid function execution state") - }; - exec_state.callframe = callframe; - Ok(ExecProgress::Suspended) - } - } - } - - /// Resume execution with up to `fuel` units of fuel. - /// - /// Fuel is accounted in chunks, so execution may overshoot the requested - /// fuel before returning [`ExecProgress::Suspended`] (currently the chunk size is 128 instructions between fuel checks, but this may change in the future). - /// - /// Returns [`ExecProgress::Suspended`] when fuel is exhausted, or - /// [`ExecProgress::Completed`] with the final values once the invocation - /// returns. - /// - /// Reentrant calls made by host functions through [`FuncContext::call`] are - /// currently blocking. They do not suspend and later resume the host - /// function in the middle of the nested call. - pub fn resume_with_fuel(&mut self, fuel: u32) -> Result>> { - self.resume(|store, callframe| InterpreterRuntime::exec_with_fuel(store, callframe, fuel)) - } - - #[cfg(feature = "std")] - /// Resume execution for at most `time_budget` wall-clock time. - /// - /// Time is checked periodically, so execution may overshoot the requested - /// time budget before returning [`ExecProgress::Suspended`] (currently time is checked every 128 instructions, but this may change in the future). - /// - /// Returns [`ExecProgress::Suspended`] when the budget is exhausted, or - /// [`ExecProgress::Completed`] with the final values once the invocation - /// returns. - /// - /// Reentrant calls made by host functions through [`FuncContext::call`] are - /// currently blocking. They do not suspend and later resume the host - /// function in the middle of the nested call. - pub fn resume_with_time_budget( - &mut self, - time_budget: crate::std::time::Duration, - ) -> Result>> { - self.resume(|store, callframe| InterpreterRuntime::exec_with_time_budget(store, callframe, time_budget)) - } -} - -fn validate_call_params(state: &crate::store::State, func_ty: &FuncType, params: &[WasmValue]) -> Result<()> { - if func_ty.params().len() != params.len() { - cold_path(); - return Err(Error::Other(format!( - "param count mismatch: expected {}, got {}", - func_ty.params().len(), - params.len() - ))); - } - - if !func_ty.params().iter().zip(params).all(|(ty, param)| state.value_matches_type(*param, *ty)) { - return Err(Error::other("Type mismatch")); - } - - Ok(()) -} - -pub(crate) fn validate_host_results( - store: &Store, - type_addr: TypeAddr, - result: Vec, -) -> Result> { - let expected = store.state.get_canonical_func_type(type_addr); - if result.len() == expected.results().len() - && result.iter().zip(expected.results()).all(|(&value, &ty)| store.state.value_matches_type(value, ty)) - { - return Ok(result); - } - - Err(Error::InvalidHostFnReturn { expected: Box::new(expected.clone()), actual: result }) -} - -fn collect_call_results( - value_stack: &mut ValueStack, - state: &crate::store::State, - func_ty: &FuncType, -) -> Result> { - debug_assert!(value_stack.len() >= func_ty.results().len()); // m values are on the top of the stack (Ensured by validation) - let results = func_ty.results(); - let mut res = vec![WasmValue::I32(0); results.len()]; - for (i, &ty) in results.iter().enumerate().rev() { - res[i] = value_stack.pop_wasmvalue(state, ty); - } - Ok(res) -} - -pub trait IntoWasmValues { - fn into_wasm_values(self) -> Vec; -} - -pub trait FromWasmValues: Sized { - fn from_wasm_values(values: &[WasmValue]) -> Result; -} - -impl FunctionTyped { - /// Call a typed function - pub fn call(&self, store: &mut Store, params: P) -> Result { - // Convert params into Vec - let wasm_values = params.into_wasm_values(); - - // Call the underlying WASM function - let result = self.func.call(store, &wasm_values)?; - - // Convert the Vec back to R - R::from_wasm_values(&result) - } - - /// Call a typed function and return a resumable execution handle. - /// - /// The handle keeps a mutable borrow of the [`Store`] until completion. - /// - /// ## Example - /// - /// ```rust - /// # fn main() -> tinywasm::Result<()> { - /// use tinywasm::{ExecProgress, ModuleInstance, Store}; - /// - /// let wasm = include_bytes!("../../../examples/wasm/add.wasm"); - /// let module = tinywasm::parse_bytes(wasm)?; - /// let mut store = Store::default(); - /// let instance = ModuleInstance::instantiate(&mut store, &module, None)?; - /// let add = instance.func::<(i32, i32), i32>(&store, "add")?; - /// - /// let mut execution = add.call_resumable(&mut store, (20, 22))?; - /// assert!(matches!(execution.resume_with_fuel(0)?, ExecProgress::Suspended)); - /// assert!(matches!(execution.resume_with_fuel(16)?, ExecProgress::Completed(42))); - /// # Ok(()) - /// # } - /// ``` - pub fn call_resumable<'store>(&self, store: &'store mut Store, params: P) -> Result> { - let wasm_values = params.into_wasm_values(); - let execution = self.func.call_resumable(store, &wasm_values)?; - Ok(FuncExecutionTyped { execution, marker: core::marker::PhantomData }) - } -} - -impl<'store, R: FromWasmValues> FuncExecutionTyped<'store, R> { - /// Resume typed execution with up to `fuel` units of fuel. - /// - /// Fuel is accounted in chunks, so execution may overshoot the requested - /// fuel before returning [`ExecProgress::Suspended`]. - pub fn resume_with_fuel(&mut self, fuel: u32) -> Result> { - match self.execution.resume_with_fuel(fuel)? { - ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_values(&values)?)), - ExecProgress::Suspended => Ok(ExecProgress::Suspended), - } - } - - #[cfg(feature = "std")] - /// Resume typed execution for at most `time_budget` wall-clock time. - /// - /// Time is checked periodically, so execution may overshoot the requested - /// time budget before returning [`ExecProgress::Suspended`]. - pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result> { - match self.execution.resume_with_time_budget(time_budget)? { - ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_values(&values)?)), - ExecProgress::Suspended => Ok(ExecProgress::Suspended), - } - } -} - -/// Describes the WebAssembly value types produced by a Rust value or tuple shape. -pub trait ToWasmTypes { - /// Static WebAssembly types for this shape. - /// - /// Implementations that require runtime construction may set this to `None`, - /// but must then override [`Self::wasm_types`]. - const WASM_TYPES: Option<&'static [WasmType]>; - - /// Return the flattened WebAssembly value types for this tuple shape. - fn wasm_types() -> Cow<'static, [WasmType]> { - Cow::Borrowed(Self::WASM_TYPES.expect("dynamic ToWasmTypes implementation must override wasm_types")) - } -} - -/// Describes the WebAssembly value types produced by a scalar Rust type. -pub trait ToWasmType { - /// The single WebAssembly value type for this scalar type. - const WASM_TYPE: WasmType; -} - -macro_rules! impl_scalar_wasm_traits { - ($($T:ty => $val_ty:expr),+ $(,)?) => { - $( - impl ToWasmType for $T { - const WASM_TYPE: WasmType = $val_ty; - } - - impl ToWasmTypes for $T { - const WASM_TYPES: Option<&'static [WasmType]> = Some(&[$val_ty]); - } - - impl IntoWasmValues for $T { - #[inline] - fn into_wasm_values(self) -> Vec { - vec![self.into()] - } - } - - impl FromWasmValues for $T { - #[inline] - fn from_wasm_values(values: &[WasmValue]) -> Result { - let value = *values.first().ok_or_else(|| { - core::hint::cold_path(); - Error::other("Not enough elements in &[WasmValue]") - })?; - - <$T>::try_from(value).map_err(|e| { - core::hint::cold_path(); - Error::Other(format!( - "FromWasmValues: Could not convert WasmValue to expected type: {e:?}" - )) - }) - } - } - )+ - }; -} - -macro_rules! impl_tuple_traits { - ($($T:ident),+) => { - impl<$($T),+> ToWasmTypes for ($($T,)+) - where - $($T: ToWasmType,)+ - { - const WASM_TYPES: Option<&'static [WasmType]> = Some(&[$($T::WASM_TYPE,)+]); - } - - impl<$($T),+> IntoWasmValues for ($($T,)+) - where - $($T: Into,)+ - { - #[allow(non_snake_case)] - #[inline] - fn into_wasm_values(self) -> Vec { - let ($($T,)+) = self; - vec![$($T.into(),)+] - } - } - - impl<$($T),+> FromWasmValues for ($($T,)+) - where - $($T: TryFrom,)+ - { - #[inline] - fn from_wasm_values(values: &[WasmValue]) -> Result { - let mut iter = values.iter(); - - Ok(($( - $T::try_from(*iter.next().ok_or(Error::other("Not enough values in WasmValue vector"))?) - .map_err(|e| Error::Other(format!("FromWasmValues: Could not convert WasmValue to expected type: {e:?}")))?, - )+)) - } - } - } -} - -macro_rules! impl_tuple { - ($macro:ident) => { - $macro!(T1); - $macro!(T1, T2); - $macro!(T1, T2, T3); - $macro!(T1, T2, T3, T4); - $macro!(T1, T2, T3, T4, T5); - $macro!(T1, T2, T3, T4, T5, T6); - $macro!(T1, T2, T3, T4, T5, T6, T7); - $macro!(T1, T2, T3, T4, T5, T6, T7, T8); - $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9); - $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); - $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); - $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); - }; -} - -impl_scalar_wasm_traits!( - i32 => WasmType::I32, - i64 => WasmType::I64, - f32 => WasmType::F32, - f64 => WasmType::F64, - FuncRef => WasmType::Ref(tinywasm_types::RefType::FUNCREF), - ExternRef => WasmType::Ref(tinywasm_types::RefType::EXTERNREF), -); -impl_tuple!(impl_tuple_traits); - -/// A helper type for using tuples of arbitrary number of elements as function parameters or results, -/// by concatenating the Wasm types of each element. -/// -/// This is useful when a function signature exceeds tuple arity 12. `tinywasm` only implements -/// direct tuple conversions up to arity 12, but `WasmTupleChain` lets you describe longer -/// signatures by combining smaller tuples at the type level. -/// -/// ## Example -/// ```rust -/// # fn main() -> tinywasm::Result<()> { -/// # use tinywasm::{ModuleInstance, Store, WasmTupleChain}; -/// # let wasm = wat::parse_str(r#" -/// # (module -/// # (func (export "echo13") -/// # (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) -/// # (result i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) -/// # local.get 0 -/// # local.get 1 -/// # local.get 2 -/// # local.get 3 -/// # local.get 4 -/// # local.get 5 -/// # local.get 6 -/// # local.get 7 -/// # local.get 8 -/// # local.get 9 -/// # local.get 10 -/// # local.get 11 -/// # local.get 12) -/// # ) -/// # "#).expect("valid wat"); -/// # let module = tinywasm::parse_bytes(&wasm)?; -/// # let mut store = Store::default(); -/// # let instance = ModuleInstance::instantiate(&mut store, &module, None)?; -/// -/// type Params = -/// WasmTupleChain<(i32, i32, i32, i32, i32, i32), (i32, i32, i32, i32, i32, i32, i32)>; -/// type Results = -/// WasmTupleChain<(i32, i32, i32, i32, i32, i32), (i32, i32, i32, i32, i32, i32, i32)>; -/// -/// let echo13 = instance.func::(&store, "echo13")?; -/// let result = echo13.call(&mut store, ((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12, 13)).into())?; -/// assert_eq!(result.into_inner(), ((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12, 13))); -/// # Ok(()) -/// # } -/// ``` -#[derive(Default)] -pub struct WasmTupleChain(T1, T2); - -impl WasmTupleChain { - /// Create a new concatenated tuple wrapper. - pub const fn new(left: T1, right: T2) -> Self { - Self(left, right) - } - - /// Split the wrapper back into its two component values. - pub fn into_inner(self) -> (T1, T2) { - (self.0, self.1) - } -} - -impl From<(T1, T2)> for WasmTupleChain { - fn from((left, right): (T1, T2)) -> Self { - Self::new(left, right) - } -} - -impl ToWasmTypes for WasmTupleChain { - const WASM_TYPES: Option<&'static [WasmType]> = None; - - #[inline] - fn wasm_types() -> Cow<'static, [WasmType]> { - let mut types = Vec::new(); - types.extend_from_slice(&T1::wasm_types()); - types.extend_from_slice(&T2::wasm_types()); - Cow::Owned(types) - } -} - -impl IntoWasmValues for WasmTupleChain { - #[inline] - fn into_wasm_values(self) -> Vec { - let (left, right) = self.into_inner(); - let mut values = Vec::new(); - values.extend(left.into_wasm_values()); - values.extend(right.into_wasm_values()); - values - } -} - -impl FromWasmValues for WasmTupleChain { - #[inline] - fn from_wasm_values(values: &[WasmValue]) -> Result { - let left_len = T1::wasm_types().len(); - let left = T1::from_wasm_values(&values[..values.len().min(left_len)])?; - let right = T2::from_wasm_values(values.get(left_len..).unwrap_or(&[]))?; - Ok(Self::new(left, right)) - } -} - -impl ToWasmTypes for () { - const WASM_TYPES: Option<&'static [WasmType]> = Some(&[]); -} - -impl IntoWasmValues for () { - #[inline] - fn into_wasm_values(self) -> Vec { - vec![] - } -} - -impl FromWasmValues for () { - #[inline] - fn from_wasm_values(_values: &[WasmValue]) -> Result { - Ok(()) - } -} diff --git a/crates/tinywasm/src/func/context.rs b/crates/tinywasm/src/func/context.rs new file mode 100644 index 0000000..95a2cec --- /dev/null +++ b/crates/tinywasm/src/func/context.rs @@ -0,0 +1,151 @@ +use alloc::vec::Vec; +use tinywasm_types::{ModuleInstanceId, WasmValue}; + +use crate::{Error, FromWasmValues, Function, FunctionTyped, IntoWasmValues, Result}; + +/// The context of a host-function call +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FuncContext<'a> { + pub(crate) store: &'a mut crate::Store, + pub(crate) module_id: ModuleInstanceId, +} + +impl FuncContext<'_> { + /// Get the store. + pub fn store(&self) -> &crate::Store { + self.store + } + + /// Get mutable access to the store. + pub fn store_mut(&mut self) -> &mut crate::Store { + self.store + } + + /// Get the module instance. + pub fn module(&self) -> crate::ModuleInstance { + self.store + .get_module_instance(self.module_id) + .unwrap_or_else(|| unreachable!("invalid module instance id in host function context: {}", self.module_id)) + .clone() + } + + /// Get a memory export. + pub fn memory(&self, name: &str) -> Result { + self.module().memory(name) + } + + /// Get any exported extern value by name. + pub fn extern_item(&self, name: &str) -> Result { + self.module().extern_item(name) + } + + /// Get a table export. + pub fn table(&self, name: &str) -> Result { + self.module().table(name) + } + + /// Get the value of a global export. + pub fn global_get(&self, name: &str) -> Result { + self.module().global_get(self.store, name) + } + + /// Get a global export. + pub fn global(&self, name: &str) -> Result { + self.module().global(name) + } + + /// Set the value of a mutable global export. + pub fn global_set(&mut self, name: &str, value: WasmValue) -> Result<()> { + self.module().global_set(self.store, name, value) + } + + /// Charge additional fuel from the currently running resumable invocation. + /// + /// This is a no-op when the current invocation is not using fuel-based + /// resumption. + pub fn charge_fuel(&mut self, fuel: u32) { + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(fuel); + } + + /// Get remaining fuel for the current invocation. + /// + /// Returns `0` when fuel-based resumption is not active. + pub fn remaining_fuel(&self) -> u32 { + self.store.execution_fuel + } + + /// Call a function from within the current host-function invocation. + /// + /// This is the safe way for host functions to perform blocking reentrant + /// calls into Wasm. Unlike [`Function::call`], it preserves the active + /// invocation's stacks and resumes the host caller after the nested call + /// completes. + /// + /// Nested calls are currently blocking only. If the surrounding invocation + /// is resumed with fuel or a time budget, this method does not suspend and + /// later continue the host function in the middle of the nested call. + pub fn call_untyped(&mut self, func: &Function, args: &[WasmValue]) -> Result> { + if !self.store.execution_active { + return Err(Error::other("FuncContext::call requires an active host-function invocation")); + } + + func.item.validate_store(self.store)?; + func.validate_params(self.store, args)?; + + let call_stack_base = self.store.call_stack.len(); + let value_stack_base = self.store.value_stack.base(); + func.call_untyped(self.store, args, call_stack_base, value_stack_base) + } + + /// Call a typed function from within the current host-function invocation. + /// + /// See [`Self::call_untyped`] for reentrancy and resumable-execution + /// limitations. + pub fn call(&mut self, func: &FunctionTyped, params: P) -> Result + where + P: IntoWasmValues, + R: FromWasmValues, + { + if !self.store.execution_active { + return Err(Error::other("FuncContext::call requires an active host-function invocation")); + } + func.func.item.validate_store(self.store)?; + let func_instance = self.store.state.get_func(func.func.addr()).clone(); + if matches!(&func_instance.kind, crate::store::FunctionKind::Host(host) if host.typed_callback().is_none()) { + let params = params.into_wasm_values().collect::>(); + let results = self.call_untyped(&func.func, ¶ms)?; + let mut values = results.into_iter(); + let result = R::from_wasm_values(&mut values)?; + return if values.next().is_none() { + Ok(result) + } else { + Err(Error::other("typed conversion did not consume all WebAssembly values")) + }; + } + + let call_stack_base = self.store.call_stack.len(); + let value_stack_base = self.store.value_stack.base(); + func.func.call_typed(self.store, &func_instance, params.into_wasm_values(), call_stack_base, value_stack_base) + } +} + +impl core::ops::Deref for FuncContext<'_> { + type Target = crate::Store; + + fn deref(&self) -> &Self::Target { + self.store + } +} + +impl core::ops::DerefMut for FuncContext<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.store + } +} + +impl<'a> FuncContext<'a> { + /// Create a new host function context. + pub const fn new(store: &'a mut crate::Store, module_id: ModuleInstanceId) -> Self { + Self { store, module_id } + } +} diff --git a/crates/tinywasm/src/func/host.rs b/crates/tinywasm/src/func/host.rs new file mode 100644 index 0000000..83b17f9 --- /dev/null +++ b/crates/tinywasm/src/func/host.rs @@ -0,0 +1,259 @@ +use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use tinywasm_types::{FuncType, ModuleInstanceId, TypeAddr, WasmType, WasmValue}; + +use super::{FromWasmValues, FuncContext, IntoWasmValues, ToWasmTypes}; +use crate::{Function, FunctionInstance, Result, Store}; + +/// A reusable host function definition. +#[derive(Clone)] +pub struct HostFunction(Arc); + +impl HostFunction { + fn instantiate_canonical(&self, store: &mut Store, ty: &FuncType) -> Function { + let type_addr = store.register_host_type(ty); + let addr = store.add_func(FunctionInstance { + type_addr, + gc: store.state.func_gc_metadata(type_addr), + kind: crate::store::FunctionKind::Host(self.clone()), + }); + Function { item: crate::StoreItem::new(store.id(), addr), module_id: 0 } + } + + /// Instantiates this definition after resolving the importing module's types. + pub(crate) fn instantiate_for_import(&self, store: &mut Store, type_addrs: &[TypeAddr]) -> Result { + let resolve = |ty: WasmType| -> Result { + let WasmType::Ref(ref_ty) = ty else { return Ok(ty) }; + let Some(module_addr) = ref_ty.type_index() else { return Ok(ty) }; + let canonical = *type_addrs + .get(module_addr as usize) + .ok_or_else(|| crate::Error::other("host function signature contains an invalid concrete type"))?; + Ok(WasmType::Ref( + tinywasm_types::RefType::new_concrete(ref_ty.is_nullable(), canonical) + .expect("canonical type addresses fit in references"), + )) + }; + let params = self.0.ty.params().iter().copied().map(resolve).collect::>>()?; + let results = self.0.ty.results().iter().copied().map(resolve).collect::>>()?; + Ok(self.instantiate_canonical(store, &FuncType::new(¶ms, &results))) + } + + /// Calls the host function through its untyped value interface. + pub(crate) fn call_values( + &self, + store: &mut Store, + module_id: ModuleInstanceId, + type_addr: TypeAddr, + args: &[WasmValue], + ) -> Result> { + let result = match &self.0.callback { + HostCallback::Untyped(func) => func(FuncContext { store, module_id }, args), + HostCallback::Typed(func) => func.call(FuncContext { store, module_id }, args), + }?; + let expected = store.state.get_canonical_func_type(type_addr); + if result.len() == expected.results().len() + && result.iter().zip(expected.results()).all(|(&value, &ty)| store.state.value_matches_type(value, ty)) + { + Ok(result) + } else { + Err(crate::Error::InvalidHostFnReturn { expected: Box::new(expected.clone()), actual: result }) + } + } + + /// Returns the allocation-free typed callback when one is available. + pub(crate) fn typed_callback(&self) -> Option<&dyn TypedHostCallback> { + match &self.0.callback { + HostCallback::Untyped(_) => None, + HostCallback::Typed(func) => Some(&**func), + } + } + + /// Create a directly callable, store-owned function from this definition. + /// + /// The returned [`Function`] can only be used with `store`. + /// + /// Host functions intended as module imports usually do not need to be + /// instantiated manually. Pass the reusable definition to + /// [`crate::Imports::define`] instead. TinyWasm will then match any GC + /// reference types to the module that imports the function. + /// + /// # Errors + /// + /// Returns an error if the signature uses a store-specific reference type + /// that is not registered in `store`. + /// + /// ## Example + /// + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// use tinywasm::types::WasmValue; + /// use tinywasm::{HostFunction, Store}; + /// + /// let mut store = Store::default(); + /// let add_one = HostFunction::from(|_ctx, value: i32| Ok(value + 1)); + /// let function = add_one.instantiate(&mut store)?; + /// + /// assert_eq!(function.call(&mut store, &[WasmValue::I32(41)])?, [WasmValue::I32(42)]); + /// # Ok(()) + /// # } + /// ``` + pub fn instantiate(&self, store: &mut Store) -> Result { + if self + .0 + .ty + .params() + .iter() + .chain(self.0.ty.results()) + .filter_map(|ty| match ty { + WasmType::Ref(ty) => ty.type_index(), + _ => None, + }) + .any(|type_addr| type_addr as usize >= store.state.canonical_types.len()) + { + return Err(crate::Error::other("host function signature contains a concrete type from another store")); + } + Ok(self.instantiate_canonical(store, &self.0.ty)) + } + + /// Create a new untyped host function. + /// + /// To call Wasm from inside the callback, use [`FuncContext::call`] or + /// [`FuncContext::call_untyped`]. + /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; + /// # use tinywasm::types::{FuncType, WasmType, WasmValue}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (import "host" "add_one" (func $add_one (param i32) (result i32))) + /// # (func (export "call") (param i32) (result i32) + /// # local.get 0 + /// # call $add_one)) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// let mut store = Store::default(); + /// let ty = FuncType::new(&[WasmType::I32], &[WasmType::I32]); + /// let add_one = HostFunction::from_untyped(&ty, |_ctx: FuncContext<'_>, args| { + /// let WasmValue::I32(value) = args[0] else { + /// return Err(tinywasm::Error::Other("expected i32".into())); + /// }; + /// Ok(vec![WasmValue::I32(value + 1)]) + /// }); + /// + /// let mut imports = Imports::new(); + /// imports.define("host", "add_one", add_one); + /// # let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; + /// # let call = instance.func::(&store, "call")?; + /// # assert_eq!(call.call(&mut store, 41)?, 42); + /// # Ok(()) + /// # } + /// ``` + pub fn from_untyped( + ty: &FuncType, + func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + Send + Sync + 'static, + ) -> Self { + Self(Arc::new(HostFunctionInner { ty: ty.clone(), callback: HostCallback::Untyped(Box::new(func)) })) + } + + /// Create a new typed host function. + /// + /// To call Wasm from inside the callback, use [`FuncContext::call`] or + /// [`FuncContext::call_untyped`]. + /// + /// ## Example + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// # use tinywasm::{HostFunction, Imports, ModuleInstance, Store}; + /// # let wasm = wat::parse_str(r#" + /// # (module + /// # (import "host" "add_one" (func $add_one (param i32) (result i32))) + /// # (func (export "call") (param i32) (result i32) + /// # local.get 0 + /// # call $add_one)) + /// # "#).expect("valid wat"); + /// # let module = tinywasm::parse_bytes(&wasm)?; + /// let mut store = Store::default(); + /// let add_one = HostFunction::from(|_ctx, value: i32| Ok(value + 1)); + /// + /// let mut imports = Imports::new(); + /// imports.define("host", "add_one", add_one); + /// # let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; + /// # let call = instance.func::(&store, "call")?; + /// # assert_eq!(call.call(&mut store, 41)?, 42); + /// # Ok(()) + /// # } + /// ``` + pub fn from(func: impl Fn(FuncContext<'_>, P) -> Result + Send + Sync + 'static) -> Self + where + P: FromWasmValues + ToWasmTypes + 'static, + R: IntoWasmValues + ToWasmTypes + 'static, + { + let ty = FuncType::new(&P::wasm_types(), &R::wasm_types()); + let func = TypedHostCallbackImpl { func, marker: core::marker::PhantomData }; + Self(Arc::new(HostFunctionInner { ty, callback: HostCallback::Typed(Box::new(func)) })) + } +} + +struct HostFunctionInner { + ty: FuncType, + callback: HostCallback, +} + +enum HostCallback { + Untyped(Box), + Typed(Box), +} + +type UntypedHostCallback = dyn Fn(FuncContext<'_>, &[WasmValue]) -> Result> + Send + Sync; + +pub(crate) trait TypedHostCallback: Send + Sync { + fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result>; + fn call_stack(&self, store: &mut Store, module_id: ModuleInstanceId, type_addr: TypeAddr) -> Result<()>; +} + +struct TypedHostCallbackImpl { + func: F, + marker: core::marker::PhantomData R>, +} + +impl TypedHostCallback for TypedHostCallbackImpl +where + F: Fn(FuncContext<'_>, P) -> Result + Send + Sync, + P: FromWasmValues, + R: IntoWasmValues, +{ + fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result> { + let mut values = args.iter().copied(); + let params = P::from_wasm_values(&mut values)?; + if values.next().is_some() { + return Err(crate::Error::other("typed conversion did not consume all WebAssembly values")); + } + Ok((self.func)(ctx, params)?.into_wasm_values().collect()) + } + + fn call_stack(&self, store: &mut Store, module_id: ModuleInstanceId, type_addr: TypeAddr) -> Result<()> { + let params = store.state.get_canonical_func_type(type_addr).params(); + let base = store.value_stack.base_before(params.iter().collect()); + let mut values = store.value_stack.wasm_values(&store.state, params, base, true); + let params = P::from_wasm_values(&mut values).and_then(|params| { + if values.next().is_some() { + Err(crate::Error::other("typed conversion did not consume all WebAssembly values")) + } else { + Ok(params) + } + }); + drop(values); + store.value_stack.truncate_to_base(base); + let params = params?; + let result = (self.func)(FuncContext { store, module_id }, params)?; + store.push_typed_values::(type_addr, result.into_wasm_values(), base) + } +} + +#[cfg(feature = "debug")] +impl core::fmt::Debug for HostFunction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("HostFunction").field("ty", &self.0.ty).finish_non_exhaustive() + } +} diff --git a/crates/tinywasm/src/func/mod.rs b/crates/tinywasm/src/func/mod.rs new file mode 100644 index 0000000..34a1938 --- /dev/null +++ b/crates/tinywasm/src/func/mod.rs @@ -0,0 +1,201 @@ +use crate::interpreter::stack::{CallFrame, StackBase}; +use crate::reference::StoreItem; +use crate::{Error, FunctionInstance, InterpreterRuntime, Result, Store}; +use alloc::{format, vec::Vec}; +use core::hint::cold_path; +use tinywasm_types::{FuncAddr, FuncType, ModuleInstanceId, WasmValue}; + +mod context; +mod host; +mod resume; +mod values; +pub use context::FuncContext; +pub use host::HostFunction; +pub use resume::{ExecProgress, FuncExecution, FuncExecutionTyped}; +#[allow(deprecated)] +pub use values::WasmTupleChain; +pub use values::{FromWasmValues, IntoWasmValues, ToWasmType, ToWasmTypes}; + +/// A function handle +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct Function { + pub(crate) item: StoreItem, + pub(crate) module_id: ModuleInstanceId, +} + +impl Function { + #[inline] + /// Returns the function's address in its store. + pub(crate) const fn addr(&self) -> FuncAddr { + self.item.addr + } + + /// Get this function's canonical type from its store. + /// + /// Concrete reference types are only meaningful in this store. + pub fn ty<'a>(&self, store: &'a Store) -> Result<&'a FuncType> { + self.item.validate_store(store)?; + Ok(store.state.get_func_type(self.addr())) + } + + /// Call a function (Invocation) + /// + /// See + #[inline] + pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { + self.item.validate_store(store)?; + self.validate_params(store, params)?; + + store.enter_execution()?; + store.call_stack.clear(); + store.value_stack.clear(); + let result = self.call_untyped(store, params, 0, StackBase::default()); + store.exit_execution(); + result + } + + fn validate_params(&self, store: &Store, params: &[WasmValue]) -> Result<()> { + let func_ty = store.state.get_func_type(self.addr()); + if func_ty.params().len() != params.len() { + cold_path(); + return Err(Error::Other(format!( + "param count mismatch: expected {}, got {}", + func_ty.params().len(), + params.len() + ))); + } + + if !func_ty.params().iter().zip(params).all(|(ty, param)| store.state.value_matches_type(*param, *ty)) { + cold_path(); + #[cfg(feature = "debug")] + return Err(Error::Other(format!( + "param type mismatch: expected {:?}, got {:?}", + func_ty.params(), + params.iter().map(|v| v.ty()).collect::>() + ))); + #[cfg(not(feature = "debug"))] + return Err(Error::Other("param type mismatch".into())); + } + Ok(()) + } + + #[inline] + fn call_untyped( + &self, + store: &mut Store, + params: &[WasmValue], + call_stack_base: u32, + value_stack_base: StackBase, + ) -> Result> { + let instance = store.state.get_func(self.addr()); + let type_addr = instance.type_addr; + let results_may_gc = instance.gc.results; + let result = match &instance.kind { + crate::store::FunctionKind::Host(host) => { + let host = host.clone(); + host.call_values(store, self.module_id, type_addr, params) + } + crate::store::FunctionKind::Wasm(wasm) => { + let wasm_params = wasm.func.params; + let wasm_locals = wasm.func.locals; + let locals_base = + store.value_stack.enter_wasm_call(params, wasm_params, wasm_locals, value_stack_base)?; + let callframe = CallFrame::new(self.addr(), locals_base, wasm_locals); + InterpreterRuntime::exec(store, callframe, call_stack_base).inspect_err(|_| { + store.call_stack.truncate_to(call_stack_base); + store.value_stack.truncate_to_base(value_stack_base); + })?; + let result_types = store.state.get_canonical_func_type(type_addr).results(); + Ok(store.value_stack.pop_wasmvalues(&store.state, result_types)) + } + }; + if results_may_gc && let Ok(values) = &result { + store.state.pin_host_values(values); + } + result + } + + fn prepare_typed( + &self, + store: &mut Store, + instance: &FunctionInstance, + params: impl Iterator, + stack_base: StackBase, + ) -> Result> { + store.push_typed_values::(instance.type_addr, params, stack_base)?; + match &instance.kind { + crate::store::FunctionKind::Wasm(wasm) => { + let locals_base = store + .value_stack + .enter_locals(&wasm.func.params, &wasm.func.locals) + .inspect_err(|_| store.value_stack.truncate_to_base(stack_base))?; + Ok(Some(CallFrame::new(self.addr(), locals_base, wasm.func.locals))) + } + crate::store::FunctionKind::Host(host) => { + host.typed_callback() + .expect("typed host function") + .call_stack(store, self.module_id, instance.type_addr) + .inspect_err(|_| store.value_stack.truncate_to_base(stack_base))?; + Ok(None) + } + } + } + + #[inline] + fn call_typed( + &self, + store: &mut Store, + instance: &FunctionInstance, + params: impl Iterator, + call_stack_base: u32, + value_stack_base: StackBase, + ) -> Result { + if let Some(callframe) = self.prepare_typed(store, instance, params, value_stack_base)? { + InterpreterRuntime::exec(store, callframe, call_stack_base).inspect_err(|_| { + store.call_stack.truncate_to(call_stack_base); + store.value_stack.truncate_to_base(value_stack_base); + })?; + } + store.take_typed_results(instance.type_addr, value_stack_base, instance.gc.results) + } +} + +/// A typed function handle. +/// +/// Parameter and result tuples are supported up to arity 20. Use +/// [`crate::ModuleInstance::func_untyped`] for larger signatures. +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FunctionTyped { + /// The underlying function handle + pub func: Function, + pub(crate) marker: core::marker::PhantomData<(P, R)>, +} + +impl FunctionTyped { + /// Call a typed function + pub fn call(&self, store: &mut Store, params: P) -> Result { + self.func.item.validate_store(store)?; + let func = store.state.get_func(self.func.addr()).clone(); + if matches!(&func.kind, crate::store::FunctionKind::Host(host) if host.typed_callback().is_none()) { + let params = params.into_wasm_values().collect::>(); + let result = self.func.call(store, ¶ms)?; + let mut values = result.into_iter(); + let result = R::from_wasm_values(&mut values)?; + return if values.next().is_none() { + Ok(result) + } else { + Err(Error::other("typed conversion did not consume all WebAssembly values")) + }; + } + + store.enter_execution()?; + let result: Result = { + store.call_stack.clear(); + store.value_stack.clear(); + self.func.call_typed(store, &func, params.into_wasm_values(), 0, StackBase::default()) + }; + store.exit_execution(); + result + } +} diff --git a/crates/tinywasm/src/func/resume.rs b/crates/tinywasm/src/func/resume.rs new file mode 100644 index 0000000..a71be32 --- /dev/null +++ b/crates/tinywasm/src/func/resume.rs @@ -0,0 +1,265 @@ +use alloc::vec::Vec; +use tinywasm_types::{FuncAddr, TypeAddr, WasmValue}; + +use super::{FromWasmValues, Function, FunctionTyped, IntoWasmValues}; +use crate::interpreter::stack::{CallFrame, StackBase}; +use crate::{Error, InterpreterRuntime, Result, Store, Trap}; + +#[derive(Clone, PartialEq, Eq)] +/// Progress for fuel-limited function execution. +pub enum ExecProgress { + /// Execution completed and produced a result. + Completed(T), + /// Execution suspended after exhausting fuel or time budget. + Suspended, +} + +/// Resumable execution for an untyped function call. +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FuncExecution<'store> { + store: &'store mut Store, + state: FuncExecutionState, +} + +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +enum FuncExecutionState { + Running { callframe: CallFrame, root_func_addr: FuncAddr }, + Completed(Option), +} + +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +enum CallResult { + Stack { type_addr: TypeAddr, pin_refs: bool }, + Values(Vec), +} + +/// Resumable execution for a typed function call. +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] +pub struct FuncExecutionTyped<'store, R> { + execution: FuncExecution<'store>, + marker: core::marker::PhantomData, +} + +impl Function { + /// Call a function and return a resumable execution handle. + /// + /// The returned handle keeps a mutable borrow of the [`Store`] until it + /// completes. Use [`FuncExecution::resume_with_fuel`] (or + /// `resume_with_time_budget` with `std`) to continue. + pub fn call_resumable<'store>( + &self, + store: &'store mut Store, + params: &[WasmValue], + ) -> Result> { + self.item.validate_store(store)?; + self.validate_params(store, params)?; + let results_may_gc = store.state.get_func(self.addr()).gc.results; + + store.enter_execution()?; + let result: Result = (|| { + let func_instance = store.state.get_func(self.addr()).clone(); + match &func_instance.kind { + crate::store::FunctionKind::Host(host_func) => { + let result = + host_func.clone().call_values(store, self.module_id, func_instance.type_addr, params)?; + Ok(FuncExecutionState::Completed(Some(CallResult::Values(result)))) + } + crate::store::FunctionKind::Wasm(wasm_func) => { + store.call_stack.clear(); + store.value_stack.clear(); + let locals = wasm_func.func.locals; + let locals_base = store.value_stack.enter_wasm_call( + params, + wasm_func.func.params, + locals, + StackBase::default(), + )?; + let callframe = CallFrame::new(self.addr(), locals_base, locals); + + Ok(FuncExecutionState::Running { callframe, root_func_addr: self.addr() }) + } + } + })(); + store.exit_execution(); + + let state = result?; + if results_may_gc && let FuncExecutionState::Completed(Some(CallResult::Values(values))) = &state { + store.state.pin_host_values(values); + } + Ok(FuncExecution { store, state }) + } +} + +impl<'store> FuncExecution<'store> { + fn resume_raw( + &mut self, + run: impl FnOnce(&mut Store, CallFrame) -> Result, + ) -> Result> { + let (callframe, root_func_addr) = match &mut self.state { + FuncExecutionState::Running { callframe, root_func_addr } => (*callframe, *root_func_addr), + FuncExecutionState::Completed(result) => { + return result + .take() + .map(ExecProgress::Completed) + .ok_or_else(|| Error::other("execution already completed")); + } + }; + + self.store.enter_execution()?; + let result = run(self.store, callframe); + self.store.exit_execution(); + + match result? { + crate::interpreter::ExecState::Completed => { + let func = self.store.state.get_func(root_func_addr); + let result_ty = func.type_addr; + let results_may_gc = func.gc.results; + self.state = FuncExecutionState::Completed(None); + Ok(ExecProgress::Completed(CallResult::Stack { type_addr: result_ty, pin_refs: results_may_gc })) + } + crate::interpreter::ExecState::Suspended(callframe) => { + let FuncExecutionState::Running { callframe: current, .. } = &mut self.state else { + unreachable!("invalid function execution state") + }; + *current = callframe; + Ok(ExecProgress::Suspended) + } + } + } + + fn resume( + &mut self, + run: impl FnOnce(&mut Store, CallFrame) -> Result, + ) -> Result>> { + match self.resume_raw(run)? { + ExecProgress::Completed(CallResult::Stack { type_addr, pin_refs }) => { + let types = self.store.state.get_canonical_func_type(type_addr).results(); + let values = self.store.value_stack.pop_wasmvalues(&self.store.state, types); + if pin_refs { + self.store.state.pin_host_values(&values); + } + Ok(ExecProgress::Completed(values)) + } + ExecProgress::Completed(CallResult::Values(values)) => Ok(ExecProgress::Completed(values)), + ExecProgress::Suspended => Ok(ExecProgress::Suspended), + } + } + + /// Resume execution with up to `fuel` units of fuel. + /// + /// Fuel is accounted in chunks, so execution may overshoot the requested + /// fuel before returning [`ExecProgress::Suspended`] (currently the chunk size is 128 instructions between fuel checks, but this may change in the future). + /// + /// Returns [`ExecProgress::Suspended`] when fuel is exhausted, or + /// [`ExecProgress::Completed`] with the final values once the invocation + /// returns. + /// + /// Reentrant calls made by host functions through [`crate::FuncContext::call`] are + /// currently blocking. They do not suspend and later resume the host + /// function in the middle of the nested call. + pub fn resume_with_fuel(&mut self, fuel: u32) -> Result>> { + self.resume(|store, callframe| InterpreterRuntime::exec_with_fuel(store, callframe, fuel)) + } + + #[cfg(feature = "std")] + /// Resume execution for at most `time_budget` wall-clock time. + /// + /// Time is checked periodically, so execution may overshoot the requested + /// time budget before returning [`ExecProgress::Suspended`] (currently time + /// is checked every 128 instructions, but this may change in the future). + /// + /// Reentrant calls made by host functions through [`crate::FuncContext::call`] + /// are blocking and do not suspend in the middle of the host callback. + pub fn resume_with_time_budget( + &mut self, + time_budget: crate::std::time::Duration, + ) -> Result>> { + self.resume(|store, callframe| InterpreterRuntime::exec_with_time_budget(store, callframe, time_budget)) + } +} + +impl FunctionTyped { + /// Call a typed function and return a resumable execution handle. + /// + /// The handle keeps a mutable borrow of the [`Store`] until completion. + /// + /// ## Example + /// + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// use tinywasm::{ExecProgress, ModuleInstance, Store}; + /// + /// let wasm = include_bytes!("../../../../examples/wasm/add.wasm"); + /// let module = tinywasm::parse_bytes(wasm)?; + /// let mut store = Store::default(); + /// let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + /// let add = instance.func::<(i32, i32), i32>(&store, "add")?; + /// + /// let mut execution = add.call_resumable(&mut store, (20, 22))?; + /// assert!(matches!(execution.resume_with_fuel(0)?, ExecProgress::Suspended)); + /// assert!(matches!(execution.resume_with_fuel(16)?, ExecProgress::Completed(42))); + /// # Ok(()) + /// # } + /// ``` + pub fn call_resumable<'store>(&self, store: &'store mut Store, params: P) -> Result> { + self.func.item.validate_store(store)?; + let func = store.state.get_func(self.func.addr()).clone(); + if matches!(&func.kind, crate::store::FunctionKind::Host(host) if host.typed_callback().is_none()) { + let params = params.into_wasm_values().collect::>(); + let execution = self.func.call_resumable(store, ¶ms)?; + return Ok(FuncExecutionTyped { execution, marker: core::marker::PhantomData }); + } + + store.enter_execution()?; + let result: Result = (|| { + store.call_stack.clear(); + store.value_stack.clear(); + match self.func.prepare_typed(store, &func, params.into_wasm_values(), StackBase::default())? { + Some(callframe) => Ok(FuncExecutionState::Running { callframe, root_func_addr: self.func.addr() }), + None => Ok(FuncExecutionState::Completed(Some(CallResult::Stack { + type_addr: func.type_addr, + pin_refs: func.gc.results, + }))), + } + })(); + store.exit_execution(); + let execution = FuncExecution { store, state: result? }; + Ok(FuncExecutionTyped { execution, marker: core::marker::PhantomData }) + } +} + +impl<'store, R: FromWasmValues> FuncExecutionTyped<'store, R> { + fn resume( + &mut self, + run: impl FnOnce(&mut Store, CallFrame) -> Result, + ) -> Result> { + match self.execution.resume_raw(run)? { + ExecProgress::Completed(CallResult::Stack { type_addr, pin_refs }) => Ok(ExecProgress::Completed( + self.execution.store.take_typed_results(type_addr, StackBase::default(), pin_refs)?, + )), + ExecProgress::Completed(CallResult::Values(values)) => { + let mut values = values.into_iter(); + let result = R::from_wasm_values(&mut values)?; + if values.next().is_some() { + return Err(Error::other("typed conversion did not consume all WebAssembly values")); + } + Ok(ExecProgress::Completed(result)) + } + ExecProgress::Suspended => Ok(ExecProgress::Suspended), + } + } + + /// Resume typed execution with up to `fuel` units of fuel. + pub fn resume_with_fuel(&mut self, fuel: u32) -> Result> { + self.resume(|store, callframe| InterpreterRuntime::exec_with_fuel(store, callframe, fuel)) + } + + #[cfg(feature = "std")] + /// Resume typed execution for at most `time_budget` wall-clock time. + /// + /// Time is checked periodically, so execution may overshoot the requested + /// time budget before returning [`ExecProgress::Suspended`]. + pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result> { + self.resume(|store, callframe| InterpreterRuntime::exec_with_time_budget(store, callframe, time_budget)) + } +} diff --git a/crates/tinywasm/src/func/values.rs b/crates/tinywasm/src/func/values.rs new file mode 100644 index 0000000..319b493 --- /dev/null +++ b/crates/tinywasm/src/func/values.rs @@ -0,0 +1,201 @@ +use crate::{Error, Result}; +use alloc::{borrow::Cow, vec::Vec}; +use tinywasm_types::{ExternRef, FuncRef, WasmType, WasmValue}; + +/// Convert a Rust value or tuple into WebAssembly values. +pub trait IntoWasmValues { + /// Return the flattened WebAssembly values. + fn into_wasm_values(self) -> impl Iterator; +} + +/// Convert WebAssembly values into a Rust value or tuple. +pub trait FromWasmValues: Sized { + /// Read this value from a flattened WebAssembly value iterator. + fn from_wasm_values(values: &mut impl Iterator) -> Result; +} + +/// Describes the WebAssembly value types produced by a Rust value or tuple shape. +pub trait ToWasmTypes { + /// Static WebAssembly types for this shape. + /// + /// Implementations that require runtime construction may set this to `None`, + /// but must then override [`Self::wasm_types`]. + const WASM_TYPES: Option<&'static [WasmType]>; + + /// Return the flattened WebAssembly value types for this tuple shape. + fn wasm_types() -> Cow<'static, [WasmType]> { + Cow::Borrowed(Self::WASM_TYPES.expect("dynamic ToWasmTypes implementation must override wasm_types")) + } +} + +/// Describes the WebAssembly value types produced by a scalar Rust type. +pub trait ToWasmType { + /// The single WebAssembly value type for this scalar type. + const WASM_TYPE: WasmType; +} + +fn next_value>(values: &mut impl Iterator) -> Result { + let value = values.next().ok_or_else(|| { + core::hint::cold_path(); + Error::other("not enough WebAssembly values") + })?; + T::try_from(value).map_err(|_| { + core::hint::cold_path(); + Error::other("WebAssembly value does not match the expected type") + }) +} + +macro_rules! impl_scalar_wasm_traits { + ($($T:ty => $val_ty:expr),+ $(,)?) => { + $( + impl ToWasmType for $T { + const WASM_TYPE: WasmType = $val_ty; + } + + impl ToWasmTypes for $T { + const WASM_TYPES: Option<&'static [WasmType]> = Some(&[$val_ty]); + } + + impl IntoWasmValues for $T { + #[inline] + fn into_wasm_values(self) -> impl Iterator { + core::iter::once(self.into()) + } + } + + impl FromWasmValues for $T { + #[inline] + fn from_wasm_values(values: &mut impl Iterator) -> Result { + next_value(values) + } + } + )+ + }; +} + +macro_rules! impl_tuple_traits { + (@next $head:ident, $($tail:ident),+) => { + impl_tuple_traits!($($tail),+); + }; + (@next $head:ident) => {}; + ($($T:ident),+) => { + impl<$($T),+> ToWasmTypes for ($($T,)+) + where + $($T: ToWasmType,)+ + { + const WASM_TYPES: Option<&'static [WasmType]> = Some(&[$($T::WASM_TYPE,)+]); + } + + impl<$($T),+> IntoWasmValues for ($($T,)+) + where + $($T: Into,)+ + { + #[allow(non_snake_case)] + #[inline] + fn into_wasm_values(self) -> impl Iterator { + let ($($T,)+) = self; + [$($T.into(),)+].into_iter() + } + } + + impl<$($T),+> FromWasmValues for ($($T,)+) + where + $($T: TryFrom,)+ + { + #[inline] + fn from_wasm_values(values: &mut impl Iterator) -> Result { + Ok(($(next_value::<$T>(values)?,)+)) + } + } + + impl_tuple_traits!(@next $($T),+); + } +} + +impl_scalar_wasm_traits!( + i32 => WasmType::I32, + i64 => WasmType::I64, + f32 => WasmType::F32, + f64 => WasmType::F64, + FuncRef => WasmType::Ref(tinywasm_types::RefType::FUNCREF), + ExternRef => WasmType::Ref(tinywasm_types::RefType::EXTERNREF), +); +impl_tuple_traits!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20); + +/// Concatenates two typed parameter or result groups. +/// +/// Direct tuple conversions are supported up to arity 20. Use untyped functions +/// for larger signatures. +#[deprecated(note = "direct tuples are supported up to arity 20, use untyped functions for larger signatures")] +#[derive(Default)] +pub struct WasmTupleChain(T1, T2); + +#[allow(deprecated)] +impl WasmTupleChain { + /// Create a new concatenated tuple wrapper. + pub const fn new(left: T1, right: T2) -> Self { + Self(left, right) + } + + /// Split the wrapper back into its two component values. + pub fn into_inner(self) -> (T1, T2) { + (self.0, self.1) + } +} + +#[allow(deprecated)] +impl From<(T1, T2)> for WasmTupleChain { + fn from((left, right): (T1, T2)) -> Self { + Self::new(left, right) + } +} + +#[allow(deprecated)] +impl ToWasmTypes for WasmTupleChain { + const WASM_TYPES: Option<&'static [WasmType]> = None; + + #[inline] + fn wasm_types() -> Cow<'static, [WasmType]> { + let mut types = Vec::new(); + types.extend_from_slice(&T1::wasm_types()); + types.extend_from_slice(&T2::wasm_types()); + Cow::Owned(types) + } +} + +#[allow(deprecated)] +impl IntoWasmValues for WasmTupleChain { + #[inline] + fn into_wasm_values(self) -> impl Iterator { + let (left, right) = self.into_inner(); + left.into_wasm_values().chain(right.into_wasm_values()) + } +} + +#[allow(deprecated)] +impl FromWasmValues for WasmTupleChain { + #[inline] + fn from_wasm_values(values: &mut impl Iterator) -> Result { + let left = T1::from_wasm_values(values)?; + let right = T2::from_wasm_values(values)?; + Ok(Self::new(left, right)) + } +} + +impl ToWasmTypes for () { + const WASM_TYPES: Option<&'static [WasmType]> = Some(&[]); +} + +impl IntoWasmValues for () { + #[inline] + fn into_wasm_values(self) -> impl Iterator { + core::iter::empty() + } +} + +impl FromWasmValues for () { + #[inline] + fn from_wasm_values(_values: &mut impl Iterator) -> Result { + Ok(()) + } +} diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 6a399ee..a35cf2c 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -1,14 +1,13 @@ use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::fmt::Debug; use core::hint::cold_path; -use crate::{Function, Global, LinkingError, Memory, Result, Table}; +use crate::{Function, Global, HostFunction, LinkingError, Memory, Result, Table}; use tinywasm_types::*; #[derive(Clone)] -#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "debug", derive(core::fmt::Debug))] #[non_exhaustive] /// An external import value. pub enum Extern { @@ -20,43 +19,28 @@ pub enum Extern { Memory(Memory), /// A function import. Function(Function), + /// A reusable host function definition. + HostFunction(HostFunction), } -impl From for Extern { - fn from(value: Global) -> Self { - Self::Global(value) - } -} - -impl From for Extern { - fn from(value: Table) -> Self { - Self::Table(value) - } -} - -impl From for Extern { - fn from(value: Memory) -> Self { - Self::Memory(value) - } -} - -impl From for Extern { - fn from(value: Function) -> Self { - Self::Function(value) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] -/// Name of an import -pub struct ExternName { - module: String, - name: String, +macro_rules! impl_conv { + ($($ty:ty => $variant:ident),* $(,)?) => { + $( + impl From<$ty> for Extern { + fn from(value: $ty) -> Self { + Self::$variant(value) + } + } + )* + }; } -impl From<&Import> for ExternName { - fn from(import: &Import) -> Self { - Self { module: import.module.to_string(), name: import.name.to_string() } - } +impl_conv! { + Global => Global, + Table => Table, + Memory => Memory, + Function => Function, + HostFunction => HostFunction, } /// Imports for a module instance @@ -75,7 +59,7 @@ impl From<&Import> for ExternName { /// # let my_other_instance = ModuleInstance::instantiate(&mut store, &module, None)?; /// let mut imports = Imports::new(); /// -/// let print_i32 = HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, arg: i32| { +/// let print_i32 = HostFunction::from(|_ctx: tinywasm::FuncContext<'_>, arg: i32| { /// log::debug!("print_i32: {}", arg); /// Ok(()) /// }); @@ -101,11 +85,13 @@ impl From<&Import> for ExternName { /// # Ok(()) /// # } /// ``` -/// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`]. +/// Host function definitions are store-independent, so the imports object can be borrowed by +/// [`crate::ModuleInstance::instantiate`] for multiple stores. +/// TinyWasm also matches GC reference types to each module when it links an imported host function. #[derive(Default, Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub struct Imports { - externs: BTreeMap, + externs: BTreeMap>, modules: BTreeMap, } @@ -124,7 +110,9 @@ impl Imports { /// Merge two import sets pub fn merge(mut self, other: Self) -> Self { - self.externs.extend(other.externs); + for (module, externs) in other.externs { + self.externs.entry(module).or_default().extend(externs); + } self.modules.extend(other.modules); self } @@ -138,15 +126,46 @@ impl Imports { } /// Define an import value. + /// + /// A [`Function`], [`Global`], [`Table`], or [`Memory`] handle belongs to + /// one store and can only be imported into that store. A [`HostFunction`] + /// is a reusable definition and can be imported into multiple stores. + /// + /// ## Example + /// + /// ```rust + /// # fn main() -> tinywasm::Result<()> { + /// use tinywasm::{HostFunction, Imports, ModuleInstance, Store}; + /// + /// let wasm = wat::parse_str( + /// r#" + /// (module + /// (import "host" "answer" (func $answer (result i32))) + /// (export "answer" (func $answer))) + /// "#, + /// ) + /// .expect("valid wat"); + /// let module = tinywasm::parse_bytes(&wasm)?; + /// let mut imports = Imports::new(); + /// imports.define("host", "answer", HostFunction::from(|_ctx, ()| Ok(42_i32))); + /// + /// let mut first_store = Store::default(); + /// let first = ModuleInstance::instantiate(&mut first_store, &module, Some(&imports))?; + /// assert_eq!(first.func::<(), i32>(&first_store, "answer")?.call(&mut first_store, ())?, 42); + /// + /// let mut second_store = Store::default(); + /// let second = ModuleInstance::instantiate(&mut second_store, &module, Some(&imports))?; + /// assert_eq!(second.func::<(), i32>(&second_store, "answer")?.call(&mut second_store, ())?, 42); + /// # Ok(()) + /// # } + /// ``` pub fn define(&mut self, module: &str, name: &str, value: impl Into) -> &mut Self { - let name = ExternName { module: module.to_string(), name: name.to_string() }; - self.externs.insert(name, value.into()); + self.externs.entry(module.to_string()).or_default().insert(name.to_string(), value.into()); self } pub(crate) fn take_defined(&self, import: &Import) -> Option { - let name = ExternName::from(import); - self.externs.get(&name).cloned() + self.externs.get(import.module.as_ref())?.get(import.name.as_ref()).cloned() } fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { @@ -237,10 +256,12 @@ impl Imports { (ExternVal::Memory(memory.0.addr), None) } Extern::Function(func) => (ExternVal::Func(func.addr()), Some(func)), + Extern::HostFunction(func) => { + (ExternVal::Func(func.instantiate_for_import(store, type_addrs)?.addr()), None) + } } } else { - let name = ExternName::from(import); - let Some(instance) = self.modules.get(&name.module) else { + let Some(instance) = self.modules.get(import.module.as_ref()) else { cold_path(); return Err(LinkingError::unknown_import(import).into()); }; diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index be38419..52bb948 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -45,7 +45,7 @@ pub struct ModuleInstance(Rc); #[cfg_attr(feature = "debug", derive(Debug))] struct ModuleInstanceInner { - store_id: usize, + store_id: u32, id: ModuleInstanceId, type_addrs: Box<[TypeAddr]>, func_addrs: Box<[FuncAddr]>, @@ -117,7 +117,7 @@ impl ModuleInstance { /// Instantiate the module in the given store /// /// See - pub fn instantiate(store: &mut Store, module: &Module, imports: Option) -> Result { + pub fn instantiate(store: &mut Store, module: &Module, imports: Option<&Imports>) -> Result { let instance = ModuleInstance::instantiate_no_start(store, module, imports)?; let _ = instance.start(store)?; Ok(instance) @@ -150,15 +150,13 @@ impl ModuleInstance { /// ``` /// /// See - pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option) -> Result { + pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option<&Imports>) -> Result { let type_addrs = store.register_module_types(&module.types); let id = store.next_module_instance_id(); - let mut addrs = imports.unwrap_or_default().link(store, module, &type_addrs)?; - let local_type_addrs = module.func_type_idxs[addrs.funcs.len()..] - .iter() - .map(|&addr| type_addrs[addr as usize]) - .collect::>(); - addrs.funcs.extend(store.init_funcs(&module.funcs, id, &local_type_addrs)); + let default_imports = Imports::default(); + let mut addrs = imports.unwrap_or(&default_imports).link(store, module, &type_addrs)?; + let imported_funcs = addrs.funcs.len(); + addrs.funcs.extend(store.init_funcs(&module.funcs, id, &module.func_type_idxs[imported_funcs..], &type_addrs)); match module.local_memory_allocation { LocalMemoryAllocation::Skip => { #[cfg(feature = "guest-debug")] @@ -246,13 +244,10 @@ impl ModuleInstance { pub fn exports(&self) -> impl Iterator + '_ { self.0.exports.iter().map(move |export| { let item = match export.kind { - ExternalKind::Func => { - let func_addr = self.resolve_func_addr(export.index); - ExternItem::Func(Function { - item: StoreItem::new(self.0.store_id, func_addr), - module_id: self.id(), - }) - } + ExternalKind::Func => ExternItem::Func(Function { + item: StoreItem::new(self.0.store_id, self.resolve_func_addr(export.index)), + module_id: self.id(), + }), ExternalKind::Table => { ExternItem::Table(Table(StoreItem::new(self.0.store_id, self.resolve_table_addr(export.index)))) } @@ -364,7 +359,6 @@ impl ModuleInstance { pub fn func_by_index(&self, store: &Store, func_index: FuncAddr) -> Result { self.validate_store(store)?; let func_addr = Self::index_addr(&self.0.func_addrs, func_index, "function")?; - Ok(Function { item: StoreItem::new(self.0.store_id, func_addr), module_id: self.id() }) } @@ -392,8 +386,7 @@ impl ModuleInstance { /// /// For untyped access, see [`Self::func_untyped`] and [`Self::extern_item`]. /// - /// For signatures that exceed tuple arity 12, see [`crate::WasmTupleChain`], which can be used - /// directly as `instance.func::, _>(...)`. + /// Tuples are supported up to arity 20. Use [`Self::func_untyped`] for larger signatures. pub fn func( &self, store: &Store, diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 1c80dac..f5c9351 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -5,7 +5,6 @@ use core::hint::cold_path; use super::no_std_floats::NoStdFloatExt; use alloc::boxed::Box; -use alloc::rc::Rc; use alloc::vec::Vec; use alloc::sync::Arc; @@ -16,7 +15,7 @@ use super::ExecState; use super::num_helpers::*; use super::values::*; use crate::engine::FuelPolicy; -use crate::func::{FuncContext, HostFunction}; +use crate::func::HostFunction; use crate::interpreter::Value128; use crate::*; @@ -1042,10 +1041,22 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_call_host( &mut self, - host_func: Rc, + host_func: HostFunction, type_addr: TypeAddr, params_may_gc: bool, ) -> Result { + if let Some(host_func) = host_func.typed_callback() { + if let Err(error) = host_func.call_stack(self.store, self.module.id(), type_addr) { + cold_path(); + return Err(Trap::HostFunction(Box::new(error))); + } + if TAIL { + return Ok(self.exec_return()); + } + self.cf.instr_ptr += 1; + return Ok(false); + } + let param_types = self.store.state.get_canonical_func_type(type_addr).params(); let mut params = Vec::new(); params.try_reserve_exact(param_types.len()).map_err(|_| Trap::OutOfMemory)?; @@ -1056,8 +1067,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { if params_may_gc { self.store.state.pin_host_values(¶ms); } - let result = host_func.call(FuncContext { store: self.store, module_id: self.module.id() }, ¶ms); - let res = match result.and_then(|result| crate::func::validate_host_results(self.store, type_addr, result)) { + let res = match host_func.call_values(self.store, self.module.id(), type_addr, ¶ms) { Ok(res) => res, Err(err) => { cold_path(); diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 852618d..1824d20 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use core::hint::cold_path; use tinywasm_types::{ValueCounts, WasmType, WasmValue}; @@ -18,6 +18,66 @@ pub(crate) struct ValueStack { pub(crate) stack_128: Stack, } +struct WasmValues<'a> { + stack: &'a ValueStack, + state: &'a crate::store::State, + types: core::slice::Iter<'a, WasmType>, + index: StackBase, + pin_refs: bool, +} + +impl Iterator for WasmValues<'_> { + type Item = WasmValue; + + fn next(&mut self) -> Option { + let value = match *self.types.next()? { + WasmType::I32 => { + let value = *self.stack.stack_32.get(self.index.s32 as usize) as i32; + self.index.s32 += 1; + WasmValue::I32(value) + } + WasmType::I64 => { + let value = *self.stack.stack_64.get(self.index.s64 as usize) as i64; + self.index.s64 += 1; + WasmValue::I64(value) + } + WasmType::F32 => { + let value = f32::from_bits(*self.stack.stack_32.get(self.index.s32 as usize)); + self.index.s32 += 1; + WasmValue::F32(value) + } + WasmType::F64 => { + let value = f64::from_bits(*self.stack.stack_64.get(self.index.s64 as usize)); + self.index.s64 += 1; + WasmValue::F64(value) + } + WasmType::Ref(ty) => { + let value = + self.state.to_ref_value(ValueRef::from_raw(*self.stack.stack_32.get(self.index.s32 as usize)), ty); + self.index.s32 += 1; + WasmValue::Ref(value) + } + WasmType::V128 => { + let value = self.stack.stack_128.get(self.index.s128 as usize).0; + self.index.s128 += 1; + WasmValue::V128(value) + } + }; + if self.pin_refs + && let WasmValue::Ref(value) = value + { + self.state.pin_host_ref(value); + } + Some(value) + } + + fn size_hint(&self) -> (usize, Option) { + self.types.size_hint() + } +} + +impl ExactSizeIterator for WasmValues<'_> {} + #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct Stack { data: Vec, @@ -202,6 +262,15 @@ impl ValueStack { } } + pub(crate) fn base_before(&self, counts: ValueCounts) -> StackBase { + let base = self.base(); + StackBase { + s32: base.s32 - counts.c32 as u32, + s64: base.s64 - counts.c64 as u32, + s128: base.s128 - counts.c128 as u32, + } + } + #[inline(always)] pub(crate) fn len(&self) -> usize { self.stack_32.len() + self.stack_64.len() + self.stack_128.len() @@ -228,6 +297,19 @@ impl ValueStack { Ok(StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128 }) } + #[inline] + /// Pushes call arguments and allocates the function's local lanes. + pub(crate) fn enter_wasm_call( + &mut self, + values: &[WasmValue], + params: ValueCounts, + locals: ValueCounts, + base: StackBase, + ) -> Result { + self.extend_from_wasmvalues(values).inspect_err(|_| self.truncate_to_base(base))?; + self.enter_locals(¶ms, &locals).inspect_err(|_| self.truncate_to_base(base)) + } + #[inline(always)] pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCounts) { self.stack_32.truncate_keep(base.s32 as usize, keep.c32 as usize); @@ -262,17 +344,41 @@ impl ValueStack { } } + /// Pops values in their logical WebAssembly order. + pub(crate) fn pop_wasmvalues(&mut self, state: &crate::store::State, types: &[WasmType]) -> Vec { + debug_assert!(self.len() >= types.len()); + let mut values = vec![WasmValue::I32(0); types.len()]; + for (index, &ty) in types.iter().enumerate().rev() { + values[index] = self.pop_wasmvalue(state, ty); + } + values + } + + pub(crate) fn wasm_values<'a>( + &'a self, + state: &'a crate::store::State, + types: &'a [WasmType], + index: StackBase, + pin_refs: bool, + ) -> impl ExactSizeIterator + 'a { + WasmValues { stack: self, state, types: types.iter(), index, pin_refs } + } + pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) -> Result<(), Trap> { + self.extend_wasmvalues(values.iter().copied()) + } + + pub(crate) fn extend_wasmvalues(&mut self, values: impl Iterator) -> Result<(), Trap> { for value in values { match value { - WasmValue::I32(v) => self.stack_32.push(*v as u32)?, - WasmValue::I64(v) => self.stack_64.push(*v as u64)?, + WasmValue::I32(v) => self.stack_32.push(v as u32)?, + WasmValue::I64(v) => self.stack_64.push(v as u64)?, WasmValue::F32(v) => self.stack_32.push(v.to_bits())?, WasmValue::F64(v) => self.stack_64.push(v.to_bits())?, WasmValue::Ref(v) => { - self.stack_32.push(TinyWasmValue::from(WasmValue::Ref(*v)).as_ref().unwrap().raw())? + self.stack_32.push(TinyWasmValue::from(WasmValue::Ref(v)).as_ref().unwrap().raw())? } - WasmValue::V128(v) => self.stack_128.push((*v).into())?, + WasmValue::V128(v) => self.stack_128.push(v.into())?, } } Ok(()) diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 1c2bcb4..2e2dce0 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -105,9 +105,11 @@ pub(crate) mod log { mod error; pub use error::*; +#[allow(deprecated)] +pub use func::WasmTupleChain; pub use func::{ - ExecProgress, FuncContext, FuncExecution, FuncExecutionTyped, Function, FunctionTyped, HostFunction, ToWasmTypes, - WasmTupleChain, + ExecProgress, FromWasmValues, FuncContext, FuncExecution, FuncExecutionTyped, Function, FunctionTyped, + HostFunction, IntoWasmValues, ToWasmType, ToWasmTypes, }; pub use imports::*; pub use instance::{ExternItem, ModuleInstance}; diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 00f966e..6e904af 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -13,13 +13,14 @@ use tinywasm_types::{Addr, GlobalType, MemAddr, MemoryType, TableAddr, TableType #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct StoreItem { - pub(crate) store_id: usize, + pub(crate) store_id: u32, pub(crate) addr: Addr, } impl StoreItem { #[inline] - pub(crate) const fn new(store_id: usize, addr: Addr) -> Self { + /// Creates a handle for an address owned by a store. + pub(crate) const fn new(store_id: u32, addr: Addr) -> Self { Self { store_id, addr } } @@ -75,11 +76,7 @@ pub struct MemoryCursor<'a> { } #[cfg(feature = "std")] -impl<'a> MemoryCursor<'a> { - fn new(memory: &'a mut MemoryInstance, position: u64) -> Self { - Self { memory, position } - } - +impl MemoryCursor<'_> { fn offset(&self) -> crate::std::io::Result { usize::try_from(self.position).map_err(|_| { crate::std::io::Error::new(crate::std::io::ErrorKind::InvalidInput, "cursor position exceeds usize") @@ -199,7 +196,7 @@ impl Memory { /// Available with the `std` feature enabled. #[cfg(feature = "std")] pub fn cursor_at<'a>(&self, store: &'a mut Store, position: u64) -> Result> { - Ok(MemoryCursor::new(self.instance_mut(store)?, position)) + Ok(MemoryCursor { memory: self.instance_mut(store)?, position }) } #[inline] @@ -375,12 +372,6 @@ impl Table { Ok(store.state.get_table(self.0.addr)) } - #[inline] - fn instance_mut<'a>(&self, store: &'a mut Store) -> Result<&'a mut TableInstance, Trap> { - self.0.validate_store(store)?; - Ok(store.state.get_table_mut(self.0.addr)) - } - /// Get the type of the table. pub fn ty(&self, store: &Store) -> Result { Ok(self.instance(store)?.kind) @@ -428,7 +419,8 @@ impl Table { /// Copy elements within the same table. pub fn copy_within(&self, store: &mut Store, src: usize, dst: usize, len: usize) -> Result<(), Trap> { - self.instance_mut(store)?.copy_within(dst, src, len) + self.0.validate_store(store)?; + store.state.get_table_mut(self.0.addr).copy_within(dst, src, len) } /// Grow the table and return the previous size. diff --git a/crates/tinywasm/src/store/const_expr.rs b/crates/tinywasm/src/store/const_expr.rs new file mode 100644 index 0000000..201a1e1 --- /dev/null +++ b/crates/tinywasm/src/store/const_expr.rs @@ -0,0 +1,194 @@ +use alloc::{format, vec::Vec}; +use core::hint::cold_path; +use tinywasm_types::*; + +use super::{State, default_value}; +use crate::interpreter::{TinyWasmValue, ValueRef}; +use crate::{Error, Result, Trap}; + +#[inline] +pub(super) fn eval_const( + state: &mut State, + instructions: &[ConstInstruction], + global_addrs: &[GlobalAddr], + func_addrs: &[FuncAddr], + type_addrs: &[TypeAddr], +) -> Result { + use ConstInstruction::*; + + let global_value = |state: &State, index: u32| -> Result { + let addr = + *global_addrs.get(index as usize).ok_or_else(|| Error::Other(format!("global {index} not found")))?; + Ok(state.globals.get(addr)) + }; + let func_ref = |index: u32| -> Result { + let addr = + *func_addrs.get(index as usize).ok_or_else(|| Error::Other(format!("function {index} not found")))?; + Ok(ValueRef::from_category_addr(addr)) + }; + let type_addr = |index: TypeAddr| -> Result { + type_addrs.get(index as usize).copied().ok_or_else(|| Error::other("GC constant type not found")) + }; + let pop_value = |stack: &mut Vec, storage: StorageType| -> Result { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + match (storage, value) { + (StorageType::I8, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u8 as u32)), + (StorageType::I16, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u16 as u32)), + (StorageType::Value(WasmType::I32 | WasmType::F32), value @ TinyWasmValue::Value32(_)) + | (StorageType::Value(WasmType::I64 | WasmType::F64), value @ TinyWasmValue::Value64(_)) + | (StorageType::Value(WasmType::V128), value @ TinyWasmValue::Value128(_)) + | (StorageType::Value(WasmType::Ref(_)), value @ TinyWasmValue::ValueRef(_)) => Ok(value), + _ => Err(Error::other("type mismatch in GC constant")), + } + }; + + if let [instruction] = instructions { + match instruction { + I32Const(value) => return Ok(TinyWasmValue::Value32(*value as u32)), + I64Const(value) => return Ok(TinyWasmValue::Value64(*value as u64)), + F32Const(value) => return Ok(TinyWasmValue::Value32(value.to_bits())), + F64Const(value) => return Ok(TinyWasmValue::Value64(value.to_bits())), + V128Const(value) => return Ok(TinyWasmValue::Value128((*value).into())), + GlobalGet(index) => return global_value(state, *index), + Ref(RefValue::Null) => return Ok(TinyWasmValue::ValueRef(ValueRef::NULL)), + Ref(RefValue::Func(func)) => return Ok(TinyWasmValue::ValueRef(func_ref(func.addr())?)), + _ => {} + } + } + + let mut stack = Vec::new(); + for instruction in instructions { + match instruction { + I32Const(value) => stack.push(TinyWasmValue::Value32(*value as u32)), + I64Const(value) => stack.push(TinyWasmValue::Value64(*value as u64)), + F32Const(value) => stack.push(TinyWasmValue::Value32(value.to_bits())), + F64Const(value) => stack.push(TinyWasmValue::Value64(value.to_bits())), + V128Const(value) => stack.push(TinyWasmValue::Value128((*value).into())), + GlobalGet(index) => stack.push(global_value(state, *index)?), + Ref(RefValue::Null) => stack.push(TinyWasmValue::ValueRef(ValueRef::NULL)), + Ref(RefValue::Func(func)) => stack.push(TinyWasmValue::ValueRef(func_ref(func.addr())?)), + Ref(_) => { + cold_path(); + return Err(Error::other("unsupported reference constant")); + } + RefI31 => { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + let TinyWasmValue::Value32(value) = value else { + return Err(Error::other("type mismatch in const ref.i31")); + }; + stack.push(TinyWasmValue::ValueRef(ValueRef::from_i31(value as i32))); + } + AnyConvertExtern | ExternConvertAny => { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + if !matches!(value, TinyWasmValue::ValueRef(_)) { + return Err(Error::other("type mismatch in const reference conversion")); + } + stack.push(value); + } + StructNew(type_index) | StructNewDefault(type_index) => { + let type_addr = type_addr(*type_index)?; + let fields = state + .get_type(type_addr) + .as_struct() + .ok_or_else(|| Error::other("GC constant type is not a struct"))? + .fields + .clone(); + let default = matches!(instruction, StructNewDefault(_)); + let mut values = Vec::new(); + values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; + if default { + values.extend(fields.iter().map(|field| default_value(field.storage))); + } else { + for field in fields.iter().rev() { + values.push(pop_value(&mut stack, field.storage)?); + } + values.reverse(); + } + let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); + let reference = state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + } + ArrayNew(type_index) | ArrayNewDefault(type_index) => { + let type_addr = type_addr(*type_index)?; + let storage = state + .get_type(type_addr) + .as_array() + .ok_or_else(|| Error::other("GC constant type is not an array"))? + .field + .storage; + let Some(TinyWasmValue::Value32(len)) = stack.pop() else { + return Err(Error::other("type mismatch in const array length")); + }; + let value = if matches!(instruction, ArrayNewDefault(_)) { + default_value(storage) + } else { + pop_value(&mut stack, storage)? + }; + let mut values = Vec::new(); + values.try_reserve_exact(len as usize).map_err(|_| Trap::OutOfMemory)?; + values.resize(len as usize, value); + let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); + let reference = state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + } + ArrayNewFixed(type_index, len) => { + let type_addr = type_addr(*type_index)?; + let storage = state + .get_type(type_addr) + .as_array() + .ok_or_else(|| Error::other("GC constant type is not an array"))? + .field + .storage; + let mut values = Vec::new(); + values.try_reserve_exact(*len as usize).map_err(|_| Trap::OutOfMemory)?; + for _ in 0..*len { + values.push(pop_value(&mut stack, storage)?); + } + values.reverse(); + let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); + let reference = state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + } + I32Add | I32Sub | I32Mul => { + let rhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + let lhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + let (TinyWasmValue::Value32(lhs), TinyWasmValue::Value32(rhs)) = (lhs, rhs) else { + cold_path(); + return Err(Error::other("type mismatch in const i32 op")); + }; + let out = match instruction { + I32Add => (lhs as i32).wrapping_add(rhs as i32), + I32Sub => (lhs as i32).wrapping_sub(rhs as i32), + I32Mul => (lhs as i32).wrapping_mul(rhs as i32), + _ => unreachable!(), + }; + stack.push(TinyWasmValue::Value32(out as u32)); + } + I64Add | I64Sub | I64Mul => { + let rhs = stack.pop(); + let lhs = stack.pop(); + let (Some(TinyWasmValue::Value64(lhs)), Some(TinyWasmValue::Value64(rhs))) = (lhs, rhs) else { + cold_path(); + return Err(Error::other("type mismatch in const i64 op")); + }; + let out = match instruction { + I64Add => (lhs as i64).wrapping_add(rhs as i64), + I64Sub => (lhs as i64).wrapping_sub(rhs as i64), + I64Mul => (lhs as i64).wrapping_mul(rhs as i64), + _ => unreachable!(), + }; + stack.push(TinyWasmValue::Value64(out as u64)); + } + } + } + + let Some(value) = stack.pop() else { + cold_path(); + return Err(Error::other("empty const expression")); + }; + if !stack.is_empty() { + cold_path(); + return Err(Error::other("const expression did not reduce to single value")); + } + Ok(value) +} diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index 367fadb..3580e9d 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -1,4 +1,4 @@ -use alloc::{rc::Rc, sync::Arc}; +use alloc::sync::Arc; use tinywasm_types::*; use crate::func::HostFunction; @@ -25,7 +25,7 @@ pub(crate) struct FunctionGcMetadata { #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) enum FunctionKind { /// A host function - Host(Rc), + Host(HostFunction), /// A pointer to a WebAssembly function Wasm(WasmFunctionInstance), diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index c685d89..638b01d 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -15,6 +15,11 @@ impl Default for GlobalLane { } impl GlobalLane { + fn reserve(&mut self, additional: usize) { + self.values.reserve_exact(additional); + self.types.reserve_exact(additional); + } + fn push(&mut self, ty: GlobalType, value: T) -> usize { debug_assert_eq!(self.values.len(), self.types.len()); let index = self.values.len(); @@ -57,6 +62,20 @@ impl Globals { const LANE_64: u32 = 1 << Self::LANE_SHIFT; const LANE_128: u32 = 2 << Self::LANE_SHIFT; + pub(crate) fn reserve(&mut self, globals: &[Global]) { + let (mut count_32, mut count_64, mut count_128) = (0, 0, 0); + for global in globals { + match global.ty.ty { + WasmType::I32 | WasmType::F32 | WasmType::Ref(_) => count_32 += 1, + WasmType::I64 | WasmType::F64 => count_64 += 1, + WasmType::V128 => count_128 += 1, + } + } + self.globals_32.reserve(count_32); + self.globals_64.reserve(count_64); + self.globals_128.reserve(count_128); + } + fn addr(lane: u32, index: usize) -> GlobalAddr { assert!(index <= Self::INDEX_MASK as usize, "too many globals in one value lane"); lane | index as u32 diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 9d5622a..0cd5e5b 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,12 +1,14 @@ use alloc::{boxed::Box, format, vec::Vec}; use core::hint::cold_path; -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::sync::atomic::{AtomicU32, Ordering}; use tinywasm_types::*; -use crate::interpreter::stack::{CallStack, ValueStack}; +use crate::func::FromWasmValues; +use crate::interpreter::stack::{CallStack, StackBase, ValueStack}; use crate::interpreter::{TinyWasmValue, ValueRef}; use crate::{Engine, Error, ModuleInstance, Result, Trap}; +mod const_expr; mod data; mod element; mod function; @@ -17,6 +19,7 @@ mod state; mod table; mod types; +use const_expr::eval_const; pub(crate) use gc::{decode_data, default_value, pop_value, push_value}; pub use memory::{LazyLinearMemory, LinearMemory, MemoryBackend, PagedMemory, VecMemory}; pub(crate) use memory::{MemValue, MemoryInstance}; @@ -25,7 +28,7 @@ pub(crate) use types::{canonicalize_ref_type, canonicalize_value_type}; pub(crate) use {data::*, element::*, function::*, global::*, table::*}; // global store id counter -static STORE_ID: AtomicUsize = AtomicUsize::new(0); +static STORE_ID: AtomicU32 = AtomicU32::new(0); /// Global state that can be manipulated by WebAssembly programs /// @@ -46,7 +49,7 @@ static STORE_ID: AtomicUsize = AtomicUsize::new(0); /// /// See pub struct Store { - id: usize, + id: u32, pub(crate) module_instances: Vec, pub(crate) engine: Engine, @@ -68,12 +71,24 @@ impl core::fmt::Debug for Store { } } +impl PartialEq for Store { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Default for Store { + fn default() -> Self { + Self::new(Engine::default()) + } +} + impl Store { /// Create a new store pub fn new(engine: Engine) -> Self { - let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - let state = - State { gc: Box::new(gc::GcHeap::new(engine.config().gc_collection_threshold)), ..State::default() }; + let id = + STORE_ID.try_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1)).expect("too many stores"); + let state = State::new(engine.config().gc_collection_threshold); Self { id, module_instances: Vec::new(), @@ -86,45 +101,16 @@ impl Store { } } + /// Get the store's ID (unique per process) + pub fn id(&self) -> u32 { + self.id + } + /// Get a module instance by the internal id pub fn get_module_instance(&self, id: ModuleInstanceId) -> Option<&ModuleInstance> { self.module_instances.get(id as usize) } - pub(crate) fn enter_execution(&mut self) -> Result<()> { - if self.execution_active { - return Err(Trap::Other( - "cannot call a function while another invocation is active; use FuncContext::call from host functions", - ) - .into()); - } - self.execution_active = true; - Ok(()) - } - - pub(crate) fn exit_execution(&mut self) { - self.execution_active = false; - } -} - -impl PartialEq for Store { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Default for Store { - fn default() -> Self { - Self::new(Engine::default()) - } -} - -impl Store { - /// Get the store's ID (unique per process) - pub fn id(&self) -> usize { - self.id - } - pub(crate) fn next_module_instance_id(&self) -> ModuleInstanceId { self.module_instances.len() as ModuleInstanceId } @@ -151,19 +137,87 @@ impl Store { pub fn value_matches_type(&self, value: WasmValue, ty: WasmType) -> bool { self.state.value_matches_type(value, ty) } -} -impl Store { + /// Marks the store as executing and rejects nested root calls. + pub(crate) fn enter_execution(&mut self) -> Result<()> { + if self.execution_active { + return Err(Trap::Other( + "cannot call a function while another invocation is active; use FuncContext::call from host functions", + ) + .into()); + } + self.execution_active = true; + Ok(()) + } + + /// Marks the current root execution as complete. + pub(crate) fn exit_execution(&mut self) { + self.execution_active = false; + } + + /// Validates and pushes typed parameters or results onto the value stack. + pub(crate) fn push_typed_values( + &mut self, + type_addr: TypeAddr, + values: impl Iterator, + stack_base: StackBase, + ) -> Result<()> { + let result: Result<()> = (|| { + let ty = self.state.get_canonical_func_type(type_addr); + let expected = if RESULTS { ty.results() } else { ty.params() }; + let mut values = values; + for &ty in expected { + let value = values.next().ok_or_else(|| Error::other("not enough typed function values"))?; + if !self.state.value_matches_type(value, ty) { + return Err(Error::other("typed function value does not match its signature")); + } + self.value_stack.extend_wasmvalues(core::iter::once(value))?; + } + if values.next().is_some() { + return Err(Error::other("too many typed function values")); + } + Ok(()) + })(); + if result.is_err() { + self.value_stack.truncate_to_base(stack_base); + } + result + } + + /// Reads typed results from the value stack and restores its previous base. + pub(crate) fn take_typed_results( + &mut self, + type_addr: TypeAddr, + stack_base: StackBase, + pin_refs: bool, + ) -> Result { + let types = self.state.get_canonical_func_type(type_addr).results(); + let mut values = self.value_stack.wasm_values(&self.state, types, stack_base, pin_refs); + let result = R::from_wasm_values(&mut values).and_then(|result| { + if values.next().is_some() { + Err(Error::other("typed conversion did not consume all WebAssembly values")) + } else { + Ok(result) + } + }); + drop(values); + self.value_stack.truncate_to_base(stack_base); + result + } + /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs( &mut self, funcs: &[alloc::sync::Arc], owner: ModuleInstanceId, + module_type_idxs: &[TypeAddr], type_addrs: &[TypeAddr], ) -> impl ExactSizeIterator { let start = self.state.funcs.len() as FuncAddr; - debug_assert_eq!(funcs.len(), type_addrs.len()); - for (func, &type_addr) in funcs.iter().cloned().zip(type_addrs) { + debug_assert_eq!(funcs.len(), module_type_idxs.len()); + self.state.funcs.reserve_exact(funcs.len()); + for (func, &type_idx) in funcs.iter().cloned().zip(module_type_idxs) { + let type_addr = type_addrs[type_idx as usize]; self.state.funcs.push(FunctionInstance { type_addr, gc: self.state.func_gc_metadata(type_addr), @@ -185,7 +239,7 @@ impl Store { self.state.tables.reserve_exact(tables.len()); for table in tables { let init = match &table.init { - Some(expr) => match self.eval_const(expr, global_addrs, func_addrs, type_addrs)? { + Some(expr) => match eval_const(&mut self.state, expr, global_addrs, func_addrs, type_addrs)? { TinyWasmValue::ValueRef(value) => value, _ => return Err(Error::other("table initializer is not a reference value")), }, @@ -208,6 +262,7 @@ impl Store { init: impl Fn(MemoryType, &MemoryBackend) -> Result, ) -> Result> { let start = self.state.memories.len() as MemAddr; + self.state.memories.reserve_exact(memories.len()); for mem in memories { self.state.memories.push(match init(*mem, &self.engine.config().memory_backend) { Ok(mem) => mem, @@ -228,8 +283,9 @@ impl Store { func_addrs: &[FuncAddr], type_addrs: &[TypeAddr], ) -> Result<()> { + self.state.globals.reserve(globals); for global in globals { - let value = match self.eval_const(&global.init, out, func_addrs, type_addrs) { + let value = match eval_const(&mut self.state, &global.init, out, func_addrs, type_addrs) { Ok(val) => val, Err(e) => { cold_path(); @@ -251,7 +307,7 @@ impl Store { type_addrs: &[TypeAddr], ) -> Result { match item { - ElementItem::Expr(expr) => match self.eval_const(expr, globals, funcs, type_addrs)? { + ElementItem::Expr(expr) => match eval_const(&mut self.state, expr, globals, funcs, type_addrs)? { TinyWasmValue::ValueRef(value) => Ok(value), other => { cold_path(); @@ -279,9 +335,10 @@ impl Store { global_addrs: &[Addr], elements: &[Element], type_addrs: &[TypeAddr], - ) -> Result<(Box<[Addr]>, Option)> { + ) -> Result<(Box<[ElemAddr]>, Option)> { let elem_count = self.state.elements.len(); let mut elem_addrs = Vec::with_capacity(elements.len()); + self.state.elements.reserve_exact(elements.len()); for (i, element) in elements.iter().enumerate() { let elem_addr = self.state.elements.len(); self.state.elements.push(ElementInstance { @@ -302,7 +359,11 @@ impl Store { // this one is active, so we need to initialize it (essentially a `table.init` instruction) ElementKind::Active { offset, table } => { - let offset = self.eval_size_const(offset, global_addrs, func_addrs, type_addrs)?; + let offset = match eval_const(&mut self.state, offset, global_addrs, func_addrs, type_addrs)? { + TinyWasmValue::Value32(value) => u64::from(value), + TinyWasmValue::Value64(value) => value, + other => return Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), + }; let table_addr = table_addrs .get(*table as usize) .copied() @@ -339,8 +400,7 @@ impl Store { elements[elem_addr].drop(); } } - - elem_addrs.push((i + elem_count) as Addr); + elem_addrs.push((i + elem_count) as ElemAddr); } // this should be optimized out by the compiler @@ -355,9 +415,10 @@ impl Store { func_addrs: &[FuncAddr], data: &[Data], type_addrs: &[TypeAddr], - ) -> Result<(Box<[Addr]>, Option)> { + ) -> Result<(Box<[DataAddr]>, Option)> { let data_count = self.state.data.len(); - let mut data_addrs = Vec::with_capacity(data_count); + let mut data_addrs = Vec::with_capacity(data.len()); + self.state.data.reserve_exact(data.len()); for (i, data) in data.iter().enumerate() { let data_val = match &data.kind { tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { @@ -365,7 +426,11 @@ impl Store { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; - let offset = self.eval_size_const(offset, global_addrs, func_addrs, type_addrs)?; + let offset = match eval_const(&mut self.state, offset, global_addrs, func_addrs, type_addrs)? { + TinyWasmValue::Value32(value) => u64::from(value), + TinyWasmValue::Value64(value) => value, + other => return Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), + }; let Some(mem) = self.state.memories.get_mut(*mem_addr as usize) else { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; @@ -389,241 +454,17 @@ impl Store { }; self.state.data.push(DataInstance { data: data_val }); - data_addrs.push((i + data_count) as Addr); + data_addrs.push((i + data_count) as DataAddr); } // this should be optimized out by the compiler Ok((data_addrs.into_boxed_slice(), None)) } + /// Adds a function and returns its store address. pub(crate) fn add_func(&mut self, func: FunctionInstance) -> FuncAddr { + let addr = self.state.funcs.len() as FuncAddr; self.state.funcs.push(func); - self.state.funcs.len() as FuncAddr - 1 - } - - /// Evaluate a constant expression that's either a i32 or a i64 as a global or a const instruction - fn eval_size_const( - &mut self, - const_instrs: &[tinywasm_types::ConstInstruction], - module_global_addrs: &[Addr], - module_func_addrs: &[FuncAddr], - module_type_addrs: &[TypeAddr], - ) -> Result { - let value = self.eval_const(const_instrs, module_global_addrs, module_func_addrs, module_type_addrs)?; - match value { - TinyWasmValue::Value32(i) => Ok(u64::from(i)), - TinyWasmValue::Value64(i) => Ok(i), - other => Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), - } - } - - /// Evaluate a constant expression - #[inline] - fn eval_const( - &mut self, - const_instrs: &[tinywasm_types::ConstInstruction], - module_global_addrs: &[Addr], - module_func_addrs: &[FuncAddr], - module_type_addrs: &[TypeAddr], - ) -> Result { - use tinywasm_types::ConstInstruction::*; - - let global_value = |state: &State, index: u32| -> Result { - let addr = *module_global_addrs - .get(index as usize) - .ok_or_else(|| Error::Other(format!("global {index} not found")))?; - Ok(state.globals.get(addr)) - }; - let func_ref = |index: u32| -> Result { - let addr = *module_func_addrs - .get(index as usize) - .ok_or_else(|| Error::Other(format!("function {index} not found")))?; - Ok(ValueRef::from_category_addr(addr)) - }; - let type_addr = |index: TypeAddr| -> Result { - module_type_addrs.get(index as usize).copied().ok_or_else(|| Error::other("GC constant type not found")) - }; - let pop_value = |stack: &mut Vec, storage: StorageType| -> Result { - let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; - match (storage, value) { - (StorageType::I8, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u8 as u32)), - (StorageType::I16, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u16 as u32)), - (StorageType::Value(WasmType::I32 | WasmType::F32), value @ TinyWasmValue::Value32(_)) - | (StorageType::Value(WasmType::I64 | WasmType::F64), value @ TinyWasmValue::Value64(_)) - | (StorageType::Value(WasmType::V128), value @ TinyWasmValue::Value128(_)) - | (StorageType::Value(WasmType::Ref(_)), value @ TinyWasmValue::ValueRef(_)) => Ok(value), - _ => Err(Error::other("type mismatch in GC constant")), - } - }; - - if let [instr] = const_instrs { - match instr { - I32Const(value) => return Ok(TinyWasmValue::Value32(*value as u32)), - I64Const(value) => return Ok(TinyWasmValue::Value64(*value as u64)), - F32Const(value) => return Ok(TinyWasmValue::Value32(value.to_bits())), - F64Const(value) => return Ok(TinyWasmValue::Value64(value.to_bits())), - V128Const(value) => return Ok(TinyWasmValue::Value128((*value).into())), - GlobalGet(index) => return global_value(&self.state, *index), - Ref(tinywasm_types::RefValue::Null) => return Ok(TinyWasmValue::ValueRef(ValueRef::NULL)), - Ref(tinywasm_types::RefValue::Func(func)) => { - return Ok(TinyWasmValue::ValueRef(func_ref(func.addr())?)); - } - _ => {} - } - } - - let mut stack = Vec::new(); - for instr in const_instrs { - match instr { - I32Const(i) => stack.push(TinyWasmValue::Value32(*i as u32)), - I64Const(i) => stack.push(TinyWasmValue::Value64(*i as u64)), - F32Const(f) => stack.push(TinyWasmValue::Value32(f.to_bits())), - F64Const(f) => stack.push(TinyWasmValue::Value64(f.to_bits())), - V128Const(i) => stack.push(TinyWasmValue::Value128((*i).into())), - GlobalGet(index) => stack.push(global_value(&self.state, *index)?), - Ref(tinywasm_types::RefValue::Null) => stack.push(TinyWasmValue::ValueRef(ValueRef::NULL)), - Ref(tinywasm_types::RefValue::Func(func)) => { - stack.push(TinyWasmValue::ValueRef(func_ref(func.addr())?)) - } - Ref(_) => { - cold_path(); - return Err(Error::other("unsupported reference constant")); - } - RefI31 => { - let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; - let TinyWasmValue::Value32(value) = value else { - return Err(Error::other("type mismatch in const ref.i31")); - }; - stack.push(TinyWasmValue::ValueRef(ValueRef::from_i31(value as i32))); - } - AnyConvertExtern | ExternConvertAny => { - let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; - if !matches!(value, TinyWasmValue::ValueRef(_)) { - return Err(Error::other("type mismatch in const reference conversion")); - } - stack.push(value); - } - StructNew(type_index) | StructNewDefault(type_index) => { - let type_addr = type_addr(*type_index)?; - let fields = self - .state - .get_type(type_addr) - .as_struct() - .ok_or_else(|| Error::other("GC constant type is not a struct"))? - .fields - .clone(); - let default = matches!(instr, StructNewDefault(_)); - let mut values = Vec::new(); - values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; - if default { - values.extend(fields.iter().map(|field| default_value(field.storage))); - } else { - for field in fields.iter().rev() { - values.push(pop_value(&mut stack, field.storage)?); - } - values.reverse(); - } - let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); - let reference = self.state.alloc_gc_object(type_addr, values, roots)?; - stack.push(TinyWasmValue::ValueRef(reference)); - } - ArrayNew(type_index) | ArrayNewDefault(type_index) => { - let type_addr = type_addr(*type_index)?; - let storage = self - .state - .get_type(type_addr) - .as_array() - .ok_or_else(|| Error::other("GC constant type is not an array"))? - .field - .storage; - let Some(TinyWasmValue::Value32(len)) = stack.pop() else { - return Err(Error::other("type mismatch in const array length")); - }; - let value = if matches!(instr, ArrayNewDefault(_)) { - default_value(storage) - } else { - pop_value(&mut stack, storage)? - }; - let mut values = Vec::new(); - values.try_reserve_exact(len as usize).map_err(|_| Trap::OutOfMemory)?; - values.resize(len as usize, value); - let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); - let reference = self.state.alloc_gc_object(type_addr, values, roots)?; - stack.push(TinyWasmValue::ValueRef(reference)); - } - ArrayNewFixed(type_index, len) => { - let type_addr = type_addr(*type_index)?; - let storage = self - .state - .get_type(type_addr) - .as_array() - .ok_or_else(|| Error::other("GC constant type is not an array"))? - .field - .storage; - let mut values = Vec::new(); - values.try_reserve_exact(*len as usize).map_err(|_| Trap::OutOfMemory)?; - for _ in 0..*len { - values.push(pop_value(&mut stack, storage)?); - } - values.reverse(); - let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); - let reference = self.state.alloc_gc_object(type_addr, values, roots)?; - stack.push(TinyWasmValue::ValueRef(reference)); - } - I32Add | I32Sub | I32Mul => { - let rhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; - let lhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; - let (TinyWasmValue::Value32(lhs), TinyWasmValue::Value32(rhs)) = (lhs, rhs) else { - cold_path(); - return Err(Error::other("type mismatch in const i32 op")); - }; - let lhs = lhs as i32; - let rhs = rhs as i32; - let out = match instr { - I32Add => lhs.wrapping_add(rhs), - I32Sub => lhs.wrapping_sub(rhs), - I32Mul => lhs.wrapping_mul(rhs), - _ => { - cold_path(); - return Err(Error::other("invalid const instruction in i32 op")); - } - }; - stack.push(TinyWasmValue::Value32(out as u32)); - } - I64Add | I64Sub | I64Mul => { - let rhs = stack.pop(); - let lhs = stack.pop(); - let (Some(TinyWasmValue::Value64(lhs)), Some(TinyWasmValue::Value64(rhs))) = (lhs, rhs) else { - cold_path(); - return Err(Error::other("type mismatch in const i64 op")); - }; - - let lhs = lhs as i64; - let rhs = rhs as i64; - let out = match instr { - I64Add => lhs.wrapping_add(rhs), - I64Sub => lhs.wrapping_sub(rhs), - I64Mul => lhs.wrapping_mul(rhs), - _ => { - cold_path(); - return Err(Error::other("invalid const instruction in i64 op")); - } - }; - stack.push(TinyWasmValue::Value64(out as u64)); - } - } - } - - let Some(value) = stack.pop() else { - cold_path(); - return Err(Error::other("empty const expression")); - }; - - if !stack.is_empty() { - cold_path(); - return Err(Error::other("const expression did not reduce to single value")); - } - - Ok(value) + addr } } diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index 5cb561a..3371d4c 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -2,7 +2,6 @@ use alloc::vec::Vec; use super::*; -#[derive(Default)] /// Global state that can be manipulated by WebAssembly programs /// /// Data should only be addressable by the module that owns it @@ -21,6 +20,20 @@ pub(crate) struct State { } impl State { + pub(crate) fn new(gc_collection_threshold: usize) -> Self { + Self { + canonical_types: Vec::new(), + canonical_rec_group_lengths: Vec::new(), + funcs: Vec::new(), + tables: Vec::new(), + memories: Vec::new(), + globals: Globals::default(), + elements: Vec::new(), + data: Vec::new(), + gc: Box::new(gc::GcHeap::new(gc_collection_threshold)), + } + } + /// Returns whether values of this type can contain a managed GC object. pub(crate) fn type_may_contain_gc(&self, ty: &WasmType) -> bool { Self::type_may_contain_gc_in(&self.canonical_types, ty) @@ -406,3 +419,9 @@ impl State { Ok(()) } } + +impl Default for State { + fn default() -> Self { + Self::new(1024 * 1024) + } +} diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index b3b5048..35e0d9b 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -1,6 +1,6 @@ use eyre::Result; use std::fmt::Write; -use tinywasm::types::{FuncType, RefValue, WasmType, WasmValue}; +use tinywasm::types::{FuncType, RefType, RefValue, WasmType, WasmValue}; use tinywasm::{FuncContext, HostFunction, Imports, Module, ModuleInstance, Store}; use tinywasm_types::ExternRef; @@ -34,10 +34,10 @@ fn test_return_invalid_type() -> Result<()> { for returned_values in VAL_LISTS { let mut store = Store::default(); let mut imports = Imports::new(); - let hfn = HostFunction::from_untyped(&mut store, &ty, |_: FuncContext<'_>, _| Ok(returned_values.to_vec())); + let hfn = HostFunction::from_untyped(&ty, |_: FuncContext<'_>, _| Ok(returned_values.to_vec())); imports.define("host", "hfn", hfn); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports)).unwrap(); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports)).unwrap(); let caller = instance.func_untyped(&store, "call_hfn").unwrap(); // Return-type mismatch is only observable at call time. let should_succeed = returned_values.len() == ty.results().len() @@ -56,13 +56,12 @@ fn test_linking_invalid_untyped_func() -> Result<()> { for (module, expected_func_ty, _) in &cases { for (_, ty, _) in &cases { let mut store = Store::default(); - let tried_fn = - HostFunction::from_untyped(&mut store, ty, |_: FuncContext<'_>, _| panic!("not intended to be called")); + let tried_fn = HostFunction::from_untyped(ty, |_: FuncContext<'_>, _| panic!("not intended to be called")); let mut imports = Imports::new(); imports.define("host", "hfn", tried_fn); let should_succeed = ty == expected_func_ty; - let link_res = ModuleInstance::instantiate(&mut store, module, Some(imports)); + let link_res = ModuleInstance::instantiate(&mut store, module, Some(&imports)); assert_eq!(link_res.is_ok(), should_succeed); } } @@ -80,26 +79,18 @@ fn test_linking_invalid_typed_func() -> Result<()> { for (module, _, _) in cases { let mut store = Store::default(); let matching_none = vec![ - HostFunction::from(&mut store, |_, _: NonMatchingTuple| -> tinywasm::Result { + HostFunction::from(|_, _: NonMatchingTuple| -> tinywasm::Result { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: NonMatchingTuple| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: NonMatchingSingle| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: Existing| -> tinywasm::Result { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: Existing| -> tinywasm::Result { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), + HostFunction::from(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), - HostFunction::from(&mut store, |_, _: NonMatchingTuple| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), - HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result { - panic!("{DONT_CALL}") - }), - HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), - HostFunction::from(&mut store, |_, _: Existing| -> tinywasm::Result { - panic!("{DONT_CALL}") - }), - HostFunction::from(&mut store, |_, _: Existing| -> tinywasm::Result { - panic!("{DONT_CALL}") - }), - HostFunction::from(&mut store, |_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), - HostFunction::from(&mut store, |_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), - HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result { - panic!("{DONT_CALL}") - }), - HostFunction::from(&mut store, |_, _: NonMatchingSingle| -> tinywasm::Result { + HostFunction::from(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), ]; @@ -107,7 +98,7 @@ fn test_linking_invalid_typed_func() -> Result<()> { for typed_fn in matching_none { let mut imports = Imports::new(); imports.define("host", "hfn", typed_fn); - let link_failure = ModuleInstance::instantiate(&mut store, &module, Some(imports)); + let link_failure = ModuleInstance::instantiate(&mut store, &module, Some(&imports)); assert!(link_failure.is_err(), "Expected linking to fail for mismatched typed func, but it succeeded"); } } @@ -142,16 +133,45 @@ fn concrete_host_references_use_canonical_types() -> Result<()> { let param_ty = instance.func_untyped(&store, "takes-a")?.ty(&store)?.clone(); let wrong_result = wrong_ref.clone(); - let wrong_return = HostFunction::from_untyped(&mut store, &return_ty, move |_, _| Ok(wrong_result.clone())); + let wrong_return = + HostFunction::from_untyped(&return_ty, move |_, _| Ok(wrong_result.clone())).instantiate(&mut store)?; assert!(wrong_return.call(&mut store, &[]).is_err()); - let accept_a = HostFunction::from_untyped(&mut store, ¶m_ty, |_, _| Ok(Vec::new())); + let accept_a = HostFunction::from_untyped(¶m_ty, |_, _| Ok(Vec::new())).instantiate(&mut store)?; assert!(accept_a.call(&mut store, &wrong_ref).is_err()); assert!(accept_a.call(&mut store, &right_ref).is_ok()); Ok(()) } +#[test] +fn imported_host_functions_resolve_concrete_types() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (type $object (struct)) + (import "host" "inspect" (func $inspect (param (ref null $object)))) + (func (export "call") + ref.null $object + call $inspect)) + "#, + )?; + let module = tinywasm::parse_bytes(&wasm)?; + let concrete = RefType::new_concrete(true, 0).expect("valid module-local type index"); + let host_ty = FuncType::new(&[WasmType::Ref(concrete)], &[]); + let host = HostFunction::from_untyped(&host_ty, |_, args| { + assert_eq!(args, &[WasmValue::Ref(RefValue::Null)]); + Ok(Vec::new()) + }); + let mut imports = Imports::new(); + imports.define("host", "inspect", host); + let mut store = Store::default(); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; + + instance.func::<(), ()>(&store, "call")?.call(&mut store, ())?; + Ok(()) +} + #[test] fn host_tail_calls_return_from_the_current_frame() -> Result<()> { let wasm = wat::parse_str( @@ -178,8 +198,8 @@ fn host_tail_calls_return_from_the_current_frame() -> Result<()> { let module = tinywasm::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - imports.define("host", "answer", HostFunction::from(&mut store, |_, ()| Ok(42_i32))); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + imports.define("host", "answer", HostFunction::from(|_, ()| Ok(42_i32))); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; for name in ["direct", "indirect", "reference"] { assert_eq!(instance.func::<(), i32>(&store, name)?.call(&mut store, ())?, 42); @@ -190,13 +210,14 @@ fn host_tail_calls_return_from_the_current_frame() -> Result<()> { } #[test] -fn host_calls_reject_unknown_function_references() { +fn host_calls_reject_unknown_function_references() -> Result<()> { let mut store = Store::default(); let ty = FuncType::new(&[WasmType::Ref(tinywasm::types::RefType::FUNCREF)], &[]); - let host = HostFunction::from_untyped(&mut store, &ty, |_, _| Ok(Vec::new())); + let host = HostFunction::from_untyped(&ty, |_, _| Ok(Vec::new())).instantiate(&mut store)?; let invalid = WasmValue::Ref(RefValue::Func(tinywasm::types::FuncRef::new(u32::MAX))); assert!(host.call(&mut store, &[invalid]).is_err()); + Ok(()) } fn to_name(ty: &WasmType) -> &str { diff --git a/crates/tinywasm/tests/host_import_arg_order.rs b/crates/tinywasm/tests/host_import_arg_order.rs index 2004641..b04ef77 100644 --- a/crates/tinywasm/tests/host_import_arg_order.rs +++ b/crates/tinywasm/tests/host_import_arg_order.rs @@ -15,17 +15,16 @@ fn multi_arg_host_imports_preserve_source_order() { .unwrap(); let module = tinywasm::parse_bytes(&wasm).unwrap(); - let mut store = Store::default(); - - let pair = HostFunction::from(&mut store, |_ctx, (left, right): (i32, i32)| -> tinywasm::Result { - Ok(left * 1000 + right) - }); - + let pair = + HostFunction::from(|_ctx, (left, right): (i32, i32)| -> tinywasm::Result { Ok(left * 1000 + right) }); let mut imports = Imports::new(); imports.define("env", "pair", pair); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports)).unwrap(); - let func = instance.func::<(i32, i32), i32>(&store, "call_pair").unwrap(); + for _ in 0..2 { + let mut store = Store::default(); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports)).unwrap(); + let func = instance.func::<(i32, i32), i32>(&store, "call_pair").unwrap(); - assert_eq!(func.call(&mut store, (12, 34)).unwrap(), 12034); + assert_eq!(func.call(&mut store, (12, 34)).unwrap(), 12034); + } } diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs index e7948fa..1d5db1b 100644 --- a/crates/tinywasm/tests/import_linking.rs +++ b/crates/tinywasm/tests/import_linking.rs @@ -34,7 +34,7 @@ fn link_module_links_same_store_instance() -> Result<()> { let mut imports = Imports::new(); imports.link_module("adder", add_instance)?; - let instance = ModuleInstance::instantiate(&mut store, &import_module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &import_module, Some(&imports))?; let main = instance.func::<(), i32>(&store, "main")?; assert_eq!(main.call(&mut store, ())?, 3); Ok(()) @@ -51,7 +51,7 @@ fn link_module_rejects_cross_store_instance() -> Result<()> { let mut imports = Imports::new(); imports.link_module("adder", add_instance)?; - let err = ModuleInstance::instantiate(&mut target_store, &import_module, Some(imports)).unwrap_err(); + let err = ModuleInstance::instantiate(&mut target_store, &import_module, Some(&imports)).unwrap_err(); assert_eq!(err, Error::from(Trap::InvalidStore)); Ok(()) } diff --git a/crates/tinywasm/tests/imported_table_init.rs b/crates/tinywasm/tests/imported_table_init.rs index d7ed636..ca52e9e 100644 --- a/crates/tinywasm/tests/imported_table_init.rs +++ b/crates/tinywasm/tests/imported_table_init.rs @@ -19,11 +19,11 @@ fn imported_table_uses_provided_init_value() -> Result<()> { let module = tinywasm::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - let _function_at_zero = HostFunction::from(&mut store, |_, ()| Ok(())); + let _function_at_zero = HostFunction::from(|_, ()| Ok(())).instantiate(&mut store)?; let table = Table::new(&mut store, TableType::new(RefType::FUNCREF, 3, None), FuncRef::new(0).into())?; imports.define("host", "table", table); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let slot_is_null = instance.func::(&store, "slot_is_null")?; assert_eq!(slot_is_null.call(&mut store, 0)?, 0); diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index 04279cc..196ad0a 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -117,9 +117,9 @@ fn extern_item_and_exports_use_actual_function_type() -> Result<()> { imports.define( "host", "imported", - tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())), + tinywasm::HostFunction::from(|_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())), ); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let ExternItem::Func(func) = instance.extern_item("f")? else { panic!("expected function export") }; assert_eq!(func.call(&mut store, &[])?, vec![]); @@ -166,9 +166,9 @@ fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<() imports.define( "spectest", "print_i64", - tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())), + tinywasm::HostFunction::from(|_ctx: tinywasm::FuncContext<'_>, _arg: i64| Ok(())), ); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let ExternItem::Func(func) = instance.extern_item("f")? else { panic!("expected function export") }; assert_eq!(func.call(&mut store, &[])?, vec![]); @@ -192,7 +192,8 @@ fn start_resolves_module_func_index_to_store_addr() -> Result<()> { let module = tinywasm::parse_bytes(&wasm)?; let mut store = Store::default(); - let _unused = tinywasm::HostFunction::from(&mut store, |_ctx: tinywasm::FuncContext<'_>, (): ()| Ok(())); + let _unused = + tinywasm::HostFunction::from(|_ctx: tinywasm::FuncContext<'_>, (): ()| Ok(())).instantiate(&mut store)?; let instance = ModuleInstance::instantiate_no_start(&mut store, &module, None)?; instance.start(&mut store)?; diff --git a/crates/tinywasm/tests/memory_backends.rs b/crates/tinywasm/tests/memory_backends.rs index c231bc1..0cf5707 100644 --- a/crates/tinywasm/tests/memory_backends.rs +++ b/crates/tinywasm/tests/memory_backends.rs @@ -190,7 +190,7 @@ fn disabled_local_memory_allocation_optimization_keeps_old_behavior() -> Result< ) "#, )?; - let parser = Parser::with_options(ParserOptions::default().with_local_memory_allocation_optimization(false)); + let parser = Parser::new(ParserOptions::default().with_local_memory_allocation_optimization(false)); let module = parser.parse_module_bytes(&wasm)?; let created = instantiate_module_with_counting_backend(module)?; diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs index 6cae984..bcb8e07 100644 --- a/crates/tinywasm/tests/store_ownership.rs +++ b/crates/tinywasm/tests/store_ownership.rs @@ -98,7 +98,7 @@ fn global_import_rejects_wrong_store() -> Result<()> { imports.define("env", "g", global); let mut other_store = Store::default(); - let err = ModuleInstance::instantiate(&mut other_store, &module, Some(imports)).unwrap_err(); + let err = ModuleInstance::instantiate(&mut other_store, &module, Some(&imports)).unwrap_err(); assert_eq!(err, tinywasm::Error::Trap(tinywasm::Trap::InvalidStore)); Ok(()) } diff --git a/crates/tinywasm/tests/typed_globals.rs b/crates/tinywasm/tests/typed_globals.rs index 80494e0..9d88ec8 100644 --- a/crates/tinywasm/tests/typed_globals.rs +++ b/crates/tinywasm/tests/typed_globals.rs @@ -85,7 +85,7 @@ fn imported_global_keeps_its_typed_store_address() -> Result<()> { let global = Global::new(&mut store, GlobalType::new(WasmType::I64, true), WasmValue::I64(1))?; let mut imports = Imports::default(); imports.define("env", "g", global); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; assert_eq!(instance.func::(&store, "roundtrip")?.call(&mut store, 42)?, 42); assert_eq!(global.get(&store)?, WasmValue::I64(42)); diff --git a/examples/doom/src/runtime.rs b/examples/doom/src/runtime.rs index 980dc21..bee961c 100644 --- a/examples/doom/src/runtime.rs +++ b/examples/doom/src/runtime.rs @@ -30,7 +30,7 @@ impl Runtime { let mut store = Store::default(); let host_state = Rc::new(RefCell::new(HostState::new(wad_path))); let imports = build_imports(&mut store, host_state.clone()); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let wad_path_buf = instance.func::<(), i32>(&store, "tinywasm_doom_wad_path_buf")?; let init = instance.func::<(), ()>(&store, "tinywasm_doom_init")?; @@ -169,7 +169,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_open", - HostFunction::from(store, move |ctx: FuncContext<'_>, (filename_ptr, mode_ptr): (i32, i32)| { + HostFunction::from(move |ctx: FuncContext<'_>, (filename_ptr, mode_ptr): (i32, i32)| { let memory = ctx.memory("memory")?; let filename = memory.read_cstring_until_null(ctx.store(), filename_ptr as usize, 1024)?; let mode = memory.read_cstring_until_null(ctx.store(), mode_ptr as usize, 16)?; @@ -208,7 +208,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_close", - HostFunction::from(store, move |_ctx: FuncContext<'_>, handle: i32| { + HostFunction::from(move |_ctx: FuncContext<'_>, handle: i32| { state.borrow_mut().files.remove(&handle); Ok(()) }), @@ -220,7 +220,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_read", - HostFunction::from(store, move |mut ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| { + HostFunction::from(move |mut ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| { let mut state = state.borrow_mut(); let Some(file) = state.files.get_mut(&handle) else { return Ok(0); @@ -238,7 +238,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_write", - HostFunction::from(store, move |ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| { + HostFunction::from(move |ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| { let data = ctx.memory("memory")?.read_vec(ctx.store(), buf_ptr as usize, count.max(0) as usize)?; let mut state = state.borrow_mut(); let Some(file) = state.files.get_mut(&handle) else { @@ -255,7 +255,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_seek", - HostFunction::from(store, move |_ctx: FuncContext<'_>, (handle, offset, origin): (i32, i32, i32)| { + HostFunction::from(move |_ctx: FuncContext<'_>, (handle, offset, origin): (i32, i32, i32)| { let seek_from = match origin { 0 => SeekFrom::Start(offset.max(0) as u64), 1 => SeekFrom::Current(offset as i64), @@ -277,7 +277,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_tell", - HostFunction::from(store, move |_ctx: FuncContext<'_>, handle: i32| { + HostFunction::from(move |_ctx: FuncContext<'_>, handle: i32| { let mut state = state.borrow_mut(); let Some(file) = state.files.get_mut(&handle) else { return Ok(-1); @@ -293,7 +293,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_eof", - HostFunction::from(store, move |_ctx: FuncContext<'_>, handle: i32| { + HostFunction::from(move |_ctx: FuncContext<'_>, handle: i32| { let mut state = state.borrow_mut(); let Some(file) = state.files.get_mut(&handle) else { return Ok(1); @@ -310,7 +310,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_gettime", - HostFunction::from(store, move |mut ctx: FuncContext<'_>, (sec_ptr, usec_ptr): (i32, i32)| { + HostFunction::from(move |mut ctx: FuncContext<'_>, (sec_ptr, usec_ptr): (i32, i32)| { let elapsed = state.borrow().start.elapsed(); let sec = elapsed.as_secs().min(i32::MAX as u64) as i32; let usec = elapsed.subsec_micros() as i32; @@ -327,7 +327,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_exit", - HostFunction::from(store, move |_ctx: FuncContext<'_>, code: i32| { + HostFunction::from(move |_ctx: FuncContext<'_>, code: i32| { state.borrow_mut().exit_code = Some(code); Ok(()) }), @@ -337,7 +337,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { imports.define( IMPORT_MODULE, "host_print", - HostFunction::from(store, move |ctx: FuncContext<'_>, ptr: i32| { + HostFunction::from(move |ctx: FuncContext<'_>, ptr: i32| { let text = ctx.memory("memory")?.read_cstring_until_null(ctx.store(), ptr as usize, 4096)?; log::info!("guest: {}", text.to_string_lossy()); Ok(()) diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index 8ff9b49..5e21a6b 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -50,19 +50,18 @@ fn run_passed_funcref_example() -> Result<()> { let module = tinywasm::parse_bytes(&wasm)?; let mut store = Store::default(); - let mul = HostFunction::from(&mut store, |_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }); - let call_this = - HostFunction::from(&mut store, |mut ctx: FuncContext<'_>, func_ref: FuncRef| -> tinywasm::Result<()> { - // Host cannot call a funcref directly, so it routes through Wasm. - let call_by_ref = ctx.module().func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; - let _result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; - Ok(()) - }); + let mul = HostFunction::from(|_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }); + let call_this = HostFunction::from(|mut ctx: FuncContext<'_>, func_ref: FuncRef| -> tinywasm::Result<()> { + // Host cannot call a funcref directly, so it routes through Wasm. + let call_by_ref = ctx.module().func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; + let _result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; + Ok(()) + }); let mut imports = Imports::new(); imports.define("host", "call_this", call_this).define("host", "mul", mul); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let caller = instance.func::<(), ()>(&store, "tell_host_to_call")?; caller.call(&mut store, ())?; @@ -107,10 +106,10 @@ fn run_returned_funcref_example() -> Result<()> { let mut store = Store::default(); let mut imports = Imports::new(); - let mul = HostFunction::from(&mut store, |_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }); + let mul = HostFunction::from(|_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }); imports.define("host", "mul", mul); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let (add_ref, sub_ref, mul_ref) = { let get_funcrefs = instance.func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; get_funcrefs.call(&mut store, ())? diff --git a/examples/linking.rs b/examples/linking.rs index b3164ae..02cb88d 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -40,7 +40,7 @@ fn main() -> Result<()> { imports.link_module("adder", add_instance)?; // Instantiate the `import` module with the linked imports. - let import_instance = ModuleInstance::instantiate(&mut store, &import_module, Some(imports))?; + let import_instance = ModuleInstance::instantiate(&mut store, &import_module, Some(&imports))?; // Call the `main` function, which uses the imported `add` function. let main = import_instance.func::<(), i32>(&store, "main")?; diff --git a/examples/reentrance.rs b/examples/reentrance.rs index 432fac7..8a1988e 100644 --- a/examples/reentrance.rs +++ b/examples/reentrance.rs @@ -22,7 +22,7 @@ fn main() -> Result<()> { let module = tinywasm::parse_bytes(&wasm)?; let mut store = Store::default(); - let call_add_twice = HostFunction::from(&mut store, |mut ctx: FuncContext<'_>, value: i32| { + let call_add_twice = HostFunction::from(|mut ctx: FuncContext<'_>, value: i32| { let add_one = ctx.module().func::(ctx.store(), "add_one")?; // Use ctx.call for reentrant calls from host functions. Function::call @@ -34,7 +34,7 @@ fn main() -> Result<()> { let mut imports = Imports::new(); imports.define("host", "call_add_twice", call_add_twice); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let run = instance.func::(&store, "run")?; assert_eq!(run.call(&mut store, 40)?, 52); diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 70de73e..b0a2e47 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -15,7 +15,7 @@ fn run() -> tinywasm::Result<()> { let module = tinywasm::parse_stream(&include_bytes!("./print.wasm")[..])?; let mut store = tinywasm::Store::default(); - let printi32 = HostFunction::from(&mut store, |_: FuncContext<'_>, v: i32| { + let printi32 = HostFunction::from(|_: FuncContext<'_>, v: i32| { unsafe { printi32(v) } Ok(()) }); @@ -23,7 +23,7 @@ fn run() -> tinywasm::Result<()> { let mut imports = tinywasm::Imports::new(); imports.define("env", "printi32", printi32); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let add_and_print = instance.func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index c1dd910..cbf10f4 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -28,17 +28,17 @@ fn run() -> tinywasm::Result<()> { let mut store = tinywasm::Store::default(); let mut imports = tinywasm::Imports::new(); - let res = tinywasm::parser::Parser::new().parse_module_bytes(include_bytes!("./print.wasm"))?; + let res = tinywasm::parser::Parser::default().parse_module_bytes(include_bytes!("./print.wasm"))?; let twasm = res.serialize_twasm()?; let module = tinywasm::parse_bytes(&twasm)?; - let printi32 = HostFunction::from(&mut store, |_: FuncContext<'_>, v: i32| { + let printi32 = HostFunction::from(|_: FuncContext<'_>, v: i32| { unsafe { printi32(v) } Ok(()) }); imports.define("env", "printi32", printi32); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let add_and_print = instance.func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index e0b9968..e4af3cd 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -75,8 +75,8 @@ fn tinywasm() -> Result<()> { let mut store = Store::default(); let mut imports = Imports::new(); - imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _x: i32| Ok(()))); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(black_box(imports)))?; + imports.define("env", "printi32", HostFunction::from(|_: FuncContext<'_>, _x: i32| Ok(()))); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(black_box(&imports)))?; let hello = instance.func::<(), ()>(&store, "hello")?; hello.call(&mut store, black_box(()))?; @@ -90,8 +90,8 @@ fn tinywasm_no_std() -> Result<()> { let mut store = Store::default(); let mut imports = Imports::new(); - imports.define("env", "printi32", HostFunction::from(&mut store, |_: FuncContext<'_>, _x: i32| Ok(()))); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(black_box(imports)))?; + imports.define("env", "printi32", HostFunction::from(|_: FuncContext<'_>, _x: i32| Ok(()))); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(black_box(&imports)))?; let hello = instance.func::<(), ()>(&store, "hello")?; hello.call(&mut store, black_box(()))?; @@ -104,7 +104,7 @@ fn hello() -> Result<()> { let module = tinywasm::parse_file("./examples/rust/out/hello.opt.wasm")?; let mut store = Store::default(); - let print_utf8 = HostFunction::from(&mut store, |ctx: FuncContext<'_>, (ptr, len): (i64, i32)| { + let print_utf8 = HostFunction::from(|ctx: FuncContext<'_>, (ptr, len): (i64, i32)| { let mem = ctx.memory("memory")?; let string = mem.read_string(ctx.store(), ptr as usize, len as usize)?; println!("{string}"); @@ -114,7 +114,7 @@ fn hello() -> Result<()> { let mut imports = Imports::new(); imports.define("env", "print_utf8", print_utf8); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let arg_ptr = instance.func::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; let arg = b"world"; @@ -129,7 +129,7 @@ fn host_fn() -> Result<()> { let module = tinywasm::parse_file("./examples/rust/out/host_fn.opt.wasm")?; let mut store = Store::default(); - let bar = HostFunction::from(&mut store, |_: FuncContext<'_>, (left, right): (i64, i32)| { + let bar = HostFunction::from(|_: FuncContext<'_>, (left, right): (i64, i32)| { assert_eq!(left, 1); assert_eq!(right, 2); Ok(left as i32 + right) @@ -138,7 +138,7 @@ fn host_fn() -> Result<()> { let mut imports = Imports::new(); imports.define("env", "bar", bar); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let host_fn = instance.func::<(), i32>(&store, "foo")?; assert_eq!(host_fn.call(&mut store, ())?, 3); Ok(()) @@ -148,7 +148,7 @@ fn printi32() -> Result<()> { let module = tinywasm::parse_file("./examples/rust/out/print.opt.wasm")?; let mut store = Store::default(); - let printi32 = HostFunction::from(&mut store, |_: FuncContext<'_>, x: i32| { + let printi32 = HostFunction::from(|_: FuncContext<'_>, x: i32| { println!("{x}"); Ok(()) }); @@ -156,7 +156,7 @@ fn printi32() -> Result<()> { let mut imports = Imports::new(); imports.define("env", "printi32", printi32); - let instance = ModuleInstance::instantiate(&mut store, &module, Some(imports))?; + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let add_and_print = instance.func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; From e005e5afd144f5659e2ed19afa8ee94ff924be55 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 9 Aug 2026 18:07:51 +0200 Subject: [PATCH 07/15] feat: initial exception support Signed-off-by: Henry --- CHANGELOG.md | 1 + README.md | 2 +- crates/cli/src/output.rs | 2 + crates/cli/src/wast_runner.rs | 179 +++++++++++++----- crates/parser/src/conversion.rs | 11 +- crates/parser/src/lib.rs | 1 + crates/parser/src/macros.rs | 1 + crates/parser/src/module.rs | 36 ++++ crates/parser/src/optimize.rs | 42 ++++ crates/parser/src/visit.rs | 127 ++++++++++++- crates/tinywasm/Cargo.toml | 2 - crates/tinywasm/src/error.rs | 7 +- crates/tinywasm/src/func/resume.rs | 20 +- crates/tinywasm/src/imports.rs | 36 +++- crates/tinywasm/src/instance.rs | 32 +++- crates/tinywasm/src/interpreter/executor.rs | 158 ++++++++++++++-- crates/tinywasm/src/interpreter/mod.rs | 8 +- .../tinywasm/src/interpreter/num_helpers.rs | 4 +- crates/tinywasm/src/reference.rs | 28 ++- crates/tinywasm/src/store/exception.rs | 10 + crates/tinywasm/src/store/mod.rs | 16 +- crates/tinywasm/src/store/state.rs | 19 ++ crates/tinywasm/src/store/tag.rs | 7 + crates/tinywasm/tests/generated/wasm-3.csv | 2 +- .../tinywasm/tests/generated/wasm-latest.csv | 2 +- crates/types/src/instructions.rs | 46 ++++- crates/types/src/lib.rs | 48 +++++ 27 files changed, 744 insertions(+), 103 deletions(-) create mode 100644 crates/tinywasm/src/store/exception.rs create mode 100644 crates/tinywasm/src/store/tag.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ae416e..64366f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for the WebAssembly function-references proposal - Added basic support for the WebAssembly garbage-collection proposal +- Added support for the WebAssembly exception-handling proposal, including tags, `try_table`, `throw`, and `throw_ref` - Added `WasmValue::ty` and `WasmValue::matches_type` - Added `ValueLane` for mapping WebAssembly value types to their physical 32-bit, 64-bit, or 128-bit storage lane. - Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules. diff --git a/README.md b/README.md index 484dbaf..305f61e 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ TinyWasm targets non-JavaScript core proposals through [phase 3](https://github. | [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes) | 🟢 | 0.9.0 | | [**Typed Function References**](https://github.com/WebAssembly/function-references) | 🟢 | `next` | | [**Garbage Collection**](https://github.com/WebAssembly/gc) | 🟢 | `next` | -| [**Exception Handling**](https://github.com/WebAssembly/exception-handling) | 🌑 | - | +| [**Exception Handling**](https://github.com/WebAssembly/exception-handling) | 🟢 | `next` | | [**Stack Switching**](https://github.com/WebAssembly/stack-switching) | 🌑 | - | | [**Compact Import Section**](https://github.com/WebAssembly/compact-import-section) | 🌑 | - | | [**Threads**](https://github.com/WebAssembly/threads) | 🌑 | - | diff --git a/crates/cli/src/output.rs b/crates/cli/src/output.rs index 40a271e..a2bd287 100644 --- a/crates/cli/src/output.rs +++ b/crates/cli/src/output.rs @@ -77,6 +77,7 @@ pub fn format_export_type(ty: ExportType<'_>) -> String { ExportType::Memory(ty) => format_memory_type(ty), ExportType::Table(ty) => format_table_type(ty), ExportType::Global(ty) => format_global_type(ty), + ExportType::Tag(ty) => format!("tag {}", format_func_type(ty)), } } @@ -86,5 +87,6 @@ pub fn format_import_type(ty: ImportType<'_>) -> String { ImportType::Memory(ty) => format_memory_type(ty), ImportType::Table(ty) => format_table_type(ty), ImportType::Global(ty) => format_global_type(ty), + ImportType::Tag(ty) => format!("tag {}", format_func_type(ty)), } } diff --git a/crates/cli/src/wast_runner.rs b/crates/cli/src/wast_runner.rs index 2ca622d..24483b6 100644 --- a/crates/cli/src/wast_runner.rs +++ b/crates/cli/src/wast_runner.rs @@ -19,53 +19,63 @@ const TEST_MAX_SUSPENSIONS: u32 = 1000; #[derive(Default)] struct ModuleRegistry { - modules: HashMap, - named_modules: HashMap, - last_module: Option, + definitions: HashMap, + instances: HashMap, + registered: HashMap, + last_definition: Option, + last_instance: Option, } impl ModuleRegistry { - fn modules(&self) -> &HashMap { - &self.modules + fn registered(&self) -> &HashMap { + &self.registered } - fn update_last_module(&mut self, module: ModuleInstance, name: Option) { - self.last_module = Some(module.clone()); + fn define(&mut self, module: Module, name: Option) { + self.last_definition = Some(module.clone()); if let Some(name) = name { - self.named_modules.insert(name, module); + self.definitions.insert(name, module); } } - fn register(&mut self, name: String, module: ModuleInstance) { + fn definition(&self, id: Option>) -> Option { + match id { + Some(id) => self.definitions.get(id.name()).cloned(), + None => self.last_definition.clone(), + } + } + + fn update_last_instance(&mut self, instance: ModuleInstance, name: Option) { + self.last_instance = Some(instance.clone()); + if let Some(name) = name { + self.instances.insert(name, instance); + } + } + + fn register(&mut self, name: String, id: Option>) -> bool { + let Some(instance) = self.get(id) else { return false }; debug!("registering module: {name}"); - self.modules.insert(name.clone(), module.clone()); - self.last_module = Some(module.clone()); - self.named_modules.insert(name, module); + self.registered.insert(name, instance); + true } fn get_idx(&self, module_id: Option>) -> Option { match module_id { - Some(module) => self - .modules - .get(module.name()) - .or_else(|| self.named_modules.get(module.name())) - .map(ModuleInstance::id), - None => self.last_module.as_ref().map(ModuleInstance::id), + Some(module) => { + self.registered.get(module.name()).or_else(|| self.instances.get(module.name())).map(ModuleInstance::id) + } + None => self.last_instance.as_ref().map(ModuleInstance::id), } } fn get(&self, module_id: Option>) -> Option { match module_id { Some(module_id) => { - self.modules.get(module_id.name()).or_else(|| self.named_modules.get(module_id.name())).cloned() + self.registered.get(module_id.name()).or_else(|| self.instances.get(module_id.name())).cloned() } - None => self.last_module.clone(), + None => self.last_instance.clone(), } } - - fn last(&self) -> Option { - self.last_module.clone() - } } #[derive(Default)] @@ -200,6 +210,11 @@ impl WastRunner { Ok(imports) } + fn instantiate_module(store: &mut Store, registry: &ModuleRegistry, module: &Module) -> Result { + let imports = Self::imports(store, registry.registered())?; + Ok(ModuleInstance::instantiate(store, module, Some(&imports))?) + } + pub fn run_file(&mut self, file: TestFile<'_>) -> Result<()> { let test_group = self.test_group(file.name(), file.parent()); let wast_raw = file.raw(); @@ -213,51 +228,72 @@ impl WastRunner { for (i, directive) in directives.into_iter().enumerate() { let span = directive.span(); use wast::WastDirective::{ - AssertExhaustion, AssertInvalid, AssertMalformed, AssertReturn, AssertTrap, AssertUnlinkable, Invoke, - Module as Wat, ModuleDefinition, Register, + AssertException, AssertExhaustion, AssertInvalid, AssertMalformed, AssertReturn, AssertTrap, + AssertUnlinkable, Invoke, Module as Wat, ModuleDefinition, ModuleInstance as Instance, Register, }; match directive { - Register { span, name, .. } => { - let Some(last) = module_registry.last() else { + Register { span, name, module } => { + if !module_registry.register(name.to_string(), module) { test_group.add_result( &format!("Register({i})"), span.linecol_in(wast_raw), - Err(eyre!("no module to register")), + Err(eyre!("module instance to register was not found")), ); continue; - }; - module_registry.register(name.to_string(), last); + } test_group.add_result(&format!("Register({i})"), span.linecol_in(wast_raw), Ok(())); } Wat(module) => { let result = catch_unwind_silent(|| { - let (name, bytes) = encode_quote_wat(module); - let module = parse_module_bytes(&bytes).expect("failed to parse module bytes"); - let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); - let module_instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports)) + let (name, module) = parse_quote_module(module).expect("failed to parse module bytes"); + let instance = Self::instantiate_module(&mut store, &module_registry, &module) .expect("failed to instantiate module"); - (name, module_instance) + (name, instance) }) .map_err(|e| eyre!("failed to parse wat module: {}", try_downcast_panic(e))); match &result { Err(err) => debug!("failed to parse module: {err:?}"), - Ok((name, module)) => module_registry.update_last_module(module.clone(), name.clone()), + Ok((name, instance)) => module_registry.update_last_instance(instance.clone(), name.clone()), }; test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast_raw), result.map(|_| ())); } ModuleDefinition(module) => { + let result = + catch_unwind_silent(|| parse_quote_module(module).expect("failed to parse module definition")) + .map_err(|err| eyre!("failed to parse module definition: {}", try_downcast_panic(err))); + + if let Ok((name, module)) = &result { + module_registry.define(module.clone(), name.clone()); + } + + test_group.add_result( + &format!("ModuleDefinition({i})"), + span.linecol_in(wast_raw), + result.map(|_| ()), + ); + } + Instance { span, instance, module } => { + let name = instance.map(|id| id.name().to_string()); let result = catch_unwind_silent(|| { - let (_, bytes) = encode_quote_wat(module); - parse_module_bytes(&bytes) + let module = module_registry + .definition(module) + .ok_or_else(|| eyre!("module definition was not found"))?; + Self::instantiate_module(&mut store, &module_registry, &module) }) - .map_err(|err| eyre!("failed to parse module definition: {}", try_downcast_panic(err))) - .and_then(|result| result) - .map(|_| ()); + .map_err(|err| eyre!("failed to instantiate module definition: {}", try_downcast_panic(err))) + .and_then(|result| result); - test_group.add_result(&format!("ModuleDefinition({i})"), span.linecol_in(wast_raw), result); + if let Ok(instance) = &result { + module_registry.update_last_instance(instance.clone(), name); + } + test_group.add_result( + &format!("ModuleInstance({i})"), + span.linecol_in(wast_raw), + result.map(|_| ()), + ); } AssertMalformed { span, mut module, message } => { let Ok(encoded) = module.encode() else { @@ -330,7 +366,7 @@ impl WastRunner { wast::WastExecute::Wat(mut wat) => { let module = parse_module_bytes(&wat.encode().expect("failed to encode module")) .expect("failed to parse module"); - let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); + let imports = Self::imports(&mut store, module_registry.registered()).unwrap(); ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; return Ok(()); } @@ -371,11 +407,37 @@ impl WastRunner { ), } } + AssertException { exec, span } => { + let res: Result, _> = catch_unwind_silent(|| { + let invoke = match exec { + wast::WastExecute::Wat(mut wat) => { + let module = parse_module_bytes(&wat.encode().expect("failed to encode module")) + .expect("failed to parse module"); + let imports = Self::imports(&mut store, module_registry.registered()).unwrap(); + ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; + return Ok(()); + } + wast::WastExecute::Get { .. } => panic!("get not supported"), + wast::WastExecute::Invoke(invoke) => invoke, + }; + let module = module_registry.get_idx(invoke.module); + let args = + convert_wastargs(invoke.args).map_err(|err| tinywasm::Error::Other(err.to_string()))?; + exec_fn_instance(module, &mut store, invoke.name, &args).map(|_| ()) + }); + let result = match res { + Err(err) => Err(eyre!("test panicked: {}", try_downcast_panic(err))), + Ok(Err(tinywasm::Error::Exception(_))) => Ok(()), + Ok(Err(err)) => Err(eyre!("expected exception, got: {err:?}")), + Ok(Ok(())) => Err(eyre!("expected exception, got Ok")), + }; + test_group.add_result(&format!("AssertException({i})"), span.linecol_in(wast_raw), result); + } AssertUnlinkable { mut module, span, message } => { let res = catch_unwind_silent(|| { let module = parse_module_bytes(&module.encode().expect("failed to encode module")) .expect("failed to parse module"); - let imports = Self::imports(&mut store, module_registry.modules()).unwrap(); + let imports = Self::imports(&mut store, module_registry.registered()).unwrap(); ModuleInstance::instantiate(&mut store, &module, Some(&imports)) }); match res { @@ -729,6 +791,11 @@ fn parse_module_bytes(bytes: &[u8]) -> Result { Ok(tinywasm::parse_bytes(bytes)?) } +fn parse_quote_module(module: QuoteWat) -> Result<(Option, Module)> { + let (name, bytes) = encode_quote_wat(module); + Ok((name, parse_module_bytes(&bytes)?)) +} + fn convert_wastargs(args: Vec) -> Result> { args.into_iter().map(wastarg2tinywasmvalue).collect() } @@ -947,4 +1014,26 @@ mod tests { let mut runner = WastRunner::new(); runner.run_paths(&[path]).unwrap(); } + + #[test] + fn runs_module_definition_and_instance_directives() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("instance.wast"); + std::fs::write( + &path, + r#" + (module definition $M (global (export "g") i32 (i32.const 42))) + (module instance $I $M) + (register "I" $I) + (module + (import "I" "g" (global $g i32)) + (func (export "get") (result i32) global.get $g)) + (assert_return (invoke "get") (i32.const 42)) + "#, + ) + .unwrap(); + + let mut runner = WastRunner::new(); + runner.run_paths(&[path]).unwrap(); + } } diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 4073a8e..7b52363 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -76,9 +76,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result { ImportKind::Global(GlobalType::new(convert_valtype(&ty.content_type)?, ty.mutable)) } - wasmparser::TypeRef::Tag(ty) => { - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); - } + wasmparser::TypeRef::Tag(ty) => ImportKind::Tag(convert_tag_type(ty)), _ => { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {:?}", import.ty))); } @@ -116,7 +114,8 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result ExternalKind::Table, wasmparser::ExternalKind::Memory => ExternalKind::Memory, wasmparser::ExternalKind::Global => ExternalKind::Global, - wasmparser::ExternalKind::Tag | wasmparser::ExternalKind::FuncExact => { + wasmparser::ExternalKind::Tag => ExternalKind::Tag, + wasmparser::ExternalKind::FuncExact => { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported export kind: {:?}", export.kind))); } }; @@ -124,6 +123,10 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result TagType { + TagType::new(ty.func_type_idx) +} + fn extend_local_types(local_types: &mut Vec, count: u32, ty: wasmparser::ValType) -> Result<()> { let size = value_lane(ty); let count = diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index fe968d8..8b6cb39 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -189,6 +189,7 @@ impl Parser { | WasmFeatures::SIMD | WasmFeatures::MEMORY64 | WasmFeatures::CUSTOM_PAGE_SIZES + | WasmFeatures::EXCEPTIONS | WasmFeatures::WIDE_ARITHMETIC; self.options.validation().then(|| Validator::new_with_features(features)) } diff --git a/crates/parser/src/macros.rs b/crates/parser/src/macros.rs index db1402d..98312ab 100644 --- a/crates/parser/src/macros.rs +++ b/crates/parser/src/macros.rs @@ -160,6 +160,7 @@ pub(crate) mod visit { (@@tail_call $($rest:tt)* ) => {}; (@@function_references $($rest:tt)* ) => {}; (@@gc $($rest:tt)* ) => {}; + (@@exceptions $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { fn $visit(&mut self $($(,_: $argty)*)?) -> Self::Output { diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 237478c..bbe2cf8 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -57,6 +57,7 @@ pub(crate) struct ModuleReader<'a> { pub(crate) globals: Box<[Global]>, pub(crate) tables: Box<[TableDefinition]>, pub(crate) memory_types: Box<[MemoryType]>, + pub(crate) tags: Box<[TagType]>, pub(crate) imports: Box<[Import]>, pub(crate) data: Box<[Data]>, pub(crate) elements: Box<[Element]>, @@ -78,6 +79,7 @@ impl<'a> ModuleReader<'a> { &self.globals, &self.memory_types, &self.tables, + &self.tags, ))); } self.translation_metadata.as_deref().unwrap() @@ -182,6 +184,25 @@ impl<'a> ModuleReader<'a> { self.memory_types = reader.into_iter().map(|memory| Ok(convert_module_memory(memory?))).collect::>()?; } + Payload::TagSection(reader) => { + check_section("tag", !self.tags.is_empty())?; + #[cfg(feature = "validate")] + if let Some(validator) = validator.as_mut() { + validator.tag_section(&reader)?; + } + let mut tags = Vec::with_capacity(reader.count() as usize); + for tag in reader { + let tag = convert_tag_type(tag?); + let ty = self.types.get(tag.type_idx).and_then(SubType::as_func).ok_or_else(|| { + ParseError::Other(format!("tag type index does not reference a function: {}", tag.type_idx)) + })?; + if !ty.results().is_empty() { + return Err(ParseError::Other(format!("tag type must not have results: {}", tag.type_idx))); + } + tags.push(tag); + } + self.tags = tags.into_boxed_slice(); + } Payload::ElementSection(reader) => { debug!("Found element section"); #[cfg(feature = "validate")] @@ -249,6 +270,20 @@ impl<'a> ModuleReader<'a> { self.imported_func_count += 1; } ImportKind::Memory(_) => self.imported_memory_count += 1, + ImportKind::Tag(tag) => { + let ty = self.types.get(tag.type_idx).and_then(SubType::as_func).ok_or_else(|| { + ParseError::Other(format!( + "tag import type index does not reference a function: {}", + tag.type_idx + )) + })?; + if !ty.results().is_empty() { + return Err(ParseError::Other(format!( + "tag import type must not have results: {}", + tag.type_idx + ))); + } + } _ => {} } imports.push(import); @@ -553,6 +588,7 @@ impl<'a> ModuleReader<'a> { exports: self.exports, elements: self.elements, memory_types: self.memory_types, + tags: self.tags, local_memory_allocation, } .into()) diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index 74a9fc0..be37e5e 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -910,11 +910,28 @@ fn is_unconditional_terminator(instr: Instruction) -> bool { | Instruction::ReturnCallSelf | Instruction::ReturnCallIndirect(..) | Instruction::ReturnCallRef(_) + | Instruction::Throw(_) + | Instruction::ThrowRef ) } fn target_boundaries(instructions: &[Instruction], function_data: &WasmFunctionData) -> Result> { let mut boundaries = alloc::vec![false; instructions.len() + 1]; + for handler in &function_data.exception_handlers { + for target in [handler.start_ip, handler.end_ip] { + let boundary = boundaries.get_mut(target as usize).ok_or_else(|| { + ParseError::Other(alloc::format!("exception handler boundary out of bounds: {target}")) + })?; + *boundary = true; + } + for catch in &handler.catches { + let target = catch.landing_pad(); + let boundary = boundaries + .get_mut(target as usize) + .ok_or_else(|| ParseError::Other(alloc::format!("exception landing pad out of bounds: {target}")))?; + *boundary = true; + } + } for instr in instructions { if let Some(target) = instruction_target(instr) { let boundary = boundaries @@ -948,6 +965,31 @@ fn finalize( imported_memory_count: u32, ) -> Result { let len = instructions.len() as u32; + for handler in &mut function_data.exception_handlers { + if let Some(old_to_new) = old_to_new { + handler.start_ip = *old_to_new.get(handler.start_ip as usize).ok_or_else(|| { + ParseError::Other(alloc::format!("exception handler boundary out of bounds: {}", handler.start_ip)) + })?; + handler.end_ip = *old_to_new.get(handler.end_ip as usize).ok_or_else(|| { + ParseError::Other(alloc::format!("exception handler boundary out of bounds: {}", handler.end_ip)) + })?; + for catch in &mut handler.catches { + let landing_pad = match catch { + tinywasm_types::ExceptionCatch::Tag { landing_pad, .. } + | tinywasm_types::ExceptionCatch::All { landing_pad, .. } => landing_pad, + }; + *landing_pad = *old_to_new.get(*landing_pad as usize).ok_or_else(|| { + ParseError::Other(alloc::format!("exception landing pad out of bounds: {landing_pad}")) + })?; + } + } + if handler.start_ip > handler.end_ip || handler.end_ip > len { + return Err(ParseError::Other("exception handler range out of bounds".into())); + } + if handler.catches.iter().any(|catch| catch.landing_pad() >= len) { + return Err(ParseError::Other("exception landing pad out of bounds".into())); + } + } for target in &mut function_data.branch_table_targets { if let Some(old_to_new) = old_to_new { *target = *old_to_new diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 46d1f9f..c462d05 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -6,8 +6,8 @@ use crate::{ use alloc::string::ToString; use alloc::vec::Vec; use tinywasm_types::{ - Global, Import, ImportKind, Instruction, MemoryArg, MemoryType, StorageType, SubType, TableDefinition, TypeSection, - ValueCounts, ValueLane, WasmFunctionData, + Global, Import, ImportKind, Instruction, MemoryArg, MemoryType, StorageType, SubType, TableDefinition, TagType, + TypeSection, ValueCounts, ValueLane, WasmFunctionData, }; use wasmparser::{FunctionBody, OperatorsReader, OperatorsReaderAllocations, VisitSimdOperator}; @@ -20,6 +20,7 @@ enum BlockKind { Block, Loop, If, + TryTable(usize), } struct ControlFrame { @@ -48,6 +49,7 @@ pub(crate) struct ModuleMetadata { globals: Vec, memories: Vec, tables: Vec, + tags: Vec, types: Vec, } @@ -55,6 +57,7 @@ pub(crate) struct ModuleMetadata { struct FunctionDataBuilder { v128_constants: Vec<[u8; 16]>, branch_table_targets: Vec, + exception_handlers: Vec, } pub(crate) struct FunctionBuilder<'a> { @@ -133,11 +136,13 @@ impl ModuleMetadata { globals: &[Global], memories: &[MemoryType], tables: &[TableDefinition], + tags: &[TagType], ) -> Self { let mut functions = Vec::with_capacity(imports.len() + code_type_addrs.len()); let mut global_sizes = Vec::with_capacity(imports.len() + globals.len()); let mut memory_sizes = Vec::with_capacity(imports.len() + memories.len()); let mut table_sizes = Vec::with_capacity(imports.len() + tables.len()); + let mut tag_types = Vec::with_capacity(imports.len() + tags.len()); for import in imports { match &import.kind { @@ -145,6 +150,7 @@ impl ModuleMetadata { ImportKind::Global(ty) => global_sizes.push(ValueLane::from(&ty.ty)), ImportKind::Memory(ty) => memory_sizes.push(Self::address_lane(ty.arch())), ImportKind::Table(ty) => table_sizes.push(Self::address_lane(ty.arch())), + ImportKind::Tag(ty) => tag_types.push(ty.type_idx), } } @@ -152,6 +158,7 @@ impl ModuleMetadata { global_sizes.extend(globals.iter().map(|global| ValueLane::from(&global.ty.ty))); memory_sizes.extend(memories.iter().map(|ty| Self::address_lane(ty.arch()))); table_sizes.extend(tables.iter().map(|table| Self::address_lane(table.ty.arch()))); + tag_types.extend(tags.iter().map(|tag| tag.type_idx)); let signatures = types .types @@ -169,6 +176,7 @@ impl ModuleMetadata { globals: global_sizes, memories: memory_sizes, tables: table_sizes, + tags: tag_types, types: types.types.to_vec(), } } @@ -188,6 +196,14 @@ impl ModuleMetadata { self.signature(ty) } + fn tag_signature(&self, idx: u32) -> Result<&Signature> { + let ty = *self + .tags + .get(idx as usize) + .ok_or_else(|| crate::ParseError::Other(alloc::format!("tag index out of bounds: {idx}")))?; + self.signature(ty) + } + fn global_size(&self, idx: u32) -> Result { Self::indexed_size(&self.globals, "global", idx) } @@ -284,6 +300,7 @@ pub(crate) fn process_operators( let data = WasmFunctionData { v128_constants: builder.data.v128_constants.into_boxed_slice(), branch_table_targets: builder.data.branch_table_targets.into_boxed_slice(), + exception_handlers: builder.data.exception_handlers.into_boxed_slice(), }; Ok((builder.instructions, data, reader.into_allocations())) } @@ -320,6 +337,7 @@ pub(crate) fn process_operators_and_validate( let data = WasmFunctionData { v128_constants: builder.data.v128_constants.into_boxed_slice(), branch_table_targets: builder.data.branch_table_targets.into_boxed_slice(), + exception_handlers: builder.data.exception_handlers.into_boxed_slice(), }; Ok((builder.instructions, data, validator.into_allocations(), reader.into_allocations())) } @@ -648,6 +666,89 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { self.push_control(BlockKind::If, ty, Some(self.instructions.len() - 1)) } + fn visit_try_table(&mut self, try_table: wasmparser::TryTable) -> Self::Output { + let signature = self.block_signature(try_table.ty)?; + for &size in signature.params.iter().rev() { + self.pop_expect(size)?; + } + let height = self.operand_stack.len(); + let base = self.lane_counts; + let entry_unreachable = self.is_unreachable(); + + let body_jump = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + let mut catches = Vec::with_capacity(try_table.catches.len()); + for catch in try_table.catches { + let (tag, depth, with_ref) = match catch { + wasmparser::Catch::One { tag, label } => (Some(tag), label, false), + wasmparser::Catch::OneRef { tag, label } => (Some(tag), label, true), + wasmparser::Catch::All { label } => (None, label, false), + wasmparser::Catch::AllRef { label } => (None, label, true), + }; + if let Some(tag) = tag { + self.metadata.tag_signature(tag)?; + } + let target_idx = self.get_ctx_idx(depth)?; + let target_base = self.control_stack[target_idx].base; + let landing_pad = u32::try_from(self.instructions.len()) + .map_err(|_| crate::ParseError::Other("function body is too large".into()))?; + match self.control_stack[target_idx].kind { + BlockKind::Function => self.instructions.push(Instruction::Return), + BlockKind::Loop => { + self.instructions.push(Instruction::Jump(self.control_stack[target_idx].start_ip as u32)); + } + BlockKind::Block | BlockKind::If | BlockKind::TryTable(_) => { + self.control_stack[target_idx].branch_jumps.push(self.instructions.len()); + self.control_stack[target_idx].end_reachable = true; + self.instructions.push(Instruction::Jump(0)); + } + } + catches.push(match tag { + Some(tag) => tinywasm_types::ExceptionCatch::Tag { tag, landing_pad, base: target_base, with_ref }, + None => tinywasm_types::ExceptionCatch::All { landing_pad, base: target_base, with_ref }, + }); + } + + let body_start = self.instructions.len(); + self.patch_jump(body_jump, body_start); + let handler_idx = self.data.exception_handlers.len(); + self.data.exception_handlers.push(tinywasm_types::ExceptionHandler { + start_ip: body_start as u32, + end_ip: 0, + catches: catches.into_boxed_slice(), + }); + self.push_sizes(&signature.params)?; + self.control_stack.push(ControlFrame { + kind: BlockKind::TryTable(handler_idx), + has_else: false, + start_ip: body_start, + branch_jumps: Vec::new(), + height, + base, + params: signature.params, + results: signature.results, + unreachable: entry_unreachable, + entry_unreachable, + end_reachable: false, + }); + Ok(()) + } + + fn visit_throw(&mut self, tag_index: u32) -> Self::Output { + let signature = self.metadata.tag_signature(tag_index)?.clone(); + self.apply_effect(&signature.params, &[])?; + self.instructions.push(Instruction::Throw(tag_index)); + self.mark_unreachable(); + Ok(()) + } + + fn visit_throw_ref(&mut self) -> Self::Output { + self.apply_effect(&[ValueLane::S32], &[])?; + self.instructions.push(Instruction::ThrowRef); + self.mark_unreachable(); + Ok(()) + } + fn visit_else(&mut self) -> Self::Output { let (cond_jump_ip, height, base, params, entry_unreachable) = { let ctx = self @@ -672,6 +773,9 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { fn visit_end(&mut self) -> Self::Output { let ctx = self.control_stack.pop().ok_or_else(|| crate::ParseError::Other("end without control frame".into()))?; + if let BlockKind::TryTable(handler_idx) = ctx.kind { + self.data.exception_handlers[handler_idx].end_ip = self.instructions.len() as u32; + } if matches!(ctx.kind, BlockKind::Function) { self.instructions.push(Instruction::Return); } else { @@ -1184,11 +1288,24 @@ impl FunctionBuilder<'_> { /// Enters a control frame with its parameters restored above the saved base. fn push_control(&mut self, kind: BlockKind, ty: wasmparser::BlockType, initial_jump: Option) -> Result<()> { - let signature = match ty { + let signature = self.block_signature(ty)?; + self.push_control_signature(kind, signature, initial_jump) + } + + fn block_signature(&self, ty: wasmparser::BlockType) -> Result { + Ok(match ty { wasmparser::BlockType::Empty => Signature { params: Vec::new(), results: Vec::new() }, wasmparser::BlockType::Type(ty) => Signature { params: Vec::new(), results: alloc::vec![value_lane(ty)] }, wasmparser::BlockType::FuncType(idx) => self.metadata.signature(idx)?.clone(), - }; + }) + } + + fn push_control_signature( + &mut self, + kind: BlockKind, + signature: Signature, + initial_jump: Option, + ) -> Result<()> { for &size in signature.params.iter().rev() { self.pop_expect(size)?; } @@ -1264,7 +1381,7 @@ impl FunctionBuilder<'_> { match self.control_stack[ctx_idx].kind { BlockKind::Function => self.instructions.push(Instruction::Return), BlockKind::Loop => self.instructions.push(Instruction::Jump(self.control_stack[ctx_idx].start_ip as u32)), - BlockKind::Block | BlockKind::If => { + BlockKind::Block | BlockKind::If | BlockKind::TryTable(_) => { self.control_stack[ctx_idx].branch_jumps.push(self.instructions.len()); self.control_stack[ctx_idx].end_reachable = true; self.instructions.push(Instruction::Jump(0)); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 6cf9b17..67d0742 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -34,7 +34,6 @@ name = "test-wasm-1" [[test]] harness = false name = "test-wasm-3" -test = false [[test]] harness = false @@ -43,7 +42,6 @@ name = "test-wasm-2" [[test]] harness = false name = "test-wasm-latest" -test = false [[test]] harness = false diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index ebd0470..0597847 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -2,8 +2,8 @@ use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt::{Debug, Display}; -use tinywasm_types::FuncType; use tinywasm_types::archive::TwasmError; +use tinywasm_types::{ExnRef, FuncType}; #[cfg(feature = "parser")] pub use tinywasm_parser::ParseError; @@ -14,6 +14,9 @@ pub enum Error { /// A WebAssembly trap occurred Trap(Trap), + /// An uncaught WebAssembly exception occurred. + Exception(ExnRef), + /// A linking error occurred Linker(LinkingError), @@ -50,6 +53,7 @@ impl PartialEq for Error { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Trap(a), Self::Trap(b)) => a == b, + (Self::Exception(a), Self::Exception(b)) => a == b, (Self::Linker(a), Self::Linker(b)) => a == b, (Self::UnsupportedFeature(a), Self::UnsupportedFeature(b)) => a == b, (Self::Other(a), Self::Other(b)) => a == b, @@ -274,6 +278,7 @@ impl Display for Error { Self::Twasm(err) => write!(f, "serialization error: {err}"), Self::Trap(trap) => write!(f, "trap: {trap}"), + Self::Exception(_) => write!(f, "uncaught WebAssembly exception"), Self::Linker(err) => write!(f, "linking error: {err}"), Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {message}"), diff --git a/crates/tinywasm/src/func/resume.rs b/crates/tinywasm/src/func/resume.rs index a71be32..892aa05 100644 --- a/crates/tinywasm/src/func/resume.rs +++ b/crates/tinywasm/src/func/resume.rs @@ -3,7 +3,7 @@ use tinywasm_types::{FuncAddr, TypeAddr, WasmValue}; use super::{FromWasmValues, Function, FunctionTyped, IntoWasmValues}; use crate::interpreter::stack::{CallFrame, StackBase}; -use crate::{Error, InterpreterRuntime, Result, Store, Trap}; +use crate::{Error, InterpreterRuntime, Result, Store}; #[derive(Clone, PartialEq, Eq)] /// Progress for fuel-limited function execution. @@ -93,7 +93,7 @@ impl Function { impl<'store> FuncExecution<'store> { fn resume_raw( &mut self, - run: impl FnOnce(&mut Store, CallFrame) -> Result, + run: impl FnOnce(&mut Store, CallFrame) -> Result, ) -> Result> { let (callframe, root_func_addr) = match &mut self.state { FuncExecutionState::Running { callframe, root_func_addr } => (*callframe, *root_func_addr), @@ -109,7 +109,17 @@ impl<'store> FuncExecution<'store> { let result = run(self.store, callframe); self.store.exit_execution(); - match result? { + let result = match result { + Ok(result) => result, + Err(error) => { + self.store.call_stack.clear(); + self.store.value_stack.clear(); + self.state = FuncExecutionState::Completed(None); + return Err(error); + } + }; + + match result { crate::interpreter::ExecState::Completed => { let func = self.store.state.get_func(root_func_addr); let result_ty = func.type_addr; @@ -129,7 +139,7 @@ impl<'store> FuncExecution<'store> { fn resume( &mut self, - run: impl FnOnce(&mut Store, CallFrame) -> Result, + run: impl FnOnce(&mut Store, CallFrame) -> Result, ) -> Result>> { match self.resume_raw(run)? { ExecProgress::Completed(CallResult::Stack { type_addr, pin_refs }) => { @@ -231,7 +241,7 @@ impl FunctionTyped { impl<'store, R: FromWasmValues> FuncExecutionTyped<'store, R> { fn resume( &mut self, - run: impl FnOnce(&mut Store, CallFrame) -> Result, + run: impl FnOnce(&mut Store, CallFrame) -> Result, ) -> Result> { match self.execution.resume_raw(run)? { ExecProgress::Completed(CallResult::Stack { type_addr, pin_refs }) => Ok(ExecProgress::Completed( diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index a35cf2c..8232a90 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -3,7 +3,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::hint::cold_path; -use crate::{Function, Global, HostFunction, LinkingError, Memory, Result, Table}; +use crate::{Function, Global, HostFunction, LinkingError, Memory, Result, Table, Tag}; use tinywasm_types::*; #[derive(Clone)] @@ -21,6 +21,8 @@ pub enum Extern { Function(Function), /// A reusable host function definition. HostFunction(HostFunction), + /// A tag instance. + Tag(Tag), } macro_rules! impl_conv { @@ -41,6 +43,7 @@ impl_conv! { Memory => Memory, Function => Function, HostFunction => HostFunction, + Tag => Tag, } /// Imports for a module instance @@ -100,6 +103,7 @@ pub(crate) struct ResolvedImports { pub(crate) tables: Vec, pub(crate) memories: Vec, pub(crate) funcs: Vec, + pub(crate) tags: Vec, } impl Imports { @@ -127,7 +131,7 @@ impl Imports { /// Define an import value. /// - /// A [`Function`], [`Global`], [`Table`], or [`Memory`] handle belongs to + /// A [`Function`], [`Global`], [`Table`], [`Memory`], or [`Tag`] handle belongs to /// one store and can only be imported into that store. A [`HostFunction`] /// is a reusable definition and can be imported into multiple stores. /// @@ -225,12 +229,13 @@ impl Imports { module: &Module, type_addrs: &[TypeAddr], ) -> Result { - let (global_count, table_count, mem_count, func_count) = - module.imports.iter().fold((0, 0, 0, 0), |(g, t, m, f), import| match import.kind { - ImportKind::Global(_) => (g + 1, t, m, f), - ImportKind::Table(_) => (g, t + 1, m, f), - ImportKind::Memory(_) => (g, t, m + 1, f), - ImportKind::Function(_) => (g, t, m, f + 1), + let (global_count, table_count, mem_count, func_count, tag_count) = + module.imports.iter().fold((0, 0, 0, 0, 0), |(g, t, m, f, e), import| match import.kind { + ImportKind::Global(_) => (g + 1, t, m, f, e), + ImportKind::Table(_) => (g, t + 1, m, f, e), + ImportKind::Memory(_) => (g, t, m + 1, f, e), + ImportKind::Function(_) => (g, t, m, f + 1, e), + ImportKind::Tag(_) => (g, t, m, f, e + 1), }); let mut imports = ResolvedImports { @@ -238,6 +243,7 @@ impl Imports { tables: Vec::with_capacity(table_count + module.tables.len()), memories: Vec::with_capacity(mem_count + module.memory_types.len()), funcs: Vec::with_capacity(func_count + module.funcs.len()), + tags: Vec::with_capacity(tag_count + module.tags.len()), }; for import in &*module.imports { @@ -259,6 +265,10 @@ impl Imports { Extern::HostFunction(func) => { (ExternVal::Func(func.instantiate_for_import(store, type_addrs)?.addr()), None) } + Extern::Tag(tag) => { + tag.0.validate_store(store)?; + (ExternVal::Tag(tag.0.addr), None) + } } } else { let Some(instance) = self.modules.get(import.module.as_ref()) else { @@ -317,6 +327,16 @@ impl Imports { } imports.funcs.push(func_addr); } + (ExternVal::Tag(tag_addr), ImportKind::Tag(ty)) => { + let expected_type_addr = type_addrs + .get(ty.type_idx as usize) + .ok_or_else(|| LinkingError::incompatible_import_type(import))?; + if store.state.get_tag(tag_addr).type_addr != *expected_type_addr { + cold_path(); + return Err(LinkingError::incompatible_import_type(import).into()); + } + imports.tags.push(tag_addr); + } _ => unreachable!("import kind checked above"), } } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 52bb948..23bb298 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -4,7 +4,7 @@ use tinywasm_types::*; use crate::func::{FromWasmValues, IntoWasmValues, ToWasmTypes}; use crate::store::MemoryInstance; -use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, StoreItem, Table, Trap}; +use crate::{Error, Function, FunctionTyped, Global, Imports, Memory, Result, Store, StoreItem, Table, Tag, Trap}; /// A typed view over an exported extern value. pub enum ExternItem { @@ -16,6 +16,8 @@ pub enum ExternItem { Table(Table), /// Exported global reference. Global(Global), + /// Exported tag reference. + Tag(Tag), } /// An instantiated WebAssembly module @@ -52,6 +54,7 @@ struct ModuleInstanceInner { table_addrs: Box<[TableAddr]>, mem_addrs: Box<[MemAddr]>, global_addrs: Box<[GlobalAddr]>, + tag_addrs: Box<[TagAddr]>, elem_addrs: Box<[ElemAddr]>, data_addrs: Box<[DataAddr]>, func_start: Option, @@ -100,6 +103,11 @@ impl ModuleInstance { *self.0.global_addrs.get(addr as usize).unwrap_or_else(|| unreachable!("invalid global address: {addr}")) } + #[inline] + pub(crate) fn resolve_tag_addr(&self, addr: TagAddr) -> TagAddr { + *self.0.tag_addrs.get(addr as usize).unwrap_or_else(|| unreachable!("invalid tag address: {addr}")) + } + #[inline] pub(crate) fn validate_store(&self, store: &Store) -> Result<()> { if self.0.store_id != store.id() { @@ -157,6 +165,7 @@ impl ModuleInstance { let mut addrs = imports.unwrap_or(&default_imports).link(store, module, &type_addrs)?; let imported_funcs = addrs.funcs.len(); addrs.funcs.extend(store.init_funcs(&module.funcs, id, &module.func_type_idxs[imported_funcs..], &type_addrs)); + addrs.tags.extend(store.init_tags(&module.tags, &type_addrs)); match module.local_memory_allocation { LocalMemoryAllocation::Skip => { #[cfg(feature = "guest-debug")] @@ -185,6 +194,7 @@ impl ModuleInstance { table_addrs: addrs.tables.into_boxed_slice(), mem_addrs: addrs.memories.into_boxed_slice(), global_addrs: addrs.globals.into_boxed_slice(), + tag_addrs: addrs.tags.into_boxed_slice(), elem_addrs, data_addrs, func_start: module.start_func, @@ -209,6 +219,7 @@ impl ModuleInstance { ExternalKind::Table => self.0.table_addrs.get(export.index as usize)?, ExternalKind::Memory => self.0.mem_addrs.get(export.index as usize)?, ExternalKind::Global => self.0.global_addrs.get(export.index as usize)?, + ExternalKind::Tag => self.0.tag_addrs.get(export.index as usize)?, }; Some(ExternVal::new(export.kind, *addr)) } @@ -257,6 +268,9 @@ impl ModuleInstance { ExternalKind::Global => { ExternItem::Global(Global(StoreItem::new(self.0.store_id, self.resolve_global_addr(export.index)))) } + ExternalKind::Tag => { + ExternItem::Tag(Tag(StoreItem::new(self.0.store_id, self.resolve_tag_addr(export.index)))) + } }; (export.name.as_ref(), item) @@ -309,6 +323,7 @@ impl ModuleInstance { ExternVal::Memory(addr) => Ok(ExternItem::Memory(Memory(StoreItem::new(self.0.store_id, addr)))), ExternVal::Table(addr) => Ok(ExternItem::Table(Table(StoreItem::new(self.0.store_id, addr)))), ExternVal::Global(addr) => Ok(ExternItem::Global(Global(StoreItem::new(self.0.store_id, addr)))), + ExternVal::Tag(addr) => Ok(ExternItem::Tag(Tag(StoreItem::new(self.0.store_id, addr)))), } } @@ -487,6 +502,21 @@ impl ModuleInstance { Ok(Table(StoreItem::new(self.0.store_id, Self::index_addr(&self.0.table_addrs, table_index, "table")?))) } + /// Get a tag export by name. + pub fn tag(&self, name: &str) -> Result { + match self.require_export(name)? { + ExternVal::Tag(tag_addr) => Ok(Tag(StoreItem::new(self.0.store_id, tag_addr))), + _ => Err(Error::Other(format!("Export is not a tag: {name}"))), + } + } + + /// Get a tag by its module-local index. + #[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))] + #[cfg(feature = "guest-debug")] + pub fn tag_by_index(&self, tag_index: TagAddr) -> Result { + Ok(Tag(StoreItem::new(self.0.store_id, Self::index_addr(&self.0.tag_addrs, tag_index, "tag")?))) + } + /// Get the value of a global export by name. pub fn global_get(&self, store: &Store, name: &str) -> Result { self.global(name)?.get(store) diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index f5c9351..22c8441 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -65,7 +65,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } #[inline(always)] - fn exec(&mut self) -> Result, Trap> { + fn exec(&mut self) -> Result> { macro_rules! stack_op { (unary $ty:ty, |$v:ident| $expr:expr) => { stack_op!(unary $ty => $ty, |$v| $expr) @@ -223,7 +223,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { use tinywasm_types::Instruction::*; #[rustfmt::skip] match &self.func.instructions[self.cf.instr_ptr] { - Unreachable => { cold_path(); return Err(Trap::Unreachable) }, + Unreachable => { cold_path(); return Err(Trap::Unreachable.into()) }, Drop32 => { _ = Value32::stack_pop(&mut self.store.value_stack)}, Drop64 => { _ = Value64::stack_pop(&mut self.store.value_stack)}, Drop128 => { _ = Value128::stack_pop(&mut self.store.value_stack)}, @@ -239,6 +239,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { ReturnCallSelf => { self.exec_return_call_self()?; return Ok(None); } ReturnCallIndirect(ty, table) => { if self.exec_call_indirect::(*ty, *table)? { return Ok(Some(())); } return Ok(None); } ReturnCallRef(ty) => { if self.exec_call_ref::(*ty)? { return Ok(Some(())); } return Ok(None); } + Throw(tag) => { self.exec_throw(*tag)?; return Ok(None); } + ThrowRef => { self.exec_throw_ref()?; return Ok(None); } Jump(ip) => { self.cf.instr_ptr = *ip as usize; return Ok(None); } JumpIfZero32(ip) => if self.exec_jump_zero_32(*ip) { return Ok(None) }, JumpIfNonZero32(ip) => if self.exec_jump_non_zero_32(*ip) { return Ok(None) }, @@ -472,7 +474,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { RefCast(ty) => { if !self.exec_ref_matches(*ty) { cold_path(); - return Err(Trap::CastFailure); + return Err(Trap::CastFailure.into()); } } // GC objects @@ -992,6 +994,135 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.value_stack.truncate_keep_counts(base, drop_keep.keep); } + fn create_exception(&mut self, tag_index: TagAddr) -> Result { + let tag_addr = self.module.resolve_tag_addr(tag_index); + let type_addr = self.store.state.get_tag(tag_addr).type_addr; + let addr = u32::try_from(self.store.state.exceptions.len()).map_err(|_| Trap::OutOfMemory)?; + self.store.state.exceptions.try_reserve(1).map_err(|_| Trap::OutOfMemory)?; + let params = self.store.state.get_canonical_func_type(type_addr).params(); + let mut payload = Vec::new(); + payload.try_reserve_exact(params.len()).map_err(|_| Trap::OutOfMemory)?; + for &ty in params.iter().rev() { + payload.push(match ty { + WasmType::I32 | WasmType::F32 => { + TinyWasmValue::Value32(Value32::stack_pop(&mut self.store.value_stack)) + } + WasmType::I64 | WasmType::F64 => { + TinyWasmValue::Value64(Value64::stack_pop(&mut self.store.value_stack)) + } + WasmType::V128 => TinyWasmValue::Value128(Value128::stack_pop(&mut self.store.value_stack)), + WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::stack_pop(&mut self.store.value_stack)), + }); + } + payload.reverse(); + self.store + .state + .exceptions + .push(crate::store::ExceptionInstance { tag_addr, payload: payload.into_boxed_slice() }); + Ok(addr) + } + + fn exec_throw(&mut self, tag_index: TagAddr) -> Result<()> { + let exception = self.create_exception(tag_index)?; + let outcome = match self.dispatch_exception(exception) { + Ok(outcome) => outcome, + Err(trap) => { + _ = self.store.state.exceptions.pop(); + return Err(trap.into()); + } + }; + match outcome { + Some(catch) if !catch.with_ref() => { + debug_assert_eq!(self.store.state.exceptions.len() - 1, exception as usize); + _ = self.store.state.exceptions.pop(); + Ok(()) + } + Some(_) => Ok(()), + None => Err(Error::Exception(ExnRef::new(exception))), + } + } + + fn exec_throw_ref(&mut self) -> Result<()> { + let exception = ValueRef::stack_pop(&mut self.store.value_stack); + let exception = exception + .addr() + .filter(|addr| self.store.state.exceptions.get(*addr as usize).is_some()) + .ok_or(Trap::NullReference)?; + match self.dispatch_exception(exception)? { + Some(_) => Ok(()), + None => Err(Error::Exception(ExnRef::new(exception))), + } + } + + fn matching_catch(&self, protected_ip: usize, tag_addr: TagAddr) -> Option { + let handlers = &self.func.data.exception_handlers; + let end = handlers.partition_point(|handler| handler.start_ip as usize <= protected_ip); + handlers[..end] + .iter() + .rev() + .filter(|handler| protected_ip < handler.end_ip as usize) + .flat_map(|handler| handler.catches.iter().copied()) + .find(|catch| match catch { + ExceptionCatch::Tag { tag, .. } => self.module.resolve_tag_addr(*tag) == tag_addr, + ExceptionCatch::All { .. } => true, + }) + } + + fn switch_to_frame(&mut self, frame: CallFrame) { + let previous = core::mem::replace(&mut self.cf, frame); + if previous.func_addr == self.cf.func_addr { + return; + } + + let wasm_func = self.store.state.get_wasm_func(self.cf.func_addr); + self.func = wasm_func.func.clone(); + if wasm_func.owner != self.module.id() { + self.module = self + .store + .get_module_instance(wasm_func.owner) + .unwrap_or_else(|| unreachable!("invalid module instance")) + .clone(); + } + } + + fn dispatch_exception(&mut self, exception_addr: ExnAddr) -> Result, Trap> { + let tag_addr = self.store.state.exceptions[exception_addr as usize].tag_addr; + let mut protected_ip = self.cf.instr_ptr; + loop { + if let Some(catch) = self.matching_catch(protected_ip, tag_addr) { + let (landing_pad, base, with_ref, include_payload) = match catch { + ExceptionCatch::Tag { landing_pad, base, with_ref, .. } => (landing_pad, base, with_ref, true), + ExceptionCatch::All { landing_pad, base, with_ref } => (landing_pad, base, with_ref, false), + }; + let stack_base = self.cf.stack_base(); + let target = interpreter::stack::StackBase { + s32: stack_base.s32 + base.c32 as u32, + s64: stack_base.s64 + base.c64 as u32, + s128: stack_base.s128 + base.c128 as u32, + }; + self.store.value_stack.truncate_to_base(target); + if include_payload { + let Store { state, value_stack, .. } = self.store; + for value in state.exceptions[exception_addr as usize].payload.iter().copied() { + value_stack.push_dyn(value)?; + } + } + if with_ref { + self.store.value_stack.push(ValueRef::from_category_addr(exception_addr))?; + } + self.cf.instr_ptr = landing_pad as usize; + return Ok(Some(catch)); + } + + self.store.value_stack.truncate_to_base(self.cf.locals_base); + let Some(caller) = self.store.call_stack.pop_frame(self.call_stack_base) else { + return Ok(None); + }; + self.switch_to_frame(caller); + protected_ip = self.cf.instr_ptr.checked_sub(1).unwrap_or_else(|| unreachable!("invalid caller IP")); + } + } + fn exec_call(&mut self, wasm_func: WasmFunctionInstance, func_addr: FuncAddr) -> Result<(), Trap> { if !Arc::ptr_eq(&self.func, &wasm_func.func) { self.func = wasm_func.func.clone(); @@ -1207,20 +1338,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let Some(caller) = self.store.call_stack.pop_frame(self.call_stack_base) else { return true; }; - if caller.func_addr == self.cf.func_addr { - self.cf = caller; - return false; - } - let wasm_func = self.store.state.get_wasm_func(caller.func_addr); - self.func = wasm_func.func.clone(); - if wasm_func.owner != self.module.id() { - self.module = self - .store - .get_module_instance(wasm_func.owner) - .unwrap_or_else(|| unreachable!("invalid module instance")) - .clone(); - } - self.cf = caller; + self.switch_to_frame(caller); false } @@ -1922,7 +2040,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { impl<'store> Executor<'store, false> { #[inline(always)] - pub(crate) fn run_to_completion(&mut self) -> Result<(), Trap> { + pub(crate) fn run_to_completion(&mut self) -> Result<()> { // ideally we use `loop_match` / `become` once thats stabilized loop { if self.exec()?.is_some() { @@ -1933,7 +2051,7 @@ impl<'store> Executor<'store, false> { #[cfg(feature = "std")] #[inline(always)] - pub(crate) fn run_with_time_budget(&mut self, time_budget: core::time::Duration) -> Result { + pub(crate) fn run_with_time_budget(&mut self, time_budget: core::time::Duration) -> Result { use crate::std::time::Instant; let start = Instant::now(); if time_budget.is_zero() { @@ -1956,7 +2074,7 @@ impl<'store> Executor<'store, false> { impl<'store> Executor<'store, true> { #[inline(always)] - pub(crate) fn run_with_fuel(&mut self, fuel: u32) -> Result { + pub(crate) fn run_with_fuel(&mut self, fuel: u32) -> Result { self.store.execution_fuel = fuel; if self.store.execution_fuel == 0 { return Ok(ExecState::Suspended(self.cf)); diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index cece36a..6e6912c 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod values; #[cfg(not(feature = "std"))] mod no_std_floats; -use crate::{Result, Store, Trap, interpreter::stack::CallFrame}; +use crate::{Result, Store, interpreter::stack::CallFrame}; pub(crate) use simd::*; pub(crate) use values::*; @@ -26,11 +26,11 @@ pub(crate) enum ExecState { pub(crate) struct InterpreterRuntime; impl InterpreterRuntime { - pub(crate) fn exec(store: &mut Store, cf: CallFrame, call_stack_base: u32) -> Result<(), Trap> { + pub(crate) fn exec(store: &mut Store, cf: CallFrame, call_stack_base: u32) -> Result<()> { executor::Executor::::new(store, cf, call_stack_base).run_to_completion() } - pub(crate) fn exec_with_fuel(store: &mut Store, cf: CallFrame, fuel: u32) -> Result { + pub(crate) fn exec_with_fuel(store: &mut Store, cf: CallFrame, fuel: u32) -> Result { executor::Executor::::new(store, cf, 0).run_with_fuel(fuel) } @@ -39,7 +39,7 @@ impl InterpreterRuntime { store: &mut Store, cf: CallFrame, time_budget: core::time::Duration, - ) -> Result { + ) -> Result { executor::Executor::::new(store, cf, 0).run_with_time_budget(time_budget) } } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 7b3807e..783b10b 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -32,11 +32,11 @@ macro_rules! checked_conv_float { let (min, max) = float_min_max!($from, $intermediate); if v.is_nan() { core::hint::cold_path(); - return Err(crate::Trap::InvalidConversionToInt); + return Err(crate::Trap::InvalidConversionToInt.into()); } if v <= min || v >= max { core::hint::cold_path(); - return Err(crate::Trap::IntegerOverflow); + return Err(crate::Trap::IntegerOverflow.into()); } $self.store.value_stack.push::<$to>((v as $intermediate as $to).into())?; }}; diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 6e904af..55f9e3b 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -8,7 +8,9 @@ use alloc::vec::Vec; use crate::interpreter::ValueRef; use crate::store::TableInstance; use crate::{Error, MemoryInstance, Result, Store, Trap}; -use tinywasm_types::{Addr, GlobalType, MemAddr, MemoryType, TableAddr, TableType, WasmType, WasmValue}; +use tinywasm_types::{ + Addr, FuncType, GlobalType, MemAddr, MemoryType, TableAddr, TableType, TagAddr, WasmType, WasmValue, +}; #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "debug", derive(Debug))] @@ -66,6 +68,11 @@ pub struct Table(pub(crate) StoreItem); #[cfg_attr(feature = "debug", derive(Debug))] pub struct Global(pub(crate) StoreItem); +/// A tag instance in a store. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct Tag(pub(crate) StoreItem); + /// A cursor over a [`Memory`] instance. /// /// Available with the `std` feature enabled. @@ -471,3 +478,22 @@ impl Global { store.state.set_global_wasmvalue(self.0.addr, value) } } + +impl Tag { + /// Create a new exception tag in the given store. + pub fn new(store: &mut Store, ty: FuncType) -> Result { + if !ty.results().is_empty() { + return Err(Error::other("tag types must not have results")); + } + let type_addr = store.register_host_type(&ty); + let addr = store.state.tags.len() as TagAddr; + store.state.tags.push(crate::store::TagInstance { type_addr }); + Ok(Self(StoreItem::new(store.id(), addr))) + } + + /// Get the payload type of the tag. + pub fn ty<'a>(&self, store: &'a Store) -> Result<&'a FuncType> { + self.0.validate_store(store)?; + Ok(store.state.get_canonical_func_type(store.state.get_tag(self.0.addr).type_addr)) + } +} diff --git a/crates/tinywasm/src/store/exception.rs b/crates/tinywasm/src/store/exception.rs new file mode 100644 index 0000000..0725cda --- /dev/null +++ b/crates/tinywasm/src/store/exception.rs @@ -0,0 +1,10 @@ +use alloc::boxed::Box; +use tinywasm_types::TagAddr; + +use crate::interpreter::TinyWasmValue; + +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct ExceptionInstance { + pub(crate) tag_addr: TagAddr, + pub(crate) payload: Box<[TinyWasmValue]>, +} diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 0cd5e5b..84a52b5 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -11,12 +11,14 @@ use crate::{Engine, Error, ModuleInstance, Result, Trap}; mod const_expr; mod data; mod element; +mod exception; mod function; mod gc; mod global; mod memory; mod state; mod table; +mod tag; mod types; use const_expr::eval_const; @@ -25,7 +27,7 @@ pub use memory::{LazyLinearMemory, LinearMemory, MemoryBackend, PagedMemory, Vec pub(crate) use memory::{MemValue, MemoryInstance}; pub(crate) use state::State; pub(crate) use types::{canonicalize_ref_type, canonicalize_value_type}; -pub(crate) use {data::*, element::*, function::*, global::*, table::*}; +pub(crate) use {data::*, element::*, exception::*, function::*, global::*, table::*, tag::*}; // global store id counter static STORE_ID: AtomicU32 = AtomicU32::new(0); @@ -227,6 +229,18 @@ impl Store { start..start + funcs.len() as FuncAddr } + /// Add tags to the store, returning their addresses in the store. + pub(crate) fn init_tags( + &mut self, + tags: &[TagType], + type_addrs: &[TypeAddr], + ) -> impl ExactSizeIterator { + let start = self.state.tags.len() as TagAddr; + self.state.tags.reserve_exact(tags.len()); + self.state.tags.extend(tags.iter().map(|tag| TagInstance { type_addr: type_addrs[tag.type_idx as usize] })); + start..start + tags.len() as TagAddr + } + /// Add tables to the store, returning their addresses in the store pub(crate) fn init_tables( &mut self, diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index 3371d4c..e6a213f 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -14,6 +14,8 @@ pub(crate) struct State { pub(crate) tables: Vec, pub(crate) memories: Vec, pub(crate) globals: Globals, + pub(crate) tags: Vec, + pub(crate) exceptions: Vec, pub(crate) elements: Vec, pub(crate) data: Vec, pub(crate) gc: Box, @@ -28,6 +30,8 @@ impl State { tables: Vec::new(), memories: Vec::new(), globals: Globals::default(), + tags: Vec::new(), + exceptions: Vec::new(), elements: Vec::new(), data: Vec::new(), gc: Box::new(gc::GcHeap::new(gc_collection_threshold)), @@ -104,6 +108,11 @@ impl State { .filter(|element| Self::type_may_contain_gc_in(canonical_types, &WasmType::Ref(element.ty))) .flat_map(|element| element.items.iter().flatten().copied()), ) + .chain( + self.exceptions + .iter() + .flat_map(|exception| exception.payload.iter().filter_map(|value| value.as_ref())), + ) .chain(values.iter().filter_map(|value| value.as_ref())); self.gc.collect(roots).map_err(|_| Trap::OutOfMemory)?; } @@ -261,6 +270,9 @@ impl State { let actual = RefType::new_concrete(false, func.type_addr).expect("canonical type fits"); return self.ref_type_is_subtype(actual, expected); } + if expected.is_exn() { + return value.addr().is_some_and(|addr| self.exceptions.get(addr as usize).is_some()); + } if value.is_i31() { let actual = RefType::new_abstract(false, AbstractHeapType::I31); return self.ref_type_is_subtype(actual, expected); @@ -304,6 +316,9 @@ impl State { { false } + (WasmValue::Ref(RefValue::Exn(value)), WasmType::Ref(expected)) if expected.is_exn() => { + self.exceptions.get(value.addr() as usize).is_some() + } (WasmValue::Ref(RefValue::Any(value)), WasmType::Ref(expected)) => { self.value_ref_matches(ValueRef::from_raw(value.raw()), expected) } @@ -332,6 +347,10 @@ impl State { Self::get(&self.funcs, addr, "function") } + pub(crate) fn get_tag(&self, addr: TagAddr) -> &TagInstance { + Self::get(&self.tags, addr, "tag") + } + /// Get a wasm function at the actual index in the store, panicking if it's a host function (which should be guaranteed by the validator) pub(crate) fn get_wasm_func(&self, addr: FuncAddr) -> &WasmFunctionInstance { match self.funcs.get(addr as usize).map(|func| &func.kind) { diff --git a/crates/tinywasm/src/store/tag.rs b/crates/tinywasm/src/store/tag.rs new file mode 100644 index 0000000..49c16d8 --- /dev/null +++ b/crates/tinywasm/src/store/tag.rs @@ -0,0 +1,7 @@ +use tinywasm_types::TypeAddr; + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct TagInstance { + pub(crate) type_addr: TypeAddr, +} diff --git a/crates/tinywasm/tests/generated/wasm-3.csv b/crates/tinywasm/tests/generated/wasm-3.csv index 7d3961c..fdc0e86 100644 --- a/crates/tinywasm/tests/generated/wasm-3.csv +++ b/crates/tinywasm/tests/generated/wasm-3.csv @@ -1,3 +1,3 @@ 0.9.0,20776,452,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":37,"failed":9},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.10.0,20778,450,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":38,"failed":8},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.11.0-pre.0,21151,77,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":65,"failed":0},{"name":"elem.wast","passed":151,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":124,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":2,"failed":0},{"name":"type-equivalence.wast","passed":32,"failed":0},{"name":"type-rec.wast","passed":27,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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.11.0-pre.0,21228,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":65,"failed":0},{"name":"elem.wast","passed":151,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":124,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":218,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":23,"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":34,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":2,"failed":0},{"name":"type-equivalence.wast","passed":32,"failed":0},{"name":"type-rec.wast","passed":27,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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/tinywasm/tests/generated/wasm-latest.csv b/crates/tinywasm/tests/generated/wasm-latest.csv index 94a8b58..b25ec13 100644 --- a/crates/tinywasm/tests/generated/wasm-latest.csv +++ b/crates/tinywasm/tests/generated/wasm-latest.csv @@ -1,3 +1,3 @@ 0.9.0,20777,452,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":37,"failed":9},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.10.0,20697,536,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":63,"failed":2},{"name":"elem.wast","passed":149,"failed":2},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":116,"failed":8},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":14,"failed":35},{"name":"return_call_indirect.wast","passed":30,"failed":51},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":38,"failed":8},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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.11.0-pre.0,21156,77,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":65,"failed":0},{"name":"elem.wast","passed":151,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":124,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":49,"failed":0},{"name":"return_call_indirect.wast","passed":81,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":2,"failed":0},{"name":"type-equivalence.wast","passed":32,"failed":0},{"name":"type-rec.wast","passed":27,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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.11.0-pre.0,21233,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":12,"failed":0},{"name":"br_on_null.wast","passed":10,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":35,"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":65,"failed":0},{"name":"elem.wast","passed":151,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":124,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":218,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":23,"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":163,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":90,"failed":0},{"name":"memory_grow.wast","passed":106,"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.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":34,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":49,"failed":0},{"name":"return_call_indirect.wast","passed":81,"failed":0},{"name":"return_call_ref.wast","passed":51,"failed":0},{"name":"select.wast","passed":157,"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.wast","passed":46,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":2,"failed":0},{"name":"type-equivalence.wast","passed":32,"failed":0},{"name":"type-rec.wast","passed":27,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":13,"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 b920ac7..aff9e24 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,4 +1,6 @@ -use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValueCounts}; +use alloc::boxed::Box; + +use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TagAddr, TypeAddr, ValueCounts}; use crate::{ConstIdx, DataAddr, ElemAddr, MemAddr, RefType, RefValue}; /// Represents a memory immediate in a WebAssembly memory instruction. @@ -20,6 +22,46 @@ pub struct DropKeep { pub keep: ValueCounts, } +/// A catch clause attached to a lowered `try_table` instruction. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub enum ExceptionCatch { + /// Catch exceptions carrying the specified module-local tag. + Tag { tag: TagAddr, landing_pad: u32, base: ValueCounts, with_ref: bool }, + /// Catch any exception. + All { landing_pad: u32, base: ValueCounts, with_ref: bool }, +} + +impl ExceptionCatch { + /// Returns the catch landing pad instruction pointer. + pub const fn landing_pad(self) -> u32 { + match self { + Self::Tag { landing_pad, .. } | Self::All { landing_pad, .. } => landing_pad, + } + } + + /// Returns whether this clause exposes the caught exception reference. + pub const fn with_ref(self) -> bool { + match self { + Self::Tag { with_ref, .. } | Self::All { with_ref, .. } => with_ref, + } + } +} + +/// A statically lowered exception handler range. +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct ExceptionHandler { + /// First protected instruction, inclusive. + pub start_ip: u32, + /// End of the protected instruction range, exclusive. + pub end_ip: u32, + /// Catch clauses in source order. + pub catches: Box<[ExceptionCatch]>, +} + impl From<(ValueCounts, ValueCounts)> for DropKeep { fn from((base, keep): (ValueCounts, ValueCounts)) -> Self { Self { base, keep } @@ -229,6 +271,8 @@ pub enum Instruction { ReturnCallSelf, ReturnCallIndirect(TypeAddr, TableAddr), ReturnCallRef(TypeAddr), + Throw(TagAddr), + ThrowRef, // > Parametric Instructions // See diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 0fd4e81..78a8d0d 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -111,6 +111,11 @@ pub struct ModuleInner { /// Corresponds to the `memory` section of the original WebAssembly module. pub memory_types: Box<[MemoryType]>, + /// Tag components of the WebAssembly module. + /// + /// Corresponds to the `tag` section of the original WebAssembly module. + pub tags: Box<[TagType]>, + /// Imports of the WebAssembly module. /// /// Corresponds to the `import` section of the original WebAssembly module. @@ -141,6 +146,7 @@ impl Module { ImportKind::Table(table_ty) => Some(ImportType::Table(table_ty)), ImportKind::Memory(memory_ty) => Some(ImportType::Memory(memory_ty)), ImportKind::Global(global_ty) => Some(ImportType::Global(global_ty)), + ImportKind::Tag(tag_ty) => Some(ImportType::Tag(self.0.types.get(tag_ty.type_idx)?.as_func()?)), }?; Some(ModuleImport { module: import.module.as_ref(), name: import.name.as_ref(), ty }) @@ -162,6 +168,7 @@ impl Module { | (ExternalKind::Table, ImportKind::Table(_)) | (ExternalKind::Memory, ImportKind::Memory(_)) | (ExternalKind::Global, ImportKind::Global(_)) + | (ExternalKind::Tag, ImportKind::Tag(_)) ) }) .count() @@ -175,6 +182,7 @@ impl Module { | (ExternalKind::Table, ImportKind::Table(_)) | (ExternalKind::Memory, ImportKind::Memory(_)) | (ExternalKind::Global, ImportKind::Global(_)) + | (ExternalKind::Tag, ImportKind::Tag(_)) ) }); let import = imports.nth(index)?; @@ -184,6 +192,7 @@ impl Module { ImportKind::Table(table_ty) => Some(ExportType::Table(table_ty)), ImportKind::Memory(memory_ty) => Some(ExportType::Memory(memory_ty)), ImportKind::Global(global_ty) => Some(ExportType::Global(global_ty)), + ImportKind::Tag(tag_ty) => Some(ExportType::Tag(module.types.get(tag_ty.type_idx)?.as_func()?)), } } @@ -223,6 +232,15 @@ impl Module { ExportType::Global(&self.0.globals.get(idx - imported_globals)?.ty) } } + ExternalKind::Tag => { + let imported_tags = imported_count(&self.0, ExternalKind::Tag); + if idx < imported_tags { + imported_type(&self.0, ExternalKind::Tag, idx)? + } else { + let tag_ty = self.0.tags.get(idx - imported_tags)?; + ExportType::Tag(self.0.types.get(tag_ty.type_idx)?.as_func()?) + } + } }; Some(ModuleExport { name: export.name.as_ref(), ty }) @@ -258,6 +276,8 @@ pub enum ImportType<'a> { Memory(&'a MemoryType), /// Imported global type. Global(&'a GlobalType), + /// Imported tag type. + Tag(&'a FuncType), } /// Exported entity type. @@ -270,6 +290,8 @@ pub enum ExportType<'a> { Memory(&'a MemoryType), /// Exported global type. Global(&'a GlobalType), + /// Exported tag type. + Tag(&'a FuncType), } /// How instantiation should prepare local memories declared by the module. @@ -301,6 +323,8 @@ pub enum ExternalKind { Memory, /// A WebAssembly Global. Global, + /// A WebAssembly Tag. + Tag, } /// A WebAssembly Address. @@ -315,6 +339,8 @@ pub type FuncAddr = Addr; pub type TableAddr = Addr; pub type MemAddr = Addr; pub type GlobalAddr = Addr; +pub type TagAddr = Addr; +pub type ExnAddr = Addr; pub type ElemAddr = Addr; pub type DataAddr = Addr; pub type ExternAddr = Addr; @@ -338,6 +364,7 @@ pub enum ExternVal { Table(TableAddr), Memory(MemAddr), Global(GlobalAddr), + Tag(TagAddr), } impl ExternVal { @@ -348,6 +375,7 @@ impl ExternVal { Self::Table(_) => ExternalKind::Table, Self::Memory(_) => ExternalKind::Memory, Self::Global(_) => ExternalKind::Global, + Self::Tag(_) => ExternalKind::Tag, } } @@ -358,6 +386,7 @@ impl ExternVal { ExternalKind::Table => Self::Table(addr), ExternalKind::Memory => Self::Memory(addr), ExternalKind::Global => Self::Global(addr), + ExternalKind::Tag => Self::Tag(addr), } } } @@ -444,6 +473,7 @@ pub struct WasmFunction { pub struct WasmFunctionData { pub v128_constants: Box<[[u8; 16]]>, pub branch_table_targets: Box<[u32]>, + pub exception_handlers: Box<[ExceptionHandler]>, } impl WasmFunctionData { @@ -638,6 +668,22 @@ pub enum MemoryArch { I64, } +/// A WebAssembly tag type. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct TagType { + /// The module-local function type index used by this tag. + pub type_idx: TypeAddr, +} + +impl TagType { + /// Creates a tag type from a module-local function type index. + pub const fn new(type_idx: TypeAddr) -> Self { + Self { type_idx } + } +} + #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] @@ -655,6 +701,7 @@ pub enum ImportKind { Table(TableType), Memory(MemoryType), Global(GlobalType), + Tag(TagType), } impl From<&ImportKind> for ExternalKind { @@ -664,6 +711,7 @@ impl From<&ImportKind> for ExternalKind { ImportKind::Table(_) => Self::Table, ImportKind::Memory(_) => Self::Memory, ImportKind::Global(_) => Self::Global, + ImportKind::Tag(_) => Self::Tag, } } } From 6ecee15efc820a23bb8a47b5ff4111037ec76bc7 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 Aug 2026 14:30:45 +0200 Subject: [PATCH 08/15] docs: update architecture, docs Signed-off-by: Henry --- ARCHITECTURE.md | 76 ++++++++++++++++++++++---------------- README.md | 12 +++--- crates/tinywasm/src/lib.rs | 14 +++---- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 422dbb5..9405d38 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,62 +1,74 @@ # TinyWasm Architecture -TinyWasm follows the general runtime model described in the [WebAssembly specification](https://webassembly.github.io/spec/core/exec/runtime.html), but lowers validated WebAssembly into a compact internal instruction format before execution. +TinyWasm follows the general runtime model described in the [WebAssembly specification](https://webassembly.github.io/spec/core/exec/runtime.html). It is a stack-based interpreter with a compact internal bytecode, width-specific value stacks, and configurable linear-memory backends. -## Runtime Layout +## Execution Pipeline -- Values are stored in untyped stacks: - - `stack_32` for `i32`, `f32`, `funcref`, and `externref` - - `stack_64` for `i64` and `f64` - - `stack_128` for `v128` -- Locals are stored directly in the value stacks. Each `CallFrame` stores a `locals_base`, and local instructions index from that base. -- Structured control flow (`block`, `loop`, `if`, `br*`) is lowered during parsing to jump-oriented internal instructions such as `Jump`, `JumpIfZero`, `BranchTable*`, `DropKeep*`, and `Return`. -- Execution is a single iterative interpreter loop over the lowered instruction stream. +TinyWasm does not execute WebAssembly instructions directly. Parsing lowers them into an internal bytecode designed to make execution simpler and cheaper: -## Internal Bytecode +- structured control flow (`block`, `loop`, `if`, and `br*`) becomes jump-oriented instructions such as `Jump`, `JumpIfZero`, `BranchTable*`, `DropKeep*`, and `Return` +- operand widths are encoded in instruction variants, and branch stack reshaping is explicit +- instructions retain compact module-local indexes, which each instance maps to Store-wide runtime addresses +- when enabled, the optimizer applies local rewrites, including superinstruction fusion, specialized calls and returns, and redundant-instruction removal +- modules can be serialized as `.twasm` archives containing this lowered representation +- execution uses a single iterative dispatch loop over the resulting instruction stream -TinyWasm does not interpret WebAssembly instructions directly. During parsing and validation, WebAssembly is translated into TinyWasm's internal bytecode format. +## Value Stacks -This internal representation is designed to make execution simpler and cheaper: +WebAssembly combines an operand stack with function-scoped locals. TinyWasm stores both in the same width-specific physical stacks: -- structured control flow is resolved ahead of time -- stack effects are made explicit -- common instruction sequences can be fused into superinstructions -- modules can optionally be serialized as `.twasm` for reuse +- `stack_32` for `i32`, `f32`, and reference values, including GC and exception references +- `stack_64` for `i64` and `f64` +- `stack_128` for `v128` -## Optimizer +The interpreter does not maintain a runtime type stack or tag individual stack slots. Lowered instructions encode the physical lane they operate on, while WebAssembly validation guarantees type correctness. Splitting values by width lets each value use its natural storage size, reducing stack memory and the data moved by common operations. -During parsing, a peephole optimizer (`optimize.rs`) fuses common instruction sequences into superinstructions. These reduce interpreter dispatch overhead by combining multiple logical operations into one internal instruction. +Locals are stored directly in these stacks. Each `CallFrame` records a base for every lane, and lowered local instructions index from those bases. The value stacks and call stack can use either a fixed capacity or dynamic initial and maximum sizes. Dynamic stacks keep the initial allocation small, grow when needed, and retain a hard limit. -Examples include: +## Interpreter Optimization -- **Fused binops**: `BinOpLocalLocal*`, `BinOpLocalConst*`, `BinOpStackGlobal*` - Combine local/global access, a binary operation, and sometimes a store/tee. -- **Fused jumps**: `JumpCmpLocalConst*`, `JumpCmpLocalLocal*`, `JumpCmpStackConst*` - Combine comparison and conditional branch logic. +Instruction dispatch is one of the interpreter's main costs. TinyWasm reduces it through superinstructions and by shaping the large Rust dispatch match based on benchmarks and assembly inspection. Most simple arithmetic remains directly in the interpreter loop. Small, frequently used stack, value, and global operations use `#[inline]` or `#[inline(always)]` where measurements show a benefit, while unlikely error paths use `core::hint::cold_path()`. + +Superinstructions also reduce value-stack traffic. They can read locals, globals, and constants directly, perform an operation, and write `set` or `tee` destinations without materializing intermediate operand-stack values. Examples include: + +- fused binary operations such as `BinOpLocalLocal*`, `BinOpLocalConst*`, and `BinOpStackGlobal*` +- fused conditional branches such as `JumpCmpLocalConst*`, `JumpCmpLocalLocal*`, and `JumpCmpStackConst*` + +The default runtime remains safe Rust throughout rather than relying on unchecked operations. + +## SIMD + +SIMD instructions have a portable safe-Rust implementation built from fixed-size arrays and lane operations, relying on the compiler to auto-vectorize where possible. Generated code is inspected with `cargo asm`, and benchmarks determine where architecture-specific alternatives are worthwhile. WebAssembly targets use native SIMD intrinsics where available, while the optional `simd-x86` feature provides selected x86 implementations for operations where the generic code produces worse results. ## Memory Backends Linear memory is implemented through the `LinearMemory` trait. The backend is selected with `engine::Config::with_memory_backend()`. +`LinearMemory` exposes separate fixed-width read and write methods for 8-, 16-, 32-, 64-, and 128-bit accesses. A const-generic method would not be callable through a `dyn LinearMemory` trait object, so each width is an explicit vtable entry that backends can optimize independently. + +This flexibility has a measurable cost: guest loads and stores cross the `dyn LinearMemory` boundary, adding an indirect call and generally preventing the backend operation from being inlined into the interpreter. The fixed-width methods keep the work behind that boundary as small and specialized as possible. + Available backends: -- `VecMemory` - contiguous `Vec` backing; the default backend. -- `PagedMemory` - chunk-based allocation, useful when growing memory without reallocating one large buffer. -- `LazyLinearMemory` - wraps another backend and allocates memory on first access. +- `VecMemory` - contiguous `Vec` backing and the default backend. +- `PagedMemory` - sparse chunk-based allocation, with untouched chunks left unallocated and growth avoiding relocation of one contiguous buffer. +- `LazyLinearMemory` - serves zero-filled reads without allocation and creates the configured backend on the first mutation or growth. - Custom backends through `MemoryBackend::custom()`. +`VecMemory` growth may reallocate, though operating-system allocators can often grow page-backed allocations without copying the full buffer. Applications on conventional operating systems should generally keep it unless sparse allocation or non-relocating growth is specifically needed. Bounded dynamic stacks and sparse paged memory trade some runtime overhead for a smaller initial footprint on embedded and other resource-constrained systems. + ## Future Experiments -TinyWasm's interpreter is intentionally simple today: validated WebAssembly is lowered to internal instructions, optimized with peephole fusion, and executed by an iterative dispatch loop. +Future work may explore additional dispatch and code-generation strategies, including Rust's experimental `loop_match` state-machine work, a tail-call-based interpreter once Rust's explicit tail-call support matures, more aggressive superinstruction fusion, top-of-stack register allocation, or optional JIT compilation. -Future work may explore additional dispatch and code-generation strategies, including Rust's experimental `loop_match` state-machine work, explicit tail calls, more aggressive superinstruction fusion, top-of-stack register allocation, or even optional JIT compilation. +For conventional operating systems, a future `mmap`-based memory backend could reserve virtual address space and use guard pages to move more bounds enforcement to the operating system, reducing explicit checks in linear-memory hot paths. This is the same broad approach described in [Wasmtime's linear-memory architecture](https://docs.wasmtime.dev/contributing-architecture.html#linear-memory), where virtual-memory reservations and guard regions eliminate or deduplicate explicit bounds checks. -## Code Map +## Important Modules -- [visit.rs](./crates/parser/src/visit.rs) - WebAssembly binary visitor +- [visit.rs](./crates/parser/src/visit.rs) - function-body operator lowering - [optimize.rs](./crates/parser/src/optimize.rs) - peephole optimizer and superinstruction fusion -- [parallel.rs](./crates/parser/src/parallel.rs) - multithreaded function parsing +- [parallel.rs](./crates/parser/src/parallel.rs) - parallel function parsing - [instructions.rs](./crates/types/src/instructions.rs) - internal instruction set -- [value_stack.rs](./crates/tinywasm/src/interpreter/stack/value_stack.rs) - typed value stacks +- [value_stack.rs](./crates/tinywasm/src/interpreter/stack/value_stack.rs) - width-specific stacks - [call_stack.rs](./crates/tinywasm/src/interpreter/stack/call_stack.rs) - call frame stack - [memory/mod.rs](./crates/tinywasm/src/store/memory/mod.rs) - memory backend trait and implementations diff --git a/README.md b/README.md index 305f61e..3d3bc4c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Why `tinywasm`? -- **Tiny**: Small by design, without significantly compromising performance or functionality. +- **Tiny**: Small by design, while still passing the full WebAssembly 3.0 core testsuite. - **Portable**: Runs anywhere Rust can target, supports `no_std`, has minimal dependencies, and can itself compile to WebAssembly. - **Safe**: Written in safe Rust, with optional `unsafe` limited to the `simd-x86` feature. Its sandbox is designed to prevent untrusted Wasm from accessing host memory or escaping the runtime. @@ -42,6 +42,10 @@ assert_eq!(result, 3); See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information. +## Precompiled Modules + +TinyWasm modules can be compiled to the internal `twasm` bytecode format, which stores the optimized instruction representation for faster loading and reuse. + ## Cargo Features - **`std`:** Enables `std` and parsing from files and streams. Enabled by default. @@ -61,12 +65,6 @@ Use `Engine` and `engine::Config` when you need non-default runtime settings suc [^libm]: [rust-lang/rust#137578](https://github.com/rust-lang/rust/issues/137578) — tracking issue for floating-point math support in `no_std`. -## Current Status - -`tinywasm` passes the WebAssembly MVP and WebAssembly 2.0 core testsuites and supports the [Lime1](https://github.com/WebAssembly/tool-conventions/blob/main/Lime.md#lime1) interoperability target. WebAssembly 3.0 support is still in progress, and some newer proposal suites are tracked in-repo as experimental coverage rather than release guarantees; see [Supported Proposals](#supported-proposals) for details. - -TinyWasm also has its own internal bytecode format, `twasm`. WebAssembly modules can be compiled to `twasm`, which stores TinyWasm's optimized instruction representation for faster loading and reuse. - ## Safety TinyWasm only uses safe Rust by default. The optional `simd-x86` feature enables x86-specific SIMD intrinsics and uses `unsafe` internally. WebAssembly input is validated by default through the `validate` feature. Disabling validation should not let Wasm access host memory or escape the sandbox, but malformed input may panic or otherwise crash the process, so only disable it for trusted input. diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 2e2dce0..e6c9ab8 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -7,12 +7,12 @@ #![cfg_attr(not(feature = "simd-x86"), forbid(unsafe_code))] #![cfg_attr(feature = "simd-x86", deny(unsafe_code))] -//! `tinywasm` provides a small, portable WebAssembly interpreter with support for -//! the WebAssembly MVP, WebAssembly 2.0, and a growing set of newer proposals. -//! It also supports the [Lime1](https://github.com/WebAssembly/tool-conventions/blob/main/Lime.md#lime1) -//! interoperability target. -//! It is designed to stay lightweight while still being practical to embed in -//! applications, tools, and `no_std + alloc` environments. +//! A small and portable WebAssembly interpreter. +//! +//! `tinywasm` passes the full WebAssembly 3.0 core testsuite and supports the +//! [Lime1](https://github.com/WebAssembly/tool-conventions/blob/main/Lime.md#lime1) +//! interoperability target. It is designed for embedding in applications, tools, +//! and `no_std + alloc` environments. #![cfg_attr(docsrs, feature(doc_cfg))] //! @@ -79,7 +79,7 @@ //! //! To provide imports to a module, you can use the [`Imports`] struct. //! This struct allows you to register custom functions, globals, memories, tables, -//! and other modules to be linked into the module when it is instantiated. +//! tags, and other modules to be linked into the module when it is instantiated. //! //! See the [`Imports`] documentation for more information. From 666c7f59301acc9fcef10ac2da5beb464e57d665 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 Aug 2026 15:22:56 +0200 Subject: [PATCH 09/15] chore: simplify runtime and clean up tests Signed-off-by: Henry --- crates/parser/src/conversion.rs | 48 +++-- crates/parser/src/macros.rs | 2 +- crates/parser/src/module.rs | 17 +- crates/parser/src/visit.rs | 73 ++++--- crates/tinywasm/Cargo.toml | 28 --- crates/tinywasm/src/func/host.rs | 17 +- crates/tinywasm/src/imports.rs | 40 ++-- crates/tinywasm/src/interpreter/executor.rs | 5 +- crates/tinywasm/src/interpreter/simd/mod.rs | 2 - crates/tinywasm/src/interpreter/simd/tests.rs | 60 ------ .../src/interpreter/stack/value_stack.rs | 4 +- crates/tinywasm/src/interpreter/values.rs | 48 +---- crates/tinywasm/src/reference.rs | 2 +- crates/tinywasm/src/store/const_expr.rs | 120 +++++++----- crates/tinywasm/src/store/gc/arena.rs | 15 ++ crates/tinywasm/src/store/gc/object.rs | 47 ++++- crates/tinywasm/src/store/global.rs | 28 +-- crates/tinywasm/src/store/memory/mod.rs | 182 ------------------ crates/tinywasm/src/store/memory/paged.rs | 45 ----- crates/tinywasm/src/store/mod.rs | 14 +- crates/tinywasm/src/store/state.rs | 25 ++- crates/tinywasm/src/store/table.rs | 55 +----- .../tests/generated/wasm-annotations.csv | 5 - .../tests/generated/wasm-extended-const.csv | 5 - .../generated/wasm-function-references.csv | 3 - ...m-nontrapping-float-to-int-conversions.csv | 4 - .../tests/generated/wasm-reference-types.csv | 4 - .../generated/wasm-sign-extension-ops.csv | 4 - .../tests/generated/wasm-tail-call.csv | 4 - .../tinywasm/tests/test-wasm-annotations.rs | 13 -- .../tests/test-wasm-extended-const.rs | 13 -- .../tests/test-wasm-function-references.rs | 13 -- ...sm-nontrapping-float-to-int-conversions.rs | 14 -- .../tests/test-wasm-reference-types.rs | 13 -- .../tests/test-wasm-sign-extension-op.rs | 13 -- crates/tinywasm/tests/test-wasm-tail-call.rs | 14 -- .../tests/wasm-custom/table-basics.wast | 32 +++ crates/types/src/instructions.rs | 5 +- 38 files changed, 329 insertions(+), 707 deletions(-) delete mode 100644 crates/tinywasm/src/interpreter/simd/tests.rs delete mode 100644 crates/tinywasm/tests/generated/wasm-annotations.csv delete mode 100644 crates/tinywasm/tests/generated/wasm-extended-const.csv delete mode 100644 crates/tinywasm/tests/generated/wasm-function-references.csv delete mode 100644 crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv delete mode 100644 crates/tinywasm/tests/generated/wasm-reference-types.csv delete mode 100644 crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv delete mode 100644 crates/tinywasm/tests/generated/wasm-tail-call.csv delete mode 100644 crates/tinywasm/tests/test-wasm-annotations.rs delete mode 100644 crates/tinywasm/tests/test-wasm-extended-const.rs delete mode 100644 crates/tinywasm/tests/test-wasm-function-references.rs delete mode 100644 crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs delete mode 100644 crates/tinywasm/tests/test-wasm-reference-types.rs delete mode 100644 crates/tinywasm/tests/test-wasm-sign-extension-op.rs delete mode 100644 crates/tinywasm/tests/test-wasm-tail-call.rs create mode 100644 crates/tinywasm/tests/wasm-custom/table-basics.wast diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 7b52363..694c639 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -14,11 +14,14 @@ pub(crate) fn value_lane(ty: wasmparser::ValType) -> ValueLane { } } -pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result { +pub(crate) fn convert_module_element( + element: wasmparser::Element<'_>, + global_types: &[WasmType], +) -> Result { let kind = match element.kind { wasmparser::ElementKind::Active { table_index, offset_expr } => tinywasm_types::ElementKind::Active { table: table_index.unwrap_or(0), - offset: process_const_operators(offset_expr.get_operators_reader())?, + offset: process_const_operators(offset_expr.get_operators_reader(), global_types)?, }, wasmparser::ElementKind::Passive => tinywasm_types::ElementKind::Passive, wasmparser::ElementKind::Declared => tinywasm_types::ElementKind::Declared, @@ -38,7 +41,7 @@ pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result wasmparser::ElementItems::Expressions(ty, exprs) => { let items = exprs .into_iter() - .map(|expr| Ok(ElementItem::Expr(process_const_operators(expr?.get_operators_reader())?))) + .map(|expr| Ok(ElementItem::Expr(process_const_operators(expr?.get_operators_reader(), global_types)?))) .collect::>>()? .into_boxed_slice(); @@ -47,13 +50,16 @@ pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result } } -pub(crate) fn convert_module_data(data: wasmparser::Data<'_>) -> Result { +pub(crate) fn convert_module_data( + data: wasmparser::Data<'_>, + global_types: &[WasmType], +) -> Result { Ok(tinywasm_types::Data { data: data.data.to_vec().into_boxed_slice(), range: data.range, kind: match data.kind { wasmparser::DataKind::Active { memory_index, offset_expr } => { - let offset = process_const_operators(offset_expr.get_operators_reader())?; + let offset = process_const_operators(offset_expr.get_operators_reader(), global_types)?; tinywasm_types::DataKind::Active { mem: memory_index, offset } } wasmparser::DataKind::Passive => tinywasm_types::DataKind::Passive, @@ -96,16 +102,17 @@ pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> MemoryTyp pub(crate) fn convert_module_globals( globals: wasmparser::SectionLimited<'_, wasmparser::Global<'_>>, + global_types: &mut Vec, ) -> Result> { - globals - .into_iter() - .map(|global| { - let global = global?; - let ty = convert_valtype(&global.ty.content_type)?; - let ops = global.init_expr.get_operators_reader(); - Ok(Global { init: process_const_operators(ops)?, ty: GlobalType::new(ty, global.ty.mutable) }) - }) - .collect::>>() + let mut out = Vec::with_capacity(globals.count() as usize); + for global in globals { + let global = global?; + let ty = convert_valtype(&global.ty.content_type)?; + let init = process_const_operators(global.init_expr.get_operators_reader(), global_types)?; + global_types.push(ty); + out.push(Global { init, ty: GlobalType::new(ty, global.ty.mutable) }); + } + Ok(out.into_boxed_slice()) } pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result { @@ -313,7 +320,10 @@ fn convert_valtype_with_group(valtype: &wasmparser::ValType, group: Option<(u32, } } -pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result> { +pub(crate) fn process_const_operators( + ops: OperatorsReader<'_>, + global_types: &[WasmType], +) -> Result> { let mut out = Vec::new(); let mut operator_count = 0; let mut end_reached = false; @@ -353,7 +363,13 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result ConstInstruction::F32Const(f32::from_bits(value.bits())), wasmparser::Operator::F64Const { value } => ConstInstruction::F64Const(f64::from_bits(value.bits())), wasmparser::Operator::V128Const { value } => ConstInstruction::V128Const(*value.bytes()), - wasmparser::Operator::GlobalGet { global_index } => ConstInstruction::GlobalGet(global_index), + wasmparser::Operator::GlobalGet { global_index } => match global_types.get(global_index as usize) { + Some(WasmType::I32 | WasmType::F32) => ConstInstruction::GlobalGet32(global_index), + Some(WasmType::I64 | WasmType::F64) => ConstInstruction::GlobalGet64(global_index), + Some(WasmType::V128) => ConstInstruction::GlobalGet128(global_index), + Some(WasmType::Ref(_)) => ConstInstruction::GlobalGetRef(global_index), + None => return Err(crate::ParseError::Other(format!("global index out of bounds: {global_index}"))), + }, wasmparser::Operator::I32Add => ConstInstruction::I32Add, wasmparser::Operator::I32Sub => ConstInstruction::I32Sub, wasmparser::Operator::I32Mul => ConstInstruction::I32Mul, diff --git a/crates/parser/src/macros.rs b/crates/parser/src/macros.rs index 98312ab..9fa7a01 100644 --- a/crates/parser/src/macros.rs +++ b/crates/parser/src/macros.rs @@ -89,7 +89,7 @@ pub(crate) mod visit { $visit:ident($type_index:ident: $type_ty:ty $(, $arg:ident: $arg_ty:ty)*) => $instr:ident ) => { fn $visit(&mut self, $type_index: $type_ty $(, $arg: $arg_ty)*) -> Self::Output { - let size = ModuleMetadata::storage_size(self.metadata.array_field($type_index)?.storage); + let size = self.metadata.array_field($type_index)?; lowering_ops!(@emit self address(size) [$($input),*] => [$($output),*] Instruction::$instr($type_index $(, $arg)*).into()) } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index bbe2cf8..3d38ad6 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -64,6 +64,7 @@ pub(crate) struct ModuleReader<'a> { pub(crate) end_reached: bool, imported_func_count: usize, imported_memory_count: u32, + global_types: Vec, #[cfg(parallel_parser)] pending_functions: Option>>, @@ -148,7 +149,7 @@ impl<'a> ModuleReader<'a> { if let Some(validator) = validator.as_mut() { validator.global_section(&reader)?; } - self.globals = convert_module_globals(reader)?; + self.globals = convert_module_globals(reader, &mut self.global_types)?; } Payload::TableSection(reader) => { check_section("table", !self.tables.is_empty())?; @@ -168,7 +169,7 @@ impl<'a> ModuleReader<'a> { let init = match table.init { wasmparser::TableInit::RefNull => None, wasmparser::TableInit::Expr(expr) => { - Some(process_const_operators(expr.get_operators_reader())?) + Some(process_const_operators(expr.get_operators_reader(), &self.global_types)?) } }; tables.push(TableDefinition { ty, init }); @@ -209,8 +210,10 @@ impl<'a> ModuleReader<'a> { if let Some(validator) = validator.as_mut() { validator.element_section(&reader)?; } - self.elements = - reader.into_iter().map(|element| convert_module_element(element?)).collect::>()?; + self.elements = reader + .into_iter() + .map(|element| convert_module_element(element?, &self.global_types)) + .collect::>()?; } Payload::DataSection(reader) => { check_section("data", !self.data.is_empty())?; @@ -218,7 +221,10 @@ impl<'a> ModuleReader<'a> { if let Some(validator) = validator.as_mut() { validator.data_section(&reader)?; } - self.data = reader.into_iter().map(|data| convert_module_data(data?)).collect::>()?; + self.data = reader + .into_iter() + .map(|data| convert_module_data(data?, &self.global_types)) + .collect::>()?; } Payload::DataCountSection { count, range } => { debug!("Found data count section"); @@ -270,6 +276,7 @@ impl<'a> ModuleReader<'a> { self.imported_func_count += 1; } ImportKind::Memory(_) => self.imported_memory_count += 1, + ImportKind::Global(ty) => self.global_types.push(ty.ty), ImportKind::Tag(tag) => { let ty = self.types.get(tag.type_idx).and_then(SubType::as_func).ok_or_else(|| { ParseError::Other(format!( diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index c462d05..063bef4 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -3,11 +3,10 @@ use crate::{ conversion::{convert_heap_type, value_lane}, macros::visit::*, }; -use alloc::string::ToString; -use alloc::vec::Vec; +use alloc::{boxed::Box, string::ToString, vec::Vec}; use tinywasm_types::{ - Global, Import, ImportKind, Instruction, MemoryArg, MemoryType, StorageType, SubType, TableDefinition, TagType, - TypeSection, ValueCounts, ValueLane, WasmFunctionData, + Global, Import, ImportKind, Instruction, MemoryArg, MemoryType, StorageType, TableDefinition, TagType, TypeSection, + ValueCounts, ValueLane, WasmFunctionData, }; use wasmparser::{FunctionBody, OperatorsReader, OperatorsReaderAllocations, VisitSimdOperator}; @@ -50,7 +49,13 @@ pub(crate) struct ModuleMetadata { memories: Vec, tables: Vec, tags: Vec, - types: Vec, + aggregate_fields: Vec, +} + +enum AggregateFields { + Other, + Struct(Box<[ValueLane]>), + Array(ValueLane), } #[derive(Default)] @@ -109,7 +114,7 @@ impl<'a> FunctionBuilder<'a> { field_index: u32, instruction: fn(u32, u32) -> Instruction, ) -> Result<()> { - let size = ModuleMetadata::storage_size(self.metadata.struct_field(type_index, field_index)?.storage); + let size = self.metadata.struct_field(type_index, field_index)?; self.emit(&[ValueLane::S32], &[size], instruction(type_index, field_index)) } } @@ -170,6 +175,19 @@ impl ModuleMetadata { }) }) .collect(); + let aggregate_fields = types + .types + .iter() + .map(|ty| { + if let Some(ty) = ty.as_struct() { + AggregateFields::Struct(ty.fields.iter().map(|field| Self::storage_size(field.storage)).collect()) + } else if let Some(ty) = ty.as_array() { + AggregateFields::Array(Self::storage_size(ty.field.storage)) + } else { + AggregateFields::Other + } + }) + .collect(); Self { signatures, functions, @@ -177,7 +195,7 @@ impl ModuleMetadata { memories: memory_sizes, tables: table_sizes, tags: tag_types, - types: types.types.to_vec(), + aggregate_fields, } } @@ -230,26 +248,30 @@ impl ModuleMetadata { } } - fn struct_fields(&self, idx: u32) -> Result<&[tinywasm_types::FieldType]> { - self.types + fn struct_fields(&self, idx: u32) -> Result<&[ValueLane]> { + self.aggregate_fields .get(idx as usize) - .and_then(SubType::as_struct) - .map(|ty| ty.fields.as_ref()) + .and_then(|fields| match fields { + AggregateFields::Struct(fields) => Some(fields.as_ref()), + AggregateFields::Other | AggregateFields::Array(_) => None, + }) .ok_or_else(|| crate::ParseError::Other(alloc::format!("type index is not a struct type: {idx}"))) } - fn struct_field(&self, type_index: u32, field_index: u32) -> Result { + fn struct_field(&self, type_index: u32, field_index: u32) -> Result { self.struct_fields(type_index)? .get(field_index as usize) .copied() .ok_or_else(|| crate::ParseError::Other("struct field index out of bounds".into())) } - fn array_field(&self, idx: u32) -> Result { - self.types + fn array_field(&self, idx: u32) -> Result { + self.aggregate_fields .get(idx as usize) - .and_then(SubType::as_array) - .map(|ty| ty.field) + .and_then(|fields| match fields { + AggregateFields::Array(field) => Some(*field), + AggregateFields::Other | AggregateFields::Struct(_) => None, + }) .ok_or_else(|| crate::ParseError::Other(alloc::format!("type index is not an array type: {idx}"))) } } @@ -490,13 +512,14 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_struct_new(&mut self, type_index: u32) -> Self::Output { - let inputs = self - .metadata - .struct_fields(type_index)? - .iter() - .map(|field| ModuleMetadata::storage_size(field.storage)) - .collect::>(); - self.emit(&inputs, &[ValueLane::S32], Instruction::StructNew(type_index)) + let field_count = self.metadata.struct_fields(type_index)?.len(); + for field_index in (0..field_count).rev() { + let size = self.metadata.struct_field(type_index, field_index as u32)?; + self.pop_expect(size)?; + } + self.push_sizes(&[ValueLane::S32])?; + self.instructions.push(Instruction::StructNew(type_index)); + Ok(()) } fn visit_struct_get(&mut self, type_index: u32, field_index: u32) -> Self::Output { @@ -512,12 +535,12 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { } fn visit_struct_set(&mut self, type_index: u32, field_index: u32) -> Self::Output { - let size = ModuleMetadata::storage_size(self.metadata.struct_field(type_index, field_index)?.storage); + let size = self.metadata.struct_field(type_index, field_index)?; self.emit(&[ValueLane::S32, size], &[], Instruction::StructSet(type_index, field_index)) } fn visit_array_new_fixed(&mut self, type_index: u32, array_size: u32) -> Self::Output { - let size = ModuleMetadata::storage_size(self.metadata.array_field(type_index)?.storage); + let size = self.metadata.array_field(type_index)?; for _ in 0..array_size { self.pop_expect(size)?; } diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 67d0742..65ef7f8 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -52,10 +52,6 @@ harness = false name = "test-wasm-threads" test = false -[[test]] -harness = false -name = "test-wasm-annotations" - [[test]] harness = false name = "test-wast" @@ -65,10 +61,6 @@ test = false harness = false name = "test-wasm-custom-page-sizes" -[[test]] -harness = false -name = "test-wasm-function-references" - [[test]] harness = false name = "test-wasm-custom" @@ -77,18 +69,10 @@ name = "test-wasm-custom" harness = false name = "test-wasm-gc" -[[test]] -harness = false -name = "test-wasm-tail-call" - [[test]] harness = false name = "test-wasm-memory64" -[[test]] -harness = false -name = "test-wasm-extended-const" - [[test]] harness = false name = "test-wasm-relaxed-simd" @@ -101,18 +85,6 @@ name = "test-wasm-simd" harness = false name = "test-wasm-wide-arithmetic" -[[test]] -harness = false -name = "test-wasm-sign-extension-op" - -[[test]] -harness = false -name = "test-wasm-nontrapping-float-to-int-conversions" - -[[test]] -harness = false -name = "test-wasm-reference-types" - [[bench]] harness = false name = "argon2id" diff --git a/crates/tinywasm/src/func/host.rs b/crates/tinywasm/src/func/host.rs index 83b17f9..5335444 100644 --- a/crates/tinywasm/src/func/host.rs +++ b/crates/tinywasm/src/func/host.rs @@ -9,8 +9,8 @@ use crate::{Function, FunctionInstance, Result, Store}; pub struct HostFunction(Arc); impl HostFunction { - fn instantiate_canonical(&self, store: &mut Store, ty: &FuncType) -> Function { - let type_addr = store.register_host_type(ty); + /// Instantiates the function with an already registered canonical type. + pub(crate) fn instantiate_registered(&self, store: &mut Store, type_addr: TypeAddr) -> Function { let addr = store.add_func(FunctionInstance { type_addr, gc: store.state.func_gc_metadata(type_addr), @@ -19,8 +19,12 @@ impl HostFunction { Function { item: crate::StoreItem::new(store.id(), addr), module_id: 0 } } - /// Instantiates this definition after resolving the importing module's types. - pub(crate) fn instantiate_for_import(&self, store: &mut Store, type_addrs: &[TypeAddr]) -> Result { + /// Resolves the importing module's types without allocating a function instance. + pub(crate) fn resolve_import_type(&self, type_addrs: &[TypeAddr]) -> Result { + let mut types = self.0.ty.params().iter().chain(self.0.ty.results()); + if types.all(|ty| !matches!(ty, WasmType::Ref(ty) if ty.is_concrete())) { + return Ok(self.0.ty.clone()); + } let resolve = |ty: WasmType| -> Result { let WasmType::Ref(ref_ty) = ty else { return Ok(ty) }; let Some(module_addr) = ref_ty.type_index() else { return Ok(ty) }; @@ -34,7 +38,7 @@ impl HostFunction { }; let params = self.0.ty.params().iter().copied().map(resolve).collect::>>()?; let results = self.0.ty.results().iter().copied().map(resolve).collect::>>()?; - Ok(self.instantiate_canonical(store, &FuncType::new(¶ms, &results))) + Ok(FuncType::new(¶ms, &results)) } /// Calls the host function through its untyped value interface. @@ -111,7 +115,8 @@ impl HostFunction { { return Err(crate::Error::other("host function signature contains a concrete type from another store")); } - Ok(self.instantiate_canonical(store, &self.0.ty)) + let type_addr = store.register_host_type(&self.0.ty); + Ok(self.instantiate_registered(store, type_addr)) } /// Create a new untyped host function. diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 8232a90..b8de4e0 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -168,8 +168,9 @@ impl Imports { self } - pub(crate) fn take_defined(&self, import: &Import) -> Option { - self.externs.get(import.module.as_ref())?.get(import.name.as_ref()).cloned() + /// Returns an explicitly defined import without cloning its handle. + pub(crate) fn defined(&self, import: &Import) -> Option<&Extern> { + self.externs.get(import.module.as_ref())?.get(import.name.as_ref()) } fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { @@ -247,27 +248,43 @@ impl Imports { }; for import in &*module.imports { - let (val, func_handle) = if let Some(defined) = self.take_defined(import) { + let val = if let Some(defined) = self.defined(import) { match defined { Extern::Global(global) => { global.0.validate_store(store)?; - (ExternVal::Global(global.0.addr), None) + ExternVal::Global(global.0.addr) } Extern::Table(table) => { table.0.validate_store(store)?; - (ExternVal::Table(table.0.addr), None) + ExternVal::Table(table.0.addr) } Extern::Memory(memory) => { memory.0.validate_store(store)?; - (ExternVal::Memory(memory.0.addr), None) + ExternVal::Memory(memory.0.addr) + } + Extern::Function(func) => { + func.item.validate_store(store)?; + ExternVal::Func(func.addr()) } - Extern::Function(func) => (ExternVal::Func(func.addr()), Some(func)), Extern::HostFunction(func) => { - (ExternVal::Func(func.instantiate_for_import(store, type_addrs)?.addr()), None) + let ImportKind::Function(type_idx) = import.kind else { + cold_path(); + return Err(LinkingError::incompatible_import_type(import).into()); + }; + let expected_type_addr = type_addrs + .get(type_idx as usize) + .ok_or_else(|| LinkingError::incompatible_import_type(import))?; + let actual_ty = func.resolve_import_type(type_addrs)?; + let actual_type_addr = store.register_host_type(&actual_ty); + if !store.state.type_addr_is_subtype(actual_type_addr, *expected_type_addr) { + cold_path(); + return Err(LinkingError::incompatible_import_type(import).into()); + } + ExternVal::Func(func.instantiate_registered(store, actual_type_addr).addr()) } Extern::Tag(tag) => { tag.0.validate_store(store)?; - (ExternVal::Tag(tag.0.addr), None) + ExternVal::Tag(tag.0.addr) } } } else { @@ -276,7 +293,7 @@ impl Imports { return Err(LinkingError::unknown_import(import).into()); }; instance.validate_store(store)?; - (instance.export_addr(&import.name).ok_or_else(|| LinkingError::unknown_import(import))?, None) + instance.export_addr(&import.name).ok_or_else(|| LinkingError::unknown_import(import))? }; if val.kind() != (&import.kind).into() { @@ -317,9 +334,6 @@ impl Imports { (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { let expected_type_addr = type_addrs.get(*ty as usize).ok_or_else(|| LinkingError::incompatible_import_type(import))?; - if let Some(func) = &func_handle { - func.item.validate_store(store)?; - } if !store.state.type_addr_is_subtype(store.state.get_func(func_addr).type_addr, *expected_type_addr) { cold_path(); diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 22c8441..d9d00eb 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -1673,10 +1673,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.state.gc.copy_within(dst, src_index..src_end, dst_index).expect("live array range"); return Ok(()); } - let mut values = Vec::new(); - values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; - values.extend_from_slice(&src_object.values[src_index..src_end]); - self.store.state.gc.set_slice(dst, dst_index, &values).expect("live array range"); + self.store.state.gc.copy_between(src, src_index..src_end, dst, dst_index).expect("live array ranges"); Ok(()) } diff --git a/crates/tinywasm/src/interpreter/simd/mod.rs b/crates/tinywasm/src/interpreter/simd/mod.rs index d54268f..406c0c7 100644 --- a/crates/tinywasm/src/interpreter/simd/mod.rs +++ b/crates/tinywasm/src/interpreter/simd/mod.rs @@ -3,8 +3,6 @@ #[macro_use] mod macros; mod instructions; -#[cfg(test)] -mod tests; mod utils; #[cfg(target_arch = "wasm32")] diff --git a/crates/tinywasm/src/interpreter/simd/tests.rs b/crates/tinywasm/src/interpreter/simd/tests.rs deleted file mode 100644 index 7400f6b..0000000 --- a/crates/tinywasm/src/interpreter/simd/tests.rs +++ /dev/null @@ -1,60 +0,0 @@ -use super::Value128; - -fn ref_swizzle(a: [u8; 16], idx: [u8; 16]) -> [u8; 16] { - let mut out = [0u8; 16]; - for i in 0..16 { - let j = idx[i]; - out[i] = if j < 16 { a[(j & 0x0f) as usize] } else { 0 }; - } - out -} - -fn ref_shuffle(a: [u8; 16], b: [u8; 16], idx: [u8; 16]) -> [u8; 16] { - let mut out = [0u8; 16]; - for i in 0..16 { - let j = idx[i] & 31; - out[i] = if j < 16 { a[j as usize] } else { b[(j & 0x0f) as usize] }; - } - out -} - -#[test] -fn swizzle_matches_reference() { - let a = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; - - for seed in 0u32..512 { - let mut s = [0u8; 16]; - let mut x = seed.wrapping_mul(0x9e37_79b9).wrapping_add(0x7f4a_7c15); - for byte in &mut s { - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - *byte = (x & 0xff) as u8; - } - - let got = Value128(a).i8x16_swizzle(Value128(s)); - let expected = ref_swizzle(a, s).into(); - assert_eq!(got, expected, "seed={seed}"); - } -} - -#[test] -fn shuffle_matches_reference() { - let a = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f]; - let b = [0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf]; - - for seed in 0u32..512 { - let mut idx = [0u8; 16]; - let mut x = seed.wrapping_mul(0x85eb_ca6b).wrapping_add(0xc2b2_ae35); - for byte in &mut idx { - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - *byte = (x & 0xff) as u8; - } - - let got = Value128::i8x16_shuffle(Value128(a), Value128(b), Value128(idx)); - let expected = ref_shuffle(a, b, idx).into(); - assert_eq!(got, expected, "seed={seed}"); - } -} diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 1824d20..ceea9eb 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -375,9 +375,7 @@ impl ValueStack { WasmValue::I64(v) => self.stack_64.push(v as u64)?, WasmValue::F32(v) => self.stack_32.push(v.to_bits())?, WasmValue::F64(v) => self.stack_64.push(v.to_bits())?, - WasmValue::Ref(v) => { - self.stack_32.push(TinyWasmValue::from(WasmValue::Ref(v)).as_ref().unwrap().raw())? - } + WasmValue::Ref(v) => self.stack_32.push(ValueRef::from(v).raw())?, WasmValue::V128(v) => self.stack_128.push(v.into())?, } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 6913605..b3382e8 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -87,55 +87,19 @@ impl From for ValueRef { } } -#[allow(private_interfaces)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// A untyped WebAssembly value -pub enum TinyWasmValue { - /// A 32-bit value +/// An untyped internal WebAssembly value. +pub(crate) enum TinyWasmValue { + /// A 32-bit value. Value32(Value32), - /// A 64-bit value + /// A 64-bit value. Value64(Value64), - /// A 128-bit value + /// A 128-bit value. Value128(Value128), - /// A reference value + /// A reference value. ValueRef(ValueRef), } -impl TinyWasmValue { - /// Converts the value to a 32-bit value (returns None if the value is not a 32-bit value) - pub fn as_32(self) -> Option { - match self { - Self::Value32(v) => Some(v), - _ => None, - } - } - - /// Converts the value to a 64-bit value (returns None if the value is not a 64-bit value) - pub fn as_64(self) -> Option { - match self { - Self::Value64(v) => Some(v), - _ => None, - } - } - - /// Converts the value to a 128-bit value (returns None if the value is not a 128-bit value) - pub fn as_128(self) -> Option { - match self { - Self::Value128(v) => Some(v), - _ => None, - } - } - - /// Converts the value to a reference value (returns None if the value is not a reference value) - #[allow(private_interfaces, dead_code)] - pub fn as_ref(self) -> Option { - match self { - Self::ValueRef(v) => Some(v), - _ => None, - } - } -} - impl From<&WasmValue> for TinyWasmValue { fn from(value: &WasmValue) -> Self { match value { diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 55f9e3b..dd31efa 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -369,7 +369,7 @@ impl Table { } let init = table_value_to_element(&store.state, ty.element_type, init).map_err(Error::from)?; let addr = store.state.tables.len() as TableAddr; - store.state.tables.push(TableInstance::new_with_init(ty, init)?); + store.state.tables.push(TableInstance::new(ty, init)?); Ok(Self(StoreItem::new(store.id(), addr))) } diff --git a/crates/tinywasm/src/store/const_expr.rs b/crates/tinywasm/src/store/const_expr.rs index 201a1e1..c371763 100644 --- a/crates/tinywasm/src/store/const_expr.rs +++ b/crates/tinywasm/src/store/const_expr.rs @@ -6,6 +6,42 @@ use super::{State, default_value}; use crate::interpreter::{TinyWasmValue, ValueRef}; use crate::{Error, Result, Trap}; +fn resolve(items: &[T], index: u32, kind: &str) -> Result { + items.get(index as usize).copied().ok_or_else(|| Error::Other(format!("{kind} {index} not found"))) +} + +fn pop_value(stack: &mut Vec, storage: StorageType) -> Result { + let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; + match (storage, value) { + (StorageType::I8, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u8 as u32)), + (StorageType::I16, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u16 as u32)), + (StorageType::Value(WasmType::I32 | WasmType::F32), value @ TinyWasmValue::Value32(_)) + | (StorageType::Value(WasmType::I64 | WasmType::F64), value @ TinyWasmValue::Value64(_)) + | (StorageType::Value(WasmType::V128), value @ TinyWasmValue::Value128(_)) + | (StorageType::Value(WasmType::Ref(_)), value @ TinyWasmValue::ValueRef(_)) => Ok(value), + _ => Err(Error::other("type mismatch in GC constant")), + } +} + +fn value_ref(value: &TinyWasmValue) -> Option { + match value { + TinyWasmValue::ValueRef(value) => Some(*value), + _ => None, + } +} + +fn alloc_object( + state: &mut State, + stack: &mut Vec, + type_addr: TypeAddr, + values: Vec, +) -> Result<()> { + let roots = stack.iter().filter_map(value_ref).map(ValueRef::raw); + let reference = state.alloc_gc_object(type_addr, values, roots)?; + stack.push(TinyWasmValue::ValueRef(reference)); + Ok(()) +} + #[inline] pub(super) fn eval_const( state: &mut State, @@ -16,32 +52,6 @@ pub(super) fn eval_const( ) -> Result { use ConstInstruction::*; - let global_value = |state: &State, index: u32| -> Result { - let addr = - *global_addrs.get(index as usize).ok_or_else(|| Error::Other(format!("global {index} not found")))?; - Ok(state.globals.get(addr)) - }; - let func_ref = |index: u32| -> Result { - let addr = - *func_addrs.get(index as usize).ok_or_else(|| Error::Other(format!("function {index} not found")))?; - Ok(ValueRef::from_category_addr(addr)) - }; - let type_addr = |index: TypeAddr| -> Result { - type_addrs.get(index as usize).copied().ok_or_else(|| Error::other("GC constant type not found")) - }; - let pop_value = |stack: &mut Vec, storage: StorageType| -> Result { - let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; - match (storage, value) { - (StorageType::I8, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u8 as u32)), - (StorageType::I16, TinyWasmValue::Value32(value)) => Ok(TinyWasmValue::Value32(value as u16 as u32)), - (StorageType::Value(WasmType::I32 | WasmType::F32), value @ TinyWasmValue::Value32(_)) - | (StorageType::Value(WasmType::I64 | WasmType::F64), value @ TinyWasmValue::Value64(_)) - | (StorageType::Value(WasmType::V128), value @ TinyWasmValue::Value128(_)) - | (StorageType::Value(WasmType::Ref(_)), value @ TinyWasmValue::ValueRef(_)) => Ok(value), - _ => Err(Error::other("type mismatch in GC constant")), - } - }; - if let [instruction] = instructions { match instruction { I32Const(value) => return Ok(TinyWasmValue::Value32(*value as u32)), @@ -49,9 +59,24 @@ pub(super) fn eval_const( F32Const(value) => return Ok(TinyWasmValue::Value32(value.to_bits())), F64Const(value) => return Ok(TinyWasmValue::Value64(value.to_bits())), V128Const(value) => return Ok(TinyWasmValue::Value128((*value).into())), - GlobalGet(index) => return global_value(state, *index), + GlobalGet32(index) => { + return Ok(TinyWasmValue::Value32(state.globals.get_32(resolve(global_addrs, *index, "global")?))); + } + GlobalGet64(index) => { + return Ok(TinyWasmValue::Value64(state.globals.get_64(resolve(global_addrs, *index, "global")?))); + } + GlobalGet128(index) => { + return Ok(TinyWasmValue::Value128(state.globals.get_128(resolve(global_addrs, *index, "global")?))); + } + GlobalGetRef(index) => { + let value = state.globals.get_32(resolve(global_addrs, *index, "global")?); + return Ok(TinyWasmValue::ValueRef(ValueRef::from_raw(value))); + } Ref(RefValue::Null) => return Ok(TinyWasmValue::ValueRef(ValueRef::NULL)), - Ref(RefValue::Func(func)) => return Ok(TinyWasmValue::ValueRef(func_ref(func.addr())?)), + Ref(RefValue::Func(func)) => { + let addr = resolve(func_addrs, func.addr(), "function")?; + return Ok(TinyWasmValue::ValueRef(ValueRef::from_category_addr(addr))); + } _ => {} } } @@ -64,9 +89,24 @@ pub(super) fn eval_const( F32Const(value) => stack.push(TinyWasmValue::Value32(value.to_bits())), F64Const(value) => stack.push(TinyWasmValue::Value64(value.to_bits())), V128Const(value) => stack.push(TinyWasmValue::Value128((*value).into())), - GlobalGet(index) => stack.push(global_value(state, *index)?), + GlobalGet32(index) => { + stack.push(TinyWasmValue::Value32(state.globals.get_32(resolve(global_addrs, *index, "global")?))); + } + GlobalGet64(index) => { + stack.push(TinyWasmValue::Value64(state.globals.get_64(resolve(global_addrs, *index, "global")?))); + } + GlobalGet128(index) => { + stack.push(TinyWasmValue::Value128(state.globals.get_128(resolve(global_addrs, *index, "global")?))); + } + GlobalGetRef(index) => { + let value = state.globals.get_32(resolve(global_addrs, *index, "global")?); + stack.push(TinyWasmValue::ValueRef(ValueRef::from_raw(value))); + } Ref(RefValue::Null) => stack.push(TinyWasmValue::ValueRef(ValueRef::NULL)), - Ref(RefValue::Func(func)) => stack.push(TinyWasmValue::ValueRef(func_ref(func.addr())?)), + Ref(RefValue::Func(func)) => { + let addr = resolve(func_addrs, func.addr(), "function")?; + stack.push(TinyWasmValue::ValueRef(ValueRef::from_category_addr(addr))); + } Ref(_) => { cold_path(); return Err(Error::other("unsupported reference constant")); @@ -86,13 +126,13 @@ pub(super) fn eval_const( stack.push(value); } StructNew(type_index) | StructNewDefault(type_index) => { - let type_addr = type_addr(*type_index)?; + let type_addr = resolve(type_addrs, *type_index, "type")?; let fields = state .get_type(type_addr) .as_struct() .ok_or_else(|| Error::other("GC constant type is not a struct"))? .fields - .clone(); + .as_ref(); let default = matches!(instruction, StructNewDefault(_)); let mut values = Vec::new(); values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; @@ -104,12 +144,10 @@ pub(super) fn eval_const( } values.reverse(); } - let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); - let reference = state.alloc_gc_object(type_addr, values, roots)?; - stack.push(TinyWasmValue::ValueRef(reference)); + alloc_object(state, &mut stack, type_addr, values)?; } ArrayNew(type_index) | ArrayNewDefault(type_index) => { - let type_addr = type_addr(*type_index)?; + let type_addr = resolve(type_addrs, *type_index, "type")?; let storage = state .get_type(type_addr) .as_array() @@ -127,12 +165,10 @@ pub(super) fn eval_const( let mut values = Vec::new(); values.try_reserve_exact(len as usize).map_err(|_| Trap::OutOfMemory)?; values.resize(len as usize, value); - let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); - let reference = state.alloc_gc_object(type_addr, values, roots)?; - stack.push(TinyWasmValue::ValueRef(reference)); + alloc_object(state, &mut stack, type_addr, values)?; } ArrayNewFixed(type_index, len) => { - let type_addr = type_addr(*type_index)?; + let type_addr = resolve(type_addrs, *type_index, "type")?; let storage = state .get_type(type_addr) .as_array() @@ -145,9 +181,7 @@ pub(super) fn eval_const( values.push(pop_value(&mut stack, storage)?); } values.reverse(); - let roots = stack.iter().filter_map(|value| value.as_ref()).map(ValueRef::raw); - let reference = state.alloc_gc_object(type_addr, values, roots)?; - stack.push(TinyWasmValue::ValueRef(reference)); + alloc_object(state, &mut stack, type_addr, values)?; } I32Add | I32Sub | I32Mul => { let rhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; diff --git a/crates/tinywasm/src/store/gc/arena.rs b/crates/tinywasm/src/store/gc/arena.rs index b8f5961..6238466 100644 --- a/crates/tinywasm/src/store/gc/arena.rs +++ b/crates/tinywasm/src/store/gc/arena.rs @@ -139,6 +139,21 @@ impl Arena { } } + /// Returns exclusive references for two distinct live handles. + pub(crate) fn get_disjoint_mut(&mut self, first: Handle, second: Handle) -> Option<(&mut T, &mut T)> { + let [first_slot, second_slot] = + self.slots.get_disjoint_mut([first.index as usize, second.index as usize]).ok()?; + if first_slot.generation != first.generation || second_slot.generation != second.generation { + return None; + } + match (&mut first_slot.state, &mut second_slot.state) { + (SlotState::Occupied { value: first, .. }, SlotState::Occupied { value: second, .. }) => { + Some((first, second)) + } + _ => None, + } + } + /// Returns whether an allocation of this size should trigger collection. pub(crate) fn should_collect(&self, out_of_line_bytes: usize) -> bool { size_of::>() diff --git a/crates/tinywasm/src/store/gc/object.rs b/crates/tinywasm/src/store/gc/object.rs index a3c5440..4af64c9 100644 --- a/crates/tinywasm/src/store/gc/object.rs +++ b/crates/tinywasm/src/store/gc/object.rs @@ -94,7 +94,10 @@ impl GcHeap { } pub(crate) fn set(&mut self, object: ValueRef, index: usize, value: TinyWasmValue) -> Option<()> { - let reference = value.as_ref().and_then(|value| self.handle(value)); + let reference = match value { + TinyWasmValue::ValueRef(value) => self.handle(value), + _ => None, + }; let object = self.get_mut(object)?; *object.values.get_mut(index)? = value; if let Some(references) = &mut object.references { @@ -112,11 +115,14 @@ impl GcHeap { object.values.get_mut(index..end)?.copy_from_slice(values); if let Some(references) = &mut object.references { for (reference, value) in references[index..end].iter_mut().zip(values) { - *reference = value.as_ref().and_then(|value| { - let key = value.addr()?; - let index = directory.binary_search_by_key(&key, |entry| entry.0).ok()?; - Some(directory[index].1) - }); + *reference = match value { + TinyWasmValue::ValueRef(value) => { + let key = value.addr(); + key.and_then(|key| directory.binary_search_by_key(&key, |entry| entry.0).ok()) + .map(|index| directory[index].1) + } + _ => None, + }; } } Some(()) @@ -129,7 +135,10 @@ impl GcHeap { range: core::ops::Range, value: TinyWasmValue, ) -> Option<()> { - let reference = value.as_ref().and_then(|value| self.handle(value)); + let reference = match value { + TinyWasmValue::ValueRef(value) => self.handle(value), + _ => None, + }; let object = self.get_mut(object)?; object.values.get_mut(range.clone())?.fill(value); if let Some(references) = &mut object.references { @@ -176,6 +185,30 @@ impl GcHeap { } Some(()) } + + /// Copies values and tracing metadata between two distinct objects. + pub(crate) fn copy_between( + &mut self, + src: ValueRef, + src_range: core::ops::Range, + dst: ValueRef, + dst_index: usize, + ) -> Option<()> { + let src = self.handle(src)?; + let dst = self.handle(dst)?; + let (src, dst) = self.objects.get_disjoint_mut(src, dst)?; + let src_values = src.values.get(src_range.clone())?; + let dst_end = dst_index.checked_add(src_values.len())?; + dst.values.get_mut(dst_index..dst_end)?.copy_from_slice(src_values); + if let Some(dst_refs) = &mut dst.references { + if let Some(src_refs) = &src.references { + dst_refs[dst_index..dst_end].copy_from_slice(&src_refs[src_range]); + } else { + dst_refs[dst_index..dst_end].fill(None); + } + } + Some(()) + } } #[cfg(test)] diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index 638b01d..69e538f 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use tinywasm_types::*; -use crate::interpreter::{TinyWasmValue, Value32, Value64, Value128, ValueRef}; +use crate::interpreter::{TinyWasmValue, Value32, Value64, Value128}; struct GlobalLane { values: Vec, @@ -116,28 +116,6 @@ impl Globals { } } - /// Returns a dynamically represented global value. - pub(crate) fn get(&self, addr: GlobalAddr) -> TinyWasmValue { - let ty = self.ty(addr); - match ty.ty { - WasmType::I32 | WasmType::F32 => TinyWasmValue::Value32(self.get_32(addr)), - WasmType::I64 | WasmType::F64 => TinyWasmValue::Value64(self.get_64(addr)), - WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::from_raw(self.get_32(addr))), - WasmType::V128 => TinyWasmValue::Value128(self.get_128(addr)), - } - } - - /// Sets a global from a dynamically represented value. - pub(crate) fn set(&mut self, addr: GlobalAddr, value: TinyWasmValue) { - match (self.ty(addr).ty, value) { - (WasmType::I32 | WasmType::F32, TinyWasmValue::Value32(value)) => self.set_32(addr, value), - (WasmType::Ref(_), TinyWasmValue::ValueRef(value)) => self.set_32(addr, value.raw()), - (WasmType::I64 | WasmType::F64, TinyWasmValue::Value64(value)) => self.set_64(addr, value), - (WasmType::V128, TinyWasmValue::Value128(value)) => self.set_128(addr, value), - _ => unreachable!("global value does not match its declared type"), - } - } - /// Returns a raw value from the 32-bit lane. #[inline(always)] pub(crate) fn get_32(&self, addr: GlobalAddr) -> Value32 { @@ -175,7 +153,7 @@ impl Globals { } /// Iterates over globals in the 32-bit lane for root tracing. - pub(crate) fn globals_32(&self) -> impl Iterator + '_ { - self.globals_32.values.iter().copied().zip(self.globals_32.types.iter().copied()) + pub(crate) fn globals_32(&self) -> impl Iterator { + self.globals_32.values.iter().zip(&self.globals_32.types) } } diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs index 5805c86..f25d4ae 100644 --- a/crates/tinywasm/src/store/memory/mod.rs +++ b/crates/tinywasm/src/store/memory/mod.rs @@ -436,185 +436,3 @@ impl MemValue<16> for Value128 { const fn memory_oob(offset: usize, len: usize, max: usize) -> crate::Trap { crate::Trap::MemoryOutOfBounds { offset, len, max } } - -#[cfg(test)] -mod tests { - use super::*; - use tinywasm_types::MemoryArch; - - fn create_test_memory(kind: MemoryType, backend: MemoryBackend) -> MemoryInstance { - MemoryInstance::new(kind, &backend).unwrap() - } - - fn test_backends() -> [MemoryBackend; 2] { - [MemoryBackend::vec(), MemoryBackend::paged(4)] - } - - #[test] - fn effective_memory_addresses_handle_host_and_wasm_overflow() { - let memory = create_test_memory(MemoryType::new(MemoryArch::I32, 1, Some(1), None), MemoryBackend::vec()); - assert_eq!(memory.effective_addr_32::<1>(1, 2), Ok(3)); - assert_eq!(memory.effective_addr_64::<1>(1, 2), Ok(3)); - assert!(memory.effective_addr_64::<1>(u64::MAX, 1).is_err()); - - #[cfg(target_pointer_width = "64")] - assert_eq!( - memory.effective_addr_32::<1>(u32::MAX, u64::from(u32::MAX)), - Ok(usize::try_from(u64::from(u32::MAX) * 2).unwrap()) - ); - - #[cfg(target_pointer_width = "32")] - assert!(memory.effective_addr_32::<1>(u32::MAX, 1).is_err()); - } - - #[test] - fn memory_copy_from_slice_and_read_vec_work() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - let data = [1, 2, 3, 4]; - assert!(memory.inner.write_all(0, &data).is_some()); - assert_eq!(memory.inner.read_vec(0, data.len()).unwrap(), data); - } - } - - #[test] - fn memory_read_returns_partial_count() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(1), Some(4)); - let memory = create_test_memory(kind, backend); - let mut dst = [9; 8]; - assert_eq!(memory.inner.read(2, &mut dst), 2); - assert_eq!(&dst[..2], &[0, 0]); - assert_eq!(&dst[2..], &[9; 6]); - } - } - - #[test] - fn memory_copy_from_slice_out_of_bounds_fails() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - let data = [1, 2, 3, 4]; - let len = memory.inner.len(); - assert!(memory.inner.write_all(len, &data).is_none()); - } - } - - #[test] - fn fixed_width_access_out_of_bounds_traps() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - let len = memory.inner.len(); - - assert!(memory.inner.read_8(len).is_err()); - assert!(memory.inner.read_32(len - 3).is_err()); - assert!(memory.inner.write_8(len, &[0]).is_err()); - assert!(memory.inner.write_32(len - 3, &[0; 4]).is_err()); - } - } - - #[test] - fn memory_fill_works() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - assert!(memory.inner.fill(0, 10, 42).is_some()); - assert_eq!(memory.inner.read_vec(0, 10).unwrap(), vec![42; 10]); - } - } - - #[test] - fn memory_fill_out_of_bounds_fails() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - let len = memory.inner.len(); - assert!(memory.inner.fill(len, 10, 42).is_none()); - } - } - - #[test] - fn memory_copy_within_works() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - memory.inner.fill(0, 10, 1).unwrap(); - assert!(memory.copy_within(10, 0, 10).is_ok()); - assert_eq!(memory.inner.read_vec(10, 10).unwrap(), vec![1; 10]); - } - } - - #[test] - fn memory_copy_within_out_of_bounds_fails() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - assert!(memory.copy_within(memory.inner.len(), 0, 10).is_err()); - } - } - - #[test] - fn memory_grow_works() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - let original_pages = memory.page_count; - assert_eq!(memory.grow(1, false).unwrap(), Some(original_pages as i64)); - assert_eq!(memory.page_count, original_pages + 1); - } - } - - #[test] - fn memory_grow_out_of_bounds_fails() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - assert_eq!(memory.grow(memory.kind.max_size() as i64 + 1, false).unwrap(), None); - } - } - - #[test] - fn memory_grow_respects_max_pages() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - assert_eq!(memory.grow(1, false).unwrap(), Some(1)); - assert_eq!(memory.grow(1, false).unwrap(), None); - } - } - - #[test] - fn memory_grow_negative_delta_fails() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let mut memory = create_test_memory(kind, backend); - let original_pages = memory.page_count; - assert_eq!(memory.grow(-1, false).unwrap(), None); - assert_eq!(memory.page_count, original_pages); - } - } - - #[test] - fn memory_custom_page_size_out_of_bounds_fails() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); - let mut memory = create_test_memory(kind, backend); - let data = [1, 2]; - assert!(memory.inner.write_all(0, &data).is_none()); - } - } - - #[test] - fn memory_custom_page_size_grow_works() { - for backend in test_backends() { - let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); - let mut memory = create_test_memory(kind, backend); - assert_eq!(memory.grow(1, false).unwrap(), Some(1)); - let data = [1, 2]; - assert!(memory.inner.write_all(0, &data).is_some()); - assert_eq!(memory.inner.read_vec(0, data.len()).unwrap(), data); - } - } -} diff --git a/crates/tinywasm/src/store/memory/paged.rs b/crates/tinywasm/src/store/memory/paged.rs index 6ae65af..fc5947c 100644 --- a/crates/tinywasm/src/store/memory/paged.rs +++ b/crates/tinywasm/src/store/memory/paged.rs @@ -297,48 +297,3 @@ impl LinearMemory for PagedMemory { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::{LinearMemory, PagedMemory}; - - #[test] - fn paged_memory_reads_zeroes_from_sparse_chunks() { - let memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible"); - let mut dst = [1; 6]; - assert_eq!(memory.read(5, &mut dst), 3); - assert_eq!(&dst[..3], &[0; 3]); - assert_eq!(&dst[3..], &[1; 3]); - } - - #[test] - fn paged_memory_store_and_load_crosses_chunk_boundaries() { - let mut memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible"); - memory.write_all(3, &[1, 2, 3, 4, 5, 6]).unwrap(); - - let mut dst = [0; 6]; - memory.read_exact(3, &mut dst).unwrap(); - assert_eq!(dst, [1, 2, 3, 4, 5, 6]); - } - - #[test] - fn paged_memory_copy_within_handles_overlap() { - let mut memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible"); - memory.write_all(0, &[1, 2, 3, 4, 5, 6]).unwrap(); - memory.copy_within(2, 0, 6).unwrap(); - - let mut dst = [0; 8]; - memory.read_exact(0, &mut dst).unwrap(); - assert_eq!(dst, [1, 2, 1, 2, 3, 4, 5, 6]); - } - - #[test] - fn paged_memory_write_stops_at_chunk_boundary() { - let mut memory = PagedMemory::try_new(16, 4).expect("test memory should be constructible"); - assert_eq!(memory.write(3, &[1, 2, 3, 4]), 1); - - let mut dst = [0; 4]; - memory.read_exact(3, &mut dst).unwrap(); - assert_eq!(dst, [1, 0, 0, 0]); - } -} diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 84a52b5..1aabfb3 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -122,18 +122,6 @@ impl Store { self.module_instances.push(instance); } - /// Gets a global by its opaque store address. - #[doc(hidden)] - pub fn get_global_val(&self, addr: GlobalAddr) -> TinyWasmValue { - self.state.globals.get(addr) - } - - /// Sets a global by its opaque store address. - #[doc(hidden)] - pub fn set_global_val(&mut self, addr: GlobalAddr, value: TinyWasmValue) { - self.state.globals.set(addr, value); - } - /// Returns whether a public value has the requested runtime type. #[doc(hidden)] pub fn value_matches_type(&self, value: WasmValue, ty: WasmType) -> bool { @@ -264,7 +252,7 @@ impl Store { MemoryArch::I32 => TableType::new(element_type, table.ty.size_initial, table.ty.size_max), MemoryArch::I64 => TableType::new64(element_type, table.ty.size_initial, table.ty.size_max), }; - self.state.tables.push(TableInstance::new_with_init(ty, init)?); + self.state.tables.push(TableInstance::new(ty, init)?); } Ok(start..start + tables.len() as TableAddr) } diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index e6a213f..e12ef1a 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -92,7 +92,7 @@ impl State { self.globals .globals_32() .filter(|(_, ty)| Self::type_may_contain_gc_in(canonical_types, &ty.ty)) - .map(|(value, _)| ValueRef::from_raw(value)), + .map(|(value, _)| ValueRef::from_raw(*value)), ) .chain( self.tables @@ -108,12 +108,16 @@ impl State { .filter(|element| Self::type_may_contain_gc_in(canonical_types, &WasmType::Ref(element.ty))) .flat_map(|element| element.items.iter().flatten().copied()), ) - .chain( - self.exceptions - .iter() - .flat_map(|exception| exception.payload.iter().filter_map(|value| value.as_ref())), - ) - .chain(values.iter().filter_map(|value| value.as_ref())); + .chain(self.exceptions.iter().flat_map(|exception| { + exception.payload.iter().filter_map(|value| match value { + TinyWasmValue::ValueRef(value) => Some(*value), + _ => None, + }) + })) + .chain(values.iter().filter_map(|value| match value { + TinyWasmValue::ValueRef(value) => Some(*value), + _ => None, + })); self.gc.collect(roots).map_err(|_| Trap::OutOfMemory)?; } self.gc.alloc(type_addr, values, trace_references).map_err(|_| Trap::OutOfMemory) @@ -270,7 +274,7 @@ impl State { let actual = RefType::new_concrete(false, func.type_addr).expect("canonical type fits"); return self.ref_type_is_subtype(actual, expected); } - if expected.is_exn() { + if expected.abstract_heap_type() == Some(AbstractHeapType::Exn) { return value.addr().is_some_and(|addr| self.exceptions.get(addr as usize).is_some()); } if value.is_i31() { @@ -316,8 +320,9 @@ impl State { { false } - (WasmValue::Ref(RefValue::Exn(value)), WasmType::Ref(expected)) if expected.is_exn() => { - self.exceptions.get(value.addr() as usize).is_some() + (WasmValue::Ref(RefValue::Exn(value)), WasmType::Ref(expected)) => { + expected.abstract_heap_type() == Some(AbstractHeapType::Exn) + && self.exceptions.get(value.addr() as usize).is_some() } (WasmValue::Ref(RefValue::Any(value)), WasmType::Ref(expected)) => { self.value_ref_matches(ValueRef::from_raw(value.raw()), expected) diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index ece0001..5cbce74 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -15,12 +15,8 @@ pub(crate) struct TableInstance { } impl TableInstance { - #[cfg(test)] - pub(crate) fn new(kind: TableType) -> Result { - Self::new_with_init(kind, ValueRef::NULL) - } - - pub(crate) fn new_with_init(kind: TableType, init: ValueRef) -> Result { + /// Creates a table filled with the given initial reference. + pub(crate) fn new(kind: TableType, init: ValueRef) -> Result { let size = usize::try_from(kind.size_initial).map_err(|_| Trap::OutOfMemory)?; if size > MAX_TABLE_SIZE { return Err(Trap::OutOfMemory.into()); @@ -101,50 +97,3 @@ impl TableInstance { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec; - - // Helper to create a dummy TableType - fn dummy_table_type() -> TableType { - TableType::new(RefType::FUNCREF, 10, Some(20)) - } - - #[test] - fn test_table_instance_creation() { - let kind = dummy_table_type(); - let table_instance = TableInstance::new(kind).unwrap(); - assert_eq!(table_instance.size() as u64, kind.size_initial, "Table instance creation failed: size mismatch"); - } - - #[test] - fn test_set_and_get() { - let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind).unwrap(); - - let value = ValueRef::from_raw(2); - let result = table_instance.set(0, value); - assert!(result.is_ok(), "Setting table element failed"); - - let elem = table_instance.get(0); - assert!(elem.is_ok() && elem.unwrap() == &value, "Getting table element failed or returned incorrect value"); - } - - #[test] - fn test_table_init() { - let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind).unwrap(); - - let init_elements = vec![ValueRef::from_raw(2); 5]; - let result = table_instance.init(0, &init_elements); - - assert!(result.is_ok(), "Initializing table with elements failed"); - - for i in 0..5 { - let elem = table_instance.get(i); - assert!(elem.is_ok() && !elem.unwrap().is_null(), "Element not initialized correctly at index {i}"); - } - } -} diff --git a/crates/tinywasm/tests/generated/wasm-annotations.csv b/crates/tinywasm/tests/generated/wasm-annotations.csv deleted file mode 100644 index f24ba50..0000000 --- a/crates/tinywasm/tests/generated/wasm-annotations.csv +++ /dev/null @@ -1,5 +0,0 @@ -0.8.0,142,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"annotations/simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"token.wast","passed":61,"failed":0}] -0.9.0,617,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"token.wast","passed":61,"failed":0}] -0.9.1,617,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"token.wast","passed":61,"failed":0}] -0.10.0,617,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"token.wast","passed":61,"failed":0}] -0.11.0-pre.0,617,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"token.wast","passed":61,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-extended-const.csv b/crates/tinywasm/tests/generated/wasm-extended-const.csv deleted file mode 100644 index 1e4e851..0000000 --- a/crates/tinywasm/tests/generated/wasm-extended-const.csv +++ /dev/null @@ -1,5 +0,0 @@ -0.8.0,211,79,[{"name":"data.wast","passed":61,"failed":4},{"name":"elem.wast","passed":99,"failed":12},{"name":"global.wast","passed":51,"failed":63}] -0.9.0,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] -0.9.1,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] -0.10.0,290,0,[{"name":"data.wast","passed":65,"failed":0},{"name":"elem.wast","passed":111,"failed":0},{"name":"global.wast","passed":114,"failed":0}] -0.11.0-pre.0,284,0,[{"name":"data.wast","passed":63,"failed":0},{"name":"elem.wast","passed":109,"failed":0},{"name":"global.wast","passed":112,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-function-references.csv b/crates/tinywasm/tests/generated/wasm-function-references.csv deleted file mode 100644 index 9d6dd64..0000000 --- a/crates/tinywasm/tests/generated/wasm-function-references.csv +++ /dev/null @@ -1,3 +0,0 @@ -0.9.0,1536,331,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":0,"failed":9},{"name":"br_on_null.wast","passed":0,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_ref.wast","passed":3,"failed":31},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":146,"failed":21},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":4},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":10,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":35,"failed":8},{"name":"type-equivalence.wast","passed":7,"failed":7},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":10}] -0.10.0,1536,331,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":0,"failed":9},{"name":"br_on_null.wast","passed":0,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_ref.wast","passed":3,"failed":31},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":146,"failed":21},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":4},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":10,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":35,"failed":8},{"name":"type-equivalence.wast","passed":7,"failed":7},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":10}] -0.11.0-pre.0,1866,0,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":9,"failed":0},{"name":"br_on_null.wast","passed":9,"failed":0},{"name":"br_table.wast","passed":186,"failed":0},{"name":"call_ref.wast","passed":34,"failed":0},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":167,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":13,"failed":0},{"name":"ref_as_non_null.wast","passed":7,"failed":0},{"name":"ref_is_null.wast","passed":22,"failed":0},{"name":"ref_null.wast","passed":4,"failed":0},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":50,"failed":0},{"name":"select.wast","passed":157,"failed":0},{"name":"table-sub.wast","passed":3,"failed":0},{"name":"table.wast","passed":43,"failed":0},{"name":"type-equivalence.wast","passed":13,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":12,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv b/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv deleted file mode 100644 index 66d5329..0000000 --- a/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv +++ /dev/null @@ -1,4 +0,0 @@ -0.9.0,615,0,[{"name":"conversions.wast","passed":615,"failed":0}] -0.9.1,615,0,[{"name":"conversions.wast","passed":615,"failed":0}] -0.10.0,615,0,[{"name":"conversions.wast","passed":615,"failed":0}] -0.11.0-pre.0,615,0,[{"name":"conversions.wast","passed":615,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-reference-types.csv b/crates/tinywasm/tests/generated/wasm-reference-types.csv deleted file mode 100644 index 92a4650..0000000 --- a/crates/tinywasm/tests/generated/wasm-reference-types.csv +++ /dev/null @@ -1,4 +0,0 @@ -0.9.0,9124,0,[{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":134,"failed":0},{"name":"br_table.wast","passed":172,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":45,"failed":0},{"name":"elem.wast","passed":60,"failed":0},{"name":"exports.wast","passed":84,"failed":0},{"name":"global.wast","passed":86,"failed":0},{"name":"imports.wast","passed":158,"failed":0},{"name":"linking.wast","passed":132,"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":"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":"select.wast","passed":141,"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":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"unreached-invalid.wast","passed":111,"failed":0}] -0.9.1,9124,0,[{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":134,"failed":0},{"name":"br_table.wast","passed":172,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":45,"failed":0},{"name":"elem.wast","passed":60,"failed":0},{"name":"exports.wast","passed":84,"failed":0},{"name":"global.wast","passed":86,"failed":0},{"name":"imports.wast","passed":158,"failed":0},{"name":"linking.wast","passed":132,"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":"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":"select.wast","passed":141,"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":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"unreached-invalid.wast","passed":111,"failed":0}] -0.10.0,9124,0,[{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":134,"failed":0},{"name":"br_table.wast","passed":172,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":45,"failed":0},{"name":"elem.wast","passed":60,"failed":0},{"name":"exports.wast","passed":84,"failed":0},{"name":"global.wast","passed":86,"failed":0},{"name":"imports.wast","passed":158,"failed":0},{"name":"linking.wast","passed":132,"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":"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":"select.wast","passed":141,"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":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"unreached-invalid.wast","passed":111,"failed":0}] -0.11.0-pre.0,9124,0,[{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":134,"failed":0},{"name":"br_table.wast","passed":172,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":45,"failed":0},{"name":"elem.wast","passed":60,"failed":0},{"name":"exports.wast","passed":84,"failed":0},{"name":"global.wast","passed":86,"failed":0},{"name":"imports.wast","passed":158,"failed":0},{"name":"linking.wast","passed":132,"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":"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":"select.wast","passed":141,"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":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"unreached-invalid.wast","passed":111,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv b/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv deleted file mode 100644 index 4e7e2ee..0000000 --- a/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv +++ /dev/null @@ -1,4 +0,0 @@ -0.9.0,872,0,[{"name":"i32.wast","passed":458,"failed":0},{"name":"i64.wast","passed":414,"failed":0}] -0.9.1,872,0,[{"name":"i32.wast","passed":458,"failed":0},{"name":"i64.wast","passed":414,"failed":0}] -0.10.0,872,0,[{"name":"i32.wast","passed":458,"failed":0},{"name":"i64.wast","passed":414,"failed":0}] -0.11.0-pre.0,872,0,[{"name":"i32.wast","passed":458,"failed":0},{"name":"i64.wast","passed":414,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-tail-call.csv b/crates/tinywasm/tests/generated/wasm-tail-call.csv deleted file mode 100644 index 316895d..0000000 --- a/crates/tinywasm/tests/generated/wasm-tail-call.csv +++ /dev/null @@ -1,4 +0,0 @@ -0.9.0,119,0,[{"name":"return_call.wast","passed":44,"failed":0},{"name":"return_call_indirect.wast","passed":75,"failed":0}] -0.9.1,119,0,[{"name":"return_call.wast","passed":44,"failed":0},{"name":"return_call_indirect.wast","passed":75,"failed":0}] -0.10.0,119,0,[{"name":"return_call.wast","passed":44,"failed":0},{"name":"return_call_indirect.wast","passed":75,"failed":0}] -0.11.0-pre.0,119,0,[{"name":"return_call.wast","passed":44,"failed":0},{"name":"return_call_indirect.wast","passed":75,"failed":0}] diff --git a/crates/tinywasm/tests/test-wasm-annotations.rs b/crates/tinywasm/tests/test-wasm-annotations.rs deleted file mode 100644 index 8655d15..0000000 --- a/crates/tinywasm/tests/test-wasm-annotations.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - - let mut test_suite = TestSuite::new(); - test_suite.run_files(proposal(&Proposal::Annotations))?; - test_suite.save_csv("./tests/generated/wasm-annotations.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/test-wasm-extended-const.rs b/crates/tinywasm/tests/test-wasm-extended-const.rs deleted file mode 100644 index 888a49f..0000000 --- a/crates/tinywasm/tests/test-wasm-extended-const.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - - let mut test_suite = TestSuite::new(); - test_suite.run_files(proposal(&Proposal::ExtendedConst))?; - test_suite.save_csv("./tests/generated/wasm-extended-const.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/test-wasm-function-references.rs b/crates/tinywasm/tests/test-wasm-function-references.rs deleted file mode 100644 index d91ff82..0000000 --- a/crates/tinywasm/tests/test-wasm-function-references.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - - let mut test_suite = TestSuite::new(); - test_suite.run_files(proposal(&Proposal::FunctionReferences))?; - test_suite.save_csv("./tests/generated/wasm-function-references.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs b/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs deleted file mode 100644 index 0475fac..0000000 --- a/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - let mut test_suite = TestSuite::new(); - - test_suite.run_files(proposal(&Proposal::NontrappingFloatToIntConversions))?; - test_suite - .save_csv("./tests/generated/wasm-nontrapping-float-to-int-conversions.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/test-wasm-reference-types.rs b/crates/tinywasm/tests/test-wasm-reference-types.rs deleted file mode 100644 index b6f1155..0000000 --- a/crates/tinywasm/tests/test-wasm-reference-types.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - let mut test_suite = TestSuite::new(); - - test_suite.run_files(proposal(&Proposal::ReferenceTypes))?; - test_suite.save_csv("./tests/generated/wasm-reference-types.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/test-wasm-sign-extension-op.rs b/crates/tinywasm/tests/test-wasm-sign-extension-op.rs deleted file mode 100644 index c0ef143..0000000 --- a/crates/tinywasm/tests/test-wasm-sign-extension-op.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - let mut test_suite = TestSuite::new(); - - test_suite.run_files(proposal(&Proposal::SignExtensionOps))?; - test_suite.save_csv("./tests/generated/wasm-sign-extension-ops.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/test-wasm-tail-call.rs b/crates/tinywasm/tests/test-wasm-tail-call.rs deleted file mode 100644 index b6d8b2b..0000000 --- a/crates/tinywasm/tests/test-wasm-tail-call.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; -use wasm_testsuite::data::{Proposal, proposal}; - -fn main() -> Result<()> { - TestSuite::set_log_level(log::LevelFilter::Off); - - let mut test_suite = TestSuite::new(); - test_suite.run_files(proposal(&Proposal::TailCall))?; - test_suite.print_errors(); - test_suite.save_csv("./tests/generated/wasm-tail-call.csv", env!("CARGO_PKG_VERSION"))?; - test_suite.report_status() -} diff --git a/crates/tinywasm/tests/wasm-custom/table-basics.wast b/crates/tinywasm/tests/wasm-custom/table-basics.wast new file mode 100644 index 0000000..64bda6c --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/table-basics.wast @@ -0,0 +1,32 @@ +(module + (type $result (func (result i32))) + (func $seven (type $result) (i32.const 7)) + (func $nine (type $result) (i32.const 9)) + + (table $table 3 5 funcref) + (elem (table $table) (i32.const 1) func $seven) + (elem $passive func $nine) + + (func (export "size") (result i32) + (table.size $table)) + (func (export "is-null") (param $index i32) (result i32) + (ref.is_null (table.get $table (local.get $index)))) + (func (export "call") (param $index i32) (result i32) + (call_indirect $table (type $result) (local.get $index))) + (func (export "set-nine") (param $index i32) + (table.set $table (local.get $index) (ref.func $nine))) + (func (export "init-nine") (param $index i32) + (table.init $table $passive (local.get $index) (i32.const 0) (i32.const 1))) +) + +(assert_return (invoke "size") (i32.const 3)) +(assert_return (invoke "is-null" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "is-null" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "call" (i32.const 1)) (i32.const 7)) +(assert_return (invoke "set-nine" (i32.const 0))) +(assert_return (invoke "is-null" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "call" (i32.const 0)) (i32.const 9)) +(assert_return (invoke "is-null" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "init-nine" (i32.const 2))) +(assert_return (invoke "is-null" (i32.const 2)) (i32.const 0)) +(assert_return (invoke "call" (i32.const 2)) (i32.const 9)) diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index aff9e24..5eb5491 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -94,7 +94,10 @@ pub enum ConstInstruction { F32Const(f32), F64Const(f64), V128Const([u8; 16]), - GlobalGet(GlobalAddr), + GlobalGet32(GlobalAddr), + GlobalGet64(GlobalAddr), + GlobalGet128(GlobalAddr), + GlobalGetRef(GlobalAddr), Ref(RefValue), RefI31, AnyConvertExtern, From 70c5bb2e4816f7ee458a6180845af3bd6b1d2d1d Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 10 Aug 2026 16:21:47 +0200 Subject: [PATCH 10/15] chore: cleanup tests / devdeps, fix doom example Signed-off-by: Henry --- Cargo.lock | 36 +++------ Cargo.toml | 26 +++++- .../tinywasm/benches => benches}/argon2id.rs | 5 +- .../tinywasm/benches => benches}/fibonacci.rs | 5 +- .../benches => benches}/memory_backends.rs | 0 .../tinywasm/benches => benches}/tinywasm.rs | 5 +- .../benches => benches}/tinywasm_modes.rs | 7 +- crates/cli/Cargo.toml | 6 +- crates/cli/src/bin.rs | 2 +- crates/cli/src/cmd/compile.rs | 2 +- crates/cli/src/cmd/completion.rs | 2 +- crates/cli/src/cmd/dump.rs | 2 +- crates/cli/src/cmd/inspect.rs | 2 +- crates/cli/src/cmd/run.rs | 8 +- crates/cli/src/cmd/wast.rs | 2 +- crates/cli/src/engine_flags.rs | 2 +- crates/cli/src/lib.rs | 4 +- crates/cli/src/load.rs | 2 +- .../testsuite/mod.rs => cli/src/testsuite.rs} | 40 +++++++--- crates/cli/src/value_parse.rs | 6 +- crates/cli/src/wast_runner.rs | 80 ++++++++++--------- crates/tinywasm/Cargo.toml | 27 +------ .../tests/host_func_signature_check.rs | 18 ++--- crates/tinywasm/tests/import_linking.rs | 7 +- crates/tinywasm/tests/imported_table_init.rs | 3 +- crates/tinywasm/tests/internal_refs.rs | 13 ++- crates/tinywasm/tests/memory_backends.rs | 31 +++---- crates/tinywasm/tests/memory_ref_api.rs | 3 +- crates/tinywasm/tests/module_descriptors.rs | 8 +- crates/tinywasm/tests/resume_execution.rs | 3 +- crates/tinywasm/tests/start_function.rs | 3 +- crates/tinywasm/tests/store_ownership.rs | 11 ++- crates/tinywasm/tests/test-wasm-1.rs | 6 +- crates/tinywasm/tests/test-wasm-2.rs | 6 +- crates/tinywasm/tests/test-wasm-3.rs | 6 +- .../tests/test-wasm-custom-page-sizes.rs | 6 +- crates/tinywasm/tests/test-wasm-custom.rs | 6 +- crates/tinywasm/tests/test-wasm-gc.rs | 6 +- crates/tinywasm/tests/test-wasm-latest.rs | 6 +- crates/tinywasm/tests/test-wasm-memory64.rs | 6 +- .../tinywasm/tests/test-wasm-multi-memory.rs | 6 +- .../tinywasm/tests/test-wasm-relaxed-simd.rs | 6 +- crates/tinywasm/tests/test-wasm-simd.rs | 6 +- crates/tinywasm/tests/test-wasm-threads.rs | 6 +- .../tests/test-wasm-wide-arithmetic.rs | 6 +- crates/tinywasm/tests/test-wast.rs | 17 ++-- crates/tinywasm/tests/typed_globals.rs | 5 +- crates/tinywasm/tests/typed_lookup.rs | 5 +- examples/archive.rs | 2 +- examples/doom/Cargo.toml | 2 +- examples/doom/src/main.rs | 10 +-- examples/doom/src/runtime.rs | 35 ++++---- examples/funcref_callbacks.rs | 2 +- examples/linking.rs | 2 +- examples/reentrance.rs | 2 +- examples/resumable.rs | 2 +- examples/simple.rs | 2 +- examples/simple2.rs | 2 +- examples/wasm-rust.rs | 4 +- 59 files changed, 254 insertions(+), 287 deletions(-) rename {crates/tinywasm/benches => benches}/argon2id.rs (91%) rename {crates/tinywasm/benches => benches}/fibonacci.rs (92%) rename {crates/tinywasm/benches => benches}/memory_backends.rs (100%) rename {crates/tinywasm/benches => benches}/tinywasm.rs (93%) rename {crates/tinywasm/benches => benches}/tinywasm_modes.rs (94%) rename crates/{tinywasm/tests/testsuite/mod.rs => cli/src/testsuite.rs} (81%) diff --git a/Cargo.lock b/Cargo.lock index 5312e7f..c4dbba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + [[package]] name = "assert_cmd" version = "2.2.2" @@ -351,16 +357,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fastrand" version = "2.5.0" @@ -447,12 +443,6 @@ dependencies = [ "quote", ] -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - [[package]] name = "indexmap" version = "2.14.0" @@ -872,13 +862,8 @@ dependencies = [ name = "tinywasm" version = "0.11.0-pre.0" dependencies = [ - "criterion", - "eyre", "libm", "log", - "owo-colors", - "serde", - "serde_json", "tinywasm-cli", "tinywasm-parser", "tinywasm-types", @@ -891,16 +876,19 @@ name = "tinywasm-cli" version = "0.11.0-pre.0" dependencies = [ "anstream", + "anyhow", "assert_cmd", "clap", "clap_complete", - "eyre", "log", "owo-colors", "predicates", "pretty_env_logger", + "serde", + "serde_json", "tempfile", "tinywasm", + "wasm-testsuite", "wast", "wat", ] @@ -918,9 +906,11 @@ dependencies = [ name = "tinywasm-root" version = "0.11.0-pre.0" dependencies = [ - "eyre", + "anyhow", + "criterion", "pretty_env_logger", "tinywasm", + "tinywasm-parser", "wat", ] diff --git a/Cargo.toml b/Cargo.toml index ca80b66..5a4e83f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ tinywasm = { path = "crates/tinywasm", version = "0.11.0-pre.0", default-feature tinywasm-parser = { path = "crates/parser", version = "0.11.0-pre.0", default-features = false } tinywasm-types = { path = "crates/types", version = "0.11.0-pre.0", default-features = false } -eyre = "0.6" +anyhow = "1.0" log = "0.4" owo-colors = { version = "4.3" } pretty_env_logger = "0.5" @@ -44,10 +44,32 @@ criterion = { version = "0.8", default-features = false, features = [ name = "wasm-rust" test = false +[[bench]] +harness = false +name = "argon2id" + +[[bench]] +harness = false +name = "fibonacci" + +[[bench]] +harness = false +name = "tinywasm" + +[[bench]] +harness = false +name = "tinywasm_modes" + +[[bench]] +harness = false +name = "memory_backends" + [dev-dependencies] -eyre.workspace = true +anyhow.workspace = true +criterion.workspace = true pretty_env_logger.workspace = true tinywasm = { path = "crates/tinywasm" } +tinywasm-parser.workspace = true wat.workspace = true [profile.bench] diff --git a/crates/tinywasm/benches/argon2id.rs b/benches/argon2id.rs similarity index 91% rename from crates/tinywasm/benches/argon2id.rs rename to benches/argon2id.rs index d2845c5..3121062 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/benches/argon2id.rs @@ -1,9 +1,8 @@ use criterion::{Criterion, criterion_group, criterion_main}; -use eyre::Result; -use tinywasm::{ModuleInstance, Store, types}; +use tinywasm::{ModuleInstance, Result, Store, types}; use types::Module; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.wasm"); +const WASM: &[u8] = include_bytes!("../examples/rust/out/argon2id.wasm"); fn argon2id_parse() -> Result { let parser = tinywasm_parser::Parser::default(); diff --git a/crates/tinywasm/benches/fibonacci.rs b/benches/fibonacci.rs similarity index 92% rename from crates/tinywasm/benches/fibonacci.rs rename to benches/fibonacci.rs index 1bf0791..51fd2d3 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -1,9 +1,8 @@ use criterion::{Criterion, criterion_group, criterion_main}; -use eyre::Result; -use tinywasm::{ModuleInstance, Store, types}; +use tinywasm::{ModuleInstance, Result, Store, types}; use types::Module; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); +const WASM: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); fn fibonacci_parse() -> Result { let parser = tinywasm_parser::Parser::default(); diff --git a/crates/tinywasm/benches/memory_backends.rs b/benches/memory_backends.rs similarity index 100% rename from crates/tinywasm/benches/memory_backends.rs rename to benches/memory_backends.rs diff --git a/crates/tinywasm/benches/tinywasm.rs b/benches/tinywasm.rs similarity index 93% rename from crates/tinywasm/benches/tinywasm.rs rename to benches/tinywasm.rs index 3560d3c..aff8c70 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/benches/tinywasm.rs @@ -1,10 +1,9 @@ use criterion::{Criterion, criterion_group, criterion_main}; -use eyre::Result; use tinywasm::engine::Config; -use tinywasm::{Engine, FuncContext, HostFunction, Imports, ModuleInstance, Store, types}; +use tinywasm::{Engine, FuncContext, HostFunction, Imports, ModuleInstance, Result, Store, types}; use types::Module; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); +const WASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); fn tinywasm_parse() -> Result { let parser = tinywasm_parser::Parser::default(); diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/benches/tinywasm_modes.rs similarity index 94% rename from crates/tinywasm/benches/tinywasm_modes.rs rename to benches/tinywasm_modes.rs index 6c1badf..7d1d993 100644 --- a/crates/tinywasm/benches/tinywasm_modes.rs +++ b/benches/tinywasm_modes.rs @@ -1,10 +1,11 @@ use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; -use eyre::Result; use tinywasm::engine::{Config, FuelPolicy}; use tinywasm::types::Module; -use tinywasm::{Engine, ExecProgress, FuncContext, FunctionTyped, HostFunction, Imports, ModuleInstance, Store}; +use tinywasm::{ + Engine, ExecProgress, FuncContext, FunctionTyped, HostFunction, Imports, ModuleInstance, Result, Store, +}; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); +const WASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); const FUEL_PER_ROUND: u32 = 512; const TIME_BUDGET_PER_ROUND: core::time::Duration = core::time::Duration::from_micros(50); const BENCH_MEASUREMENT_TIME: core::time::Duration = core::time::Duration::from_secs(10); diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 17b56e6..1aae437 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -23,12 +23,14 @@ path = "src/bin.rs" [dependencies] anstream = { version = "1.0" } +anyhow.workspace = true clap = { version = "4.6", features = ["derive"] } clap_complete = "4.6" -eyre.workspace = true log.workspace = true owo-colors = { workspace = true } pretty_env_logger.workspace = true +serde = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } tinywasm = { workspace = true, features = [ "archive", "canonicalize-nans", @@ -39,6 +41,7 @@ tinywasm = { workspace = true, features = [ "std", "validate", ] } +wasm-testsuite = { workspace = true, optional = true } wast = { workspace = true, optional = true } wat = { workspace = true, optional = true } @@ -49,5 +52,6 @@ tempfile = "3.27" [features] default = ["wast", "wat"] +tests = ["dep:serde", "dep:serde_json", "dep:wasm-testsuite", "wast"] wast = ["dep:wast"] wat = ["dep:wat"] diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 592fda7..d630fcf 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -1,5 +1,5 @@ +use anyhow::Result; use clap::Parser; -use eyre::Result; use tinywasm_cli::{Cli, run_cli}; fn main() -> Result<()> { diff --git a/crates/cli/src/cmd/compile.rs b/crates/cli/src/cmd/compile.rs index 93e38d5..c7007ee 100644 --- a/crates/cli/src/cmd/compile.rs +++ b/crates/cli/src/cmd/compile.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use crate::cli::CompileArgs; use crate::load::{default_twasm_output_path, load_compilable_module, write_output_bytes}; diff --git a/crates/cli/src/cmd/completion.rs b/crates/cli/src/cmd/completion.rs index 5815f57..b3aec7c 100644 --- a/crates/cli/src/cmd/completion.rs +++ b/crates/cli/src/cmd/completion.rs @@ -1,7 +1,7 @@ use std::io; +use anyhow::Result; use clap::CommandFactory; -use eyre::Result; use crate::cli::{Cli, CompletionArgs}; diff --git a/crates/cli/src/cmd/dump.rs b/crates/cli/src/cmd/dump.rs index 7a88cc2..da98a0b 100644 --- a/crates/cli/src/cmd/dump.rs +++ b/crates/cli/src/cmd/dump.rs @@ -1,5 +1,5 @@ use anstream::println; -use eyre::Result; +use anyhow::Result; use owo_colors::OwoColorize; use tinywasm::types::{ExternalKind, ImportKind}; diff --git a/crates/cli/src/cmd/inspect.rs b/crates/cli/src/cmd/inspect.rs index ea2cec7..cf7cb5f 100644 --- a/crates/cli/src/cmd/inspect.rs +++ b/crates/cli/src/cmd/inspect.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use crate::cli::ModuleInputArgs; use crate::load::load_module; diff --git a/crates/cli/src/cmd/run.rs b/crates/cli/src/cmd/run.rs index 06f29f1..3d25641 100644 --- a/crates/cli/src/cmd/run.rs +++ b/crates/cli/src/cmd/run.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::types::ExportType; use tinywasm::{ModuleInstance, Store}; @@ -8,7 +8,7 @@ use crate::output::print_results; use crate::value_parse::parse_invocation_args; pub fn run(args: RunArgs) -> Result<()> { - let module_path = args.module.as_deref().ok_or_else(|| eyre::eyre!("missing module path"))?; + let module_path = args.module.as_deref().ok_or_else(|| anyhow::anyhow!("missing module path"))?; let loaded = load_module(module_path)?; let mut store = Store::new(args.engine.build_engine()?); let instance = ModuleInstance::instantiate_no_start(&mut store, &loaded.module, None)?; @@ -26,7 +26,7 @@ pub fn run(args: RunArgs) -> Result<()> { (true, ExportType::Func(ty)) => Some(ty), _ => None, }) - .ok_or_else(|| eyre::eyre!("export is not a function: {export}"))?; + .ok_or_else(|| anyhow::anyhow!("export is not a function: {export}"))?; let func = instance.func_untyped(&store, export)?; let params = parse_invocation_args(func_ty, &args.args)?; let results = func.call(&mut store, ¶ms)?; @@ -40,7 +40,7 @@ pub fn run(args: RunArgs) -> Result<()> { } let start = instance.func_untyped(&store, "_start").map_err(|_| { - eyre::eyre!( + anyhow::anyhow!( "module has no start function or `_start` export. Use `tinywasm inspect {module_path}` or `tinywasm run --invoke {module_path}`" ) })?; diff --git a/crates/cli/src/cmd/wast.rs b/crates/cli/src/cmd/wast.rs index ef9c263..c669a0a 100644 --- a/crates/cli/src/cmd/wast.rs +++ b/crates/cli/src/cmd/wast.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use eyre::Result; +use anyhow::Result; use crate::cli::WastArgs; use crate::wast_runner::WastRunner; diff --git a/crates/cli/src/engine_flags.rs b/crates/cli/src/engine_flags.rs index c8df1fe..3d90847 100644 --- a/crates/cli/src/engine_flags.rs +++ b/crates/cli/src/engine_flags.rs @@ -1,5 +1,5 @@ +use anyhow::{Result, bail}; use clap::{Args, ValueEnum}; -use eyre::{Result, bail}; use tinywasm::{Engine, StackConfig, engine::FuelPolicy}; #[derive(Args, Clone, Default)] diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index bef6b72..877bfe1 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -3,12 +3,14 @@ pub mod cmd; pub mod engine_flags; pub mod load; pub mod output; +#[cfg(feature = "tests")] +pub mod testsuite; pub mod value_parse; #[cfg(feature = "wast")] pub mod wast_runner; +use anyhow::Result; use clap::CommandFactory; -use eyre::Result; pub use cli::{Cli, Commands}; diff --git a/crates/cli/src/load.rs b/crates/cli/src/load.rs index 9f7c616..12c0e1f 100644 --- a/crates/cli/src/load.rs +++ b/crates/cli/src/load.rs @@ -2,7 +2,7 @@ use std::ffi::OsStr; use std::io::{Read, Write}; use std::path::Path; -use eyre::{Context, Result, bail}; +use anyhow::{Context, Result, bail}; use tinywasm::Module; #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/cli/src/testsuite.rs similarity index 81% rename from crates/tinywasm/tests/testsuite/mod.rs rename to crates/cli/src/testsuite.rs index ea44c29..032aa88 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/cli/src/testsuite.rs @@ -1,11 +1,22 @@ -#![allow(dead_code)] - -use eyre::{Result, eyre}; +use crate::wast_runner::{GroupResult, TestFile as RunnerTestFile, WastRunner}; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::io::{BufRead, BufReader, Seek, SeekFrom}; -use tinywasm_cli::wast_runner::{GroupResult, TestFile as RunnerTestFile, WastRunner}; + +/// Result type used by the WAST test utilities. +pub type TestResult = core::result::Result>; + +#[derive(Debug)] +struct TestFailure(String); + +impl Display for TestFailure { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl core::error::Error for TestFailure {} #[derive(Serialize, Deserialize)] pub struct TestGroupResult { @@ -27,7 +38,7 @@ impl TestSuite { Self { runner: WastRunner::new() } } - pub fn run_paths(&mut self, tests: &[std::path::PathBuf]) -> Result<()> { + pub fn run_paths(&mut self, tests: &[std::path::PathBuf]) -> TestResult<()> { let mut files = Vec::new(); for path in tests { if path.is_dir() { @@ -51,38 +62,43 @@ impl TestSuite { let name = path.to_string_lossy().into_owned(); Ok((name, contents)) }) - .collect::>>()?; + .collect::>>()?; self.runner.run_files(runner_files.iter().map(|(name, contents)| RunnerTestFile { name: name.clone(), parent: name.clone(), contents, - })) + }))?; + Ok(()) } - pub fn run_files<'a>(&mut self, tests: impl IntoIterator>) -> Result<()> { + pub fn run_files<'a>( + &mut self, + tests: impl IntoIterator>, + ) -> TestResult<()> { self.runner.run_files(tests.into_iter().map(|file| RunnerTestFile { name: file.name().to_string(), parent: file.parent().to_string(), contents: file.raw(), - })) + }))?; + Ok(()) } pub fn print_errors(&self) { self.runner.print_errors(); } - pub fn report_status(&self) -> Result<()> { + pub fn report_status(&self) -> TestResult<()> { if self.runner.failed() { println!(); - Err(eyre!(format!("{}:\n{self}", "failed one or more tests".red().bold()))) + Err(TestFailure(format!("{}:\n{self}", "failed one or more tests".red().bold())).into()) } else { println!("{self}"); Ok(()) } } - pub fn save_csv(&self, path: &str, version: &str) -> Result<()> { + pub fn save_csv(&self, path: &str, version: &str) -> TestResult<()> { use std::fs::OpenOptions; use std::io::Write; diff --git a/crates/cli/src/value_parse.rs b/crates/cli/src/value_parse.rs index eabbdc4..3191eaf 100644 --- a/crates/cli/src/value_parse.rs +++ b/crates/cli/src/value_parse.rs @@ -1,4 +1,4 @@ -use eyre::{Result, bail}; +use anyhow::{Result, bail}; use tinywasm::types::{FuncType, WasmType, WasmValue}; use crate::output::format_wasm_type; @@ -33,8 +33,8 @@ fn parse_arg(index: usize, ty: WasmType, value: &str) -> Result { Ok(parsed) } -fn format_error(index: usize, ty: WasmType, value: &str, error: impl core::fmt::Display) -> eyre::Report { - eyre::eyre!("failed to parse argument {} as {} from `{value}`: {error}", index + 1, format_wasm_type(ty)) +fn format_error(index: usize, ty: WasmType, value: &str, error: impl core::fmt::Display) -> anyhow::Error { + anyhow::anyhow!("failed to parse argument {} as {} from `{value}`: {error}", index + 1, format_wasm_type(ty)) } #[cfg(test)] diff --git a/crates/cli/src/wast_runner.rs b/crates/cli/src/wast_runner.rs index 24483b6..1b8ae9c 100644 --- a/crates/cli/src/wast_runner.rs +++ b/crates/cli/src/wast_runner.rs @@ -5,7 +5,7 @@ use std::panic::{self, AssertUnwindSafe}; use std::path::PathBuf; use std::time::Duration; -use eyre::{Context, Result, bail, eyre}; +use anyhow::{Context, Result, anyhow, bail}; use log::{debug, error}; use tinywasm::types::{ AbstractHeapType as TinyAbstractHeapType, AnyRef, ExternRef, FuncRef, MemoryType, RefType, RefValue, TableType, @@ -111,7 +111,7 @@ impl WastRunner { self.print_errors(); if self.failed() { anstream::println!("{self}"); - Err(eyre!("failed one or more tests")) + Err(anyhow!("failed one or more tests")) } else { anstream::println!("{self}"); Ok(()) @@ -238,7 +238,7 @@ impl WastRunner { test_group.add_result( &format!("Register({i})"), span.linecol_in(wast_raw), - Err(eyre!("module instance to register was not found")), + Err(anyhow!("module instance to register was not found")), ); continue; } @@ -251,7 +251,7 @@ impl WastRunner { .expect("failed to instantiate module"); (name, instance) }) - .map_err(|e| eyre!("failed to parse wat module: {}", try_downcast_panic(e))); + .map_err(|e| anyhow!("failed to parse wat module: {}", try_downcast_panic(e))); match &result { Err(err) => debug!("failed to parse module: {err:?}"), @@ -263,7 +263,7 @@ impl WastRunner { ModuleDefinition(module) => { let result = catch_unwind_silent(|| parse_quote_module(module).expect("failed to parse module definition")) - .map_err(|err| eyre!("failed to parse module definition: {}", try_downcast_panic(err))); + .map_err(|err| anyhow!("failed to parse module definition: {}", try_downcast_panic(err))); if let Ok((name, module)) = &result { module_registry.define(module.clone(), name.clone()); @@ -280,10 +280,10 @@ impl WastRunner { let result = catch_unwind_silent(|| { let module = module_registry .definition(module) - .ok_or_else(|| eyre!("module definition was not found"))?; + .ok_or_else(|| anyhow!("module definition was not found"))?; Self::instantiate_module(&mut store, &module_registry, &module) }) - .map_err(|err| eyre!("failed to instantiate module definition: {}", try_downcast_panic(err))) + .map_err(|err| anyhow!("failed to instantiate module definition: {}", try_downcast_panic(err))) .and_then(|result| result); if let Ok(instance) = &result { @@ -301,7 +301,7 @@ impl WastRunner { continue; }; let res = catch_unwind_silent(|| parse_module_bytes(&encoded)) - .map_err(|e| eyre!("failed to parse module (expected): {}", try_downcast_panic(e))) + .map_err(|e| anyhow!("failed to parse module (expected): {}", try_downcast_panic(e))) .and_then(|res| res); test_group.add_result( &format!("AssertMalformed({i})"), @@ -314,7 +314,7 @@ impl WastRunner { { continue; } - Err(eyre!("expected module to be malformed: {message}")) + Err(anyhow!("expected module to be malformed: {message}")) } Err(_) => Ok(()), }, @@ -326,13 +326,13 @@ impl WastRunner { continue; } let res = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module (invalid): {}", try_downcast_panic(e))) + .map_err(|e| anyhow!("failed to parse module (invalid): {}", try_downcast_panic(e))) .and_then(|res| res); test_group.add_result( &format!("AssertInvalid({i})"), span.linecol_in(wast_raw), match res { - Ok(_) => Err(eyre!("expected module to be invalid")), + Ok(_) => Err(anyhow!("expected module to be invalid")), Err(_) => Ok(()), }, ); @@ -346,7 +346,7 @@ impl WastRunner { test_group.add_result( &format!("AssertExhaustion({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected trap")), + Err(anyhow!("expected trap")), ); continue; }; @@ -354,7 +354,7 @@ impl WastRunner { test_group.add_result( &format!("AssertExhaustion({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected trap: {}, got: {}", message, trap.message())), + Err(anyhow!("expected trap: {}, got: {}", message, trap.message())), ); continue; } @@ -382,14 +382,14 @@ impl WastRunner { Err(err) => test_group.add_result( &format!("AssertTrap({i})"), span.linecol_in(wast_raw), - Err(eyre!("test panicked: {}", try_downcast_panic(err))), + Err(anyhow!("test panicked: {}", try_downcast_panic(err))), ), Ok(Err(tinywasm::Error::Trap(trap))) => { if !message.starts_with(trap.message()) && !trap.message().starts_with(message) { test_group.add_result( &format!("AssertTrap({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected trap: {}, got: {}", message, trap.message())), + Err(anyhow!("expected trap: {}, got: {}", message, trap.message())), ); continue; } @@ -398,12 +398,12 @@ impl WastRunner { Ok(Err(err)) => test_group.add_result( &format!("AssertTrap({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected trap, {}, got: {:?}", message, err)), + Err(anyhow!("expected trap, {}, got: {:?}", message, err)), ), Ok(Ok(())) => test_group.add_result( &format!("AssertTrap({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected trap {}, got Ok", message)), + Err(anyhow!("expected trap {}, got Ok", message)), ), } } @@ -426,10 +426,10 @@ impl WastRunner { exec_fn_instance(module, &mut store, invoke.name, &args).map(|_| ()) }); let result = match res { - Err(err) => Err(eyre!("test panicked: {}", try_downcast_panic(err))), + Err(err) => Err(anyhow!("test panicked: {}", try_downcast_panic(err))), Ok(Err(tinywasm::Error::Exception(_))) => Ok(()), - Ok(Err(err)) => Err(eyre!("expected exception, got: {err:?}")), - Ok(Ok(())) => Err(eyre!("expected exception, got Ok")), + Ok(Err(err)) => Err(anyhow!("expected exception, got: {err:?}")), + Ok(Ok(())) => Err(anyhow!("expected exception, got Ok")), }; test_group.add_result(&format!("AssertException({i})"), span.linecol_in(wast_raw), result); } @@ -444,7 +444,7 @@ impl WastRunner { Err(err) => test_group.add_result( &format!("AssertUnlinkable({i})"), span.linecol_in(wast_raw), - Err(eyre!("test panicked: {}", try_downcast_panic(err))), + Err(anyhow!("test panicked: {}", try_downcast_panic(err))), ), Ok(Err(tinywasm::Error::Linker(err))) => { if err.message() != message @@ -454,7 +454,7 @@ impl WastRunner { test_group.add_result( &format!("AssertUnlinkable({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected linker error: {}, got: {}", message, err.message())), + Err(anyhow!("expected linker error: {}, got: {}", message, err.message())), ); continue; } @@ -463,12 +463,12 @@ impl WastRunner { Ok(Err(err)) => test_group.add_result( &format!("AssertUnlinkable({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected linker error, {}, got: {:?}", message, err)), + Err(anyhow!("expected linker error, {}, got: {:?}", message, err)), ), Ok(Ok(_)) => test_group.add_result( &format!("AssertUnlinkable({i})"), span.linecol_in(wast_raw), - Err(eyre!("expected linker error {}, got Ok", message)), + Err(anyhow!("expected linker error {}, got Ok", message)), ), } } @@ -483,7 +483,7 @@ impl WastRunner { })?; Ok(()) }); - let res = res.map_err(|e| eyre!("test panicked: {}", try_downcast_panic(e))).and_then(|r| r); + let res = res.map_err(|e| anyhow!("test panicked: {}", try_downcast_panic(e))).and_then(|r| r); test_group.add_result(&format!("Invoke({name}-{i})"), span.linecol_in(wast_raw), res); } AssertReturn { span, exec, results } => { @@ -492,7 +492,7 @@ impl WastRunner { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!("failed to convert expected results: {err:?}")), + Err(anyhow!("failed to convert expected results: {err:?}")), ); continue; } @@ -500,13 +500,13 @@ impl WastRunner { }; let invoke = match match exec { - wast::WastExecute::Wat(_) => Err(eyre!("wat not supported")), + wast::WastExecute::Wat(_) => Err(anyhow!("wat not supported")), wast::WastExecute::Get { module: module_id, global, .. } => { let Some(module) = module_registry.get(module_id) else { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!("no module to get global from")), + Err(anyhow!("no module to get global from")), ); continue; }; @@ -516,7 +516,7 @@ impl WastRunner { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!("failed to get global: {err:?}")), + Err(anyhow!("failed to get global: {err:?}")), ); continue; } @@ -529,7 +529,7 @@ impl WastRunner { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!( + Err(anyhow!( "global value did not match any expected alternative: {:?}", module_global )), @@ -550,7 +550,7 @@ impl WastRunner { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!("unsupported directive: {err:?}")), + Err(anyhow!("unsupported directive: {err:?}")), ); continue; } @@ -565,7 +565,7 @@ impl WastRunner { e })?; if !expected_alternatives.iter().any(|expected| expected.len() == outcomes.len()) { - return Err(eyre!( + return Err(anyhow!( "expected {} results, got {}", expected_alternatives.first().map_or(0, |v| v.len()), outcomes.len() @@ -580,17 +580,17 @@ impl WastRunner { }) { Ok(()) } else { - Err(eyre!("results did not match any expected alternative")) + Err(anyhow!("results did not match any expected alternative")) } }); - let res = res.map_err(|e| eyre!("test panicked: {}", try_downcast_panic(e))).and_then(|r| r); + let res = res.map_err(|e| anyhow!("test panicked: {}", try_downcast_panic(e))).and_then(|r| r); test_group.add_result(&format!("AssertReturn({invoke_name}-{i})"), span.linecol_in(wast_raw), res); } _ => test_group.add_result( &format!("Unknown({i})"), span.linecol_in(wast_raw), - Err(eyre!("unsupported directive")), + Err(anyhow!("unsupported directive")), ), } } @@ -828,7 +828,7 @@ fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { I32(i) => WasmValue::I32(i), I64(i) => WasmValue::I64(i), V128(i) => WasmValue::V128(i.to_le_bytes()), - RefExtern(v) => ExternRef::try_new(v).ok_or_else(|| eyre!("external reference address is too large"))?.into(), + RefExtern(v) => ExternRef::try_new(v).ok_or_else(|| anyhow!("external reference address is too large"))?.into(), RefNull(t) => match t { wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func } => RefValue::Null.into(), wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern | AbstractHeapType::Any } => { @@ -839,7 +839,8 @@ fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { } }, RefHost(value) => { - RefValue::Any(AnyRef::from_host(value).ok_or_else(|| eyre!("host reference address is too large"))?).into() + RefValue::Any(AnyRef::from_host(value).ok_or_else(|| anyhow!("host reference address is too large"))?) + .into() } }) } @@ -918,7 +919,7 @@ fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result ExpectedValue::Exact(WasmValue::V128(wast_v128_to_bytes(i))), RefNull(_) => ExpectedValue::RefNull, RefExtern(Some(v)) => ExpectedValue::Exact( - ExternRef::try_new(v).ok_or_else(|| eyre!("external reference address is too large"))?.into(), + ExternRef::try_new(v).ok_or_else(|| anyhow!("external reference address is too large"))?.into(), ), RefExtern(None) => ExpectedValue::RefExtern, RefFunc(Some(wast::token::Index::Num(n, _))) => ExpectedValue::Exact(FuncRef::new(n).into()), @@ -932,7 +933,8 @@ fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result ExpectedValue::RefStruct, RefArray => ExpectedValue::RefArray, RefHost(value) => ExpectedValue::Exact( - RefValue::Any(AnyRef::from_host(value).ok_or_else(|| eyre!("host reference address is too large"))?).into(), + RefValue::Any(AnyRef::from_host(value).ok_or_else(|| anyhow!("host reference address is too large"))?) + .into(), ), a => { bail!("unsupported arg type {:?}", a); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 65ef7f8..eb5dd94 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -85,26 +85,6 @@ name = "test-wasm-simd" harness = false name = "test-wasm-wide-arithmetic" -[[bench]] -harness = false -name = "argon2id" - -[[bench]] -harness = false -name = "fibonacci" - -[[bench]] -harness = false -name = "tinywasm" - -[[bench]] -harness = false -name = "tinywasm_modes" - -[[bench]] -harness = false -name = "memory_backends" - [dependencies] libm = { version = "0.2", default-features = false } log = { workspace = true, optional = true } @@ -112,12 +92,7 @@ tinywasm-parser = { workspace = true, optional = true } tinywasm-types = { workspace = true } [dev-dependencies] -criterion.workspace = true -eyre.workspace = true -owo-colors.workspace = true -serde.workspace = true -serde_json.workspace = true -tinywasm-cli = { path = "../cli", features = ["wast", "wat"] } +tinywasm-cli = { path = "../cli", features = ["tests", "wat"] } wasm-testsuite.workspace = true wat.workspace = true diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 35e0d9b..128f1ed 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -1,8 +1,6 @@ -use eyre::Result; use std::fmt::Write; -use tinywasm::types::{FuncType, RefType, RefValue, WasmType, WasmValue}; +use tinywasm::types::{ExternRef, FuncType, RefType, RefValue, WasmType, WasmValue}; use tinywasm::{FuncContext, HostFunction, Imports, Module, ModuleInstance, Store}; -use tinywasm_types::ExternRef; const VAL_LISTS: &[&[WasmValue]] = &[ &[], @@ -27,7 +25,7 @@ fn module_cases() -> Vec<(Module, FuncType, Vec)> { } #[test] -fn test_return_invalid_type() -> Result<()> { +fn test_return_invalid_type() -> Result<(), Box> { let cases = module_cases(); for (module, ty, args) in cases { @@ -51,7 +49,7 @@ fn test_return_invalid_type() -> Result<()> { } #[test] -fn test_linking_invalid_untyped_func() -> Result<()> { +fn test_linking_invalid_untyped_func() -> Result<(), Box> { let cases = module_cases(); for (module, expected_func_ty, _) in &cases { for (_, ty, _) in &cases { @@ -69,7 +67,7 @@ fn test_linking_invalid_untyped_func() -> Result<()> { } #[test] -fn test_linking_invalid_typed_func() -> Result<()> { +fn test_linking_invalid_typed_func() -> Result<(), Box> { type Existing = (i32, i32, f64); type NonMatchingSingle = f64; type NonMatchingTuple = (f64, i32, i32); @@ -107,7 +105,7 @@ fn test_linking_invalid_typed_func() -> Result<()> { } #[test] -fn concrete_host_references_use_canonical_types() -> Result<()> { +fn concrete_host_references_use_canonical_types() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -145,7 +143,7 @@ fn concrete_host_references_use_canonical_types() -> Result<()> { } #[test] -fn imported_host_functions_resolve_concrete_types() -> Result<()> { +fn imported_host_functions_resolve_concrete_types() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -173,7 +171,7 @@ fn imported_host_functions_resolve_concrete_types() -> Result<()> { } #[test] -fn host_tail_calls_return_from_the_current_frame() -> Result<()> { +fn host_tail_calls_return_from_the_current_frame() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -210,7 +208,7 @@ fn host_tail_calls_return_from_the_current_frame() -> Result<()> { } #[test] -fn host_calls_reject_unknown_function_references() -> Result<()> { +fn host_calls_reject_unknown_function_references() -> Result<(), Box> { let mut store = Store::default(); let ty = FuncType::new(&[WasmType::Ref(tinywasm::types::RefType::FUNCREF)], &[]); let host = HostFunction::from_untyped(&ty, |_, _| Ok(Vec::new())).instantiate(&mut store)?; diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs index 1d5db1b..a65c8a4 100644 --- a/crates/tinywasm/tests/import_linking.rs +++ b/crates/tinywasm/tests/import_linking.rs @@ -1,4 +1,3 @@ -use eyre::Result; use tinywasm::{Error, Imports, Module, ModuleInstance, Store, Trap}; const WASM_ADD: &str = r#" @@ -19,14 +18,14 @@ const WASM_IMPORT: &str = r#" call $add)) "#; -fn parse_modules() -> Result<(Module, Module)> { +fn parse_modules() -> Result<(Module, Module), Box> { let add = tinywasm::parse_bytes(&wat::parse_str(WASM_ADD)?)?; let import = tinywasm::parse_bytes(&wat::parse_str(WASM_IMPORT)?)?; Ok((add, import)) } #[test] -fn link_module_links_same_store_instance() -> Result<()> { +fn link_module_links_same_store_instance() -> Result<(), Box> { let (add_module, import_module) = parse_modules()?; let mut store = Store::default(); @@ -41,7 +40,7 @@ fn link_module_links_same_store_instance() -> Result<()> { } #[test] -fn link_module_rejects_cross_store_instance() -> Result<()> { +fn link_module_rejects_cross_store_instance() -> Result<(), Box> { let (add_module, import_module) = parse_modules()?; let mut source_store = Store::default(); diff --git a/crates/tinywasm/tests/imported_table_init.rs b/crates/tinywasm/tests/imported_table_init.rs index ca52e9e..aaa781b 100644 --- a/crates/tinywasm/tests/imported_table_init.rs +++ b/crates/tinywasm/tests/imported_table_init.rs @@ -1,9 +1,8 @@ -use eyre::Result; use tinywasm::types::{FuncRef, RefType, TableType}; use tinywasm::{HostFunction, Imports, ModuleInstance, Store, Table}; #[test] -fn imported_table_uses_provided_init_value() -> Result<()> { +fn imported_table_uses_provided_init_value() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index 196ad0a..07cbde2 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -1,4 +1,3 @@ -use eyre::Result; use tinywasm::types::WasmValue; #[cfg(feature = "guest-debug")] use tinywasm::types::{FuncRef, RefValue}; @@ -6,7 +5,7 @@ use tinywasm::{ExternItem, ModuleInstance, Store}; #[test] #[cfg(feature = "guest-debug")] -fn private_items_are_accessible_by_index() -> Result<()> { +fn private_items_are_accessible_by_index() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -42,7 +41,7 @@ fn private_items_are_accessible_by_index() -> Result<()> { } #[test] -fn exported_tables_and_globals_have_handle_and_helper_apis() -> Result<()> { +fn exported_tables_and_globals_have_handle_and_helper_apis() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -73,7 +72,7 @@ fn exported_tables_and_globals_have_handle_and_helper_apis() -> Result<()> { } #[test] -fn extern_item_lookup_returns_expected_kinds() -> Result<()> { +fn extern_item_lookup_returns_expected_kinds() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -98,7 +97,7 @@ fn extern_item_lookup_returns_expected_kinds() -> Result<()> { } #[test] -fn extern_item_and_exports_use_actual_function_type() -> Result<()> { +fn extern_item_and_exports_use_actual_function_type() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -134,7 +133,7 @@ fn extern_item_and_exports_use_actual_function_type() -> Result<()> { } #[test] -fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<()> { +fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -177,7 +176,7 @@ fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<() } #[test] -fn start_resolves_module_func_index_to_store_addr() -> Result<()> { +fn start_resolves_module_func_index_to_store_addr() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/crates/tinywasm/tests/memory_backends.rs b/crates/tinywasm/tests/memory_backends.rs index 0cf5707..b7f9438 100644 --- a/crates/tinywasm/tests/memory_backends.rs +++ b/crates/tinywasm/tests/memory_backends.rs @@ -6,13 +6,14 @@ use core::sync::atomic::{AtomicUsize, Ordering}; #[cfg(feature = "std")] use std::io::{Read, Seek, SeekFrom, Write}; -use eyre::Result; use tinywasm::engine::Config; use tinywasm::types::{MemoryArch, MemoryType}; use tinywasm::{Engine, Memory, MemoryBackend, Module, ModuleInstance, PagedMemory, Store}; use tinywasm_parser::{Parser, ParserOptions}; -fn instantiate_module_with_counting_backend(module: Module) -> Result { +type TestResult = Result>; + +fn instantiate_module_with_counting_backend(module: Module) -> TestResult { let created = Arc::new(AtomicUsize::new(0)); let factory_calls = created.clone(); let backend = MemoryBackend::custom(move |ty| { @@ -27,7 +28,7 @@ fn instantiate_module_with_counting_backend(module: Module) -> Result { Ok(created.load(Ordering::Relaxed)) } -fn instantiate_with_counting_backend(wat: &str) -> Result { +fn instantiate_with_counting_backend(wat: &str) -> TestResult { let wasm = wat::parse_str(wat)?; let module = tinywasm::parse_bytes(&wasm)?; instantiate_module_with_counting_backend(module) @@ -35,7 +36,7 @@ fn instantiate_with_counting_backend(wat: &str) -> Result { fn instantiate_exported_memory_with_counting_backend( wat: &str, -) -> Result<(Store, tinywasm::ModuleInstance, Arc)> { +) -> TestResult<(Store, tinywasm::ModuleInstance, Arc)> { let wasm = wat::parse_str(wat)?; let module = tinywasm::parse_bytes(&wasm)?; let created = Arc::new(AtomicUsize::new(0)); @@ -51,7 +52,7 @@ fn instantiate_exported_memory_with_counting_backend( } #[test] -fn paged_backend_works_for_module_memories() -> Result<()> { +fn paged_backend_works_for_module_memories() -> TestResult { let wasm = wat::parse_str( r#" (module @@ -73,7 +74,7 @@ fn paged_backend_works_for_module_memories() -> Result<()> { } #[test] -fn custom_backend_factory_is_used_for_host_memories() -> Result<()> { +fn custom_backend_factory_is_used_for_host_memories() -> TestResult { let created = Arc::new(AtomicUsize::new(0)); let seen_page_size = Arc::new(AtomicUsize::new(0)); let factory_calls = created.clone(); @@ -100,7 +101,7 @@ fn custom_backend_factory_is_used_for_host_memories() -> Result<()> { } #[test] -fn local_memory_without_observable_use_is_not_allocated() -> Result<()> { +fn local_memory_without_observable_use_is_not_allocated() -> TestResult { let created = instantiate_with_counting_backend( r#" (module @@ -115,7 +116,7 @@ fn local_memory_without_observable_use_is_not_allocated() -> Result<()> { } #[test] -fn exported_local_memory_is_not_eagerly_allocated() -> Result<()> { +fn exported_local_memory_is_not_eagerly_allocated() -> TestResult { let created = instantiate_with_counting_backend( r#" (module @@ -129,7 +130,7 @@ fn exported_local_memory_is_not_eagerly_allocated() -> Result<()> { } #[test] -fn exported_local_memory_reads_zeroes_without_materializing() -> Result<()> { +fn exported_local_memory_reads_zeroes_without_materializing() -> TestResult { let (mut store, instance, created) = instantiate_exported_memory_with_counting_backend( r#" (module @@ -151,7 +152,7 @@ fn exported_local_memory_reads_zeroes_without_materializing() -> Result<()> { } #[test] -fn active_data_segment_on_local_memory_is_allocated() -> Result<()> { +fn active_data_segment_on_local_memory_is_allocated() -> TestResult { let created = instantiate_with_counting_backend( r#" (module @@ -166,7 +167,7 @@ fn active_data_segment_on_local_memory_is_allocated() -> Result<()> { } #[test] -fn local_memory_instruction_is_allocated() -> Result<()> { +fn local_memory_instruction_is_allocated() -> TestResult { let created = instantiate_with_counting_backend( r#" (module @@ -181,7 +182,7 @@ fn local_memory_instruction_is_allocated() -> Result<()> { } #[test] -fn disabled_local_memory_allocation_optimization_keeps_old_behavior() -> Result<()> { +fn disabled_local_memory_allocation_optimization_keeps_old_behavior() -> TestResult { let wasm = wat::parse_str( r#" (module @@ -200,7 +201,7 @@ fn disabled_local_memory_allocation_optimization_keeps_old_behavior() -> Result< } #[test] -fn read_returns_short_count_at_end_of_memory() -> Result<()> { +fn read_returns_short_count_at_end_of_memory() -> TestResult { let mut store = Store::default(); let memory = Memory::new(&mut store, MemoryType::new(MemoryArch::I32, 1, Some(1), Some(4)))?; memory.copy_from_slice(&mut store, 0, &[1, 2, 3, 4])?; @@ -214,7 +215,7 @@ fn read_returns_short_count_at_end_of_memory() -> Result<()> { } #[test] -fn paged_read_and_write_stop_at_chunk_boundaries() -> Result<()> { +fn paged_read_and_write_stop_at_chunk_boundaries() -> TestResult { let engine = Engine::new(Config::new().with_memory_backend(MemoryBackend::paged(4))); let mut store = Store::new(engine); let memory = Memory::new(&mut store, MemoryType::new(MemoryArch::I32, 1, Some(1), Some(16)))?; @@ -241,7 +242,7 @@ fn paged_read_and_write_stop_at_chunk_boundaries() -> Result<()> { #[cfg(feature = "std")] #[test] -fn memory_cursor_supports_read_write_and_seek() -> Result<()> { +fn memory_cursor_supports_read_write_and_seek() -> TestResult { let mut store = Store::default(); let memory = Memory::new(&mut store, MemoryType::new(MemoryArch::I32, 1, Some(1), Some(8)))?; diff --git a/crates/tinywasm/tests/memory_ref_api.rs b/crates/tinywasm/tests/memory_ref_api.rs index 6f72477..768c873 100644 --- a/crates/tinywasm/tests/memory_ref_api.rs +++ b/crates/tinywasm/tests/memory_ref_api.rs @@ -1,8 +1,7 @@ -use eyre::Result; use tinywasm::{ModuleInstance, Store}; #[test] -fn memory_ref_mut_copy_within_uses_src_then_dst_order() -> Result<()> { +fn memory_ref_mut_copy_within_uses_src_then_dst_order() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/crates/tinywasm/tests/module_descriptors.rs b/crates/tinywasm/tests/module_descriptors.rs index 81ab5ab..8bd73b6 100644 --- a/crates/tinywasm/tests/module_descriptors.rs +++ b/crates/tinywasm/tests/module_descriptors.rs @@ -1,9 +1,7 @@ -use eyre::Result; -use tinywasm::types::WasmType; -use tinywasm_types::{ExportType, ImportType}; +use tinywasm::types::{ExportType, ImportType, WasmType}; #[test] -fn module_descriptors_resolve_imported_and_local_export_types() -> Result<()> { +fn module_descriptors_resolve_imported_and_local_export_types() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -91,7 +89,7 @@ fn module_descriptors_resolve_imported_and_local_export_types() -> Result<()> { } #[test] -fn module_descriptors_resolve_imported_and_local_table_and_memory_exports() -> Result<()> { +fn module_descriptors_resolve_imported_and_local_table_and_memory_exports() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs index 060cefb..2798e7b 100644 --- a/crates/tinywasm/tests/resume_execution.rs +++ b/crates/tinywasm/tests/resume_execution.rs @@ -1,6 +1,5 @@ -use eyre::Result; use tinywasm::engine::{Config, FuelPolicy}; -use tinywasm::{ExecProgress, ModuleInstance, types::WasmValue}; +use tinywasm::{ExecProgress, ModuleInstance, Result, types::WasmValue}; #[cfg(feature = "std")] use std::time::Duration; diff --git a/crates/tinywasm/tests/start_function.rs b/crates/tinywasm/tests/start_function.rs index 077c0fc..912b200 100644 --- a/crates/tinywasm/tests/start_function.rs +++ b/crates/tinywasm/tests/start_function.rs @@ -1,8 +1,7 @@ -use eyre::Result; use tinywasm::{ModuleInstance, Store}; #[test] -fn exported_wasi_start_is_not_run_during_instantiation() -> Result<()> { +fn exported_wasi_start_is_not_run_during_instantiation() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs index bcb8e07..a508817 100644 --- a/crates/tinywasm/tests/store_ownership.rs +++ b/crates/tinywasm/tests/store_ownership.rs @@ -1,4 +1,3 @@ -use eyre::Result; use tinywasm::types::{GlobalType, WasmType}; use tinywasm::{Global, Imports, ModuleInstance, Store}; @@ -13,7 +12,7 @@ const MODULE_WAT: &str = r#" "#; #[test] -fn func_handle_rejects_wrong_store() -> Result<()> { +fn func_handle_rejects_wrong_store() -> Result<(), Box> { let wasm = wat::parse_str(MODULE_WAT)?; let module = tinywasm::parse_bytes(&wasm)?; @@ -29,7 +28,7 @@ fn func_handle_rejects_wrong_store() -> Result<()> { } #[test] -fn memory_access_rejects_wrong_store() -> Result<()> { +fn memory_access_rejects_wrong_store() -> Result<(), Box> { let wasm = wat::parse_str(MODULE_WAT)?; let module = tinywasm::parse_bytes(&wasm)?; @@ -45,7 +44,7 @@ fn memory_access_rejects_wrong_store() -> Result<()> { } #[test] -fn global_access_rejects_wrong_store() -> Result<()> { +fn global_access_rejects_wrong_store() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -67,7 +66,7 @@ fn global_access_rejects_wrong_store() -> Result<()> { } #[test] -fn table_grow_rejects_wrong_store_with_invalid_store_error() -> Result<()> { +fn table_grow_rejects_wrong_store_with_invalid_store_error() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -89,7 +88,7 @@ fn table_grow_rejects_wrong_store_with_invalid_store_error() -> Result<()> { } #[test] -fn global_import_rejects_wrong_store() -> Result<()> { +fn global_import_rejects_wrong_store() -> Result<(), Box> { let wasm = wat::parse_str(r#"(module (import "env" "g" (global i32)))"#)?; let module = tinywasm::parse_bytes(&wasm)?; let mut owner_store = Store::default(); diff --git a/crates/tinywasm/tests/test-wasm-1.rs b/crates/tinywasm/tests/test-wasm-1.rs index be235ae..723f007 100644 --- a/crates/tinywasm/tests/test-wasm-1.rs +++ b/crates/tinywasm/tests/test-wasm-1.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-2.rs b/crates/tinywasm/tests/test-wasm-2.rs index 0379ca6..c762eec 100644 --- a/crates/tinywasm/tests/test-wasm-2.rs +++ b/crates/tinywasm/tests/test-wasm-2.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-3.rs b/crates/tinywasm/tests/test-wasm-3.rs index 11ebbc8..a843ae0 100644 --- a/crates/tinywasm/tests/test-wasm-3.rs +++ b/crates/tinywasm/tests/test-wasm-3.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-custom-page-sizes.rs b/crates/tinywasm/tests/test-wasm-custom-page-sizes.rs index e4aacfc..a71266a 100644 --- a/crates/tinywasm/tests/test-wasm-custom-page-sizes.rs +++ b/crates/tinywasm/tests/test-wasm-custom-page-sizes.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-custom.rs b/crates/tinywasm/tests/test-wasm-custom.rs index ae08142..b516428 100644 --- a/crates/tinywasm/tests/test-wasm-custom.rs +++ b/crates/tinywasm/tests/test-wasm-custom.rs @@ -1,8 +1,6 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let custom_dir = std::path::Path::new("./tests/wasm-custom"); diff --git a/crates/tinywasm/tests/test-wasm-gc.rs b/crates/tinywasm/tests/test-wasm-gc.rs index a80d347..86496c4 100644 --- a/crates/tinywasm/tests/test-wasm-gc.rs +++ b/crates/tinywasm/tests/test-wasm-gc.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-latest.rs b/crates/tinywasm/tests/test-wasm-latest.rs index af83c2f..649c44c 100644 --- a/crates/tinywasm/tests/test-wasm-latest.rs +++ b/crates/tinywasm/tests/test-wasm-latest.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-memory64.rs b/crates/tinywasm/tests/test-wasm-memory64.rs index 447bbfc..717ae03 100644 --- a/crates/tinywasm/tests/test-wasm-memory64.rs +++ b/crates/tinywasm/tests/test-wasm-memory64.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-multi-memory.rs b/crates/tinywasm/tests/test-wasm-multi-memory.rs index e4487f1..b0963b2 100644 --- a/crates/tinywasm/tests/test-wasm-multi-memory.rs +++ b/crates/tinywasm/tests/test-wasm-multi-memory.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-relaxed-simd.rs b/crates/tinywasm/tests/test-wasm-relaxed-simd.rs index 0851d18..92a5991 100644 --- a/crates/tinywasm/tests/test-wasm-relaxed-simd.rs +++ b/crates/tinywasm/tests/test-wasm-relaxed-simd.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-simd.rs b/crates/tinywasm/tests/test-wasm-simd.rs index d8f15ea..3d1932f 100644 --- a/crates/tinywasm/tests/test-wasm-simd.rs +++ b/crates/tinywasm/tests/test-wasm-simd.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-threads.rs b/crates/tinywasm/tests/test-wasm-threads.rs index 2fd5a9b..5cff235 100644 --- a/crates/tinywasm/tests/test-wasm-threads.rs +++ b/crates/tinywasm/tests/test-wasm-threads.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs b/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs index 2aa6b43..e4b5647 100644 --- a/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs +++ b/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs @@ -1,9 +1,7 @@ -mod testsuite; -use eyre::Result; -use testsuite::TestSuite; +use tinywasm_cli::testsuite::TestSuite; use wasm_testsuite::data::{Proposal, proposal}; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs index 92fcbd2..004bafa 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -1,12 +1,9 @@ -use std::path::PathBuf; - -use eyre::{Result, bail}; use tinywasm_cli::wast_runner::WastRunner; -fn main() -> Result<()> { - let args = std::env::args().collect::>(); - if args.len() < 2 { - bail!("usage: cargo test-wast "); +fn main() -> Result<(), Box> { + let Some(input) = std::env::args().nth(1) else { + eprintln!("usage: cargo test-wast "); + std::process::exit(2); }; let mut cwd = std::env::current_dir()?; @@ -15,8 +12,7 @@ fn main() -> Result<()> { cwd.pop(); } - // if its a folder, run all the wast files in the folder - let arg = PathBuf::from(cwd.clone()).join(&args[1]); + let arg = cwd.join(input); println!("running tests in {:?}", arg); let files = if arg.is_dir() { @@ -26,5 +22,6 @@ fn main() -> Result<()> { }; let mut test_suite = WastRunner::new(); - test_suite.run_paths(&files) + test_suite.run_paths(&files)?; + Ok(()) } diff --git a/crates/tinywasm/tests/typed_globals.rs b/crates/tinywasm/tests/typed_globals.rs index 9d88ec8..ac79f6a 100644 --- a/crates/tinywasm/tests/typed_globals.rs +++ b/crates/tinywasm/tests/typed_globals.rs @@ -1,9 +1,8 @@ -use eyre::Result; use tinywasm::types::{GlobalType, Instruction, RefValue, WasmType, WasmValue}; use tinywasm::{Global, Imports, ModuleInstance, Store}; #[test] -fn globals_use_typed_instructions_and_roundtrip_values() -> Result<()> { +fn globals_use_typed_instructions_and_roundtrip_values() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -70,7 +69,7 @@ fn globals_use_typed_instructions_and_roundtrip_values() -> Result<()> { } #[test] -fn imported_global_keeps_its_typed_store_address() -> Result<()> { +fn imported_global_keeps_its_typed_store_address() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/crates/tinywasm/tests/typed_lookup.rs b/crates/tinywasm/tests/typed_lookup.rs index 15b31b1..c5e052e 100644 --- a/crates/tinywasm/tests/typed_lookup.rs +++ b/crates/tinywasm/tests/typed_lookup.rs @@ -1,8 +1,7 @@ -use eyre::Result; use tinywasm::ModuleInstance; #[test] -fn func_typed_rejects_wrong_param_or_result_types() -> Result<()> { +fn func_typed_rejects_wrong_param_or_result_types() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module @@ -26,7 +25,7 @@ fn func_typed_rejects_wrong_param_or_result_types() -> Result<()> { } #[test] -fn func_typed_rejects_partial_multi_value_results() -> Result<()> { +fn func_typed_rejects_partial_multi_value_results() -> Result<(), Box> { let wasm = wat::parse_str( r#" (module diff --git a/examples/archive.rs b/examples/archive.rs index 7a2ebab..3f64704 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{Module, ModuleInstance, Store, parser::Parser}; const WASM: &str = r#" diff --git a/examples/doom/Cargo.toml b/examples/doom/Cargo.toml index 38e16c4..abec73d 100644 --- a/examples/doom/Cargo.toml +++ b/examples/doom/Cargo.toml @@ -7,7 +7,7 @@ publish = false [workspace] [dependencies] -eyre = "0.6" +anyhow = "1.0" log = "0.4" pretty_env_logger = "0.5" softbuffer = "0.4" diff --git a/examples/doom/src/main.rs b/examples/doom/src/main.rs index 095183d..c5e49b5 100644 --- a/examples/doom/src/main.rs +++ b/examples/doom/src/main.rs @@ -4,7 +4,7 @@ use std::num::NonZeroU32; use std::path::{Path, PathBuf}; use std::rc::Rc; -use eyre::{ContextCompat, Result, bail, eyre}; +use anyhow::{Context, Result, anyhow, bail}; use runtime::{Runtime, SCREEN_HEIGHT, SCREEN_WIDTH}; use softbuffer::{Context as SoftbufferContext, Surface}; use winit::application::ApplicationHandler; @@ -60,10 +60,10 @@ impl DoomApp { surface .resize(NonZeroU32::new(SCREEN_WIDTH as u32).unwrap(), NonZeroU32::new(SCREEN_HEIGHT as u32).unwrap()) - .map_err(|err| eyre!(err.to_string()))?; - let mut buffer = surface.buffer_mut().map_err(|err| eyre!(err.to_string()))?; + .map_err(|err| anyhow!(err.to_string()))?; + let mut buffer = surface.buffer_mut().map_err(|err| anyhow!(err.to_string()))?; self.runtime.write_framebuffer(&mut buffer)?; - buffer.present().map_err(|err| eyre!(err.to_string()))?; + buffer.present().map_err(|err| anyhow!(err.to_string()))?; Ok(()) } } @@ -129,7 +129,7 @@ impl ApplicationHandler for DoomApp { window.request_redraw(); } - if self.runtime.host_state.borrow().exit_code.is_some() { + if self.runtime.host_state.lock().unwrap().exit_code.is_some() { event_loop.exit(); } } diff --git a/examples/doom/src/runtime.rs b/examples/doom/src/runtime.rs index bee961c..2ad02ae 100644 --- a/examples/doom/src/runtime.rs +++ b/examples/doom/src/runtime.rs @@ -1,12 +1,11 @@ -use std::cell::RefCell; use std::collections::BTreeMap; use std::fs::{File, OpenOptions, create_dir_all}; use std::io::{Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::time::Instant; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; -use eyre::Result; +use anyhow::Result; use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; const IMPORT_MODULE: &str = "env"; @@ -21,15 +20,15 @@ pub struct Runtime { key_up: tinywasm::FunctionTyped, memory: tinywasm::Memory, framebuffer_bytes: Vec, - pub host_state: Rc>, + pub host_state: Arc>, } impl Runtime { pub fn new(wad_path: PathBuf, guest_path: PathBuf) -> Result { let module = tinywasm::parse_file(&guest_path)?; let mut store = Store::default(); - let host_state = Rc::new(RefCell::new(HostState::new(wad_path))); - let imports = build_imports(&mut store, host_state.clone()); + let host_state = Arc::new(Mutex::new(HostState::new(wad_path))); + let imports = build_imports(host_state.clone()); let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; let wad_path_buf = instance.func::<(), i32>(&store, "tinywasm_doom_wad_path_buf")?; @@ -41,7 +40,7 @@ impl Runtime { let memory = instance.memory("memory")?; let buf_ptr = wad_path_buf.call(&mut store, ())? as usize; - let wad_path_string = host_state.borrow().wad_path.to_string_lossy().into_owned(); + let wad_path_string = host_state.lock().unwrap().wad_path.to_string_lossy().into_owned(); memory.write_cstring_bytes(&mut store, buf_ptr, &wad_path_string)?; init.call(&mut store, ())?; @@ -161,7 +160,7 @@ impl HostState { } } -fn build_imports(store: &mut Store, state: Rc>) -> Imports { +fn build_imports(state: Arc>) -> Imports { let mut imports = Imports::new(); { @@ -175,7 +174,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { let mode = memory.read_cstring_until_null(ctx.store(), mode_ptr as usize, 16)?; let filename = filename.to_string_lossy(); let mode = mode.to_string_lossy(); - let mut state = state.borrow_mut(); + let mut state = state.lock().unwrap(); let path = if filename == state.wad_path.to_string_lossy() || state.should_redirect_to_wad(&filename) { state.wad_path.clone() } else { @@ -209,7 +208,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { IMPORT_MODULE, "host_close", HostFunction::from(move |_ctx: FuncContext<'_>, handle: i32| { - state.borrow_mut().files.remove(&handle); + state.lock().unwrap().files.remove(&handle); Ok(()) }), ); @@ -221,7 +220,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { IMPORT_MODULE, "host_read", HostFunction::from(move |mut ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| { - let mut state = state.borrow_mut(); + let mut state = state.lock().unwrap(); let Some(file) = state.files.get_mut(&handle) else { return Ok(0); }; @@ -240,7 +239,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { "host_write", HostFunction::from(move |ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| { let data = ctx.memory("memory")?.read_vec(ctx.store(), buf_ptr as usize, count.max(0) as usize)?; - let mut state = state.borrow_mut(); + let mut state = state.lock().unwrap(); let Some(file) = state.files.get_mut(&handle) else { return Ok(-1); }; @@ -262,7 +261,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { 2 => SeekFrom::End(offset as i64), _ => return Err(tinywasm::Error::Other(format!("invalid seek origin: {origin}"))), }; - let mut state = state.borrow_mut(); + let mut state = state.lock().unwrap(); let Some(file) = state.files.get_mut(&handle) else { return Ok(-1); }; @@ -278,7 +277,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { IMPORT_MODULE, "host_tell", HostFunction::from(move |_ctx: FuncContext<'_>, handle: i32| { - let mut state = state.borrow_mut(); + let mut state = state.lock().unwrap(); let Some(file) = state.files.get_mut(&handle) else { return Ok(-1); }; @@ -294,7 +293,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { IMPORT_MODULE, "host_eof", HostFunction::from(move |_ctx: FuncContext<'_>, handle: i32| { - let mut state = state.borrow_mut(); + let mut state = state.lock().unwrap(); let Some(file) = state.files.get_mut(&handle) else { return Ok(1); }; @@ -311,7 +310,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { IMPORT_MODULE, "host_gettime", HostFunction::from(move |mut ctx: FuncContext<'_>, (sec_ptr, usec_ptr): (i32, i32)| { - let elapsed = state.borrow().start.elapsed(); + let elapsed = state.lock().unwrap().start.elapsed() + Duration::from_secs(1); let sec = elapsed.as_secs().min(i32::MAX as u64) as i32; let usec = elapsed.subsec_micros() as i32; let memory = ctx.memory("memory")?; @@ -328,7 +327,7 @@ fn build_imports(store: &mut Store, state: Rc>) -> Imports { IMPORT_MODULE, "host_exit", HostFunction::from(move |_ctx: FuncContext<'_>, code: i32| { - state.borrow_mut().exit_code = Some(code); + state.lock().unwrap().exit_code = Some(code); Ok(()) }), ); diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index 5e21a6b..959d798 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store, types::FuncRef}; const LHS: i32 = 5; diff --git a/examples/linking.rs b/examples/linking.rs index 02cb88d..2df7133 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{ModuleInstance, Store}; // WebAssembly module defining and exporting an `add` function. diff --git a/examples/reentrance.rs b/examples/reentrance.rs index 8a1988e..0084a61 100644 --- a/examples/reentrance.rs +++ b/examples/reentrance.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; const WASM: &str = r#" diff --git a/examples/resumable.rs b/examples/resumable.rs index 0163414..26b5f47 100644 --- a/examples/resumable.rs +++ b/examples/resumable.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{ExecProgress, ModuleInstance, Store}; const WASM: &str = r#" diff --git a/examples/simple.rs b/examples/simple.rs index a902b4c..0d57265 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{ModuleInstance, Store}; const WASM: &str = r#" diff --git a/examples/simple2.rs b/examples/simple2.rs index da7def7..f673dfe 100644 --- a/examples/simple2.rs +++ b/examples/simple2.rs @@ -1,4 +1,4 @@ -use eyre::Result; +use anyhow::Result; use tinywasm::{ModuleInstance, Store}; const WASM: &str = r#" diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index e4af3cd..672b290 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,6 +1,6 @@ use std::hint::black_box; -use eyre::{Result, eyre}; +use anyhow::{Result, anyhow}; use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; /// Examples of using WebAssembly compiled from Rust with tinywasm. @@ -23,7 +23,7 @@ fn main() -> Result<()> { pretty_env_logger::init(); if !std::path::Path::new("./examples/rust/out/").exists() { - return Err(eyre!("No WebAssembly files found. See examples/wasm-rust.rs for instructions.")); + return Err(anyhow!("No WebAssembly files found. See examples/wasm-rust.rs for instructions.")); } let args = std::env::args().collect::>(); From be3c350746e3dc6284b40349a8ac4a170d807c58 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Aug 2026 00:09:26 +0200 Subject: [PATCH 11/15] chore: improve interpreter loop performance, add more super instructions Signed-off-by: Henry --- crates/parser/src/optimize.rs | 62 ++++++++ crates/tinywasm/src/interpreter/executor.rs | 148 +++++++++++++------- crates/tinywasm/src/interpreter/values.rs | 10 +- crates/types/src/instructions.rs | 19 ++- 4 files changed, 172 insertions(+), 67 deletions(-) diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index be37e5e..2b5e331 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -131,6 +131,14 @@ fn rewrite( rewrite!(instrs, i, [BinOpLocalConst64(BinOp::IShl, local, 32), Const64(32)] => [LocalGet64(local), I64Extend32S]); } } + instr @ (I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS | I32GeU) => { + let Some(op) = cmp_op(instr) else { unreachable!() }; + rewrite!(instrs, i, [LocalGet32(left), LocalGet32(right)] => CmpLocalLocal32(op, left, right)); + } + instr @ (I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU | I64LeS | I64LeU | I64GeS | I64GeU) => { + let Some(op) = cmp_op(instr) else { unreachable!() }; + rewrite!(instrs, i, [LocalGet64(left), LocalGet64(right)] => CmpLocalLocal64(op, left, right)); + } instr @ (F32Add | F32Mul | F32Min | F32Max) => { let Some(op) = float_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); @@ -543,6 +551,31 @@ fn rewrite( replace!(instrs, i, 1 => JumpIfNonZero64(target)); continue; }); + rewrite!(instrs, i, + [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst)] if (local == dst) => + IncLocalJump32 { target_ip: target, local, delta, on_zero: true } + ); + rewrite!(instrs, i, + [CmpLocalLocal32(op, left, right)] => + JumpCmpLocalLocal32 { target_ip: target, left, right, op: inverse_cmp_op(op) } + ); + rewrite!(instrs, i, + [CmpLocalLocal64(op, left, right)] => + JumpCmpLocalLocal64 { target_ip: target, left, right, op: inverse_cmp_op(op) } + ); + rewrite!(instrs, i, + [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst), LocalGet32(right), cmp] if + (local == dst && let Some(op) = cmp_op(cmp)) => + IncLocalJumpCmpLocal32 { target_ip: target, local, delta, right, op: inverse_cmp_op(op) } + ); + rewrite!(instrs, i, + [LocalGet32(local), cmp] if (let Some(op) = cmp_op(cmp)) => + JumpCmpStackLocal32 { target_ip: target, local, op: inverse_cmp_op(op) } + ); + rewrite!(instrs, i, + [LocalGet64(local), cmp] if (let Some(op) = cmp_op(cmp)) => + JumpCmpStackLocal64 { target_ip: target, local, op: inverse_cmp_op(op) } + ); rewrite!(instrs, i, [LocalGet32(local), Const32(imm), cmp] if (let Some(op) = cmp_op(cmp)) => match (imm, inverse_cmp_op(op)) { @@ -599,6 +632,31 @@ fn rewrite( replace!(instrs, i, 1 => JumpIfZero64(target)); continue; }); + rewrite!(instrs, i, + [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst)] if (local == dst) => + IncLocalJump32 { target_ip: target, local, delta, on_zero: false } + ); + rewrite!(instrs, i, + [CmpLocalLocal32(op, left, right)] => + JumpCmpLocalLocal32 { target_ip: target, left, right, op } + ); + rewrite!(instrs, i, + [CmpLocalLocal64(op, left, right)] => + JumpCmpLocalLocal64 { target_ip: target, left, right, op } + ); + rewrite!(instrs, i, + [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst), LocalGet32(right), cmp] if + (local == dst && let Some(op) = cmp_op(cmp)) => + IncLocalJumpCmpLocal32 { target_ip: target, local, delta, right, op } + ); + rewrite!(instrs, i, + [LocalGet32(local), cmp] if (let Some(op) = cmp_op(cmp)) => + JumpCmpStackLocal32 { target_ip: target, local, op } + ); + rewrite!(instrs, i, + [LocalGet64(local), cmp] if (let Some(op) = cmp_op(cmp)) => + JumpCmpStackLocal64 { target_ip: target, local, op } + ); rewrite!(instrs, i, [LocalGet32(local), Const32(imm), cmp] if (let Some(op) = cmp_op(cmp)) => match (imm, op) { @@ -861,6 +919,10 @@ fn instruction_target_mut(instr: &mut Instruction) -> Option<&mut u32> { | Instruction::BrOnCast(ip, _, _) | Instruction::JumpCmpStackConst32 { target_ip: ip, .. } | Instruction::JumpCmpStackConst64 { target_ip: ip, .. } + | Instruction::JumpCmpStackLocal32 { target_ip: ip, .. } + | Instruction::JumpCmpStackLocal64 { target_ip: ip, .. } + | Instruction::IncLocalJump32 { target_ip: ip, .. } + | Instruction::IncLocalJumpCmpLocal32 { target_ip: ip, .. } | Instruction::JumpIfLocalZero32 { target_ip: ip, .. } | Instruction::JumpIfLocalNonZero32 { target_ip: ip, .. } | Instruction::JumpIfLocalZero64 { target_ip: ip, .. } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index d9d00eb..2d2a745 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -65,7 +65,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } #[inline(always)] - fn exec(&mut self) -> Result> { + fn exec(&mut self, instr_ptr: usize) -> Result> { macro_rules! stack_op { (unary $ty:ty, |$v:ident| $expr:expr) => { stack_op!(unary $ty => $ty, |$v| $expr) @@ -197,6 +197,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); self.store.value_stack.push(value)?; }}; + (local_local_cmp $vt:ty, $cmp:ident, $op:ident, $a:ident, $b:ident) => {{ + let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$a); + let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$b); + self.store.value_stack.push(i32::from($cmp(lhs, rhs, *$op)))?; + }}; (local_const $vt:ty, $width:tt, $op:ident, $local:ident, $rhs:expr) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); self.store.value_stack.push(exec_binop!($width, *$op, lhs, $rhs))?; @@ -222,14 +227,14 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { use tinywasm_types::Instruction::*; #[rustfmt::skip] - match &self.func.instructions[self.cf.instr_ptr] { + match &self.func.instructions[instr_ptr] { Unreachable => { cold_path(); return Err(Trap::Unreachable.into()) }, Drop32 => { _ = Value32::stack_pop(&mut self.store.value_stack)}, Drop64 => { _ = Value64::stack_pop(&mut self.store.value_stack)}, Drop128 => { _ = Value128::stack_pop(&mut self.store.value_stack)}, - Select32 => Value32::stack_select(&mut self.store.value_stack)?, - Select64 => Value64::stack_select(&mut self.store.value_stack)?, - Select128 => Value128::stack_select(&mut self.store.value_stack)?, + Select32 => Value32::stack_select(&mut self.store.value_stack), + Select64 => Value64::stack_select(&mut self.store.value_stack), + Select128 => Value128::stack_select(&mut self.store.value_stack), SelectMulti(counts) => self.store.value_stack.select_multi(*counts), Call(v) => { self.exec_call_direct(*v)?; return Ok(None); } CallSelf => { self.exec_call_self()?; return Ok(None); } @@ -255,6 +260,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { JumpIfLocalNonZero64 { target_ip, local } => if self.exec_jump_local_non_zero_64(*target_ip, *local) { return Ok(None) }, JumpCmpStackConst32 { target_ip, imm, op } => if self.exec_jump_cmp_stack_const_32(*target_ip, *imm, *op) { return Ok(None) }, JumpCmpStackConst64 { target_ip, imm, op } => if self.exec_jump_cmp_stack_const_64(*target_ip, *imm, *op) { return Ok(None) }, + JumpCmpStackLocal32 { target_ip, local, op } => if self.exec_jump_cmp_stack_local_32(*target_ip, *local, *op) { return Ok(None) }, + JumpCmpStackLocal64 { target_ip, local, op } => if self.exec_jump_cmp_stack_local_64(*target_ip, *local, *op) { return Ok(None) }, + IncLocalJump32 { target_ip, local, delta, on_zero } => if self.exec_inc_local_jump_32(*target_ip, *local, *delta, *on_zero) { return Ok(None) }, + IncLocalJumpCmpLocal32 { target_ip, local, delta, right, op } => if self.exec_inc_local_jump_cmp_local_32(*target_ip, *local, *delta, *right, *op) { return Ok(None) }, JumpCmpLocalConst32 { target_ip, local, imm, op } => if self.exec_jump_cmp_local_const_32(*target_ip, *local, *imm, *op) { return Ok(None) }, JumpCmpLocalConst64 { target_ip, local, imm, op } => if self.exec_jump_cmp_local_const_64(*target_ip, *local, *imm, *op) { return Ok(None) }, JumpCmpLocalLocal32 { target_ip, left, right, op } => if self.exec_jump_cmp_local_local_32(*target_ip, *left, *right, *op) { return Ok(None) }, @@ -288,6 +297,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { BinOpLocalLocal32(op, a, b) => binop!(local_local Value32, 32, op, a, b), BinOpLocalLocal64(op, a, b) => binop!(local_local Value64, 64, op, a, b), BinOpLocalLocal128(op, a, b) => binop!(local_local Value128, 128, op, a, b), + CmpLocalLocal32(op, a, b) => binop!(local_local_cmp i32, cmp_i32, op, a, b), + CmpLocalLocal64(op, a, b) => binop!(local_local_cmp i64, cmp_i64, op, a, b), BinOpLocalLocalSet32(op, a, b, dst) => binop!(local_local_set Value32, 32, op, a, b, dst), BinOpLocalLocalSet64(op, a, b, dst) => binop!(local_local_set Value64, 64, op, a, b, dst), BinOpLocalLocalSet128(op, a, b, dst) => binop!(local_local_set Value128, 128, op, a, b, dst), @@ -313,24 +324,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { StoreLocalLocal32(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local)?, StoreLocalLocal64(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local)?, StoreLocalLocal128(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local)?, - LoadLocal32(m, addr_local) => self.store.value_stack.push(self.exec_load_local_value::(*m, *addr_local)?)?, - LoadLocal64(m, addr_local) => self.store.value_stack.push(self.exec_load_local_value::(*m, *addr_local)?)?, - LoadLocal8S32(m, addr_local) => { - let value = self.exec_load_local_value::(*m, *addr_local)?; - self.store.value_stack.push(i32::from(value))?; - } - LoadLocal8U32(m, addr_local) => { - let value = self.exec_load_local_value::(*m, *addr_local)?; - self.store.value_stack.push(i32::from(value))?; - } - LoadLocal16S32(m, addr_local) => { - let value = self.exec_load_local_value::(*m, *addr_local)?; - self.store.value_stack.push(i32::from(value))?; - } - LoadLocal16U32(m, addr_local) => { - let value = self.exec_load_local_value::(*m, *addr_local)?; - self.store.value_stack.push(i32::from(value))?; - } + LoadLocal32(m, addr_local) => self.exec_load_local::(*m, *addr_local, |v| v)?, + LoadLocal64(m, addr_local) => self.exec_load_local::(*m, *addr_local, |v| v)?, + LoadLocal8S32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, + LoadLocal8U32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, + LoadLocal16S32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, + LoadLocal16U32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, LoadLocalTee32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, |v| v)?, LoadLocalSet32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, |v| v)?, LoadLocalTee8S32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, i32::from)?, @@ -466,17 +465,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I31GetS => self.exec_i31_get(true)?, I31GetU => self.exec_i31_get(false)?, RefEq => stack_op!(binary ValueRef => i32, |a, b| i32::from(a == b)), - RefTest(ty) => { - let value = ValueRef::stack_pop(&mut self.store.value_stack); - let ty = self.canonical_ref_type(*ty); - self.store.value_stack.push(i32::from(self.store.state.value_ref_matches(value, ty)))?; - } - RefCast(ty) => { - if !self.exec_ref_matches(*ty) { - cold_path(); - return Err(Trap::CastFailure.into()); - } - } + RefTest(ty) => self.exec_ref_test(*ty)?, + RefCast(ty) => self.exec_ref_cast(*ty)?, // GC objects StructNew(ty) => self.exec_struct_new(*ty, false)?, StructNewDefault(ty) => self.exec_struct_new(*ty, true)?, @@ -864,7 +854,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I32x4RelaxedTruncF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_u_zero()), }; - self.cf.instr_ptr += 1; + self.cf.instr_ptr = instr_ptr + 1; Ok(None) } @@ -951,6 +941,42 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.jump_if(condition, target_ip) } + #[inline(always)] + fn exec_jump_cmp_stack_local_32(&mut self, target_ip: u32, local: LocalAddr, op: CmpOp) -> bool { + let lhs = ::stack_pop(&mut self.store.value_stack); + let rhs = i32::local_get(&self.store.value_stack, &self.cf, local); + self.jump_if(cmp_i32(lhs, rhs, op), target_ip) + } + + #[inline(always)] + fn exec_jump_cmp_stack_local_64(&mut self, target_ip: u32, local: LocalAddr, op: CmpOp) -> bool { + let lhs = ::stack_pop(&mut self.store.value_stack); + let rhs = i64::local_get(&self.store.value_stack, &self.cf, local); + self.jump_if(cmp_i64(lhs, rhs, op), target_ip) + } + + #[inline(always)] + fn exec_inc_local_jump_32(&mut self, target_ip: u32, local: LocalAddr, delta: i32, on_zero: bool) -> bool { + let value = i32::local_get(&self.store.value_stack, &self.cf, local).wrapping_add(delta); + i32::local_set(&mut self.store.value_stack, &self.cf, local, value); + self.jump_if((value == 0) == on_zero, target_ip) + } + + #[inline(always)] + fn exec_inc_local_jump_cmp_local_32( + &mut self, + target_ip: u32, + local: LocalAddr, + delta: i32, + right: LocalAddr, + op: CmpOp, + ) -> bool { + let lhs = i32::local_get(&self.store.value_stack, &self.cf, local).wrapping_add(delta); + i32::local_set(&mut self.store.value_stack, &self.cf, local, lhs); + let rhs = i32::local_get(&self.store.value_stack, &self.cf, right); + self.jump_if(cmp_i32(lhs, rhs, op), target_ip) + } + #[inline(always)] fn exec_jump_cmp_local_const_32(&mut self, target_ip: u32, local: LocalAddr, imm: i32, op: CmpOp) -> bool { self.jump_if(cmp_i32(i32::local_get(&self.store.value_stack, &self.cf, local), imm, op), target_ip) @@ -1372,6 +1398,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.finish_return() } + #[inline(always)] fn exec_store_local_local, const N: usize>( &mut self, memarg: MemoryArg, @@ -1382,8 +1409,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let value = T::local_get(&self.store.value_stack, &self.cf, u16::from(value_local)); let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(memarg.mem_addr())); let addr = mem.effective_addr_32::(base, memarg.offset())?; - value.store_at(&mut *mem.inner, addr)?; - Ok(()) + value.store_at(&mut *mem.inner, addr) } #[inline(always)] @@ -1439,6 +1465,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { T::local_update(&mut self.store.value_stack, &self.cf, acc, |v| add(mul(lhs, rhs), v)); } + #[inline(always)] fn exec_load_local_value, const N: usize>( &self, memarg: MemoryArg, @@ -1452,15 +1479,21 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let base = u32::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)); mem.effective_addr_32::(base, memarg.offset())? }; - match T::load_at(&*mem.inner, addr) { - Ok(res) => Ok(res), - Err(err) => { - cold_path(); - Err(err) - } - } + T::load_at(&*mem.inner, addr) + } + + #[inline(always)] + fn exec_load_local, const N: usize, TARGET: InternalValue>( + &mut self, + memarg: MemoryArg, + addr_local: u8, + cast: impl Fn(LOAD) -> TARGET, + ) -> Result<(), Trap> { + let value = cast(self.exec_load_local_value::(memarg, addr_local)?); + TARGET::stack_push(&mut self.store.value_stack, value) } + #[inline(always)] fn exec_load_local_tee, const N: usize, TARGET: InternalValue>( &mut self, memarg: MemoryArg, @@ -1474,6 +1507,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } + #[inline(always)] fn exec_load_local_set, const N: usize, TARGET: InternalValue>( &mut self, memarg: MemoryArg, @@ -1514,6 +1548,20 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.state.value_ref_matches(value, self.canonical_ref_type(ty)) } + fn exec_ref_test(&mut self, ty: RefType) -> Result<(), Trap> { + let value = ValueRef::stack_pop(&mut self.store.value_stack); + let matches = self.store.state.value_ref_matches(value, self.canonical_ref_type(ty)); + self.store.value_stack.push(i32::from(matches)) + } + + fn exec_ref_cast(&self, ty: RefType) -> Result<(), Trap> { + if !self.exec_ref_matches(ty) { + cold_path(); + return Err(Trap::CastFailure); + } + Ok(()) + } + fn exec_i31_get(&mut self, signed: bool) -> Result<(), Trap> { let value = ValueRef::stack_pop(&mut self.store.value_stack); if value.is_null() { @@ -2037,10 +2085,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { impl<'store> Executor<'store, false> { #[inline(always)] - pub(crate) fn run_to_completion(&mut self) -> Result<()> { + pub(crate) fn run_to_completion(mut self) -> Result<()> { // ideally we use `loop_match` / `become` once thats stabilized loop { - if self.exec()?.is_some() { + if self.exec(self.cf.instr_ptr)?.is_some() { return Ok(()); } } @@ -2048,7 +2096,7 @@ impl<'store> Executor<'store, false> { #[cfg(feature = "std")] #[inline(always)] - pub(crate) fn run_with_time_budget(&mut self, time_budget: core::time::Duration) -> Result { + pub(crate) fn run_with_time_budget(mut self, time_budget: core::time::Duration) -> Result { use crate::std::time::Instant; let start = Instant::now(); if time_budget.is_zero() { @@ -2057,7 +2105,7 @@ impl<'store> Executor<'store, false> { loop { for _ in 0..128 { - if self.exec()?.is_some() { + if self.exec(self.cf.instr_ptr)?.is_some() { return Ok(ExecState::Completed); } } @@ -2071,7 +2119,7 @@ impl<'store> Executor<'store, false> { impl<'store> Executor<'store, true> { #[inline(always)] - pub(crate) fn run_with_fuel(&mut self, fuel: u32) -> Result { + pub(crate) fn run_with_fuel(mut self, fuel: u32) -> Result { self.store.execution_fuel = fuel; if self.store.execution_fuel == 0 { return Ok(ExecState::Suspended(self.cf)); @@ -2079,7 +2127,7 @@ impl<'store> Executor<'store, true> { loop { for _ in 0..128 { - if self.exec()?.is_some() { + if self.exec(self.cf.instr_ptr)?.is_some() { return Ok(ExecState::Completed); } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index b3382e8..cc91a55 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -135,7 +135,7 @@ pub(crate) trait InternalValue: sealed::Sealed + Copy + Default { fn stack_push(stack: &mut ValueStack, value: Self) -> Result<(), crate::Trap>; fn stack_pop(stack: &mut ValueStack) -> Self; fn stack_peek(stack: &ValueStack) -> Self; - fn stack_select(stack: &mut ValueStack) -> Result<(), crate::Trap>; + fn stack_select(stack: &mut ValueStack); fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self; fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, f: impl FnOnce(Self) -> Self); @@ -227,16 +227,14 @@ macro_rules! impl_internalvalue { } #[inline(always)] - fn stack_select(stack: &mut ValueStack) -> Result<(), crate::Trap> { + fn stack_select(stack: &mut ValueStack) { let cond = stack.stack_32.pop() as i32; let val2 = stack.$stack.pop(); if cond == 0 { - Self::stack_pop(stack); - stack.$stack.push(val2)?; + let val1 = stack.$stack.len() - 1; + stack.$stack.set(val1, val2); } - - Ok(()) } } )* diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 5eb5491..4e14d75 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -186,6 +186,7 @@ pub enum Instruction { // The 32/64 suffix describes the operand width. Future compare-style ops may still yield i32 results. BinOpLocalLocal32(BinOp, LocalAddr, LocalAddr), BinOpLocalLocal64(BinOp, LocalAddr, LocalAddr), BinOpLocalLocal128(BinOp128, LocalAddr, LocalAddr), + CmpLocalLocal32(CmpOp, LocalAddr, LocalAddr), CmpLocalLocal64(CmpOp, LocalAddr, LocalAddr), BinOpLocalLocalSet32(BinOp, LocalAddr, LocalAddr, LocalAddr), BinOpLocalLocalSet64(BinOp, LocalAddr, LocalAddr, LocalAddr), BinOpLocalLocalSet128(BinOp128, LocalAddr, LocalAddr, LocalAddr), @@ -255,6 +256,10 @@ pub enum Instruction { JumpIfLocalNonZero64 { target_ip: u32, local: LocalAddr }, JumpCmpStackConst32 { target_ip: u32, imm: i32, op: CmpOp }, JumpCmpStackConst64 { target_ip: u32, imm: i64, op: CmpOp }, + JumpCmpStackLocal32 { target_ip: u32, local: LocalAddr, op: CmpOp }, + JumpCmpStackLocal64 { target_ip: u32, local: LocalAddr, op: CmpOp }, + IncLocalJump32 { target_ip: u32, local: LocalAddr, delta: i32, on_zero: bool }, + IncLocalJumpCmpLocal32 { target_ip: u32, local: LocalAddr, delta: i32, right: LocalAddr, op: CmpOp }, JumpCmpLocalConst32 { target_ip: u32, local: LocalAddr, imm: i32, op: CmpOp }, JumpCmpLocalConst64 { target_ip: u32, local: LocalAddr, imm: i32, op: CmpOp }, JumpCmpLocalLocal32 { target_ip: u32, left: LocalAddr, right: LocalAddr, op: CmpOp }, @@ -477,9 +482,11 @@ pub enum Instruction { F64x2RelaxedMin, F64x2RelaxedMax, I16x8RelaxedQ15mulrS, I16x8RelaxedDotI8x16I7x16S, - I32x4RelaxedDotI8x16I7x16AddS + I32x4RelaxedDotI8x16I7x16AddS, } +const _: () = assert!(core::mem::size_of::() <= 16); + impl Instruction { #[inline] pub const fn memory_addr(&self) -> Option { @@ -564,13 +571,3 @@ impl Instruction { } } } - -#[cfg(test)] -mod tests { - use super::Instruction; - - #[test] - fn instruction_layout_size_is_stable() { - assert_eq!(core::mem::size_of::(), 16); - } -} From 64cf33ced2db2135dd355a1c6e049bf51c93d956 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 11 Aug 2026 17:25:32 +0200 Subject: [PATCH 12/15] chore: more superinstructions, cleanup Signed-off-by: Henry --- crates/parser/src/conversion.rs | 3 +- crates/parser/src/optimize.rs | 108 +- crates/tinywasm/src/func/host.rs | 5 +- crates/tinywasm/src/interpreter/executor.rs | 1304 ++++++++--------- crates/tinywasm/src/store/state.rs | 21 +- crates/tinywasm/src/store/types.rs | 12 +- .../tests/host_func_signature_check.rs | 2 +- crates/types/src/instructions.rs | 14 +- crates/types/src/reference.rs | 13 +- crates/types/src/value.rs | 2 +- examples/rust/Cargo.toml | 7 +- examples/rust/build.sh | 7 +- examples/rust/src/print.twasm | Bin 0 -> 154 bytes examples/rust/src/tinywasm_no_std.rs | 6 +- examples/rust/src/tinywasm_precompiled.rs | 30 + examples/wasm-rust.rs | 25 + 16 files changed, 827 insertions(+), 732 deletions(-) create mode 100644 examples/rust/src/print.twasm create mode 100644 examples/rust/src/tinywasm_precompiled.rs diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 694c639..480a8d0 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -440,8 +440,7 @@ fn convert_heap_type_with_group( ))); } }; - RefType::new_concrete(nullable, index) - .ok_or_else(|| crate::ParseError::Other(format!("heap type index is too large: {index}"))) + Ok(RefType::new_concrete(nullable, index)) } wasmparser::HeapType::Abstract { shared: true, .. } | wasmparser::HeapType::Exact(_) => { Err(crate::ParseError::UnsupportedOperator(format!("Unsupported heap type: {heap:?}"))) diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index 2b5e331..7081187 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -92,7 +92,9 @@ fn rewrite( let Some(op) = int_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); + rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [Const32(c), LocalGet32(local)] => BinOpLocalConst32(op, local, c)); + rewrite!(instrs, i, [Const32(c), GlobalGet32(global)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [GlobalGet32(global)] => BinOpStackGlobal32(op, global)); if matches!(op, BinOp::IAdd) { rewrite!(instrs, i, [Const32(c)] => AddConst32(c)); @@ -103,6 +105,7 @@ fn rewrite( let Some(op) = int_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); + rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [GlobalGet32(global)] => BinOpStackGlobal32(op, global)); if matches!(op, BinOp::IShrS) { rewrite!(instrs, i, [BinOpLocalConst32(BinOp::IShl, local, 8), Const32(8)] => [LocalGet32(local), I32Extend8S]); @@ -113,7 +116,9 @@ fn rewrite( let Some(op) = int_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b)); rewrite!(instrs, i, [LocalGet64(local), Const64(c)] => BinOpLocalConst64(op, local, c)); + rewrite!(instrs, i, [GlobalGet64(global), Const64(c)] => BinOpGlobalConst64(op, global, c)); rewrite!(instrs, i, [Const64(c), LocalGet64(local)] => BinOpLocalConst64(op, local, c)); + rewrite!(instrs, i, [Const64(c), GlobalGet64(global)] => BinOpGlobalConst64(op, global, c)); rewrite!(instrs, i, [GlobalGet64(global)] => BinOpStackGlobal64(op, global)); if matches!(op, BinOp::IAdd) { rewrite!(instrs, i, [Const64(c)] => AddConst64(c)); @@ -124,6 +129,7 @@ fn rewrite( let Some(op) = int_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b)); rewrite!(instrs, i, [LocalGet64(local), Const64(c)] => BinOpLocalConst64(op, local, c)); + rewrite!(instrs, i, [GlobalGet64(global), Const64(c)] => BinOpGlobalConst64(op, global, c)); rewrite!(instrs, i, [GlobalGet64(global)] => BinOpStackGlobal64(op, global)); if matches!(op, BinOp::IShrS) { rewrite!(instrs, i, [BinOpLocalConst64(BinOp::IShl, local, 8), Const64(8)] => [LocalGet64(local), I64Extend8S]); @@ -143,33 +149,42 @@ fn rewrite( let Some(op) = float_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); + rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [Const32(c), LocalGet32(local)] => BinOpLocalConst32(op, local, c)); + rewrite!(instrs, i, [Const32(c), GlobalGet32(global)] => BinOpGlobalConst32(op, global, c)); } instr @ (F32Sub | F32Div | F32Copysign) => { let Some(op) = float_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); + rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); } instr @ (F64Add | F64Mul | F64Min | F64Max) => { let Some(op) = float_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b)); rewrite!(instrs, i, [LocalGet64(local), Const64(c)] => BinOpLocalConst64(op, local, c)); + rewrite!(instrs, i, [GlobalGet64(global), Const64(c)] => BinOpGlobalConst64(op, global, c)); rewrite!(instrs, i, [Const64(c), LocalGet64(local)] => BinOpLocalConst64(op, local, c)); + rewrite!(instrs, i, [Const64(c), GlobalGet64(global)] => BinOpGlobalConst64(op, global, c)); } instr @ (F64Sub | F64Div | F64Copysign) => { let Some(op) = float_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b)); rewrite!(instrs, i, [LocalGet64(local), Const64(c)] => BinOpLocalConst64(op, local, c)); + rewrite!(instrs, i, [GlobalGet64(global), Const64(c)] => BinOpGlobalConst64(op, global, c)); } instr @ (V128And | V128Or | V128Xor | I64x2Add | I64x2Mul) => { let Some(op) = bin_op_128(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet128(a), LocalGet128(b)] => BinOpLocalLocal128(op, a, b)); rewrite!(instrs, i, [LocalGet128(local), Const128(c)] => BinOpLocalConst128(op, local, c)); + rewrite!(instrs, i, [GlobalGet128(global), Const128(c)] => BinOpGlobalConst128(op, global, c)); rewrite!(instrs, i, [Const128(c), LocalGet128(local)] => BinOpLocalConst128(op, local, c)); + rewrite!(instrs, i, [Const128(c), GlobalGet128(global)] => BinOpGlobalConst128(op, global, c)); } V128AndNot => { rewrite!(instrs, i, [LocalGet128(a), LocalGet128(b)] => BinOpLocalLocal128(BinOp128::AndNot, a, b)); rewrite!(instrs, i, [LocalGet128(local), Const128(c)] => BinOpLocalConst128(BinOp128::AndNot, local, c)); + rewrite!(instrs, i, [GlobalGet128(global), Const128(c)] => BinOpGlobalConst128(BinOp128::AndNot, global, c)); } I32Store(memarg) | F32Store(memarg) => { rewrite!(instrs, i, @@ -277,6 +292,9 @@ fn rewrite( MemoryFill(mem) => { rewrite!(instrs, i, [Const32(val), Const32(size)] => MemoryFillImm(mem, val as u8, size)) } + GlobalGet32(dst) => rewrite!(instrs, i, [GlobalSet32(src)] if (src == dst) => GlobalTee32(src)), + GlobalGet64(dst) => rewrite!(instrs, i, [GlobalSet64(src)] if (src == dst) => GlobalTee64(src)), + GlobalGet128(dst) => rewrite!(instrs, i, [GlobalSet128(src)] if (src == dst) => GlobalTee128(src)), LocalGet32(dst) => rewrite!(instrs, i, [LocalSet32(src)] if (src == dst) => LocalTee32(src)), LocalGet64(dst) => rewrite!(instrs, i, [LocalSet64(src)] if (src == dst) => LocalTee64(src)), LocalGet128(dst) => rewrite!(instrs, i, [LocalSet128(src)] if (src == dst) => LocalTee128(src)), @@ -542,7 +560,6 @@ fn rewrite( replace!(instrs, i, 1 => JumpIfNonZero32(target)); continue; }); - rewrite!(instrs, i, [LocalGet32(local)] => JumpIfLocalZero32 { target_ip: target, local }); rewrite!(instrs, i, [LocalGet64(local), I64Eqz] => { replace!(instrs, i, 2 => JumpIfLocalNonZero64 { target_ip: target, local }); continue; @@ -552,8 +569,30 @@ fn rewrite( continue; }); rewrite!(instrs, i, - [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst)] if (local == dst) => - IncLocalJump32 { target_ip: target, local, delta, on_zero: true } + [BinOpLocalConstTee32(op, local, imm, dst)] if (local == dst) => + match inc_delta(op, imm) { + Some(delta) => IncLocalJump32 { target_ip: target, local, delta, on_zero: true }, + None => BinOpLocalConstJump32 { target_ip: target, local, imm, op, on_zero: true }, + } + ); + rewrite!(instrs, i, + [AddConst32(imm), LocalTee32(local), LocalGet32(cond)] if (local == cond) => + IncStackTeeLocalJump32 { target_ip: target, local, delta: imm, on_zero: true } + ); + rewrite!(instrs, i, + [AndConstTee32(imm, local), LocalGet32(cond)] if (local == cond) => + BinOpStackConstTeeLocalJump32 { target_ip: target, local, imm, op: BinOp::IAnd, on_zero: true } + ); + rewrite!(instrs, i, + [SubConstTee32(imm, local), LocalGet32(cond)] if (local == cond) => + IncStackTeeLocalJump32 { target_ip: target, local, delta: imm.wrapping_neg(), on_zero: true } + ); + rewrite!(instrs, i, + [BinOpGlobalConst32(op, global, imm), GlobalTee32(dst)] if (global == dst) => + match inc_delta(op, imm) { + Some(delta) => IncGlobalJump32 { target_ip: target, global, delta, on_zero: true }, + None => BinOpGlobalConstJump32 { target_ip: target, global, imm, op, on_zero: true }, + } ); rewrite!(instrs, i, [CmpLocalLocal32(op, left, right)] => @@ -564,9 +603,12 @@ fn rewrite( JumpCmpLocalLocal64 { target_ip: target, left, right, op: inverse_cmp_op(op) } ); rewrite!(instrs, i, - [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst), LocalGet32(right), cmp] if - (local == dst && let Some(op) = cmp_op(cmp)) => - IncLocalJumpCmpLocal32 { target_ip: target, local, delta, right, op: inverse_cmp_op(op) } + [BinOpLocalConstTee32(binop, local, imm, dst), LocalGet32(right), cmp] if + (local == dst && let Some(cmp) = cmp_op(cmp)) => + match inc_delta(binop, imm) { + Some(delta) => IncLocalJumpCmpLocal32 { target_ip: target, local, delta, right, op: inverse_cmp_op(cmp) }, + None => BinOpLocalConstJumpCmpLocal32 { target_ip: target, local, imm, binop, right, cmp: inverse_cmp_op(cmp) }, + } ); rewrite!(instrs, i, [LocalGet32(local), cmp] if (let Some(op) = cmp_op(cmp)) => @@ -611,6 +653,8 @@ fn rewrite( (0, CmpOp::Ne) => JumpIfNonZero64(target), (imm, op) => JumpCmpStackConst64 { target_ip: target, imm, op }, }); + rewrite!(instrs, i, [LocalGet32(local)] => JumpIfLocalZero32 { target_ip: target, local }); + rewrite!(instrs, i, [LocalGet64(local)] => JumpIfLocalZero64 { target_ip: target, local }); canonicalize_jump_like_with_target(&mut instrs, i, target, old_idx as u32 + 1); } JumpIfNonZero32(ip) => { @@ -623,7 +667,6 @@ fn rewrite( replace!(instrs, i, 1 => JumpIfZero32(target)); continue; }); - rewrite!(instrs, i, [LocalGet32(local)] => JumpIfLocalNonZero32 { target_ip: target, local }); rewrite!(instrs, i, [LocalGet64(local), I64Eqz] => { replace!(instrs, i, 2 => JumpIfLocalZero64 { target_ip: target, local }); continue; @@ -633,8 +676,30 @@ fn rewrite( continue; }); rewrite!(instrs, i, - [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst)] if (local == dst) => - IncLocalJump32 { target_ip: target, local, delta, on_zero: false } + [BinOpLocalConstTee32(op, local, imm, dst)] if (local == dst) => + match inc_delta(op, imm) { + Some(delta) => IncLocalJump32 { target_ip: target, local, delta, on_zero: false }, + None => BinOpLocalConstJump32 { target_ip: target, local, imm, op, on_zero: false }, + } + ); + rewrite!(instrs, i, + [AddConst32(imm), LocalTee32(local), LocalGet32(cond)] if (local == cond) => + IncStackTeeLocalJump32 { target_ip: target, local, delta: imm, on_zero: false } + ); + rewrite!(instrs, i, + [AndConstTee32(imm, local), LocalGet32(cond)] if (local == cond) => + BinOpStackConstTeeLocalJump32 { target_ip: target, local, imm, op: BinOp::IAnd, on_zero: false } + ); + rewrite!(instrs, i, + [SubConstTee32(imm, local), LocalGet32(cond)] if (local == cond) => + IncStackTeeLocalJump32 { target_ip: target, local, delta: imm.wrapping_neg(), on_zero: false } + ); + rewrite!(instrs, i, + [BinOpGlobalConst32(op, global, imm), GlobalTee32(dst)] if (global == dst) => + match inc_delta(op, imm) { + Some(delta) => IncGlobalJump32 { target_ip: target, global, delta, on_zero: false }, + None => BinOpGlobalConstJump32 { target_ip: target, global, imm, op, on_zero: false }, + } ); rewrite!(instrs, i, [CmpLocalLocal32(op, left, right)] => @@ -645,9 +710,12 @@ fn rewrite( JumpCmpLocalLocal64 { target_ip: target, left, right, op } ); rewrite!(instrs, i, - [BinOpLocalConstTee32(BinOp::IAdd, local, delta, dst), LocalGet32(right), cmp] if - (local == dst && let Some(op) = cmp_op(cmp)) => - IncLocalJumpCmpLocal32 { target_ip: target, local, delta, right, op } + [BinOpLocalConstTee32(binop, local, imm, dst), LocalGet32(right), cmp] if + (local == dst && let Some(cmp) = cmp_op(cmp)) => + match inc_delta(binop, imm) { + Some(delta) => IncLocalJumpCmpLocal32 { target_ip: target, local, delta, right, op: cmp }, + None => BinOpLocalConstJumpCmpLocal32 { target_ip: target, local, imm, binop, right, cmp }, + } ); rewrite!(instrs, i, [LocalGet32(local), cmp] if (let Some(op) = cmp_op(cmp)) => @@ -692,6 +760,8 @@ fn rewrite( (0, CmpOp::Ne) => JumpIfNonZero64(target), (imm, op) => JumpCmpStackConst64 { target_ip: target, imm, op }, }); + rewrite!(instrs, i, [LocalGet32(local)] => JumpIfLocalNonZero32 { target_ip: target, local }); + rewrite!(instrs, i, [LocalGet64(local)] => JumpIfLocalNonZero64 { target_ip: target, local }); canonicalize_jump_like_with_target(&mut instrs, i, target, old_idx as u32 + 1); } JumpIfZero64(ip) => { @@ -774,6 +844,14 @@ fn cmp_op(instr: Instruction) -> Option { }) } +fn inc_delta(op: BinOp, imm: i32) -> Option { + match op { + BinOp::IAdd => Some(imm), + BinOp::ISub => Some(imm.wrapping_neg()), + _ => None, + } +} + fn int_bin_op(instr: Instruction) -> Option { Some(match instr { Instruction::I32Add | Instruction::I64Add => BinOp::IAdd, @@ -921,7 +999,13 @@ fn instruction_target_mut(instr: &mut Instruction) -> Option<&mut u32> { | Instruction::JumpCmpStackConst64 { target_ip: ip, .. } | Instruction::JumpCmpStackLocal32 { target_ip: ip, .. } | Instruction::JumpCmpStackLocal64 { target_ip: ip, .. } + | Instruction::BinOpLocalConstJump32 { target_ip: ip, .. } + | Instruction::BinOpLocalConstJumpCmpLocal32 { target_ip: ip, .. } + | Instruction::BinOpStackConstTeeLocalJump32 { target_ip: ip, .. } + | Instruction::BinOpGlobalConstJump32 { target_ip: ip, .. } | Instruction::IncLocalJump32 { target_ip: ip, .. } + | Instruction::IncStackTeeLocalJump32 { target_ip: ip, .. } + | Instruction::IncGlobalJump32 { target_ip: ip, .. } | Instruction::IncLocalJumpCmpLocal32 { target_ip: ip, .. } | Instruction::JumpIfLocalZero32 { target_ip: ip, .. } | Instruction::JumpIfLocalNonZero32 { target_ip: ip, .. } diff --git a/crates/tinywasm/src/func/host.rs b/crates/tinywasm/src/func/host.rs index 5335444..27c5573 100644 --- a/crates/tinywasm/src/func/host.rs +++ b/crates/tinywasm/src/func/host.rs @@ -31,10 +31,7 @@ impl HostFunction { let canonical = *type_addrs .get(module_addr as usize) .ok_or_else(|| crate::Error::other("host function signature contains an invalid concrete type"))?; - Ok(WasmType::Ref( - tinywasm_types::RefType::new_concrete(ref_ty.is_nullable(), canonical) - .expect("canonical type addresses fit in references"), - )) + Ok(WasmType::Ref(tinywasm_types::RefType::new_concrete(ref_ty.is_nullable(), canonical))) }; let params = self.0.ty.params().iter().copied().map(resolve).collect::>>()?; let results = self.0.ty.results().iter().copied().map(resolve).collect::>>()?; diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 2d2a745..051b12f 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -66,14 +66,14 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { #[inline(always)] fn exec(&mut self, instr_ptr: usize) -> Result> { - macro_rules! stack_op { - (unary $ty:ty, |$v:ident| $expr:expr) => { - stack_op!(unary $ty => $ty, |$v| $expr) - }; - (binary $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { - stack_op!(binary $ty => $ty, |$lhs, $rhs| $expr) - }; - (binary try $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + macro_rules! exec_op { + (branch $target:expr, $condition:expr) => {{ + if $condition { + self.cf.instr_ptr = *$target as usize; + return Ok(None); + } + }}; + (binary_fallible $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ let $rhs = <$ty>::stack_pop(&mut self.store.value_stack); let $lhs = <$ty>::stack_pop(&mut self.store.value_stack); <$ty>::stack_push(&mut self.store.value_stack, $expr?)?; @@ -87,28 +87,25 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let $lhs = <$from>::stack_pop(&mut self.store.value_stack); <$to>::stack_push(&mut self.store.value_stack, $expr)?; }}; - (binary_into2 $from:ty => $to:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + (binary_two_results $from:ty => $to:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ let $rhs = <$from>::stack_pop(&mut self.store.value_stack); let $lhs = <$from>::stack_pop(&mut self.store.value_stack); let out = $expr; <$to>::stack_push(&mut self.store.value_stack, out.0)?; <$to>::stack_push(&mut self.store.value_stack, out.1)?; }}; - (binary $lhs_ty:ty, $rhs_ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { - stack_op!(binary $lhs_ty, $rhs_ty => $rhs_ty, |$lhs, $rhs| $expr) - }; - (binary $lhs_ty:ty, $rhs_ty:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + (binary_mixed $lhs_ty:ty, $rhs_ty:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ let $rhs = <$rhs_ty>::stack_pop(&mut self.store.value_stack); let $lhs = <$lhs_ty>::stack_pop(&mut self.store.value_stack); <$res>::stack_push(&mut self.store.value_stack, $expr)?; }}; - (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => {{ - let $c = <$ty>::stack_pop(&mut self.store.value_stack); - let $b = <$ty>::stack_pop(&mut self.store.value_stack); - let $a = <$ty>::stack_pop(&mut self.store.value_stack); - <$ty>::stack_push(&mut self.store.value_stack, $expr)?; + (ternary $from:ty => $to:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => {{ + let $c = <$from>::stack_pop(&mut self.store.value_stack); + let $b = <$from>::stack_pop(&mut self.store.value_stack); + let $a = <$from>::stack_pop(&mut self.store.value_stack); + <$to>::stack_push(&mut self.store.value_stack, $expr)?; }}; - (quaternary_into2 $from:ty => $to:ty, |$a:ident, $b:ident, $c:ident, $d:ident| $expr:expr) => {{ + (quaternary_two_results $from:ty => $to:ty, |$a:ident, $b:ident, $c:ident, $d:ident| $expr:expr) => {{ let $d = <$from>::stack_pop(&mut self.store.value_stack); let $c = <$from>::stack_pop(&mut self.store.value_stack); let $b = <$from>::stack_pop(&mut self.store.value_stack); @@ -135,93 +132,64 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let value = <$ty>::stack_pop(&mut self.store.value_stack); <$ty>::global_set(&mut self.store.state.globals, addr, value); }}; - } - - macro_rules! exec_binop { - (32, $op:expr, $lhs:expr, $rhs:expr) => { - exec_binop!(@scalar i32, u32, f32, $op, $lhs, $rhs) - }; - (64, $op:expr, $lhs:expr, $rhs:expr) => { - exec_binop!(@scalar i64, u64, f64, $op, $lhs, $rhs) - }; - (@scalar $signed:ty, $unsigned:ty, $float:ty, $op:expr, $lhs:expr, $rhs:expr) => {{ - match $op { - BinOp::IAdd => $lhs.wrapping_add($rhs), - BinOp::ISub => $lhs.wrapping_sub($rhs), - BinOp::IMul => $lhs.wrapping_mul($rhs), - BinOp::IAnd => $lhs & $rhs, - BinOp::IOr => $lhs | $rhs, - BinOp::IXor => $lhs ^ $rhs, - BinOp::IShl => (($lhs as $signed).wrapping_shl($rhs as u32)) as $unsigned, - BinOp::IShrS => (($lhs as $signed).wrapping_shr($rhs as u32)) as $unsigned, - BinOp::IShrU => $lhs.wrapping_shr($rhs as u32), - BinOp::IRotl => (($lhs as $signed).rotate_left($rhs as u32)) as $unsigned, - BinOp::IRotr => (($lhs as $signed).rotate_right($rhs as u32)) as $unsigned, - BinOp::FAdd => (<$float>::from_bits($lhs) + <$float>::from_bits($rhs)).to_bits(), - BinOp::FSub => (<$float>::from_bits($lhs) - <$float>::from_bits($rhs)).to_bits(), - BinOp::FMul => (<$float>::from_bits($lhs) * <$float>::from_bits($rhs)).to_bits(), - BinOp::FDiv => (<$float>::from_bits($lhs) / <$float>::from_bits($rhs)).to_bits(), - BinOp::FMin => <$float>::from_bits($lhs).tw_minimum(<$float>::from_bits($rhs)).to_bits(), - BinOp::FMax => <$float>::from_bits($lhs).tw_maximum(<$float>::from_bits($rhs)).to_bits(), - BinOp::FCopysign => <$float>::from_bits($lhs).copysign(<$float>::from_bits($rhs)).to_bits(), - } - }}; - (128, $op:expr, $lhs:expr, $rhs:expr) => {{ - match $op { - BinOp128::And => $lhs.v128_and($rhs), - BinOp128::AndNot => $lhs.v128_andnot($rhs), - BinOp128::Or => $lhs.v128_or($rhs), - BinOp128::Xor => $lhs.v128_xor($rhs), - BinOp128::I64x2Add => $lhs.i64x2_add($rhs), - BinOp128::I64x2Mul => $lhs.i64x2_mul($rhs), - } + (global_tee $ty:ty, $global_index:expr) => {{ + let addr = self.module.resolve_global_addr(*$global_index); + let value = <$ty>::stack_peek(&self.store.value_stack); + <$ty>::global_set(&mut self.store.state.globals, addr, value); }}; - } - - macro_rules! binop { - (local_local $vt:ty, $width:tt, $op:ident, $a:ident, $b:ident) => {{ + (binop_local_local $vt:ty, $exec:ident, $op:ident, $a:ident, $b:ident) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$a); let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$b); - self.store.value_stack.push(exec_binop!($width, *$op, lhs, rhs))?; + <$vt>::stack_push(&mut self.store.value_stack, $exec(*$op, lhs, rhs))?; }}; - (local_local_set $vt:ty, $width:tt, $op:ident, $a:ident, $b:ident, $dst:ident) => {{ + (binop_local_local_set $vt:ty, $exec:ident, $op:ident, $a:ident, $b:ident, $dst:ident) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$a); let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$b); - let value = exec_binop!($width, *$op, lhs, rhs); + let value = $exec(*$op, lhs, rhs); <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); }}; - (local_local_tee $vt:ty, $width:tt, $op:ident, $a:ident, $b:ident, $dst:ident) => {{ + (binop_local_local_tee $vt:ty, $exec:ident, $op:ident, $a:ident, $b:ident, $dst:ident) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$a); let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$b); - let value = exec_binop!($width, *$op, lhs, rhs); + let value = $exec(*$op, lhs, rhs); <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); - self.store.value_stack.push(value)?; + <$vt>::stack_push(&mut self.store.value_stack, value)?; }}; - (local_local_cmp $vt:ty, $cmp:ident, $op:ident, $a:ident, $b:ident) => {{ + (cmp_local_local $vt:ty, $cmp:ident, $op:ident, $a:ident, $b:ident) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$a); let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$b); self.store.value_stack.push(i32::from($cmp(lhs, rhs, *$op)))?; }}; - (local_const $vt:ty, $width:tt, $op:ident, $local:ident, $rhs:expr) => {{ + (binop_local_const $vt:ty, $exec:ident, $op:ident, $local:ident, $rhs:expr) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); - self.store.value_stack.push(exec_binop!($width, *$op, lhs, $rhs))?; + <$vt>::stack_push(&mut self.store.value_stack, $exec(*$op, lhs, $rhs))?; }}; - (local_const_set $vt:ty, $width:tt, $op:ident, $local:ident, $rhs:expr, $dst:ident) => {{ + (binop_local_const_set $vt:ty, $exec:ident, $op:ident, $local:ident, $rhs:expr, $dst:ident) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); - let value = exec_binop!($width, *$op, lhs, $rhs); + let value = $exec(*$op, lhs, $rhs); <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); }}; - (local_const_tee $vt:ty, $width:tt, $op:ident, $local:ident, $rhs:expr, $dst:ident) => {{ + (binop_local_const_tee $vt:ty, $exec:ident, $op:ident, $local:ident, $rhs:expr, $dst:ident) => {{ let lhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); - let value = exec_binop!($width, *$op, lhs, $rhs); + let value = $exec(*$op, lhs, $rhs); <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); - self.store.value_stack.push(value)?; + <$vt>::stack_push(&mut self.store.value_stack, value)?; }}; - (stack_global $vt:ty, $width:tt, $op:ident, $global:ident) => {{ + (binop_global_const $vt:ty, $exec:ident, $op:ident, $global:ident, $rhs:expr) => {{ + let lhs = <$vt>::global_get(&self.store.state.globals, self.module.resolve_global_addr(*$global)); + self.store.value_stack.push($exec(*$op, lhs, $rhs))?; + }}; + (binop_stack_global $vt:ty, $exec:ident, $op:ident, $global:ident) => {{ let global_val = <$vt>::global_get(&self.store.state.globals, self.module.resolve_global_addr(*$global)); let stack_val = <$vt>::stack_pop(&mut self.store.value_stack); - self.store.value_stack.push(exec_binop!($width, *$op, stack_val, global_val))?; + self.store.value_stack.push($exec(*$op, stack_val, global_val))?; + }}; + (binop_acc_local $ty:ty, $acc:ident, $mul:expr, $add:expr) => {{ + let rhs = <$ty>::stack_pop(&mut self.store.value_stack); + let lhs = <$ty>::stack_pop(&mut self.store.value_stack); + let value = ($mul)(lhs, rhs); + <$ty>::local_update(&mut self.store.value_stack, &self.cf, *$acc, |acc| ($add)(value, acc)); }}; } @@ -247,27 +215,115 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Throw(tag) => { self.exec_throw(*tag)?; return Ok(None); } ThrowRef => { self.exec_throw_ref()?; return Ok(None); } Jump(ip) => { self.cf.instr_ptr = *ip as usize; return Ok(None); } - JumpIfZero32(ip) => if self.exec_jump_zero_32(*ip) { return Ok(None) }, - JumpIfNonZero32(ip) => if self.exec_jump_non_zero_32(*ip) { return Ok(None) }, - JumpIfZero64(ip) => if self.exec_jump_zero_64(*ip) { return Ok(None) }, - JumpIfNonZero64(ip) => if self.exec_jump_non_zero_64(*ip) { return Ok(None) }, - JumpIfRefNull(ip) => if self.exec_jump_ref_null(*ip) { return Ok(None) }, - JumpIfRefNonNull(ip) => if self.exec_jump_ref_non_null(*ip) { return Ok(None) }, + JumpIfZero32(ip) => exec_op!(branch ip, i32::stack_pop(&mut self.store.value_stack) == 0), + JumpIfNonZero32(ip) => exec_op!(branch ip, i32::stack_pop(&mut self.store.value_stack) != 0), + JumpIfZero64(ip) => exec_op!(branch ip, i64::stack_pop(&mut self.store.value_stack) == 0), + JumpIfNonZero64(ip) => exec_op!(branch ip, i64::stack_pop(&mut self.store.value_stack) != 0), + JumpIfRefNull(ip) => { + let is_null = ValueRef::stack_peek(&self.store.value_stack).is_null(); + if is_null { + ValueRef::stack_pop(&mut self.store.value_stack); + } + exec_op!(branch ip, is_null); + }, + JumpIfRefNonNull(ip) => { + let is_non_null = !ValueRef::stack_peek(&self.store.value_stack).is_null(); + if !is_non_null { + ValueRef::stack_pop(&mut self.store.value_stack); + } + exec_op!(branch ip, is_non_null); + }, BrOnCast(ip, ty, on_fail) => if self.exec_ref_matches(*ty) == *on_fail { self.cf.instr_ptr = *ip as usize; return Ok(None); }, - JumpIfLocalZero32 { target_ip, local } => if self.exec_jump_local_zero_32(*target_ip, *local) { return Ok(None) }, - JumpIfLocalNonZero32 { target_ip, local } => if self.exec_jump_local_non_zero_32(*target_ip, *local) { return Ok(None) }, - JumpIfLocalZero64 { target_ip, local } => if self.exec_jump_local_zero_64(*target_ip, *local) { return Ok(None) }, - JumpIfLocalNonZero64 { target_ip, local } => if self.exec_jump_local_non_zero_64(*target_ip, *local) { return Ok(None) }, - JumpCmpStackConst32 { target_ip, imm, op } => if self.exec_jump_cmp_stack_const_32(*target_ip, *imm, *op) { return Ok(None) }, - JumpCmpStackConst64 { target_ip, imm, op } => if self.exec_jump_cmp_stack_const_64(*target_ip, *imm, *op) { return Ok(None) }, - JumpCmpStackLocal32 { target_ip, local, op } => if self.exec_jump_cmp_stack_local_32(*target_ip, *local, *op) { return Ok(None) }, - JumpCmpStackLocal64 { target_ip, local, op } => if self.exec_jump_cmp_stack_local_64(*target_ip, *local, *op) { return Ok(None) }, - IncLocalJump32 { target_ip, local, delta, on_zero } => if self.exec_inc_local_jump_32(*target_ip, *local, *delta, *on_zero) { return Ok(None) }, - IncLocalJumpCmpLocal32 { target_ip, local, delta, right, op } => if self.exec_inc_local_jump_cmp_local_32(*target_ip, *local, *delta, *right, *op) { return Ok(None) }, - JumpCmpLocalConst32 { target_ip, local, imm, op } => if self.exec_jump_cmp_local_const_32(*target_ip, *local, *imm, *op) { return Ok(None) }, - JumpCmpLocalConst64 { target_ip, local, imm, op } => if self.exec_jump_cmp_local_const_64(*target_ip, *local, *imm, *op) { return Ok(None) }, - JumpCmpLocalLocal32 { target_ip, left, right, op } => if self.exec_jump_cmp_local_local_32(*target_ip, *left, *right, *op) { return Ok(None) }, - JumpCmpLocalLocal64 { target_ip, left, right, op } => if self.exec_jump_cmp_local_local_64(*target_ip, *left, *right, *op) { return Ok(None) }, + JumpIfLocalZero32 { target_ip, local } => exec_op!(branch target_ip, Value32::local_get(&self.store.value_stack, &self.cf, *local) == 0), + JumpIfLocalNonZero32 { target_ip, local } => exec_op!(branch target_ip, Value32::local_get(&self.store.value_stack, &self.cf, *local) != 0), + JumpIfLocalZero64 { target_ip, local } => exec_op!(branch target_ip, Value64::local_get(&self.store.value_stack, &self.cf, *local) == 0), + JumpIfLocalNonZero64 { target_ip, local } => exec_op!(branch target_ip, Value64::local_get(&self.store.value_stack, &self.cf, *local) != 0), + JumpCmpStackConst32 { target_ip, imm, op } => { + let lhs = i32::stack_pop(&mut self.store.value_stack); + exec_op!(branch target_ip, cmp_i32(lhs, *imm, *op)); + }, + JumpCmpStackConst64 { target_ip, imm, op } => { + let lhs = i64::stack_pop(&mut self.store.value_stack); + exec_op!(branch target_ip, cmp_i64(lhs, *imm, *op)); + }, + JumpCmpStackLocal32 { target_ip, local, op } => { + let lhs = i32::stack_pop(&mut self.store.value_stack); + let rhs = i32::local_get(&self.store.value_stack, &self.cf, *local); + exec_op!(branch target_ip, cmp_i32(lhs, rhs, *op)); + }, + JumpCmpStackLocal64 { target_ip, local, op } => { + let lhs = i64::stack_pop(&mut self.store.value_stack); + let rhs = i64::local_get(&self.store.value_stack, &self.cf, *local); + exec_op!(branch target_ip, cmp_i64(lhs, rhs, *op)); + }, + BinOpLocalConstJump32 { target_ip, local, imm, op, on_zero } => { + let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local); + let value = exec_binop_32(*op, lhs as u32, *imm as u32) as i32; + i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); + exec_op!(branch target_ip, (value == 0) == *on_zero); + }, + BinOpLocalConstJumpCmpLocal32 { target_ip, local, imm, binop, right, cmp } => { + let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local); + let lhs = exec_binop_32(*binop, lhs as u32, *imm as u32) as i32; + i32::local_set(&mut self.store.value_stack, &self.cf, *local, lhs); + let rhs = i32::local_get(&self.store.value_stack, &self.cf, *right); + exec_op!(branch target_ip, cmp_i32(lhs, rhs, *cmp)); + }, + BinOpStackConstTeeLocalJump32 { target_ip, local, imm, op, on_zero } => { + let lhs = i32::stack_pop(&mut self.store.value_stack); + let value = exec_binop_32(*op, lhs as u32, *imm as u32) as i32; + i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); + i32::stack_push(&mut self.store.value_stack, value)?; + exec_op!(branch target_ip, (value == 0) == *on_zero); + }, + BinOpGlobalConstJump32 { target_ip, global, imm, op, on_zero } => { + let global = self.module.resolve_global_addr(*global); + let lhs = i32::global_get(&self.store.state.globals, global); + let value = exec_binop_32(*op, lhs as u32, *imm as u32) as i32; + i32::global_set(&mut self.store.state.globals, global, value); + exec_op!(branch target_ip, (value == 0) == *on_zero); + }, + IncLocalJump32 { target_ip, local, delta, on_zero } => { + let value = i32::local_get(&self.store.value_stack, &self.cf, *local).wrapping_add(*delta); + i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); + exec_op!(branch target_ip, (value == 0) == *on_zero); + }, + IncStackTeeLocalJump32 { target_ip, local, delta, on_zero } => { + let value = i32::stack_pop(&mut self.store.value_stack).wrapping_add(*delta); + i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); + i32::stack_push(&mut self.store.value_stack, value)?; + exec_op!(branch target_ip, (value == 0) == *on_zero); + }, + IncGlobalJump32 { target_ip, global, delta, on_zero } => { + let global = self.module.resolve_global_addr(*global); + let value = i32::global_get(&self.store.state.globals, global).wrapping_add(*delta); + i32::global_set(&mut self.store.state.globals, global, value); + exec_op!(branch target_ip, (value == 0) == *on_zero); + }, + IncLocalJumpCmpLocal32 { target_ip, local, delta, right, op } => { + let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local).wrapping_add(*delta); + i32::local_set(&mut self.store.value_stack, &self.cf, *local, lhs); + let rhs = i32::local_get(&self.store.value_stack, &self.cf, *right); + exec_op!(branch target_ip, cmp_i32(lhs, rhs, *op)); + }, + JumpCmpLocalConst32 { target_ip, local, imm, op } => { + let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local); + exec_op!(branch target_ip, cmp_i32(lhs, *imm, *op)); + }, + JumpCmpLocalConst64 { target_ip, local, imm, op } => { + let lhs = i64::local_get(&self.store.value_stack, &self.cf, *local); + exec_op!(branch target_ip, cmp_i64(lhs, i64::from(*imm), *op)); + }, + JumpCmpLocalLocal32 { target_ip, left, right, op } => { + let lhs = i32::local_get(&self.store.value_stack, &self.cf, *left); + let rhs = i32::local_get(&self.store.value_stack, &self.cf, *right); + exec_op!(branch target_ip, cmp_i32(lhs, rhs, *op)); + }, + JumpCmpLocalLocal64 { target_ip, left, right, op } => { + let lhs = i64::local_get(&self.store.value_stack, &self.cf, *left); + let rhs = i64::local_get(&self.store.value_stack, &self.cf, *right); + exec_op!(branch target_ip, cmp_i64(lhs, rhs, *op)); + }, DropKeep(drop_keep) => self.exec_drop_keep(*drop_keep), BranchTable(default_ip, start, len) => { self.exec_branch_table(*default_ip, *start, *len); return Ok(None); } Return => { if self.exec_return() { return Ok(Some(())); } return Ok(None); } @@ -278,44 +334,47 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { LocalGet32(local_index) => self.store.value_stack.push(Value32::local_get(&self.store.value_stack, &self.cf, *local_index))?, LocalGet64(local_index) => self.store.value_stack.push(Value64::local_get(&self.store.value_stack, &self.cf, *local_index))?, LocalGet128(local_index) => self.store.value_stack.push(Value128::local_get(&self.store.value_stack, &self.cf, *local_index))?, - LocalSet32(local_index) => stack_op!(local_set_pop Value32, local_index), - LocalSet64(local_index) => stack_op!(local_set_pop Value64, local_index), - LocalSet128(local_index) => stack_op!(local_set_pop Value128, local_index), + LocalSet32(local_index) => exec_op!(local_set_pop Value32, local_index), + LocalSet64(local_index) => exec_op!(local_set_pop Value64, local_index), + LocalSet128(local_index) => exec_op!(local_set_pop Value128, local_index), LocalCopy32(from, to) => Value32::local_copy(&mut self.store.value_stack, &self.cf, *from, *to), LocalCopy64(from, to) => Value64::local_copy(&mut self.store.value_stack, &self.cf, *from, *to), LocalCopy128(from, to) => Value128::local_copy(&mut self.store.value_stack, &self.cf, *from, *to), - AddConst32(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), - AddConst64(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), + AddConst32(c) => exec_op!(unary i32 => i32, |v| v.wrapping_add(*c)), + AddConst64(c) => exec_op!(unary i64 => i64, |v| v.wrapping_add(*c)), IncLocal32(local_index, delta) => i32::local_update(&mut self.store.value_stack, &self.cf, *local_index, |v| v.wrapping_add(*delta)), IncLocal64(local_index, delta) => i64::local_update(&mut self.store.value_stack, &self.cf, *local_index, |v| v.wrapping_add(*delta )), - I32Add3 => stack_op!(ternary i32, |a, b, c| a.wrapping_add(b).wrapping_add(c)), - I64Add3 => stack_op!(ternary i64, |a, b, c| a.wrapping_add(b).wrapping_add(c)), - MulAccLocal32(acc) => self.exec_binop_acc_local::(*acc, |a, b| a.wrapping_mul(b), |a, b| a.wrapping_add(b)), - MulAccLocal64(acc) => self.exec_binop_acc_local::(*acc, |a, b| a.wrapping_mul(b), |a, b| a.wrapping_add(b)), - FMulAccLocal32(acc) => self.exec_binop_acc_local::(*acc, |a, b| a * b, |a, b| a + b), - FMulAccLocal64(acc) => self.exec_binop_acc_local::(*acc, |a, b| a * b, |a, b| a + b), - BinOpLocalLocal32(op, a, b) => binop!(local_local Value32, 32, op, a, b), - BinOpLocalLocal64(op, a, b) => binop!(local_local Value64, 64, op, a, b), - BinOpLocalLocal128(op, a, b) => binop!(local_local Value128, 128, op, a, b), - CmpLocalLocal32(op, a, b) => binop!(local_local_cmp i32, cmp_i32, op, a, b), - CmpLocalLocal64(op, a, b) => binop!(local_local_cmp i64, cmp_i64, op, a, b), - BinOpLocalLocalSet32(op, a, b, dst) => binop!(local_local_set Value32, 32, op, a, b, dst), - BinOpLocalLocalSet64(op, a, b, dst) => binop!(local_local_set Value64, 64, op, a, b, dst), - BinOpLocalLocalSet128(op, a, b, dst) => binop!(local_local_set Value128, 128, op, a, b, dst), - BinOpLocalLocalTee32(op, a, b, dst) => binop!(local_local_tee Value32, 32, op, a, b, dst), - BinOpLocalLocalTee64(op, a, b, dst) => binop!(local_local_tee Value64, 64, op, a, b, dst), - BinOpLocalLocalTee128(op, a, b, dst) => binop!(local_local_tee Value128, 128, op, a, b, dst), - BinOpLocalConst32(op, local_index, c) => binop!(local_const Value32, 32, op, local_index, *c as u32), - BinOpLocalConst64(op, local_index, c) => binop!(local_const Value64, 64, op, local_index, *c as u64), - BinOpLocalConst128(op, local_index, c) => binop!(local_const Value128, 128, op, local_index, Value128(self.func.data.v128_const(*c))), - BinOpLocalConstSet32(op, local_index, c, dst) => binop!(local_const_set Value32, 32, op, local_index, *c as u32, dst), - BinOpLocalConstSet64(op, local_index, c, dst) => binop!(local_const_set Value64, 64, op, local_index, *c as u64, dst), - BinOpLocalConstSet128(op, local_index, c, dst) => binop!(local_const_set Value128, 128, op, local_index, Value128(self.func.data.v128_const(*c)), dst), - BinOpLocalConstTee32(op, local_index, c, dst) => binop!(local_const_tee Value32, 32, op, local_index, *c as u32, dst), - BinOpLocalConstTee64(op, local_index, c, dst) => binop!(local_const_tee Value64, 64, op, local_index, *c as u64, dst), - BinOpLocalConstTee128(op, local_index, c, dst) => binop!(local_const_tee Value128, 128, op, local_index, Value128(self.func.data.v128_const(*c)), dst), - BinOpStackGlobal32(op, global_index) => binop!(stack_global Value32, 32, op, global_index), - BinOpStackGlobal64(op, global_index) => binop!(stack_global Value64, 64, op, global_index), + I32Add3 => exec_op!(ternary i32 => i32, |a, b, c| a.wrapping_add(b).wrapping_add(c)), + I64Add3 => exec_op!(ternary i64 => i64, |a, b, c| a.wrapping_add(b).wrapping_add(c)), + MulAccLocal32(acc) => exec_op!(binop_acc_local i32, acc, |a: i32, b| a.wrapping_mul(b), |a: i32, b| a.wrapping_add(b)), + MulAccLocal64(acc) => exec_op!(binop_acc_local i64, acc, |a: i64, b| a.wrapping_mul(b), |a: i64, b| a.wrapping_add(b)), + FMulAccLocal32(acc) => exec_op!(binop_acc_local f32, acc, |a: f32, b| a * b, |a: f32, b| a + b), + FMulAccLocal64(acc) => exec_op!(binop_acc_local f64, acc, |a: f64, b| a * b, |a: f64, b| a + b), + BinOpLocalLocal32(op, a, b) => exec_op!(binop_local_local Value32, exec_binop_32, op, a, b), + BinOpLocalLocal64(op, a, b) => exec_op!(binop_local_local Value64, exec_binop_64, op, a, b), + BinOpLocalLocal128(op, a, b) => exec_op!(binop_local_local Value128, exec_binop_128, op, a, b), + CmpLocalLocal32(op, a, b) => exec_op!(cmp_local_local i32, cmp_i32, op, a, b), + CmpLocalLocal64(op, a, b) => exec_op!(cmp_local_local i64, cmp_i64, op, a, b), + BinOpLocalLocalSet32(op, a, b, dst) => exec_op!(binop_local_local_set Value32, exec_binop_32, op, a, b, dst), + BinOpLocalLocalSet64(op, a, b, dst) => exec_op!(binop_local_local_set Value64, exec_binop_64, op, a, b, dst), + BinOpLocalLocalSet128(op, a, b, dst) => exec_op!(binop_local_local_set Value128, exec_binop_128, op, a, b, dst), + BinOpLocalLocalTee32(op, a, b, dst) => exec_op!(binop_local_local_tee Value32, exec_binop_32, op, a, b, dst), + BinOpLocalLocalTee64(op, a, b, dst) => exec_op!(binop_local_local_tee Value64, exec_binop_64, op, a, b, dst), + BinOpLocalLocalTee128(op, a, b, dst) => exec_op!(binop_local_local_tee Value128, exec_binop_128, op, a, b, dst), + BinOpLocalConst32(op, local_index, c) => exec_op!(binop_local_const Value32, exec_binop_32, op, local_index, *c as u32), + BinOpLocalConst64(op, local_index, c) => exec_op!(binop_local_const Value64, exec_binop_64, op, local_index, *c as u64), + BinOpLocalConst128(op, local_index, c) => exec_op!(binop_local_const Value128, exec_binop_128, op, local_index, Value128(self.func.data.v128_const(*c))), + BinOpGlobalConst32(op, global_index, c) => exec_op!(binop_global_const Value32, exec_binop_32, op, global_index, *c as u32), + BinOpGlobalConst64(op, global_index, c) => exec_op!(binop_global_const Value64, exec_binop_64, op, global_index, *c as u64), + BinOpGlobalConst128(op, global_index, c) => exec_op!(binop_global_const Value128, exec_binop_128, op, global_index, Value128(self.func.data.v128_const(*c))), + BinOpLocalConstSet32(op, local_index, c, dst) => exec_op!(binop_local_const_set Value32, exec_binop_32, op, local_index, *c as u32, dst), + BinOpLocalConstSet64(op, local_index, c, dst) => exec_op!(binop_local_const_set Value64, exec_binop_64, op, local_index, *c as u64, dst), + BinOpLocalConstSet128(op, local_index, c, dst) => exec_op!(binop_local_const_set Value128, exec_binop_128, op, local_index, Value128(self.func.data.v128_const(*c)), dst), + BinOpLocalConstTee32(op, local_index, c, dst) => exec_op!(binop_local_const_tee Value32, exec_binop_32, op, local_index, *c as u32, dst), + BinOpLocalConstTee64(op, local_index, c, dst) => exec_op!(binop_local_const_tee Value64, exec_binop_64, op, local_index, *c as u64, dst), + BinOpLocalConstTee128(op, local_index, c, dst) => exec_op!(binop_local_const_tee Value128, exec_binop_128, op, local_index, Value128(self.func.data.v128_const(*c)), dst), + BinOpStackGlobal32(op, global_index) => exec_op!(binop_stack_global Value32, exec_binop_32, op, global_index), + BinOpStackGlobal64(op, global_index) => exec_op!(binop_stack_global Value64, exec_binop_64, op, global_index), SetLocalConst32(local_index, c) => i32::local_set(&mut self.store.value_stack, &self.cf, *local_index, *c), SetLocalConst64(local_index, c) => i64::local_set(&mut self.store.value_stack, &self.cf, *local_index, *c), SetLocalConst128(local_index, c) => Value128::local_set(&mut self.store.value_stack, &self.cf, *local_index, Value128(self.func.data.v128_const(*c))), @@ -342,129 +401,132 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { LoadLocalSet16U32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, i32::from)?, LoadLocalTee128(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, |v| v)?, LoadLocalSet128(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, |v| v)?, - AndConstTee32(c, local_index) => { stack_op!(unary i32, |v| v & *c); stack_op!(local_tee i32, local_index); } - SubConstTee32(c, local_index) => { stack_op!(unary i32, |v| v.wrapping_sub(*c)); stack_op!(local_tee i32, local_index); } - AndConstTee64(c, local_index) => { stack_op!(unary i64, |v| v & *c); stack_op!(local_tee i64, local_index); } - SubConstTee64(c, local_index) => { stack_op!(unary i64, |v| v.wrapping_sub(*c)); stack_op!(local_tee i64, local_index); } - LocalTee32(local_index) => stack_op!(local_tee Value32, local_index), - LocalTee64(local_index) => stack_op!(local_tee Value64, local_index), - LocalTee128(local_index) => stack_op!(local_tee Value128, local_index), - GlobalGet32(global_index) => stack_op!(global_get Value32, global_index), - GlobalGet64(global_index) => stack_op!(global_get Value64, global_index), - GlobalGet128(global_index) => stack_op!(global_get Value128, global_index), - GlobalSet32(global_index) => stack_op!(global_set Value32, global_index), - GlobalSet64(global_index) => stack_op!(global_set Value64, global_index), - GlobalSet128(global_index) => stack_op!(global_set Value128, global_index), - Const32(val) => self.exec_const(*val)?, - Const64(val) => self.exec_const(*val)?, - I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), - I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), - I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), - I64Eq => stack_op!(binary i64 => i32, |a, b| i32::from(a == b)), - F32Eq => stack_op!(binary f32 => i32, |a, b| i32::from(a == b)), - F64Eq => stack_op!(binary f64 => i32, |a, b| i32::from(a == b)), - I32Ne => stack_op!(binary i32, |a, b| i32::from(a != b)), - I64Ne => stack_op!(binary i64 => i32, |a, b| i32::from(a != b)), - F32Ne => stack_op!(binary f32 => i32, |a, b| i32::from(a != b)), - F64Ne => stack_op!(binary f64 => i32, |a, b| i32::from(a != b)), - I32LtS => stack_op!(binary i32, |a, b| i32::from(a < b)), - I64LtS => stack_op!(binary i64 => i32, |a, b| i32::from(a < b)), - I32LtU => stack_op!(binary u32 => i32, |a, b| i32::from(a < b)), - I64LtU => stack_op!(binary u64 => i32, |a, b| i32::from(a < b)), - F32Lt => stack_op!(binary f32 => i32, |a, b| i32::from(a < b)), - F64Lt => stack_op!(binary f64 => i32, |a, b| i32::from(a < b)), - I32LeS => stack_op!(binary i32, |a, b| i32::from(a <= b)), - I64LeS => stack_op!(binary i64 => i32, |a, b| i32::from(a <= b)), - I32LeU => stack_op!(binary u32 => i32, |a, b| i32::from(a <= b)), - I64LeU => stack_op!(binary u64 => i32, |a, b| i32::from(a <= b)), - F32Le => stack_op!(binary f32 => i32, |a, b| i32::from(a <= b)), - F64Le => stack_op!(binary f64 => i32, |a, b| i32::from(a <= b)), - I32GeS => stack_op!(binary i32, |a, b| i32::from(a >= b)), - I64GeS => stack_op!(binary i64 => i32, |a, b| i32::from(a >= b)), - I32GeU => stack_op!(binary u32 => i32, |a, b| i32::from(a >= b)), - I64GeU => stack_op!(binary u64 => i32, |a, b| i32::from(a >= b)), - F32Ge => stack_op!(binary f32 => i32, |a, b| i32::from(a >= b)), - F64Ge => stack_op!(binary f64 => i32, |a, b| i32::from(a >= b)), - I32GtS => stack_op!(binary i32, |a, b| i32::from(a > b)), - I64GtS => stack_op!(binary i64 => i32, |a, b| i32::from(a > b)), - I32GtU => stack_op!(binary u32 => i32, |a, b| i32::from(a > b)), - I64GtU => stack_op!(binary u64 => i32, |a, b| i32::from(a > b)), - F32Gt => stack_op!(binary f32 => i32, |a, b| i32::from(a > b)), - F64Gt => stack_op!(binary f64 => i32, |a, b| i32::from(a > b)), - I32Add => stack_op!(binary i32, |a, b| a.wrapping_add(b)), - I64Add => stack_op!(binary i64, |a, b| a.wrapping_add(b)), - F32Add => stack_op!(binary f32, |a, b| a + b), - F64Add => stack_op!(binary f64, |a, b| a + b), - I32Sub => stack_op!(binary i32, |a, b| a.wrapping_sub(b)), - I64Sub => stack_op!(binary i64, |a, b| a.wrapping_sub(b)), - F32Sub => stack_op!(binary f32, |a, b| a - b), - F64Sub => stack_op!(binary f64, |a, b| a - b), - F32Div => stack_op!(binary f32, |a, b| a / b), - F64Div => stack_op!(binary f64, |a, b| a / b), - I32Mul => stack_op!(binary i32, |a, b| a.wrapping_mul(b)), - I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), - F32Mul => stack_op!(binary f32, |a, b| a * b), - F64Mul => stack_op!(binary f64, |a, b| a * b), - I32DivS => stack_op!(binary try i32, |a, b| a.tw_checked_div(b)), - I64DivS => stack_op!(binary try i64, |a, b| a.tw_checked_div(b)), - I32DivU => stack_op!(binary try u32, |a, b| a.checked_div(b).ok_or(Trap::DivisionByZero)), - I64DivU => stack_op!(binary try u64, |a, b| a.checked_div(b).ok_or(Trap::DivisionByZero)), - I32RemS => stack_op!(binary try i32, |a, b| a.tw_checked_wrapping_rem(b)), - I64RemS => stack_op!(binary try i64, |a, b| a.tw_checked_wrapping_rem(b)), - I32RemU => stack_op!(binary try u32, |a, b| a.tw_checked_wrapping_rem(b)), - I64RemU => stack_op!(binary try u64, |a, b| a.tw_checked_wrapping_rem(b)), - I32And => stack_op!(binary i32, |a, b| a & b), - I64And => stack_op!(binary i64, |a, b| a & b), - I32Or => stack_op!(binary i32, |a, b| a | b), - I64Or => stack_op!(binary i64, |a, b| a | b), - I32Xor => stack_op!(binary i32, |a, b| a ^ b), - I64Xor => stack_op!(binary i64, |a, b| a ^ b), - I32Shl => stack_op!(binary i32, |a, b| a.wrapping_shl(b as u32)), - I64Shl => stack_op!(binary i64, |a, b| a.wrapping_shl(b as u32)), - I32ShrS => stack_op!(binary i32, |a, b| a.wrapping_shr(b as u32)), - I64ShrS => stack_op!(binary i64, |a, b| a.wrapping_shr(b as u32)), - I32ShrU => stack_op!(binary u32, |a, b| a.wrapping_shr(b)), - I64ShrU => stack_op!(binary u64, |a, b| a.wrapping_shr(b as u32)), - I32Rotl => stack_op!(binary i32, |a, b| a.rotate_left(b as u32)), - I64Rotl => stack_op!(binary i64, |a, b| a.rotate_left(b as u32)), - I32Rotr => stack_op!(binary i32, |a, b| a.rotate_right(b as u32)), - I64Rotr => stack_op!(binary i64, |a, b| a.rotate_right(b as u32)), - I64Add128 => stack_op!(quaternary_into2 i64 => i64, |a_lo, a_hi, b_lo, b_hi| { + AndConstTee32(c, local_index) => { exec_op!(unary i32 => i32, |v| v & *c); exec_op!(local_tee i32, local_index); } + SubConstTee32(c, local_index) => { exec_op!(unary i32 => i32, |v| v.wrapping_sub(*c)); exec_op!(local_tee i32, local_index); } + AndConstTee64(c, local_index) => { exec_op!(unary i64 => i64, |v| v & *c); exec_op!(local_tee i64, local_index); } + SubConstTee64(c, local_index) => { exec_op!(unary i64 => i64, |v| v.wrapping_sub(*c)); exec_op!(local_tee i64, local_index); } + LocalTee32(local_index) => exec_op!(local_tee Value32, local_index), + LocalTee64(local_index) => exec_op!(local_tee Value64, local_index), + LocalTee128(local_index) => exec_op!(local_tee Value128, local_index), + GlobalGet32(global_index) => exec_op!(global_get Value32, global_index), + GlobalGet64(global_index) => exec_op!(global_get Value64, global_index), + GlobalGet128(global_index) => exec_op!(global_get Value128, global_index), + GlobalSet32(global_index) => exec_op!(global_set Value32, global_index), + GlobalSet64(global_index) => exec_op!(global_set Value64, global_index), + GlobalSet128(global_index) => exec_op!(global_set Value128, global_index), + GlobalTee32(global_index) => exec_op!(global_tee Value32, global_index), + GlobalTee64(global_index) => exec_op!(global_tee Value64, global_index), + GlobalTee128(global_index) => exec_op!(global_tee Value128, global_index), + Const32(val) => i32::stack_push(&mut self.store.value_stack, *val)?, + Const64(val) => i64::stack_push(&mut self.store.value_stack, *val)?, + I64Eqz => exec_op!(unary i64 => i32, |v| i32::from(v == 0)), + I32Eqz => exec_op!(unary i32 => i32, |v| i32::from(v == 0)), + I32Eq => exec_op!(binary i32 => i32, |a, b| i32::from(a == b)), + I64Eq => exec_op!(binary i64 => i32, |a, b| i32::from(a == b)), + F32Eq => exec_op!(binary f32 => i32, |a, b| i32::from(a == b)), + F64Eq => exec_op!(binary f64 => i32, |a, b| i32::from(a == b)), + I32Ne => exec_op!(binary i32 => i32, |a, b| i32::from(a != b)), + I64Ne => exec_op!(binary i64 => i32, |a, b| i32::from(a != b)), + F32Ne => exec_op!(binary f32 => i32, |a, b| i32::from(a != b)), + F64Ne => exec_op!(binary f64 => i32, |a, b| i32::from(a != b)), + I32LtS => exec_op!(binary i32 => i32, |a, b| i32::from(a < b)), + I64LtS => exec_op!(binary i64 => i32, |a, b| i32::from(a < b)), + I32LtU => exec_op!(binary u32 => i32, |a, b| i32::from(a < b)), + I64LtU => exec_op!(binary u64 => i32, |a, b| i32::from(a < b)), + F32Lt => exec_op!(binary f32 => i32, |a, b| i32::from(a < b)), + F64Lt => exec_op!(binary f64 => i32, |a, b| i32::from(a < b)), + I32LeS => exec_op!(binary i32 => i32, |a, b| i32::from(a <= b)), + I64LeS => exec_op!(binary i64 => i32, |a, b| i32::from(a <= b)), + I32LeU => exec_op!(binary u32 => i32, |a, b| i32::from(a <= b)), + I64LeU => exec_op!(binary u64 => i32, |a, b| i32::from(a <= b)), + F32Le => exec_op!(binary f32 => i32, |a, b| i32::from(a <= b)), + F64Le => exec_op!(binary f64 => i32, |a, b| i32::from(a <= b)), + I32GeS => exec_op!(binary i32 => i32, |a, b| i32::from(a >= b)), + I64GeS => exec_op!(binary i64 => i32, |a, b| i32::from(a >= b)), + I32GeU => exec_op!(binary u32 => i32, |a, b| i32::from(a >= b)), + I64GeU => exec_op!(binary u64 => i32, |a, b| i32::from(a >= b)), + F32Ge => exec_op!(binary f32 => i32, |a, b| i32::from(a >= b)), + F64Ge => exec_op!(binary f64 => i32, |a, b| i32::from(a >= b)), + I32GtS => exec_op!(binary i32 => i32, |a, b| i32::from(a > b)), + I64GtS => exec_op!(binary i64 => i32, |a, b| i32::from(a > b)), + I32GtU => exec_op!(binary u32 => i32, |a, b| i32::from(a > b)), + I64GtU => exec_op!(binary u64 => i32, |a, b| i32::from(a > b)), + F32Gt => exec_op!(binary f32 => i32, |a, b| i32::from(a > b)), + F64Gt => exec_op!(binary f64 => i32, |a, b| i32::from(a > b)), + I32Add => exec_op!(binary i32 => i32, |a, b| a.wrapping_add(b)), + I64Add => exec_op!(binary i64 => i64, |a, b| a.wrapping_add(b)), + F32Add => exec_op!(binary f32 => f32, |a, b| a + b), + F64Add => exec_op!(binary f64 => f64, |a, b| a + b), + I32Sub => exec_op!(binary i32 => i32, |a, b| a.wrapping_sub(b)), + I64Sub => exec_op!(binary i64 => i64, |a, b| a.wrapping_sub(b)), + F32Sub => exec_op!(binary f32 => f32, |a, b| a - b), + F64Sub => exec_op!(binary f64 => f64, |a, b| a - b), + F32Div => exec_op!(binary f32 => f32, |a, b| a / b), + F64Div => exec_op!(binary f64 => f64, |a, b| a / b), + I32Mul => exec_op!(binary i32 => i32, |a, b| a.wrapping_mul(b)), + I64Mul => exec_op!(binary i64 => i64, |a, b| a.wrapping_mul(b)), + F32Mul => exec_op!(binary f32 => f32, |a, b| a * b), + F64Mul => exec_op!(binary f64 => f64, |a, b| a * b), + I32DivS => exec_op!(binary_fallible i32, |a, b| a.tw_checked_div(b)), + I64DivS => exec_op!(binary_fallible i64, |a, b| a.tw_checked_div(b)), + I32DivU => exec_op!(binary_fallible u32, |a, b| a.checked_div(b).ok_or(Trap::DivisionByZero)), + I64DivU => exec_op!(binary_fallible u64, |a, b| a.checked_div(b).ok_or(Trap::DivisionByZero)), + I32RemS => exec_op!(binary_fallible i32, |a, b| a.tw_checked_wrapping_rem(b)), + I64RemS => exec_op!(binary_fallible i64, |a, b| a.tw_checked_wrapping_rem(b)), + I32RemU => exec_op!(binary_fallible u32, |a, b| a.tw_checked_wrapping_rem(b)), + I64RemU => exec_op!(binary_fallible u64, |a, b| a.tw_checked_wrapping_rem(b)), + I32And => exec_op!(binary i32 => i32, |a, b| a & b), + I64And => exec_op!(binary i64 => i64, |a, b| a & b), + I32Or => exec_op!(binary i32 => i32, |a, b| a | b), + I64Or => exec_op!(binary i64 => i64, |a, b| a | b), + I32Xor => exec_op!(binary i32 => i32, |a, b| a ^ b), + I64Xor => exec_op!(binary i64 => i64, |a, b| a ^ b), + I32Shl => exec_op!(binary i32 => i32, |a, b| a.wrapping_shl(b as u32)), + I64Shl => exec_op!(binary i64 => i64, |a, b| a.wrapping_shl(b as u32)), + I32ShrS => exec_op!(binary i32 => i32, |a, b| a.wrapping_shr(b as u32)), + I64ShrS => exec_op!(binary i64 => i64, |a, b| a.wrapping_shr(b as u32)), + I32ShrU => exec_op!(binary u32 => u32, |a, b| a.wrapping_shr(b)), + I64ShrU => exec_op!(binary u64 => u64, |a, b| a.wrapping_shr(b as u32)), + I32Rotl => exec_op!(binary i32 => i32, |a, b| a.rotate_left(b as u32)), + I64Rotl => exec_op!(binary i64 => i64, |a, b| a.rotate_left(b as u32)), + I32Rotr => exec_op!(binary i32 => i32, |a, b| a.rotate_right(b as u32)), + I64Rotr => exec_op!(binary i64 => i64, |a, b| a.rotate_right(b as u32)), + I64Add128 => exec_op!(quaternary_two_results i64 => i64, |a_lo, a_hi, b_lo, b_hi| { let lo = a_lo.wrapping_add(b_lo); let carry = u64::from((lo as u64) < (a_lo as u64)); let hi = a_hi.wrapping_add(b_hi).wrapping_add(carry as i64); (lo, hi) }), - I64Sub128 => stack_op!(quaternary_into2 i64 => i64, |a_lo, a_hi, b_lo, b_hi| { + I64Sub128 => exec_op!(quaternary_two_results i64 => i64, |a_lo, a_hi, b_lo, b_hi| { let lo = a_lo.wrapping_sub(b_lo); let borrow = u64::from((a_lo as u64) < (b_lo as u64)); let hi = a_hi.wrapping_sub(b_hi).wrapping_sub(borrow as i64); (lo, hi) }), - I64MulWideS => stack_op!(binary_into2 i64 => i64, |a, b| { + I64MulWideS => exec_op!(binary_two_results i64 => i64, |a, b| { let product = (a as i128).wrapping_mul(b as i128); (product as i64, (product >> 64) as i64) }), - I64MulWideU => stack_op!(binary_into2 i64 => i64, |a, b| { + I64MulWideU => exec_op!(binary_two_results i64 => i64, |a, b| { let product = (a as u64 as u128).wrapping_mul(b as u64 as u128); (product as u64 as i64, (product >> 64) as u64 as i64) }), - I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), - I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), - I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), - I64Ctz => stack_op!(unary i64, |v| i64::from(v.trailing_zeros())), - I32Popcnt => stack_op!(unary i32, |v| v.count_ones() as i32), - I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), + I32Clz => exec_op!(unary i32 => i32, |v| v.leading_zeros() as i32), + I64Clz => exec_op!(unary i64 => i64, |v| i64::from(v.leading_zeros())), + I32Ctz => exec_op!(unary i32 => i32, |v| v.trailing_zeros() as i32), + I64Ctz => exec_op!(unary i64 => i64, |v| i64::from(v.trailing_zeros())), + I32Popcnt => exec_op!(unary i32 => i32, |v| v.count_ones() as i32), + I64Popcnt => exec_op!(unary i64 => i64, |v| i64::from(v.count_ones())), // Reference types - RefFunc(func_idx) => self.exec_const(ValueRef::from_category_addr(self.module.resolve_func_addr(*func_idx)))?, - RefNull(_) => self.exec_const(ValueRef::NULL)?, + RefFunc(func_idx) => ValueRef::stack_push(&mut self.store.value_stack, ValueRef::from_category_addr(self.module.resolve_func_addr(*func_idx)))?, + RefNull(_) => ValueRef::stack_push(&mut self.store.value_stack, ValueRef::NULL)?, RefIsNull => self.exec_ref_is_null()?, RefAsNonNull => self.exec_ref_as_non_null()?, - RefI31 => stack_op!(unary i32 => ValueRef, |v| ValueRef::from_i31(v)), + RefI31 => exec_op!(unary i32 => ValueRef, |v| ValueRef::from_i31(v)), I31GetS => self.exec_i31_get(true)?, I31GetU => self.exec_i31_get(false)?, - RefEq => stack_op!(binary ValueRef => i32, |a, b| i32::from(a == b)), + RefEq => exec_op!(binary ValueRef => i32, |a, b| i32::from(a == b)), RefTest(ty) => self.exec_ref_test(*ty)?, RefCast(ty) => self.exec_ref_cast(*ty)?, // GC objects @@ -536,46 +598,46 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, // Numeric conversion operations - F32ConvertI32S => stack_op!(unary i32 => f32, |v| v as f32), - F32ConvertI64S => stack_op!(unary i64 => f32, |v| v as f32), - F64ConvertI32S => stack_op!(unary i32 => f64, |v| f64::from(v)), - F64ConvertI64S => stack_op!(unary i64 => f64, |v| v as f64), - F32ConvertI32U => stack_op!(unary u32 => f32, |v| v as f32), - F32ConvertI64U => stack_op!(unary u64 => f32, |v| v as f32), - F64ConvertI32U => stack_op!(unary u32 => f64, |v| f64::from(v)), - F64ConvertI64U => stack_op!(unary u64 => f64, |v| v as f64), + F32ConvertI32S => exec_op!(unary i32 => f32, |v| v as f32), + F32ConvertI64S => exec_op!(unary i64 => f32, |v| v as f32), + F64ConvertI32S => exec_op!(unary i32 => f64, |v| f64::from(v)), + F64ConvertI64S => exec_op!(unary i64 => f64, |v| v as f64), + F32ConvertI32U => exec_op!(unary u32 => f32, |v| v as f32), + F32ConvertI64U => exec_op!(unary u64 => f32, |v| v as f32), + F64ConvertI32U => exec_op!(unary u32 => f64, |v| f64::from(v)), + F64ConvertI64U => exec_op!(unary u64 => f64, |v| v as f64), // Sign-extension operations - I32Extend8S => stack_op!(unary i32, |v| i32::from(v as i8)), - I32Extend16S => stack_op!(unary i32, |v| i32::from(v as i16)), - I64Extend8S => stack_op!(unary i64, |v| i64::from(v as i8)), - I64Extend16S => stack_op!(unary i64, |v| i64::from(v as i16)), - I64Extend32S => stack_op!(unary i64, |v| i64::from(v as i32)), - I64ExtendI32U => stack_op!(unary u32 => i64, |v| i64::from(v)), - I64ExtendI32S => stack_op!(unary i32 => i64, |v| i64::from(v)), - I32WrapI64 => stack_op!(unary i64 => i32, |v| v as i32), - F32DemoteF64 => stack_op!(unary f64 => f32, |v| v as f32), - F64PromoteF32 => stack_op!(unary f32 => f64, |v| f64::from(v)), - F32Abs => stack_op!(unary f32, |v| v.abs()), - F64Abs => stack_op!(unary f64, |v| v.abs()), - F32Neg => stack_op!(unary f32, |v| -v), - F64Neg => stack_op!(unary f64, |v| -v), - F32Ceil => stack_op!(unary f32, |v| v.ceil()), - F64Ceil => stack_op!(unary f64, |v| v.ceil()), - F32Floor => stack_op!(unary f32, |v| v.floor()), - F64Floor => stack_op!(unary f64, |v| v.floor()), - F32Trunc => stack_op!(unary f32, |v| v.trunc()), - F64Trunc => stack_op!(unary f64, |v| v.trunc()), - F32Nearest => stack_op!(unary f32, |v| v.tw_nearest()), - F64Nearest => stack_op!(unary f64, |v| v.tw_nearest()), - F32Sqrt => stack_op!(unary f32, |v| v.sqrt()), - F64Sqrt => stack_op!(unary f64, |v| v.sqrt()), - F32Min => stack_op!(binary f32, |a, b| a.tw_minimum(b)), - F64Min => stack_op!(binary f64, |a, b| a.tw_minimum(b)), - F32Max => stack_op!(binary f32, |a, b| a.tw_maximum(b)), - F64Max => stack_op!(binary f64, |a, b| a.tw_maximum(b)), - F32Copysign => stack_op!(binary f32, |a, b| a.copysign(b)), - F64Copysign => stack_op!(binary f64, |a, b| a.copysign(b)), + I32Extend8S => exec_op!(unary i32 => i32, |v| i32::from(v as i8)), + I32Extend16S => exec_op!(unary i32 => i32, |v| i32::from(v as i16)), + I64Extend8S => exec_op!(unary i64 => i64, |v| i64::from(v as i8)), + I64Extend16S => exec_op!(unary i64 => i64, |v| i64::from(v as i16)), + I64Extend32S => exec_op!(unary i64 => i64, |v| i64::from(v as i32)), + I64ExtendI32U => exec_op!(unary u32 => i64, |v| i64::from(v)), + I64ExtendI32S => exec_op!(unary i32 => i64, |v| i64::from(v)), + I32WrapI64 => exec_op!(unary i64 => i32, |v| v as i32), + F32DemoteF64 => exec_op!(unary f64 => f32, |v| v as f32), + F64PromoteF32 => exec_op!(unary f32 => f64, |v| f64::from(v)), + F32Abs => exec_op!(unary f32 => f32, |v| v.abs()), + F64Abs => exec_op!(unary f64 => f64, |v| v.abs()), + F32Neg => exec_op!(unary f32 => f32, |v| -v), + F64Neg => exec_op!(unary f64 => f64, |v| -v), + F32Ceil => exec_op!(unary f32 => f32, |v| v.ceil()), + F64Ceil => exec_op!(unary f64 => f64, |v| v.ceil()), + F32Floor => exec_op!(unary f32 => f32, |v| v.floor()), + F64Floor => exec_op!(unary f64 => f64, |v| v.floor()), + F32Trunc => exec_op!(unary f32 => f32, |v| v.trunc()), + F64Trunc => exec_op!(unary f64 => f64, |v| v.trunc()), + F32Nearest => exec_op!(unary f32 => f32, |v| v.tw_nearest()), + F64Nearest => exec_op!(unary f64 => f64, |v| v.tw_nearest()), + F32Sqrt => exec_op!(unary f32 => f32, |v| v.sqrt()), + F64Sqrt => exec_op!(unary f64 => f64, |v| v.sqrt()), + F32Min => exec_op!(binary f32 => f32, |a, b| a.tw_minimum(b)), + F64Min => exec_op!(binary f64 => f64, |a, b| a.tw_minimum(b)), + F32Max => exec_op!(binary f32 => f32, |a, b| a.tw_maximum(b)), + F64Max => exec_op!(binary f64 => f64, |a, b| a.tw_maximum(b)), + F32Copysign => exec_op!(binary f32 => f32, |a, b| a.copysign(b)), + F64Copysign => exec_op!(binary f64 => f64, |a, b| a.copysign(b)), I32TruncF32S => checked_conv_float!(f32, i32, self), I32TruncF64S => checked_conv_float!(f64, i32, self), I32TruncF32U => checked_conv_float!(f32, u32, i32, self), @@ -586,25 +648,25 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64TruncF64U => checked_conv_float!(f64, u64, i64, self), // Non-trapping float-to-int conversions - I32TruncSatF32S => stack_op!(unary f32 => i32, |v| v.trunc() as i32), - I32TruncSatF32U => stack_op!(unary f32 => u32, |v| v.trunc() as u32), - I32TruncSatF64S => stack_op!(unary f64 => i32, |v| v.trunc() as i32), - I32TruncSatF64U => stack_op!(unary f64 => u32, |v| v.trunc() as u32), - I64TruncSatF32S => stack_op!(unary f32 => i64, |v| v.trunc() as i64), - I64TruncSatF32U => stack_op!(unary f32 => u64, |v| v.trunc() as u64), - I64TruncSatF64S => stack_op!(unary f64 => i64, |v| v.trunc() as i64), - I64TruncSatF64U => stack_op!(unary f64 => u64, |v| v.trunc() as u64), + I32TruncSatF32S => exec_op!(unary f32 => i32, |v| v.trunc() as i32), + I32TruncSatF32U => exec_op!(unary f32 => u32, |v| v.trunc() as u32), + I32TruncSatF64S => exec_op!(unary f64 => i32, |v| v.trunc() as i32), + I32TruncSatF64U => exec_op!(unary f64 => u32, |v| v.trunc() as u32), + I64TruncSatF32S => exec_op!(unary f32 => i64, |v| v.trunc() as i64), + I64TruncSatF32U => exec_op!(unary f32 => u64, |v| v.trunc() as u64), + I64TruncSatF64S => exec_op!(unary f64 => i64, |v| v.trunc() as i64), + I64TruncSatF64U => exec_op!(unary f64 => u64, |v| v.trunc() as u64), // SIMD extension - V128Not => stack_op!(unary Value128, |v| v.v128_not()), - V128And => stack_op!(binary Value128, |a, b| a.v128_and(b)), - V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), - V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), - V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), - V128Bitselect => stack_op!(ternary Value128, |a, b, c| Value128::v128_bitselect(a, b, c)), - V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), - I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), - I8x16RelaxedSwizzle => stack_op!(binary Value128, |a, s| a.i8x16_relaxed_swizzle(s)), + V128Not => exec_op!(unary Value128 => Value128, |v| v.v128_not()), + V128And => exec_op!(binary Value128 => Value128, |a, b| a.v128_and(b)), + V128AndNot => exec_op!(binary Value128 => Value128, |a, b| a.v128_andnot(b)), + V128Or => exec_op!(binary Value128 => Value128, |a, b| a.v128_or(b)), + V128Xor => exec_op!(binary Value128 => Value128, |a, b| a.v128_xor(b)), + V128Bitselect => exec_op!(ternary Value128 => Value128, |a, b, c| Value128::v128_bitselect(a, b, c)), + V128AnyTrue => exec_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), + I8x16Swizzle => exec_op!(binary Value128 => Value128, |a, s| a.i8x16_swizzle(s)), + I8x16RelaxedSwizzle => exec_op!(binary Value128 => Value128, |a, s| a.i8x16_relaxed_swizzle(s)), V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, @@ -623,235 +685,235 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, - Const128(arg) => self.exec_const(Value128(self.func.data.v128_const(*arg)))?, - I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), - I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), - I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), - I16x8ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u16(*lane) as i32), - I32x4ExtractLane(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i32(*lane)), - I64x2ExtractLane(lane) => stack_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), - F32x4ExtractLane(lane) => stack_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), - F64x2ExtractLane(lane) => stack_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), + Const128(arg) => Value128::stack_push(&mut self.store.value_stack, Value128(self.func.data.v128_const(*arg)))?, + I8x16ExtractLaneS(lane) => exec_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), + I8x16ExtractLaneU(lane) => exec_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), + I16x8ExtractLaneS(lane) => exec_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), + I16x8ExtractLaneU(lane) => exec_op!(unary Value128 => i32, |v| v.extract_lane_u16(*lane) as i32), + I32x4ExtractLane(lane) => exec_op!(unary Value128 => i32, |v| v.extract_lane_i32(*lane)), + I64x2ExtractLane(lane) => exec_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), + F32x4ExtractLane(lane) => exec_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), + F64x2ExtractLane(lane) => exec_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - I8x16ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), - I16x8ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), - I32x4ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), - I64x2ReplaceLane(lane) => stack_op!(binary i64, Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), - F32x4ReplaceLane(lane) => stack_op!(binary f32, Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), - F64x2ReplaceLane(lane) => stack_op!(binary f64, Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), - I8x16Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), - I16x8Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), - I32x4Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), - I64x2Splat => stack_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), - F32x4Splat => stack_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), - F64x2Splat => stack_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), - I8x16Eq => stack_op!(binary Value128, |a, b| a.i8x16_eq(b)), - I16x8Eq => stack_op!(binary Value128, |a, b| a.i16x8_eq(b)), - I32x4Eq => stack_op!(binary Value128, |a, b| a.i32x4_eq(b)), - I64x2Eq => stack_op!(binary Value128, |a, b| a.i64x2_eq(b)), - F32x4Eq => stack_op!(binary Value128, |a, b| a.f32x4_eq(b)), - F64x2Eq => stack_op!(binary Value128, |a, b| a.f64x2_eq(b)), - I8x16Ne => stack_op!(binary Value128, |a, b| a.i8x16_ne(b)), - I16x8Ne => stack_op!(binary Value128, |a, b| a.i16x8_ne(b)), - I32x4Ne => stack_op!(binary Value128, |a, b| a.i32x4_ne(b)), - I64x2Ne => stack_op!(binary Value128, |a, b| a.i64x2_ne(b)), - F32x4Ne => stack_op!(binary Value128, |a, b| a.f32x4_ne(b)), - F64x2Ne => stack_op!(binary Value128, |a, b| a.f64x2_ne(b)), - I8x16LtS => stack_op!(binary Value128, |a, b| a.i8x16_lt_s(b)), - I16x8LtS => stack_op!(binary Value128, |a, b| a.i16x8_lt_s(b)), - I32x4LtS => stack_op!(binary Value128, |a, b| a.i32x4_lt_s(b)), - I64x2LtS => stack_op!(binary Value128, |a, b| a.i64x2_lt_s(b)), - I8x16LtU => stack_op!(binary Value128, |a, b| a.i8x16_lt_u(b)), - I16x8LtU => stack_op!(binary Value128, |a, b| a.i16x8_lt_u(b)), - I32x4LtU => stack_op!(binary Value128, |a, b| a.i32x4_lt_u(b)), - F32x4Lt => stack_op!(binary Value128, |a, b| a.f32x4_lt(b)), - F64x2Lt => stack_op!(binary Value128, |a, b| a.f64x2_lt(b)), - F32x4Gt => stack_op!(binary Value128, |a, b| a.f32x4_gt(b)), - F64x2Gt => stack_op!(binary Value128, |a, b| a.f64x2_gt(b)), - I8x16GtS => stack_op!(binary Value128, |a, b| a.i8x16_gt_s(b)), - I16x8GtS => stack_op!(binary Value128, |a, b| a.i16x8_gt_s(b)), - I32x4GtS => stack_op!(binary Value128, |a, b| a.i32x4_gt_s(b)), - I64x2GtS => stack_op!(binary Value128, |a, b| a.i64x2_gt_s(b)), - I64x2LeS => stack_op!(binary Value128, |a, b| a.i64x2_le_s(b)), - F32x4Le => stack_op!(binary Value128, |a, b| a.f32x4_le(b)), - F64x2Le => stack_op!(binary Value128, |a, b| a.f64x2_le(b)), - I8x16GtU => stack_op!(binary Value128, |a, b| a.i8x16_gt_u(b)), - I16x8GtU => stack_op!(binary Value128, |a, b| a.i16x8_gt_u(b)), - I32x4GtU => stack_op!(binary Value128, |a, b| a.i32x4_gt_u(b)), - F32x4Ge => stack_op!(binary Value128, |a, b| a.f32x4_ge(b)), - F64x2Ge => stack_op!(binary Value128, |a, b| a.f64x2_ge(b)), - I8x16LeS => stack_op!(binary Value128, |a, b| a.i8x16_le_s(b)), - I16x8LeS => stack_op!(binary Value128, |a, b| a.i16x8_le_s(b)), - I32x4LeS => stack_op!(binary Value128, |a, b| a.i32x4_le_s(b)), - I8x16LeU => stack_op!(binary Value128, |a, b| a.i8x16_le_u(b)), - I16x8LeU => stack_op!(binary Value128, |a, b| a.i16x8_le_u(b)), - I32x4LeU => stack_op!(binary Value128, |a, b| a.i32x4_le_u(b)), - I8x16GeS => stack_op!(binary Value128, |a, b| a.i8x16_ge_s(b)), - I16x8GeS => stack_op!(binary Value128, |a, b| a.i16x8_ge_s(b)), - I32x4GeS => stack_op!(binary Value128, |a, b| a.i32x4_ge_s(b)), - I64x2GeS => stack_op!(binary Value128, |a, b| a.i64x2_ge_s(b)), - I8x16GeU => stack_op!(binary Value128, |a, b| a.i8x16_ge_u(b)), - I16x8GeU => stack_op!(binary Value128, |a, b| a.i16x8_ge_u(b)), - I32x4GeU => stack_op!(binary Value128, |a, b| a.i32x4_ge_u(b)), - I8x16Abs => stack_op!(unary Value128, |a| a.i8x16_abs()), - I16x8Abs => stack_op!(unary Value128, |a| a.i16x8_abs()), - I32x4Abs => stack_op!(unary Value128, |a| a.i32x4_abs()), - I64x2Abs => stack_op!(unary Value128, |a| a.i64x2_abs()), - I8x16Neg => stack_op!(unary Value128, |a| a.i8x16_neg()), - I16x8Neg => stack_op!(unary Value128, |a| a.i16x8_neg()), - I32x4Neg => stack_op!(unary Value128, |a| a.i32x4_neg()), - I64x2Neg => stack_op!(unary Value128, |a| a.i64x2_neg()), - I8x16AllTrue => stack_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), - I16x8AllTrue => stack_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), - I32x4AllTrue => stack_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), - I64x2AllTrue => stack_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), - I8x16Bitmask => stack_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), - I16x8Bitmask => stack_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), - I32x4Bitmask => stack_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), - I64x2Bitmask => stack_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), - I8x16Shl => stack_op!(binary i32, Value128, |a, b| b.i8x16_shl(a as u32)), - I16x8Shl => stack_op!(binary i32, Value128, |a, b| b.i16x8_shl(a as u32)), - I32x4Shl => stack_op!(binary i32, Value128, |a, b| b.i32x4_shl(a as u32)), - I64x2Shl => stack_op!(binary i32, Value128, |a, b| b.i64x2_shl(a as u32)), - I8x16ShrS => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_s(a as u32)), - I16x8ShrS => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_s(a as u32)), - I32x4ShrS => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_s(a as u32)), - I64x2ShrS => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_s(a as u32)), - I8x16ShrU => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_u(a as u32)), - I16x8ShrU => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_u(a as u32)), - I32x4ShrU => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_u(a as u32)), - I64x2ShrU => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_u(a as u32)), - I8x16Add => stack_op!(binary Value128, |a, b| a.i8x16_add(b)), - I16x8Add => stack_op!(binary Value128, |a, b| a.i16x8_add(b)), - I32x4Add => stack_op!(binary Value128, |a, b| a.i32x4_add(b)), - I64x2Add => stack_op!(binary Value128, |a, b| a.i64x2_add(b)), - I8x16Sub => stack_op!(binary Value128, |a, b| a.i8x16_sub(b)), - I16x8Sub => stack_op!(binary Value128, |a, b| a.i16x8_sub(b)), - I32x4Sub => stack_op!(binary Value128, |a, b| a.i32x4_sub(b)), - I64x2Sub => stack_op!(binary Value128, |a, b| a.i64x2_sub(b)), - I8x16MinS => stack_op!(binary Value128, |a, b| a.i8x16_min_s(b)), - I16x8MinS => stack_op!(binary Value128, |a, b| a.i16x8_min_s(b)), - I32x4MinS => stack_op!(binary Value128, |a, b| a.i32x4_min_s(b)), - I8x16MinU => stack_op!(binary Value128, |a, b| a.i8x16_min_u(b)), - I16x8MinU => stack_op!(binary Value128, |a, b| a.i16x8_min_u(b)), - I32x4MinU => stack_op!(binary Value128, |a, b| a.i32x4_min_u(b)), - I8x16MaxS => stack_op!(binary Value128, |a, b| a.i8x16_max_s(b)), - I16x8MaxS => stack_op!(binary Value128, |a, b| a.i16x8_max_s(b)), - I32x4MaxS => stack_op!(binary Value128, |a, b| a.i32x4_max_s(b)), - I8x16MaxU => stack_op!(binary Value128, |a, b| a.i8x16_max_u(b)), - I16x8MaxU => stack_op!(binary Value128, |a, b| a.i16x8_max_u(b)), - I32x4MaxU => stack_op!(binary Value128, |a, b| a.i32x4_max_u(b)), - I64x2Mul => stack_op!(binary Value128, |a, b| a.i64x2_mul(b)), - I16x8Mul => stack_op!(binary Value128, |a, b| a.i16x8_mul(b)), - I32x4Mul => stack_op!(binary Value128, |a, b| a.i32x4_mul(b)), - I8x16NarrowI16x8S => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), - I8x16NarrowI16x8U => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), - I16x8NarrowI32x4S => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), - I16x8NarrowI32x4U => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), - I8x16AddSatS => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_s(b)), - I16x8AddSatS => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_s(b)), - I8x16AddSatU => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_u(b)), - I16x8AddSatU => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_u(b)), - I8x16SubSatS => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_s(b)), - I16x8SubSatS => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_s(b)), - I8x16SubSatU => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_u(b)), - I16x8SubSatU => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_u(b)), - I8x16AvgrU => stack_op!(binary Value128, |a, b| a.i8x16_avgr_u(b)), - I16x8AvgrU => stack_op!(binary Value128, |a, b| a.i16x8_avgr_u(b)), - I16x8ExtAddPairwiseI8x16S => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), - I16x8ExtAddPairwiseI8x16U => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), - I32x4ExtAddPairwiseI16x8S => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), - I32x4ExtAddPairwiseI16x8U => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), - I16x8ExtMulLowI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), - I16x8ExtMulLowI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), - I16x8ExtMulHighI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), - I16x8ExtMulHighI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_u(b)), - I32x4ExtMulLowI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_s(b)), - I32x4ExtMulLowI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_u(b)), - I32x4ExtMulHighI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_s(b)), - I32x4ExtMulHighI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_u(b)), - I64x2ExtMulLowI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_s(b)), - I64x2ExtMulLowI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), - I64x2ExtMulHighI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), - I64x2ExtMulHighI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), - I16x8ExtendLowI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_s()), - I16x8ExtendLowI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_u()), - I16x8ExtendHighI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_s()), - I16x8ExtendHighI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_u()), - I32x4ExtendLowI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_s()), - I32x4ExtendLowI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_u()), - I32x4ExtendHighI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_s()), - I32x4ExtendHighI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_u()), - I64x2ExtendLowI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_s()), - I64x2ExtendLowI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_u()), - I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), - I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), - I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), - I8x16Shuffle(idx) => stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, Value128(self.func.data.v128_const(*idx)))), - I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), - I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), - I8x16RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i8x16_relaxed_laneselect(a, b, c)), - I16x8RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i16x8_relaxed_laneselect(a, b, c)), - I32x4RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i32x4_relaxed_laneselect(a, b, c)), - I64x2RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i64x2_relaxed_laneselect(a, b, c)), - I16x8RelaxedQ15mulrS => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_q15mulr_s(b)), - I16x8RelaxedDotI8x16I7x16S => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_dot_i8x16_i7x16_s(b)), - I32x4RelaxedDotI8x16I7x16AddS => stack_op!(ternary Value128, |a, b, c| a.i32x4_relaxed_dot_i8x16_i7x16_add_s(b, c)), - F32x4Ceil => stack_op!(unary Value128, |v| v.f32x4_ceil()), - F64x2Ceil => stack_op!(unary Value128, |v| v.f64x2_ceil()), - F32x4Floor => stack_op!(unary Value128, |v| v.f32x4_floor()), - F64x2Floor => stack_op!(unary Value128, |v| v.f64x2_floor()), - F32x4Trunc => stack_op!(unary Value128, |v| v.f32x4_trunc()), - F64x2Trunc => stack_op!(unary Value128, |v| v.f64x2_trunc()), - F32x4Nearest => stack_op!(unary Value128, |v| v.f32x4_nearest()), - F64x2Nearest => stack_op!(unary Value128, |v| v.f64x2_nearest()), - F32x4Abs => stack_op!(unary Value128, |v| v.f32x4_abs()), - F64x2Abs => stack_op!(unary Value128, |v| v.f64x2_abs()), - F32x4Neg => stack_op!(unary Value128, |v| v.f32x4_neg()), - F64x2Neg => stack_op!(unary Value128, |v| v.f64x2_neg()), - F32x4Sqrt => stack_op!(unary Value128, |v| v.f32x4_sqrt()), - F64x2Sqrt => stack_op!(unary Value128, |v| v.f64x2_sqrt()), - F32x4Add => stack_op!(binary Value128, |a, b| a.f32x4_add(b)), - F64x2Add => stack_op!(binary Value128, |a, b| a.f64x2_add(b)), - F32x4Sub => stack_op!(binary Value128, |a, b| a.f32x4_sub(b)), - F64x2Sub => stack_op!(binary Value128, |a, b| a.f64x2_sub(b)), - F32x4Mul => stack_op!(binary Value128, |a, b| a.f32x4_mul(b)), - F64x2Mul => stack_op!(binary Value128, |a, b| a.f64x2_mul(b)), - F32x4Div => stack_op!(binary Value128, |a, b| a.f32x4_div(b)), - F64x2Div => stack_op!(binary Value128, |a, b| a.f64x2_div(b)), - F32x4Min => stack_op!(binary Value128, |a, b| a.f32x4_min(b)), - F64x2Min => stack_op!(binary Value128, |a, b| a.f64x2_min(b)), - F32x4Max => stack_op!(binary Value128, |a, b| a.f32x4_max(b)), - F64x2Max => stack_op!(binary Value128, |a, b| a.f64x2_max(b)), - F32x4PMin => stack_op!(binary Value128, |a, b| a.f32x4_pmin(b)), - F32x4PMax => stack_op!(binary Value128, |a, b| a.f32x4_pmax(b)), - F64x2PMin => stack_op!(binary Value128, |a, b| a.f64x2_pmin(b)), - F64x2PMax => stack_op!(binary Value128, |a, b| a.f64x2_pmax(b)), - F32x4RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_madd(b, c)), - F32x4RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_nmadd(b, c)), - F64x2RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_madd(b, c)), - F64x2RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_nmadd(b, c)), - F32x4RelaxedMin => stack_op!(binary Value128, |a, b| a.f32x4_relaxed_min(b)), - F32x4RelaxedMax => stack_op!(binary Value128, |a, b| a.f32x4_relaxed_max(b)), - F64x2RelaxedMin => stack_op!(binary Value128, |a, b| a.f64x2_relaxed_min(b)), - F64x2RelaxedMax => stack_op!(binary Value128, |a, b| a.f64x2_relaxed_max(b)), - I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), - I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), - F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), - F32x4ConvertI32x4U => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_u()), - F64x2ConvertLowI32x4S => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_s()), - F64x2ConvertLowI32x4U => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_u()), - F32x4DemoteF64x2Zero => stack_op!(unary Value128, |v| v.f32x4_demote_f64x2_zero()), - F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), - I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), - I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), - I32x4RelaxedTruncF32x4S => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f32x4_s()), - I32x4RelaxedTruncF32x4U => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f32x4_u()), - I32x4RelaxedTruncF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_s_zero()), - I32x4RelaxedTruncF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_u_zero()), + I8x16ReplaceLane(lane) => exec_op!(binary_mixed i32, Value128 => Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), + I16x8ReplaceLane(lane) => exec_op!(binary_mixed i32, Value128 => Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), + I32x4ReplaceLane(lane) => exec_op!(binary_mixed i32, Value128 => Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), + I64x2ReplaceLane(lane) => exec_op!(binary_mixed i64, Value128 => Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), + F32x4ReplaceLane(lane) => exec_op!(binary_mixed f32, Value128 => Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), + F64x2ReplaceLane(lane) => exec_op!(binary_mixed f64, Value128 => Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), + I8x16Splat => exec_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), + I16x8Splat => exec_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), + I32x4Splat => exec_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), + I64x2Splat => exec_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), + F32x4Splat => exec_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), + F64x2Splat => exec_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), + I8x16Eq => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_eq(b)), + I16x8Eq => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_eq(b)), + I32x4Eq => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_eq(b)), + I64x2Eq => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_eq(b)), + F32x4Eq => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_eq(b)), + F64x2Eq => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_eq(b)), + I8x16Ne => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_ne(b)), + I16x8Ne => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_ne(b)), + I32x4Ne => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_ne(b)), + I64x2Ne => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_ne(b)), + F32x4Ne => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_ne(b)), + F64x2Ne => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_ne(b)), + I8x16LtS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_lt_s(b)), + I16x8LtS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_lt_s(b)), + I32x4LtS => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_lt_s(b)), + I64x2LtS => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_lt_s(b)), + I8x16LtU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_lt_u(b)), + I16x8LtU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_lt_u(b)), + I32x4LtU => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_lt_u(b)), + F32x4Lt => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_lt(b)), + F64x2Lt => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_lt(b)), + F32x4Gt => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_gt(b)), + F64x2Gt => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_gt(b)), + I8x16GtS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_gt_s(b)), + I16x8GtS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_gt_s(b)), + I32x4GtS => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_gt_s(b)), + I64x2GtS => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_gt_s(b)), + I64x2LeS => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_le_s(b)), + F32x4Le => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_le(b)), + F64x2Le => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_le(b)), + I8x16GtU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_gt_u(b)), + I16x8GtU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_gt_u(b)), + I32x4GtU => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_gt_u(b)), + F32x4Ge => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_ge(b)), + F64x2Ge => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_ge(b)), + I8x16LeS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_le_s(b)), + I16x8LeS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_le_s(b)), + I32x4LeS => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_le_s(b)), + I8x16LeU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_le_u(b)), + I16x8LeU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_le_u(b)), + I32x4LeU => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_le_u(b)), + I8x16GeS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_ge_s(b)), + I16x8GeS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_ge_s(b)), + I32x4GeS => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_ge_s(b)), + I64x2GeS => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_ge_s(b)), + I8x16GeU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_ge_u(b)), + I16x8GeU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_ge_u(b)), + I32x4GeU => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_ge_u(b)), + I8x16Abs => exec_op!(unary Value128 => Value128, |a| a.i8x16_abs()), + I16x8Abs => exec_op!(unary Value128 => Value128, |a| a.i16x8_abs()), + I32x4Abs => exec_op!(unary Value128 => Value128, |a| a.i32x4_abs()), + I64x2Abs => exec_op!(unary Value128 => Value128, |a| a.i64x2_abs()), + I8x16Neg => exec_op!(unary Value128 => Value128, |a| a.i8x16_neg()), + I16x8Neg => exec_op!(unary Value128 => Value128, |a| a.i16x8_neg()), + I32x4Neg => exec_op!(unary Value128 => Value128, |a| a.i32x4_neg()), + I64x2Neg => exec_op!(unary Value128 => Value128, |a| a.i64x2_neg()), + I8x16AllTrue => exec_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), + I16x8AllTrue => exec_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), + I32x4AllTrue => exec_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), + I64x2AllTrue => exec_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), + I8x16Bitmask => exec_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), + I16x8Bitmask => exec_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), + I32x4Bitmask => exec_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), + I64x2Bitmask => exec_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), + I8x16Shl => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i8x16_shl(a as u32)), + I16x8Shl => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i16x8_shl(a as u32)), + I32x4Shl => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i32x4_shl(a as u32)), + I64x2Shl => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i64x2_shl(a as u32)), + I8x16ShrS => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i8x16_shr_s(a as u32)), + I16x8ShrS => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i16x8_shr_s(a as u32)), + I32x4ShrS => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i32x4_shr_s(a as u32)), + I64x2ShrS => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i64x2_shr_s(a as u32)), + I8x16ShrU => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i8x16_shr_u(a as u32)), + I16x8ShrU => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i16x8_shr_u(a as u32)), + I32x4ShrU => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i32x4_shr_u(a as u32)), + I64x2ShrU => exec_op!(binary_mixed i32, Value128 => Value128, |a, b| b.i64x2_shr_u(a as u32)), + I8x16Add => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_add(b)), + I16x8Add => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_add(b)), + I32x4Add => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_add(b)), + I64x2Add => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_add(b)), + I8x16Sub => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_sub(b)), + I16x8Sub => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_sub(b)), + I32x4Sub => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_sub(b)), + I64x2Sub => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_sub(b)), + I8x16MinS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_min_s(b)), + I16x8MinS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_min_s(b)), + I32x4MinS => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_min_s(b)), + I8x16MinU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_min_u(b)), + I16x8MinU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_min_u(b)), + I32x4MinU => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_min_u(b)), + I8x16MaxS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_max_s(b)), + I16x8MaxS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_max_s(b)), + I32x4MaxS => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_max_s(b)), + I8x16MaxU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_max_u(b)), + I16x8MaxU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_max_u(b)), + I32x4MaxU => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_max_u(b)), + I64x2Mul => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_mul(b)), + I16x8Mul => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_mul(b)), + I32x4Mul => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_mul(b)), + I8x16NarrowI16x8S => exec_op!(binary Value128 => Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), + I8x16NarrowI16x8U => exec_op!(binary Value128 => Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), + I16x8NarrowI32x4S => exec_op!(binary Value128 => Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), + I16x8NarrowI32x4U => exec_op!(binary Value128 => Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), + I8x16AddSatS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_add_sat_s(b)), + I16x8AddSatS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_add_sat_s(b)), + I8x16AddSatU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_add_sat_u(b)), + I16x8AddSatU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_add_sat_u(b)), + I8x16SubSatS => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_sub_sat_s(b)), + I16x8SubSatS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_sub_sat_s(b)), + I8x16SubSatU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_sub_sat_u(b)), + I16x8SubSatU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_sub_sat_u(b)), + I8x16AvgrU => exec_op!(binary Value128 => Value128, |a, b| a.i8x16_avgr_u(b)), + I16x8AvgrU => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_avgr_u(b)), + I16x8ExtAddPairwiseI8x16S => exec_op!(unary Value128 => Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), + I16x8ExtAddPairwiseI8x16U => exec_op!(unary Value128 => Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), + I32x4ExtAddPairwiseI16x8S => exec_op!(unary Value128 => Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), + I32x4ExtAddPairwiseI16x8U => exec_op!(unary Value128 => Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), + I16x8ExtMulLowI8x16S => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), + I16x8ExtMulLowI8x16U => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), + I16x8ExtMulHighI8x16S => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), + I16x8ExtMulHighI8x16U => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_extmul_high_i8x16_u(b)), + I32x4ExtMulLowI16x8S => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_extmul_low_i16x8_s(b)), + I32x4ExtMulLowI16x8U => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_extmul_low_i16x8_u(b)), + I32x4ExtMulHighI16x8S => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_extmul_high_i16x8_s(b)), + I32x4ExtMulHighI16x8U => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_extmul_high_i16x8_u(b)), + I64x2ExtMulLowI32x4S => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_extmul_low_i32x4_s(b)), + I64x2ExtMulLowI32x4U => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), + I64x2ExtMulHighI32x4S => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), + I64x2ExtMulHighI32x4U => exec_op!(binary Value128 => Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), + I16x8ExtendLowI8x16S => exec_op!(unary Value128 => Value128, |a| a.i16x8_extend_low_i8x16_s()), + I16x8ExtendLowI8x16U => exec_op!(unary Value128 => Value128, |a| a.i16x8_extend_low_i8x16_u()), + I16x8ExtendHighI8x16S => exec_op!(unary Value128 => Value128, |a| a.i16x8_extend_high_i8x16_s()), + I16x8ExtendHighI8x16U => exec_op!(unary Value128 => Value128, |a| a.i16x8_extend_high_i8x16_u()), + I32x4ExtendLowI16x8S => exec_op!(unary Value128 => Value128, |a| a.i32x4_extend_low_i16x8_s()), + I32x4ExtendLowI16x8U => exec_op!(unary Value128 => Value128, |a| a.i32x4_extend_low_i16x8_u()), + I32x4ExtendHighI16x8S => exec_op!(unary Value128 => Value128, |a| a.i32x4_extend_high_i16x8_s()), + I32x4ExtendHighI16x8U => exec_op!(unary Value128 => Value128, |a| a.i32x4_extend_high_i16x8_u()), + I64x2ExtendLowI32x4S => exec_op!(unary Value128 => Value128, |a| a.i64x2_extend_low_i32x4_s()), + I64x2ExtendLowI32x4U => exec_op!(unary Value128 => Value128, |a| a.i64x2_extend_low_i32x4_u()), + I64x2ExtendHighI32x4S => exec_op!(unary Value128 => Value128, |a| a.i64x2_extend_high_i32x4_s()), + I64x2ExtendHighI32x4U => exec_op!(unary Value128 => Value128, |a| a.i64x2_extend_high_i32x4_u()), + I8x16Popcnt => exec_op!(unary Value128 => Value128, |v| v.i8x16_popcnt()), + I8x16Shuffle(idx) => exec_op!(binary Value128 => Value128, |a, b| Value128::i8x16_shuffle(a, b, Value128(self.func.data.v128_const(*idx)))), + I16x8Q15MulrSatS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), + I32x4DotI16x8S => exec_op!(binary Value128 => Value128, |a, b| a.i32x4_dot_i16x8_s(b)), + I8x16RelaxedLaneselect => exec_op!(ternary Value128 => Value128, |a, b, c| Value128::i8x16_relaxed_laneselect(a, b, c)), + I16x8RelaxedLaneselect => exec_op!(ternary Value128 => Value128, |a, b, c| Value128::i16x8_relaxed_laneselect(a, b, c)), + I32x4RelaxedLaneselect => exec_op!(ternary Value128 => Value128, |a, b, c| Value128::i32x4_relaxed_laneselect(a, b, c)), + I64x2RelaxedLaneselect => exec_op!(ternary Value128 => Value128, |a, b, c| Value128::i64x2_relaxed_laneselect(a, b, c)), + I16x8RelaxedQ15mulrS => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_relaxed_q15mulr_s(b)), + I16x8RelaxedDotI8x16I7x16S => exec_op!(binary Value128 => Value128, |a, b| a.i16x8_relaxed_dot_i8x16_i7x16_s(b)), + I32x4RelaxedDotI8x16I7x16AddS => exec_op!(ternary Value128 => Value128, |a, b, c| a.i32x4_relaxed_dot_i8x16_i7x16_add_s(b, c)), + F32x4Ceil => exec_op!(unary Value128 => Value128, |v| v.f32x4_ceil()), + F64x2Ceil => exec_op!(unary Value128 => Value128, |v| v.f64x2_ceil()), + F32x4Floor => exec_op!(unary Value128 => Value128, |v| v.f32x4_floor()), + F64x2Floor => exec_op!(unary Value128 => Value128, |v| v.f64x2_floor()), + F32x4Trunc => exec_op!(unary Value128 => Value128, |v| v.f32x4_trunc()), + F64x2Trunc => exec_op!(unary Value128 => Value128, |v| v.f64x2_trunc()), + F32x4Nearest => exec_op!(unary Value128 => Value128, |v| v.f32x4_nearest()), + F64x2Nearest => exec_op!(unary Value128 => Value128, |v| v.f64x2_nearest()), + F32x4Abs => exec_op!(unary Value128 => Value128, |v| v.f32x4_abs()), + F64x2Abs => exec_op!(unary Value128 => Value128, |v| v.f64x2_abs()), + F32x4Neg => exec_op!(unary Value128 => Value128, |v| v.f32x4_neg()), + F64x2Neg => exec_op!(unary Value128 => Value128, |v| v.f64x2_neg()), + F32x4Sqrt => exec_op!(unary Value128 => Value128, |v| v.f32x4_sqrt()), + F64x2Sqrt => exec_op!(unary Value128 => Value128, |v| v.f64x2_sqrt()), + F32x4Add => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_add(b)), + F64x2Add => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_add(b)), + F32x4Sub => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_sub(b)), + F64x2Sub => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_sub(b)), + F32x4Mul => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_mul(b)), + F64x2Mul => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_mul(b)), + F32x4Div => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_div(b)), + F64x2Div => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_div(b)), + F32x4Min => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_min(b)), + F64x2Min => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_min(b)), + F32x4Max => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_max(b)), + F64x2Max => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_max(b)), + F32x4PMin => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_pmin(b)), + F32x4PMax => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_pmax(b)), + F64x2PMin => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_pmin(b)), + F64x2PMax => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_pmax(b)), + F32x4RelaxedMadd => exec_op!(ternary Value128 => Value128, |a, b, c| a.f32x4_relaxed_madd(b, c)), + F32x4RelaxedNmadd => exec_op!(ternary Value128 => Value128, |a, b, c| a.f32x4_relaxed_nmadd(b, c)), + F64x2RelaxedMadd => exec_op!(ternary Value128 => Value128, |a, b, c| a.f64x2_relaxed_madd(b, c)), + F64x2RelaxedNmadd => exec_op!(ternary Value128 => Value128, |a, b, c| a.f64x2_relaxed_nmadd(b, c)), + F32x4RelaxedMin => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_relaxed_min(b)), + F32x4RelaxedMax => exec_op!(binary Value128 => Value128, |a, b| a.f32x4_relaxed_max(b)), + F64x2RelaxedMin => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_relaxed_min(b)), + F64x2RelaxedMax => exec_op!(binary Value128 => Value128, |a, b| a.f64x2_relaxed_max(b)), + I32x4TruncSatF32x4S => exec_op!(unary Value128 => Value128, |v| v.i32x4_trunc_sat_f32x4_s()), + I32x4TruncSatF32x4U => exec_op!(unary Value128 => Value128, |v| v.i32x4_trunc_sat_f32x4_u()), + F32x4ConvertI32x4S => exec_op!(unary Value128 => Value128, |v| v.f32x4_convert_i32x4_s()), + F32x4ConvertI32x4U => exec_op!(unary Value128 => Value128, |v| v.f32x4_convert_i32x4_u()), + F64x2ConvertLowI32x4S => exec_op!(unary Value128 => Value128, |v| v.f64x2_convert_low_i32x4_s()), + F64x2ConvertLowI32x4U => exec_op!(unary Value128 => Value128, |v| v.f64x2_convert_low_i32x4_u()), + F32x4DemoteF64x2Zero => exec_op!(unary Value128 => Value128, |v| v.f32x4_demote_f64x2_zero()), + F64x2PromoteLowF32x4 => exec_op!(unary Value128 => Value128, |v| v.f64x2_promote_low_f32x4()), + I32x4TruncSatF64x2SZero => exec_op!(unary Value128 => Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), + I32x4TruncSatF64x2UZero => exec_op!(unary Value128 => Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), + I32x4RelaxedTruncF32x4S => exec_op!(unary Value128 => Value128, |v| v.i32x4_relaxed_trunc_f32x4_s()), + I32x4RelaxedTruncF32x4U => exec_op!(unary Value128 => Value128, |v| v.i32x4_relaxed_trunc_f32x4_u()), + I32x4RelaxedTruncF64x2SZero => exec_op!(unary Value128 => Value128, |v| v.i32x4_relaxed_trunc_f64x2_s_zero()), + I32x4RelaxedTruncF64x2UZero => exec_op!(unary Value128 => Value128, |v| v.i32x4_relaxed_trunc_f64x2_u_zero()), }; self.cf.instr_ptr = instr_ptr + 1; @@ -859,148 +921,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(None) } - #[inline(always)] - fn jump_if(&mut self, condition: bool, ip: u32) -> bool { - if condition { - self.cf.instr_ptr = ip as usize; - } - condition - } - - #[inline(always)] - fn exec_jump_zero_32(&mut self, target_ip: u32) -> bool { - let cond = ::stack_pop(&mut self.store.value_stack) == 0; - self.jump_if(cond, target_ip) - } - - #[inline(always)] - fn exec_jump_non_zero_32(&mut self, target_ip: u32) -> bool { - let cond = ::stack_pop(&mut self.store.value_stack) != 0; - self.jump_if(cond, target_ip) - } - - #[inline(always)] - fn exec_jump_zero_64(&mut self, target_ip: u32) -> bool { - let cond = ::stack_pop(&mut self.store.value_stack) == 0; - self.jump_if(cond, target_ip) - } - - #[inline(always)] - fn exec_jump_non_zero_64(&mut self, target_ip: u32) -> bool { - let cond = ::stack_pop(&mut self.store.value_stack) != 0; - self.jump_if(cond, target_ip) - } - - #[inline(always)] - fn exec_jump_ref_null(&mut self, target_ip: u32) -> bool { - let is_null = ValueRef::stack_peek(&self.store.value_stack).is_null(); - if is_null { - ValueRef::stack_pop(&mut self.store.value_stack); - } - self.jump_if(is_null, target_ip) - } - - #[inline(always)] - fn exec_jump_ref_non_null(&mut self, target_ip: u32) -> bool { - let is_non_null = !ValueRef::stack_peek(&self.store.value_stack).is_null(); - if !is_non_null { - ValueRef::stack_pop(&mut self.store.value_stack); - } - self.jump_if(is_non_null, target_ip) - } - - #[inline(always)] - fn exec_jump_local_zero_32(&mut self, target_ip: u32, local: LocalAddr) -> bool { - self.jump_if(Value32::local_get(&self.store.value_stack, &self.cf, local) == 0, target_ip) - } - - #[inline(always)] - fn exec_jump_local_non_zero_32(&mut self, target_ip: u32, local: LocalAddr) -> bool { - self.jump_if(Value32::local_get(&self.store.value_stack, &self.cf, local) != 0, target_ip) - } - - #[inline(always)] - fn exec_jump_local_zero_64(&mut self, target_ip: u32, local: LocalAddr) -> bool { - self.jump_if(Value64::local_get(&self.store.value_stack, &self.cf, local) == 0, target_ip) - } - - #[inline(always)] - fn exec_jump_local_non_zero_64(&mut self, target_ip: u32, local: LocalAddr) -> bool { - self.jump_if(Value64::local_get(&self.store.value_stack, &self.cf, local) != 0, target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_stack_const_32(&mut self, target_ip: u32, imm: i32, op: CmpOp) -> bool { - let condition = cmp_i32(::stack_pop(&mut self.store.value_stack), imm, op); - self.jump_if(condition, target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_stack_const_64(&mut self, target_ip: u32, imm: i64, op: CmpOp) -> bool { - let condition = cmp_i64(::stack_pop(&mut self.store.value_stack), imm, op); - self.jump_if(condition, target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_stack_local_32(&mut self, target_ip: u32, local: LocalAddr, op: CmpOp) -> bool { - let lhs = ::stack_pop(&mut self.store.value_stack); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, local); - self.jump_if(cmp_i32(lhs, rhs, op), target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_stack_local_64(&mut self, target_ip: u32, local: LocalAddr, op: CmpOp) -> bool { - let lhs = ::stack_pop(&mut self.store.value_stack); - let rhs = i64::local_get(&self.store.value_stack, &self.cf, local); - self.jump_if(cmp_i64(lhs, rhs, op), target_ip) - } - - #[inline(always)] - fn exec_inc_local_jump_32(&mut self, target_ip: u32, local: LocalAddr, delta: i32, on_zero: bool) -> bool { - let value = i32::local_get(&self.store.value_stack, &self.cf, local).wrapping_add(delta); - i32::local_set(&mut self.store.value_stack, &self.cf, local, value); - self.jump_if((value == 0) == on_zero, target_ip) - } - - #[inline(always)] - fn exec_inc_local_jump_cmp_local_32( - &mut self, - target_ip: u32, - local: LocalAddr, - delta: i32, - right: LocalAddr, - op: CmpOp, - ) -> bool { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, local).wrapping_add(delta); - i32::local_set(&mut self.store.value_stack, &self.cf, local, lhs); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, right); - self.jump_if(cmp_i32(lhs, rhs, op), target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_local_const_32(&mut self, target_ip: u32, local: LocalAddr, imm: i32, op: CmpOp) -> bool { - self.jump_if(cmp_i32(i32::local_get(&self.store.value_stack, &self.cf, local), imm, op), target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_local_const_64(&mut self, target_ip: u32, local: LocalAddr, imm: i32, op: CmpOp) -> bool { - self.jump_if(cmp_i64(i64::local_get(&self.store.value_stack, &self.cf, local), i64::from(imm), op), target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_local_local_32(&mut self, target_ip: u32, left: LocalAddr, right: LocalAddr, op: CmpOp) -> bool { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, left); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, right); - self.jump_if(cmp_i32(lhs, rhs, op), target_ip) - } - - #[inline(always)] - fn exec_jump_cmp_local_local_64(&mut self, target_ip: u32, left: LocalAddr, right: LocalAddr, op: CmpOp) -> bool { - let lhs = i64::local_get(&self.store.value_stack, &self.cf, left); - let rhs = i64::local_get(&self.store.value_stack, &self.cf, right); - self.jump_if(cmp_i64(lhs, rhs, op), target_ip) - } - fn exec_branch_table(&mut self, default_ip: u32, start: u32, len: u32) { let idx = ::stack_pop(&mut self.store.value_stack); let target_ip = if idx >= 0 && (idx as u32) < len { @@ -1453,18 +1373,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } - #[inline(always)] - fn exec_binop_acc_local(&mut self, acc: LocalAddr, mul: M, add: A) - where - T: InternalValue, - M: Fn(T, T) -> T, - A: Fn(T, T) -> T, - { - let rhs = T::stack_pop(&mut self.store.value_stack); - let lhs = T::stack_pop(&mut self.store.value_stack); - T::local_update(&mut self.store.value_stack, &self.cf, acc, |v| add(mul(lhs, rhs), v)); - } - #[inline(always)] fn exec_load_local_value, const N: usize>( &self, @@ -1520,10 +1428,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } - fn exec_const(&mut self, val: T) -> Result<(), Trap> { - self.store.value_stack.push(val) - } - fn exec_ref_is_null(&mut self) -> Result<(), Trap> { let is_null = i32::from(::stack_pop(&mut self.store.value_stack).is_null()); self.store.value_stack.push::(is_null) @@ -1540,7 +1444,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn canonical_ref_type(&self, ty: RefType) -> RefType { let Some(type_index) = ty.type_index() else { return ty }; RefType::new_concrete(ty.is_nullable(), self.module.resolve_type_addr(type_index)) - .expect("canonical type address fits in a reference") } fn exec_ref_matches(&self, ty: RefType) -> bool { @@ -2171,3 +2074,60 @@ fn cmp_i64(lhs: i64, rhs: i64, op: CmpOp) -> bool { CmpOp::GeU => (lhs as u64) >= (rhs as u64), } } + +fn exec_binop_32(op: BinOp, lhs: u32, rhs: u32) -> u32 { + match op { + BinOp::IAdd => lhs.wrapping_add(rhs), + BinOp::ISub => lhs.wrapping_sub(rhs), + BinOp::IMul => lhs.wrapping_mul(rhs), + BinOp::IAnd => lhs & rhs, + BinOp::IOr => lhs | rhs, + BinOp::IXor => lhs ^ rhs, + BinOp::IShl => (lhs as i32).wrapping_shl(rhs) as u32, + BinOp::IShrS => (lhs as i32).wrapping_shr(rhs) as u32, + BinOp::IShrU => lhs.wrapping_shr(rhs), + BinOp::IRotl => (lhs as i32).rotate_left(rhs) as u32, + BinOp::IRotr => (lhs as i32).rotate_right(rhs) as u32, + BinOp::FAdd => (f32::from_bits(lhs) + f32::from_bits(rhs)).to_bits(), + BinOp::FSub => (f32::from_bits(lhs) - f32::from_bits(rhs)).to_bits(), + BinOp::FMul => (f32::from_bits(lhs) * f32::from_bits(rhs)).to_bits(), + BinOp::FDiv => (f32::from_bits(lhs) / f32::from_bits(rhs)).to_bits(), + BinOp::FMin => f32::from_bits(lhs).tw_minimum(f32::from_bits(rhs)).to_bits(), + BinOp::FMax => f32::from_bits(lhs).tw_maximum(f32::from_bits(rhs)).to_bits(), + BinOp::FCopysign => f32::from_bits(lhs).copysign(f32::from_bits(rhs)).to_bits(), + } +} + +fn exec_binop_64(op: BinOp, lhs: u64, rhs: u64) -> u64 { + match op { + BinOp::IAdd => lhs.wrapping_add(rhs), + BinOp::ISub => lhs.wrapping_sub(rhs), + BinOp::IMul => lhs.wrapping_mul(rhs), + BinOp::IAnd => lhs & rhs, + BinOp::IOr => lhs | rhs, + BinOp::IXor => lhs ^ rhs, + BinOp::IShl => (lhs as i64).wrapping_shl(rhs as u32) as u64, + BinOp::IShrS => (lhs as i64).wrapping_shr(rhs as u32) as u64, + BinOp::IShrU => lhs.wrapping_shr(rhs as u32), + BinOp::IRotl => (lhs as i64).rotate_left(rhs as u32) as u64, + BinOp::IRotr => (lhs as i64).rotate_right(rhs as u32) as u64, + BinOp::FAdd => (f64::from_bits(lhs) + f64::from_bits(rhs)).to_bits(), + BinOp::FSub => (f64::from_bits(lhs) - f64::from_bits(rhs)).to_bits(), + BinOp::FMul => (f64::from_bits(lhs) * f64::from_bits(rhs)).to_bits(), + BinOp::FDiv => (f64::from_bits(lhs) / f64::from_bits(rhs)).to_bits(), + BinOp::FMin => f64::from_bits(lhs).tw_minimum(f64::from_bits(rhs)).to_bits(), + BinOp::FMax => f64::from_bits(lhs).tw_maximum(f64::from_bits(rhs)).to_bits(), + BinOp::FCopysign => f64::from_bits(lhs).copysign(f64::from_bits(rhs)).to_bits(), + } +} + +fn exec_binop_128(op: BinOp128, lhs: Value128, rhs: Value128) -> Value128 { + match op { + BinOp128::And => lhs.v128_and(rhs), + BinOp128::AndNot => lhs.v128_andnot(rhs), + BinOp128::Or => lhs.v128_or(rhs), + BinOp128::Xor => lhs.v128_xor(rhs), + BinOp128::I64x2Add => lhs.i64x2_add(rhs), + BinOp128::I64x2Mul => lhs.i64x2_mul(rhs), + } +} diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index e12ef1a..6d13579 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -271,23 +271,20 @@ impl State { if expected_func { let Some(func_addr) = value.addr() else { return false }; let Some(func) = self.funcs.get(func_addr as usize) else { return false }; - let actual = RefType::new_concrete(false, func.type_addr).expect("canonical type fits"); - return self.ref_type_is_subtype(actual, expected); + return self.ref_type_is_subtype(RefType::new_concrete(false, func.type_addr), expected); } if expected.abstract_heap_type() == Some(AbstractHeapType::Exn) { return value.addr().is_some_and(|addr| self.exceptions.get(addr as usize).is_some()); } if value.is_i31() { - let actual = RefType::new_abstract(false, AbstractHeapType::I31); - return self.ref_type_is_subtype(actual, expected); + return self.ref_type_is_subtype(RefType::new_abstract(false, AbstractHeapType::I31), expected); } if value.is_host_any() { - let actual = RefType::new_abstract(false, AbstractHeapType::Any); - return self.ref_type_is_subtype(actual, expected); + return self.ref_type_is_subtype(RefType::new_abstract(false, AbstractHeapType::Any), expected); } let Some(object) = self.gc.get(value) else { return false }; - let actual = RefType::new_concrete(false, object.type_addr).expect("canonical type fits"); + let actual = RefType::new_concrete(false, object.type_addr); self.ref_type_is_subtype(actual, expected) } @@ -309,12 +306,10 @@ impl State { pub(crate) fn value_matches_type(&self, value: WasmValue, expected: WasmType) -> bool { match (value, expected) { (WasmValue::Ref(RefValue::Null), WasmType::Ref(expected)) => expected.is_nullable(), - (WasmValue::Ref(RefValue::Func(func)), WasmType::Ref(expected)) => { - self.funcs.get(func.addr() as usize).is_some_and(|func| { - let actual = RefType::new_concrete(false, func.type_addr).expect("canonical type fits"); - self.ref_type_is_subtype(actual, expected) - }) - } + (WasmValue::Ref(RefValue::Func(func)), WasmType::Ref(expected)) => self + .funcs + .get(func.addr() as usize) + .is_some_and(|func| self.ref_type_is_subtype(RefType::new_concrete(false, func.type_addr), expected)), (WasmValue::Ref(RefValue::Any(_)), WasmType::Ref(expected)) if expected.is_func() || expected.is_extern() || expected.is_exn() => { diff --git a/crates/tinywasm/src/store/types.rs b/crates/tinywasm/src/store/types.rs index 4954bba..e9fa6d1 100644 --- a/crates/tinywasm/src/store/types.rs +++ b/crates/tinywasm/src/store/types.rs @@ -6,7 +6,7 @@ use super::Store; pub(crate) fn canonicalize_ref_type(ty: RefType, type_addrs: &[TypeAddr]) -> RefType { let Some(type_addr) = ty.type_index() else { return ty }; let canonical = *type_addrs.get(type_addr as usize).expect("validated type address should exist"); - RefType::new_concrete(ty.is_nullable(), canonical).expect("canonical type addresses fit in references") + RefType::new_concrete(ty.is_nullable(), canonical) } pub(crate) fn canonicalize_value_type(ty: WasmType, type_addrs: &[TypeAddr]) -> WasmType { @@ -18,12 +18,10 @@ pub(crate) fn canonicalize_value_type(ty: WasmType, type_addrs: &[TypeAddr]) -> fn map_value_type(ty: WasmType, resolve: &mut impl FnMut(TypeAddr) -> TypeAddr) -> WasmType { match ty { - WasmType::Ref(ty) if ty.is_concrete() => { - let addr = resolve(ty.type_index().expect("concrete reference has a type index")); - WasmType::Ref( - RefType::new_concrete(ty.is_nullable(), addr).expect("canonical type addresses fit in references"), - ) - } + WasmType::Ref(ty) if ty.is_concrete() => WasmType::Ref(RefType::new_concrete( + ty.is_nullable(), + resolve(ty.type_index().expect("concrete reference has a type index")), + )), ty => ty, } } diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 128f1ed..38a85ef 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -155,7 +155,7 @@ fn imported_host_functions_resolve_concrete_types() -> Result<(), Box Variable Instructions // See - GlobalGet32(GlobalAddr), LocalGet32(LocalAddr), LocalSet32(LocalAddr), LocalTee32(LocalAddr), GlobalSet32(GlobalAddr), - GlobalGet64(GlobalAddr), LocalGet64(LocalAddr), LocalSet64(LocalAddr), LocalTee64(LocalAddr), GlobalSet64(GlobalAddr), - GlobalGet128(GlobalAddr), LocalGet128(LocalAddr), LocalSet128(LocalAddr), LocalTee128(LocalAddr), GlobalSet128(GlobalAddr), + GlobalGet32(GlobalAddr), GlobalSet32(GlobalAddr), GlobalTee32(GlobalAddr), LocalGet32(LocalAddr), LocalSet32(LocalAddr), LocalTee32(LocalAddr), + GlobalGet64(GlobalAddr), GlobalSet64(GlobalAddr), GlobalTee64(GlobalAddr), LocalGet64(LocalAddr), LocalSet64(LocalAddr), LocalTee64(LocalAddr), + GlobalGet128(GlobalAddr), GlobalSet128(GlobalAddr), GlobalTee128(GlobalAddr), LocalGet128(LocalAddr), LocalSet128(LocalAddr), LocalTee128(LocalAddr), // > Memory Instructions I32Load(MemoryArg), diff --git a/crates/types/src/reference.rs b/crates/types/src/reference.rs index 884d330..9ed5df1 100644 --- a/crates/types/src/reference.rs +++ b/crates/types/src/reference.rs @@ -73,12 +73,9 @@ impl RefType { } #[inline] - pub const fn new_concrete(nullable: bool, type_index: u32) -> Option { - if type_index <= Self::PAYLOAD_MASK { - Some(Self(((nullable as u32) << 31) | Self::CONCRETE | type_index)) - } else { - None - } + pub const fn new_concrete(nullable: bool, type_index: u32) -> Self { + assert!(type_index <= Self::PAYLOAD_MASK, "type index is too large for a reference type"); + Self(((nullable as u32) << 31) | Self::CONCRETE | type_index) } #[inline] @@ -193,7 +190,7 @@ impl ExternRef { #[inline] pub const fn try_new(addr: u32) -> Option { match encode_host_ref(addr) { - Some(raw) => Some(Self(raw)), + Some(encoded) => Some(Self(encoded)), None => None, } } @@ -251,7 +248,7 @@ impl AnyRef { #[inline] pub const fn from_host(addr: u32) -> Option { match encode_host_ref(addr) { - Some(raw) => Some(Self(raw)), + Some(encoded) => Some(Self(encoded)), None => None, } } diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index f0a576b..5049ba2 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -81,7 +81,7 @@ mod tests { #[test] fn concrete_references_require_store_type_information() { - let concrete = WasmType::Ref(RefType::new_concrete(false, 0).unwrap()); + let concrete = WasmType::Ref(RefType::new_concrete(false, 0)); assert!(!WasmValue::from(FuncRef::new(0)).matches_type(concrete)); assert!(!WasmValue::from(AnyRef::from_host(0).unwrap()).matches_type(concrete)); diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 0ae646c..b338bd2 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -22,6 +22,10 @@ path = "src/print.rs" name = "tinywasm" path = "src/tinywasm.rs" +[[bin]] +name = "tinywasm_precompiled" +path = "src/tinywasm_precompiled.rs" + [[bin]] name = "tinywasm_no_std" path = "src/tinywasm_no_std.rs" @@ -39,8 +43,7 @@ argon2 = { version = "0.5" } dlmalloc = { version = "0.2", features = ["global"] } tinywasm = { path = "../../crates/tinywasm", default-features = false, features = [ "archive", - "parser", - "validate" + "parser" ] } [features] diff --git a/examples/rust/build.sh b/examples/rust/build.sh index db4cb43..b2a6149 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash cd "$(dirname "$0")" || exit -bins=("host_fn" "hello" "fibonacci" "print" "tinywasm" "argon2id") -exclude_wat=("tinywasm") +bins=("host_fn" "hello" "fibonacci" "print" "tinywasm" "tinywasm_precompiled" "argon2id") +exclude_wat=("tinywasm" "tinywasm_precompiled") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" @@ -13,8 +13,9 @@ wasmopt_features="--enable-simd --enable-relaxed-simd --enable-tail-call --enabl mkdir -p "$dest_dir" # build no_std -cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin tinywasm_no_std --no-default-features +RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none -C target-feature=$rust_features -C panic=abort" cargo build -Z build-std=core,alloc,panic_abort -Z build-std-features="optimize_for_size" --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin tinywasm_no_std --no-default-features cp "$out_dir/tinywasm_no_std.wasm" "$dest_dir/" +wasm-opt "$dest_dir/tinywasm_no_std.wasm" -o "$dest_dir/tinywasm_no_std.opt.wasm" -O3 $wasmopt_features for bin in "${bins[@]}"; do RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none -C target-feature=$rust_features -C panic=abort" cargo build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" diff --git a/examples/rust/src/print.twasm b/examples/rust/src/print.twasm new file mode 100644 index 0000000000000000000000000000000000000000..f0fa9a87c3b90554afabdd92675a76a7a92f0820 GIT binary patch literal 154 zcmYL@!3qK~3`A#IQBedxMHD~4Z&2_e#7GM6Ve7Ikg13IUX)lJ52@htH%k^~L9q}*5 z1;9IQjza6Y&>85nqCf_4Ht$pa8ovP>H51Jy!`S5sT#1-YDk)pUTI88@kcYlgbf4#x WQd(Y9V(SMOQ~oTQzdP*fH~ay3HX1|# literal 0 HcmV?d00001 diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index cbf10f4..a70abb9 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -1,7 +1,7 @@ #![no_main] #![no_std] use dlmalloc::GlobalDlmalloc; -use tinywasm::{FuncContext, HostFunction, ModuleInstance}; +use tinywasm::{FuncContext, HostFunction, Module, ModuleInstance}; extern crate alloc; @@ -28,9 +28,7 @@ fn run() -> tinywasm::Result<()> { let mut store = tinywasm::Store::default(); let mut imports = tinywasm::Imports::new(); - let res = tinywasm::parser::Parser::default().parse_module_bytes(include_bytes!("./print.wasm"))?; - let twasm = res.serialize_twasm()?; - let module = tinywasm::parse_bytes(&twasm)?; + let module = Module::try_from_twasm(include_bytes!("./print.twasm"))?; let printi32 = HostFunction::from(|_: FuncContext<'_>, v: i32| { unsafe { printi32(v) } diff --git a/examples/rust/src/tinywasm_precompiled.rs b/examples/rust/src/tinywasm_precompiled.rs new file mode 100644 index 0000000..bc0e0c1 --- /dev/null +++ b/examples/rust/src/tinywasm_precompiled.rs @@ -0,0 +1,30 @@ +#![no_main] +use tinywasm::{FuncContext, HostFunction, Module, ModuleInstance}; + +#[link(wasm_import_module = "env")] +unsafe extern "C" { + fn printi32(x: i32); +} + +#[unsafe(no_mangle)] +pub extern "C" fn hello() { + let _ = run(); +} + +fn run() -> tinywasm::Result<()> { + let module = Module::try_from_twasm(include_bytes!("./print.twasm"))?; + let mut store = tinywasm::Store::default(); + + let printi32 = HostFunction::from(|_: FuncContext<'_>, v: i32| { + unsafe { printi32(v) } + Ok(()) + }); + + let mut imports = tinywasm::Imports::new(); + imports.define("env", "printi32", printi32); + + let instance = ModuleInstance::instantiate(&mut store, &module, Some(&imports))?; + let add_and_print = instance.func::<(i32, i32), ()>(&store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; + Ok(()) +} diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 672b290..b3398f8 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -35,6 +35,8 @@ fn main() -> Result<()> { println!(" host_fn"); println!(" fibonacci - calculate fibonacci(30)"); println!(" tinywasm - run printi32 inside of tinywasm inside of itself"); + println!(" tinywasm_precompiled - run a precompiled module inside of tinywasm"); + println!(" tinywasm_no_std - run a precompiled module inside of no_std tinywasm"); println!(" argon2id - run argon2id(1000, 2, 1)"); return Ok(()); } @@ -44,6 +46,7 @@ fn main() -> Result<()> { "printi32" => printi32()?, "fibonacci" => fibonacci()?, "tinywasm" => tinywasm()?, + "tinywasm_precompiled" => tinywasm_precompiled()?, "tinywasm_no_std" => tinywasm_no_std()?, "argon2id" => argon2id()?, "host_fn" => host_fn()?, @@ -57,6 +60,8 @@ fn main() -> Result<()> { fibonacci()?; println!("\ntinywasm.wasm:"); tinywasm()?; + println!("\ntinywasm_precompiled.wasm:"); + tinywasm_precompiled()?; println!("\ntinywasm_no_std.wasm:"); tinywasm_no_std()?; println!("argon2id.wasm:"); @@ -100,6 +105,21 @@ fn tinywasm_no_std() -> Result<()> { Ok(()) } +fn tinywasm_precompiled() -> Result<()> { + let module = tinywasm::parse_file("./examples/rust/out/tinywasm_precompiled.opt.wasm")?; + let mut store = Store::default(); + + let mut imports = Imports::new(); + imports.define("env", "printi32", HostFunction::from(|_: FuncContext<'_>, _x: i32| Ok(()))); + let instance = ModuleInstance::instantiate(&mut store, &module, Some(black_box(&imports)))?; + + let hello = instance.func::<(), ()>(&store, "hello")?; + hello.call(&mut store, black_box(()))?; + hello.call(&mut store, black_box(()))?; + hello.call(&mut store, black_box(()))?; + Ok(()) +} + fn hello() -> Result<()> { let module = tinywasm::parse_file("./examples/rust/out/hello.opt.wasm")?; let mut store = Store::default(); @@ -211,6 +231,11 @@ mod tests { tinywasm().unwrap(); } + #[test] + fn test_tinywasm_precompiled() { + tinywasm_precompiled().unwrap(); + } + #[test] fn test_tinywasm_no_std() { tinywasm_no_std().unwrap(); From e862a9ef83957b2e1e92e211922ce4e131c6e2b3 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Aug 2026 18:00:11 +0200 Subject: [PATCH 13/15] chore: improve interpreter performance, add cold path macros Signed-off-by: Henry --- Cargo.lock | 20 +- Cargo.toml | 6 +- crates/parser/src/optimize.rs | 48 +- crates/tinywasm/src/error.rs | 4 + crates/tinywasm/src/imports.rs | 31 +- crates/tinywasm/src/instance.rs | 15 +- crates/tinywasm/src/interpreter/executor.rs | 492 +++++++----------- .../src/interpreter/stack/call_stack.rs | 7 +- .../src/interpreter/stack/value_stack.rs | 3 +- crates/tinywasm/src/interpreter/values.rs | 5 +- crates/tinywasm/src/lib.rs | 2 + crates/tinywasm/src/macros.rs | 18 + crates/tinywasm/src/store/const_expr.rs | 22 +- crates/tinywasm/src/store/element.rs | 11 +- crates/tinywasm/src/store/gc/mod.rs | 2 +- crates/tinywasm/src/store/memory/instance.rs | 12 +- crates/tinywasm/src/store/memory/lazy.rs | 4 +- crates/tinywasm/src/store/memory/mod.rs | 34 +- crates/tinywasm/src/store/memory/paged.rs | 17 +- crates/tinywasm/src/store/memory/vec.rs | 21 +- crates/tinywasm/src/store/mod.rs | 18 +- crates/tinywasm/src/store/state.rs | 4 +- crates/tinywasm/src/store/table.rs | 6 +- crates/tinywasm/tests/typed_globals.rs | 10 +- crates/types/src/instructions.rs | 3 + 25 files changed, 343 insertions(+), 472 deletions(-) create mode 100644 crates/tinywasm/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index c4dbba7..b06f70f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,9 +111,9 @@ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bstr" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", "regex-automata", @@ -962,9 +962,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.255.0" +version = "0.256.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42" +checksum = "ec1492381bfd5ea51c2a99a919b676662559925cb8d7490547ec2e14c1ad3eb1" dependencies = [ "leb128fmt", "wasmparser", @@ -982,9 +982,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.255.0" +version = "0.256.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" +checksum = "60bd825ffedc6cba8a642924ba7ae424afbc47811cffbcb7b92031ec24e59b4c" dependencies = [ "bitflags", "indexmap", @@ -993,9 +993,9 @@ dependencies = [ [[package]] name = "wast" -version = "255.0.0" +version = "256.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ffec530f199bd3d553ac442c13dd108353cad533cad8514bc41e1f1f0fe686" +checksum = "a3ad42723fc9222da007f05812a3249c9a4ee7af79d497fc2a2079579ad7efe6" dependencies = [ "bumpalo", "leb128fmt", @@ -1006,9 +1006,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.255.0" +version = "1.256.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda82c82e1486c7eed42a0465e544d80fff37abc3b39482e0d34dbaabe2fe5b1" +checksum = "37cc86c54d8011b3202e265bfebada440733ea3a788ffaf46d395f7d2fdeacfb" dependencies = [ "wast", ] diff --git a/Cargo.toml b/Cargo.toml index 5a4e83f..3af064c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,9 +31,9 @@ pretty_env_logger = "0.5" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } wasm-testsuite = { version = "0.7" } -wasmparser = { version = "0.255", default-features = false } -wast = "255" -wat = "1.255" +wasmparser = { version = "0.256", default-features = false } +wast = "256" +wat = "1.256" criterion = { version = "0.8", default-features = false, features = [ "cargo_bench_support", diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index 7081187..969d686 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -96,9 +96,14 @@ fn rewrite( rewrite!(instrs, i, [Const32(c), LocalGet32(local)] => BinOpLocalConst32(op, local, c)); rewrite!(instrs, i, [Const32(c), GlobalGet32(global)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [GlobalGet32(global)] => BinOpStackGlobal32(op, global)); + rewrite!(instrs, i, [LocalGet32(local)] => BinOpStackLocal32(op, local)); if matches!(op, BinOp::IAdd) { rewrite!(instrs, i, [Const32(c)] => AddConst32(c)); rewrite!(instrs, i, [I32Add] => I32Add3); + rewrite!(instrs, i, + [BinOpStackLocal32(BinOp::IAdd, local)] => + [LocalGet32(local), I32Add3] + ); } } instr @ (I32Sub | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr) => { @@ -107,6 +112,7 @@ fn rewrite( rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [GlobalGet32(global)] => BinOpStackGlobal32(op, global)); + rewrite!(instrs, i, [LocalGet32(local)] => BinOpStackLocal32(op, local)); if matches!(op, BinOp::IShrS) { rewrite!(instrs, i, [BinOpLocalConst32(BinOp::IShl, local, 8), Const32(8)] => [LocalGet32(local), I32Extend8S]); rewrite!(instrs, i, [BinOpLocalConst32(BinOp::IShl, local, 16), Const32(16)] => [LocalGet32(local), I32Extend16S]); @@ -152,12 +158,14 @@ fn rewrite( rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); rewrite!(instrs, i, [Const32(c), LocalGet32(local)] => BinOpLocalConst32(op, local, c)); rewrite!(instrs, i, [Const32(c), GlobalGet32(global)] => BinOpGlobalConst32(op, global, c)); + rewrite!(instrs, i, [LocalGet32(local)] => BinOpStackLocal32(op, local)); } instr @ (F32Sub | F32Div | F32Copysign) => { let Some(op) = float_bin_op(instr) else { unreachable!() }; rewrite!(instrs, i, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b)); rewrite!(instrs, i, [LocalGet32(local), Const32(c)] => BinOpLocalConst32(op, local, c)); rewrite!(instrs, i, [GlobalGet32(global), Const32(c)] => BinOpGlobalConst32(op, global, c)); + rewrite!(instrs, i, [LocalGet32(local)] => BinOpStackLocal32(op, local)); } instr @ (F64Add | F64Mul | F64Min | F64Max) => { let Some(op) = float_bin_op(instr) else { unreachable!() }; @@ -198,6 +206,10 @@ fn rewrite( IncMemoryLocal32(memarg, load_addr) ); rewrite!(instrs, i, [F32Mul, F32Add] => FMaStoreF32(memarg)); + rewrite!(instrs, i, + [BinOpStackLocal32(BinOp::FMul, local), F32Add] => + [LocalGet32(local), FMaStoreF32(memarg)] + ); rewrite!(instrs, i, [LocalGet32(addr_local), LocalGet32(value_local)] if (let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local))) => @@ -313,6 +325,15 @@ fn rewrite( ); rewrite!(instrs, i, [I32Mul, LocalGet32(acc), I32Add] if (acc == dst) => MulAccLocal32(dst)); rewrite!(instrs, i, [F32Mul, LocalGet32(acc), F32Add] if (acc == dst) => FMulAccLocal32(dst)); + rewrite!(instrs, i, + [I32Mul, BinOpStackLocal32(BinOp::IAdd, acc)] if (acc == dst) => + MulAccLocal32(dst) + ); + rewrite!(instrs, i, + [F32Mul, BinOpStackLocal32(BinOp::FAdd, acc)] if (acc == dst) => + FMulAccLocal32(dst) + ); + rewrite!(instrs, i, [BinOpStackLocal32(op, local)] => BinOpStackLocalSet32(op, local, dst)); rewrite_local_set_direct!( instrs, i, @@ -403,6 +424,7 @@ fn rewrite( ); } LocalTee32(dst) => { + rewrite!(instrs, i, [BinOpStackLocal32(op, local)] => BinOpStackLocalTee32(op, local, dst)); fold_local_binop!( instrs, i, dst, source = resolve_local_source_32, @@ -501,16 +523,22 @@ fn rewrite( LoadLocalTee128(memarg, addr, dst) ); } - Drop32 => rewrite_drop_tee_direct!( - instrs, - i, - tee = LocalTee32, - set = LocalSet32, - binop_local_local_tee = BinOpLocalLocalTee32, - binop_local_local_set = BinOpLocalLocalSet32, - binop_local_const_tee = BinOpLocalConstTee32, - binop_local_const_set = BinOpLocalConstSet32 - ), + Drop32 => { + rewrite!(instrs, i, + [BinOpStackLocalTee32(op, local, dst)] => + BinOpStackLocalSet32(op, local, dst) + ); + rewrite_drop_tee_direct!( + instrs, + i, + tee = LocalTee32, + set = LocalSet32, + binop_local_local_tee = BinOpLocalLocalTee32, + binop_local_local_set = BinOpLocalLocalSet32, + binop_local_const_tee = BinOpLocalConstTee32, + binop_local_const_set = BinOpLocalConstSet32 + ); + } Drop64 => rewrite_drop_tee_direct!( instrs, i, diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 0597847..05e36aa 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -250,18 +250,21 @@ impl LinkingError { } impl From for Error { + #[cold] fn from(value: LinkingError) -> Self { Self::Linker(value) } } impl From for Error { + #[cold] fn from(value: TwasmError) -> Self { Self::Twasm(value) } } impl From for Error { + #[cold] fn from(value: Trap) -> Self { Self::Trap(value) } @@ -364,6 +367,7 @@ impl From for crate::std::io::Error { #[cfg(feature = "parser")] impl From for Error { + #[cold] fn from(value: tinywasm_parser::ParseError) -> Self { Self::Parser(value) } diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index b8de4e0..eb2351b 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -1,7 +1,6 @@ use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::hint::cold_path; use crate::{Function, Global, HostFunction, LinkingError, Memory, Result, Table, Tag}; use tinywasm_types::*; @@ -175,8 +174,7 @@ impl Imports { fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { if expected != actual { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } Ok(()) } @@ -187,14 +185,12 @@ impl Imports { return Err(LinkingError::incompatible_import_type(import).into()); } if actual.size_initial < expected.size_initial { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } match expected.size_max { Some(expected_max) if actual.size_max.is_none_or(|actual_max| actual_max > expected_max) => { - cold_path(); - Err(LinkingError::incompatible_import_type(import).into()) + cold!(Err(LinkingError::incompatible_import_type(import).into())) } _ => Ok(()), } @@ -268,8 +264,7 @@ impl Imports { } Extern::HostFunction(func) => { let ImportKind::Function(type_idx) = import.kind else { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); }; let expected_type_addr = type_addrs .get(type_idx as usize) @@ -277,8 +272,7 @@ impl Imports { let actual_ty = func.resolve_import_type(type_addrs)?; let actual_type_addr = store.register_host_type(&actual_ty); if !store.state.type_addr_is_subtype(actual_type_addr, *expected_type_addr) { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } ExternVal::Func(func.instantiate_registered(store, actual_type_addr).addr()) } @@ -289,16 +283,14 @@ impl Imports { } } else { let Some(instance) = self.modules.get(import.module.as_ref()) else { - cold_path(); - return Err(LinkingError::unknown_import(import).into()); + return cold!(Err(LinkingError::unknown_import(import).into())); }; instance.validate_store(store)?; instance.export_addr(&import.name).ok_or_else(|| LinkingError::unknown_import(import))? }; if val.kind() != (&import.kind).into() { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } match (val, &import.kind) { @@ -309,8 +301,7 @@ impl Imports { && store.state.value_type_is_subtype(global_ty.ty, expected.ty) && (!ty.mutable || store.state.value_type_is_subtype(expected.ty, global_ty.ty)); if !compatible { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } imports.globals.push(global_addr); } @@ -336,8 +327,7 @@ impl Imports { type_addrs.get(*ty as usize).ok_or_else(|| LinkingError::incompatible_import_type(import))?; if !store.state.type_addr_is_subtype(store.state.get_func(func_addr).type_addr, *expected_type_addr) { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } imports.funcs.push(func_addr); } @@ -346,8 +336,7 @@ impl Imports { .get(ty.type_idx as usize) .ok_or_else(|| LinkingError::incompatible_import_type(import))?; if store.state.get_tag(tag_addr).type_addr != *expected_type_addr { - cold_path(); - return Err(LinkingError::incompatible_import_type(import).into()); + return cold!(Err(LinkingError::incompatible_import_type(import).into())); } imports.tags.push(tag_addr); } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 23bb298..f45b718 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -111,8 +111,7 @@ impl ModuleInstance { #[inline] pub(crate) fn validate_store(&self, store: &Store) -> Result<()> { if self.0.store_id != store.id() { - cold_path(); - return Err(Trap::InvalidStore.into()); + return cold!(Err(Trap::InvalidStore.into())); } Ok(()) } @@ -205,8 +204,7 @@ impl ModuleInstance { store.add_instance(instance.clone()); if let Some(trap) = elem_trapped.or(data_trapped) { - cold_path(); - return Err(trap.into()); + return cold!(Err(trap.into())); } Ok(instance) } @@ -356,8 +354,7 @@ impl ModuleInstance { self.validate_store(store)?; let ExternVal::Func(func_addr) = self.require_export(name)? else { - cold_path(); - return Err(Error::Other(format!("Export is not a function: {name}"))); + return cold!(Err(Error::Other(format!("Export is not a function: {name}")))); }; Ok(Function { item: StoreItem::new(self.0.store_id, func_addr), module_id: self.id() }) @@ -410,8 +407,7 @@ impl ModuleInstance { self.validate_store(store)?; let ExternVal::Func(func_addr) = self.require_export(name)? else { - cold_path(); - return Err(Error::Other(format!("Export is not a function: {name}"))); + return cold!(Err(Error::Other(format!("Export is not a function: {name}")))); }; let func = Function { item: StoreItem::new(self.0.store_id, func_addr), module_id: self.id() }; @@ -464,8 +460,7 @@ impl ModuleInstance { match self.require_export(name)? { ExternVal::Memory(mem_addr) => Ok(Memory(StoreItem::new(self.0.store_id, mem_addr))), _ => { - cold_path(); - Err(Error::Other(format!("Export is not a memory: {name}"))) + cold!(Err(Error::Other(format!("Export is not a memory: {name}")))) } } } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 051b12f..585819e 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -67,12 +67,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { #[inline(always)] fn exec(&mut self, instr_ptr: usize) -> Result> { macro_rules! exec_op { - (branch $target:expr, $condition:expr) => {{ - if $condition { - self.cf.instr_ptr = *$target as usize; - return Ok(None); - } - }}; (binary_fallible $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ let $rhs = <$ty>::stack_pop(&mut self.store.value_stack); let $lhs = <$ty>::stack_pop(&mut self.store.value_stack); @@ -185,6 +179,24 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let stack_val = <$vt>::stack_pop(&mut self.store.value_stack); self.store.value_stack.push($exec(*$op, stack_val, global_val))?; }}; + (binop_stack_local $vt:ty, $exec:ident, $op:ident, $local:ident) => {{ + let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); + let lhs = <$vt>::stack_pop(&mut self.store.value_stack); + <$vt>::stack_push(&mut self.store.value_stack, $exec(*$op, lhs, rhs))?; + }}; + (binop_stack_local_set $vt:ty, $exec:ident, $op:ident, $local:ident, $dst:ident) => {{ + let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); + let lhs = <$vt>::stack_pop(&mut self.store.value_stack); + let value = $exec(*$op, lhs, rhs); + <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); + }}; + (binop_stack_local_tee $vt:ty, $exec:ident, $op:ident, $local:ident, $dst:ident) => {{ + let rhs = <$vt>::local_get(&self.store.value_stack, &self.cf, *$local); + let lhs = <$vt>::stack_pop(&mut self.store.value_stack); + let value = $exec(*$op, lhs, rhs); + <$vt>::local_set(&mut self.store.value_stack, &self.cf, *$dst, value); + <$vt>::stack_push(&mut self.store.value_stack, value)?; + }}; (binop_acc_local $ty:ty, $acc:ident, $mul:expr, $add:expr) => {{ let rhs = <$ty>::stack_pop(&mut self.store.value_stack); let lhs = <$ty>::stack_pop(&mut self.store.value_stack); @@ -215,115 +227,83 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Throw(tag) => { self.exec_throw(*tag)?; return Ok(None); } ThrowRef => { self.exec_throw_ref()?; return Ok(None); } Jump(ip) => { self.cf.instr_ptr = *ip as usize; return Ok(None); } - JumpIfZero32(ip) => exec_op!(branch ip, i32::stack_pop(&mut self.store.value_stack) == 0), - JumpIfNonZero32(ip) => exec_op!(branch ip, i32::stack_pop(&mut self.store.value_stack) != 0), - JumpIfZero64(ip) => exec_op!(branch ip, i64::stack_pop(&mut self.store.value_stack) == 0), - JumpIfNonZero64(ip) => exec_op!(branch ip, i64::stack_pop(&mut self.store.value_stack) != 0), - JumpIfRefNull(ip) => { + JumpIfZero32(ip) => if Self::exec_jump_if(&mut self.cf, *ip, |_| i32::stack_pop(&mut self.store.value_stack) == 0) { return Ok(None) }, + JumpIfNonZero32(ip) => if Self::exec_jump_if(&mut self.cf, *ip, |_| i32::stack_pop(&mut self.store.value_stack) != 0) { return Ok(None) }, + JumpIfZero64(ip) => if Self::exec_jump_if(&mut self.cf, *ip, |_| i64::stack_pop(&mut self.store.value_stack) == 0) { return Ok(None) }, + JumpIfNonZero64(ip) => if Self::exec_jump_if(&mut self.cf, *ip, |_| i64::stack_pop(&mut self.store.value_stack) != 0) { return Ok(None) }, + JumpIfRefNull(ip) => if Self::exec_jump_if(&mut self.cf, *ip, |_| { let is_null = ValueRef::stack_peek(&self.store.value_stack).is_null(); - if is_null { - ValueRef::stack_pop(&mut self.store.value_stack); - } - exec_op!(branch ip, is_null); - }, - JumpIfRefNonNull(ip) => { + if is_null { ValueRef::stack_pop(&mut self.store.value_stack); } + is_null + }) { return Ok(None) }, + JumpIfRefNonNull(ip) => if Self::exec_jump_if(&mut self.cf, *ip, |_| { let is_non_null = !ValueRef::stack_peek(&self.store.value_stack).is_null(); - if !is_non_null { - ValueRef::stack_pop(&mut self.store.value_stack); - } - exec_op!(branch ip, is_non_null); - }, + if !is_non_null { ValueRef::stack_pop(&mut self.store.value_stack); } + is_non_null + }) { return Ok(None) }, BrOnCast(ip, ty, on_fail) => if self.exec_ref_matches(*ty) == *on_fail { self.cf.instr_ptr = *ip as usize; return Ok(None); }, - JumpIfLocalZero32 { target_ip, local } => exec_op!(branch target_ip, Value32::local_get(&self.store.value_stack, &self.cf, *local) == 0), - JumpIfLocalNonZero32 { target_ip, local } => exec_op!(branch target_ip, Value32::local_get(&self.store.value_stack, &self.cf, *local) != 0), - JumpIfLocalZero64 { target_ip, local } => exec_op!(branch target_ip, Value64::local_get(&self.store.value_stack, &self.cf, *local) == 0), - JumpIfLocalNonZero64 { target_ip, local } => exec_op!(branch target_ip, Value64::local_get(&self.store.value_stack, &self.cf, *local) != 0), - JumpCmpStackConst32 { target_ip, imm, op } => { - let lhs = i32::stack_pop(&mut self.store.value_stack); - exec_op!(branch target_ip, cmp_i32(lhs, *imm, *op)); - }, - JumpCmpStackConst64 { target_ip, imm, op } => { - let lhs = i64::stack_pop(&mut self.store.value_stack); - exec_op!(branch target_ip, cmp_i64(lhs, *imm, *op)); - }, - JumpCmpStackLocal32 { target_ip, local, op } => { + JumpIfLocalZero32 { target_ip, local } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| Value32::local_get(&self.store.value_stack, cf, *local) == 0) { return Ok(None) }, + JumpIfLocalNonZero32 { target_ip, local } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| Value32::local_get(&self.store.value_stack, cf, *local) != 0) { return Ok(None) }, + JumpIfLocalZero64 { target_ip, local } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| Value64::local_get(&self.store.value_stack, cf, *local) == 0) { return Ok(None) }, + JumpIfLocalNonZero64 { target_ip, local } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| Value64::local_get(&self.store.value_stack, cf, *local) != 0) { return Ok(None) }, + JumpCmpStackConst32 { target_ip, imm, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |_| cmp_i32(i32::stack_pop(&mut self.store.value_stack), *imm, *op)) { return Ok(None) }, + JumpCmpStackConst64 { target_ip, imm, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |_| cmp_i64(i64::stack_pop(&mut self.store.value_stack), *imm, *op)) { return Ok(None) }, + JumpCmpStackLocal32 { target_ip, local, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| { let lhs = i32::stack_pop(&mut self.store.value_stack); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, *local); - exec_op!(branch target_ip, cmp_i32(lhs, rhs, *op)); - }, - JumpCmpStackLocal64 { target_ip, local, op } => { + cmp_i32(lhs, i32::local_get(&self.store.value_stack, cf, *local), *op) + }) { return Ok(None) }, + JumpCmpStackLocal64 { target_ip, local, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| { let lhs = i64::stack_pop(&mut self.store.value_stack); - let rhs = i64::local_get(&self.store.value_stack, &self.cf, *local); - exec_op!(branch target_ip, cmp_i64(lhs, rhs, *op)); - }, - BinOpLocalConstJump32 { target_ip, local, imm, op, on_zero } => { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local); - let value = exec_binop_32(*op, lhs as u32, *imm as u32) as i32; - i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); - exec_op!(branch target_ip, (value == 0) == *on_zero); - }, - BinOpLocalConstJumpCmpLocal32 { target_ip, local, imm, binop, right, cmp } => { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local); - let lhs = exec_binop_32(*binop, lhs as u32, *imm as u32) as i32; - i32::local_set(&mut self.store.value_stack, &self.cf, *local, lhs); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, *right); - exec_op!(branch target_ip, cmp_i32(lhs, rhs, *cmp)); - }, + cmp_i64(lhs, i64::local_get(&self.store.value_stack, cf, *local), *op) + }) { return Ok(None) }, + BinOpLocalConstJump32 { target_ip, local, imm, op, on_zero } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| { + let value = exec_binop_32(*op, i32::local_get(&self.store.value_stack, cf, *local) as u32, *imm as u32) as i32; + i32::local_set(&mut self.store.value_stack, cf, *local, value); + (value == 0) == *on_zero + }) { return Ok(None) }, + BinOpLocalConstJumpCmpLocal32 { target_ip, local, imm, binop, right, cmp } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| { + let lhs = exec_binop_32(*binop, i32::local_get(&self.store.value_stack, cf, *local) as u32, *imm as u32) as i32; + i32::local_set(&mut self.store.value_stack, cf, *local, lhs); + cmp_i32(lhs, i32::local_get(&self.store.value_stack, cf, *right), *cmp) + }) { return Ok(None) }, BinOpStackConstTeeLocalJump32 { target_ip, local, imm, op, on_zero } => { - let lhs = i32::stack_pop(&mut self.store.value_stack); - let value = exec_binop_32(*op, lhs as u32, *imm as u32) as i32; + let value = exec_binop_32(*op, i32::stack_pop(&mut self.store.value_stack) as u32, *imm as u32) as i32; i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); i32::stack_push(&mut self.store.value_stack, value)?; - exec_op!(branch target_ip, (value == 0) == *on_zero); + if Self::exec_jump_if(&mut self.cf, *target_ip, |_| (value == 0) == *on_zero) { return Ok(None) } }, - BinOpGlobalConstJump32 { target_ip, global, imm, op, on_zero } => { + BinOpGlobalConstJump32 { target_ip, global, imm, op, on_zero } => if Self::exec_jump_if(&mut self.cf, *target_ip, |_| { let global = self.module.resolve_global_addr(*global); - let lhs = i32::global_get(&self.store.state.globals, global); - let value = exec_binop_32(*op, lhs as u32, *imm as u32) as i32; + let value = exec_binop_32(*op, i32::global_get(&self.store.state.globals, global) as u32, *imm as u32) as i32; i32::global_set(&mut self.store.state.globals, global, value); - exec_op!(branch target_ip, (value == 0) == *on_zero); - }, - IncLocalJump32 { target_ip, local, delta, on_zero } => { - let value = i32::local_get(&self.store.value_stack, &self.cf, *local).wrapping_add(*delta); - i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); - exec_op!(branch target_ip, (value == 0) == *on_zero); - }, + (value == 0) == *on_zero + }) { return Ok(None) }, + IncLocalJump32 { target_ip, local, delta, on_zero } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| { + let value = i32::local_get(&self.store.value_stack, cf, *local).wrapping_add(*delta); + i32::local_set(&mut self.store.value_stack, cf, *local, value); + (value == 0) == *on_zero + }) { return Ok(None) }, IncStackTeeLocalJump32 { target_ip, local, delta, on_zero } => { let value = i32::stack_pop(&mut self.store.value_stack).wrapping_add(*delta); i32::local_set(&mut self.store.value_stack, &self.cf, *local, value); i32::stack_push(&mut self.store.value_stack, value)?; - exec_op!(branch target_ip, (value == 0) == *on_zero); + if Self::exec_jump_if(&mut self.cf, *target_ip, |_| (value == 0) == *on_zero) { return Ok(None) } }, - IncGlobalJump32 { target_ip, global, delta, on_zero } => { + IncGlobalJump32 { target_ip, global, delta, on_zero } => if Self::exec_jump_if(&mut self.cf, *target_ip, |_| { let global = self.module.resolve_global_addr(*global); let value = i32::global_get(&self.store.state.globals, global).wrapping_add(*delta); i32::global_set(&mut self.store.state.globals, global, value); - exec_op!(branch target_ip, (value == 0) == *on_zero); - }, - IncLocalJumpCmpLocal32 { target_ip, local, delta, right, op } => { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local).wrapping_add(*delta); - i32::local_set(&mut self.store.value_stack, &self.cf, *local, lhs); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, *right); - exec_op!(branch target_ip, cmp_i32(lhs, rhs, *op)); - }, - JumpCmpLocalConst32 { target_ip, local, imm, op } => { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, *local); - exec_op!(branch target_ip, cmp_i32(lhs, *imm, *op)); - }, - JumpCmpLocalConst64 { target_ip, local, imm, op } => { - let lhs = i64::local_get(&self.store.value_stack, &self.cf, *local); - exec_op!(branch target_ip, cmp_i64(lhs, i64::from(*imm), *op)); - }, - JumpCmpLocalLocal32 { target_ip, left, right, op } => { - let lhs = i32::local_get(&self.store.value_stack, &self.cf, *left); - let rhs = i32::local_get(&self.store.value_stack, &self.cf, *right); - exec_op!(branch target_ip, cmp_i32(lhs, rhs, *op)); - }, - JumpCmpLocalLocal64 { target_ip, left, right, op } => { - let lhs = i64::local_get(&self.store.value_stack, &self.cf, *left); - let rhs = i64::local_get(&self.store.value_stack, &self.cf, *right); - exec_op!(branch target_ip, cmp_i64(lhs, rhs, *op)); - }, + (value == 0) == *on_zero + }) { return Ok(None) }, + IncLocalJumpCmpLocal32 { target_ip, local, delta, right, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| { + let lhs = i32::local_get(&self.store.value_stack, cf, *local).wrapping_add(*delta); + i32::local_set(&mut self.store.value_stack, cf, *local, lhs); + cmp_i32(lhs, i32::local_get(&self.store.value_stack, cf, *right), *op) + }) { return Ok(None) }, + JumpCmpLocalConst32 { target_ip, local, imm, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| cmp_i32(i32::local_get(&self.store.value_stack, cf, *local), *imm, *op)) { return Ok(None) }, + JumpCmpLocalConst64 { target_ip, local, imm, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| cmp_i64(i64::local_get(&self.store.value_stack, cf, *local), i64::from(*imm), *op)) { return Ok(None) }, + JumpCmpLocalLocal32 { target_ip, left, right, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| cmp_i32(i32::local_get(&self.store.value_stack, cf, *left), i32::local_get(&self.store.value_stack, cf, *right), *op)) { return Ok(None) }, + JumpCmpLocalLocal64 { target_ip, left, right, op } => if Self::exec_jump_if(&mut self.cf, *target_ip, |cf| cmp_i64(i64::local_get(&self.store.value_stack, cf, *left), i64::local_get(&self.store.value_stack, cf, *right), *op)) { return Ok(None) }, DropKeep(drop_keep) => self.exec_drop_keep(*drop_keep), BranchTable(default_ip, start, len) => { self.exec_branch_table(*default_ip, *start, *len); return Ok(None); } Return => { if self.exec_return() { return Ok(Some(())); } return Ok(None); } @@ -375,6 +355,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { BinOpLocalConstTee128(op, local_index, c, dst) => exec_op!(binop_local_const_tee Value128, exec_binop_128, op, local_index, Value128(self.func.data.v128_const(*c)), dst), BinOpStackGlobal32(op, global_index) => exec_op!(binop_stack_global Value32, exec_binop_32, op, global_index), BinOpStackGlobal64(op, global_index) => exec_op!(binop_stack_global Value64, exec_binop_64, op, global_index), + BinOpStackLocal32(op, local) => exec_op!(binop_stack_local Value32, exec_binop_32, op, local), + BinOpStackLocalSet32(op, local, dst) => exec_op!(binop_stack_local_set Value32, exec_binop_32, op, local, dst), + BinOpStackLocalTee32(op, local, dst) => exec_op!(binop_stack_local_tee Value32, exec_binop_32, op, local, dst), SetLocalConst32(local_index, c) => i32::local_set(&mut self.store.value_stack, &self.cf, *local_index, *c), SetLocalConst64(local_index, c) => i64::local_set(&mut self.store.value_stack, &self.cf, *local_index, *c), SetLocalConst128(local_index, c) => Value128::local_set(&mut self.store.value_stack, &self.cf, *local_index, Value128(self.func.data.v128_const(*c))), @@ -383,24 +366,24 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { StoreLocalLocal32(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local)?, StoreLocalLocal64(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local)?, StoreLocalLocal128(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local)?, - LoadLocal32(m, addr_local) => self.exec_load_local::(*m, *addr_local, |v| v)?, - LoadLocal64(m, addr_local) => self.exec_load_local::(*m, *addr_local, |v| v)?, - LoadLocal8S32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, - LoadLocal8U32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, - LoadLocal16S32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, - LoadLocal16U32(m, addr_local) => self.exec_load_local::(*m, *addr_local, i32::from)?, - LoadLocalTee32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, |v| v)?, - LoadLocalSet32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, |v| v)?, - LoadLocalTee8S32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalTee8U32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalTee16S32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalTee16U32(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalSet8S32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalSet8U32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalSet16S32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalSet16U32(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, i32::from)?, - LoadLocalTee128(m, addr_local, dst_local) => self.exec_load_local_tee::(*m, *addr_local, *dst_local, |v| v)?, - LoadLocalSet128(m, addr_local, dst_local) => self.exec_load_local_set::(*m, *addr_local, *dst_local, |v| v)?, + LoadLocal32(m, addr_local) => self.exec_load_local::(*m, *addr_local, 0, |v| v)?, + LoadLocal64(m, addr_local) => self.exec_load_local::(*m, *addr_local, 0, |v| v)?, + LoadLocal8S32(m, addr_local) => self.exec_load_local::(*m, *addr_local, 0, i32::from)?, + LoadLocal8U32(m, addr_local) => self.exec_load_local::(*m, *addr_local, 0, i32::from)?, + LoadLocal16S32(m, addr_local) => self.exec_load_local::(*m, *addr_local, 0, i32::from)?, + LoadLocal16U32(m, addr_local) => self.exec_load_local::(*m, *addr_local, 0, i32::from)?, + LoadLocalTee32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, |v| v)?, + LoadLocalSet32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, |v| v)?, + LoadLocalTee8S32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalTee8U32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalTee16S32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalTee16U32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalSet8S32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalSet8U32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalSet16S32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalSet16U32(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, i32::from)?, + LoadLocalTee128(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, |v| v)?, + LoadLocalSet128(m, addr_local, dst_local) => self.exec_load_local::(*m, *addr_local, *dst_local, |v| v)?, AndConstTee32(c, local_index) => { exec_op!(unary i32 => i32, |v| v & *c); exec_op!(local_tee i32, local_index); } SubConstTee32(c, local_index) => { exec_op!(unary i32 => i32, |v| v.wrapping_sub(*c)); exec_op!(local_tee i32, local_index); } AndConstTee64(c, local_index) => { exec_op!(unary i64 => i64, |v| v & *c); exec_op!(local_tee i64, local_index); } @@ -921,6 +904,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(None) } + #[inline(always)] + fn exec_jump_if(cf: &mut CallFrame, target_ip: u32, condition: impl FnOnce(&CallFrame) -> bool) -> bool { + let condition = condition(cf); + if condition { + cf.instr_ptr = target_ip as usize; + } + condition + } + fn exec_branch_table(&mut self, default_ip: u32, start: u32, len: u32) { let idx = ::stack_pop(&mut self.store.value_stack); let target_ip = if idx >= 0 && (idx as u32) < len { @@ -943,28 +935,23 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn create_exception(&mut self, tag_index: TagAddr) -> Result { let tag_addr = self.module.resolve_tag_addr(tag_index); let type_addr = self.store.state.get_tag(tag_addr).type_addr; - let addr = u32::try_from(self.store.state.exceptions.len()).map_err(|_| Trap::OutOfMemory)?; - self.store.state.exceptions.try_reserve(1).map_err(|_| Trap::OutOfMemory)?; + let addr = cold_err!(u32::try_from(self.store.state.exceptions.len())).map_err(|_| Trap::OutOfMemory)?; + cold_err!(self.store.state.exceptions.try_reserve(1)).map_err(|_| Trap::OutOfMemory)?; let params = self.store.state.get_canonical_func_type(type_addr).params(); let mut payload = Vec::new(); - payload.try_reserve_exact(params.len()).map_err(|_| Trap::OutOfMemory)?; + cold_err!(payload.try_reserve_exact(params.len())).map_err(|_| Trap::OutOfMemory)?; + let value_stack = &mut self.store.value_stack; for &ty in params.iter().rev() { payload.push(match ty { - WasmType::I32 | WasmType::F32 => { - TinyWasmValue::Value32(Value32::stack_pop(&mut self.store.value_stack)) - } - WasmType::I64 | WasmType::F64 => { - TinyWasmValue::Value64(Value64::stack_pop(&mut self.store.value_stack)) - } - WasmType::V128 => TinyWasmValue::Value128(Value128::stack_pop(&mut self.store.value_stack)), - WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::stack_pop(&mut self.store.value_stack)), + WasmType::I32 | WasmType::F32 => TinyWasmValue::Value32(Value32::stack_pop(value_stack)), + WasmType::I64 | WasmType::F64 => TinyWasmValue::Value64(Value64::stack_pop(value_stack)), + WasmType::V128 => TinyWasmValue::Value128(Value128::stack_pop(value_stack)), + WasmType::Ref(_) => TinyWasmValue::ValueRef(ValueRef::stack_pop(value_stack)), }); } payload.reverse(); - self.store - .state - .exceptions - .push(crate::store::ExceptionInstance { tag_addr, payload: payload.into_boxed_slice() }); + let exception = crate::store::ExceptionInstance { tag_addr, payload: payload.into_boxed_slice() }; + self.store.state.exceptions.push(exception); Ok(addr) } @@ -1076,8 +1063,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let Ok(locals_base) = self.store.value_stack.enter_locals(&wasm_func.func.params, &wasm_func.func.locals) else { - cold_path(); - return Err(Trap::CallStackOverflow); + return cold!(Err(Trap::CallStackOverflow)); }; self.store.call_stack.push(self.cf)?; @@ -1101,8 +1087,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.value_stack.truncate_keep_counts(self.cf.locals_base, wasm_func.func.params); let Ok(locals_base) = self.store.value_stack.enter_locals(&wasm_func.func.params, &wasm_func.func.locals) else { - cold_path(); - return Err(Trap::CallStackOverflow); + return cold!(Err(Trap::CallStackOverflow)); }; self.cf = CallFrame::new(func_addr, locals_base, wasm_func.func.locals); if wasm_func.owner != self.module.id() { @@ -1123,10 +1108,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { params_may_gc: bool, ) -> Result { if let Some(host_func) = host_func.typed_callback() { - if let Err(error) = host_func.call_stack(self.store, self.module.id(), type_addr) { - cold_path(); - return Err(Trap::HostFunction(Box::new(error))); - } + cold_err!(host_func.call_stack(self.store, self.module.id(), type_addr)) + .map_err(|error| Trap::HostFunction(Box::new(error)))?; if TAIL { return Ok(self.exec_return()); } @@ -1135,8 +1118,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } let param_types = self.store.state.get_canonical_func_type(type_addr).params(); - let mut params = Vec::new(); - params.try_reserve_exact(param_types.len()).map_err(|_| Trap::OutOfMemory)?; + let mut params = core::mem::take(&mut self.store.host_params); + debug_assert!(params.is_empty()); + cold_err!(params.try_reserve_exact(param_types.len())).map_err(|_| Trap::OutOfMemory)?; for &ty in param_types.iter().rev() { params.push(self.store.value_stack.pop_wasmvalue(&self.store.state, ty)); } @@ -1144,13 +1128,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { if params_may_gc { self.store.state.pin_host_values(¶ms); } - let res = match host_func.call_values(self.store, self.module.id(), type_addr, ¶ms) { - Ok(res) => res, - Err(err) => { - cold_path(); - return Err(Trap::HostFunction(Box::new(err))); - } - }; + let result = host_func.call_values(self.store, self.module.id(), type_addr, ¶ms); + params.clear(); + self.store.host_params = params; + let res = cold_err!(result).map_err(|error| Trap::HostFunction(Box::new(error)))?; self.store.value_stack.extend_from_wasmvalues(&res)?; if TAIL { @@ -1192,8 +1173,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_call_self(&mut self) -> Result<(), Trap> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); let Ok(locals_base) = self.store.value_stack.enter_locals(&self.func.params, &self.func.locals) else { - cold_path(); - return Err(Trap::CallStackOverflow); + return cold!(Err(Trap::CallStackOverflow)); }; let new = CallFrame::new(self.cf.func_addr, locals_base, self.func.locals); self.store.call_stack.push(core::mem::replace(&mut self.cf, new))?; @@ -1206,8 +1186,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.value_stack.truncate_keep_counts(self.cf.locals_base, self.func.params); let Ok(locals_base) = self.store.value_stack.enter_locals(&self.func.params, &self.func.locals) else { - cold_path(); - return Err(Trap::CallStackOverflow); + return cold!(Err(Trap::CallStackOverflow)); }; self.cf = CallFrame::new(self.cf.func_addr, locals_base, self.func.locals); Ok(()) @@ -1226,13 +1205,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let table = self.store.state.get_table(table_addr); let Ok(table) = table.get(table_idx) else { - cold_path(); - return Err(Trap::UndefinedElement { index: table_idx }); + return cold!(Err(Trap::UndefinedElement { index: table_idx })); }; let Some(func_ref) = table.addr() else { - cold_path(); - return Err(Trap::UninitializedElement { index: table_idx }); + return cold!(Err(Trap::UninitializedElement { index: table_idx })); }; self.exec_typed_call::(func_ref, self.module.resolve_type_addr(type_addr)) @@ -1245,11 +1222,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { ) -> Result { let func = self.store.state.get_func(func_addr).clone(); if !self.store.state.type_addr_is_subtype(func.type_addr, expected_type_addr) { - cold_path(); - return Err(Trap::IndirectCallTypeMismatch { + return cold!(Err(Trap::IndirectCallTypeMismatch { actual: Box::new(self.store.state.get_canonical_func_type(func.type_addr).clone()), expected: Box::new(self.store.state.get_canonical_func_type(expected_type_addr).clone()), - }); + })); } match func.kind { crate::store::FunctionKind::Wasm(wasm_func) => match IS_RETURN_CALL { @@ -1267,8 +1243,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); let func_ref = ValueRef::stack_pop(&mut self.store.value_stack); let Some(func_addr) = func_ref.addr() else { - cold_path(); - return Err(Trap::NullFunctionReference); + return cold!(Err(Trap::NullFunctionReference)); }; self.exec_typed_call::(func_addr, self.module.resolve_type_addr(type_addr)) @@ -1284,7 +1259,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let Some(caller) = self.store.call_stack.pop_frame(self.call_stack_base) else { return true; }; - self.switch_to_frame(caller); + if caller.func_addr == self.cf.func_addr { + self.cf = caller; + } else { + self.switch_to_frame(caller); + } false } @@ -1391,32 +1370,13 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } #[inline(always)] - fn exec_load_local, const N: usize, TARGET: InternalValue>( - &mut self, - memarg: MemoryArg, - addr_local: u8, - cast: impl Fn(LOAD) -> TARGET, - ) -> Result<(), Trap> { - let value = cast(self.exec_load_local_value::(memarg, addr_local)?); - TARGET::stack_push(&mut self.store.value_stack, value) - } - - #[inline(always)] - fn exec_load_local_tee, const N: usize, TARGET: InternalValue>( - &mut self, - memarg: MemoryArg, - addr_local: u8, - dst_local: u8, - cast: impl Fn(LOAD) -> TARGET, - ) -> Result<(), Trap> { - let value = cast(self.exec_load_local_value::(memarg, addr_local)?); - TARGET::local_set(&mut self.store.value_stack, &self.cf, u16::from(dst_local), value); - self.store.value_stack.push(value)?; - Ok(()) - } - - #[inline(always)] - fn exec_load_local_set, const N: usize, TARGET: InternalValue>( + fn exec_load_local< + LOAD: MemValue, + const N: usize, + TARGET: InternalValue, + const SET_LOCAL: bool, + const TEE: bool, + >( &mut self, memarg: MemoryArg, addr_local: u8, @@ -1424,7 +1384,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { cast: impl Fn(LOAD) -> TARGET, ) -> Result<(), Trap> { let value = cast(self.exec_load_local_value::(memarg, addr_local)?); - TARGET::local_set(&mut self.store.value_stack, &self.cf, u16::from(dst_local), value); + if SET_LOCAL { + TARGET::local_set(&mut self.store.value_stack, &self.cf, u16::from(dst_local), value); + } + if !SET_LOCAL || TEE { + TARGET::stack_push(&mut self.store.value_stack, value)?; + } Ok(()) } @@ -1435,8 +1400,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_ref_as_non_null(&mut self) -> Result<(), Trap> { if ValueRef::stack_peek(&self.store.value_stack).is_null() { - cold_path(); - return Err(Trap::NullReference); + return cold!(Err(Trap::NullReference)); } Ok(()) } @@ -1459,8 +1423,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_ref_cast(&self, ty: RefType) -> Result<(), Trap> { if !self.exec_ref_matches(ty) { - cold_path(); - return Err(Trap::CastFailure); + return cold!(Err(Trap::CastFailure)); } Ok(()) } @@ -1468,8 +1431,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_i31_get(&mut self, signed: bool) -> Result<(), Trap> { let value = ValueRef::stack_pop(&mut self.store.value_stack); if value.is_null() { - cold_path(); - return Err(Trap::NullI31Reference); + return cold!(Err(Trap::NullI31Reference)); } let value = if signed { value.i31_s().expect("validated i31.get operand") @@ -1479,11 +1441,17 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.value_stack.push(value) } + fn push_gc_object(&mut self, type_addr: TypeAddr, values: Vec) -> Result<(), Trap> { + let roots = (&self.store.value_stack.stack_32).into_iter().copied(); + let reference = self.store.state.alloc_gc_object(type_addr, values, roots)?; + self.store.value_stack.push(reference) + } + fn exec_struct_new(&mut self, type_index: TypeAddr, default: bool) -> Result<(), Trap> { let type_addr = self.module.resolve_type_addr(type_index); let fields = &self.store.state.get_type(type_addr).as_struct().expect("validated struct.new type").fields; let mut values = Vec::new(); - values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(fields.len())).map_err(|_| Trap::OutOfMemory)?; if default { values.extend(fields.iter().map(|field| default_value(field.storage))); } else { @@ -1492,12 +1460,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } values.reverse(); } - let value = self.store.state.alloc_gc_object( - type_addr, - values, - (&self.store.value_stack.stack_32).into_iter().copied(), - )?; - self.store.value_stack.push(value) + self.push_gc_object(type_addr, values) } fn exec_struct_get(&mut self, type_index: TypeAddr, field_index: u32, signed: Option) -> Result<(), Trap> { @@ -1529,14 +1492,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let len = u32::stack_pop(&mut self.store.value_stack) as usize; let value = if default { default_value(storage) } else { pop_value(&mut self.store.value_stack, storage) }; let mut values = Vec::new(); - values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(len)).map_err(|_| Trap::OutOfMemory)?; values.resize(len, value); - let reference = self.store.state.alloc_gc_object( - type_addr, - values, - (&self.store.value_stack.stack_32).into_iter().copied(), - )?; - self.store.value_stack.push(reference) + self.push_gc_object(type_addr, values) } fn exec_array_new_fixed(&mut self, type_index: TypeAddr, len: u32) -> Result<(), Trap> { @@ -1545,17 +1503,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.state.get_type(type_addr).as_array().expect("validated array.new_fixed type").field.storage; let len = len as usize; let mut values = Vec::new(); - values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(len)).map_err(|_| Trap::OutOfMemory)?; for _ in 0..len { values.push(pop_value(&mut self.store.value_stack, storage)); } values.reverse(); - let reference = self.store.state.alloc_gc_object( - type_addr, - values, - (&self.store.value_stack.stack_32).into_iter().copied(), - )?; - self.store.value_stack.push(reference) + self.push_gc_object(type_addr, values) } fn exec_array_get(&mut self, type_index: TypeAddr, signed: Option) -> Result<(), Trap> { @@ -1574,12 +1527,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let value = pop_value(&mut self.store.value_stack, storage); let index = u32::stack_pop(&mut self.store.value_stack) as usize; let reference = ValueRef::stack_pop(&mut self.store.value_stack); - let object = self.store.state.gc_object(reference, type_addr)?; - if index >= object.values.len() { - return Err(Trap::ArrayOutOfBounds); - } - self.store.state.gc.set(reference, index, value).expect("live array element"); - Ok(()) + self.store.state.gc_object(reference, type_addr)?; + self.store.state.gc.set(reference, index, value).ok_or(Trap::ArrayOutOfBounds) } fn exec_array_len(&mut self) -> Result<(), Trap> { @@ -1636,34 +1585,19 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let data_addr = self.module.resolve_data_addr(data_index); let data = self.store.state.data[data_addr as usize].data.as_deref().unwrap_or(&[]); let values = decode_data(storage, data, src, len)?; - let reference = self.store.state.alloc_gc_object( - type_addr, - values, - (&self.store.value_stack.stack_32).into_iter().copied(), - )?; - self.store.value_stack.push(reference) + self.push_gc_object(type_addr, values) } fn exec_array_new_elem(&mut self, type_index: TypeAddr, elem_index: ElemAddr) -> Result<(), Trap> { let len = u32::stack_pop(&mut self.store.value_stack) as usize; let src = u32::stack_pop(&mut self.store.value_stack) as usize; let elem_addr = self.module.resolve_elem_addr(elem_index); - let items = self.store.state.elements[elem_addr as usize].items.as_deref().unwrap_or(&[]); - let end = src.checked_add(len).filter(|end| *end <= items.len()).ok_or_else(|| Trap::TableOutOfBounds { - offset: src, - len, - max: items.len(), - })?; + let items = self.store.state.elements[elem_addr as usize].items_range(src, len)?; let mut values = Vec::new(); - values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; - values.extend(items[src..end].iter().copied().map(TinyWasmValue::ValueRef)); + cold_err!(values.try_reserve_exact(len)).map_err(|_| Trap::OutOfMemory)?; + values.extend(items.iter().copied().map(TinyWasmValue::ValueRef)); let type_addr = self.module.resolve_type_addr(type_index); - let reference = self.store.state.alloc_gc_object( - type_addr, - values, - (&self.store.value_stack.stack_32).into_iter().copied(), - )?; - self.store.value_stack.push(reference) + self.push_gc_object(type_addr, values) } fn exec_array_init_data(&mut self, type_index: TypeAddr, data_index: DataAddr) -> Result<(), Trap> { @@ -1690,18 +1624,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let type_addr = self.module.resolve_type_addr(type_index); let object_len = self.store.state.gc_object(reference, type_addr)?.values.len(); dst.checked_add(len).filter(|end| *end <= object_len).ok_or(Trap::ArrayOutOfBounds)?; - let items = self.store.state.elements[self.module.resolve_elem_addr(elem_index) as usize] - .items - .as_deref() - .unwrap_or(&[]); - let end = src.checked_add(len).filter(|end| *end <= items.len()).ok_or_else(|| Trap::TableOutOfBounds { - offset: src, - len, - max: items.len(), - })?; + let items = + self.store.state.elements[self.module.resolve_elem_addr(elem_index) as usize].items_range(src, len)?; let mut values = Vec::new(); - values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; - values.extend(items[src..end].iter().copied().map(TinyWasmValue::ValueRef)); + cold_err!(values.try_reserve_exact(len)).map_err(|_| Trap::OutOfMemory)?; + values.extend(items.iter().copied().map(TinyWasmValue::ValueRef)); self.store.state.gc.set_slice(reference, dst, &values).expect("live array range"); Ok(()) } @@ -1764,13 +1691,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { fn exec_memory_fill_impl(&mut self, addr: u32, dst: i32, val: u8, size: i32) -> Result<(), Trap> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); + let max = mem.inner.len(); if mem.inner.fill(dst as usize, size as usize, val).is_none() { - cold_path(); - return Err(Trap::MemoryOutOfBounds { - offset: dst as usize, - len: size as usize, - max: self.store.state.get_mem(self.module.resolve_mem_addr(addr)).inner.len(), - }); + return cold!(Err(Trap::MemoryOutOfBounds { offset: dst as usize, len: size as usize, max })); } Ok(()) } @@ -1783,9 +1706,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let data = &self.store.state.data[self.module.resolve_data_addr(data_index) as usize]; let mem = &mut self.store.state.memories[self.module.resolve_mem_addr(mem_index) as usize]; let data_len = data.data.as_ref().map_or(0, |d| d.len()); - if ((size + offset) as usize > data_len) || ((dst + size) as usize > mem.inner.len()) { - cold_path(); - return Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len }); + let mem_len = mem.inner.len(); + if ((size + offset) as usize > data_len) || ((dst + size) as usize > mem_len) { + return cold!(Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len })); } if size == 0 { @@ -1793,13 +1716,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } let Some(data) = &data.data else { - cold_path(); - return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }); + return cold!(Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 })); }; if mem.inner.write_all(dst as usize, &data[offset as usize..((offset + size) as usize)]).is_none() { - cold_path(); - return Err(Trap::MemoryOutOfBounds { offset: dst as usize, len: size as usize, max: mem.inner.len() }); + return cold!(Err(Trap::MemoryOutOfBounds { offset: dst as usize, len: size as usize, max: mem_len })); } Ok(()) } @@ -1850,17 +1771,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { ) -> Result<(), Trap> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; - - match LOAD::load_at(&*mem.inner, addr) { - Ok(val) => { - self.store.value_stack.push(cast(val))?; - Ok(()) - } - Err(e) => { - cold_path(); - Err(e) - } - } + let value = cold_err!(LOAD::load_at(&*mem.inner, addr))?; + self.store.value_stack.push(cast(value)) } fn exec_mem_store_lane + Copy, const N: usize>( @@ -1877,13 +1789,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem_addr = self.module.resolve_mem_addr(mem_addr); let mem = self.store.state.get_mem_mut(mem_addr); let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; - match val.store_at(&mut *mem.inner, addr) { - Ok(()) => Ok(()), - Err(e) => { - cold_path(); - Err(e) - } - } + cold_err!(val.store_at(&mut *mem.inner, addr))?; + Ok(()) } fn exec_mem_store, const N: usize>( @@ -1898,13 +1805,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem_addr = self.module.resolve_mem_addr(mem_addr); let mem = self.store.state.get_mem_mut(mem_addr); let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; - match val.store_at(&mut *mem.inner, addr) { - Ok(()) => Ok(()), - Err(e) => { - cold_path(); - Err(e) - } - } + cold_err!(val.store_at(&mut *mem.inner, addr))?; + Ok(()) } fn exec_table_get(&mut self, table_index: u32) -> Result<(), Trap> { @@ -1937,19 +1839,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let dst = self.pop_table_operand(self.store.state.get_table(table_addr).kind.arch())?; // d let elem_addr = self.module.resolve_elem_addr(elem_index) as usize; let elem = self.store.state.elements.get(elem_addr).ok_or_else(|| Trap::Other("element not found"))?; - let items = elem.items.as_deref().unwrap_or(&[]); - let Some(end) = offset.checked_add(size) else { - cold_path(); - return Err(Trap::TableOutOfBounds { offset, len: size, max: items.len() }); - }; - if end > items.len() { - cold_path(); - return Err(Trap::TableOutOfBounds { offset, len: size, max: items.len() }); - } + let items = elem.items_range(offset, size)?; let table = self.store.state.tables.get_mut(table_addr as usize).ok_or_else(|| Trap::Other("table not found"))?; - table.init(dst, &items[offset..end]) + table.init(dst, items) } fn exec_table_grow(&mut self, table_index: u32) -> Result<(), Trap> { diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 62f1f8e..f4e2595 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,5 +1,4 @@ use crate::{Result, Trap}; -use core::hint::cold_path; use alloc::vec::Vec; use tinywasm_types::{FuncAddr, ValueCounts}; @@ -51,14 +50,12 @@ impl CallStack { } if required_len > self.max_size || !self.dynamic { - cold_path(); - return Err(Trap::CallStackOverflow); + return cold!(Err(Trap::CallStackOverflow)); } let target_capacity = required_len.max(self.stack.capacity().max(1).saturating_mul(2)).min(self.max_size); let Ok(()) = self.stack.try_reserve(target_capacity.saturating_sub(self.stack.len())) else { - cold_path(); - return Err(Trap::CallStackOverflow); + return cold!(Err(Trap::CallStackOverflow)); }; Ok(()) } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index ceea9eb..0b37f51 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -102,8 +102,7 @@ impl Stack { #[inline(always)] pub(crate) fn push(&mut self, value: T) -> Result<(), Trap> { if !self.ensure_capacity_for(self.data.len() + 1) { - cold_path(); - return Err(Trap::ValueStackOverflow); + return cold!(Err(Trap::ValueStackOverflow)); } self.data.push(value); diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index cc91a55..e2cbef0 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -168,10 +168,7 @@ macro_rules! impl_internalvalue { #[inline(always)] fn stack_push(stack: &mut ValueStack, value: Self) -> Result<(), crate::Trap> { let $to_stack_v = value; - if let Err(e) = stack.$stack.push($to_stack) { - core::hint::cold_path(); - return Err(e); - } + cold_err!(stack.$stack.push($to_stack))?; Ok(()) } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index e6c9ab8..e941430 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -83,6 +83,8 @@ //! //! See the [`Imports`] documentation for more information. +#[macro_use] +mod macros; mod std; extern crate alloc; diff --git a/crates/tinywasm/src/macros.rs b/crates/tinywasm/src/macros.rs new file mode 100644 index 0000000..351ae88 --- /dev/null +++ b/crates/tinywasm/src/macros.rs @@ -0,0 +1,18 @@ +macro_rules! cold { + ($value:expr) => {{ + core::hint::cold_path(); + $value + }}; +} + +// Mark the caller's error path cold. This makes a significant difference in interpreter benchmarks, +// while doing it inside map_err or inspect_err is unreliable: +// https://internals.rust-lang.org/t/err-automatic-hint-cold-path/24404 +macro_rules! cold_err { + ($result:expr) => { + match $result { + Ok(value) => Ok(value), + Err(error) => cold!(Err(error)), + } + }; +} diff --git a/crates/tinywasm/src/store/const_expr.rs b/crates/tinywasm/src/store/const_expr.rs index c371763..2c847a7 100644 --- a/crates/tinywasm/src/store/const_expr.rs +++ b/crates/tinywasm/src/store/const_expr.rs @@ -1,5 +1,4 @@ use alloc::{format, vec::Vec}; -use core::hint::cold_path; use tinywasm_types::*; use super::{State, default_value}; @@ -108,8 +107,7 @@ pub(super) fn eval_const( stack.push(TinyWasmValue::ValueRef(ValueRef::from_category_addr(addr))); } Ref(_) => { - cold_path(); - return Err(Error::other("unsupported reference constant")); + return cold!(Err(Error::other("unsupported reference constant"))); } RefI31 => { let value = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; @@ -135,7 +133,7 @@ pub(super) fn eval_const( .as_ref(); let default = matches!(instruction, StructNewDefault(_)); let mut values = Vec::new(); - values.try_reserve_exact(fields.len()).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(fields.len())).map_err(|_| Trap::OutOfMemory)?; if default { values.extend(fields.iter().map(|field| default_value(field.storage))); } else { @@ -163,7 +161,7 @@ pub(super) fn eval_const( pop_value(&mut stack, storage)? }; let mut values = Vec::new(); - values.try_reserve_exact(len as usize).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(len as usize)).map_err(|_| Trap::OutOfMemory)?; values.resize(len as usize, value); alloc_object(state, &mut stack, type_addr, values)?; } @@ -176,7 +174,7 @@ pub(super) fn eval_const( .field .storage; let mut values = Vec::new(); - values.try_reserve_exact(*len as usize).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(*len as usize)).map_err(|_| Trap::OutOfMemory)?; for _ in 0..*len { values.push(pop_value(&mut stack, storage)?); } @@ -187,8 +185,7 @@ pub(super) fn eval_const( let rhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; let lhs = stack.pop().ok_or_else(|| Error::other("const stack underflow"))?; let (TinyWasmValue::Value32(lhs), TinyWasmValue::Value32(rhs)) = (lhs, rhs) else { - cold_path(); - return Err(Error::other("type mismatch in const i32 op")); + return cold!(Err(Error::other("type mismatch in const i32 op"))); }; let out = match instruction { I32Add => (lhs as i32).wrapping_add(rhs as i32), @@ -202,8 +199,7 @@ pub(super) fn eval_const( let rhs = stack.pop(); let lhs = stack.pop(); let (Some(TinyWasmValue::Value64(lhs)), Some(TinyWasmValue::Value64(rhs))) = (lhs, rhs) else { - cold_path(); - return Err(Error::other("type mismatch in const i64 op")); + return cold!(Err(Error::other("type mismatch in const i64 op"))); }; let out = match instruction { I64Add => (lhs as i64).wrapping_add(rhs as i64), @@ -217,12 +213,10 @@ pub(super) fn eval_const( } let Some(value) = stack.pop() else { - cold_path(); - return Err(Error::other("empty const expression")); + return cold!(Err(Error::other("empty const expression"))); }; if !stack.is_empty() { - cold_path(); - return Err(Error::other("const expression did not reduce to single value")); + return cold!(Err(Error::other("const expression did not reduce to single value"))); } Ok(value) } diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs index 4437bd4..8119fe8 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -1,4 +1,4 @@ -use crate::interpreter::ValueRef; +use crate::{Trap, interpreter::ValueRef}; use alloc::vec::Vec; use tinywasm_types::RefType; @@ -15,4 +15,13 @@ impl ElementInstance { pub(crate) fn drop(&mut self) { self.items.take(); } + + pub(crate) fn items_range(&self, offset: usize, len: usize) -> Result<&[ValueRef], Trap> { + let items = self.items.as_deref().unwrap_or(&[]); + let end = offset.checked_add(len).filter(|end| *end <= items.len()).ok_or_else(|| { + core::hint::cold_path(); + Trap::TableOutOfBounds { offset, len, max: items.len() } + })?; + Ok(&items[offset..end]) + } } diff --git a/crates/tinywasm/src/store/gc/mod.rs b/crates/tinywasm/src/store/gc/mod.rs index 2a87a5f..6431037 100644 --- a/crates/tinywasm/src/store/gc/mod.rs +++ b/crates/tinywasm/src/store/gc/mod.rs @@ -79,7 +79,7 @@ pub(crate) fn decode_data( return Err(Trap::MemoryOutOfBounds { offset: src, len: len.saturating_mul(width), max: data.len() }); }; let mut values = Vec::new(); - values.try_reserve_exact(len).map_err(|_| Trap::OutOfMemory)?; + cold_err!(values.try_reserve_exact(len)).map_err(|_| Trap::OutOfMemory)?; values.extend(data[src..end].chunks_exact(width).map(|bytes| match width { 1 => TinyWasmValue::Value32(u32::from(bytes[0])), 2 => TinyWasmValue::Value32(u32::from(u16::from_le_bytes(bytes.try_into().unwrap()))), diff --git a/crates/tinywasm/src/store/memory/instance.rs b/crates/tinywasm/src/store/memory/instance.rs index be0f3b4..d9c7990 100644 --- a/crates/tinywasm/src/store/memory/instance.rs +++ b/crates/tinywasm/src/store/memory/instance.rs @@ -38,8 +38,7 @@ impl MemoryInstance { match usize::try_from(u64::from(base) + offset) { Ok(addr) => Ok(addr), Err(_) => { - cold_path(); - Err(memory_oob(base as usize, N, self.inner.len())) + cold!(Err(memory_oob(base as usize, N, self.inner.len()))) } } } @@ -50,8 +49,7 @@ impl MemoryInstance { match base.checked_add(offset).and_then(|addr| usize::try_from(addr).ok()) { Some(addr) => Ok(addr), None => { - cold_path(); - Err(memory_oob(base as usize, N, self.inner.len())) + cold!(Err(memory_oob(base as usize, N, self.inner.len()))) } } } @@ -108,13 +106,11 @@ impl MemoryInstance { ) -> Result<(), Trap> { fn check_range(mem: &MemoryStorage, addr: usize, len: usize) -> Result<(), crate::Trap> { let Some(end) = addr.checked_add(len) else { - cold_path(); - return Err(memory_oob(addr, len, mem.len())); + return cold!(Err(memory_oob(addr, len, mem.len()))); }; if end > mem.len() || end < addr { - cold_path(); - return Err(memory_oob(addr, len, mem.len())); + return cold!(Err(memory_oob(addr, len, mem.len()))); } Ok(()) } diff --git a/crates/tinywasm/src/store/memory/lazy.rs b/crates/tinywasm/src/store/memory/lazy.rs index ef6dace..40451bb 100644 --- a/crates/tinywasm/src/store/memory/lazy.rs +++ b/crates/tinywasm/src/store/memory/lazy.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use core::hint::cold_path; use tinywasm_types::MemoryType; @@ -43,8 +42,7 @@ impl LazyLinearMemory { let storage = match self.backend.create(self.ty, self.initial_len) { Ok(storage) => storage, Err(Error::Trap(trap)) => { - cold_path(); - return Err(trap); + return cold!(Err(trap)); } Err(err) => panic!("lazy memory materialization failed: {err}"), }; diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs index f25d4ae..739a6a4 100644 --- a/crates/tinywasm/src/store/memory/mod.rs +++ b/crates/tinywasm/src/store/memory/mod.rs @@ -54,21 +54,18 @@ pub trait LinearMemory { /// Writes all bytes in `src` starting at `addr`, or returns `None` if any byte could not be written. fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> { let Some(end) = addr.checked_add(src.len()) else { - cold_path(); - return None; + return cold!(None); }; if end > self.len() { - cold_path(); - return None; + return cold!(None); } let mut offset = 0; while offset < src.len() { let written = self.write(addr + offset, &src[offset..]); if written == 0 { - cold_path(); - return None; + return cold!(None); } offset += written; } @@ -134,21 +131,18 @@ pub trait LinearMemory { /// Reads exactly `dst.len()` bytes starting at `addr`. fn read_exact(&self, addr: usize, dst: &mut [u8]) -> Option<()> { let Some(end) = addr.checked_add(dst.len()) else { - cold_path(); - return None; + return cold!(None); }; if end > self.len() { - cold_path(); - return None; + return cold!(None); } let mut offset = 0; while offset < dst.len() { let read = self.read(addr + offset, &mut dst[offset..]); if read == 0 { - cold_path(); - return None; + return cold!(None); } offset += read; } @@ -377,13 +371,7 @@ macro_rules! impl_mem_traits { #[inline(always)] fn load_at(mem: &dyn LinearMemory, addr: usize) -> core::result::Result { - match mem.$read(addr) { - Ok(bytes) => Ok(Self::from_le_bytes(bytes)), - Err(trap) => { - cold_path(); - Err(trap) - } - } + Ok(Self::from_le_bytes(cold_err!(mem.$read(addr))?)) } #[inline(always)] @@ -418,13 +406,7 @@ impl MemValue<16> for Value128 { #[inline(always)] fn load_at(mem: &dyn LinearMemory, addr: usize) -> core::result::Result { - match mem.read_128(addr) { - Ok(bytes) => Ok(Self(bytes)), - Err(trap) => { - cold_path(); - Err(trap) - } - } + Ok(Self(cold_err!(mem.read_128(addr))?)) } #[inline(always)] diff --git a/crates/tinywasm/src/store/memory/paged.rs b/crates/tinywasm/src/store/memory/paged.rs index fc5947c..206014e 100644 --- a/crates/tinywasm/src/store/memory/paged.rs +++ b/crates/tinywasm/src/store/memory/paged.rs @@ -45,13 +45,7 @@ impl PagedMemory { #[inline(always)] fn allocate_chunk(&self) -> Result, crate::Trap> { let mut chunk = Vec::new(); - match chunk.try_reserve_exact(self.chunk_size) { - Ok(()) => {} - Err(_) => { - cold_path(); - return Err(crate::Trap::OutOfMemory); - } - } + cold_err!(chunk.try_reserve_exact(self.chunk_size)).map_err(|_| crate::Trap::OutOfMemory)?; chunk.resize(self.chunk_size, 0); Ok(chunk.into_boxed_slice()) } @@ -135,13 +129,8 @@ impl LinearMemory for PagedMemory { let new_chunk_count = if new_len == 0 { 0 } else { new_len.div_ceil(self.chunk_size) }; if new_chunk_count > self.chunks.len() { - match self.chunks.try_reserve_exact(new_chunk_count - self.chunks.len()) { - Ok(()) => {} - Err(_) => { - cold_path(); - return Err(crate::Trap::OutOfMemory); - } - } + cold_err!(self.chunks.try_reserve_exact(new_chunk_count - self.chunks.len())) + .map_err(|_| crate::Trap::OutOfMemory)?; self.chunks.resize_with(new_chunk_count, || None); } else { self.chunks.truncate(new_chunk_count); diff --git a/crates/tinywasm/src/store/memory/vec.rs b/crates/tinywasm/src/store/memory/vec.rs index aa468ec..17b1f83 100644 --- a/crates/tinywasm/src/store/memory/vec.rs +++ b/crates/tinywasm/src/store/memory/vec.rs @@ -1,5 +1,4 @@ use alloc::vec::Vec; -use core::hint::cold_path; use super::{LinearMemory, memory_oob}; @@ -21,13 +20,7 @@ impl VecMemory { /// Prefer this backend when contiguous access is more important than grow performance. pub fn try_new(len: usize) -> Result { let mut data = Vec::new(); - match data.try_reserve_exact(len) { - Ok(()) => {} - Err(_) => { - cold_path(); - return Err(crate::Trap::OutOfMemory); - } - } + cold_err!(data.try_reserve_exact(len)).map_err(|_| crate::Trap::OutOfMemory)?; data.resize(len, 0); Ok(Self { data }) } @@ -43,8 +36,7 @@ impl VecMemory { #[inline(always)] fn check_fixed_addr(&self, addr: usize) -> Result<(), crate::Trap> { if N > self.data.len() || addr > self.data.len() - N { - cold_path(); - return Err(memory_oob(addr, N, self.data.len())); + return cold!(Err(memory_oob(addr, N, self.data.len()))); } Ok(()) } @@ -61,13 +53,8 @@ impl LinearMemory for VecMemory { if new_len < self.data.len() { return Err(crate::Trap::MemoryOutOfBounds { offset: new_len, len: 0, max: self.data.len() }); } - match self.data.try_reserve_exact(new_len.saturating_sub(self.data.len())) { - Ok(()) => {} - Err(_) => { - cold_path(); - return Err(crate::Trap::OutOfMemory); - } - } + cold_err!(self.data.try_reserve_exact(new_len.saturating_sub(self.data.len()))) + .map_err(|_| crate::Trap::OutOfMemory)?; self.data.resize(new_len, 0); Ok(()) } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 1aabfb3..46bb727 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -60,6 +60,7 @@ pub struct Store { pub(crate) state: State, pub(crate) call_stack: CallStack, pub(crate) value_stack: ValueStack, + pub(crate) host_params: Vec, } #[cfg(feature = "debug")] @@ -97,6 +98,7 @@ impl Store { state, call_stack: CallStack::new(engine.config()), value_stack: ValueStack::new(engine.config()), + host_params: Vec::new(), engine, execution_fuel: 0, execution_active: false, @@ -266,13 +268,7 @@ impl Store { let start = self.state.memories.len() as MemAddr; self.state.memories.reserve_exact(memories.len()); for mem in memories { - self.state.memories.push(match init(*mem, &self.engine.config().memory_backend) { - Ok(mem) => mem, - Err(e) => { - cold_path(); - return Err(e); - } - }); + self.state.memories.push(cold_err!(init(*mem, &self.engine.config().memory_backend))?); } Ok(start..start + memories.len() as MemAddr) } @@ -287,13 +283,7 @@ impl Store { ) -> Result<()> { self.state.globals.reserve(globals); for global in globals { - let value = match eval_const(&mut self.state, &global.init, out, func_addrs, type_addrs) { - Ok(val) => val, - Err(e) => { - cold_path(); - return Err(e); - } - }; + let value = cold_err!(eval_const(&mut self.state, &global.init, out, func_addrs, type_addrs))?; let ty = global.ty.with_ty(canonicalize_value_type(global.ty.ty, type_addrs)); out.push(self.state.globals.push(ty, value)); } diff --git a/crates/tinywasm/src/store/state.rs b/crates/tinywasm/src/store/state.rs index 6d13579..601bb6a 100644 --- a/crates/tinywasm/src/store/state.rs +++ b/crates/tinywasm/src/store/state.rs @@ -118,9 +118,9 @@ impl State { TinyWasmValue::ValueRef(value) => Some(*value), _ => None, })); - self.gc.collect(roots).map_err(|_| Trap::OutOfMemory)?; + cold_err!(self.gc.collect(roots)).map_err(|_| Trap::OutOfMemory)?; } - self.gc.alloc(type_addr, values, trace_references).map_err(|_| Trap::OutOfMemory) + cold_err!(self.gc.alloc(type_addr, values, trace_references)).map_err(|_| Trap::OutOfMemory) } /// Pins a host-visible reference when it resolves to a managed GC object. diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index 5cbce74..8e6c055 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -17,12 +17,12 @@ pub(crate) struct TableInstance { impl TableInstance { /// Creates a table filled with the given initial reference. pub(crate) fn new(kind: TableType, init: ValueRef) -> Result { - let size = usize::try_from(kind.size_initial).map_err(|_| Trap::OutOfMemory)?; + let size = cold_err!(usize::try_from(kind.size_initial)).map_err(|_| Trap::OutOfMemory)?; if size > MAX_TABLE_SIZE { return Err(Trap::OutOfMemory.into()); } let mut elements = Vec::new(); - elements.try_reserve_exact(size).map_err(|_| Trap::OutOfMemory)?; + cold_err!(elements.try_reserve_exact(size)).map_err(|_| Trap::OutOfMemory)?; elements.resize(size, init); Ok(Self { elements, kind }) } @@ -82,7 +82,7 @@ impl TableInstance { return Err(crate::Trap::TableOutOfBounds { offset: len, len: 1, max: self.elements.len() }); } - self.elements.try_reserve_exact(n).map_err(|_| Trap::OutOfMemory)?; + cold_err!(self.elements.try_reserve_exact(n)).map_err(|_| Trap::OutOfMemory)?; self.elements.resize(len, init); Ok(()) } diff --git a/crates/tinywasm/tests/typed_globals.rs b/crates/tinywasm/tests/typed_globals.rs index ac79f6a..e57309f 100644 --- a/crates/tinywasm/tests/typed_globals.rs +++ b/crates/tinywasm/tests/typed_globals.rs @@ -35,18 +35,18 @@ fn globals_use_typed_instructions_and_roundtrip_values() -> Result<(), Box get32 += 1, - Instruction::GlobalGet64(_) => get64 += 1, - Instruction::GlobalGet128(_) => get128 += 1, + Instruction::GlobalTee32(_) => tee32 += 1, + Instruction::GlobalTee64(_) => tee64 += 1, + Instruction::GlobalTee128(_) => tee128 += 1, Instruction::BinOpStackGlobal32(..) => fused32 += 1, Instruction::BinOpStackGlobal64(..) => fused64 += 1, _ => {} } } - assert_eq!((get32, get64, get128), (3, 2, 1)); + assert_eq!((tee32, tee64, tee128), (3, 2, 1)); assert_eq!((fused32, fused64), (1, 1)); let mut store = Store::default(); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index a505aed..a43c82c 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -203,6 +203,9 @@ pub enum Instruction { BinOpLocalConstTee32(BinOp, LocalAddr, i32, LocalAddr), BinOpLocalConstTee64(BinOp, LocalAddr, i64, LocalAddr), BinOpLocalConstTee128(BinOp128, LocalAddr, ConstIdx, LocalAddr), + BinOpStackLocal32(BinOp, LocalAddr), + BinOpStackLocalSet32(BinOp, LocalAddr, LocalAddr), + BinOpStackLocalTee32(BinOp, LocalAddr, LocalAddr), BinOpStackGlobal32(BinOp, u32), BinOpStackGlobal64(BinOp, u32), SetLocalConst32(LocalAddr, i32), SetLocalConst64(LocalAddr, i64), SetLocalConst128(LocalAddr, ConstIdx), From 68f85f6d2ed5870e497a5e87a40ec4e3f440984f Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Aug 2026 18:10:07 +0200 Subject: [PATCH 14/15] chore: simplify validator feature set, allow compact imports Signed-off-by: Henry --- CHANGELOG.md | 1 + crates/parser/src/lib.rs | 26 +++++--------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64366f4..51f68d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for the WebAssembly function-references proposal - Added basic support for the WebAssembly garbage-collection proposal - Added support for the WebAssembly exception-handling proposal, including tags, `try_table`, `throw`, and `throw_ref` +- Added support for the WebAssembly compact-imports proposal - Added `WasmValue::ty` and `WasmValue::matches_type` - Added `ValueLane` for mapping WebAssembly value types to their physical 32-bit, 64-bit, or 128-bit storage lane. - Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules. diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 8b6cb39..8db7920 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -170,27 +170,11 @@ impl Parser { fn validator(&self) -> Option { #[cfg(feature = "validate")] { - let features = WasmFeatures::CALL_INDIRECT_OVERLONG - | WasmFeatures::BULK_MEMORY_OPT - | WasmFeatures::RELAXED_SIMD - | WasmFeatures::GC_TYPES - | WasmFeatures::GC - | WasmFeatures::REFERENCE_TYPES - | WasmFeatures::MUTABLE_GLOBAL - | WasmFeatures::MULTI_VALUE - | WasmFeatures::FLOATS - | WasmFeatures::BULK_MEMORY - | WasmFeatures::SATURATING_FLOAT_TO_INT - | WasmFeatures::SIGN_EXTENSION - | WasmFeatures::EXTENDED_CONST - | WasmFeatures::FUNCTION_REFERENCES - | WasmFeatures::TAIL_CALL - | WasmFeatures::MULTI_MEMORY - | WasmFeatures::SIMD - | WasmFeatures::MEMORY64 - | WasmFeatures::CUSTOM_PAGE_SIZES - | WasmFeatures::EXCEPTIONS - | WasmFeatures::WIDE_ARITHMETIC; + let features = WasmFeatures::WASM3 + .difference(WasmFeatures::THREADS) + .union(WasmFeatures::CUSTOM_PAGE_SIZES) + .union(WasmFeatures::WIDE_ARITHMETIC) + .union(WasmFeatures::COMPACT_IMPORTS); self.options.validation().then(|| Validator::new_with_features(features)) } #[cfg(not(feature = "validate"))] From a42cb3434bd79accb6a49298f498e016e9c93b9e Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 12 Aug 2026 20:51:47 +0200 Subject: [PATCH 15/15] fix: correct memory64 execution and backend errors Signed-off-by: Henry --- CHANGELOG.md | 4 + Cargo.toml | 5 +- benches/memory_backends.rs | 1 - crates/cli/src/output.rs | 2 +- crates/parser/src/visit.rs | 10 +- crates/tinywasm/src/interpreter/executor.rs | 140 +++++++++++------- .../src/interpreter/stack/value_stack.rs | 26 +++- crates/tinywasm/src/reference.rs | 8 +- crates/tinywasm/src/store/memory/instance.rs | 68 +++++---- crates/tinywasm/src/store/memory/lazy.rs | 65 ++++---- crates/tinywasm/src/store/memory/mod.rs | 100 +++++++------ crates/tinywasm/src/store/memory/paged.rs | 128 +++++++++++++--- crates/tinywasm/src/store/memory/vec.rs | 31 ++-- crates/tinywasm/src/store/mod.rs | 2 +- crates/tinywasm/tests/memory_backends.rs | 50 ++++++- .../tests/wasm-custom/memory64-bulk.wast | 74 +++++++++ crates/types/src/instructions.rs | 11 +- crates/types/src/lib.rs | 25 ++-- 18 files changed, 497 insertions(+), 253 deletions(-) create mode 100644 crates/tinywasm/tests/wasm-custom/memory64-bulk.wast diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f68d5..3427a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,11 +24,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Typed function tuples now support up to 20 parameters or results. `WasmTupleChain` is deprecated. Use untyped functions for larger signatures. - Module types now use one dense recursive type space, while function types are resolved through `Function::ty(&Store)`. - Globals are stored in separate 32-bit, 64-bit, and 128-bit value lanes, avoiding tagged value conversion during guest execution. +- `LinearMemory` mutation methods and custom memory-backend factories now return `Trap` errors so lazy backend failures can be propagated. ### Fixed - Directly defined imports now reject handles from a different `Store`. - Tail calls to host functions now return directly to the caller frame. +- Fixed Memory64 bulk-memory operations and optimized stores using the wrong value-stack lane. +- Fixed `memory.init` bounds checks, operand lowering, and local-memory allocation analysis. +- Fixed Memory64 default limits and host-size handling, including 32-bit targets. ### Breaking Changes diff --git a/Cargo.toml b/Cargo.toml index 3af064c..7ea5181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,10 +35,7 @@ wasmparser = { version = "0.256", default-features = false } wast = "256" wat = "1.256" -criterion = { version = "0.8", default-features = false, features = [ - "cargo_bench_support", - "rayon" -] } +criterion = { version = "0.8", default-features = false, features = ["cargo_bench_support", "rayon"] } [[example]] name = "wasm-rust" diff --git a/benches/memory_backends.rs b/benches/memory_backends.rs index 3a59f8c..d7b8703 100644 --- a/benches/memory_backends.rs +++ b/benches/memory_backends.rs @@ -78,7 +78,6 @@ fn criterion_benchmark(c: &mut Criterion) { bench_grow(&mut group, "paged", || { PagedMemory::try_new(PAGE_SIZE, CHUNK_SIZE).expect("bench memory should be constructible") }); - bench_write_all( &mut group, "vec", diff --git a/crates/cli/src/output.rs b/crates/cli/src/output.rs index a2bd287..75be9f8 100644 --- a/crates/cli/src/output.rs +++ b/crates/cli/src/output.rs @@ -48,7 +48,7 @@ pub fn format_memory_type(ty: &MemoryType) -> String { MemoryArch::I32 => "i32", MemoryArch::I64 => "i64", }; - let max = if ty.page_count_max() == ty.page_count_initial() && ty.max_size() == ty.initial_size() { + let max = if ty.page_count_max() == ty.page_count_initial() { ty.page_count_initial().to_string() } else { ty.page_count_max().to_string() diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 063bef4..cecbb8c 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -477,10 +477,7 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { terminating [] => [] { visit_unreachable => Unreachable, visit_return => Return } memory_index [] => [Addr] { visit_memory_size(memory: u32) => MemorySize } memory_index [Addr] => [Addr] { visit_memory_grow(memory: u32) => MemoryGrow } - memory_index [Addr, S32, Addr] => [] { - visit_memory_init(data_index: u32, memory: u32) => MemoryInit, - visit_memory_fill(memory: u32) => MemoryFill, - } + memory_index [Addr, S32, Addr] => [] { visit_memory_fill(memory: u32) => MemoryFill } table [Addr] => [S32] { visit_table_get(table: u32) => TableGet } table [Addr, S32] => [] { visit_table_set(table: u32) => TableSet } table [] => [Addr] { visit_table_size(table: u32) => TableSize } @@ -941,6 +938,11 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder<'_> { self.emit(&[dst, src, len], &[], Instruction::MemoryCopy { dst_mem, src_mem }) } + fn visit_memory_init(&mut self, data_index: u32, memory: u32) -> Self::Output { + let dst = self.metadata.memory_size(memory)?; + self.emit(&[dst, ValueLane::S32, ValueLane::S32], &[], Instruction::MemoryInit(data_index, memory)) + } + fn visit_br_on_cast( &mut self, relative_depth: u32, diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 585819e..0bdb474 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -8,7 +8,7 @@ use alloc::boxed::Box; use alloc::vec::Vec; use alloc::sync::Arc; -use interpreter::stack::{CallFrame, ValueStack}; +use interpreter::stack::CallFrame; use tinywasm_types::*; use super::ExecState; @@ -30,19 +30,6 @@ pub(crate) struct Executor<'store, const BUDGETED: bool> { } impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { - #[inline(always)] - fn pop_memory_addr( - stack: &mut ValueStack, - mem: &MemoryInstance, - offset: u64, - ) -> Result { - if mem.is_64bit() { - mem.effective_addr_64::(::stack_pop(stack) as u64, offset) - } else { - mem.effective_addr_32::(::stack_pop(stack) as u32, offset) - } - } - pub(crate) fn new(store: &'store mut Store, cf: CallFrame, call_stack_base: u32) -> Self { let wasm_func = store.state.get_wasm_func(cf.func_addr); let module = store @@ -1133,7 +1120,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.host_params = params; let res = cold_err!(result).map_err(|error| Trap::HostFunction(Box::new(error)))?; - self.store.value_stack.extend_from_wasmvalues(&res)?; + self.store.value_stack.extend_wasmvalues(res.iter().copied())?; if TAIL { Ok(self.exec_return()) } else { @@ -1304,10 +1291,22 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { addr_local: u8, value_local: u8, ) -> Result<(), Trap> { - let base = u32::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)); let value = T::local_get(&self.store.value_stack, &self.cf, u16::from(value_local)); - let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(memarg.mem_addr())); - let addr = mem.effective_addr_32::(base, memarg.offset())?; + let mem_addr = self.module.resolve_mem_addr(memarg.mem_addr()); + let mem = self.store.state.get_mem(mem_addr); + let addr = if mem.is_64bit() { + let base = u64::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)); + let base = cold_err!(usize::try_from(base).map_err(|_| Trap::MemoryOutOfBounds { + offset: usize::MAX, + len: N, + max: mem.inner.len(), + }))?; + mem.effective_addr::(base, memarg.offset())? + } else { + let base = u32::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)); + mem.effective_addr::(base as usize, memarg.offset())? + }; + let mem = self.store.state.get_mem_mut(mem_addr); value.store_at(&mut *mem.inner, addr) } @@ -1322,10 +1321,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem = self.store.state.get_mem(mem_addr); let addr = if mem.is_64bit() { let base = i64::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)) as u64; - mem.effective_addr_64::(base, memarg.offset())? + let base = cold_err!(usize::try_from(base).map_err(|_| Trap::MemoryOutOfBounds { + offset: usize::MAX, + len: N, + max: mem.inner.len(), + }))?; + mem.effective_addr::(base, memarg.offset())? } else { let base = u32::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)); - mem.effective_addr_32::(base, memarg.offset())? + mem.effective_addr::(base as usize, memarg.offset())? }; let mem = self.store.state.get_mem_mut(mem_addr); @@ -1344,10 +1348,12 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let rhs = T::stack_pop(&mut self.store.value_stack); let lhs = T::stack_pop(&mut self.store.value_stack); let acc = T::stack_pop(&mut self.store.value_stack); - let addr = i32::stack_pop(&mut self.store.value_stack); let fma = acc + lhs * rhs; - let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); - let addr = mem.effective_addr_32::(addr as u32, m.offset())?; + let mem_addr = self.module.resolve_mem_addr(m.mem_addr()); + let mem = self.store.state.get_mem(mem_addr); + let base = self.store.value_stack.pop_memory_operand(mem.kind.arch())?; + let addr = mem.effective_addr::(base, m.offset())?; + let mem = self.store.state.get_mem_mut(mem_addr); fma.store_at(&mut *mem.inner, addr)?; Ok(()) } @@ -1361,10 +1367,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(memarg.mem_addr())); let addr = if mem.is_64bit() { let base = i64::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)) as u64; - mem.effective_addr_64::(base, memarg.offset())? + let base = cold_err!(usize::try_from(base).map_err(|_| Trap::MemoryOutOfBounds { + offset: usize::MAX, + len: N, + max: mem.inner.len(), + }))?; + mem.effective_addr::(base, memarg.offset())? } else { let base = u32::local_get(&self.store.value_stack, &self.cf, u16::from(addr_local)); - mem.effective_addr_32::(base, memarg.offset())? + mem.effective_addr::(base as usize, memarg.offset())? }; T::load_at(&*mem.inner, addr) } @@ -1659,56 +1670,69 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } fn exec_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Result<(), Trap> { - let size = i32::stack_pop(&mut self.store.value_stack); - let src = i32::stack_pop(&mut self.store.value_stack); - let dst = i32::stack_pop(&mut self.store.value_stack); let dst_mem_addr = self.module.resolve_mem_addr(dst_mem); + let src_mem_addr = self.module.resolve_mem_addr(src_mem); + let dst_arch = self.store.state.get_mem(dst_mem_addr).kind.arch(); + let src_arch = self.store.state.get_mem(src_mem_addr).kind.arch(); + let len_arch = + if dst_arch == MemoryArch::I32 || src_arch == MemoryArch::I32 { MemoryArch::I32 } else { MemoryArch::I64 }; + let size = self.store.value_stack.pop_memory_operand(len_arch)?; + let src = self.store.value_stack.pop_memory_operand(src_arch)?; + let dst = self.store.value_stack.pop_memory_operand(dst_arch)?; if dst_mem == src_mem { // copy within the same memory let mem = self.store.state.get_mem_mut(dst_mem_addr); - mem.copy_within(dst as usize, src as usize, size as usize)?; + mem.copy_within(dst, src, size)?; } else { // copy between two memories - let src_mem_addr = self.module.resolve_mem_addr(src_mem); let (dst_memory, src_memory) = self.store.state.get_mems_mut(dst_mem_addr, src_mem_addr); - dst_memory.copy_from_memory(dst as usize, src_memory, src as usize, size as usize)?; + dst_memory.copy_from_memory(dst, src_memory, src, size)?; } Ok(()) } fn exec_memory_fill(&mut self, addr: u32) -> Result<(), Trap> { - let size = i32::stack_pop(&mut self.store.value_stack); + let mem_addr = self.module.resolve_mem_addr(addr); + let arch = self.store.state.get_mem(mem_addr).kind.arch(); + let size = self.store.value_stack.pop_memory_operand(arch)?; let val = i32::stack_pop(&mut self.store.value_stack); - let dst = i32::stack_pop(&mut self.store.value_stack); - self.exec_memory_fill_impl(addr, dst, val as u8, size) + let dst = self.store.value_stack.pop_memory_operand(arch)?; + self.exec_memory_fill_impl(mem_addr, dst, val as u8, size) } fn exec_memory_fill_imm(&mut self, addr: u32, val: u8, size: i32) -> Result<(), Trap> { - let dst = i32::stack_pop(&mut self.store.value_stack); - self.exec_memory_fill_impl(addr, dst, val, size) + let mem_addr = self.module.resolve_mem_addr(addr); + let arch = self.store.state.get_mem(mem_addr).kind.arch(); + let dst = self.store.value_stack.pop_memory_operand(arch)?; + self.exec_memory_fill_impl(mem_addr, dst, val, size as u32 as usize) } - fn exec_memory_fill_impl(&mut self, addr: u32, dst: i32, val: u8, size: i32) -> Result<(), Trap> { - let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); + fn exec_memory_fill_impl(&mut self, mem_addr: MemAddr, dst: usize, val: u8, size: usize) -> Result<(), Trap> { + let mem = self.store.state.get_mem_mut(mem_addr); let max = mem.inner.len(); - if mem.inner.fill(dst as usize, size as usize, val).is_none() { - return cold!(Err(Trap::MemoryOutOfBounds { offset: dst as usize, len: size as usize, max })); + if mem.inner.fill(dst, size, val)?.is_none() { + return cold!(Err(Trap::MemoryOutOfBounds { offset: dst, len: size, max })); } Ok(()) } fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<(), Trap> { - let size = i32::stack_pop(&mut self.store.value_stack); - let offset = i32::stack_pop(&mut self.store.value_stack); - let dst = i32::stack_pop(&mut self.store.value_stack); + let size = u32::stack_pop(&mut self.store.value_stack) as usize; + let offset = u32::stack_pop(&mut self.store.value_stack) as usize; + let mem_addr = self.module.resolve_mem_addr(mem_index); + let arch = self.store.state.get_mem(mem_addr).kind.arch(); + let dst = self.store.value_stack.pop_memory_operand(arch)?; let data = &self.store.state.data[self.module.resolve_data_addr(data_index) as usize]; - let mem = &mut self.store.state.memories[self.module.resolve_mem_addr(mem_index) as usize]; + let mem = &mut self.store.state.memories[mem_addr as usize]; let data_len = data.data.as_ref().map_or(0, |d| d.len()); let mem_len = mem.inner.len(); - if ((size + offset) as usize > data_len) || ((dst + size) as usize > mem_len) { - return cold!(Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len })); + if offset.checked_add(size).is_none_or(|end| end > data_len) { + return cold!(Err(Trap::MemoryOutOfBounds { offset, len: size, max: data_len })); + } + if dst.checked_add(size).is_none_or(|end| end > mem_len) { + return cold!(Err(Trap::MemoryOutOfBounds { offset: dst, len: size, max: mem_len })); } if size == 0 { @@ -1719,8 +1743,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { return cold!(Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 })); }; - if mem.inner.write_all(dst as usize, &data[offset as usize..((offset + size) as usize)]).is_none() { - return cold!(Err(Trap::MemoryOutOfBounds { offset: dst as usize, len: size as usize, max: mem_len })); + if mem.inner.write_all(dst, &data[offset..offset + size])?.is_none() { + return cold!(Err(Trap::MemoryOutOfBounds { offset: dst, len: size, max: mem_len })); } Ok(()) } @@ -1753,7 +1777,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { lane: u8, ) -> Result<(), Trap> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); - let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; + let base = self.store.value_stack.pop_memory_operand(mem.kind.arch())?; + let addr = mem.effective_addr::(base, offset)?; let val = LOAD::load_at(&*mem.inner, addr)?; let offset = lane as usize * LOAD_SIZE; let mut imm = ::stack_pop(&mut self.store.value_stack).to_mem_bytes(); @@ -1770,7 +1795,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { cast: impl Fn(LOAD) -> TARGET, ) -> Result<(), Trap> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); - let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; + let base = self.store.value_stack.pop_memory_operand(mem.kind.arch())?; + let addr = mem.effective_addr::(base, offset)?; let value = cold_err!(LOAD::load_at(&*mem.inner, addr))?; self.store.value_stack.push(cast(value)) } @@ -1787,8 +1813,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { val_bytes.copy_from_slice(&bytes[lane_offset..lane_offset + N]); let val = U::from_mem_bytes(val_bytes); let mem_addr = self.module.resolve_mem_addr(mem_addr); + let mem = self.store.state.get_mem(mem_addr); + let base = self.store.value_stack.pop_memory_operand(mem.kind.arch())?; + let addr = mem.effective_addr::(base, offset)?; let mem = self.store.state.get_mem_mut(mem_addr); - let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; cold_err!(val.store_at(&mut *mem.inner, addr))?; Ok(()) } @@ -1803,8 +1831,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let val = cast(val); let mem_addr = self.module.resolve_mem_addr(mem_addr); + let mem = self.store.state.get_mem(mem_addr); + let base = self.store.value_stack.pop_memory_operand(mem.kind.arch())?; + let addr = mem.effective_addr::(base, offset)?; let mem = self.store.state.get_mem_mut(mem_addr); - let addr = Self::pop_memory_addr::(&mut self.store.value_stack, mem, offset)?; cold_err!(val.store_at(&mut *mem.inner, addr))?; Ok(()) } @@ -1876,7 +1906,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { MemoryArch::I32 => ::stack_pop(&mut self.store.value_stack) as u32 as u64, MemoryArch::I64 => ::stack_pop(&mut self.store.value_stack) as u64, }; - usize::try_from(value).map_err(|_| Trap::TableOutOfBounds { offset: usize::MAX, len: 1, max: usize::MAX }) + cold_err!(usize::try_from(value).map_err(|_| Trap::TableOutOfBounds { + offset: usize::MAX, + len: 1, + max: usize::MAX, + })) } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 0b37f51..43c214a 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,6 +1,6 @@ use alloc::{vec, vec::Vec}; use core::hint::cold_path; -use tinywasm_types::{ValueCounts, WasmType, WasmValue}; +use tinywasm_types::{MemoryArch, ValueCounts, WasmType, WasmValue}; use super::StackBase; use crate::engine::{Config, StackConfig}; @@ -280,6 +280,24 @@ impl ValueStack { T::stack_push(self, value) } + #[inline(always)] + pub(crate) fn pop_memory_operand(&mut self, arch: MemoryArch) -> Result { + match arch { + MemoryArch::I32 => Ok(self.stack_32.pop() as usize), + MemoryArch::I64 => { + let value = self.stack_64.pop(); + #[cfg(target_pointer_width = "64")] + return Ok(value as usize); + #[cfg(not(target_pointer_width = "64"))] + return cold_err!(usize::try_from(value).map_err(|_| Trap::MemoryOutOfBounds { + offset: usize::MAX, + len: 0, + max: usize::MAX, + })); + } + } + } + #[inline] pub(crate) fn select_multi(&mut self, counts: ValueCounts) { let condition = i32::stack_pop(self) != 0; @@ -305,7 +323,7 @@ impl ValueStack { locals: ValueCounts, base: StackBase, ) -> Result { - self.extend_from_wasmvalues(values).inspect_err(|_| self.truncate_to_base(base))?; + self.extend_wasmvalues(values.iter().copied()).inspect_err(|_| self.truncate_to_base(base))?; self.enter_locals(¶ms, &locals).inspect_err(|_| self.truncate_to_base(base)) } @@ -363,10 +381,6 @@ impl ValueStack { WasmValues { stack: self, state, types: types.iter(), index, pin_refs } } - pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) -> Result<(), Trap> { - self.extend_wasmvalues(values.iter().copied()) - } - pub(crate) fn extend_wasmvalues(&mut self, values: impl Iterator) -> Result<(), Trap> { for value in values { match value { diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index dd31efa..f5d9c23 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -122,7 +122,7 @@ impl crate::std::io::Read for MemoryCursor<'_> { impl crate::std::io::Write for MemoryCursor<'_> { fn write(&mut self, buf: &[u8]) -> crate::std::io::Result { let offset = self.offset()?; - let written = self.memory.inner.write(offset, buf); + let written = self.memory.inner.write(offset, buf).map_err(Error::from)?; self.advance(written)?; Ok(written) } @@ -242,7 +242,7 @@ impl Memory { /// Depending on the configured backend, this may return fewer bytes than requested even when /// more space is available. Use [`Self::copy_from_slice`] when you need the full slice written. pub fn write(&self, store: &mut Store, offset: usize, src: &[u8]) -> Result { - Ok(self.instance_mut(store)?.inner.write(offset, src)) + Ok(self.instance_mut(store)?.inner.write(offset, src)?) } /// Reads exactly `dst.len()` bytes from memory. @@ -281,14 +281,14 @@ impl Memory { /// Fill a slice of memory with a value. pub fn fill(&self, store: &mut Store, offset: usize, len: usize, val: u8) -> Result<()> { - self.instance_mut(store)?.inner.fill(offset, len, val).ok_or_else(|| { + self.instance_mut(store)?.inner.fill(offset, len, val)?.ok_or_else(|| { Error::Trap(crate::Trap::MemoryOutOfBounds { offset, len, max: self.instance(store).unwrap().inner.len() }) }) } /// Copies a full slice into memory. pub fn copy_from_slice(&self, store: &mut Store, offset: usize, data: &[u8]) -> Result<()> { - self.instance_mut(store)?.inner.write_all(offset, data).ok_or_else(|| { + self.instance_mut(store)?.inner.write_all(offset, data)?.ok_or_else(|| { Error::Trap(crate::Trap::MemoryOutOfBounds { offset, len: data.len(), diff --git a/crates/tinywasm/src/store/memory/instance.rs b/crates/tinywasm/src/store/memory/instance.rs index d9c7990..68a075b 100644 --- a/crates/tinywasm/src/store/memory/instance.rs +++ b/crates/tinywasm/src/store/memory/instance.rs @@ -25,40 +25,50 @@ impl core::fmt::Debug for MemoryInstance { impl MemoryInstance { const COPY_CHUNK_SIZE: usize = 4 * 1024; - #[inline(always)] - pub(crate) fn effective_addr_32(&self, base: u32, offset: u64) -> Result { + fn host_size(kind: MemoryType, pages: u64) -> Result { #[cfg(target_pointer_width = "64")] { - debug_assert!(u32::try_from(offset).is_ok(), "validated memory32 offsets fit in u32"); - Ok(base as usize + offset as usize) + pages + .checked_mul(kind.page_size()) + .map(|size| size as usize) + .ok_or(Error::UnsupportedFeature("memory size exceeds the host address space")) } #[cfg(not(target_pointer_width = "64"))] { - match usize::try_from(u64::from(base) + offset) { - Ok(addr) => Ok(addr), - Err(_) => { - cold!(Err(memory_oob(base as usize, N, self.inner.len()))) - } - } + let page_size = usize::try_from(kind.page_size()) + .map_err(|_| Error::UnsupportedFeature("memory page size exceeds the host address space"))?; + let pages = usize::try_from(pages) + .map_err(|_| Error::UnsupportedFeature("memory size exceeds the host address space"))?; + pages.checked_mul(page_size).ok_or(Error::UnsupportedFeature("memory size exceeds the host address space")) } } #[inline(always)] - pub(crate) fn effective_addr_64(&self, base: u64, offset: u64) -> Result { - match base.checked_add(offset).and_then(|addr| usize::try_from(addr).ok()) { - Some(addr) => Ok(addr), - None => { - cold!(Err(memory_oob(base as usize, N, self.inner.len()))) + pub(crate) fn effective_addr(&self, base: usize, offset: u64) -> Result { + #[cfg(target_pointer_width = "64")] + { + if !self.is_64bit() { + debug_assert!(u32::try_from(offset).is_ok(), "validated memory32 offsets fit in u32"); + return Ok(base + offset as usize); + } + match base.checked_add(offset as usize) { + Some(addr) => Ok(addr), + None => cold!(Err(memory_oob(base, N, self.inner.len()))), + } + } + + #[cfg(not(target_pointer_width = "64"))] + { + match usize::try_from(offset).ok().and_then(|offset| base.checked_add(offset)) { + Some(addr) => Ok(addr), + None => cold!(Err(memory_oob(base, N, self.inner.len()))), } } } pub(crate) fn new(kind: MemoryType, backend: &MemoryBackend) -> Result { - assert!(kind.page_count_initial() <= kind.page_count_max()); - - let initial_len = usize::try_from(kind.initial_size()) - .map_err(|_| Error::UnsupportedFeature("memory size exceeds the host address space"))?; + let initial_len = Self::host_size(kind, kind.page_count_initial())?; crate::log::debug!( "initializing memory with {} pages of {} bytes", @@ -78,10 +88,7 @@ impl MemoryInstance { } pub(crate) fn new_lazy(kind: MemoryType, backend: &MemoryBackend) -> Result { - assert!(kind.page_count_initial() <= kind.page_count_max()); - - let initial_len = usize::try_from(kind.initial_size()) - .map_err(|_| Error::UnsupportedFeature("memory size exceeds the host address space"))?; + let initial_len = Self::host_size(kind, kind.page_count_initial())?; crate::log::debug!( "initializing lazy memory with {} pages of {} bytes", @@ -130,7 +137,7 @@ impl MemoryInstance { cold_path(); memory_oob(src + copied, chunk_len, src_memory.inner.len()) })?; - self.inner.write_all(dst + copied, &buf[..chunk_len]).ok_or_else(|| { + self.inner.write_all(dst + copied, &buf[..chunk_len])?.ok_or_else(|| { cold_path(); memory_oob(dst + copied, chunk_len, self.inner.len()) })?; @@ -141,7 +148,7 @@ impl MemoryInstance { } pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<(), Trap> { - self.inner.copy_within(dst, src, len).ok_or_else(|| { + self.inner.copy_within(dst, src, len)?.ok_or_else(|| { cold_path(); memory_oob(dst, len, self.inner.len()) }) @@ -169,16 +176,7 @@ impl MemoryInstance { return Ok(None); } - let Some(new_size) = (new_pages as u64).checked_mul(self.kind.page_size()) else { - return Ok(None); - }; - if new_size > self.kind.max_size() { - cold_path(); - crate::log::debug!("memory.grow failed: new_size={}, max_size={}", new_size, self.kind.max_size()); - return Ok(None); - } - - let Some(new_size) = usize::try_from(new_size).ok() else { + let Ok(new_size) = Self::host_size(self.kind, new_pages as u64) else { return Ok(None); }; if new_size == self.inner.len() { diff --git a/crates/tinywasm/src/store/memory/lazy.rs b/crates/tinywasm/src/store/memory/lazy.rs index 40451bb..780d5b5 100644 --- a/crates/tinywasm/src/store/memory/lazy.rs +++ b/crates/tinywasm/src/store/memory/lazy.rs @@ -20,8 +20,13 @@ pub struct LazyLinearMemory { impl LazyLinearMemory { /// Creates a lazy memory for `ty` using `backend` for eventual storage. pub fn try_new(ty: MemoryType, backend: MemoryBackend) -> Result { - let initial_len = usize::try_from(ty.initial_size()) + let page_size = usize::try_from(ty.page_size()) + .map_err(|_| Error::UnsupportedFeature("memory page size exceeds the host address space"))?; + let pages = usize::try_from(ty.page_count_initial()) .map_err(|_| Error::UnsupportedFeature("memory size exceeds the host address space"))?; + let initial_len = pages + .checked_mul(page_size) + .ok_or(Error::UnsupportedFeature("memory size exceeds the host address space"))?; Ok(Self::new_with_initial_len(ty, initial_len, backend)) } @@ -29,23 +34,9 @@ impl LazyLinearMemory { Self { ty, initial_len, backend, inner: None } } - fn materialize(&mut self) -> &mut dyn LinearMemory { + fn materialize(&mut self) -> core::result::Result<&mut dyn LinearMemory, crate::Trap> { if self.inner.is_none() { - self.inner = - Some(self.backend.create(self.ty, self.initial_len).expect("lazy memory materialization failed")); - } - self.inner.as_deref_mut().expect("lazy memory should be materialized") - } - - fn try_materialize(&mut self) -> core::result::Result<&mut dyn LinearMemory, crate::Trap> { - if self.inner.is_none() { - let storage = match self.backend.create(self.ty, self.initial_len) { - Ok(storage) => storage, - Err(Error::Trap(trap)) => { - return cold!(Err(trap)); - } - Err(err) => panic!("lazy memory materialization failed: {err}"), - }; + let storage = cold_err!(self.backend.create(self.ty, self.initial_len))?; self.inner = Some(storage); } Ok(self.inner.as_deref_mut().expect("lazy memory should be materialized")) @@ -58,7 +49,7 @@ impl LinearMemory for LazyLinearMemory { } fn grow_to(&mut self, new_len: usize) -> Result<(), crate::Trap> { - self.try_materialize()?.grow_to(new_len) + self.materialize()?.grow_to(new_len) } fn read(&self, addr: usize, dst: &mut [u8]) -> usize { @@ -73,45 +64,45 @@ impl LinearMemory for LazyLinearMemory { read_len } - fn write(&mut self, addr: usize, src: &[u8]) -> usize { + fn write(&mut self, addr: usize, src: &[u8]) -> core::result::Result { if src.is_empty() || addr >= self.len() { - return 0; + return Ok(0); } - self.materialize().write(addr, src) + self.materialize()?.write(addr, src) } - fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> { - let end = addr.checked_add(src.len())?; + fn write_all(&mut self, addr: usize, src: &[u8]) -> core::result::Result, crate::Trap> { + let Some(end) = addr.checked_add(src.len()) else { return Ok(None) }; if end > self.len() { - return None; + return Ok(None); } if src.is_empty() { - return Some(()); + return Ok(Some(())); } - self.materialize().write_all(addr, src) + self.materialize()?.write_all(addr, src) } - fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> { - let end = addr.checked_add(len)?; + fn fill(&mut self, addr: usize, len: usize, val: u8) -> core::result::Result, crate::Trap> { + let Some(end) = addr.checked_add(len) else { return Ok(None) }; if end > self.len() { - return None; + return Ok(None); } if len == 0 || val == 0 && self.inner.is_none() { - return Some(()); + return Ok(Some(())); } - self.materialize().fill(addr, len, val) + self.materialize()?.fill(addr, len, val) } - fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> { - let src_end = src.checked_add(len)?; - let dst_end = dst.checked_add(len)?; + fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> core::result::Result, crate::Trap> { + let Some(src_end) = src.checked_add(len) else { return Ok(None) }; + let Some(dst_end) = dst.checked_add(len) else { return Ok(None) }; if src_end > self.len() || dst_end > self.len() { - return None; + return Ok(None); } if self.inner.is_none() || len == 0 || dst == src { - return Some(()); + return Ok(Some(())); } - self.materialize().copy_within(dst, src, len) + self.materialize()?.copy_within(dst, src, len) } } diff --git a/crates/tinywasm/src/store/memory/mod.rs b/crates/tinywasm/src/store/memory/mod.rs index 739a6a4..70ffb20 100644 --- a/crates/tinywasm/src/store/memory/mod.rs +++ b/crates/tinywasm/src/store/memory/mod.rs @@ -1,12 +1,12 @@ -use alloc::{boxed::Box, format, sync::Arc}; +use alloc::{boxed::Box, sync::Arc}; use alloc::{vec, vec::Vec}; use core::cmp::min; use core::hint::cold_path; use tinywasm_types::MemoryType; +use crate::Result; use crate::interpreter::Value128; -use crate::{Error, Result}; mod instance; mod lazy; @@ -48,69 +48,76 @@ pub trait LinearMemory { /// Writes up to `src.len()` bytes starting at `addr` and returns the number of bytes written. /// /// Backends may return fewer bytes than requested even when more space is available. This lets - /// non-contiguous backends stop at a natural boundary such as the end of a chunk. - fn write(&mut self, addr: usize, src: &[u8]) -> usize; + /// non-contiguous backends stop at a natural boundary such as the end of a chunk. Backend + /// failures are returned as traps. + fn write(&mut self, addr: usize, src: &[u8]) -> core::result::Result; - /// Writes all bytes in `src` starting at `addr`, or returns `None` if any byte could not be written. - fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> { + /// Writes all bytes in `src`, returns `Ok(None)` for an invalid range, or returns a backend trap. + fn write_all(&mut self, addr: usize, src: &[u8]) -> core::result::Result, crate::Trap> { let Some(end) = addr.checked_add(src.len()) else { - return cold!(None); + return cold!(Ok(None)); }; if end > self.len() { - return cold!(None); + return cold!(Ok(None)); } let mut offset = 0; while offset < src.len() { - let written = self.write(addr + offset, &src[offset..]); + let written = self.write(addr + offset, &src[offset..])?; if written == 0 { - return cold!(None); + return cold!(Ok(None)); } offset += written; } - Some(()) + Ok(Some(())) } /// Fills the range `[addr, addr + len)` with `val`. - fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> { - let end = addr.checked_add(len)?; + fn fill(&mut self, addr: usize, len: usize, val: u8) -> core::result::Result, crate::Trap> { + let Some(end) = addr.checked_add(len) else { return Ok(None) }; if end > self.len() { - return None; + return Ok(None); } + let chunk = [val; 1024]; let mut offset = 0; while offset < len { let chunk_len = min(len - offset, 1024); - let chunk = vec![val; chunk_len]; - self.write_all(addr + offset, &chunk)?; + if self.write_all(addr + offset, &chunk[..chunk_len])?.is_none() { + return Ok(None); + } offset += chunk_len; } - Some(()) + Ok(Some(())) } /// Copies `len` bytes from `src` to `dst` within the same memory. - fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> { - let src_end = src.checked_add(len)?; - let dst_end = dst.checked_add(len)?; + fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> core::result::Result, crate::Trap> { + let Some(src_end) = src.checked_add(len) else { return Ok(None) }; + let Some(dst_end) = dst.checked_add(len) else { return Ok(None) }; if src_end > self.len() || dst_end > self.len() { - return None; + return Ok(None); } if len == 0 || dst == src { - return Some(()); + return Ok(Some(())); } + let mut chunk = [0; 1024]; + // If the source and destination ranges are disjoint, we can copy forward without a temporary buffer. if dst < src || dst >= src_end { let mut offset = 0; while offset < len { let chunk_len = min(len - offset, 1024); - let mut chunk = vec![0; chunk_len]; - self.read_exact(src + offset, &mut chunk)?; - self.write_all(dst + offset, &chunk)?; + if self.read_exact(src + offset, &mut chunk[..chunk_len]).is_none() + || self.write_all(dst + offset, &chunk[..chunk_len])?.is_none() + { + return Ok(None); + } offset += chunk_len; } } else { @@ -119,13 +126,15 @@ pub trait LinearMemory { while offset > 0 { let chunk_len = min(offset, 1024); offset -= chunk_len; - let mut chunk = vec![0; chunk_len]; - self.read_exact(src + offset, &mut chunk)?; - self.write_all(dst + offset, &chunk)?; + if self.read_exact(src + offset, &mut chunk[..chunk_len]).is_none() + || self.write_all(dst + offset, &chunk[..chunk_len])?.is_none() + { + return Ok(None); + } } } - Some(()) + Ok(Some(())) } /// Reads exactly `dst.len()` bytes starting at `addr`. @@ -214,7 +223,7 @@ pub trait LinearMemory { /// Writes exactly 1 byte at `addr`. fn write_8(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { - self.write_all(addr, bytes).ok_or_else(|| { + self.write_all(addr, bytes)?.ok_or_else(|| { cold_path(); memory_oob(addr, 1, self.len()) }) @@ -222,7 +231,7 @@ pub trait LinearMemory { /// Writes exactly 2 bytes at `addr`. fn write_16(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { - self.write_all(addr, bytes).ok_or_else(|| { + self.write_all(addr, bytes)?.ok_or_else(|| { cold_path(); memory_oob(addr, 2, self.len()) }) @@ -230,7 +239,7 @@ pub trait LinearMemory { /// Writes exactly 4 bytes at `addr`. fn write_32(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { - self.write_all(addr, bytes).ok_or_else(|| { + self.write_all(addr, bytes)?.ok_or_else(|| { cold_path(); memory_oob(addr, 4, self.len()) }) @@ -238,7 +247,7 @@ pub trait LinearMemory { /// Writes exactly 8 bytes at `addr`. fn write_64(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { - self.write_all(addr, bytes).ok_or_else(|| { + self.write_all(addr, bytes)?.ok_or_else(|| { cold_path(); memory_oob(addr, 8, self.len()) }) @@ -246,14 +255,14 @@ pub trait LinearMemory { /// Writes exactly 16 bytes at `addr`. fn write_128(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { - self.write_all(addr, bytes).ok_or_else(|| { + self.write_all(addr, bytes)?.ok_or_else(|| { cold_path(); memory_oob(addr, 16, self.len()) }) } } -type MemoryFactory = dyn Fn(MemoryType) -> Result> + Send + Sync; +type MemoryFactory = dyn Fn(MemoryType) -> core::result::Result, crate::Trap> + Send + Sync; /// Configures how runtime memory instances are created. #[derive(Clone, Default)] @@ -291,9 +300,11 @@ impl MemoryBackend { } /// Uses a custom factory to create memory instances. + /// + /// Factory traps are returned during eager creation or when a lazy memory first materializes. pub fn custom(factory: F) -> Self where - F: Fn(MemoryType) -> Result + Send + Sync + 'static, + F: Fn(MemoryType) -> core::result::Result + Send + Sync + 'static, M: LinearMemory + 'static, { Self(MemoryBackendInner::Custom(Arc::new(move |ty| { @@ -302,22 +313,21 @@ impl MemoryBackend { }))) } - pub(crate) fn create(&self, ty: MemoryType, initial_len: usize) -> Result { + pub(crate) fn create( + &self, + ty: MemoryType, + initial_len: usize, + ) -> core::result::Result { let storage = match &self.0 { - MemoryBackendInner::Vec => { - Box::new(VecMemory::try_new(initial_len).map_err(Error::Trap)?) as Box - } + MemoryBackendInner::Vec => Box::new(VecMemory::try_new(initial_len)?) as Box, MemoryBackendInner::Paged { chunk_size } => { - Box::new(PagedMemory::try_new(initial_len, *chunk_size).map_err(Error::Trap)?) as Box + Box::new(PagedMemory::try_new(initial_len, *chunk_size)?) as Box } MemoryBackendInner::Custom(factory) => factory(ty)?, }; if storage.len() < initial_len { - return Err(Error::Other(format!( - "memory backend returned {} bytes for a memory that requires at least {initial_len}", - storage.len() - ))); + return Err(crate::Trap::Other("memory backend returned less storage than required")); } Ok(storage) diff --git a/crates/tinywasm/src/store/memory/paged.rs b/crates/tinywasm/src/store/memory/paged.rs index 206014e..3e6e5ea 100644 --- a/crates/tinywasm/src/store/memory/paged.rs +++ b/crates/tinywasm/src/store/memory/paged.rs @@ -64,6 +64,41 @@ impl PagedMemory { self.chunks[chunk_idx].as_deref() } + #[inline(always)] + fn read_fixed(&self, addr: usize) -> Result<[u8; N], crate::Trap> { + let Some(end) = addr.checked_add(N).filter(|end| *end <= self.len) else { + return cold!(Err(memory_oob(addr, N, self.len))); + }; + let chunk_idx = addr >> self.chunk_shift; + let chunk_offset = addr & self.chunk_mask; + if end <= ((chunk_idx + 1) << self.chunk_shift) { + let mut bytes = [0; N]; + if let Some(chunk) = self.chunk_slice(chunk_idx) { + bytes.copy_from_slice(&chunk[chunk_offset..chunk_offset + N]); + } + return Ok(bytes); + } + cold_path(); + let mut bytes = [0; N]; + self.read_exact(addr, &mut bytes).ok_or_else(|| memory_oob(addr, N, self.len))?; + Ok(bytes) + } + + #[inline(always)] + fn write_fixed(&mut self, addr: usize, bytes: &[u8]) -> Result<(), crate::Trap> { + let Some(end) = addr.checked_add(N).filter(|end| *end <= self.len) else { + return cold!(Err(memory_oob(addr, N, self.len))); + }; + let chunk_idx = addr >> self.chunk_shift; + let chunk_offset = addr & self.chunk_mask; + if end <= ((chunk_idx + 1) << self.chunk_shift) { + self.chunk_mut(chunk_idx)?[chunk_offset..chunk_offset + N].copy_from_slice(bytes); + return Ok(()); + } + cold_path(); + self.write_all(addr, bytes)?.ok_or_else(|| memory_oob(addr, N, self.len)) + } + #[inline(always)] fn checked_end(&self, addr: usize, len: usize) -> Option { let end = addr.checked_add(len)?; @@ -160,25 +195,23 @@ impl LinearMemory for PagedMemory { } #[inline(always)] - fn write(&mut self, addr: usize, src: &[u8]) -> usize { + fn write(&mut self, addr: usize, src: &[u8]) -> Result { if addr >= self.len || src.is_empty() { - return 0; + return Ok(0); } let chunk_idx = addr >> self.chunk_shift; let chunk_offset = addr & self.chunk_mask; let write_len = min(min(self.chunk_size - chunk_offset, self.len - addr), src.len()); - let Ok(chunk) = self.chunk_mut(chunk_idx) else { - return 0; - }; + let chunk = self.chunk_mut(chunk_idx)?; chunk[chunk_offset..chunk_offset + write_len].copy_from_slice(&src[..write_len]); - write_len + Ok(write_len) } #[inline(always)] - fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> { - let end = self.checked_end(addr, src.len())?; + fn write_all(&mut self, addr: usize, src: &[u8]) -> Result, crate::Trap> { + let Some(end) = self.checked_end(addr, src.len()) else { return Ok(None) }; let mut pos = addr; let mut src_offset = 0; @@ -187,19 +220,19 @@ impl LinearMemory for PagedMemory { let chunk_offset = pos & self.chunk_mask; let copy_len = min(self.chunk_size - chunk_offset, end - pos); - let chunk = self.chunk_mut(chunk_idx).ok()?; + let chunk = self.chunk_mut(chunk_idx)?; chunk[chunk_offset..chunk_offset + copy_len].copy_from_slice(&src[src_offset..src_offset + copy_len]); pos += copy_len; src_offset += copy_len; } - Some(()) + Ok(Some(())) } #[inline(always)] - fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> { - let end = self.checked_end(addr, len)?; + fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result, crate::Trap> { + let Some(end) = self.checked_end(addr, len) else { return Ok(None) }; let mut pos = addr; while pos < end { @@ -217,26 +250,27 @@ impl LinearMemory for PagedMemory { chunk[chunk_offset..chunk_offset + fill_len].fill(0); } } else { - self.chunk_mut(chunk_idx).ok()?[chunk_offset..chunk_offset + fill_len].fill(val); + self.chunk_mut(chunk_idx)?[chunk_offset..chunk_offset + fill_len].fill(val); } pos = chunk_end; } - Some(()) + Ok(Some(())) } #[inline(always)] - fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> { - self.checked_end(src, len)?; - self.checked_end(dst, len)?; + fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result, crate::Trap> { + if self.checked_end(src, len).is_none() || self.checked_end(dst, len).is_none() { + return Ok(None); + } if len == 0 || dst == src { - return Some(()); + return Ok(Some(())); } if self.copy_within_single_chunk(dst, src, len) { - return Some(()); + return Ok(Some(())); } let mut buf = [0u8; 256]; @@ -245,8 +279,11 @@ impl LinearMemory for PagedMemory { let mut copied = 0; while copied < len { let chunk_len = min(buf.len(), len - copied); - self.read_exact(src + copied, &mut buf[..chunk_len])?; - self.write_all(dst + copied, &buf[..chunk_len])?; + if self.read_exact(src + copied, &mut buf[..chunk_len]).is_none() + || self.write_all(dst + copied, &buf[..chunk_len])?.is_none() + { + return Ok(None); + } copied += chunk_len; } } else { @@ -254,13 +291,16 @@ impl LinearMemory for PagedMemory { while remaining > 0 { let chunk_len = min(buf.len(), remaining); let chunk_start = remaining - chunk_len; - self.read_exact(src + chunk_start, &mut buf[..chunk_len])?; - self.write_all(dst + chunk_start, &buf[..chunk_len])?; + if self.read_exact(src + chunk_start, &mut buf[..chunk_len]).is_none() + || self.write_all(dst + chunk_start, &buf[..chunk_len])?.is_none() + { + return Ok(None); + } remaining = chunk_start; } } - Some(()) + Ok(Some(())) } #[inline(always)] @@ -274,6 +314,26 @@ impl LinearMemory for PagedMemory { Ok([self.chunk_slice(chunk_idx).map_or(0, |chunk| chunk[chunk_offset])]) } + #[inline(always)] + fn read_16(&self, addr: usize) -> core::result::Result<[u8; 2], crate::Trap> { + self.read_fixed::<2>(addr) + } + + #[inline(always)] + fn read_32(&self, addr: usize) -> core::result::Result<[u8; 4], crate::Trap> { + self.read_fixed::<4>(addr) + } + + #[inline(always)] + fn read_64(&self, addr: usize) -> core::result::Result<[u8; 8], crate::Trap> { + self.read_fixed::<8>(addr) + } + + #[inline(always)] + fn read_128(&self, addr: usize) -> core::result::Result<[u8; 16], crate::Trap> { + self.read_fixed::<16>(addr) + } + #[inline(always)] fn write_8(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { if addr >= self.len { @@ -285,4 +345,24 @@ impl LinearMemory for PagedMemory { self.chunk_mut(chunk_idx)?[chunk_offset] = bytes[0]; Ok(()) } + + #[inline(always)] + fn write_16(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { + self.write_fixed::<2>(addr, bytes) + } + + #[inline(always)] + fn write_32(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { + self.write_fixed::<4>(addr, bytes) + } + + #[inline(always)] + fn write_64(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { + self.write_fixed::<8>(addr, bytes) + } + + #[inline(always)] + fn write_128(&mut self, addr: usize, bytes: &[u8]) -> core::result::Result<(), crate::Trap> { + self.write_fixed::<16>(addr, bytes) + } } diff --git a/crates/tinywasm/src/store/memory/vec.rs b/crates/tinywasm/src/store/memory/vec.rs index 17b1f83..be85e9b 100644 --- a/crates/tinywasm/src/store/memory/vec.rs +++ b/crates/tinywasm/src/store/memory/vec.rs @@ -81,39 +81,42 @@ impl LinearMemory for VecMemory { } #[inline(always)] - fn write(&mut self, addr: usize, src: &[u8]) -> usize { + fn write(&mut self, addr: usize, src: &[u8]) -> Result { if addr >= self.data.len() { - return 0; + return Ok(0); } let write_len = src.len().min(self.data.len() - addr); self.data[addr..addr + write_len].copy_from_slice(&src[..write_len]); - write_len + Ok(write_len) } #[inline(always)] - fn write_all(&mut self, addr: usize, src: &[u8]) -> Option<()> { - let dst = self.data.get_mut(addr..addr.checked_add(src.len())?)?; + fn write_all(&mut self, addr: usize, src: &[u8]) -> Result, crate::Trap> { + let Some(end) = addr.checked_add(src.len()) else { return Ok(None) }; + let Some(dst) = self.data.get_mut(addr..end) else { return Ok(None) }; dst.copy_from_slice(src); - Some(()) + Ok(Some(())) } #[inline(always)] - fn fill(&mut self, addr: usize, len: usize, val: u8) -> Option<()> { - self.data.get_mut(addr..addr.checked_add(len)?)?.fill(val); - Some(()) + fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result, crate::Trap> { + let Some(end) = addr.checked_add(len) else { return Ok(None) }; + let Some(dst) = self.data.get_mut(addr..end) else { return Ok(None) }; + dst.fill(val); + Ok(Some(())) } #[inline(always)] - fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Option<()> { - let src_end = src.checked_add(len)?; - let dst_end = dst.checked_add(len)?; + fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result, crate::Trap> { + let Some(src_end) = src.checked_add(len) else { return Ok(None) }; + let Some(dst_end) = dst.checked_add(len) else { return Ok(None) }; if src_end > self.data.len() || dst_end > self.data.len() { - return None; + return Ok(None); } self.data.copy_within(src..src_end, dst); - Some(()) + Ok(Some(())) } #[inline(always)] diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 46bb727..33a549c 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -428,7 +428,7 @@ impl Store { }; let offset = usize::try_from(offset).unwrap_or(usize::MAX); - match mem.inner.write_all(offset, &data.data) { + match mem.inner.write_all(offset, &data.data)? { Some(()) => None, None => { return Ok(( diff --git a/crates/tinywasm/tests/memory_backends.rs b/crates/tinywasm/tests/memory_backends.rs index b7f9438..73657df 100644 --- a/crates/tinywasm/tests/memory_backends.rs +++ b/crates/tinywasm/tests/memory_backends.rs @@ -13,12 +13,19 @@ use tinywasm_parser::{Parser, ParserOptions}; type TestResult = Result>; +fn initial_memory_size(ty: MemoryType) -> usize { + usize::try_from(ty.page_count_initial()) + .ok() + .and_then(|pages| pages.checked_mul(usize::try_from(ty.page_size()).ok()?)) + .expect("test memory size should fit usize") +} + fn instantiate_module_with_counting_backend(module: Module) -> TestResult { let created = Arc::new(AtomicUsize::new(0)); let factory_calls = created.clone(); let backend = MemoryBackend::custom(move |ty| { factory_calls.fetch_add(1, Ordering::Relaxed); - Ok(PagedMemory::try_new(ty.initial_size() as usize, 16)?) + PagedMemory::try_new(initial_memory_size(ty), 16) }); let engine = Engine::new(Config::new().with_memory_backend(backend)); let mut store = Store::new(engine); @@ -43,7 +50,7 @@ fn instantiate_exported_memory_with_counting_backend( let factory_calls = created.clone(); let backend = MemoryBackend::custom(move |ty| { factory_calls.fetch_add(1, Ordering::Relaxed); - Ok(PagedMemory::try_new(ty.initial_size() as usize, 16)?) + PagedMemory::try_new(initial_memory_size(ty), 16) }); let engine = Engine::new(Config::new().with_memory_backend(backend)); let mut store = Store::new(engine); @@ -83,7 +90,7 @@ fn custom_backend_factory_is_used_for_host_memories() -> TestResult { let backend = MemoryBackend::custom(move |ty| { factory_calls.fetch_add(1, Ordering::Relaxed); page_size_seen.store(ty.page_size() as usize, Ordering::Relaxed); - Ok(PagedMemory::try_new(ty.initial_size() as usize, 16)?) + PagedMemory::try_new(initial_memory_size(ty), 16) }); let engine = Engine::new(Config::new().with_memory_backend(backend)); @@ -240,6 +247,43 @@ fn paged_read_and_write_stop_at_chunk_boundaries() -> TestResult { Ok(()) } +#[test] +fn paged_fixed_width_accesses_cross_chunk_boundaries() -> TestResult { + use tinywasm::LinearMemory; + + let mut memory = PagedMemory::try_new(32, 4).map_err(tinywasm::Error::from)?; + memory.write_32(2, &0x1234_5678u32.to_le_bytes()).map_err(tinywasm::Error::from)?; + memory.write_64(6, &0x0123_4567_89ab_cdefu64.to_le_bytes()).map_err(tinywasm::Error::from)?; + memory.write_128(14, &u128::MAX.to_le_bytes()).map_err(tinywasm::Error::from)?; + + assert_eq!(u32::from_le_bytes(memory.read_32(2).map_err(tinywasm::Error::from)?), 0x1234_5678); + assert_eq!(u64::from_le_bytes(memory.read_64(6).map_err(tinywasm::Error::from)?), 0x0123_4567_89ab_cdef); + assert_eq!(u128::from_le_bytes(memory.read_128(14).map_err(tinywasm::Error::from)?), u128::MAX); + Ok(()) +} + +#[test] +fn lazy_custom_backend_creation_trap_is_propagated() -> TestResult { + let wasm = wat::parse_str(r#"(module (memory (export "memory") 1))"#)?; + let module = tinywasm::parse_bytes(&wasm)?; + let backend = MemoryBackend::custom(|_| Err::(tinywasm::Trap::Other("backend unavailable"))); + let mut store = Store::new(Engine::new(Config::new().with_memory_backend(backend))); + let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + let memory = instance.memory("memory")?; + + assert_eq!( + memory.copy_from_slice(&mut store, 0, &[1]).unwrap_err(), + tinywasm::Error::from(tinywasm::Trap::Other("backend unavailable")) + ); + Ok(()) +} + +#[test] +fn memory64_default_limit_is_not_memory32_limit() { + let ty = MemoryType::new(MemoryArch::I64, 65_537, None, None); + assert!(ty.page_count_max() > 65_536); +} + #[cfg(feature = "std")] #[test] fn memory_cursor_supports_read_write_and_seek() -> TestResult { diff --git a/crates/tinywasm/tests/wasm-custom/memory64-bulk.wast b/crates/tinywasm/tests/wasm-custom/memory64-bulk.wast new file mode 100644 index 0000000..3302a72 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/memory64-bulk.wast @@ -0,0 +1,74 @@ +;; Memory64 bulk operations use the target memory's address width. +(module + (memory (export "memory") i64 1) + (data "abc") + (func (export "run") + (memory.init 0 (i64.const 4) (i32.const 0) (i32.const 3)) + (memory.copy (i64.const 8) (i64.const 4) (i64.const 3)) + (memory.fill (i64.const 12) (i32.const 120) (i64.const 2))) + (func (export "load8") (param i64) (result i32) + (i32.load8_u (local.get 0)))) + +(invoke "run") +(assert_return (invoke "load8" (i64.const 4)) (i32.const 97)) +(assert_return (invoke "load8" (i64.const 6)) (i32.const 99)) +(assert_return (invoke "load8" (i64.const 8)) (i32.const 97)) +(assert_return (invoke "load8" (i64.const 10)) (i32.const 99)) +(assert_return (invoke "load8" (i64.const 12)) (i32.const 120)) +(assert_return (invoke "load8" (i64.const 13)) (i32.const 120)) + +;; Mixed-width memory.copy pops each operand from the correct value lane. +(module + (memory $src 1) + (memory $dst i64 1) + (data (memory $src) (i32.const 0) "abc") + (func (export "run") + (memory.copy $dst $src (i64.const 5) (i32.const 0) (i32.const 3))) + (func (export "load8") (param i64) (result i32) + (i32.load8_u $dst (local.get 0)))) + +(invoke "run") +(assert_return (invoke "load8" (i64.const 5)) (i32.const 97)) +(assert_return (invoke "load8" (i64.const 7)) (i32.const 99)) + +;; Unsigned memory.init source bounds produce a Wasm trap rather than a host panic. +(module + (memory 1) + (data "x") + (func (export "run") + (memory.init 0 (i32.const 0) (i32.const -1) (i32.const 1)))) + +(assert_trap (invoke "run") "out of bounds memory access") + +;; memory.init must mark its target local memory as used by allocation analysis. +(module $host + (memory (export "memory") 1)) +(register "host" $host) + +(module + (import "host" "memory" (memory 1)) + (memory $local 1) + (data $data "x") + (func (export "run") + (memory.init $local $data (i32.const 0) (i32.const 0) (i32.const 1)))) + +(assert_return (invoke "run")) + +;; Store superinstructions select the address lane from the target memory. +(module + (memory i64 1) + (func (export "store") (param i64 i32) + (i32.store (local.get 0) (local.get 1))) + (func (export "fma") (param i64 f32 f32 f32) + (f32.store + (local.get 0) + (f32.add (local.get 1) (f32.mul (local.get 2) (local.get 3))))) + (func (export "load_i32") (param i64) (result i32) + (i32.load (local.get 0))) + (func (export "load_f32") (param i64) (result f32) + (f32.load (local.get 0)))) + +(invoke "store" (i64.const 4) (i32.const 42)) +(invoke "fma" (i64.const 8) (f32.const 1) (f32.const 2) (f32.const 3)) +(assert_return (invoke "load_i32" (i64.const 4)) (i32.const 42)) +(assert_return (invoke "load_f32" (i64.const 8)) (f32.const 7)) diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index a43c82c..10ab028 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -409,7 +409,7 @@ pub enum Instruction { TableFill(TableAddr), // > Bulk Memory Instructions - MemoryInit(MemAddr, DataAddr), + MemoryInit(DataAddr, MemAddr), MemoryCopy { dst_mem: MemAddr, src_mem: MemAddr }, MemoryFill(MemAddr), MemoryFillImm(MemAddr, u8, i32), @@ -572,11 +572,10 @@ impl Instruction { | Self::V128Store16Lane(arg, ..) | Self::V128Store32Lane(arg, ..) | Self::V128Store64Lane(arg, ..) => Some(arg.mem_addr()), - Self::MemorySize(mem) - | Self::MemoryGrow(mem) - | Self::MemoryInit(mem, ..) - | Self::MemoryFill(mem) - | Self::MemoryFillImm(mem, ..) => Some(*mem), + Self::MemorySize(mem) | Self::MemoryGrow(mem) | Self::MemoryFill(mem) | Self::MemoryFillImm(mem, ..) => { + Some(*mem) + } + Self::MemoryInit(_, mem) => Some(*mem), Self::MemoryCopy { dst_mem, src_mem } => Some(if *dst_mem >= *src_mem { *dst_mem } else { *src_mem }), _ => None, } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 78a8d0d..9a7071c 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -14,10 +14,11 @@ use core::ops::{Deref, Range}; // Memory defaults const MEM_PAGE_SIZE: u64 = 65536; -const MAX_MEMORY_SIZE: u64 = 4294967296; - -const fn max_page_count(page_size: u64) -> u64 { - MAX_MEMORY_SIZE / page_size +const fn max_page_count(arch: MemoryArch, page_size: u64) -> u64 { + match arch { + MemoryArch::I32 => (1u64 << 32) / page_size, + MemoryArch::I64 => u64::MAX / page_size, + } } mod instructions; @@ -611,7 +612,11 @@ impl MemoryType { #[inline] pub const fn page_count_max(&self) -> u64 { - if let Some(page_count_max) = self.page_count_max { page_count_max } else { max_page_count(self.page_size()) } + if let Some(page_count_max) = self.page_count_max { + page_count_max + } else { + max_page_count(self.arch, self.page_size()) + } } #[inline] @@ -619,16 +624,6 @@ impl MemoryType { if let Some(page_size) = self.page_size { page_size } else { MEM_PAGE_SIZE } } - #[inline] - pub const fn initial_size(&self) -> u64 { - self.page_count_initial * self.page_size() - } - - #[inline] - pub const fn max_size(&self) -> u64 { - self.page_count_max() * self.page_size() - } - /// Set a different memory architecture. pub const fn with_arch(mut self, arch: MemoryArch) -> Self { self.arch = arch;