diff --git a/.cargo/config.toml b/.cargo/config.toml index f9691ee..743421a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,5 +2,6 @@ version-dev="workspaces version --no-git-commit --force tinywasm*" dev="run -- -l debug run" -test-mvp="test --package tinywasm --test mvp -- test_mvp --exact --nocapture --ignored" -generate-charts="test --package tinywasm --test mvp -- generate_charts --ignored" +test-mvp="test --package tinywasm --test test-mvp" +test-wast="test --package tinywasm --test test-wast --" +generate-charts="test --package tinywasm --test generate-charts" diff --git a/.gitignore b/.gitignore index 0d6dc9e..1e56606 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target notes.md -examples/tinywasm.wat \ No newline at end of file +examples/tinywasm.wat +examples/wast/* \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fa8ac8..8c00a34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,20 @@ -# Common Commands +# Scripts and Commands -> To improve the development experience, a number of aliases have been added to the `.cargo/config.toml` file. These can be run using `cargo `. +> To improve the development experience, a number of custom commands and aliases have been added to the `.cargo/config.toml` file. These can be run using `cargo `. -- **`cargo dev`**\ +- **`cargo dev [args]`**\ e.g. `cargo dev -f check ./examples/wasm/call.wat -a i32:0`\ - Run the development version of the tinywasm-cli. This is the main command used for developing new features. + Run the development version of the tinywasm-cli. This is the main command used for developing new features.\ + See [tinywasm-cli](./crates/cli) for more information. - **`cargo generate-charts`**\ - Generate test result charts + Generate test result charts from the previous test runs. This is used to generate the charts in the [README](./README.md). - **`cargo test-mvp`**\ - Run the WebAssembly MVP (1.0) test suite + Run the WebAssembly MVP (1.0) test suite. Be sure to cloned this repo with `--recursive` or initialize the submodules with `git submodule update --init --recursive` + +- **`cargo test-wast `**\ + Run a single WAST test file. e.g. `cargo test-wast ./examples/wast/i32.wast` - **`cargo version-dev`**\ Bump the version to the next dev version. This should be used after a release so test results are not overwritten. Does not create a new github release. @@ -22,5 +26,5 @@ - **`cargo workspaces version`**\ Bump the version of all crates in the workspace and push changes to git. This is used for releasing new versions on github. -- **`cargo workspaces publish --from-git`**\ +- **`cargo workspaces publish --publish-as-is`**\ Publish all crates in the workspace to crates.io. This should be used a new version has been released on github. After publishing, the version should be bumped to the next dev version. diff --git a/Cargo.lock b/Cargo.lock index 710ebe2..acee43d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.43", ] [[package]] @@ -241,7 +241,7 @@ dependencies = [ "eyre", "indenter", "once_cell", - "owo-colors", + "owo-colors 3.5.0", ] [[package]] @@ -423,9 +423,9 @@ dependencies = [ [[package]] name = "fdeflate" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" dependencies = [ "simd-adler32", ] @@ -765,9 +765,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -784,6 +784,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + [[package]] name = "pathfinder_geometry" version = "0.5.1" @@ -805,9 +811,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "plotters" @@ -880,9 +886,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -1029,7 +1035,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.41", + "syn 2.0.43", "walkdir", ] @@ -1116,7 +1122,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.43", ] [[package]] @@ -1166,9 +1172,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -1192,22 +1198,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.43", ] [[package]] @@ -1227,12 +1233,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tinywasm" -version = "0.0.5" +version = "0.1.0" dependencies = [ "eyre", "log", - "owo-colors", + "owo-colors 4.0.0", "plotters", + "pretty_env_logger", "serde", "serde_json", "tinywasm-parser", @@ -1243,7 +1250,7 @@ dependencies = [ [[package]] name = "tinywasm-cli" -version = "0.0.5" +version = "0.1.0" dependencies = [ "argh", "color-eyre", @@ -1255,7 +1262,7 @@ dependencies = [ [[package]] name = "tinywasm-parser" -version = "0.0.5" +version = "0.1.0" dependencies = [ "log", "tinywasm-types", @@ -1264,7 +1271,7 @@ dependencies = [ [[package]] name = "tinywasm-types" -version = "0.0.5" +version = "0.1.0" dependencies = [ "log", "rkyv", @@ -1343,7 +1350,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.43", "wasm-bindgen-shared", ] @@ -1365,7 +1372,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.43", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 067fca2..bf078c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members=["crates/cli"] resolver="2" [workspace.package] -version="0.0.5" +version="0.1.0" edition="2021" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] diff --git a/README.md b/README.md index e272fff..00b828e 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ I'm currently working on supporting the WebAssembly MVP (1.0) specification. You can see the current status in the graph below. The goal is to support all the features of the MVP specification and then move on to the next version.

- - + +

diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 866f8cb..1a23d16 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,7 +14,7 @@ path="src/bin.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tinywasm={version="0.0.5-alpha.0", path="../tinywasm", features=["std", "parser"]} +tinywasm={version="0.1.0", path="../tinywasm", features=["std", "parser"]} argh="0.1" color-eyre={version="0.6", default-features=false} log="0.4" diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index bbdf4ac..29908b6 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace=true # TODO: create dependency free parser wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} log={version="0.4", optional=true} -tinywasm-types={version="0.0.5-alpha.0", path="../types"} +tinywasm-types={version="0.1.0", path="../types"} [features] default=["std", "logging"] diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index fb48321..b962ff4 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -1,53 +1,166 @@ use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use log::info; use tinywasm_types::{ - BlockArgs, ConstInstruction, Export, ExternalKind, FuncType, Global, Instruction, MemArg, MemoryArch, MemoryType, - TableType, ValType, + BlockArgs, ConstInstruction, ElementItem, Export, ExternalKind, FuncType, Global, GlobalType, Import, ImportKind, + Instruction, MemArg, MemoryArch, MemoryType, TableType, ValType, }; -use wasmparser::{FuncValidator, ValidatorResources}; +use wasmparser::{FuncValidator, OperatorsReader, ValidatorResources}; use crate::{module::CodeSection, Result}; +pub(crate) fn convert_module_elements<'a, T: IntoIterator>>>( + elements: T, +) -> Result> { + let elements = elements + .into_iter() + .map(|element| convert_module_element(element?)) + .collect::>>()?; + Ok(elements) +} + +pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result { + let kind = match element.kind { + wasmparser::ElementKind::Active { + table_index, + offset_expr, + } => tinywasm_types::ElementKind::Active { + table: table_index, + offset: process_const_operators(offset_expr.get_operators_reader())?, + }, + wasmparser::ElementKind::Passive => tinywasm_types::ElementKind::Passive, + wasmparser::ElementKind::Declared => tinywasm_types::ElementKind::Declared, + }; + + let items = match element.items { + wasmparser::ElementItems::Functions(funcs) => funcs + .into_iter() + .map(|func| Ok(ElementItem::Func(func?))) + .collect::>>()? + .into_boxed_slice(), + + wasmparser::ElementItems::Expressions(exprs) => exprs + .into_iter() + .map(|expr| { + Ok(ElementItem::Expr(process_const_operators( + expr?.get_operators_reader(), + )?)) + }) + .collect::>>()? + .into_boxed_slice(), + }; + + Ok(tinywasm_types::Element { + kind, + items, + ty: convert_valtype(&element.ty), + range: element.range, + }) +} + +pub(crate) fn convert_module_data_sections<'a, T: IntoIterator>>>( + data_sections: T, +) -> Result> { + let data_sections = data_sections + .into_iter() + .map(|data| convert_module_data(data?)) + .collect::>>()?; + Ok(data_sections) +} + +pub(crate) fn convert_module_data(data: wasmparser::Data<'_>) -> Result { + Ok(tinywasm_types::Data { + data: data.data.to_vec().into_boxed_slice(), + range: data.range, + kind: match data.kind { + wasmparser::DataKind::Active { + memory_index, + offset_expr, + } => { + let offset = process_const_operators(offset_expr.get_operators_reader())?; + tinywasm_types::DataKind::Active { + mem: memory_index, + offset, + } + } + wasmparser::DataKind::Passive => tinywasm_types::DataKind::Passive, + }, + }) +} + +pub(crate) fn convert_module_imports<'a, T: IntoIterator>>>( + imports: T, +) -> Result> { + let imports = imports + .into_iter() + .map(|import| convert_module_import(import?)) + .collect::>>()?; + Ok(imports) +} + +pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result { + Ok(Import { + module: import.module.to_string().into_boxed_str(), + name: import.name.to_string().into_boxed_str(), + kind: match import.ty { + wasmparser::TypeRef::Func(ty) => ImportKind::Func(ty), + wasmparser::TypeRef::Table(ty) => ImportKind::Table(convert_module_table(ty)?), + wasmparser::TypeRef::Memory(ty) => ImportKind::Mem(convert_module_memory(ty)?), + wasmparser::TypeRef::Global(ty) => ImportKind::Global(GlobalType { + mutable: ty.mutable, + ty: convert_valtype(&ty.content_type), + }), + wasmparser::TypeRef::Tag(ty) => { + return Err(crate::ParseError::UnsupportedOperator(format!( + "Unsupported import kind: {:?}", + ty + ))) + } + }, + }) +} + pub(crate) fn convert_module_memories>>( memory_types: T, ) -> Result> { let memory_type = memory_types .into_iter() - .map(|memory| { - let memory = memory?; - Ok(MemoryType { - arch: match memory.memory64 { - true => MemoryArch::I64, - false => MemoryArch::I32, - }, - page_count_initial: memory.initial, - page_count_max: memory.maximum, - }) - }) + .map(|memory| convert_module_memory(memory?)) .collect::>>()?; Ok(memory_type) } +pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> Result { + Ok(MemoryType { + arch: match memory.memory64 { + true => MemoryArch::I64, + false => MemoryArch::I32, + }, + page_count_initial: memory.initial, + page_count_max: memory.maximum, + }) +} + pub(crate) fn convert_module_tables>>( table_types: T, ) -> Result> { let table_type = table_types .into_iter() - .map(|table| { - let table = table?; - let ty = convert_valtype(&table.element_type); - Ok(TableType { - element_type: ty, - size_initial: table.initial, - size_max: table.maximum, - }) - }) + .map(|table| convert_module_table(table?)) .collect::>>()?; Ok(table_type) } +pub(crate) fn convert_module_table(table: wasmparser::TableType) -> Result { + let ty = convert_valtype(&table.element_type); + Ok(TableType { + element_type: ty, + size_initial: table.initial, + size_max: table.maximum, + }) +} + pub(crate) fn convert_module_globals<'a, T: IntoIterator>>>( globals: T, ) -> Result> { @@ -56,23 +169,14 @@ pub(crate) fn convert_module_globals<'a, T: IntoIterator>>()?; - - // In practice, the len can never be something other than 2, - // but we'll keep this here since it's part of the spec - // Invalid modules will be rejected by the validator anyway (there are also tests for this in the testsuite) - assert!(ops.len() >= 2); - assert!(matches!(ops[ops.len() - 1], wasmparser::Operator::End)); + let ops = global.init_expr.get_operators_reader(); Ok(Global { - ty, - init: process_const_operator(ops[ops.len() - 2].clone())?, - mutable: global.ty.mutable, + init: process_const_operators(ops)?, + ty: GlobalType { + mutable: global.ty.mutable, + ty, + }, }) }) .collect::>>()?; @@ -179,6 +283,17 @@ pub(crate) fn convert_memarg(memarg: wasmparser::MemArg) -> MemArg { } } +pub(crate) fn process_const_operators(ops: OperatorsReader) -> Result { + let ops = ops.into_iter().collect::>>()?; + // In practice, the len can never be something other than 2, + // but we'll keep this here since it's part of the spec + // Invalid modules will be rejected by the validator anyway (there are also tests for this in the testsuite) + assert!(ops.len() >= 2); + assert!(matches!(ops[ops.len() - 1], wasmparser::Operator::End)); + + process_const_operator(ops[ops.len() - 2].clone()) +} + pub fn process_const_operator(op: wasmparser::Operator) -> Result { match op { wasmparser::Operator::I32Const { value } => Ok(ConstInstruction::I32Const(value)), @@ -292,7 +407,8 @@ pub fn process_operators<'a>( .. } => Instruction::CallIndirect(type_index, table_index), Drop => Instruction::Drop, - Select => Instruction::Select, + Select => Instruction::Select(None), + TypedSelect { ty } => Instruction::Select(Some(convert_valtype(&ty))), LocalGet { local_index } => Instruction::LocalGet(local_index), LocalSet { local_index } => Instruction::LocalSet(local_index), LocalTee { local_index } => Instruction::LocalTee(local_index), @@ -430,8 +546,13 @@ pub fn process_operators<'a>( I32TruncF32U => Instruction::I32TruncF32U, I32TruncF64S => Instruction::I32TruncF64S, I32TruncF64U => Instruction::I32TruncF64U, + I64Extend8S => Instruction::I64Extend8S, + I64Extend16S => Instruction::I64Extend16S, + I64Extend32S => Instruction::I64Extend32S, I64ExtendI32S => Instruction::I64ExtendI32S, I64ExtendI32U => Instruction::I64ExtendI32U, + I32Extend8S => Instruction::I32Extend8S, + I32Extend16S => Instruction::I32Extend16S, I64TruncF32S => Instruction::I64TruncF32S, I64TruncF32U => Instruction::I64TruncF32U, I64TruncF64S => Instruction::I64TruncF64S, @@ -450,6 +571,14 @@ pub fn process_operators<'a>( I64ReinterpretF64 => Instruction::I64ReinterpretF64, F32ReinterpretI32 => Instruction::F32ReinterpretI32, F64ReinterpretI64 => Instruction::F64ReinterpretI64, + I32TruncSatF32S => Instruction::I32TruncSatF32S, + I32TruncSatF32U => Instruction::I32TruncSatF32U, + I32TruncSatF64S => Instruction::I32TruncSatF64S, + I32TruncSatF64U => Instruction::I32TruncSatF64U, + I64TruncSatF32S => Instruction::I64TruncSatF32S, + I64TruncSatF32U => Instruction::I64TruncSatF32U, + I64TruncSatF64S => Instruction::I64TruncSatF64S, + I64TruncSatF64U => Instruction::I64TruncSatF64U, op => { return Err(crate::ParseError::UnsupportedOperator(format!( "Unsupported instruction: {:?}", diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 36deb7f..22e2661 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -126,6 +126,9 @@ impl TryFrom for TinyWasmModule { globals: globals.into_boxed_slice(), table_types: table_types.into_boxed_slice(), memory_types: reader.memory_types.into_boxed_slice(), + imports: reader.imports.into_boxed_slice(), + data: reader.data.into_boxed_slice(), + elements: reader.elements.into_boxed_slice(), }) } } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index ea6aaad..660a702 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,11 +1,10 @@ use crate::log::debug; +use crate::{conversion, ParseError, Result}; use alloc::{boxed::Box, format, vec::Vec}; use core::fmt::Debug; -use tinywasm_types::{Export, FuncType, Global, Instruction, MemoryType, TableType, ValType}; +use tinywasm_types::{Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, ValType}; use wasmparser::{Payload, Validator}; -use crate::{conversion, ParseError, Result}; - #[derive(Debug, Clone, PartialEq)] pub struct CodeSection { pub locals: Box<[ValType]>, @@ -24,10 +23,11 @@ pub struct ModuleReader { pub globals: Vec, pub table_types: Vec, pub memory_types: Vec, + pub imports: Vec, + pub data: Vec, + pub elements: Vec, // pub element_section: Option>, - // pub data_section: Option>, - // pub import_section: Option>, pub end_reached: bool, } @@ -42,9 +42,9 @@ impl Debug for ModuleReader { .field("globals", &self.globals) .field("table_types", &self.table_types) .field("memory_types", &self.memory_types) + .field("import_section", &self.imports) // .field("element_section", &self.element_section) // .field("data_section", &self.data_section) - // .field("import_section", &self.import_section) .finish() } } @@ -67,11 +67,19 @@ impl ModuleReader { } } StartSection { func, range } => { + if self.start_func.is_some() { + return Err(ParseError::DuplicateSection("Start section".into())); + } + debug!("Found start section"); validator.start_section(func, &range)?; self.start_func = Some(func); } TypeSection(reader) => { + if !self.func_types.is_empty() { + return Err(ParseError::DuplicateSection("Type section".into())); + } + debug!("Found type section"); validator.type_section(&reader)?; self.func_types = reader @@ -80,43 +88,60 @@ impl ModuleReader { .collect::>>()?; } FunctionSection(reader) => { + if !self.func_addrs.is_empty() { + return Err(ParseError::DuplicateSection("Function section".into())); + } + debug!("Found function section"); validator.function_section(&reader)?; self.func_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; } GlobalSection(reader) => { + if !self.globals.is_empty() { + return Err(ParseError::DuplicateSection("Global section".into())); + } + debug!("Found global section"); validator.global_section(&reader)?; self.globals = conversion::convert_module_globals(reader)?; } TableSection(reader) => { + if !self.table_types.is_empty() { + return Err(ParseError::DuplicateSection("Table section".into())); + } + debug!("Found table section"); validator.table_section(&reader)?; self.table_types = conversion::convert_module_tables(reader)?; } MemorySection(reader) => { + if !self.memory_types.is_empty() { + return Err(ParseError::DuplicateSection("Memory section".into())); + } + debug!("Found memory section"); validator.memory_section(&reader)?; self.memory_types = conversion::convert_module_memories(reader)?; } - ElementSection(_reader) => { - return Err(ParseError::UnsupportedSection("Element section".into())); - // debug!("Found element section"); - // validator.element_section(&reader)?; - // self.element_section = Some(reader); + ElementSection(reader) => { + debug!("Found element section"); + validator.element_section(&reader)?; + self.elements = conversion::convert_module_elements(reader)?; } - DataSection(_reader) => { - return Err(ParseError::UnsupportedSection("Data section".into())); - // debug!("Found data section"); - // validator.data_section(&reader)?; - // self.data_section = Some(reader); + DataSection(reader) => { + if !self.data.is_empty() { + return Err(ParseError::DuplicateSection("Data section".into())); + } + + debug!("Found data section"); + validator.data_section(&reader)?; + self.data = conversion::convert_module_data_sections(reader)?; } CodeSectionStart { count, range, .. } => { debug!("Found code section ({} functions)", count); if !self.code.is_empty() { return Err(ParseError::DuplicateSection("Code section".into())); } - validator.code_section_start(count, &range)?; } CodeSectionEntry(function) => { @@ -127,14 +152,20 @@ impl ModuleReader { self.code .push(conversion::convert_module_code(function, func_validator)?); } - ImportSection(_reader) => { - return Err(ParseError::UnsupportedSection("Import section".into())); + ImportSection(reader) => { + if !self.imports.is_empty() { + return Err(ParseError::DuplicateSection("Import section".into())); + } - // debug!("Found import section"); - // validator.import_section(&reader)?; - // self.import_section = Some(reader); + debug!("Found import section"); + validator.import_section(&reader)?; + self.imports = conversion::convert_module_imports(reader)?; } ExportSection(reader) => { + if !self.exports.is_empty() { + return Err(ParseError::DuplicateSection("Export section".into())); + } + debug!("Found export section"); validator.export_section(&reader)?; self.exports = reader diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 50e6480..3f97364 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -14,20 +14,33 @@ path="src/lib.rs" [dependencies] log={version="0.4", optional=true} -tinywasm-parser={version="0.0.5-alpha.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.0.5-alpha.0", path="../types", default-features=false} +tinywasm-parser={version="0.1.0", path="../parser", default-features=false, optional=true} +tinywasm-types={version="0.1.0", path="../types", default-features=false} [dev-dependencies] wasm-testsuite={path="../wasm-testsuite"} wast={version="69.0"} -owo-colors={version="3.5"} +owo-colors={version="4.0"} eyre={version="0.6"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} plotters={version="0.3"} +pretty_env_logger="0.5" [features] default=["std", "parser", "logging"] logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] + +[[test]] +name="generate-charts" +harness=false + +[[test]] +name="test-mvp" +harness=false + +[[test]] +name="test-wast" +harness=false diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 8776365..208dab1 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -17,6 +17,12 @@ pub enum Trap { /// A division by zero occurred DivisionByZero, + + /// Invalid Integer Conversion + InvalidConversionToInt, + + /// Integer Overflow + IntegerOverflow, } #[derive(Debug)] diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 5e87e74..2fa9cf3 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -65,9 +65,16 @@ impl ModuleInstance { } let export = self.0.exports.get(name, ExternalKind::Func)?; + log::debug!("get_func: export: {:?}", export); + + log::debug!("{:?}", self.0.func_addrs); let func_addr = self.0.func_addrs[export.index as usize]; + log::debug!("get_func: func index: {}", export.index); let func = store.get_func(func_addr as usize)?; + log::debug!("get_func: func_addr: {}, func: {:?}", func_addr, func); let ty = self.0.types[func.ty_addr() as usize].clone(); + log::debug!("get_func: ty: {:?}", ty); + log::debug!("types: {:?}", self.0.types); Ok(FuncHandle { addr: export.index, diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 9580f9a..4f3575d 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -59,8 +59,8 @@ impl Module { // imports: Option<()>, ) -> Result { let idx = store.next_module_instance_idx(); - let func_addrs = store.add_funcs(self.data.funcs.into(), idx); + let instance = ModuleInstance::new( self.data.func_types, self.data.start_func, diff --git a/crates/tinywasm/src/runtime/executor/macros.rs b/crates/tinywasm/src/runtime/executor/macros.rs index 5a9a570..61678f2 100644 --- a/crates/tinywasm/src/runtime/executor/macros.rs +++ b/crates/tinywasm/src/runtime/executor/macros.rs @@ -10,6 +10,77 @@ macro_rules! conv_1 { }}; } +macro_rules! float_min_max { + (f32, i32) => { + (-2147483904.0_f32, 2147483648.0_f32) + }; + (f64, i32) => { + (-2147483649.0_f64, 2147483648.0_f64) + }; + (f32, u32) => { + (-1.0_f32, 4294967296.0_f32) + }; + (f64, u32) => { + (-1.0_f64, 4294967296.0_f64) + }; + (f32, i64) => { + (-9223373136366403584.0_f32, 9223372036854775808.0_f32) + }; + (f64, i64) => { + (-9223372036854777856.0_f64, 9223372036854775808.0_f64) + }; + (f32, u64) => { + (-1.0_f32, 18446744073709551616.0_f32) + }; + (f64, u64) => { + (-1.0_f64, 18446744073709551616.0_f64) + }; + // other conversions are not allowed + ($from:ty, $to:ty) => { + compile_error!("invalid float conversion"); + }; +} + +// Convert a float to an int, checking for overflow +macro_rules! checked_float_conv_1 { + ($from:tt, $to:tt, $stack:ident) => {{ + let (min, max) = float_min_max!($from, $to); + let a: $from = $stack.values.pop()?.into(); + + if a.is_nan() { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + + if a <= min || a >= max { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + + $stack.values.push((a as $to).into()); + }}; +} + +// Convert a float to an int, checking for overflow +macro_rules! checked_float_conv_2 { + ($from:tt, $uty:tt, $to:tt, $stack:ident) => {{ + let (min, max) = float_min_max!($from, $uty); + let a: $from = $stack.values.pop()?.into(); + + if a.is_nan() { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + + log::info!("a: {}", a); + log::info!("min: {}", min); + log::info!("max: {}", max); + + if a <= min || a >= max { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + + $stack.values.push((a as $uty as $to).into()); + }}; +} + /// Convert the unsigned value on the top of the stack to a specific type macro_rules! conv_2 { ($ty:ty, $uty:ty, $to:ty, $stack:ident) => {{ @@ -51,7 +122,7 @@ macro_rules! comp_zero { } /// Apply an arithmetic operation to two values on the stack -macro_rules! arithmetic { +macro_rules! arithmetic_op { ($op:tt, $ty:ty, $stack:ident) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; let a: $ty = a.into(); @@ -60,19 +131,59 @@ macro_rules! arithmetic { }}; } +macro_rules! arithmetic_method { + ($op:ident, $ty:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + let result = a.$op(b); + $stack.values.push(result.into()); + }}; +} + +macro_rules! arithmetic_method_self { + ($op:ident, $ty:ty, $stack:ident) => {{ + let a: $ty = $stack.values.pop()?.into(); + let result = a.$op(); + $stack.values.push((result as $ty).into()); + }}; +} + +macro_rules! arithmetic_method_cast { + ($op:ident, $ty:ty, $ty2:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + + // Cast to unsigned type before operation + let a_unsigned: $ty2 = a as $ty2; + let b_unsigned: $ty2 = b as $ty2; + + let result = a_unsigned.$op(b_unsigned); + $stack.values.push((result as $ty).into()); + }}; +} + /// Apply an arithmetic operation to two values on the stack -macro_rules! checked_arithmetic { +macro_rules! checked_arithmetic_method { ($op:ident, $ty:ty, $stack:ident, $trap:expr) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; let a: $ty = a.into(); let b: $ty = b.into(); let result = a.$op(b).ok_or_else(|| Error::Trap($trap))?; + debug!( + "checked_arithmetic_method: {}, a: {}, b: {}, res: {}", + stringify!($op), + a, + b, + result + ); $stack.values.push(result.into()); }}; } /// Apply an arithmetic operation to two values on the stack (cast to ty2 before operation) -macro_rules! checked_arithmetic_cast { +macro_rules! checked_arithmetic_method_cast { ($op:ident, $ty:ty, $ty2:ty, $stack:ident, $trap:expr) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; let a: $ty = a.into(); @@ -87,11 +198,17 @@ macro_rules! checked_arithmetic_cast { }}; } -pub(super) use arithmetic; -pub(super) use checked_arithmetic; -pub(super) use checked_arithmetic_cast; +pub(super) use arithmetic_method; +pub(super) use arithmetic_method_cast; +pub(super) use arithmetic_method_self; +pub(super) use arithmetic_op; +pub(super) use checked_arithmetic_method; +pub(super) use checked_arithmetic_method_cast; +pub(super) use checked_float_conv_1; +pub(super) use checked_float_conv_2; pub(super) use comp; pub(super) use comp_cast; pub(super) use comp_zero; pub(super) use conv_1; pub(super) use conv_2; +pub(super) use float_min_max; diff --git a/crates/tinywasm/src/runtime/executor/mod.rs b/crates/tinywasm/src/runtime/executor/mod.rs index 93ebbd2..6486b38 100644 --- a/crates/tinywasm/src/runtime/executor/mod.rs +++ b/crates/tinywasm/src/runtime/executor/mod.rs @@ -1,3 +1,5 @@ +use core::ops::{BitAnd, BitOr, BitXor, Neg}; + use super::{DefaultRuntime, Stack}; use crate::{ get_label_args, @@ -10,7 +12,9 @@ use log::info; use tinywasm_types::Instruction; mod macros; +mod traits; use macros::*; +use traits::*; impl DefaultRuntime { pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack, module: ModuleInstance) -> Result<()> { @@ -85,7 +89,11 @@ fn exec_one( Nop => { /* do nothing */ } Unreachable => return Ok(ExecResult::Trap(crate::Trap::Unreachable)), // we don't need to include the call frame here because it's already on the stack Drop => stack.values.pop().map(|_| ())?, - Select => { + Select(t) => { + if t.is_some() { + unimplemented!("select with type"); + } + let cond: i32 = stack.values.pop()?.into(); let val2 = stack.values.pop()?; @@ -117,8 +125,6 @@ fn exec_one( return Ok(ExecResult::Call); } - Return => todo!("called function returned"), - If(args, else_offset, end_offset) => { let end_instr_ptr = cf.instr_ptr + *end_offset; @@ -166,7 +172,6 @@ fn exec_one( } Block(args, end_offset) => { - // let params = stack.values.pop_block_params(*args, &module)?; cf.enter_label( LabelFrame { instr_ptr: cf.instr_ptr, @@ -202,6 +207,14 @@ fn exec_one( }; } + Return => match stack.call_stack.is_empty() { + true => return Ok(ExecResult::Return), + false => { + *cf = stack.call_stack.pop()?; + return Ok(ExecResult::Call); + } + }, + EndFunc => { if cf.labels.len() > 0 { panic!("endfunc: block frames not empty, this should have been validated by the parser"); @@ -283,29 +296,58 @@ fn exec_one( F32Gt => comp!(>, f32, stack), F64Gt => comp!(>, f64, stack), - I64Add => arithmetic!(+, i64, stack), - I32Add => arithmetic!(+, i32, stack), - F32Add => arithmetic!(+, f32, stack), - F64Add => arithmetic!(+, f64, stack), + I64Add => arithmetic_method!(wrapping_add, i64, stack), + I32Add => arithmetic_method!(wrapping_add, i32, stack), + F32Add => arithmetic_op!(+, f32, stack), + F64Add => arithmetic_op!(+, f64, stack), - I32Sub => arithmetic!(-, i32, stack), - I64Sub => arithmetic!(-, i64, stack), - F32Sub => arithmetic!(-, f32, stack), - F64Sub => arithmetic!(-, f64, stack), + I32Sub => arithmetic_method!(wrapping_sub, i32, stack), + I64Sub => arithmetic_method!(wrapping_sub, i64, stack), + F32Sub => arithmetic_op!(-, f32, stack), + F64Sub => arithmetic_op!(-, f64, stack), - F32Div => arithmetic!(/, f32, stack), - F64Div => arithmetic!(/, f64, stack), + F32Div => arithmetic_op!(/, f32, stack), + F64Div => arithmetic_op!(/, f64, stack), - I32Mul => arithmetic!(*, i32, stack), - I64Mul => arithmetic!(*, i64, stack), - F32Mul => arithmetic!(*, f32, stack), - F64Mul => arithmetic!(*, f64, stack), + I32Mul => arithmetic_method!(wrapping_mul, i32, stack), + I64Mul => arithmetic_method!(wrapping_mul, i64, stack), + F32Mul => arithmetic_op!(*, f32, stack), + F64Mul => arithmetic_op!(*, f64, stack), // these can trap - I32DivS => checked_arithmetic!(checked_div, i32, stack, crate::Trap::DivisionByZero), - I64DivS => checked_arithmetic!(checked_div, i64, stack, crate::Trap::DivisionByZero), - I32DivU => checked_arithmetic_cast!(checked_div, i32, u32, stack, crate::Trap::DivisionByZero), - I64DivU => checked_arithmetic_cast!(checked_div, i64, u64, stack, crate::Trap::DivisionByZero), + I32DivS => checked_arithmetic_method!(checked_div, i32, stack, crate::Trap::DivisionByZero), + I64DivS => checked_arithmetic_method!(checked_div, i64, stack, crate::Trap::DivisionByZero), + I32DivU => checked_arithmetic_method_cast!(checked_div, i32, u32, stack, crate::Trap::DivisionByZero), + I64DivU => checked_arithmetic_method_cast!(checked_div, i64, u64, stack, crate::Trap::DivisionByZero), + + I32RemS => checked_arithmetic_method!(checked_wrapping_rem, i32, stack, crate::Trap::DivisionByZero), + I64RemS => checked_arithmetic_method!(checked_wrapping_rem, i64, stack, crate::Trap::DivisionByZero), + I32RemU => checked_arithmetic_method_cast!(checked_wrapping_rem, i32, u32, stack, crate::Trap::DivisionByZero), + I64RemU => checked_arithmetic_method_cast!(checked_wrapping_rem, i64, u64, stack, crate::Trap::DivisionByZero), + + I32And => arithmetic_method!(bitand, i32, stack), + I64And => arithmetic_method!(bitand, i64, stack), + I32Or => arithmetic_method!(bitor, i32, stack), + I64Or => arithmetic_method!(bitor, i64, stack), + I32Xor => arithmetic_method!(bitxor, i32, stack), + I64Xor => arithmetic_method!(bitxor, i64, stack), + I32Shl => arithmetic_method!(wasm_shl, i32, stack), + I64Shl => arithmetic_method!(wasm_shl, i64, stack), + I32ShrS => arithmetic_method!(wasm_shr, i32, stack), + I64ShrS => arithmetic_method!(wasm_shr, i64, stack), + I32ShrU => arithmetic_method_cast!(wasm_shr, i32, u32, stack), + I64ShrU => arithmetic_method_cast!(wasm_shr, i64, u64, stack), + I32Rotl => arithmetic_method!(wasm_rotl, i32, stack), + I64Rotl => arithmetic_method!(wasm_rotl, i64, stack), + I32Rotr => arithmetic_method!(wasm_rotr, i32, stack), + I64Rotr => arithmetic_method!(wasm_rotr, i64, stack), + + I32Clz => arithmetic_method_self!(leading_zeros, i32, stack), + I64Clz => arithmetic_method_self!(leading_zeros, i64, stack), + I32Ctz => arithmetic_method_self!(trailing_zeros, i32, stack), + I64Ctz => arithmetic_method_self!(trailing_zeros, i64, stack), + I32Popcnt => arithmetic_method_self!(count_ones, i32, stack), + I64Popcnt => arithmetic_method_self!(count_ones, i64, stack), F32ConvertI32S => conv_1!(i32, f32, stack), F32ConvertI64S => conv_1!(i64, f32, stack), @@ -315,17 +357,56 @@ fn exec_one( F32ConvertI64U => conv_2!(i64, u64, f32, stack), F64ConvertI32U => conv_2!(i32, u32, f64, stack), F64ConvertI64U => conv_2!(i64, u64, f64, stack), + I32Extend8S => conv_2!(i32, i8, i32, stack), + I32Extend16S => conv_2!(i32, i16, i32, stack), + I64Extend8S => conv_2!(i64, i8, i64, stack), + I64Extend16S => conv_2!(i64, i16, i64, stack), + I64Extend32S => conv_2!(i64, i32, i64, stack), I64ExtendI32U => conv_2!(i32, u32, i64, stack), I64ExtendI32S => conv_1!(i32, i64, stack), I32WrapI64 => conv_1!(i64, i32, stack), + F32Abs => arithmetic_method_self!(abs, f32, stack), + F64Abs => arithmetic_method_self!(abs, f64, stack), + F32Neg => arithmetic_method_self!(neg, f32, stack), + F64Neg => arithmetic_method_self!(neg, f64, stack), + F32Ceil => arithmetic_method_self!(ceil, f32, stack), + F64Ceil => arithmetic_method_self!(ceil, f64, stack), + F32Floor => arithmetic_method_self!(floor, f32, stack), + F64Floor => arithmetic_method_self!(floor, f64, stack), + F32Trunc => arithmetic_method_self!(trunc, f32, stack), + F64Trunc => arithmetic_method_self!(trunc, f64, stack), + F32Nearest => arithmetic_method_self!(wasm_nearest, f32, stack), + F64Nearest => arithmetic_method_self!(wasm_nearest, f64, stack), + F32Sqrt => arithmetic_method_self!(sqrt, f32, stack), + F64Sqrt => arithmetic_method_self!(sqrt, f64, stack), + F32Min => arithmetic_method!(wasm_min, f32, stack), + F64Min => arithmetic_method!(wasm_min, f64, stack), + F32Max => arithmetic_method!(wasm_max, f32, stack), + F64Max => arithmetic_method!(wasm_max, f64, stack), + F32Copysign => arithmetic_method!(copysign, f32, stack), + F64Copysign => arithmetic_method!(copysign, f64, stack), + // no-op instructions since types are erased at runtime I32ReinterpretF32 => {} I64ReinterpretF64 => {} F32ReinterpretI32 => {} F64ReinterpretI64 => {} - i => todo!("{:?}", i), + // unsigned versions of these are a bit broken atm + I32TruncF32S => checked_float_conv_1!(f32, i32, stack), + I32TruncF64S => checked_float_conv_1!(f64, i32, stack), + I32TruncF32U => checked_float_conv_2!(f32, u32, i32, stack), + I32TruncF64U => checked_float_conv_2!(f64, u32, i32, stack), + I64TruncF32S => checked_float_conv_1!(f32, i64, stack), + I64TruncF64S => checked_float_conv_1!(f64, i64, stack), + I64TruncF32U => checked_float_conv_2!(f32, u64, i64, stack), + I64TruncF64U => checked_float_conv_2!(f64, u64, i64, stack), + + i => { + log::error!("unimplemented instruction: {:?}", i); + panic!("Unimplemented instruction: {:?}", i) + } }; Ok(ExecResult::Ok) diff --git a/crates/tinywasm/src/runtime/executor/traits.rs b/crates/tinywasm/src/runtime/executor/traits.rs new file mode 100644 index 0000000..9bddba1 --- /dev/null +++ b/crates/tinywasm/src/runtime/executor/traits.rs @@ -0,0 +1,116 @@ +pub(crate) trait CheckedWrappingRem +where + Self: Sized, +{ + fn checked_wrapping_rem(self, rhs: Self) -> Option; +} + +pub(crate) trait WasmFloatOps { + fn wasm_min(self, other: Self) -> Self; + fn wasm_max(self, other: Self) -> Self; + fn wasm_nearest(self) -> Self; +} + +macro_rules! impl_wasm_float_ops { + ($($t:ty)*) => ($( + impl WasmFloatOps for $t { + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest + fn wasm_nearest(self) -> Self { + log::info!("wasm_nearest: {}", self); + match self { + x if x.is_nan() => x, + x if x.is_infinite() || x == 0.0 => x, + x if (0.0..=0.5).contains(&x) => 0.0, + x if (-0.5..0.0).contains(&x) => -0.0, + x => x.round(), + } + } + + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmin + // Based on f32::minimum (which is not yet stable) + #[inline] + fn wasm_min(self, other: Self) -> Self { + if self < other { + self + } else if other < self { + other + } else if self == other { + if self.is_sign_negative() && other.is_sign_positive() { self } else { other } + } else { + // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + self + other + } + } + + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax + // Based on f32::maximum (which is not yet stable) + #[inline] + fn wasm_max(self, other: Self) -> Self { + if self > other { + self + } else if other > self { + other + } else if self == other { + if self.is_sign_negative() && other.is_sign_positive() { other } else { self } + } else { + // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + self + other + } + } + } + )*) +} + +impl_wasm_float_ops! { f32 f64 } + +pub(crate) trait WasmIntOps { + fn wasm_shl(self, rhs: Self) -> Self; + fn wasm_shr(self, rhs: Self) -> Self; + fn wasm_rotl(self, rhs: Self) -> Self; + fn wasm_rotr(self, rhs: Self) -> Self; +} + +macro_rules! impl_wrapping_self_sh { + ($($t:ty)*) => ($( + impl WasmIntOps for $t { + #[inline] + fn wasm_shl(self, rhs: Self) -> Self { + self.wrapping_shl(rhs as u32) + } + + #[inline] + fn wasm_shr(self, rhs: Self) -> Self { + self.wrapping_shr(rhs as u32) + } + + #[inline] + fn wasm_rotl(self, rhs: Self) -> Self { + self.rotate_left(rhs as u32) + } + + #[inline] + fn wasm_rotr(self, rhs: Self) -> Self { + self.rotate_right(rhs as u32) + } + } + )*) +} + +impl_wrapping_self_sh! { i32 i64 u32 u64 } + +macro_rules! impl_checked_wrapping_rem { + ($($t:ty)*) => ($( + impl CheckedWrappingRem for $t { + #[inline] + fn checked_wrapping_rem(self, rhs: Self) -> Option { + if rhs == 0 { + None + } else { + Some(self.wrapping_rem(rhs)) + } + } + } + )*) +} + +impl_checked_wrapping_rem! { i32 i64 u32 u64 } diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index 1a9d968..ff44d0d 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -126,12 +126,12 @@ impl Store { pub(crate) fn add_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Vec { let mut func_addrs = Vec::with_capacity(funcs.len()); - for func in funcs.into_iter() { + for (i, func) in funcs.into_iter().enumerate() { self.data.funcs.push(Rc::new(FunctionInstance { func, _module_instance: idx, })); - func_addrs.push(idx as FuncAddr); + func_addrs.push(i as FuncAddr); } func_addrs } diff --git a/crates/tinywasm/tests/generate-charts.rs b/crates/tinywasm/tests/generate-charts.rs new file mode 100644 index 0000000..f80b092 --- /dev/null +++ b/crates/tinywasm/tests/generate-charts.rs @@ -0,0 +1,16 @@ +mod charts; +use eyre::Result; + +fn main() -> Result<()> { + generate_charts() +} + +fn generate_charts() -> Result<()> { + // Create a line chart + charts::create_progress_chart( + std::path::Path::new("./tests/generated/mvp.csv"), + std::path::Path::new("./tests/generated/progress-mvp.svg"), + )?; + + Ok(()) +} diff --git a/crates/tinywasm/tests/generated/mvp.csv b/crates/tinywasm/tests/generated/mvp.csv new file mode 100644 index 0000000..c18acc5 --- /dev/null +++ b/crates/tinywasm/tests/generated/mvp.csv @@ -0,0 +1,4 @@ +0.0.3,9258,7567,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] +0.0.4,9258,10909,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] +0.0.5,11135,9093,[{"name":"address.wast","passed":1,"failed":259},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":78,"failed":13},{"name":"binary.wast","passed":107,"failed":5},{"name":"block.wast","passed":170,"failed":53},{"name":"br.wast","passed":20,"failed":77},{"name":"br_if.wast","passed":29,"failed":89},{"name":"br_table.wast","passed":24,"failed":150},{"name":"call.wast","passed":18,"failed":73},{"name":"call_indirect.wast","passed":34,"failed":136},{"name":"comments.wast","passed":5,"failed":3},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":25,"failed":594},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":22,"failed":39},{"name":"elem.wast","passed":27,"failed":72},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":90,"failed":6},{"name":"f32.wast","passed":1018,"failed":1496},{"name":"f32_bitwise.wast","passed":4,"failed":360},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":1018,"failed":1496},{"name":"f64_bitwise.wast","passed":4,"failed":360},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":1,"failed":7},{"name":"float_exprs.wast","passed":275,"failed":625},{"name":"float_literals.wast","passed":112,"failed":51},{"name":"float_memory.wast","passed":0,"failed":90},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":81,"failed":91},{"name":"func_ptrs.wast","passed":7,"failed":29},{"name":"global.wast","passed":50,"failed":60},{"name":"i32.wast","passed":85,"failed":375},{"name":"i64.wast","passed":31,"failed":385},{"name":"if.wast","passed":116,"failed":125},{"name":"imports.wast","passed":23,"failed":160},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":25,"failed":26},{"name":"labels.wast","passed":13,"failed":16},{"name":"left-to-right.wast","passed":0,"failed":96},{"name":"linking.wast","passed":5,"failed":127},{"name":"load.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":18,"failed":18},{"name":"local_set.wast","passed":38,"failed":15},{"name":"local_tee.wast","passed":41,"failed":56},{"name":"loop.wast","passed":42,"failed":78},{"name":"memory.wast","passed":30,"failed":49},{"name":"memory_grow.wast","passed":11,"failed":85},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":1,"failed":181},{"name":"names.wast","passed":484,"failed":2},{"name":"nop.wast","passed":4,"failed":84},{"name":"return.wast","passed":20,"failed":64},{"name":"select.wast","passed":28,"failed":120},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":4,"failed":16},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":39,"failed":19},{"name":"traps.wast","passed":4,"failed":32},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":0,"failed":64},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":9,"failed":41},{"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.0.6-alpha.0,17630,2598,[{"name":"address.wast","passed":5,"failed":255},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":110,"failed":2},{"name":"block.wast","passed":193,"failed":30},{"name":"br.wast","passed":84,"failed":13},{"name":"br_if.wast","passed":90,"failed":28},{"name":"br_table.wast","passed":25,"failed":149},{"name":"call.wast","passed":29,"failed":62},{"name":"call_indirect.wast","passed":36,"failed":134},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":371,"failed":248},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":50,"failed":49},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":92,"failed":4},{"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":2,"failed":6},{"name":"float_exprs.wast","passed":761,"failed":139},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":6,"failed":84},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":124,"failed":48},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":51,"failed":59},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":120,"failed":121},{"name":"imports.wast","passed":74,"failed":109},{"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":14,"failed":15},{"name":"left-to-right.wast","passed":1,"failed":95},{"name":"linking.wast","passed":21,"failed":111},{"name":"load.wast","passed":60,"failed":37},{"name":"local_get.wast","passed":32,"failed":4},{"name":"local_set.wast","passed":50,"failed":3},{"name":"local_tee.wast","passed":68,"failed":29},{"name":"loop.wast","passed":93,"failed":27},{"name":"memory.wast","passed":34,"failed":45},{"name":"memory_grow.wast","passed":12,"failed":84},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":2,"failed":180},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":46,"failed":42},{"name":"return.wast","passed":73,"failed":11},{"name":"select.wast","passed":86,"failed":62},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":9,"failed":11},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":22,"failed":14},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":50,"failed":14},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":35,"failed":15},{"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/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg similarity index 78% rename from crates/tinywasm/tests/progress-mvp.svg rename to crates/tinywasm/tests/generated/progress-mvp.svg index c8a9bc1..331b0f8 100644 --- a/crates/tinywasm/tests/progress-mvp.svg +++ b/crates/tinywasm/tests/generated/progress-mvp.svg @@ -36,19 +36,24 @@ TinyWasm Version - + v0.0.3 (9258) - - + + v0.0.4 (9258) - - -v0.0.5-alpha.0 (11046) + + +v0.0.5 (11135) - - - - + + +v0.0.6-alpha.0 (17630) + + + + + + diff --git a/crates/tinywasm/tests/mvp.csv b/crates/tinywasm/tests/mvp.csv deleted file mode 100644 index 79028b6..0000000 --- a/crates/tinywasm/tests/mvp.csv +++ /dev/null @@ -1,3 +0,0 @@ -0.0.3,9258,7567,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] -0.0.4,9258,10909,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] -0.0.5-alpha.0,11135,9093,[{"name":"address.wast","passed":1,"failed":259},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":78,"failed":13},{"name":"binary.wast","passed":107,"failed":5},{"name":"block.wast","passed":170,"failed":53},{"name":"br.wast","passed":20,"failed":77},{"name":"br_if.wast","passed":29,"failed":89},{"name":"br_table.wast","passed":24,"failed":150},{"name":"call.wast","passed":18,"failed":73},{"name":"call_indirect.wast","passed":34,"failed":136},{"name":"comments.wast","passed":5,"failed":3},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":25,"failed":594},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":22,"failed":39},{"name":"elem.wast","passed":27,"failed":72},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":90,"failed":6},{"name":"f32.wast","passed":1018,"failed":1496},{"name":"f32_bitwise.wast","passed":4,"failed":360},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":1018,"failed":1496},{"name":"f64_bitwise.wast","passed":4,"failed":360},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":1,"failed":7},{"name":"float_exprs.wast","passed":275,"failed":625},{"name":"float_literals.wast","passed":112,"failed":51},{"name":"float_memory.wast","passed":0,"failed":90},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":81,"failed":91},{"name":"func_ptrs.wast","passed":7,"failed":29},{"name":"global.wast","passed":50,"failed":60},{"name":"i32.wast","passed":85,"failed":375},{"name":"i64.wast","passed":31,"failed":385},{"name":"if.wast","passed":116,"failed":125},{"name":"imports.wast","passed":23,"failed":160},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":25,"failed":26},{"name":"labels.wast","passed":13,"failed":16},{"name":"left-to-right.wast","passed":0,"failed":96},{"name":"linking.wast","passed":5,"failed":127},{"name":"load.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":18,"failed":18},{"name":"local_set.wast","passed":38,"failed":15},{"name":"local_tee.wast","passed":41,"failed":56},{"name":"loop.wast","passed":42,"failed":78},{"name":"memory.wast","passed":30,"failed":49},{"name":"memory_grow.wast","passed":11,"failed":85},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":1,"failed":181},{"name":"names.wast","passed":484,"failed":2},{"name":"nop.wast","passed":4,"failed":84},{"name":"return.wast","passed":20,"failed":64},{"name":"select.wast","passed":28,"failed":120},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":4,"failed":16},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":39,"failed":19},{"name":"traps.wast","passed":4,"failed":32},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":0,"failed":64},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":9,"failed":41},{"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/mvp.rs b/crates/tinywasm/tests/mvp.rs deleted file mode 100644 index e02436f..0000000 --- a/crates/tinywasm/tests/mvp.rs +++ /dev/null @@ -1,40 +0,0 @@ -mod charts; -mod testsuite; - -use eyre::{eyre, Result}; -use testsuite::TestSuite; - -#[test] -#[ignore] -fn generate_charts() -> Result<()> { - // Create a line chart - charts::create_progress_chart( - std::path::Path::new("./tests/mvp.csv"), - std::path::Path::new("./tests/progress-mvp.svg"), - )?; - - // // Create a bar chart - // charts::create_bar_chart( - // std::path::Path::new("./tests/mvp.csv"), - // std::path::Path::new("./tests/mvp_bar_chart.png"), - // )?; - - Ok(()) -} - -#[test] -#[ignore] -fn test_mvp() -> Result<()> { - let mut test_suite = TestSuite::new(); - - test_suite.run(wasm_testsuite::MVP_TESTS)?; - test_suite.save_csv("./tests/mvp.csv", env!("CARGO_PKG_VERSION"))?; - - if test_suite.failed() { - eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); - Err(eyre!("failed one or more tests")) - } else { - println!("\n\npassed all tests:\n{:#?}", test_suite); - Ok(()) - } -} diff --git a/crates/tinywasm/tests/test-mvp.rs b/crates/tinywasm/tests/test-mvp.rs new file mode 100644 index 0000000..fdd043d --- /dev/null +++ b/crates/tinywasm/tests/test-mvp.rs @@ -0,0 +1,22 @@ +mod testsuite; +use eyre::{eyre, Result}; +use testsuite::TestSuite; + +fn main() -> Result<()> { + test_mvp() +} + +fn test_mvp() -> Result<()> { + let mut test_suite = TestSuite::new(); + + test_suite.run_spec_group(wasm_testsuite::MVP_TESTS)?; + test_suite.save_csv("./tests/generated/mvp.csv", env!("CARGO_PKG_VERSION"))?; + + if test_suite.failed() { + eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); + Err(eyre!("failed one or more tests")) + } else { + println!("\n\npassed all tests:\n{:#?}", test_suite); + Ok(()) + } +} diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs new file mode 100644 index 0000000..68372a0 --- /dev/null +++ b/crates/tinywasm/tests/test-wast.rs @@ -0,0 +1,50 @@ +use std::path::PathBuf; + +use eyre::{bail, Result}; +use testsuite::TestSuite; + +mod testsuite; + +fn main() -> Result<()> { + let args = std::env::args().collect::>(); + if args.len() < 2 { + bail!("usage: cargo test-wast ") + } + + // cwd for relative paths, absolute paths are kept as-is + let cwd = std::env::current_dir()?; + + // if current dir is crates/tinywasm, then we want to go up 2 levels + let mut wast_file = if cwd.ends_with("crates/tinywasm") { + PathBuf::from("../../") + } else { + PathBuf::from("./") + }; + + wast_file.push(&args[1]); + let wast_file = cwd.join(wast_file); + + test_wast(wast_file.to_str().expect("wast_file is not a valid path"))?; + Ok(()) +} + +fn test_wast(wast_file: &str) -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Debug); + + let args = std::env::args().collect::>(); + println!("args: {:?}", args); + + let mut test_suite = TestSuite::new(); + println!("running wast file: {}", wast_file); + + test_suite.run_paths(&[wast_file])?; + + if test_suite.failed() { + eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); + test_suite.print_errors(); + bail!("failed one or more tests") + } else { + println!("\n\npassed all tests:\n{:#?}", test_suite); + Ok(()) + } +} diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index d5cde5f..fd3e39f 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] // rust analyzer doesn't recognize that code is used by tests without harness + use eyre::Result; use std::io::{BufRead, Seek, SeekFrom}; use std::{ @@ -21,6 +23,20 @@ pub struct TestGroupResult { pub struct TestSuite(BTreeMap); impl TestSuite { + pub fn set_log_level(level: log::LevelFilter) { + pretty_env_logger::formatted_builder().filter_level(level).init(); + } + + pub fn print_errors(&self) { + for (group_name, group) in &self.0 { + for (test_name, test) in &group.tests { + if let Err(e) = &test.result { + eprintln!("{}: {} failed: {:?}", group_name, test_name, e); + } + } + } + } + pub fn new() -> Self { Self(BTreeMap::new()) } diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index ec4715b..fc3d7bc 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -1,164 +1,238 @@ use crate::testsuite::util::*; +use std::borrow::Cow; use super::TestSuite; use eyre::{eyre, Result}; +use log::{debug, error, info}; use tinywasm_types::TinyWasmModule; use wast::{lexer::Lexer, parser::ParseBuffer, Wast}; impl TestSuite { - pub fn run(&mut self, tests: &[&str]) -> Result<()> { + pub fn run_paths(&mut self, tests: &[&str]) -> Result<()> { tests.iter().for_each(|group| { - let test_group = self.test_group(group); - - let wast = wasm_testsuite::get_test_wast(group).expect("failed to get test wast"); - let wast = std::str::from_utf8(&wast).expect("failed to convert wast to utf8"); - - let mut lexer = Lexer::new(wast); - // we need to allow confusing unicode characters since they are technically valid wasm - lexer.allow_confusing_unicode(true); - - let buf = ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); - let wast_data = wast::parser::parse::(&buf).expect("failed to parse wat"); - - let mut last_module: Option = None; - for (i, directive) in wast_data.directives.into_iter().enumerate() { - let span = directive.span(); - use wast::WastDirective::*; - let name = format!("{}-{}", group, i); - - match directive { - // TODO: needs to support more binary sections - Wat(mut module) => { - let result = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); - - match &result { - Err(_) => last_module = None, - Ok(m) => last_module = Some(m.clone()), - } + let group_wast = std::fs::read(group).expect("failed to read test wast"); + let group_wast = Cow::Owned(group_wast); + debug!("running group: {}", group); + self.run_group(group, group_wast).expect("failed to run group"); + }); - test_group.add_result(&format!("{}-parse", name), span, result.map(|_| ())); - } + Ok(()) + } - AssertMalformed { - span, - mut module, - message: _, - } => { - let Ok(module) = module.encode() else { - test_group.add_result(&format!("{}-malformed", name), span, Ok(())); - continue; - }; + pub fn run_spec_group(&mut self, tests: &[&str]) -> Result<()> { + tests.iter().for_each(|group| { + let group_wast = wasm_testsuite::get_test_wast(group).expect("failed to get test wast"); + self.run_group(group, group_wast).expect("failed to run group"); + }); - let res = catch_unwind_silent(|| parse_module_bytes(&module)) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); + Ok(()) + } - test_group.add_result( - &format!("{}-malformed", name), - span, - match res { - Ok(_) => Err(eyre!("expected module to be malformed")), - Err(_) => Ok(()), - }, - ); + pub fn run_group(&mut self, group_name: &str, group_wast: Cow<'_, [u8]>) -> Result<()> { + let test_group = self.test_group(group_name); + let wast = std::str::from_utf8(&group_wast).expect("failed to convert wast to utf8"); + + let mut lexer = Lexer::new(wast); + // we need to allow confusing unicode characters since they are technically valid wasm + lexer.allow_confusing_unicode(true); + + let buf = ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); + let wast_data = wast::parser::parse::(&buf).expect("failed to parse wat"); + + let mut last_module: Option = None; + debug!("running {} tests for group: {}", wast_data.directives.len(), group_name); + for (i, directive) in wast_data.directives.into_iter().enumerate() { + let span = directive.span(); + use wast::WastDirective::*; + let name = format!("{}-{}", group_name, i); + + debug!("directive: {:?}", directive); + + match directive { + Wat(mut module) => { + debug!("got wat module"); + + let result = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) + .map_err(|e| eyre!("failed to parse module: {:?}", e)) + .and_then(|res| res); + + match &result { + Err(_) => last_module = None, + Ok(m) => last_module = Some(m.clone()), } - AssertInvalid { - span, - mut module, - message: _, - } => { - let res = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); - - test_group.add_result( - &format!("{}-invalid", name), - span, - match res { - Ok(_) => Err(eyre!("expected module to be invalid")), - Err(_) => Ok(()), - }, - ); + if let Err(err) = &result { + debug!("failed to parse module: {:?}", err) } - AssertTrap { exec, message: _, span } => { - let res: Result, _> = catch_unwind_silent(|| { - let (module, name) = match exec { - wast::WastExecute::Wat(_wat) => unimplemented!("wat"), - wast::WastExecute::Get { module: _, global: _ } => unimplemented!("get"), - wast::WastExecute::Invoke(invoke) => (last_module.as_ref(), invoke.name), - }; - exec_fn(module, name, &[]).map(|_| ()) - }); + test_group.add_result(&format!("{}-parse", name), span, result.map(|_| ())); + } + + AssertMalformed { + span, + mut module, + message: _, + } => { + let Ok(module) = module.encode() else { + test_group.add_result(&format!("{}-malformed", name), span, Ok(())); + continue; + }; + + let res = catch_unwind_silent(|| parse_module_bytes(&module)) + .map_err(|e| eyre!("failed to parse module: {:?}", e)) + .and_then(|res| res); + + test_group.add_result( + &format!("{}-malformed", name), + span, + match res { + Ok(_) => Err(eyre!("expected module to be malformed")), + Err(_) => Ok(()), + }, + ); + } + AssertInvalid { + span, + mut module, + message: _, + } => { + let res = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) + .map_err(|e| eyre!("failed to parse module: {:?}", e)) + .and_then(|res| res); + + test_group.add_result( + &format!("{}-invalid", name), + span, match res { - Err(err) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("test panicked: {:?}", err)), - ), - Ok(Err(tinywasm::Error::Trap(_))) => { - test_group.add_result(&format!("{}-trap", name), span, Ok(())) + Ok(_) => Err(eyre!("expected module to be invalid")), + Err(_) => Ok(()), + }, + ); + } + + AssertTrap { exec, message: _, span } => { + let res: Result, _> = catch_unwind_silent(|| { + let (module, name, args) = match exec { + wast::WastExecute::Wat(_wat) => { + panic!("wat not supported"); } - Ok(Err(err)) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("expected trap, got error: {:?}", err)), - ), - Ok(Ok(())) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("expected trap, got ok")), - ), + wast::WastExecute::Get { module: _, global: _ } => { + panic!("wat not supported"); + } + wast::WastExecute::Invoke(invoke) => (last_module.as_ref(), invoke.name, invoke.args), + }; + let args = args + .into_iter() + .map(wastarg2tinywasmvalue) + .collect::>>() + .expect("failed to convert args"); + + exec_fn(module, name, &args).map(|_| ()) + }); + + match res { + Err(err) => test_group.add_result( + &format!("{}-trap", name), + span, + Err(eyre!("test panicked: {:?}, span: {:?}", err, span.linecol_in(&wast))), + ), + Ok(Err(tinywasm::Error::Trap(_))) => { + test_group.add_result(&format!("{}-trap", name), span, Ok(())) } + Ok(Err(err)) => test_group.add_result( + &format!("{}-trap", name), + span, + Err(eyre!( + "expected trap, got error: {:?}, span: {:?}", + err, + span.linecol_in(&wast) + )), + ), + Ok(Ok(())) => test_group.add_result( + &format!("{}-trap", name), + span, + Err(eyre!("expected trap, got ok, span: {:?}", span.linecol_in(&wast))), + ), } + } - AssertReturn { span, exec, results } => { - let res: Result, _> = catch_unwind_silent(|| { - let invoke = match exec { - wast::WastExecute::Wat(_) => unimplemented!("wat"), - wast::WastExecute::Get { module: _, global: _ } => { - return Err(eyre!("get not supported")) - } - wast::WastExecute::Invoke(invoke) => invoke, - }; - - let args = invoke - .args - .into_iter() - .map(wastarg2tinywasmvalue) - .collect::>>()?; - - let outcomes = exec_fn(last_module.as_ref(), invoke.name, &args)?; - let expected = results - .into_iter() - .map(wastret2tinywasmvalue) - .collect::>>()?; - - if outcomes.len() != expected.len() { - return Err(eyre!("expected {} results, got {}", expected.len(), outcomes.len())); + AssertReturn { span, exec, results } => { + info!("AssertReturn: {:?}", exec); + let res: Result, _> = catch_unwind_silent(|| { + let invoke = match exec { + wast::WastExecute::Wat(_) => { + error!("wat not supported"); + return Err(eyre!("wat not supported")); } - outcomes - .iter() - .zip(expected) - .enumerate() - .try_for_each(|(i, (outcome, exp))| { - (outcome == &exp) - .then_some(()) - .ok_or_else(|| eyre!("result {} did not match: {:?} != {:?}", i, outcome, exp)) + wast::WastExecute::Get { module: _, global: _ } => return Err(eyre!("get not supported")), + wast::WastExecute::Invoke(invoke) => invoke, + }; + + debug!("invoke: {:?}", invoke); + let args = invoke + .args + .into_iter() + .map(wastarg2tinywasmvalue) + .collect::>>() + .map_err(|e| { + error!("failed to convert args: {:?}", e); + e + })?; + + let outcomes = exec_fn(last_module.as_ref(), invoke.name, &args).map_err(|e| { + error!("failed to execute function: {:?}", e); + e + })?; + + debug!("outcomes: {:?}", outcomes); + + let expected = results + .into_iter() + .map(wastret2tinywasmvalue) + .collect::>>() + .map_err(|e| { + error!("failed to convert expected results: {:?}", e); + e + })?; + + debug!("expected: {:?}", expected); + + if outcomes.len() != expected.len() { + return Err(eyre!( + "span: {:?} expected {} results, got {}", + span, + expected.len(), + outcomes.len() + )); + } + + outcomes + .iter() + .zip(expected) + .enumerate() + .try_for_each(|(i, (outcome, exp))| { + (outcome.eq_loose(&exp)).then_some(()).ok_or_else(|| { + eyre!( + "span: {:?}: result {} did not match: {:?} != {:?}", + span.linecol_in(&wast), + i, + outcome, + exp + ) }) - }); + }) + }); - let res = res.map_err(|e| eyre!("test panicked: {:?}", e)).and_then(|r| r); - test_group.add_result(&format!("{}-return", name), span, res); - } - _ => test_group.add_result(&format!("{}-unknown", name), span, Err(eyre!("unsupported directive"))), + let res = res + .map_err(|e| eyre!("test panicked: {:?}", e.downcast_ref::<&str>())) + .and_then(|r| r); + + test_group.add_result(&format!("{}-return", name), span, res); } + _ => test_group.add_result(&format!("{}-unknown", name), span, Err(eyre!("unsupported directive"))), } - }); + } Ok(()) } diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index ab2ffa3..85b2a06 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -1,7 +1,7 @@ use std::panic; use eyre::{eyre, Result}; -use tinywasm_types::TinyWasmModule; +use tinywasm_types::{TinyWasmModule, WasmValue}; pub fn exec_fn( module: Option<&TinyWasmModule>, @@ -36,7 +36,6 @@ pub fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result WasmValue::F32(f32::from_bits(f.bits)), @@ -52,7 +51,6 @@ pub fn wastret2tinywasmvalue(arg: wast::WastRet) -> Result nanpattern2tinywasmvalue(f)?, @@ -69,16 +67,40 @@ enum Bits { } trait FloatToken { fn bits(&self) -> Bits; + fn canonical_nan() -> WasmValue; + fn arithmetic_nan() -> WasmValue; + fn value(&self) -> WasmValue { + match self.bits() { + Bits::U32(v) => WasmValue::F32(f32::from_bits(v)), + Bits::U64(v) => WasmValue::F64(f64::from_bits(v)), + } + } } impl FloatToken for wast::token::Float32 { fn bits(&self) -> Bits { Bits::U32(self.bits) } + + fn canonical_nan() -> WasmValue { + WasmValue::F32(f32::NAN) + } + + fn arithmetic_nan() -> WasmValue { + WasmValue::F32(f32::NAN) + } } impl FloatToken for wast::token::Float64 { fn bits(&self) -> Bits { Bits::U64(self.bits) } + + fn canonical_nan() -> WasmValue { + WasmValue::F64(f64::NAN) + } + + fn arithmetic_nan() -> WasmValue { + WasmValue::F64(f64::NAN) + } } fn nanpattern2tinywasmvalue(arg: wast::core::NanPattern) -> Result @@ -87,11 +109,8 @@ where { use wast::core::NanPattern::*; Ok(match arg { - CanonicalNan => tinywasm_types::WasmValue::F32(f32::NAN), - ArithmeticNan => tinywasm_types::WasmValue::F32(f32::NAN), - Value(v) => match v.bits() { - Bits::U32(v) => tinywasm_types::WasmValue::F32(f32::from_bits(v)), - Bits::U64(v) => tinywasm_types::WasmValue::F64(f64::from_bits(v)), - }, + CanonicalNan => T::canonical_nan(), + ArithmeticNan => T::arithmetic_nan(), + Value(v) => v.value(), }) } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 3f7b2ef..95fddc5 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -65,7 +65,7 @@ pub enum Instruction { // Parametric Instructions // See Drop, - Select, + Select(Option), // Variable Instructions // See @@ -213,6 +213,11 @@ pub enum Instruction { I32TruncF32U, I32TruncF64S, I32TruncF64U, + I32Extend8S, + I32Extend16S, + I64Extend8S, + I64Extend16S, + I64Extend32S, I64ExtendI32S, I64ExtendI32U, I64TruncF32S, @@ -233,4 +238,12 @@ pub enum Instruction { I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + I32TruncSatF32S, + I32TruncSatF32U, + I32TruncSatF64S, + I32TruncSatF64U, + I64TruncSatF32S, + I64TruncSatF32U, + I64TruncSatF64S, + I64TruncSatF64U, } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 387a878..ad82bd4 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -26,7 +26,7 @@ extern crate alloc; // } mod instructions; -use core::fmt::Debug; +use core::{fmt::Debug, ops::Range}; use alloc::boxed::Box; pub use instructions::*; @@ -61,9 +61,15 @@ pub struct TinyWasmModule { /// The memories of the WebAssembly module. pub memory_types: Box<[MemoryType]>, - // pub elements: Option>, - // pub imports: Option>, - // pub data_segments: Option>, + + /// The imports of the WebAssembly module. + pub imports: Box<[Import]>, + + /// Data segments of the WebAssembly module. + pub data: Box<[Data]>, + + /// Element segments of the WebAssembly module. + pub elements: Box<[Element]>, } /// A WebAssembly value. @@ -97,6 +103,28 @@ impl WasmValue { ValType::ExternRef => unimplemented!("ExternRef is not yet supported"), } } + + pub fn eq_loose(&self, other: &Self) -> bool { + match (self, other) { + (Self::I32(a), Self::I32(b)) => a == b, + (Self::I64(a), Self::I64(b)) => a == b, + (Self::F32(a), Self::F32(b)) => { + if a.is_nan() && b.is_nan() { + true // Both are NaN, treat them as equal + } else { + a.to_bits() == b.to_bits() + } + } + (Self::F64(a), Self::F64(b)) => { + if a.is_nan() && b.is_nan() { + true // Both are NaN, treat them as equal + } else { + a.to_bits() == b.to_bits() + } + } + _ => false, + } + } } impl From for WasmValue { @@ -302,9 +330,14 @@ pub struct Export { #[derive(Debug, Clone)] pub struct Global { + pub ty: GlobalType, + pub init: ConstInstruction, +} + +#[derive(Debug, Clone)] +pub struct GlobalType { pub mutable: bool, pub ty: ValType, - pub init: ConstInstruction, } #[derive(Debug, Clone)] @@ -329,3 +362,52 @@ pub enum MemoryArch { I32, I64, } + +#[derive(Debug, Clone)] +pub struct Import { + pub module: Box, + pub name: Box, + pub kind: ImportKind, +} + +#[derive(Debug, Clone)] +pub enum ImportKind { + Func(TypeAddr), + Table(TableType), + Mem(MemoryType), + Global(GlobalType), +} + +#[derive(Debug, Clone)] +pub struct Data { + pub data: Box<[u8]>, + pub range: Range, + pub kind: DataKind, +} + +#[derive(Debug, Clone)] +pub enum DataKind { + Active { mem: MemAddr, offset: ConstInstruction }, + Passive, +} + +#[derive(Debug, Clone)] +pub struct Element { + pub kind: ElementKind, + pub items: Box<[ElementItem]>, + pub range: Range, + pub ty: ValType, +} + +#[derive(Debug, Clone)] +pub enum ElementKind { + Passive, + Active { table: TableAddr, offset: ConstInstruction }, + Declared, +} + +#[derive(Debug, Clone)] +pub enum ElementItem { + Func(FuncAddr), + Expr(ConstInstruction), +} diff --git a/examples/wasm/test.wat b/examples/wasm/test.wat new file mode 100644 index 0000000..563f382 --- /dev/null +++ b/examples/wasm/test.wat @@ -0,0 +1,7 @@ +(module + (func (export "test") (result i32) + (i32.const 1) + ;; comment + (return (i32.const 2)) + ) +) \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy