diff --git a/.cargo/config.toml b/.cargo/config.toml index 1337d89..31a61b0 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,3 @@ [alias] dev="run -- -l debug run" +spec-mvp="test --package tinywasm --test mvp -- test_mvp --exact --nocapture --ignored" diff --git a/Cargo.lock b/Cargo.lock index d8b151c..bdc7417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,6 +664,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" name = "tinywasm" version = "0.0.3" dependencies = [ + "eyre", "log", "owo-colors", "tinywasm-parser", diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs index a5c807a..acacfa0 100644 --- a/crates/parser/src/error.rs +++ b/crates/parser/src/error.rs @@ -1,8 +1,9 @@ -use core::fmt::Debug; +use core::fmt::{Debug, Display}; use alloc::string::{String, ToString}; use wasmparser::Encoding; +#[derive(Debug)] pub enum ParseError { InvalidType, UnsupportedSection(String), @@ -16,7 +17,7 @@ pub enum ParseError { Other(String), } -impl Debug for ParseError { +impl Display for ParseError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::InvalidType => write!(f, "invalid type"), @@ -37,6 +38,9 @@ impl Debug for ParseError { } } +#[cfg(any(feature = "std", all(not(feature = "std"), nightly)))] +impl crate::std::error::Error for ParseError {} + impl From for ParseError { fn from(value: wasmparser::BinaryReaderError) -> Self { Self::ParseError { diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 1865614..8c9ea50 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -21,6 +21,7 @@ tinywasm-types={version="0.0.3", path="../types", default-features=false} wasm-testsuite={path="../wasm-testsuite"} wast={version="69.0"} owo-colors={version="3.5"} +eyre={version="0.6"} [features] default=["std", "parser", "logging"] diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 885f28c..8c38a89 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -10,6 +10,12 @@ pub struct Module { data: TinyWasmModule, } +impl From<&TinyWasmModule> for Module { + fn from(data: &TinyWasmModule) -> Self { + Self { data: data.clone() } + } +} + impl From for Module { fn from(data: TinyWasmModule) -> Self { Self { data } diff --git a/crates/tinywasm/tests/mvp.rs b/crates/tinywasm/tests/mvp.rs index 87a36d8..c6fe43d 100644 --- a/crates/tinywasm/tests/mvp.rs +++ b/crates/tinywasm/tests/mvp.rs @@ -3,7 +3,8 @@ use std::{ fmt::{Debug, Formatter}, }; -use tinywasm::{Error, Result}; +use eyre::{eyre, Result}; +use log::debug; use tinywasm_types::TinyWasmModule; use wast::{ lexer::Lexer, @@ -11,7 +12,7 @@ use wast::{ QuoteWat, Wast, }; -fn parse_module(mut module: wast::core::Module) -> Result { +fn parse_module(mut module: wast::core::Module) -> Result { let parser = tinywasm_parser::Parser::new(); Ok(parser.parse_module_bytes(module.encode().expect("failed to encode module"))?) } @@ -27,32 +28,34 @@ fn test_mvp() -> Result<()> { 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); + 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 = 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(QuoteWat::Wat(wast::Wat::Module(module))) => { - let module = std::panic::catch_unwind(|| parse_module(module)); - test_group.add_result( - &format!("{}-parse", name), - span, - match module { - Ok(Ok(_)) => Ok(()), - Ok(Err(e)) => Err(e), - Err(e) => Err(Error::Other(format!("failed to parse module: {:?}", e))), - }, - ); + let result = std::panic::catch_unwind(|| parse_module(module)) + .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()), + } + + test_group.add_result(&format!("{}-parse", name), span, result.map(|_| ())); } + // these all pass already :) AssertMalformed { span, @@ -64,11 +67,49 @@ fn test_mvp() -> Result<()> { &format!("{}-malformed", name), span, match res { - Ok(Ok(_)) => Err(Error::Other("expected module to be malformed".to_string())), + Ok(Ok(_)) => Err(eyre!("expected module to be malformed")), Err(_) | Ok(Err(_)) => Ok(()), }, ); } + AssertReturn { span, exec, results: _ } => { + let Some(module) = last_module.as_ref() else { + // println!("no module found for assert_return: {:?}", exec); + continue; + }; + + let res: Result, _> = std::panic::catch_unwind(|| { + let mut store = tinywasm::Store::new(); + let module = tinywasm::Module::from(module); + let instance = module.instantiate(&mut store)?; + + use wast::WastExecute::*; + match exec { + Wat(_) => return Result::Ok(()), // not used by the testsuite + Get { module: _, global: _ } => return Result::Ok(()), + Invoke(invoke) => { + for arg in invoke.args { + let arg = get_tinywasm_wasm_value(arg)?; + let _res = instance.get_func(&store, invoke.name)?.call(&mut store, &[arg])?; + // TODO: check the result + } + } + } + + Ok(()) + }); + + let res = match res { + Err(e) => Err(eyre!("test panicked: {:?}", e)), + Ok(Err(e)) => Err(e), + Ok(Ok(())) => Ok(()), + }; + + test_group.add_result(&format!("{}-return", name), span, res); + } + Invoke(m) => { + debug!("invoke: {:?}", m); + } // _ => test_group.add_result( // &format!("{}-unknown", name), // span, @@ -82,13 +123,29 @@ fn test_mvp() -> Result<()> { if test_suite.failed() { eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); - Err(Error::Other("failed one or more tests".to_string())) + Err(eyre!("failed one or more tests")) } else { println!("\n\npassed all tests:\n{:#?}", test_suite); Ok(()) } } +fn get_tinywasm_wasm_value(arg: wast::WastArg) -> Result { + let wast::WastArg::Core(arg) = arg else { + return Err(eyre!("unsupported arg type")); + }; + + use tinywasm_types::WasmValue; + use wast::core::WastArgCore::*; + Ok(match arg { + F32(f) => WasmValue::F32(f32::from_bits(f.bits)), + F64(f) => WasmValue::F64(f64::from_bits(f.bits)), + I32(i) => WasmValue::I32(i), + I64(i) => WasmValue::I64(i), + _ => return Err(eyre!("unsupported arg type")), + }) +} + struct TestSuite(BTreeMap); impl TestSuite { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 4e09e00..4defb23 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -36,7 +36,7 @@ pub use instructions::*; /// This is the internal representation of a WebAssembly module in TinyWasm. /// TinyWasmModules are validated before being created, so they are guaranteed to be valid (as long as they were created by TinyWasm). /// This means you should not trust a TinyWasmModule created by a third party to be valid. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TinyWasmModule { /// The version of the WebAssembly module. pub version: Option, @@ -266,7 +266,7 @@ pub struct FuncType { } /// A WebAssembly Function -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Function { pub ty: TypeAddr, pub locals: Box<[ValType]>, @@ -274,7 +274,7 @@ pub struct Function { } /// A WebAssembly Module Export -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Export { /// The name of the export. pub name: Box, diff --git a/crates/wasm-testsuite/README.md b/crates/wasm-testsuite/README.md index c3dcde2..51d574c 100644 --- a/crates/wasm-testsuite/README.md +++ b/crates/wasm-testsuite/README.md @@ -7,9 +7,9 @@ This crate embeds the latest version of the [WebAssembly Test Suite](https://git ```rust use wasm_testsuite::{MVP_TESTS, get_test_wast}; -wasm_testsuite::MVP_TESTS.iter().for_each(|test| { +MVP_TESTS.iter().for_each(|test| { let wast_bytes = get_test_wast(test).expect("Failed to get wast bytes"); - let wast = std::str::from_utf8(&wast).expect("failed to convert wast to utf8"); + let wast = std::str::from_utf8(&wast_bytes).expect("failed to convert wast to utf8"); // Do something with the wast (e.g. parse it using the `wast` crate) });