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/crates/cli/src/wast_runner.rs b/crates/cli/src/wast_runner.rs index c773663..e53e48a 100644 --- a/crates/cli/src/wast_runner.rs +++ b/crates/cli/src/wast_runner.rs @@ -791,6 +791,9 @@ enum ExpectedValue { RefNull, RefFunc, RefExtern, + RefAny, + RefEq, + RefI31, } impl ExpectedValue { @@ -800,6 +803,9 @@ impl ExpectedValue { 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 => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()), + Self::RefI31 => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()), } } } @@ -817,7 +823,9 @@ 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, RefEq, RefExtern, RefFunc, RefI31, RefI31Shared, RefNull, V128, + }; Ok(match ret { F32(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?), F64(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?), @@ -832,6 +840,9 @@ fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result { bail!("unsupported arg type: reffunc: {:?}", v); } + RefAny => ExpectedValue::RefAny, + RefEq => ExpectedValue::RefEq, + RefI31 | RefI31Shared => ExpectedValue::RefI31, a => { bail!("unsupported arg type {:?}", a); } diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 600976a..043f961 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,74 @@ 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}"))) + } + _ => 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 +283,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 +324,9 @@ 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::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 +356,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 +386,22 @@ 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)? + } + index => { + 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..bca9b2f 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 @@ -128,6 +149,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..e081652 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, TableDefinition, TypeSection, + ValueCounts, WasmFunctionData, WasmType, }; use wasmparser::{FunctionBody, OperatorsReader, OperatorsReaderAllocations, VisitSimdOperator}; @@ -85,7 +85,7 @@ pub(crate) struct Signature { } pub(crate) struct ModuleMetadata { - signatures: Vec, + signatures: Vec>, functions: Vec, globals: Vec, memories: Vec, @@ -151,7 +151,7 @@ struct ValidateThenVisit<'a, 'm> { impl ModuleMetadata { pub(crate) fn new( - types: &[FuncType], + types: &TypeSection, code_type_addrs: &[u32], imports: &[Import], globals: &[Global], @@ -178,10 +178,13 @@ 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 } @@ -190,7 +193,8 @@ impl ModuleMetadata { 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> { @@ -339,8 +343,18 @@ 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_any_convert_extern => AnyConvertExtern, visit_extern_convert_any => ExternConvertAny, + 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, @@ -373,7 +387,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 +435,16 @@ 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 } + unsupported [] { visit_array_len } + unsupported [u32] { + visit_struct_new, visit_struct_new_default, visit_array_new, visit_array_new_default, + visit_array_get, visit_array_get_s, visit_array_get_u, visit_array_set, visit_array_fill, + } + unsupported [u32, u32] { + visit_struct_get, visit_struct_get_s, visit_struct_get_u, visit_struct_set, + visit_array_new_fixed, visit_array_new_data, visit_array_new_elem, visit_array_copy, + visit_array_init_data, visit_array_init_elem, + } } fn visit_call(&mut self, function_index: u32) -> Self::Output { @@ -718,14 +743,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 +1004,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 +1158,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/src/error.rs b/crates/tinywasm/src/error.rs index b5885ca..0cbdbf3 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -172,6 +172,12 @@ pub enum Trap { /// 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 @@ -201,6 +207,8 @@ impl Trap { Self::UninitializedElement { .. } => "uninitialized element", Self::NullReference => "null 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", @@ -303,6 +311,8 @@ impl Display for Trap { } Self::NullReference => write!(f, "null 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..4f71957 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -44,7 +44,7 @@ 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.get_canonical_func_type(func_instance.type_addr)) } self.item.validate_store(store)?; @@ -354,7 +354,10 @@ 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.get_canonical_func_type(func_instance.type_addr), + ) } } } @@ -445,7 +448,7 @@ impl<'store> FuncExecution<'store> { self.state = FuncExecutionState::Completed { result: None }; Ok(ExecProgress::Completed(collect_call_results( &mut self.store.value_stack, - self.store.state.get_type(result_ty), + self.store.state.get_canonical_func_type(result_ty), )?)) } crate::interpreter::ExecState::Suspended(callframe) => { @@ -517,7 +520,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)) { 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..0cb62c3 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()..] diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 5e78dae..839c954 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,30 @@ 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(if ValueRef::stack_peek(&self.store.value_stack).is_null() && !ty.is_nullable() { + Trap::NullReference + } else { + Trap::CastFailure + }); + } + } + AnyConvertExtern | ExternConvertAny => {} MemorySize(addr) => self.exec_memory_size(*addr)?, MemoryGrow(addr) => self.exec_memory_grow(*addr)?, @@ -988,7 +1009,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { host_func: Rc, type_addr: TypeAddr, ) -> Result { - let ty = self.store.state.get_type(type_addr); + let ty = self.store.state.get_canonical_func_type(type_addr); let mut params = self.store.value_stack.pop_types(ty.params().iter().rev()).collect::>(); params.reverse(); let result = host_func.call(FuncContext { store: self.store, module_id: self.module.id() }, ¶ms); @@ -1092,11 +1113,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 { @@ -1327,6 +1348,31 @@ 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_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 +1577,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 +1586,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 +1626,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 +1641,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/values.rs b/crates/tinywasm/src/interpreter/values.rs index be2e0a1..ce24d5d 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,7 @@ impl Default for ValueRef { } impl ValueRef { - pub(crate) const NULL: Self = Self(u32::MAX); + pub(crate) const NULL: Self = Self(0); #[inline] pub(crate) const fn from_raw(raw: u32) -> Self { @@ -25,16 +23,20 @@ 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) } #[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 +44,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 @@ -105,13 +122,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 +147,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..5fcb220 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 { @@ -394,16 +396,20 @@ impl Table { } /// 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()) + Ok(elements.iter().copied().map(move |value| { + TinyWasmValue::ValueRef(value) + .attach_type(WasmType::Ref(element_type)) + .expect("table value matches its element type") + })) } /// Set a table element. diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs index f3a8602..28a0344 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -1,4 +1,4 @@ -use crate::TableElement; +use crate::interpreter::ValueRef; use alloc::vec::Vec; /// A WebAssembly Element Instance @@ -6,7 +6,7 @@ use alloc::vec::Vec; /// 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 } impl ElementInstance { diff --git a/crates/tinywasm/src/store/gc/arena.rs b/crates/tinywasm/src/store/gc/arena.rs new file mode 100644 index 0000000..a52a775 --- /dev/null +++ b/crates/tinywasm/src/store/gc/arena.rs @@ -0,0 +1,267 @@ +//! Mark-and-sweep storage with stable handles for WebAssembly GC objects. + +use alloc::vec::Vec; +use core::mem::size_of; +use core::num::NonZeroU32; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// 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, 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.load(Ordering::Relaxed) + { + return; + } + slot.marked.store(true, Ordering::Relaxed); + worklist.push(handle.index); +} + +struct Slot { + generation: NonZeroU32, + marked: AtomicBool, + 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.get_mut() = 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: AtomicBool::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.get_mut() { + *slot.marked.get_mut() = 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..feb3d55 --- /dev/null +++ b/crates/tinywasm/src/store/gc/mod.rs @@ -0,0 +1,3 @@ +mod arena; + +pub(crate) use arena::{AllocError, Arena, Handle, Trace}; diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 517a561..3d2dc67 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,31 +10,23 @@ use crate::{Engine, Error, ModuleInstance, Result, Trap}; mod data; mod element; mod function; +#[allow(dead_code, unused_imports)] +mod gc; mod global; mod memory; +mod state; mod table; +mod types; 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 @@ -130,140 +121,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 { @@ -292,60 +149,11 @@ impl Store { } } -// 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 - } - /// 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 { @@ -371,10 +179,10 @@ impl Store { 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()), + 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() { @@ -431,17 +239,17 @@ impl Store { Ok(()) } - fn elem_addr(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result> { + fn elem_value(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result { match item { ElementItem::Expr(expr) => match self.eval_const(expr, globals, funcs)? { - TinyWasmValue::ValueRef(v) => Ok(v.addr()), + 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!( @@ -467,7 +275,7 @@ impl Store { let init = element .items .iter() - .map(|item| Ok(TableElement::from(self.elem_addr(item, global_addrs, func_addrs)?))) + .map(|item| self.elem_value(item, global_addrs, func_addrs)) .collect::>>()?; let items = match &element.kind { @@ -634,7 +442,7 @@ impl Store { GlobalGet(addr) => resolve_global(*addr)?, Ref(tinywasm_types::RefValue::Null) => TinyWasmValue::ValueRef(ValueRef::NULL), Ref(tinywasm_types::RefValue::Func(func)) => { - TinyWasmValue::ValueRef(ValueRef::from_raw(resolve_func(func.addr())?)) + TinyWasmValue::ValueRef(ValueRef::from_category_addr(resolve_func(func.addr())?)) } Ref(_) => return Err(Error::other("unsupported reference constant")), _ => { @@ -657,12 +465,26 @@ impl Store { GlobalGet(addr) => stack.push(resolve_global(*addr)?), 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(ValueRef::from_category_addr(resolve_func(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); + } 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..714a7aa --- /dev/null +++ b/crates/tinywasm/src/store/state.rs @@ -0,0 +1,247 @@ +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, +} + +impl State { + /// 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(); + } + 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); + } + + // Step 3 will resolve nonzero even values to their GC object's canonical type. + false + } + + #[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..7be4c73 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()); @@ -46,26 +46,28 @@ impl TableInstance { } pub(crate) fn get_wasm_val(&self, addr: usize) -> Result { - Ok(self.get(addr)?.to_wasm_value(self.kind.element_type)) + Ok(crate::interpreter::TinyWasmValue::ValueRef(*self.get(addr)?) + .attach_type(WasmType::Ref(self.kind.element_type)) + .expect("table value matches its 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 +78,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 +101,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::*; @@ -167,8 +130,9 @@ mod tests { 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"); + let func = crate::interpreter::ValueRef::from_category_addr(0); + table_instance.set(0, func).expect("Setting table element failed"); + table_instance.set(1, ValueRef::NULL).expect("Setting table element failed"); match table_instance.get_wasm_val(0) { Ok(WasmValue::Ref(RefValue::Func(_))) => {} @@ -191,14 +155,12 @@ mod tests { 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 +168,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/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..badb55c 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,139,645,[{"name":"array.wast","passed":8,"failed":46},{"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":7,"failed":23},{"name":"type-subtyping.wast","passed":91,"failed":39}] 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..f6293cf 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -54,6 +54,9 @@ pub enum ConstInstruction { V128Const([u8; 16]), GlobalGet(GlobalAddr), Ref(RefValue), + RefI31, + AnyConvertExtern, + ExternConvertAny, I32Add, I32Sub, I32Mul, @@ -272,6 +275,15 @@ pub enum Instruction { RefFunc(FuncAddr), RefIsNull, RefAsNonNull, + RefI31, + I31GetS, + I31GetU, + RefEq, + RefTest(RefType), + RefCast(RefType), + BrOnCast(u32, RefType, bool), + AnyConvertExtern, + ExternConvertAny, // > 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..69f5e51 100644 --- a/crates/types/src/reference.rs +++ b/crates/types/src/reference.rs @@ -31,7 +31,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,7 +117,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. + // Concrete runtime references are functions until GC objects are added. self.is_concrete() || matches!(self.abstract_heap_type(), Some(AbstractHeapType::Func | AbstractHeapType::NoFunc)) } @@ -132,6 +133,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 +145,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 +165,10 @@ impl FuncRef { } } +/// An opaque external reference. +/// +/// Packed as `[payload:31 i31:1]`. Host addresses use non-zero even values, +/// while 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 +177,27 @@ pub struct ExternRef(u32); impl ExternRef { #[inline] pub const fn new(addr: u32) -> Self { - Self(addr) + let Some(raw) = addr.checked_add(1) else { panic!("external reference address is too large") }; + let Some(raw) = raw.checked_mul(2) else { panic!("external reference address is too large") }; + Self(raw) } + #[doc(hidden)] #[inline] - pub const fn addr(self) -> u32 { + 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,15 +215,22 @@ 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. - #[doc(hidden)] #[inline] pub const fn from_raw(raw: u32) -> Self { @@ -234,20 +249,6 @@ 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 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), +}