diff options
| -rw-r--r-- | Cargo.lock | 52 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | README.md | 17 | ||||
| -rw-r--r-- | crates/parser/Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/tinywasm/tests/test-wasm-3.rs | 2 | ||||
| -rw-r--r-- | crates/tinywasm/tests/testsuite/run.rs | 4 | ||||
| -rw-r--r-- | crates/tinywasm/tests/testsuite/util.rs | 12 | ||||
| -rw-r--r-- | examples/archive.rs | 6 | ||||
| -rw-r--r-- | examples/funcref_callbacks.rs | 101 | ||||
| -rw-r--r-- | examples/linking.rs | 9 | ||||
| -rw-r--r-- | examples/wasm-rust.rs | 3 |
11 files changed, 129 insertions, 83 deletions
@@ -704,7 +704,7 @@ dependencies = [ "tinywasm-parser", "tinywasm-types", "wasm-testsuite", - "wast", + "wast 223.0.0", "wat", ] @@ -717,7 +717,7 @@ dependencies = [ "log", "pretty_env_logger", "tinywasm", - "wast", + "wast 223.0.0", ] [[package]] @@ -726,7 +726,7 @@ version = "0.9.0-alpha.0" dependencies = [ "log", "tinywasm-types", - "wasmparser", + "wasmparser 0.223.0", ] [[package]] @@ -782,7 +782,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3432682105d7e994565ef928ccf5856cf6af4ba3dddebedb737f61caed70f956" dependencies = [ "leb128", - "wasmparser", + "wasmparser 0.222.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.223.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e636076193fa68103e937ac951b5f2f587624097017d764b8984d9c0f149464" +dependencies = [ + "leb128", + "wasmparser 0.223.0", ] [[package]] @@ -792,7 +802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01e1e4857b055d8ad6d542d95074106b5b2afd411d3e26ae6da37a560053c97" dependencies = [ "include_dir", - "wast", + "wast 222.0.0", ] [[package]] @@ -807,6 +817,17 @@ dependencies = [ ] [[package]] +name = "wasmparser" +version = "0.223.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a99faceb1a5a84dd6084ec4bfa4b2ab153b5793b43fd8f58b89232634afc35" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] name = "wast" version = "222.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -816,16 +837,29 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.222.0", +] + +[[package]] +name = "wast" +version = "223.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59b2ba8a2ff9f06194b7be9524f92e45e70149f4dacc0d0c7ad92b59ac875e4" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.223.0", ] [[package]] name = "wat" -version = "1.222.0" +version = "1.223.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fde61b4b52f9a84ae31b5e8902a2cd3162ea45d8bf564c729c3288fe52f4334" +checksum = "662786915c427e4918ff01eabb3c4756d4d947cd8f635761526b4cc9da2eaaad" dependencies = [ - "wast", + "wast 223.0.0", ] [[package]] @@ -4,8 +4,8 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="222" -wat="1.222" +wast="223" +wat="1.223" eyre="0.6" log="0.4" pretty_env_logger="0.5" @@ -14,7 +14,7 @@ - **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality (< 4000 LLOC). - **Portable**: TinyWasm runs on any platform that Rust can target, including `no_std`, with minimal external dependencies. -- **Safe**: No unsafe code is used in the runtime (`rkyv` which uses unsafe code can be used for serialization, but is optional). +- **Safe**: No unsafe code is used in the runtime (`rkyv`, which uses unsafe code, can be used for serialization but is optional). ## Status @@ -24,7 +24,7 @@ TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite]( **Legend**\ 🌑 -- not available\ -🚧 -- in development / partialy supported\ +🚧 -- in development/partially supported\ 🟢 -- fully supported | Proposal | Status | TinyWasm Version | @@ -61,24 +61,21 @@ $ tinywasm-cli --help - **`archive`**\ Enables pre-parsing of archives. This is enabled by default. -With all these features disabled, TinyWasm only depends on `core`, `alloc` ,and `libm` and can be used in `no_std` environments. -Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on wasm32 targets. +With all these features disabled, TinyWasm only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. ## Inspiration Big thanks to the authors of the following projects, which have inspired and influenced TinyWasm: -- [wasmi](https://github.com/wasmi-labs/wasmi) - an efficient and lightweight WebAssembly interpreter that also runs on `no_std` environments -- [wasm3](https://github.com/wasm3/wasm3) - a high performance WebAssembly interpreter written in C -- [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in go +- [wasmi](https://github.com/wasmi-labs/wasmi) - an efficient and lightweight WebAssembly interpreter that also runs in `no_std` environments +- [wasm3](https://github.com/wasm3/wasm3) - a high-performance WebAssembly interpreter written in C +- [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in Go - [wain](https://github.com/rhysd/wain) - a zero-dependency WebAssembly interpreter written in Rust -I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly Runtimes. +I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes. ## License Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in TinyWasm by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. - -**Note:** The GitHub repository contains a Submodule (`crates/tinywasm-parser/data`) which is licensed only under the [Apache License, Version 2.0](https://github.com/WebAssembly/spec/blob/main/test/LICENSE). This data is generated from the [WebAssembly Specification](https://github.com/WebAssembly/spec/tree/main/test) and is only used for testing purposes and not included in the final binary. diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 61224fd..f8eabb4 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace=true rust-version.workspace=true [dependencies] -wasmparser={version="0.222", default-features=false, features=["validate", "features", "simd"]} +wasmparser={version="0.223", default-features=false, features=["validate", "features", "simd"]} log={workspace=true, optional=true} tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} diff --git a/crates/tinywasm/tests/test-wasm-3.rs b/crates/tinywasm/tests/test-wasm-3.rs index 0bd2cb0..a0e2543 100644 --- a/crates/tinywasm/tests/test-wasm-3.rs +++ b/crates/tinywasm/tests/test-wasm-3.rs @@ -4,7 +4,7 @@ use testsuite::TestSuite; use wasm_testsuite::data::{spec, SpecVersion}; fn main() -> Result<()> { - if std::env::args().find(|x| x == "--enable").is_none() { + if !std::env::args().any(|x| &x == "--enable") { println!("Skipping wasm-3 tests, use --enable to run"); return Ok(()); } diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index 7534142..0804266 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -8,8 +8,8 @@ use log::{debug, error, info}; use tinywasm::{Extern, Imports, ModuleInstance}; use tinywasm_types::{ExternVal, MemoryType, ModuleInstanceAddr, TableType, ValType, WasmValue}; use wasm_testsuite::data::TestFile; -use wasm_testsuite::wast::{lexer::Lexer, parser::ParseBuffer, Wast}; use wasm_testsuite::wast; +use wasm_testsuite::wast::{lexer::Lexer, parser::ParseBuffer, Wast}; #[derive(Default)] struct ModuleRegistry { @@ -171,7 +171,7 @@ impl TestSuite { Ok(()) } - pub fn run_file<'a>(&mut self, file: TestFile<'a>) -> Result<()> { + pub fn run_file(&mut self, file: TestFile<'_>) -> Result<()> { let test_group = self.test_group(file.name(), file.parent()); let wast_raw = file.raw(); let wast = file.wast()?; diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index b555153..f8f862b 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -120,15 +120,15 @@ fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result<tinywasm_types::WasmValue fn wast_i128_to_i128(i: wast::core::V128Pattern) -> u128 { match i { wast::core::V128Pattern::F32x4(f) => { - f.iter().fold(0, |acc, &f| acc << 32 | nanpattern2tinywasmvalue(f).unwrap().as_f32().unwrap() as u128) + f.iter().fold(0, |acc, &f| (acc << 32) | nanpattern2tinywasmvalue(f).unwrap().as_f32().unwrap() as u128) } wast::core::V128Pattern::F64x2(f) => { - f.iter().fold(0, |acc, &f| acc << 64 | nanpattern2tinywasmvalue(f).unwrap().as_f64().unwrap() as u128) + f.iter().fold(0, |acc, &f| (acc << 64) | nanpattern2tinywasmvalue(f).unwrap().as_f64().unwrap() as u128) } - wast::core::V128Pattern::I16x8(f) => f.iter().fold(0, |acc, &f| acc << 16 | f as u128), - wast::core::V128Pattern::I32x4(f) => f.iter().fold(0, |acc, &f| acc << 32 | f as u128), - wast::core::V128Pattern::I64x2(f) => f.iter().fold(0, |acc, &f| acc << 64 | f as u128), - wast::core::V128Pattern::I8x16(f) => f.iter().fold(0, |acc, &f| acc << 8 | f as u128), + wast::core::V128Pattern::I16x8(f) => f.iter().fold(0, |acc, &f| (acc << 16) | f as u128), + wast::core::V128Pattern::I32x4(f) => f.iter().fold(0, |acc, &f| (acc << 32) | f as u128), + wast::core::V128Pattern::I64x2(f) => f.iter().fold(0, |acc, &f| (acc << 64) | f as u128), + wast::core::V128Pattern::I8x16(f) => f.iter().fold(0, |acc, &f| (acc << 8) | f as u128), } } diff --git a/examples/archive.rs b/examples/archive.rs index ea82c51..f1a19b6 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -11,12 +11,12 @@ const WASM: &str = r#" "#; fn main() -> Result<()> { - let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); let module = Parser::default().parse_module_bytes(wasm)?; let twasm = module.serialize_twasm(); - // now, you could e.g. write twasm to a file called `add.twasm` - // and load it later in a different program + // Now, you could e.g. write `twasm` to a file called `add.twasm` + // and load it later in a different program. let module: Module = TinyWasmModule::from_twasm(&twasm)?.into(); let mut store = Store::default(); diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index 08332cb..df50e75 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -1,6 +1,5 @@ use eyre::Result; use tinywasm::{types::FuncRef, Extern, FuncContext, Imports, Module, Store}; -use wat; fn main() -> Result<()> { by_func_ref_passed()?; @@ -8,30 +7,30 @@ fn main() -> Result<()> { Ok(()) } -/// example of passing wasm functions (as funcref) to imported host function -/// and imported host function calling them +/// Example of passing Wasm functions (as `funcref`) to an imported host function +/// and the imported host function calling them. fn by_func_ref_passed() -> Result<()> { - // a module with: - // imported function "host.call_this" that accepts a callback - // exported wasm function "tell_host_to_call" that calls "host.call_this" with wasm functions $add and $sub - // wasm functions $add and $sub and imported function $mul used as callbacks - // (just to show that imported functions can be referenced too) - // exported wasm function "call_binop_by_ref" is a proxy used by host to call func-references of type (i32, i32)->i32 + // A module with: + // - Imported function "host.call_this" that accepts a callback. + // - Exported Wasm function "tell_host_to_call" that calls "host.call_this" with Wasm functions $add and $sub. + // - Wasm functions $add and $sub and an imported function $mul used as callbacks + // (just to show that imported functions can be referenced too). + // - Exported Wasm function "call_binop_by_ref", a proxy used by the host to call func-references of type (i32, i32) -> i32. const WASM: &str = r#" (module (import "host" "call_this" (func $host_callback_caller (param funcref))) (import "host" "mul" (func $host_mul (param $x i32) (param $y i32) (result i32))) (func $tell_host_to_call (export "tell_host_to_call") - (call $host_callback_caller (ref.func $add)) - (call $host_callback_caller (ref.func $sub)) - (call $host_callback_caller (ref.func $host_mul)) + (call $host_callback_caller (ref.func $add)) + (call $host_callback_caller (ref.func $sub)) + (call $host_callback_caller (ref.func $host_mul)) ) (type $binop (func (param i32 i32) (result i32))) (table 3 funcref) - (elem (i32.const 0) $add $sub $host_mul) ;; function can only be taken reference of if it's added to a table + (elem (i32.const 0) $add $sub $host_mul) ;; Function can only be referenced if added to a table. (func $add (param $x i32) (param $y i32) (result i32) local.get $x local.get $y @@ -45,59 +44,63 @@ fn by_func_ref_passed() -> Result<()> { (table $callback_register 1 funcref) (func (export "call_binop_by_ref") (param funcref i32 i32) (result i32) - (table.set $callback_register (i32.const 0) (local.get 0)) + (table.set $callback_register (i32.const 0) (local.get 0)) (call_indirect $callback_register (type $binop) (local.get 1)(local.get 2)(i32.const 0)) ) ) "#; - let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - // import host function that takes callbacks and calls them + + // Import host function that takes callbacks and calls them. imports.define( "host", "call_this", Extern::typed_func(|mut ctx: FuncContext<'_>, fn_ref: FuncRef| -> tinywasm::Result<()> { let proxy_caller = ctx.module().exported_func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; - // call callback we got as argument using call_binop_by_ref + // Call the callback we got as an argument using "call_binop_by_ref". let res = proxy_caller.call(ctx.store_mut(), (fn_ref, 5, 3))?; println!("(funcref {fn_ref:?})(5,3) results in {res}"); Ok(()) }), )?; - // import host.mul function (one of the functions whose references are taken) + + // Import host.mul function (one of the functions whose references are taken). imports.define( "host", "mul", Extern::typed_func(|_, args: (i32, i32)| -> tinywasm::Result<i32> { Ok(args.0 * args.1) }), )?; + let instance = module.instantiate(&mut store, Some(imports))?; - let caller = instance.exported_func::<(), ()>(&mut store, "tell_host_to_call")?; - // call the tell_host_to_call + let caller = instance.exported_func::<(), ()>(&store, "tell_host_to_call")?; + + // Call "tell_host_to_call". caller.call(&mut store, ())?; - // interesting detail is that neither $add $sub $mul were exported, - // but with a little help from proxy "call_binop_by_ref" references to them host was able to call them + // An interesting detail is that neither $add, $sub, nor $mul were exported, + // but with a little help from the proxy "call_binop_by_ref", references to them are callable by the host. Ok(()) } -/// example of returning wasm function as callback to host function -/// and host function calling it +/// Example of returning a Wasm function as a callback to a host function +/// and the host function calling it. fn by_func_ref_returned() -> Result<()> { - // a module with: - // an exported function "what_should_host_call" that returns 3 funcrefs - // wasm functions $add and $sub and imported function $mul used as callbacks - // (just to show that imported functions can be referenced too) - // exported wasm function "call_binop_by_ref" is a proxy used by host to call func-references of type (i32, i32)->i32 + // A module with: + // - An exported function "what_should_host_call" that returns 3 `funcref`s. + // - Wasm functions $add and $sub and an imported function $mul used as callbacks + // (just to show that imported functions can be referenced too). + // - Another exported Wasm function "call_binop_by_ref", a proxy used by the host to call func-references of type (i32, i32) -> i32 const WASM: &str = r#" (module (import "host" "mul" (func $host_mul (param $x i32) (param $y i32) (result i32))) (type $binop (func (param i32 i32) (result i32))) (table 3 funcref) - (elem (i32.const 0) $add $sub $host_mul) + (elem (i32.const 0) $add $sub $host_mul) (func $add (param $x i32) (param $y i32) (result i32) local.get $x local.get $y @@ -109,24 +112,25 @@ fn by_func_ref_returned() -> Result<()> { i32.sub ) (func $ref_to_funcs (export "what_should_host_call") (result funcref funcref funcref) - (ref.func $add) - (ref.func $sub) + (ref.func $add) + (ref.func $sub) (ref.func $host_mul) ) (table $callback_register 1 funcref) (func $call (export "call_binop_by_ref") (param funcref i32 i32) (result i32) - (table.set $callback_register (i32.const 0) (local.get 0)) + (table.set $callback_register (i32.const 0) (local.get 0)) (call_indirect $callback_register (type $binop) (local.get 1)(local.get 2)(i32.const 0)) ) ) "#; - let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - // import host.mul function (one of the possible operations) + + // Import host.mul function (one of the possible operations). imports.define( "host", "mul", @@ -134,19 +138,20 @@ fn by_func_ref_returned() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - { - // ask module what should we call - let funcrefs = { - let address_getter = - instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&mut store, "what_should_host_call")?; - address_getter.call(&mut store, ())? - }; - let proxy_caller = instance.exported_func::<(FuncRef, i32, i32), i32>(&mut store, "call_binop_by_ref")?; - for (idx, func_ref) in [funcrefs.0, funcrefs.1, funcrefs.2].iter().enumerate() { - // call those funcrefs via "call_binop_by_ref" - let res = proxy_caller.call(&mut store, (*func_ref, 5, 3))?; - println!("at idx: {idx} funcref {func_ref:?}(5,3) results in {res}"); - } + + // Ask the module what to call. + let funcrefs = { + let address_getter = + instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; + address_getter.call(&mut store, ())? + }; + + let proxy_caller = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; + + for (idx, func_ref) in [funcrefs.0, funcrefs.1, funcrefs.2].iter().enumerate() { + // Call those `funcref`s via "call_binop_by_ref". + let res = proxy_caller.call(&mut store, (*func_ref, 5, 3))?; + println!("At idx: {idx}, funcref {func_ref:?}(5,3) results in {res}"); } Ok(()) } diff --git a/examples/linking.rs b/examples/linking.rs index d215898..f94773a 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -1,6 +1,7 @@ use eyre::Result; use tinywasm::{Module, Store}; +// WebAssembly module defining and exporting an `add` function. const WASM_ADD: &str = r#" (module (func $add (param $lhs i32) (param $rhs i32) (result i32) @@ -10,6 +11,7 @@ const WASM_ADD: &str = r#" (export "add" (func $add))) "#; +// WebAssembly module importing an `add` function and using it. const WASM_IMPORT: &str = r#" (module (import "adder" "add" (func $add (param i32 i32) (result i32))) @@ -29,13 +31,20 @@ fn main() -> Result<()> { let import_module = Module::parse_bytes(&wasm_import)?; let mut store = Store::default(); + + // Instantiate the `add` module. let add_instance = add_module.instantiate(&mut store, None)?; + // Link the `adder` namespace to the `add` module's instance. let mut imports = tinywasm::Imports::new(); imports.link_module("adder", add_instance.id())?; + + // Instantiate the `import` module with the linked imports. let import_instance = import_module.instantiate(&mut store, Some(imports))?; + // Call the `main` function, which uses the imported `add` function. let main = import_instance.exported_func::<(), i32>(&store, "main")?; assert_eq!(main.call(&mut store, ())?, 3); + Ok(()) } diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 89203c1..429bdcb 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -14,7 +14,8 @@ use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; /// ./examples/rust/build.sh /// ``` /// -/// This requires the `wasm32-unknown-unknown` target, `binaryen` and `wabt` to be installed. +/// This requires the `wasm32-unknown-unknown` target, `binaryen`, and `wabt` to be installed: +/// /// `rustup target add wasm32-unknown-unknown`. /// <https://github.com/WebAssembly/wabt> /// <https://github.com/WebAssembly/binaryen> |
