From d8d439e6401607fab18feb5025f3b91f135e3a50 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 15:16:05 +0100 Subject: [PATCH 01/27] feat: add more examples, work on new public api Signed-off-by: Henry Gressmann --- .cargo/config.toml | 1 - .github/workflows/test.yaml | 8 +++--- .gitignore | 2 ++ .vscode/settings.json | 6 +++- Cargo.lock | 7 ----- Cargo.toml | 6 +++- crates/cli/src/bin.rs | 2 +- crates/tinywasm/src/export.rs | 1 - crates/tinywasm/src/imports.rs | 9 ++++-- crates/tinywasm/src/instance.rs | 36 ++++++++++++++++-------- crates/tinywasm/src/lib.rs | 5 +++- crates/tinywasm/src/reference.rs | 17 +++++++++++ crates/tinywasm/tests/testsuite/run.rs | 2 +- crates/tinywasm/tests/testsuite/util.rs | 4 +-- crates/types/src/lib.rs | 6 ++-- examples/README.md | 7 +++-- examples/rust/Cargo.toml | 14 +++++++++ examples/rust/README.md | 5 +++- examples/rust/build.sh | 7 +++-- examples/rust/src/fibonacci.rs | 17 +++++++++++ examples/rust/src/tinywasm.rs | 2 +- examples/wasm-rust.rs | 19 +++++++++++-- examples/wasm/add.wasm | Bin 0 -> 65 bytes 23 files changed, 138 insertions(+), 45 deletions(-) delete mode 100644 crates/tinywasm/src/export.rs create mode 100644 crates/tinywasm/src/reference.rs create mode 100644 examples/rust/src/fibonacci.rs create mode 100644 examples/wasm/add.wasm diff --git a/.cargo/config.toml b/.cargo/config.toml index 19d47a2..807bc56 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,6 @@ [alias] version-dev="workspaces version --no-git-commit --force tinywasm*" dev="run -- -l debug run" - test-mvp="test --package tinywasm --test test-mvp --release -- --enable " test-2="test --package tinywasm --test test-two --release -- --enable " test-wast="test --package tinywasm --test test-wast -- --enable " diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 92d56f4..05f7397 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,10 +20,10 @@ jobs: run: rustup update stable - name: Build (stable) - run: cargo +stable build --workspace --exclude wasm-testsuite + run: cargo +stable build --workspace - name: Run tests (stable) - run: cargo +stable test --workspace --exclude wasm-testsuite + run: cargo +stable test --workspace - name: Run MVP testsuite run: cargo +stable test-mvp @@ -41,10 +41,10 @@ jobs: run: rustup update nightly - name: Build (nightly, no default features) - run: cargo +nightly build --workspace --exclude wasm-testsuite --exclude rust-wasm-examples --no-default-features + run: cargo +nightly build --workspace --no-default-features - name: Run tests (nightly, no default features) - run: cargo +nightly test --workspace --exclude wasm-testsuite --exclude rust-wasm-examples --no-default-features + run: cargo +nightly test --workspace --no-default-features - name: Run MVP testsuite (nightly) run: cargo +nightly test-mvp diff --git a/.gitignore b/.gitignore index f5dcc83..a41fff2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target notes.md examples/rust/out/* +examples/rust/target +examples/rust/Cargo.lock examples/wast/* diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f9768f..51f36b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,9 @@ { "search.exclude": { "**/wasm-testsuite/data": true - } + }, + "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + "./examples/rust/Cargo.toml" + ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b145eff..ff4d494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1045,13 +1045,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rust-wasm-examples" -version = "0.0.0" -dependencies = [ - "tinywasm", -] - [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/Cargo.toml b/Cargo.toml index ee9a9df..13a9397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members=["crates/*", "examples/rust"] +members=["crates/*"] resolver="2" [profile.wasm] @@ -21,6 +21,10 @@ name="tinywasm-root" publish=false edition="2021" +[[example]] +name="wasm-rust" +test=false + [dev-dependencies] color-eyre="0.6" tinywasm={path="crates/tinywasm"} diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 1c166cf..34d4bcd 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -112,7 +112,7 @@ fn run(module: Module, func: Option, args: Vec) -> Result<()> let instance = module.instantiate(&mut store, None)?; if let Some(func) = func { - let func = instance.exported_func_by_name(&store, &func)?; + let func = instance.exported_func_untyped(&store, &func)?; let res = func.call(&mut store, &args)?; info!("{res:?}"); } diff --git a/crates/tinywasm/src/export.rs b/crates/tinywasm/src/export.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/tinywasm/src/export.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index a640081..9c5086a 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -77,6 +77,11 @@ impl FuncContext<'_> { pub fn module(&self) -> &crate::ModuleInstance { self.module } + + /// Get a reference to an exported memory + pub fn memory(&mut self, name: &str) -> Result { + self.module.exported_memory(self.store, name) + } } impl Debug for HostFunction { @@ -276,7 +281,7 @@ impl Imports { if let Some(addr) = self.modules.get(&name.module) { let instance = store.get_module_instance(*addr)?; - return Some(ResolvedExtern::Store(instance.export(&import.name)?)); + return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?)); } None @@ -398,7 +403,7 @@ impl Imports { Self::compare_table_types(import, &table.borrow().kind, ty)?; imports.tables.push(table_addr); } - (ExternVal::Mem(memory_addr), ImportKind::Memory(ty)) => { + (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => { let mem = store.get_mem(memory_addr as usize)?; let (size, kind) = { let mem = mem.borrow(); diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index d750cd5..dfee2ca 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,12 +1,9 @@ use alloc::{boxed::Box, format, string::ToString, sync::Arc}; -use tinywasm_types::{ - DataAddr, ElemAddr, Export, ExternVal, ExternalKind, FuncAddr, FuncType, GlobalAddr, Import, MemAddr, - ModuleInstanceAddr, TableAddr, -}; +use tinywasm_types::*; use crate::{ func::{FromWasmValueTuple, IntoWasmValueTuple}, - log, Error, FuncHandle, FuncHandleTyped, Imports, Module, Result, Store, + log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, Module, Result, Store, }; /// An instanciated WebAssembly module @@ -106,7 +103,7 @@ impl ModuleInstance { } /// Get a export by name - pub fn export(&self, name: &str) -> Option { + pub fn export_addr(&self, name: &str) -> Option { let exports = self.0.exports.iter().find(|e| e.name == name.into())?; let kind = exports.kind.clone(); let addr = match kind { @@ -162,12 +159,12 @@ impl ModuleInstance { } /// Get an exported function by name - pub fn exported_func_by_name(&self, store: &Store, name: &str) -> Result { + pub fn exported_func_untyped(&self, store: &Store, name: &str) -> Result { if self.0.store_id != store.id() { return Err(Error::InvalidStore); } - let export = self.export(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?; + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?; let ExternVal::Func(func_addr) = export else { return Err(Error::Other(format!("Export is not a function: {}", name))); }; @@ -179,15 +176,32 @@ impl ModuleInstance { } /// Get a typed exported function by name - pub fn typed_func(&self, store: &Store, name: &str) -> Result> + pub fn exported_func(&self, store: &Store, name: &str) -> Result> where P: IntoWasmValueTuple, R: FromWasmValueTuple, { - let func = self.exported_func_by_name(store, name)?; + let func = self.exported_func_untyped(store, name)?; Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) } + /// Get an exported memory by name + pub fn exported_memory(&self, store: &mut Store, name: &str) -> Result { + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?; + let ExternVal::Memory(mem_addr) = export else { + return Err(Error::Other(format!("Export is not a memory: {}", name))); + }; + let mem = self.memory(store, mem_addr)?; + Ok(mem) + } + + /// Get a memory by address + pub fn memory(&self, store: &Store, addr: MemAddr) -> Result { + let addr = self.resolve_mem_addr(addr); + let mem = store.get_mem(addr as usize)?; + Ok(MemoryRef { instance: mem.clone() }) + } + /// Get the start function of the module /// /// Returns None if the module has no start function @@ -204,7 +218,7 @@ impl ModuleInstance { Some(func_index) => func_index, None => { // alternatively, check for a _start function in the exports - let Some(ExternVal::Func(func_addr)) = self.export("_start") else { + let Some(ExternVal::Func(func_addr)) = self.export_addr("_start") else { return Ok(None); }; diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 36270a7..f2951b6 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -51,7 +51,7 @@ //! // Get a typed handle to the exported "add" function //! // Alternatively, you can use `instance.get_func` to get an untyped handle //! // that takes and returns [`WasmValue`]s -//! let func = instance.typed_func::<(i32, i32), i32>(&mut store, "add")?; +//! let func = instance.exported_func::<(i32, i32), i32>(&mut store, "add")?; //! let res = func.call(&mut store, (1, 2))?; //! //! assert_eq!(res, 3); @@ -99,6 +99,9 @@ pub use module::Module; mod instance; pub use instance::ModuleInstance; +mod reference; +pub use reference::*; + mod func; pub use func::{FuncHandle, FuncHandleTyped}; diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs new file mode 100644 index 0000000..21471f6 --- /dev/null +++ b/crates/tinywasm/src/reference.rs @@ -0,0 +1,17 @@ +use core::cell::RefCell; + +use alloc::rc::Rc; + +use crate::{GlobalInstance, MemoryInstance}; + +/// A reference to a memory instance +#[derive(Debug, Clone)] +pub struct MemoryRef { + pub(crate) instance: Rc>, +} + +/// A reference to a global instance +#[derive(Debug, Clone)] +pub struct GlobalRef { + pub(crate) instance: Rc>, +} diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index 79d9acc..c44c3fb 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -428,7 +428,7 @@ impl TestSuite { continue; }; - let module_global = match match module.export(global) { + let module_global = match match module.export_addr(global) { Some(ExternVal::Global(addr)) => { store.get_global_val(addr as usize).map_err(|_| eyre!("failed to get global")) } diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index b74eec5..09a4769 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -25,7 +25,7 @@ pub fn exec_fn_instance( return Err(tinywasm::Error::Other("no instance found".to_string())); }; - let func = instance.exported_func_by_name(store, name)?; + let func = instance.exported_func_untyped(store, name)?; func.call(store, args) } @@ -42,7 +42,7 @@ pub fn exec_fn( let mut store = tinywasm::Store::new(); let module = tinywasm::Module::from(module); let instance = module.instantiate(&mut store, imports)?; - instance.exported_func_by_name(&store, name)?.call(&mut store, args) + instance.exported_func_untyped(&store, name)?.call(&mut store, args) } pub fn catch_unwind_silent R, R>(f: F) -> std::thread::Result { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index d0d854c..365ead7 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -316,7 +316,7 @@ pub type ModuleInstanceAddr = Addr; pub enum ExternVal { Func(FuncAddr), Table(TableAddr), - Mem(MemAddr), + Memory(MemAddr), Global(GlobalAddr), } @@ -325,7 +325,7 @@ impl ExternVal { match self { Self::Func(_) => ExternalKind::Func, Self::Table(_) => ExternalKind::Table, - Self::Mem(_) => ExternalKind::Memory, + Self::Memory(_) => ExternalKind::Memory, Self::Global(_) => ExternalKind::Global, } } @@ -334,7 +334,7 @@ impl ExternVal { match kind { ExternalKind::Func => Self::Func(addr), ExternalKind::Table => Self::Table(addr), - ExternalKind::Memory => Self::Mem(addr), + ExternalKind::Memory => Self::Memory(addr), ExternalKind::Global => Self::Global(addr), } } diff --git a/examples/README.md b/examples/README.md index ce47073..94f974b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,10 +1,10 @@ # Examples -## WasmRust +## Wasm-Rust These are examples using WebAssembly generated from Rust code. -To run these, you first need to build the Rust code into WebAssembly, since the wasm files are not included in the repository to keep it small. -This requires the `wasm32-unknown-unknown` target and `wasm-opt` to be installed (available via Binaryen). +To run these, you first need to build the Rust code, since the resulting wasm files are not included in the repository to keep it small. +This requires the `wasm32-unknown-unknown` target and `wasm-opt` to be installed (available via [Binaryen](https://github.com/WebAssembly/binaryen)). ```bash $ ./examples/rust/build.sh @@ -20,3 +20,4 @@ Where `` is one of the following: - `hello`: A simple example that prints a number to the console. - `tinywasm`: Runs `hello` using TinyWasm - inside of TinyWasm itself! +- `fibonacci`: Calculates the x-th Fibonacci number. diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 9ff80dc..d557392 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -1,5 +1,8 @@ cargo-features=["per-package-target"] +# treat this as an independent package +[workspace] + [package] publish=false name="rust-wasm-examples" @@ -16,3 +19,14 @@ path="src/hello.rs" [[bin]] name="tinywasm" path="src/tinywasm.rs" + +[[bin]] +name="fibonacci" +path="src/fibonacci.rs" + +[profile.wasm] +opt-level="s" +lto="thin" +codegen-units=1 +panic="abort" +inherits="release" diff --git a/examples/rust/README.md b/examples/rust/README.md index 8ecac52..1b6be2f 100644 --- a/examples/rust/README.md +++ b/examples/rust/README.md @@ -1 +1,4 @@ -# Examples using Rust compiled to WebAssembly +# WebAssembly Rust Examples + +This is a seperate crate that generates WebAssembly from Rust code. +It is used by the `wasm-rust` example. diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 2c8069a..a8c587a 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -1,11 +1,14 @@ #!/usr/bin/env bash cd "$(dirname "$0")" -bins=("hello" "tinywasm") +bins=("hello" "tinywasm" "fibonacci") exclude_wat=("tinywasm") -out_dir="../../target/wasm32-unknown-unknown/wasm" +out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" +# ensure out dir exists +mkdir -p "$dest_dir" + for bin in "${bins[@]}"; do cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs new file mode 100644 index 0000000..7493132 --- /dev/null +++ b/examples/rust/src/fibonacci.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + core::arch::wasm32::unreachable() +} + +#[no_mangle] +// The rust compiler will convert this to an iterative algorithm. +pub extern "C" fn fibonacci(n: i32) -> i32 { + if n <= 1 { + return n; + } + fibonacci(n - 1) + fibonacci(n - 2) +} diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 0f18ab9..0fb9261 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -26,7 +26,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.typed_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 3b8877e..3b26863 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -13,6 +13,7 @@ fn main() -> Result<()> { match args[1].as_str() { "hello" => hello()?, + "fibonacci" => fibonacci()?, "tinywasm" => tinywasm()?, _ => {} } @@ -36,7 +37,7 @@ fn tinywasm() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let hello = instance.typed_func::<(), ()>(&mut store, "hello")?; + let hello = instance.exported_func::<(), ()>(&mut store, "hello")?; hello.call(&mut store, ())?; Ok(()) @@ -58,8 +59,22 @@ fn hello() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.typed_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } + +fn fibonacci() -> Result<()> { + const FIBONACCI_WASM: &[u8] = include_bytes!("./rust/out/fibonacci.wasm"); + let module = Module::parse_bytes(&FIBONACCI_WASM)?; + let mut store = Store::default(); + + let instance = module.instantiate(&mut store, None)?; + let fibonacci = instance.exported_func::(&mut store, "fibonacci")?; + let n = 30; + let result = fibonacci.call(&mut store, n)?; + println!("fibonacci({}) = {}", n, result); + + Ok(()) +} diff --git a/examples/wasm/add.wasm b/examples/wasm/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..92e343278f5bf783a31d603872c2af25cb118d1b GIT binary patch literal 65 zcmZQbEY4+QU|?Y6WlCVGuV<`JV5+NQtYc8Nnv1M1CsG(CJc;Rf=uiT O3JeO2S= Date: Fri, 26 Jan 2024 15:21:29 +0100 Subject: [PATCH 02/27] ci: exclude wasm-rust example Signed-off-by: Henry Gressmann --- crates/tinywasm/src/instance.rs | 2 +- crates/tinywasm/src/reference.rs | 8 ++++---- examples/wasm-rust.rs | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index dfee2ca..5346ac3 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -199,7 +199,7 @@ impl ModuleInstance { pub fn memory(&self, store: &Store, addr: MemAddr) -> Result { let addr = self.resolve_mem_addr(addr); let mem = store.get_mem(addr as usize)?; - Ok(MemoryRef { instance: mem.clone() }) + Ok(MemoryRef { _instance: mem.clone() }) } /// Get the start function of the module diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 21471f6..bc02338 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,17 +1,17 @@ use core::cell::RefCell; -use alloc::rc::Rc; - use crate::{GlobalInstance, MemoryInstance}; +use alloc::rc::Rc; +// This module essentially contains the public APIs to interact with the data stored in the store /// A reference to a memory instance #[derive(Debug, Clone)] pub struct MemoryRef { - pub(crate) instance: Rc>, + pub(crate) _instance: Rc>, } /// A reference to a global instance #[derive(Debug, Clone)] pub struct GlobalRef { - pub(crate) instance: Rc>, + pub(crate) _instance: Rc>, } diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 3b26863..2c50c4f 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,6 +1,7 @@ use color_eyre::eyre::Result; use tinywasm::{Extern, FuncContext, Imports, Module, Store}; +#[cfg(not(test))] fn main() -> Result<()> { let args = std::env::args().collect::>(); if args.len() < 2 { @@ -21,6 +22,7 @@ fn main() -> Result<()> { Ok(()) } +#[cfg(not(test))] fn tinywasm() -> Result<()> { const TINYWASM: &[u8] = include_bytes!("./rust/out/tinywasm.wasm"); let module = Module::parse_bytes(&TINYWASM)?; @@ -43,6 +45,7 @@ fn tinywasm() -> Result<()> { Ok(()) } +#[cfg(not(test))] fn hello() -> Result<()> { const HELLO_WASM: &[u8] = include_bytes!("./rust/out/hello.wasm"); let module = Module::parse_bytes(&HELLO_WASM)?; @@ -65,6 +68,7 @@ fn hello() -> Result<()> { Ok(()) } +#[cfg(not(test))] fn fibonacci() -> Result<()> { const FIBONACCI_WASM: &[u8] = include_bytes!("./rust/out/fibonacci.wasm"); let module = Module::parse_bytes(&FIBONACCI_WASM)?; From bed5f13e0c50c44c8b98f5f758ba487a198931ec Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 15:25:36 +0100 Subject: [PATCH 03/27] ci: fix ci Signed-off-by: Henry Gressmann --- .github/workflows/test.yaml | 19 +++++++++++++++---- examples/wasm-rust.rs | 4 ---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 05f7397..6f6fc10 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,8 +16,14 @@ jobs: with: submodules: true - - name: Install stable Rust toolchain - run: rustup update stable + - name: Install stable Rust toolchain & Binaryen + run: | + rustup update stable + rustup update nightly + sudo apt-get install -y binaryen + + - name: Build wasm + run: ./examples/rust/build.sh - name: Build (stable) run: cargo +stable build --workspace @@ -37,8 +43,13 @@ jobs: with: submodules: true - - name: Install nightly Rust toolchain - run: rustup update nightly + - name: Install nightly Rust toolchain & Binaryen + run: | + rustup update nightly + sudo apt-get install -y binaryen + + - name: Build wasm + run: ./examples/rust/build.sh - name: Build (nightly, no default features) run: cargo +nightly build --workspace --no-default-features diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 2c50c4f..3b26863 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,7 +1,6 @@ use color_eyre::eyre::Result; use tinywasm::{Extern, FuncContext, Imports, Module, Store}; -#[cfg(not(test))] fn main() -> Result<()> { let args = std::env::args().collect::>(); if args.len() < 2 { @@ -22,7 +21,6 @@ fn main() -> Result<()> { Ok(()) } -#[cfg(not(test))] fn tinywasm() -> Result<()> { const TINYWASM: &[u8] = include_bytes!("./rust/out/tinywasm.wasm"); let module = Module::parse_bytes(&TINYWASM)?; @@ -45,7 +43,6 @@ fn tinywasm() -> Result<()> { Ok(()) } -#[cfg(not(test))] fn hello() -> Result<()> { const HELLO_WASM: &[u8] = include_bytes!("./rust/out/hello.wasm"); let module = Module::parse_bytes(&HELLO_WASM)?; @@ -68,7 +65,6 @@ fn hello() -> Result<()> { Ok(()) } -#[cfg(not(test))] fn fibonacci() -> Result<()> { const FIBONACCI_WASM: &[u8] = include_bytes!("./rust/out/fibonacci.wasm"); let module = Module::parse_bytes(&FIBONACCI_WASM)?; From ab6a39ae6a2e1ca92449863f1b0a1472d8ec8a70 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 15:27:04 +0100 Subject: [PATCH 04/27] ci: fix ci Signed-off-by: Henry Gressmann --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6f6fc10..91df17f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,6 +20,7 @@ jobs: run: | rustup update stable rustup update nightly + rustup target add wasm32-unknown-unknown sudo apt-get install -y binaryen - name: Build wasm @@ -46,6 +47,7 @@ jobs: - name: Install nightly Rust toolchain & Binaryen run: | rustup update nightly + rustup target add wasm32-unknown-unknown sudo apt-get install -y binaryen - name: Build wasm From 6fd283b06c4912f5d3408b2072cbc3c4e195a236 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 15:28:37 +0100 Subject: [PATCH 05/27] ci: fix ci Signed-off-by: Henry Gressmann --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 91df17f..9061eb8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,7 +21,7 @@ jobs: rustup update stable rustup update nightly rustup target add wasm32-unknown-unknown - sudo apt-get install -y binaryen + sudo apt-get install -y binaryen wabt - name: Build wasm run: ./examples/rust/build.sh @@ -48,7 +48,7 @@ jobs: run: | rustup update nightly rustup target add wasm32-unknown-unknown - sudo apt-get install -y binaryen + sudo apt-get install -y binaryen wabt - name: Build wasm run: ./examples/rust/build.sh From 8adce672578683dcc7ab62e5492e0f5dbc07a0b4 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 18:03:59 +0100 Subject: [PATCH 06/27] docs: string example Signed-off-by: Henry Gressmann --- README.md | 1 + crates/tinywasm/src/instance.rs | 4 +- crates/tinywasm/src/reference.rs | 128 +++++++++++++++++- .../src/runtime/interpreter/macros.rs | 2 +- crates/tinywasm/src/store.rs | 61 ++++++++- examples/rust/Cargo.toml | 4 + examples/rust/build.sh | 4 +- examples/rust/src/hello.rs | 30 ++-- examples/rust/src/print.rs | 18 +++ examples/rust/src/tinywasm.rs | 2 +- examples/wasm-rust.rs | 35 ++++- 11 files changed, 261 insertions(+), 28 deletions(-) create mode 100644 examples/rust/src/print.rs diff --git a/README.md b/README.md index 5055889..57728a4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ There are a couple of low-hanging fruits on the performance side, but they are n - [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) - **Fully implemented** - [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) - **Fully implemented** - [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented** +- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **_Partially implemented_** - [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_** - [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** (not tested yet) - [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_** (only 32-bit addressing is supported at the moment, but larger memories can be created) diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 5346ac3..35b54d6 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -199,14 +199,14 @@ impl ModuleInstance { pub fn memory(&self, store: &Store, addr: MemAddr) -> Result { let addr = self.resolve_mem_addr(addr); let mem = store.get_mem(addr as usize)?; - Ok(MemoryRef { _instance: mem.clone() }) + Ok(MemoryRef { instance: mem.clone() }) } /// Get the start function of the module /// /// Returns None if the module has no start function /// If no start function is specified, also checks for a _start function in the exports - /// (which is not part of the spec, but used by llvm) + /// (which is not part of the spec, but used by some compilers) /// /// See pub fn start_func(&self, store: &Store) -> Result> { diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index bc02338..fdaae18 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,17 +1,135 @@ -use core::cell::RefCell; +use core::{ + cell::{Ref, RefCell}, + ffi::CStr, +}; + +use crate::{GlobalInstance, MemoryInstance, Result}; +use alloc::{ + ffi::CString, + rc::Rc, + string::{String, ToString}, + vec::Vec, +}; +use tinywasm_types::WasmValue; -use crate::{GlobalInstance, MemoryInstance}; -use alloc::rc::Rc; // This module essentially contains the public APIs to interact with the data stored in the store /// A reference to a memory instance #[derive(Debug, Clone)] pub struct MemoryRef { - pub(crate) _instance: Rc>, + pub(crate) instance: Rc>, +} + +/// A borrowed reference to a memory instance +#[derive(Debug)] +pub struct BorrowedMemory<'a> { + pub(crate) instance: Ref<'a, MemoryInstance>, +} + +impl<'a> BorrowedMemory<'a> { + /// Load a slice of memory + pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.instance.load(offset, 0, len) + } + + /// Load a C-style string from memory + pub fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> { + let bytes = self.load(offset, len)?; + CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) + } + + /// Load a C-style string from memory, stopping at the first nul byte + pub fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> { + let bytes = self.load(offset, max_len)?; + CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) + } +} + +impl MemoryRef { + /// Borrow the memory instance + /// + /// This is useful for when you want to load only a reference to a slice of memory + /// without copying the data. The borrow should be dropped before any other memory + /// operations are performed. + pub fn borrow(&self) -> BorrowedMemory<'_> { + BorrowedMemory { instance: self.instance.borrow() } + } + + /// Load a slice of memory + pub fn load_vec(&self, offset: usize, len: usize) -> Result> { + self.instance.borrow().load(offset, 0, len).map(|x| x.to_vec()) + } + + /// Grow the memory by the given number of pages + pub fn grow(&self, delta_pages: i32) -> Option { + self.instance.borrow_mut().grow(delta_pages) + } + + /// Get the current size of the memory in pages + pub fn page_count(&self) -> usize { + self.instance.borrow().page_count() + } + + /// Copy a slice of memory to another place in memory + pub fn copy_within(&self, src: usize, dst: usize, len: usize) -> Result<()> { + self.instance.borrow_mut().copy_within(src, dst, len) + } + + /// Fill a slice of memory with a value + pub fn fill(&self, offset: usize, len: usize, val: u8) -> Result<()> { + self.instance.borrow_mut().fill(offset, len, val) + } + + /// Load a UTF-8 string from memory + pub fn load_string(&self, offset: usize, len: usize) -> Result { + let bytes = self.load_vec(offset, len)?; + Ok(String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string()))?) + } + + /// Load a C-style string from memory + pub fn load_cstring(&self, offset: usize, len: usize) -> Result { + Ok(CString::from(self.borrow().load_cstr(offset, len)?)) + } + + /// Load a C-style string from memory, stopping at the first nul byte + pub fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result { + Ok(CString::from(self.borrow().load_cstr_until_nul(offset, max_len)?)) + } + + /// Load a JavaScript-style utf-16 string from memory + pub fn load_js_string(&self, offset: usize, len: usize) -> Result { + let memref = self.borrow(); + let bytes = memref.load(offset, len)?; + let mut string = String::new(); + for i in 0..(len / 2) { + let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]); + string.push( + char::from_u32(c as u32).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?, + ); + } + Ok(string) + } + + /// Store a slice of memory + pub fn store(&self, offset: usize, len: usize, data: &[u8]) -> Result<()> { + self.instance.borrow_mut().store(offset, 0, data, len) + } } /// A reference to a global instance #[derive(Debug, Clone)] pub struct GlobalRef { - pub(crate) _instance: Rc>, + pub(crate) instance: Rc>, +} + +impl GlobalRef { + /// Get the value of the global + pub fn get(&self) -> WasmValue { + self.instance.borrow().get() + } + + /// Set the value of the global + pub fn set(&self, val: WasmValue) -> Result<()> { + self.instance.borrow_mut().set(val) + } } diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs index 909acb3..cc65812 100644 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ b/crates/tinywasm/src/runtime/interpreter/macros.rs @@ -63,7 +63,7 @@ macro_rules! mem_store { let val = val as $store_type; let val = val.to_le_bytes(); - mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val)?; + mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val, val.len())?; }}; } diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index d281b5c..1c0d260 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -306,7 +306,9 @@ impl Store { })?; // See comment for active element sections in the function above why we need to do this here - if let Err(Error::Trap(trap)) = mem.borrow_mut().store(offset as usize, 0, &data.data) { + if let Err(Error::Trap(trap)) = + mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len()) + { return Ok((data_addrs.into_boxed_slice(), Some(trap))); } @@ -607,8 +609,8 @@ impl MemoryInstance { } } - pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8]) -> Result<()> { - let end = addr.checked_add(data.len()).ok_or_else(|| { + pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> { + let end = addr.checked_add(len).ok_or_else(|| { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: data.len(), max: self.data.len() }) })?; @@ -646,9 +648,37 @@ impl MemoryInstance { self.page_count } - pub(crate) fn grow(&mut self, delta: i32) -> Option { + pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { + let end = addr + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?; + if end > self.data.len() { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + } + self.data[addr..end].fill(val); + Ok(()) + } + + pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { + let end = src + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?; + if end > self.data.len() || end < src { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() })); + } + let end = dst + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?; + if end > self.data.len() || end < dst { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() })); + } + self.data[dst..end].copy_within(src..end, len); + Ok(()) + } + + pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { let current_pages = self.page_count(); - let new_pages = current_pages as i64 + delta as i64; + let new_pages = current_pages as i64 + pages_delta as i64; if new_pages < 0 || new_pages > MAX_PAGES as i64 { return None; @@ -669,7 +699,7 @@ impl MemoryInstance { self.page_count = new_pages as usize; log::debug!("memory was {} pages", current_pages); - log::debug!("memory grown by {} pages", delta); + log::debug!("memory grown by {} pages", pages_delta); log::debug!("memory grown to {} pages", self.page_count); Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier")) @@ -690,6 +720,25 @@ impl GlobalInstance { pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { Self { ty, value, _owner: owner } } + + pub(crate) fn get(&self) -> WasmValue { + self.value.attach_type(self.ty.ty) + } + + pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { + if val.val_type() != self.ty.ty { + return Err(Error::Other(format!( + "global type mismatch: expected {:?}, got {:?}", + self.ty.ty, + val.val_type() + ))); + } + if !self.ty.mutable { + return Err(Error::Other("global is immutable".to_string())); + } + self.value = val.into(); + Ok(()) + } } /// A WebAssembly Element Instance diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index d557392..837f7f3 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -16,6 +16,10 @@ tinywasm={path="../../crates/tinywasm", features=["parser", "std"]} name="hello" path="src/hello.rs" +[[bin]] +name="print" +path="src/print.rs" + [[bin]] name="tinywasm" path="src/tinywasm.rs" diff --git a/examples/rust/build.sh b/examples/rust/build.sh index a8c587a..a13357d 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash cd "$(dirname "$0")" -bins=("hello" "tinywasm" "fibonacci") +bins=("hello" "fibonacci" "print" "tinywasm") exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" @@ -10,7 +10,7 @@ dest_dir="out" mkdir -p "$dest_dir" for bin in "${bins[@]}"; do - cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" + RUSTFLAGS="-C target-feature=+reference-types -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O --intrinsic-lowering -O diff --git a/examples/rust/src/hello.rs b/examples/rust/src/hello.rs index 34f3c7f..6bb6d97 100644 --- a/examples/rust/src/hello.rs +++ b/examples/rust/src/hello.rs @@ -1,18 +1,28 @@ -#![no_std] #![no_main] -#[cfg(not(test))] -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - core::arch::wasm32::unreachable() -} - #[link(wasm_import_module = "env")] extern "C" { - fn printi32(x: i32); + fn print_utf8(location: i32, len: i32); +} + +const ARG: &[u8] = &[0u8; 100]; + +#[no_mangle] +pub unsafe extern "C" fn arg_ptr() -> i32 { + ARG.as_ptr() as i32 } #[no_mangle] -pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) { - printi32(lh + rh); +pub unsafe extern "C" fn arg_size() -> i32 { + ARG.len() as i32 +} + +#[no_mangle] +pub unsafe extern "C" fn hello(len: i32) { + let arg = core::str::from_utf8(&ARG[0..len as usize]).unwrap(); + let res = format!("Hello, {}!", arg).as_bytes().to_vec(); + + let len = res.len() as i32; + let ptr = res.leak().as_ptr() as i32; + print_utf8(ptr, len); } diff --git a/examples/rust/src/print.rs b/examples/rust/src/print.rs new file mode 100644 index 0000000..34f3c7f --- /dev/null +++ b/examples/rust/src/print.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + core::arch::wasm32::unreachable() +} + +#[link(wasm_import_module = "env")] +extern "C" { + fn printi32(x: i32); +} + +#[no_mangle] +pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) { + printi32(lh + rh); +} diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 0fb9261..08c8135 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -12,7 +12,7 @@ pub extern "C" fn hello() { } fn run() -> tinywasm::Result<()> { - let module = tinywasm::Module::parse_bytes(include_bytes!("../out/hello.wasm"))?; + let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; let mut store = tinywasm::Store::default(); let mut imports = tinywasm::Imports::new(); diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 3b26863..6580bd5 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -7,12 +7,15 @@ fn main() -> Result<()> { println!("Usage: cargo run --example wasm-rust "); println!("Available examples:"); println!(" hello"); - println!(" tinywasm"); + println!(" printi32"); + println!(" fibonacci - calculate fibonacci(30)"); + println!(" tinywasm - run printi32 inside of tinywasm inside of itself"); return Ok(()); } match args[1].as_str() { "hello" => hello()?, + "printi32" => printi32()?, "fibonacci" => fibonacci()?, "tinywasm" => tinywasm()?, _ => {} @@ -48,6 +51,36 @@ fn hello() -> Result<()> { let module = Module::parse_bytes(&HELLO_WASM)?; let mut store = Store::default(); + let mut imports = Imports::new(); + imports.define( + "env", + "print_utf8", + Extern::typed_func(|mut ctx: FuncContext<'_>, args: (i32, i32)| { + let mem = ctx.memory("memory")?; + let ptr = args.1 as usize; + let len = args.0 as usize; + let string = mem.load_string(ptr, len)?; + println!("{}", string); + Ok(()) + }), + )?; + + let instance = module.instantiate(&mut store, Some(imports))?; + let arg_ptr = instance.exported_func::<(), i32>(&mut store, "arg_ptr")?.call(&mut store, ())?; + let arg = b"world"; + + instance.exported_memory(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; + let hello = instance.exported_func::(&mut store, "hello")?; + hello.call(&mut store, arg.len() as i32)?; + + Ok(()) +} + +fn printi32() -> Result<()> { + const HELLO_WASM: &[u8] = include_bytes!("./rust/out/print.wasm"); + let module = Module::parse_bytes(&HELLO_WASM)?; + let mut store = Store::default(); + let mut imports = Imports::new(); imports.define( "env", From 1f6ac248e106f77dee20af26567c61b2a35b75ba Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 19:14:54 +0100 Subject: [PATCH 07/27] fix: import param order Signed-off-by: Henry Gressmann --- crates/tinywasm/src/runtime/stack/value_stack.rs | 10 +--------- examples/rust/src/hello.rs | 4 ++-- examples/wasm-rust.rs | 6 +++--- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index bb4e398..f8f0951 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -1,6 +1,5 @@ use core::ops::Range; -use crate::log; use crate::{runtime::RawWasmValue, Error, Result}; use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; @@ -85,14 +84,7 @@ impl ValueStack { #[inline] pub(crate) fn pop_params(&mut self, types: &[ValType]) -> Result> { - log::info!("pop_params: types={:?}", types); - log::info!("stack={:?}", self.stack); - - let mut res = Vec::with_capacity(types.len()); - for ty in types.iter() { - res.push(self.pop()?.attach_type(*ty)); - } - + let res = self.pop_n_rev(types.len())?.iter().zip(types.iter()).map(|(v, ty)| v.attach_type(*ty)).collect(); Ok(res) } diff --git a/examples/rust/src/hello.rs b/examples/rust/src/hello.rs index 6bb6d97..c8e2ac3 100644 --- a/examples/rust/src/hello.rs +++ b/examples/rust/src/hello.rs @@ -2,7 +2,7 @@ #[link(wasm_import_module = "env")] extern "C" { - fn print_utf8(location: i32, len: i32); + fn print_utf8(location: i64, len: i32); } const ARG: &[u8] = &[0u8; 100]; @@ -23,6 +23,6 @@ pub unsafe extern "C" fn hello(len: i32) { let res = format!("Hello, {}!", arg).as_bytes().to_vec(); let len = res.len() as i32; - let ptr = res.leak().as_ptr() as i32; + let ptr = res.leak().as_ptr() as i64; print_utf8(ptr, len); } diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 6580bd5..468ab2e 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -55,10 +55,10 @@ fn hello() -> Result<()> { imports.define( "env", "print_utf8", - Extern::typed_func(|mut ctx: FuncContext<'_>, args: (i32, i32)| { + Extern::typed_func(|mut ctx: FuncContext<'_>, args: (i64, i32)| { let mem = ctx.memory("memory")?; - let ptr = args.1 as usize; - let len = args.0 as usize; + let ptr = args.0 as usize; + let len = args.1 as usize; let string = mem.load_string(ptr, len)?; println!("{}", string); Ok(()) From 893e72e93d1739ffa6b9d945a4f1e0ec525f782f Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 23:08:38 +0100 Subject: [PATCH 08/27] chore: benchmarking + major performance improvements (50%+ faster) Signed-off-by: Henry Gressmann --- .gitignore | 2 + Cargo.lock | 2991 +++++++++++++---- Cargo.toml | 15 + benches/selfhosted.rs | 38 + benches/util/mod.rs | 6 + crates/tinywasm/src/imports.rs | 7 + crates/tinywasm/src/lib.rs | 10 + .../src/runtime/interpreter/macros.rs | 38 +- .../tinywasm/src/runtime/interpreter/mod.rs | 50 +- .../tinywasm/src/runtime/stack/call_stack.rs | 1 - .../tinywasm/src/runtime/stack/value_stack.rs | 87 +- 11 files changed, 2516 insertions(+), 729 deletions(-) create mode 100644 benches/selfhosted.rs create mode 100644 benches/util/mod.rs diff --git a/.gitignore b/.gitignore index a41fff2..d2fbb51 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ examples/rust/out/* examples/rust/target examples/rust/Cargo.lock examples/wast/* +perf.* +flamegraph.svg diff --git a/Cargo.lock b/Cargo.lock index ff4d494..941a5e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli", + "gimli 0.28.1", ] [[package]] @@ -28,6 +28,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -52,6 +64,30 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "argh" version = "0.1.12" @@ -83,6 +119,23 @@ dependencies = [ "serde", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -104,6 +157,21 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -193,12 +261,19 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -222,6 +297,58 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "color-eyre" version = "0.6.2" @@ -314,1170 +441,2656 @@ dependencies = [ ] [[package]] -name = "cpufeatures" -version = "0.2.12" +name = "corosensei" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" dependencies = [ + "autocfg", + "cfg-if", "libc", + "scopeguard", + "windows-sys 0.33.0", ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "cpp_demangle" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ "cfg-if", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "cpufeatures" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ - "generic-array", - "typenum", + "libc", ] [[package]] -name = "digest" -version = "0.10.7" +name = "cranelift-bforest" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" dependencies = [ - "block-buffer", - "crypto-common", + "cranelift-entity 0.91.1", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "cranelift-bforest" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "d819feeda4c420a18f1e28236ca0ce1177b22bf7c8a44ddee92dfe40de15bcf0" dependencies = [ - "cfg-if", - "dirs-sys-next", + "cranelift-entity 0.104.0", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "cranelift-codegen" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" dependencies = [ - "libc", - "redox_users", - "winapi", + "arrayvec", + "bumpalo", + "cranelift-bforest 0.91.1", + "cranelift-codegen-meta 0.91.1", + "cranelift-codegen-shared 0.91.1", + "cranelift-egraph", + "cranelift-entity 0.91.1", + "cranelift-isle 0.91.1", + "gimli 0.26.2", + "log", + "regalloc2 0.5.1", + "smallvec", + "target-lexicon", ] [[package]] -name = "dlib" -version = "0.5.2" +name = "cranelift-codegen" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "e9b8d03d5bdbca7e5f72b0e0a0f69933ed1f09e24be6c075aa6fe3f802b0cc0c" dependencies = [ - "libloading", + "bumpalo", + "cranelift-bforest 0.104.0", + "cranelift-codegen-meta 0.104.0", + "cranelift-codegen-shared 0.104.0", + "cranelift-control", + "cranelift-entity 0.104.0", + "cranelift-isle 0.104.0", + "gimli 0.28.1", + "hashbrown 0.14.3", + "log", + "regalloc2 0.9.3", + "smallvec", + "target-lexicon", ] [[package]] -name = "dwrote" -version = "0.11.0" +name = "cranelift-codegen-meta" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", + "cranelift-codegen-shared 0.91.1", ] [[package]] -name = "env_logger" -version = "0.10.2" +name = "cranelift-codegen-meta" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "a3fd3664e38e51649b17dc30cfdd561273fe2f590dcd013fb75d9eabc6272dfb" dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", + "cranelift-codegen-shared 0.104.0", ] [[package]] -name = "errno" -version = "0.3.8" +name = "cranelift-codegen-shared" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" [[package]] -name = "eyre" -version = "0.6.11" +name = "cranelift-codegen-shared" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" -dependencies = [ - "indenter", - "once_cell", -] +checksum = "4b031ec5e605828975952622b5a77d49126f20ffe88d33719a0af66b23a0fc36" [[package]] -name = "fdeflate" -version = "0.3.4" +name = "cranelift-control" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "fada054d017cf2ed8f7ed2336e0517fc1b19e6825be1790de9eb00c94788362b" dependencies = [ - "simd-adler32", + "arbitrary", ] [[package]] -name = "flate2" -version = "1.0.28" +name = "cranelift-egraph" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" dependencies = [ - "crc32fast", - "miniz_oxide", + "cranelift-entity 0.91.1", + "fxhash", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "log", + "smallvec", ] [[package]] -name = "float-ord" -version = "0.2.0" +name = "cranelift-entity" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" +checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" [[package]] -name = "font-kit" -version = "0.11.0" +name = "cranelift-entity" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" +checksum = "177b6f94ae8de6348eb45bf977c79ab9e3c40fc3ac8cb7ed8109560ea39bee7d" dependencies = [ - "bitflags 1.3.2", - "byteorder", - "core-foundation", - "core-graphics", - "core-text", - "dirs-next", - "dwrote", - "float-ord", - "freetype", - "lazy_static", - "libc", + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.91.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" +dependencies = [ + "cranelift-codegen 0.91.1", "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", + "smallvec", + "target-lexicon", ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "cranelift-frontend" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "ebebd23a69a23e3ddea78e98ff3a2de222e88c8e045d81ef4a72f042e0d79dbd" dependencies = [ - "foreign-types-shared", + "cranelift-codegen 0.104.0", + "log", + "smallvec", + "target-lexicon", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "cranelift-isle" +version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" [[package]] -name = "freetype" -version = "0.7.1" +name = "cranelift-isle" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc8599a3078adf8edeb86c71e9f8fa7d88af5ca31e806a867756081f90f5d83" +checksum = "1571bfc14df8966d12c6121b5325026591a4b4009e22fea0fe3765ab7cd33b96" + +[[package]] +name = "cranelift-native" +version = "0.104.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a69c37e0c10b46fe5527f2397ac821046efbf5f7ec112c8b84df25712f465b" dependencies = [ - "freetype-sys", + "cranelift-codegen 0.104.0", "libc", + "target-lexicon", ] [[package]] -name = "freetype-sys" -version = "0.19.0" +name = "cranelift-wasm" +version = "0.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ee28c39a43d89fbed8b4798fb4ba56722cfd2b5af81f9326c27614ba88ecd5" +checksum = "9b3fef8bbceb8cb56d3f1778b0418d75c5cf12ec571a35fc01eb41abb0227a25" dependencies = [ - "cc", - "libc", - "pkg-config", + "cranelift-codegen 0.104.0", + "cranelift-entity 0.104.0", + "cranelift-frontend 0.104.0", + "itertools", + "log", + "smallvec", + "wasmparser 0.118.1", + "wasmtime-types", ] [[package]] -name = "funty" -version = "2.0.0" +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] [[package]] -name = "generic-array" -version = "0.14.7" +name = "criterion" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ - "typenum", - "version_check", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", ] [[package]] -name = "getrandom" -version = "0.2.12" +name = "criterion-plot" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ - "cfg-if", - "libc", - "wasi", + "cast", + "itertools", ] [[package]] -name = "gif" -version = "0.12.0" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "color_quant", - "weezl", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "gimli" -version = "0.28.1" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "globset" -version = "0.4.14" +name = "crossbeam-queue" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", + "crossbeam-utils", ] [[package]] -name = "hermit-abi" -version = "0.3.4" +name = "crossbeam-utils" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] -name = "humantime" -version = "2.1.0" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "iana-time-zone" -version = "0.1.59" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "generic-array", + "typenum", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "darling" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "cc", + "darling_core", + "darling_macro", ] [[package]] -name = "image" -version = "0.24.8" +name = "darling_core" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "jpeg-decoder", - "num-traits", - "png", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap-nostd" -version = "0.4.0" +name = "darling_macro" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.48", +] [[package]] -name = "is-terminal" -version = "0.4.10" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.52.0", + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "itoa" -version = "1.0.10" +name = "debugid" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] [[package]] -name = "jpeg-decoder" -version = "0.3.1" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "js-sys" -version = "0.3.67" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "wasm-bindgen", + "block-buffer", + "crypto-common", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "directories-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] [[package]] -name = "leb128" -version = "0.2.5" +name = "dirs-next" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] [[package]] -name = "libc" -version = "0.2.152" +name = "dirs-sys-next" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] [[package]] -name = "libloading" -version = "0.8.1" +name = "dlib" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "libloading", ] [[package]] -name = "libm" -version = "0.2.8" +name = "downcast-rs" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] -name = "libredox" -version = "0.0.1" +name = "dwrote" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" dependencies = [ - "bitflags 2.4.2", + "lazy_static", "libc", - "redox_syscall", + "winapi", + "wio", ] [[package]] -name = "linux-raw-sys" -version = "0.4.13" +name = "dynasm" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "log" -version = "0.4.20" +name = "dynasmrt" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +dependencies = [ + "byteorder", + "dynasm", + "memmap2 0.5.10", +] [[package]] -name = "memchr" -version = "2.7.1" +name = "either" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "miniz_oxide" -version = "0.7.1" +name = "enum-iterator" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" dependencies = [ - "adler", - "simd-adler32", + "enum-iterator-derive", ] [[package]] -name = "num-traits" -version = "0.2.17" +name = "enum-iterator-derive" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ - "autocfg", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "object" -version = "0.32.2" +name = "enumset" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" dependencies = [ - "memchr", + "enumset_derive", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "enumset_derive" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.48", +] [[package]] -name = "owo-colors" -version = "3.5.0" +name = "env_logger" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] [[package]] -name = "owo-colors" -version = "4.0.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "pathfinder_geometry" -version = "0.5.1" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "log", - "pathfinder_simd", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "pathfinder_simd" -version = "0.5.2" +name = "eyre" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444332826c70dc47be74a7c6a5fc44e23a7905ad6858d4162b658320455ef93" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" dependencies = [ - "rustc_version", + "indenter", + "once_cell", ] [[package]] -name = "pin-project-lite" -version = "0.2.13" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] -name = "pkg-config" -version = "0.3.29" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "plotters" -version = "0.3.5" +name = "fdeflate" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ - "chrono", - "font-kit", - "image", - "lazy_static", - "num-traits", - "pathfinder_geometry", - "plotters-backend", - "plotters-bitmap", - "plotters-svg", - "ttf-parser", - "wasm-bindgen", - "web-sys", + "simd-adler32", ] [[package]] -name = "plotters-backend" -version = "0.3.5" +name = "flate2" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] [[package]] -name = "plotters-bitmap" -version = "0.3.3" +name = "float-ord" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cebbe1f70205299abc69e8b295035bb52a6a70ee35474ad10011f0a4efb8543" +checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "font-kit" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" dependencies = [ - "gif", - "image", - "plotters-backend", + "bitflags 1.3.2", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", ] [[package]] -name = "plotters-svg" -version = "0.3.5" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "plotters-backend", + "foreign-types-shared", ] [[package]] -name = "png" -version = "0.17.11" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "percent-encoding", ] [[package]] -name = "pretty_env_logger" -version = "0.5.0" +name = "freetype" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +checksum = "efc8599a3078adf8edeb86c71e9f8fa7d88af5ca31e806a867756081f90f5d83" dependencies = [ - "env_logger", - "log", + "freetype-sys", + "libc", ] [[package]] -name = "proc-macro2" -version = "1.0.78" +name = "freetype-sys" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "66ee28c39a43d89fbed8b4798fb4ba56722cfd2b5af81f9326c27614ba88ecd5" dependencies = [ - "unicode-ident", + "cc", + "libc", + "pkg-config", ] [[package]] -name = "ptr_meta" -version = "0.1.4" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ - "ptr_meta_derive", + "byteorder", ] [[package]] -name = "ptr_meta_derive" -version = "0.1.4" +name = "fxprof-processed-profile" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "bitflags 2.4.2", + "debugid", + "fxhash", + "serde", + "serde_json", ] [[package]] -name = "quote" -version = "1.0.35" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "proc-macro2", + "typenum", + "version_check", ] [[package]] -name = "radium" -version = "0.7.0" +name = "getrandom" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "gif" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" dependencies = [ - "bitflags 1.3.2", + "color_quant", + "weezl", ] [[package]] -name = "redox_users" -version = "0.4.4" +name = "gimli" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "fallible-iterator 0.2.0", + "indexmap 1.9.3", + "stable_deref_trait", ] [[package]] -name = "regex" -version = "1.10.3" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator 0.3.0", + "indexmap 2.1.0", + "stable_deref_trait", +] + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", - "memchr", + "bstr", + "log", "regex-automata", "regex-syntax", ] [[package]] -name = "regex-automata" -version = "0.4.4" +name = "half" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "cfg-if", + "crunchy", ] [[package]] -name = "regex-syntax" -version = "0.8.2" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.7", +] [[package]] -name = "rend" -version = "0.4.1" +name = "hashbrown" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "bytecheck", + "ahash 0.8.7", ] [[package]] -name = "rkyv" -version = "0.7.43" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", + "ahash 0.8.7", ] [[package]] -name = "rkyv_derive" -version = "0.7.43" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "rust-embed" -version = "8.2.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", + "cc", ] [[package]] -name = "rust-embed-impl" -version = "8.2.0" +name = "id-arena" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.48", - "walkdir", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "rust-embed-utils" -version = "8.2.0" +name = "image" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "ittapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix", +] + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "crc32fast", + "hashbrown 0.14.3", + "indexmap 2.1.0", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "owo-colors" +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 = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0444332826c70dc47be74a7c6a5fc44e23a7905ad6858d4162b658320455ef93" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-bitmap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cebbe1f70205299abc69e8b295035bb52a6a70ee35474ad10011f0a4efb8543" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rayon" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regalloc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "region" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "rend" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust-embed" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.48", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +dependencies = [ + "globset", + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared-buffer" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" +dependencies = [ + "bytes", + "memmap2 0.6.2", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tinywasm" +version = "0.3.0" +dependencies = [ + "eyre", + "libm", + "log", + "owo-colors 4.0.0", + "plotters", + "pretty_env_logger", + "serde", + "serde_json", + "tinywasm-parser", + "tinywasm-types", + "wasm-testsuite", + "wast", +] + +[[package]] +name = "tinywasm-cli" +version = "0.3.0" +dependencies = [ + "argh", + "color-eyre", + "log", + "pretty_env_logger", + "tinywasm", + "wast", +] + +[[package]] +name = "tinywasm-parser" +version = "0.3.0" +dependencies = [ + "log", + "tinywasm-types", + "wasmparser-nostd", +] + +[[package]] +name = "tinywasm-root" +version = "0.0.0" +dependencies = [ + "color-eyre", + "criterion", + "tinywasm", + "wasmer", + "wasmi", + "wasmtime", +] + +[[package]] +name = "tinywasm-types" +version = "0.3.0" +dependencies = [ + "log", + "rkyv", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "ttf-parser" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" -dependencies = [ - "globset", - "sha2", - "walkdir", -] +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "rustc_version" -version = "0.4.0" +name = "unicode-normalization" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ - "semver", + "tinyvec", ] [[package]] -name = "rustix" -version = "0.38.30" +name = "unicode-width" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" -dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] -name = "ryu" -version = "1.0.16" +name = "unicode-xid" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] -name = "same-file" -version = "1.0.6" +name = "url" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ - "winapi-util", + "form_urlencoded", + "idna", + "percent-encoding", ] [[package]] -name = "seahash" -version = "4.1.0" +name = "uuid" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" [[package]] -name = "semver" -version = "1.0.21" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] -name = "serde" -version = "1.0.195" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" -dependencies = [ - "serde_derive", -] +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "serde_derive" -version = "1.0.195" +name = "walkdir" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", + "same-file", + "winapi-util", ] [[package]] -name = "serde_json" -version = "1.0.111" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" -dependencies = [ - "itoa", - "ryu", - "serde", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "sha2" -version = "0.10.8" +name = "wasm-bindgen" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "wasm-bindgen-macro", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "wasm-bindgen-backend" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ - "lazy_static", + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", ] [[package]] -name = "simd-adler32" -version = "0.3.7" +name = "wasm-bindgen-downcast" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "5dac026d43bcca6e7ce1c0956ba68f59edf6403e8e930a5d891be72c31a44340" +dependencies = [ + "js-sys", + "once_cell", + "wasm-bindgen", + "wasm-bindgen-downcast-macros", +] [[package]] -name = "simdutf8" -version = "0.1.4" +name = "wasm-bindgen-downcast-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "c5020cfa87c7cecefef118055d44e3c1fc122c7ec25701d528ee458a0b45f38f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "syn" -version = "1.0.109" +name = "wasm-bindgen-macro" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ - "proc-macro2", "quote", - "unicode-ident", + "wasm-bindgen-macro-support", ] [[package]] -name = "syn" -version = "2.0.48" +name = "wasm-bindgen-macro-support" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] -name = "tap" -version = "1.0.1" +name = "wasm-bindgen-shared" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] -name = "termcolor" -version = "1.4.1" +name = "wasm-encoder" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" dependencies = [ - "winapi-util", + "leb128", ] [[package]] -name = "thiserror" -version = "1.0.56" +name = "wasm-encoder" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" dependencies = [ - "thiserror-impl", + "leb128", ] [[package]] -name = "thiserror-impl" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +name = "wasm-testsuite" +version = "0.2.1" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", + "rust-embed", ] [[package]] -name = "thread_local" -version = "1.1.7" +name = "wasmer" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "0e626f958755a90a6552b9528f59b58a62ae288e6c17fcf40e99495bc33c60f0" dependencies = [ + "bytes", "cfg-if", - "once_cell", + "derivative", + "indexmap 1.9.3", + "js-sys", + "more-asserts", + "rustc-demangle", + "serde", + "serde-wasm-bindgen", + "shared-buffer", + "target-lexicon", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-downcast", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-compiler-singlepass", + "wasmer-derive", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "wasmer-compiler" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "848e1922694cf97f4df680a0534c9d72c836378b5eb2313c1708fe1a75b40044" dependencies = [ - "tinyvec_macros", + "backtrace", + "bytes", + "cfg-if", + "enum-iterator", + "enumset", + "lazy_static", + "leb128", + "memmap2 0.5.10", + "more-asserts", + "region", + "rkyv", + "self_cell", + "shared-buffer", + "smallvec", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser 0.95.0", + "winapi", ] [[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "wasmer-compiler-cranelift" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tinywasm" -version = "0.3.0" +checksum = "3d96bce6fad15a954edcfc2749b59e47ea7de524b6ef3df392035636491a40b4" dependencies = [ - "eyre", - "libm", - "log", - "owo-colors 4.0.0", - "plotters", - "pretty_env_logger", - "serde", - "serde_json", - "tinywasm-parser", - "tinywasm-types", - "wasm-testsuite", - "wast", + "cranelift-codegen 0.91.1", + "cranelift-entity 0.91.1", + "cranelift-frontend 0.91.1", + "gimli 0.26.2", + "more-asserts", + "rayon", + "smallvec", + "target-lexicon", + "tracing", + "wasmer-compiler", + "wasmer-types", ] [[package]] -name = "tinywasm-cli" -version = "0.3.0" +name = "wasmer-compiler-singlepass" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebaa865b40ffb3351b03dab9fe9930a5248c25daebd55b464b79b862d9b55ccd" dependencies = [ - "argh", - "color-eyre", - "log", - "pretty_env_logger", - "tinywasm", - "wast", + "byteorder", + "dynasm", + "dynasmrt", + "enumset", + "gimli 0.26.2", + "lazy_static", + "more-asserts", + "rayon", + "smallvec", + "wasmer-compiler", + "wasmer-types", ] [[package]] -name = "tinywasm-parser" -version = "0.3.0" +name = "wasmer-derive" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f08f80d166a9279671b7af7a09409c28ede2e0b4e3acabbf0e3cb22c8038ba7" dependencies = [ - "log", - "tinywasm-types", - "wasmparser-nostd", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "tinywasm-root" -version = "0.0.0" +name = "wasmer-types" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae2c892882f0b416783fb4310e5697f5c30587f6f9555f9d4f2be85ab39d5d3d" dependencies = [ - "color-eyre", - "tinywasm", + "bytecheck", + "enum-iterator", + "enumset", + "indexmap 1.9.3", + "more-asserts", + "rkyv", + "target-lexicon", + "thiserror", ] [[package]] -name = "tinywasm-types" -version = "0.3.0" +name = "wasmer-vm" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0a9a57b627fb39e5a491058d4365f099bc9b140031c000fded24a3306d9480" dependencies = [ - "log", - "rkyv", + "backtrace", + "cc", + "cfg-if", + "corosensei", + "crossbeam-queue", + "dashmap", + "derivative", + "enum-iterator", + "fnv", + "indexmap 1.9.3", + "lazy_static", + "libc", + "mach", + "memoffset 0.8.0", + "more-asserts", + "region", + "scopeguard", + "thiserror", + "wasmer-types", + "winapi", ] [[package]] -name = "tracing" -version = "0.1.40" +name = "wasmi" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" dependencies = [ - "pin-project-lite", - "tracing-core", + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", ] [[package]] -name = "tracing-core" -version = "0.1.32" +name = "wasmi_arena" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" dependencies = [ - "once_cell", - "valuable", + "downcast-rs", + "libm", + "num-traits", + "paste", ] [[package]] -name = "tracing-error" -version = "0.2.0" +name = "wasmparser" +version = "0.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a" dependencies = [ - "tracing", - "tracing-subscriber", + "indexmap 1.9.3", + "url", ] [[package]] -name = "tracing-subscriber" -version = "0.3.18" +name = "wasmparser" +version = "0.118.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9" dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", + "indexmap 2.1.0", + "semver", ] [[package]] -name = "ttf-parser" -version = "0.17.1" +name = "wasmparser-nostd" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +dependencies = [ + "indexmap-nostd", +] [[package]] -name = "typenum" -version = "1.17.0" +name = "wasmtime" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "910fabce77e660f0e0e41cfd5f69fc8bf020a025f059718846e918db7177f469" +dependencies = [ + "anyhow", + "async-trait", + "bincode", + "bumpalo", + "cfg-if", + "fxprof-processed-profile", + "indexmap 2.1.0", + "libc", + "log", + "object", + "once_cell", + "paste", + "rayon", + "serde", + "serde_derive", + "serde_json", + "target-lexicon", + "wasm-encoder 0.38.1", + "wasmparser 0.118.1", + "wasmtime-cache", + "wasmtime-component-macro", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "windows-sys 0.52.0", +] [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "wasmtime-asm-macros" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "37288142e9b4a61655a3bcbdc7316c2e4bb9e776b10ce3dd758f8186b4469572" +dependencies = [ + "cfg-if", +] [[package]] -name = "unicode-width" -version = "0.1.11" +name = "wasmtime-cache" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "45cbd74a636f09d2108f9405c79857f061e19323e4abeed22e837cfe7b08a22b" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "log", + "rustix", + "serde", + "serde_derive", + "sha2", + "toml", + "windows-sys 0.52.0", + "zstd", +] [[package]] -name = "uuid" -version = "1.7.0" +name = "wasmtime-component-macro" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "ad63de18eb42e586386b6091f787c82707cbd5ac5e9343216dba1976190cd03a" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser", +] [[package]] -name = "valuable" -version = "0.1.0" +name = "wasmtime-component-util" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "7e0a160c0c44369aa4bee6d311a8e4366943bab1651040cc8b0fcec2c9eb8906" [[package]] -name = "version_check" -version = "0.9.4" +name = "wasmtime-cranelift" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "3734cc01b7cd37bc62fdbcd9529ca9547440052d4b3886cfdec3b8081a5d3647" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen 0.104.0", + "cranelift-control", + "cranelift-entity 0.104.0", + "cranelift-frontend 0.104.0", + "cranelift-native", + "cranelift-wasm", + "gimli 0.28.1", + "log", + "object", + "target-lexicon", + "thiserror", + "wasmparser 0.118.1", + "wasmtime-cranelift-shared", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] [[package]] -name = "walkdir" -version = "2.4.0" +name = "wasmtime-cranelift-shared" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "e0eb33cd30c47844aa228d4d0030587e65c1108343f311fe9f7248b5bd9cb65c" dependencies = [ - "same-file", - "winapi-util", + "anyhow", + "cranelift-codegen 0.104.0", + "cranelift-control", + "cranelift-native", + "gimli 0.28.1", + "object", + "target-lexicon", + "wasmtime-environ", ] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasmtime-environ" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9a3a056b041fdea604f0972e2fae97958e7748d629a55180228348baefdfc217" +dependencies = [ + "anyhow", + "cranelift-entity 0.104.0", + "gimli 0.28.1", + "indexmap 2.1.0", + "log", + "object", + "serde", + "serde_derive", + "target-lexicon", + "thiserror", + "wasmparser 0.118.1", + "wasmtime-types", +] [[package]] -name = "wasm-bindgen" -version = "0.2.90" +name = "wasmtime-fiber" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "43987d0977c07f15c3608c2f255870c127ffd19e35eeedb1ac1dccedf9932a42" dependencies = [ + "anyhow", + "cc", "cfg-if", - "wasm-bindgen-macro", + "rustix", + "wasmtime-asm-macros", + "wasmtime-versioned-export-macros", + "windows-sys 0.52.0", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.90" +name = "wasmtime-jit" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "9b3e48395ac672b386ed588d97a9612aa13a345008f26466f0dfb2a91628aa9f" dependencies = [ - "bumpalo", + "addr2line", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli 0.28.1", + "ittapi", "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.48", - "wasm-bindgen-shared", + "object", + "rustc-demangle", + "rustix", + "serde", + "serde_derive", + "target-lexicon", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", + "wasmtime-runtime", + "windows-sys 0.52.0", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.90" +name = "wasmtime-jit-debug" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "dd21fd0f5ca68681d3d5b636eea00f182d0f9d764144469e9257fd7e3f55ae0e" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "object", + "once_cell", + "rustix", + "wasmtime-versioned-export-macros", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.90" +name = "wasmtime-jit-icache-coherence" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "bdc26415bb89e9ccd3bdc498fef63aabf665c4c0dd710c107691deb9694955da" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "cfg-if", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.90" +name = "wasmtime-runtime" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "0abddaf17912aabaf39be0802d5eba9a002e956e902d1ebd438a2fe1c88769a2" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 2.1.0", + "libc", + "log", + "mach", + "memfd", + "memoffset 0.9.0", + "paste", + "psm", + "rustix", + "sptr", + "wasm-encoder 0.38.1", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "wasmtime-versioned-export-macros", + "wasmtime-wmemcheck", + "windows-sys 0.52.0", +] [[package]] -name = "wasm-encoder" -version = "0.39.0" +name = "wasmtime-types" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" +checksum = "b35a95cdc1433729085beab42c0a5c742b431f25b17c40d7718e46df63d5ffc7" dependencies = [ - "leb128", + "cranelift-entity 0.104.0", + "serde", + "serde_derive", + "thiserror", + "wasmparser 0.118.1", ] [[package]] -name = "wasm-testsuite" -version = "0.2.1" +name = "wasmtime-versioned-export-macros" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad322733fe67e45743784d8b1df452bcb54f581572a4f1a646a4332deecbcc2" dependencies = [ - "rust-embed", + "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] -name = "wasmparser-nostd" -version = "0.100.1" +name = "wasmtime-wit-bindgen" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +checksum = "41e5675998fdc74495afdd90ad2bd221206a258075b23048af0535a969b07893" dependencies = [ - "indexmap-nostd", + "anyhow", + "heck", + "indexmap 2.1.0", + "wit-parser", ] +[[package]] +name = "wasmtime-wmemcheck" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b20a19e10d8cb50b45412fb21192982b7ce85c0122dc33bb71f1813e25dc6e52" + [[package]] name = "wast" version = "70.0.0" @@ -1487,7 +3100,16 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.39.0", +] + +[[package]] +name = "wat" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0dce8cdc288c717cf01e461a1e451a7b8445d53451123536ba576e423a101a" +dependencies = [ + "wast", ] [[package]] @@ -1546,6 +3168,19 @@ dependencies = [ "windows-targets 0.52.0", ] +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1606,6 +3241,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1618,6 +3259,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1630,6 +3277,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1642,6 +3295,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1666,6 +3325,12 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1687,6 +3352,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "wit-parser" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.1.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", +] + [[package]] name = "wyz" version = "0.5.1" @@ -1707,3 +3389,52 @@ dependencies = [ "once_cell", "pkg-config", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 13a9397..20674d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,21 @@ edition="2021" name="wasm-rust" test=false +[[bench]] +name="selfhosted" +harness=false + +[profile.bench] +opt-level=3 +lto="thin" +codegen-units=1 +debug=true + [dev-dependencies] color-eyre="0.6" +criterion={version="0.5", features=["html_reports"]} + tinywasm={path="crates/tinywasm"} +wasmi={version="0.31", features=["std"]} +wasmer={version="4.2", features=["cranelift", "singlepass"]} +wasmtime={version="17.0", features=["cranelift"]} diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs new file mode 100644 index 0000000..700e3f6 --- /dev/null +++ b/benches/selfhosted.rs @@ -0,0 +1,38 @@ +mod util; +use criterion::{criterion_group, criterion_main, Criterion}; +use tinywasm::types::TinyWasmModule; +use util::tinywasm_module; + +fn run_tinywasm(module: TinyWasmModule) { + use tinywasm::*; + let module = Module::from(module); + let mut store = Store::default(); + let mut imports = Imports::default(); + imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); + let instance = module.instantiate(&mut store, Some(imports)).expect("instantiate"); + let hello = instance.exported_func::<(), ()>(&mut store, "hello").expect("exported_func"); + hello.call(&mut store, ()).expect("call"); +} + +fn run_wasmi() { + use wasmi::*; + let engine = Engine::default(); + let module = wasmi::Module::new(&engine, TINYWASM).expect("wasmi::Module::new"); + let mut store = Store::new(&engine, ()); + let mut linker = >::new(&engine); + linker.define("env", "printi32", Func::wrap(&mut store, |_: Caller<'_, ()>, _: i32| {})).expect("define"); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let hello = instance.get_typed_func::<(), ()>(&mut store, "hello").expect("get_typed_func"); + hello.call(&mut store, ()).expect("call"); +} + +const TINYWASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + let module = tinywasm_module(TINYWASM); + + c.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); + c.bench_function("wasmi", |b| b.iter(|| run_wasmi())); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/benches/util/mod.rs b/benches/util/mod.rs new file mode 100644 index 0000000..7baddb7 --- /dev/null +++ b/benches/util/mod.rs @@ -0,0 +1,6 @@ +use tinywasm::{self, parser::Parser, types::TinyWasmModule}; + +pub fn tinywasm_module(wasm: &[u8]) -> TinyWasmModule { + let parser = Parser::new(); + parser.parse_module_bytes(wasm).expect("parse_module_bytes") +} diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 9c5086a..294e547 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -255,6 +255,13 @@ impl Imports { Imports { values: BTreeMap::new(), modules: BTreeMap::new() } } + /// Merge two import sets + pub fn merge(mut self, other: Self) -> Self { + self.values.extend(other.values); + self.modules.extend(other.modules); + self + } + /// Link a module /// /// This will automatically link all imported values on instantiation diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index f2951b6..bc64ac4 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -122,3 +122,13 @@ pub mod parser { pub mod types { pub use tinywasm_types::*; } + +#[cold] +pub(crate) fn cold() {} + +pub(crate) fn unlikely(b: bool) -> bool { + if b { + cold(); + } + b +} diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs index cc65812..eba866c 100644 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ b/crates/tinywasm/src/runtime/interpreter/macros.rs @@ -146,13 +146,8 @@ macro_rules! comp { }}; ($op:tt, $intermediate:ty, $to:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $intermediate = a.into(); - let b: $intermediate = b.into(); - - // Cast to unsigned type before comparison - let a = a as $to; - let b = b as $to; + let b = $stack.values.pop_t::<$intermediate>()? as $to; + let a = $stack.values.pop_t::<$intermediate>()? as $to; $stack.values.push(((a $op b) as i32).into()); }}; } @@ -160,7 +155,7 @@ macro_rules! comp { /// Compare a value on the stack to zero macro_rules! comp_zero { ($op:tt, $ty:ty, $stack:ident) => {{ - let a: $ty = $stack.values.pop()?.into(); + let a = $stack.values.pop_t::<$ty>()?; $stack.values.push(((a $op 0) as i32).into()); }}; } @@ -173,20 +168,14 @@ macro_rules! arithmetic { // also allow operators such as +, - ($op:tt, $ty:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); + let b: $ty = $stack.values.pop_t()?; + let a: $ty = $stack.values.pop_t()?; $stack.values.push((a $op b).into()); }}; ($op:ident, $intermediate:ty, $to:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $to = a.into(); - let b: $to = b.into(); - - let a = a as $intermediate; - let b = b as $intermediate; - + let b = $stack.values.pop_t::<$to>()? as $intermediate; + let a = $stack.values.pop_t::<$to>()? as $intermediate; let result = a.$op(b); $stack.values.push((result as $to).into()); }}; @@ -215,19 +204,14 @@ macro_rules! checked_int_arithmetic { }}; ($op:ident, $from:ty, $to:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $from = a.into(); - let b: $from = b.into(); - - let a_casted: $to = a as $to; - let b_casted: $to = b as $to; + let b = $stack.values.pop_t::<$from>()? as $to; + let a = $stack.values.pop_t::<$from>()? as $to; - if b_casted == 0 { + if b == 0 { return Err(Error::Trap(crate::Trap::DivisionByZero)); } - let result = a_casted.$op(b_casted).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow))?; - + let result = a.$op(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow))?; // Cast back to original type if different $stack.values.push((result as $from).into()); }}; diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index db013a0..ad261db 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -1,7 +1,6 @@ use super::{InterpreterRuntime, Stack}; -use crate::log; +use crate::{cold, log, unlikely}; use crate::{ - log::debug, runtime::{BlockType, CallFrame, LabelArgs, LabelFrame}, Error, FuncContext, ModuleInstance, Result, Store, Trap, }; @@ -23,6 +22,7 @@ use macros::*; use traits::*; impl InterpreterRuntime { + #[inline(always)] // a small 2-3% performance improvement in some cases pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack) -> Result<()> { // The current call frame, gets updated inside of exec_one let mut cf = stack.call_stack.pop()?; @@ -80,10 +80,10 @@ impl InterpreterRuntime { } } - debug!("end of exec"); - debug!("stack: {:?}", stack.values); - debug!("insts: {:?}", instrs); - debug!("instr_ptr: {}", cf.instr_ptr); + log::debug!("end of exec"); + log::debug!("stack: {:?}", stack.values); + log::debug!("insts: {:?}", instrs); + log::debug!("instr_ptr: {}", cf.instr_ptr); Err(Error::FuncDidNotReturn) } } @@ -115,7 +115,7 @@ macro_rules! break_to { /// Run a single step of the interpreter /// A seperate function is used so later, we can more easily implement /// a step-by-step debugger (using generators once they're stable?) -#[inline] +#[inline(always)] // this improves performance by more than 20% in some cases fn exec_one( cf: &mut CallFrame, instr: &Instruction, @@ -124,12 +124,13 @@ fn exec_one( store: &mut Store, module: &ModuleInstance, ) -> Result { - debug!("ptr: {} instr: {:?}", cf.instr_ptr, instr); - use tinywasm_types::Instruction::*; match instr { 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 + Unreachable => { + cold(); + 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( @@ -162,7 +163,7 @@ fn exec_one( } }; - let params = stack.values.pop_n_rev(ty.params.len())?; + let params = stack.values.pop_n_rev(ty.params.len())?.collect::>(); let call_frame = CallFrame::new_raw(func_inst, ¶ms, locals); // push the call frame @@ -191,15 +192,9 @@ fn exec_one( let func_inst = store.get_func(func_ref as usize)?.clone(); let func_ty = func_inst.func.ty(); - - log::info!("type_addr: {}", type_addr); - log::info!("types: {:?}", module.func_tys()); let call_ty = module.func_ty(*type_addr); - log::info!("call_indirect: current fn owner: {:?}", module.id()); - log::info!("call_indirect: func owner: {:?}", func_inst.owner); - - if func_ty != call_ty { + if unlikely(func_ty != call_ty) { log::error!("indirect call type mismatch: {:?} != {:?}", func_ty, call_ty); return Err( Trap::IndirectCallTypeMismatch { actual: func_ty.clone(), expected: call_ty.clone() }.into() @@ -217,7 +212,7 @@ fn exec_one( } }; - let params = stack.values.pop_n_rev(func_ty.params.len())?; + let params = stack.values.pop_n_rev(func_ty.params.len())?.collect::>(); let call_frame = CallFrame::new_raw(func_inst, ¶ms, locals); // push the call frame @@ -232,7 +227,6 @@ fn exec_one( If(args, else_offset, end_offset) => { // truthy value is on the top of the stack, so enter the then block if stack.values.pop_t::()? != 0 { - log::trace!("entering then"); cf.enter_label( LabelFrame { instr_ptr: cf.instr_ptr, @@ -248,7 +242,6 @@ fn exec_one( // falsy value is on the top of the stack if let Some(else_offset) = else_offset { - log::debug!("entering else at {}", cf.instr_ptr + *else_offset); cf.enter_label( LabelFrame { instr_ptr: cf.instr_ptr + *else_offset, @@ -266,7 +259,6 @@ fn exec_one( } Loop(args, end_offset) => { - // let params = stack.values.pop_block_params(*args, &module)?; cf.enter_label( LabelFrame { instr_ptr: cf.instr_ptr, @@ -297,11 +289,14 @@ fn exec_one( .iter() .map(|i| match i { BrLabel(l) => Ok(*l), - _ => panic!("Expected BrLabel, this should have been validated by the parser"), + _ => { + cold(); + panic!("Expected BrLabel, this should have been validated by the parser") + } }) .collect::>>()?; - if instr.len() != *len { + if unlikely(instr.len() != *len) { panic!( "Expected {} BrLabel instructions, got {}, this should have been validated by the parser", len, @@ -341,6 +336,7 @@ fn exec_one( // We're essentially using else as a EndBlockFrame instruction for if blocks Else(end_offset) => { let Some(block) = cf.labels.pop() else { + cold(); panic!("else: no label to end, this should have been validated by the parser"); }; @@ -352,6 +348,7 @@ fn exec_one( EndBlockFrame => { // remove the label from the label stack let Some(block) = cf.labels.pop() else { + cold(); panic!("end: no label to end, this should have been validated by the parser"); }; stack.values.truncate_keep(block.stack_ptr, block.args.results) @@ -379,7 +376,8 @@ fn exec_one( MemorySize(addr, byte) => { if *byte != 0 { - unimplemented!("memory.size with byte != 0"); + cold(); + return Err(Error::UnsupportedFeature("memory.size with byte != 0".to_string())); } let mem_idx = module.resolve_mem_addr(*addr); @@ -389,6 +387,7 @@ fn exec_one( MemoryGrow(addr, byte) => { if *byte != 0 { + cold(); return Err(Error::UnsupportedFeature("memory.grow with byte != 0".to_string())); } @@ -633,6 +632,7 @@ fn exec_one( I64TruncSatF64U => arithmetic_single!(trunc, f64, u64, stack), i => { + cold(); log::error!("unimplemented instruction: {:?}", i); return Err(Error::UnsupportedFeature(alloc::format!("unimplemented instruction: {:?}", i))); } diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index 20de728..b19e332 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -78,7 +78,6 @@ impl CallFrame { /// Break to a block at the given index (relative to the current frame) /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) - #[inline] pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Option<()> { log::debug!("break_to_relative: {}", break_to_relative); let break_to = self.labels.get_relative_to_top(break_to_relative as usize)?; diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index f8f0951..d36373b 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -1,6 +1,6 @@ use core::ops::Range; -use crate::{runtime::RawWasmValue, Error, Result}; +use crate::{cold, runtime::RawWasmValue, unlikely, Error, Result}; use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; @@ -10,19 +10,17 @@ pub(crate) const STACK_SIZE: usize = 1024; #[derive(Debug)] pub(crate) struct ValueStack { stack: Vec, - top: usize, } impl Default for ValueStack { fn default() -> Self { - Self { stack: Vec::with_capacity(STACK_SIZE), top: 0 } + Self { stack: Vec::with_capacity(STACK_SIZE) } } } impl ValueStack { #[inline] pub(crate) fn extend_from_within(&mut self, range: Range) { - self.top += range.len(); self.stack.extend_from_within(range); } @@ -32,98 +30,95 @@ impl ValueStack { return; } - self.top += values.len(); self.stack.extend(values.iter().map(|v| RawWasmValue::from(*v))); } #[inline] pub(crate) fn len(&self) -> usize { - assert!(self.top <= self.stack.len()); - self.top + self.stack.len() } pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { let total_to_keep = n + end_keep; - assert!(self.top >= total_to_keep, "Total to keep should be less than or equal to self.top"); + let len = self.stack.len(); + assert!(len >= total_to_keep, "Total to keep should be less than or equal to self.top"); - let current_size = self.stack.len(); - if current_size <= total_to_keep { + if len <= total_to_keep { return; // No need to truncate if the current size is already less than or equal to total_to_keep } - let items_to_remove = current_size - total_to_keep; - let remove_start_index = self.top - items_to_remove - end_keep; - let remove_end_index = self.top - end_keep; - + let items_to_remove = len - total_to_keep; + let remove_start_index = len - items_to_remove - end_keep; + let remove_end_index = len - end_keep; self.stack.drain(remove_start_index..remove_end_index); - self.top = total_to_keep; // Update top to reflect the new size } #[inline] pub(crate) fn push(&mut self, value: RawWasmValue) { - self.top += 1; self.stack.push(value); } #[inline] pub(crate) fn last(&self) -> Result<&RawWasmValue> { - self.stack.last().ok_or(Error::StackUnderflow) + match self.stack.last() { + Some(v) => Ok(v), + None => { + cold(); + Err(Error::StackUnderflow) + } + } } #[inline] pub(crate) fn pop_t>(&mut self) -> Result { - self.top -= 1; - Ok(self.stack.pop().ok_or(Error::StackUnderflow)?.into()) + match self.stack.pop() { + Some(v) => Ok(v.into()), + None => { + cold(); + Err(Error::StackUnderflow) + } + } } #[inline] pub(crate) fn pop(&mut self) -> Result { - self.top -= 1; - self.stack.pop().ok_or(Error::StackUnderflow) + match self.stack.pop() { + Some(v) => Ok(v), + None => { + cold(); + Err(Error::StackUnderflow) + } + } } #[inline] pub(crate) fn pop_params(&mut self, types: &[ValType]) -> Result> { - let res = self.pop_n_rev(types.len())?.iter().zip(types.iter()).map(|(v, ty)| v.attach_type(*ty)).collect(); + let res = self.pop_n_rev(types.len())?.zip(types.iter()).map(|(v, ty)| v.attach_type(*ty)).collect(); Ok(res) } pub(crate) fn break_to(&mut self, new_stack_size: usize, result_count: usize) { - assert!(self.top >= result_count); - self.stack.copy_within((self.top - result_count)..self.top, new_stack_size); - self.top = new_stack_size + result_count; - self.stack.truncate(self.top); + let len = self.stack.len(); + self.stack.copy_within((len - result_count)..len, new_stack_size); + self.stack.truncate(new_stack_size + result_count); } #[inline] pub(crate) fn last_n(&self, n: usize) -> Result<&[RawWasmValue]> { - if self.top < n { - return Err(Error::StackUnderflow); - } - Ok(&self.stack[self.top - n..self.top]) - } - - #[inline] - pub(crate) fn pop_n_rev(&mut self, n: usize) -> Result> { - if self.top < n { + let len = self.stack.len(); + if unlikely(len < n) { return Err(Error::StackUnderflow); } - self.top -= n; - let res = self.stack.drain(self.top..).collect::>(); - Ok(res) + Ok(&self.stack[len - n..len]) } #[inline] - pub(crate) fn pop_n_const(&mut self) -> Result<[RawWasmValue; N]> { - if self.top < N { + pub(crate) fn pop_n_rev(&mut self, n: usize) -> Result> { + let len = self.stack.len(); + if unlikely(len < n) { return Err(Error::StackUnderflow); } - self.top -= N; - let mut res = [RawWasmValue::default(); N]; - for i in res.iter_mut().rev() { - *i = self.stack.pop().ok_or(Error::InvalidStore)?; - } - + let res = self.stack.drain((len - n)..); Ok(res) } } From 55b69039c954b49cb98f90c30d58187b9741f545 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Fri, 26 Jan 2024 23:34:30 +0100 Subject: [PATCH 09/27] perf: improve hot interpreter loop Signed-off-by: Henry Gressmann --- benches/selfhosted.rs | 2 +- .../tinywasm/src/runtime/interpreter/mod.rs | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index 700e3f6..78ea48b 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -9,7 +9,7 @@ fn run_tinywasm(module: TinyWasmModule) { let mut store = Store::default(); let mut imports = Imports::default(); imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); - let instance = module.instantiate(&mut store, Some(imports)).expect("instantiate"); + let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); let hello = instance.exported_func::<(), ()>(&mut store, "hello").expect("exported_func"); hello.call(&mut store, ()).expect("call"); } diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index ad261db..80fb3f9 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -4,6 +4,7 @@ use crate::{ runtime::{BlockType, CallFrame, LabelArgs, LabelFrame}, Error, FuncContext, ModuleInstance, Result, Store, Trap, }; +use alloc::format; use alloc::{string::ToString, vec::Vec}; use core::ops::{BitAnd, BitOr, BitXor, Neg}; use tinywasm_types::{ElementKind, Instruction, ValType}; @@ -32,28 +33,33 @@ impl InterpreterRuntime { // The function to execute, gets updated from ExecResult::Call let mut instrs = &wasm_func.instructions; + let mut instr_count = instrs.len(); let mut current_module = store.get_module_instance(func_inst.owner).unwrap().clone(); - while let Some(instr) = instrs.get(cf.instr_ptr) { + loop { + if unlikely(cf.instr_ptr >= instr_count) { + cold(); + log::error!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instr_count); + return Err(Error::Other(format!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instr_count))); + } + let instr = &instrs[cf.instr_ptr]; + match exec_one(&mut cf, instr, instrs, stack, store, ¤t_module)? { // Continue execution at the new top of the call stack ExecResult::Call => { cf = stack.call_stack.pop()?; func_inst = cf.func_instance.clone(); - wasm_func = func_inst.assert_wasm().expect("exec expected wasm function"); + wasm_func = + func_inst.assert_wasm().map_err(|_| Error::Other("call expected wasm function".to_string()))?; instrs = &wasm_func.instructions; + instr_count = instrs.len(); if cf.func_instance.owner != current_module.id() { current_module.swap( store .get_module_instance(cf.func_instance.owner) - .unwrap_or_else(|| { - panic!( - "exec expected module instance {} to exist for function", - cf.func_instance.owner - ) - }) + .ok_or_else(|| Error::Other("call expected module instance".to_string()))? .clone(), ); } @@ -79,12 +85,6 @@ impl InterpreterRuntime { } } } - - log::debug!("end of exec"); - log::debug!("stack: {:?}", stack.values); - log::debug!("insts: {:?}", instrs); - log::debug!("instr_ptr: {}", cf.instr_ptr); - Err(Error::FuncDidNotReturn) } } From a89f75ce42f83de09bad72c1baf697a007751b1b Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 27 Jan 2024 00:43:32 +0100 Subject: [PATCH 10/27] pref: use Rc's for Module Instances Signed-off-by: Henry Gressmann --- crates/tinywasm/src/func.rs | 16 ++++++++-------- crates/tinywasm/src/imports.rs | 22 +++++++++------------- crates/tinywasm/src/instance.rs | 10 +++++----- crates/tinywasm/src/store.rs | 2 +- crates/tinywasm/tests/testsuite/run.rs | 2 +- crates/tinywasm/tests/testsuite/util.rs | 4 ++-- crates/types/src/lib.rs | 2 +- 7 files changed, 27 insertions(+), 31 deletions(-) diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 8433236..9c9938b 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -100,7 +100,7 @@ pub trait IntoWasmValueTuple { } pub trait FromWasmValueTuple { - fn from_wasm_value_tuple(values: Vec) -> Result + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result where Self: Sized; } @@ -115,7 +115,7 @@ impl FuncHandleTyped { let result = self.func.call(store, &wasm_values)?; // Convert the Vec back to R - R::from_wasm_value_tuple(result) + R::from_wasm_value_tuple(&result) } } macro_rules! impl_into_wasm_value_tuple { @@ -164,14 +164,14 @@ macro_rules! impl_from_wasm_value_tuple { where $($T: TryFrom),* { - fn from_wasm_value_tuple(values: Vec) -> Result { + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { #[allow(unused_variables, unused_mut)] - let mut iter = values.into_iter(); + let mut iter = values.iter(); Ok(( $( $T::try_from( - iter.next() + *iter.next() .ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))? ) .map_err(|e| Error::Other(format!("FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", e, @@ -186,10 +186,10 @@ macro_rules! impl_from_wasm_value_tuple { macro_rules! impl_from_wasm_value_tuple_single { ($T:ident) => { impl FromWasmValueTuple for $T { - fn from_wasm_value_tuple(values: Vec) -> Result { + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { #[allow(unused_variables, unused_mut)] - let mut iter = values.into_iter(); - $T::try_from(iter.next().ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?) + let mut iter = values.iter(); + $T::try_from(*iter.next().ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?) .map_err(|e| { Error::Other(format!( "FromWasmValueTupleSingle: Could not convert WasmValue to expected type: {:?}", diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 294e547..28ea0ec 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -8,8 +8,8 @@ use crate::{ }; use alloc::{ collections::BTreeMap, + rc::Rc, string::{String, ToString}, - sync::Arc, vec::Vec, }; use tinywasm_types::*; @@ -52,8 +52,7 @@ impl HostFunction { } } -pub(crate) type HostFuncInner = - Arc, &[WasmValue]) -> Result> + 'static + Send + Sync>; +pub(crate) type HostFuncInner = Rc, &[WasmValue]) -> Result>>; /// The context of a host-function call #[derive(Debug)] @@ -139,31 +138,28 @@ impl Extern { /// Create a new function import pub fn func( ty: &tinywasm_types::FuncType, - func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static + Send + Sync, + func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static, ) -> Self { - let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| { - let args = args.to_vec(); - func(ctx, &args) - }; - - Self::Function(Function::Host(HostFunction { func: Arc::new(inner_func), ty: ty.clone() })) + Self::Function(Function::Host(HostFunction { func: Rc::new(func), ty: ty.clone() })) } /// Create a new typed function import - pub fn typed_func(func: impl Fn(FuncContext<'_>, P) -> Result + 'static + Send + Sync) -> Self + // TODO: currently, this is slower than `Extern::func` because of the type conversions. + // we should be able to optimize this and make it even faster than `Extern::func`. + pub fn typed_func(func: impl Fn(FuncContext<'_>, P) -> Result + 'static) -> Self where P: FromWasmValueTuple + ValTypesFromTuple, R: IntoWasmValueTuple + ValTypesFromTuple + Debug, { let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { - let args = P::from_wasm_value_tuple(args.to_vec())?; + let args = P::from_wasm_value_tuple(args)?; let result = func(ctx, args)?; Ok(result.into_wasm_value_tuple()) }; let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; - Self::Function(Function::Host(HostFunction { func: Arc::new(inner_func), ty })) + Self::Function(Function::Host(HostFunction { func: Rc::new(inner_func), ty })) } pub(crate) fn kind(&self) -> ExternalKind { diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 35b54d6..b79f551 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,4 +1,4 @@ -use alloc::{boxed::Box, format, string::ToString, sync::Arc}; +use alloc::{boxed::Box, format, rc::Rc, string::ToString}; use tinywasm_types::*; use crate::{ @@ -8,11 +8,11 @@ use crate::{ /// An instanciated WebAssembly module /// -/// Backed by an Arc, so cloning is cheap +/// Backed by an Rc, so cloning is cheap /// /// See #[derive(Debug, Clone)] -pub struct ModuleInstance(Arc); +pub struct ModuleInstance(Rc); #[allow(dead_code)] #[derive(Debug)] @@ -69,7 +69,7 @@ impl ModuleInstance { let global_addrs = store.init_globals(addrs.globals, data.globals.into(), &addrs.funcs, idx)?; let (elem_addrs, elem_trapped) = - store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, data.elements.into(), idx)?; + store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &data.elements, idx)?; let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, data.data.into(), idx)?; let instance = ModuleInstanceInner { @@ -126,7 +126,7 @@ impl ModuleInstance { } pub(crate) fn new(inner: ModuleInstanceInner) -> Self { - Self(Arc::new(inner)) + Self(Rc::new(inner)) } pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index 1c0d260..ff40b33 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -217,7 +217,7 @@ impl Store { table_addrs: &[TableAddr], func_addrs: &[FuncAddr], global_addrs: &[Addr], - elements: Vec, + elements: &[Element], idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { let elem_count = self.data.elements.len(); diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index c44c3fb..de5d5ab 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -413,7 +413,7 @@ impl TestSuite { AssertReturn { span, exec, results } => { info!("AssertReturn: {:?}", exec); - let expected = convert_wastret(results)?; + let expected = convert_wastret(results.into_iter())?; let invoke = match match exec { wast::WastExecute::Wat(_) => Err(eyre!("wat not supported")), diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 09a4769..1b91e1e 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -62,8 +62,8 @@ pub fn convert_wastargs(args: Vec) -> Result) -> Result> { - args.into_iter().map(|a| wastret2tinywasmvalue(a)).collect() +pub fn convert_wastret<'a>(args: impl Iterator>) -> Result> { + args.map(|a| wastret2tinywasmvalue(a)).collect() } fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 365ead7..3729e1a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -480,7 +480,7 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, From 9d30a1b0d93b45c05f86e61df77359303aab449d Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 27 Jan 2024 02:40:08 +0100 Subject: [PATCH 11/27] pref: callstack/callframe improvements Signed-off-by: Henry Gressmann --- Cargo.toml | 4 ++ benches/fibonacci.rs | 42 +++++++++++++++++++ benches/selfhosted.rs | 12 ++++-- crates/parser/src/module.rs | 2 +- .../tinywasm/src/runtime/interpreter/mod.rs | 3 +- crates/tinywasm/src/runtime/stack/blocks.rs | 5 --- .../tinywasm/src/runtime/stack/call_stack.rs | 29 +++++-------- .../tinywasm/src/runtime/stack/value_stack.rs | 4 +- crates/types/src/instructions.rs | 6 +-- crates/types/src/lib.rs | 12 +++--- examples/rust/Cargo.toml | 2 +- 11 files changed, 80 insertions(+), 41 deletions(-) create mode 100644 benches/fibonacci.rs diff --git a/Cargo.toml b/Cargo.toml index 20674d9..f52bc22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,10 @@ test=false name="selfhosted" harness=false +[[bench]] +name="fibonacci" +harness=false + [profile.bench] opt-level=3 lto="thin" diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs new file mode 100644 index 0000000..b0a4834 --- /dev/null +++ b/benches/fibonacci.rs @@ -0,0 +1,42 @@ +mod util; +use criterion::{criterion_group, criterion_main, Criterion}; +use tinywasm::types::TinyWasmModule; +use util::tinywasm_module; + +fn run_tinywasm(module: TinyWasmModule) { + use tinywasm::*; + let module = Module::from(module); + let mut store = Store::default(); + let imports = Imports::default(); + let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); + let hello = instance.exported_func::(&mut store, "fibonacci").expect("exported_func"); + hello.call(&mut store, 28).expect("call"); +} + +fn run_wasmi() { + use wasmi::*; + let engine = Engine::default(); + let module = wasmi::Module::new(&engine, FIBONACCI).expect("wasmi::Module::new"); + let mut store = Store::new(&engine, ()); + let linker = >::new(&engine); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let hello = instance.get_typed_func::(&mut store, "fibonacci").expect("get_typed_func"); + hello.call(&mut store, 28).expect("call"); +} + +const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + let module = tinywasm_module(FIBONACCI); + + let mut group = c.benchmark_group("fibonacci"); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); + // group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(50).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + targets = criterion_benchmark +); + +criterion_main!(benches); diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index 78ea48b..f78d958 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -30,9 +30,15 @@ const TINYWASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_module(TINYWASM); - c.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - c.bench_function("wasmi", |b| b.iter(|| run_wasmi())); + let mut group = c.benchmark_group("selfhosted"); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); } -criterion_group!(benches, criterion_benchmark); +criterion_group!( + name = benches; + config = Criterion::default().sample_size(500).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + targets = criterion_benchmark +); + criterion_main!(benches); diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index e0fb0cc..5bc6a7e 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -5,7 +5,7 @@ use core::fmt::Debug; use tinywasm_types::{Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, ValType}; use wasmparser::{Payload, Validator}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct CodeSection { pub locals: Box<[ValType]>, pub body: Box<[Instruction]>, diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index 80fb3f9..285ba8d 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -115,6 +115,7 @@ macro_rules! break_to { /// Run a single step of the interpreter /// A seperate function is used so later, we can more easily implement /// a step-by-step debugger (using generators once they're stable?) +// TODO: perf: don't push then pop the call frame, just pass it via ExecResult::Call instead #[inline(always)] // this improves performance by more than 20% in some cases fn exec_one( cf: &mut CallFrame, @@ -611,7 +612,7 @@ fn exec_one( let elem_idx = module.resolve_elem_addr(*elem_index); let elem = store.get_elem(elem_idx as usize)?; - if elem.kind != ElementKind::Passive { + if let ElementKind::Passive = elem.kind { return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); } diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs index 76252b5..ab2d28c 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/blocks.rs @@ -19,11 +19,6 @@ impl Labels { #[inline] /// get the label at the given index, where 0 is the top of the stack pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { - let len = self.0.len(); - if index >= len { - return None; - } - self.0.get(self.0.len() - index - 1) } diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index b19e332..46c745a 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -3,52 +3,45 @@ use crate::{ runtime::{BlockType, RawWasmValue}, Error, FunctionInstance, Result, Trap, }; +use alloc::vec; use alloc::{boxed::Box, rc::Rc, vec::Vec}; use tinywasm_types::{ValType, WasmValue}; use super::{blocks::Labels, LabelFrame}; // minimum call stack size -const CALL_STACK_SIZE: usize = 128; +const CALL_STACK_SIZE: usize = 256; const CALL_STACK_MAX_SIZE: usize = 1024; #[derive(Debug)] pub(crate) struct CallStack { stack: Vec, - top: usize, } impl Default for CallStack { fn default() -> Self { - Self { stack: Vec::with_capacity(CALL_STACK_SIZE), top: 0 } + Self { stack: Vec::with_capacity(CALL_STACK_SIZE) } } } impl CallStack { + #[inline] pub(crate) fn is_empty(&self) -> bool { - self.top == 0 + self.stack.is_empty() } + #[inline] pub(crate) fn pop(&mut self) -> Result { - assert!(self.top <= self.stack.len()); - if self.top == 0 { - return Err(Error::CallStackEmpty); - } - - self.top -= 1; - Ok(self.stack.pop().unwrap()) + self.stack.pop().ok_or_else(|| Error::CallStackEmpty) } #[inline] pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - assert!(self.top <= self.stack.len(), "stack is too small"); - log::debug!("stack size: {}", self.stack.len()); if self.stack.len() >= CALL_STACK_MAX_SIZE { return Err(Trap::CallStackOverflow.into()); } - self.top += 1; self.stack.push(call_frame); Ok(()) } @@ -79,7 +72,6 @@ impl CallFrame { /// Break to a block at the given index (relative to the current frame) /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Option<()> { - log::debug!("break_to_relative: {}", break_to_relative); let break_to = self.labels.get_relative_to_top(break_to_relative as usize)?; // instr_ptr points to the label instruction, but the next step @@ -111,14 +103,15 @@ impl CallFrame { Some(()) } + // TOOD: perf: this function is pretty hot + // Especially the two `extend` calls pub(crate) fn new_raw( func_instance_ptr: Rc, params: &[RawWasmValue], local_types: Vec, ) -> Self { - let mut locals = Vec::with_capacity(local_types.len() + params.len()); - locals.extend(params.iter().cloned()); - locals.extend(local_types.iter().map(|_| RawWasmValue::default())); + let mut locals = vec![RawWasmValue::default(); local_types.len() + params.len()]; + locals[..params.len()].copy_from_slice(params); Self { instr_ptr: 0, diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index d36373b..e1c3107 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -98,9 +98,7 @@ impl ValueStack { } pub(crate) fn break_to(&mut self, new_stack_size: usize, result_count: usize) { - let len = self.stack.len(); - self.stack.copy_within((len - result_count)..len, new_stack_size); - self.stack.truncate(new_stack_size + result_count); + self.stack.drain(new_stack_size..(self.stack.len() - result_count)); } #[inline] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index d5de50a..ba13979 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -10,7 +10,7 @@ pub enum BlockArgs { } /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone)] pub struct MemoryArg { pub mem_addr: MemAddr, pub align: u8, @@ -23,7 +23,7 @@ type BrTableLen = usize; type EndOffset = usize; type ElseOffset = usize; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum ConstInstruction { I32Const(i32), I64Const(i64), @@ -46,7 +46,7 @@ pub enum ConstInstruction { /// This makes it easier to implement the label stack (we call it BlockFrameStack) iteratively. /// /// See -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum Instruction { // Custom Instructions BrLabel(LabelAddr), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 3729e1a..9af8e11 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -71,7 +71,7 @@ pub struct TinyWasmModule { /// A WebAssembly value. /// /// See -#[derive(Clone, PartialEq, Copy)] +#[derive(Clone, Copy)] pub enum WasmValue { // Num types /// A 32-bit integer. @@ -253,7 +253,7 @@ impl WasmValue { } /// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ValType { /// A 32-bit integer. I32, @@ -380,13 +380,13 @@ pub struct Global { pub init: ConstInstruction, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct TableType { pub element_type: ValType, pub size_initial: u32, @@ -406,7 +406,7 @@ impl TableType { #[derive(Debug, Clone)] /// Represents a memory's type. -#[derive(Copy, PartialEq, Eq, Hash)] +#[derive(Copy)] pub struct MemoryType { pub arch: MemoryArch, pub page_count_initial: u64, @@ -480,7 +480,7 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 837f7f3..8a608d0 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -29,7 +29,7 @@ name="fibonacci" path="src/fibonacci.rs" [profile.wasm] -opt-level="s" +opt-level=3 lto="thin" codegen-units=1 panic="abort" From 4f405d192503d0b22f2da59cfac64f4b4e3aebe6 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 27 Jan 2024 15:27:47 +0100 Subject: [PATCH 12/27] pref: more callstack/callframe improvements Signed-off-by: Henry Gressmann --- crates/tinywasm/src/func.rs | 4 +- crates/tinywasm/src/imports.rs | 4 +- crates/tinywasm/src/reference.rs | 2 +- .../tinywasm/src/runtime/interpreter/mod.rs | 79 ++++++++++--------- crates/tinywasm/src/runtime/stack.rs | 2 +- crates/tinywasm/src/runtime/stack/blocks.rs | 60 ++++++++------ .../tinywasm/src/runtime/stack/call_stack.rs | 63 ++++++--------- .../tinywasm/src/runtime/stack/value_stack.rs | 8 +- crates/tinywasm/src/runtime/value.rs | 1 + crates/tinywasm/src/store.rs | 20 +++-- crates/types/src/lib.rs | 9 ++- 11 files changed, 128 insertions(+), 124 deletions(-) diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 9c9938b..1c5129b 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,4 +1,4 @@ -use crate::log; +use crate::{log, runtime::RawWasmValue}; use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; use tinywasm_types::{FuncAddr, FuncType, ValType, WasmValue}; @@ -63,7 +63,7 @@ impl FuncHandle { // 6. Let f be the dummy frame log::debug!("locals: {:?}", locals); - let call_frame = CallFrame::new(func_inst, params, locals); + let call_frame = CallFrame::new(func_inst, params.iter().map(|v| RawWasmValue::from(*v)), locals); // 7. Push the frame f to the call stack // & 8. Push the values to the stack (Not needed since the call frame owns the values) diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 28ea0ec..32140a7 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -154,7 +154,7 @@ impl Extern { let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { let args = P::from_wasm_value_tuple(args)?; let result = func(ctx, args)?; - Ok(result.into_wasm_value_tuple()) + Ok(result.into_wasm_value_tuple().to_vec()) }; let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; @@ -383,7 +383,7 @@ impl Imports { .ok_or_else(|| LinkingError::incompatible_import_type(import))?; Self::compare_types(import, extern_func.ty(), import_func_type)?; - imports.funcs.push(store.add_func(extern_func, *ty, idx)?); + imports.funcs.push(store.add_func(extern_func, idx)?); } _ => return Err(LinkingError::incompatible_import_type(import).into()), }, diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index fdaae18..ed09530 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -83,7 +83,7 @@ impl MemoryRef { /// Load a UTF-8 string from memory pub fn load_string(&self, offset: usize, len: usize) -> Result { let bytes = self.load_vec(offset, len)?; - Ok(String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string()))?) + String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string())) } /// Load a C-style string from memory diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index 285ba8d..a59b544 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -1,7 +1,7 @@ use super::{InterpreterRuntime, Stack}; use crate::{cold, log, unlikely}; use crate::{ - runtime::{BlockType, CallFrame, LabelArgs, LabelFrame}, + runtime::{BlockType, CallFrame, LabelFrame}, Error, FuncContext, ModuleInstance, Result, Store, Trap, }; use alloc::format; @@ -29,13 +29,12 @@ impl InterpreterRuntime { let mut cf = stack.call_stack.pop()?; let mut func_inst = cf.func_instance.clone(); - let mut wasm_func = func_inst.assert_wasm().expect("exec expected wasm function"); + let mut wasm_func = func_inst.assert_wasm()?; // The function to execute, gets updated from ExecResult::Call let mut instrs = &wasm_func.instructions; let mut instr_count = instrs.len(); - - let mut current_module = store.get_module_instance(func_inst.owner).unwrap().clone(); + let mut current_module = store.get_module_instance_raw(func_inst.owner); loop { if unlikely(cf.instr_ptr >= instr_count) { @@ -164,8 +163,8 @@ fn exec_one( } }; - let params = stack.values.pop_n_rev(ty.params.len())?.collect::>(); - let call_frame = CallFrame::new_raw(func_inst, ¶ms, locals); + let params = stack.values.pop_n_rev(ty.params.len())?; + let call_frame = CallFrame::new(func_inst, params, locals); // push the call frame cf.instr_ptr += 1; // skip the call instruction @@ -213,8 +212,8 @@ fn exec_one( } }; - let params = stack.values.pop_n_rev(func_ty.params.len())?.collect::>(); - let call_frame = CallFrame::new_raw(func_inst, ¶ms, locals); + let params = stack.values.pop_n_rev(func_ty.params.len())?; + let call_frame = CallFrame::new(func_inst, params, locals); // push the call frame cf.instr_ptr += 1; // skip the call instruction @@ -229,13 +228,14 @@ fn exec_one( // truthy value is on the top of the stack, so enter the then block if stack.values.pop_t::()? != 0 { cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), // - params, - args: LabelArgs::new(*args, module)?, - ty: BlockType::If, - }, + LabelFrame::new( + cf.instr_ptr, + cf.instr_ptr + *end_offset, + stack.values.len(), // - params, + BlockType::If, + args, + module, + ), &mut stack.values, ); return Ok(ExecResult::Ok); @@ -244,13 +244,14 @@ fn exec_one( // falsy value is on the top of the stack if let Some(else_offset) = else_offset { cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr + *else_offset, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), // - params, - args: LabelArgs::new(*args, module)?, - ty: BlockType::Else, - }, + LabelFrame::new( + cf.instr_ptr + *else_offset, + cf.instr_ptr + *end_offset, + stack.values.len(), // - params, + BlockType::Else, + args, + module, + ), &mut stack.values, ); cf.instr_ptr += *else_offset; @@ -261,26 +262,28 @@ fn exec_one( Loop(args, end_offset) => { cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), // - params, - args: LabelArgs::new(*args, module)?, - ty: BlockType::Loop, - }, + LabelFrame::new( + cf.instr_ptr, + cf.instr_ptr + *end_offset, + stack.values.len(), // - params, + BlockType::Loop, + args, + module, + ), &mut stack.values, ); } Block(args, end_offset) => { cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), //- params, - args: LabelArgs::new(*args, module)?, - ty: BlockType::Block, - }, + LabelFrame::new( + cf.instr_ptr, + cf.instr_ptr + *end_offset, + stack.values.len(), // - params, + BlockType::Block, + args, + module, + ), &mut stack.values, ); } @@ -341,7 +344,7 @@ fn exec_one( panic!("else: no label to end, this should have been validated by the parser"); }; - let res_count = block.args.results; + let res_count = block.results; stack.values.truncate_keep(block.stack_ptr, res_count); cf.instr_ptr += *end_offset; } @@ -352,7 +355,7 @@ fn exec_one( cold(); panic!("end: no label to end, this should have been validated by the parser"); }; - stack.values.truncate_keep(block.stack_ptr, block.args.results) + stack.values.truncate_keep(block.stack_ptr, block.results) } LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)), diff --git a/crates/tinywasm/src/runtime/stack.rs b/crates/tinywasm/src/runtime/stack.rs index 07d9316..285d967 100644 --- a/crates/tinywasm/src/runtime/stack.rs +++ b/crates/tinywasm/src/runtime/stack.rs @@ -3,7 +3,7 @@ mod call_stack; mod value_stack; use self::{call_stack::CallStack, value_stack::ValueStack}; -pub(crate) use blocks::{BlockType, LabelArgs, LabelFrame}; +pub(crate) use blocks::{BlockType, LabelFrame}; pub(crate) use call_stack::CallFrame; /// A WebAssembly Stack diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs index ab2d28c..ff77cf8 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/blocks.rs @@ -1,12 +1,17 @@ use alloc::vec::Vec; use tinywasm_types::BlockArgs; -use crate::{ModuleInstance, Result}; +use crate::{unlikely, ModuleInstance}; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub(crate) struct Labels(Vec); impl Labels { + pub(crate) fn new() -> Self { + // this is somehow a lot faster than Vec::with_capacity(128) or even using Default::default() in the benchmarks + Self(Vec::new()) + } + pub(crate) fn len(&self) -> usize { self.0.len() } @@ -19,7 +24,12 @@ impl Labels { #[inline] /// get the label at the given index, where 0 is the top of the stack pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { - self.0.get(self.0.len() - index - 1) + // the vast majority of wasm functions don't use break to return + if unlikely(index >= self.0.len()) { + return None; + } + + Some(&self.0[self.0.len() - index - 1]) } #[inline] @@ -43,10 +53,34 @@ pub(crate) struct LabelFrame { // position of the stack pointer when the block was entered pub(crate) stack_ptr: usize, - pub(crate) args: LabelArgs, + pub(crate) results: usize, + pub(crate) params: usize, pub(crate) ty: BlockType, } +impl LabelFrame { + #[inline] + pub(crate) fn new( + instr_ptr: usize, + end_instr_ptr: usize, + stack_ptr: usize, + ty: BlockType, + args: &BlockArgs, + module: &ModuleInstance, + ) -> Self { + let (params, results) = match args { + BlockArgs::Empty => (0, 0), + BlockArgs::Type(_) => (0, 1), + BlockArgs::FuncType(t) => { + let ty = module.func_ty(*t); + (ty.params.len(), ty.results.len()) + } + }; + + Self { instr_ptr, end_instr_ptr, stack_ptr, results, params, ty } + } +} + #[derive(Debug, Copy, Clone)] #[allow(dead_code)] pub(crate) enum BlockType { @@ -55,21 +89,3 @@ pub(crate) enum BlockType { Else, Block, } - -#[derive(Debug, Clone, Default)] -pub(crate) struct LabelArgs { - pub(crate) params: usize, - pub(crate) results: usize, -} - -impl LabelArgs { - pub(crate) fn new(args: BlockArgs, module: &ModuleInstance) -> Result { - Ok(match args { - BlockArgs::Empty => LabelArgs { params: 0, results: 0 }, - BlockArgs::Type(_) => LabelArgs { params: 0, results: 1 }, - BlockArgs::FuncType(t) => { - LabelArgs { params: module.func_ty(t).params.len(), results: module.func_ty(t).results.len() } - } - }) - } -} diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index 46c745a..ebec142 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -1,16 +1,15 @@ -use crate::log; +use crate::unlikely; use crate::{ runtime::{BlockType, RawWasmValue}, Error, FunctionInstance, Result, Trap, }; -use alloc::vec; use alloc::{boxed::Box, rc::Rc, vec::Vec}; -use tinywasm_types::{ValType, WasmValue}; +use tinywasm_types::ValType; use super::{blocks::Labels, LabelFrame}; // minimum call stack size -const CALL_STACK_SIZE: usize = 256; +const CALL_STACK_SIZE: usize = 128; const CALL_STACK_MAX_SIZE: usize = 1024; #[derive(Debug)] @@ -32,16 +31,17 @@ impl CallStack { #[inline] pub(crate) fn pop(&mut self) -> Result { - self.stack.pop().ok_or_else(|| Error::CallStackEmpty) + match self.stack.pop() { + Some(frame) => Ok(frame), + None => Err(Error::CallStackEmpty), + } } #[inline] pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - log::debug!("stack size: {}", self.stack.len()); - if self.stack.len() >= CALL_STACK_MAX_SIZE { + if unlikely(self.stack.len() >= CALL_STACK_MAX_SIZE) { return Err(Trap::CallStackOverflow.into()); } - self.stack.push(call_frame); Ok(()) } @@ -55,15 +55,14 @@ pub(crate) struct CallFrame { pub(crate) labels: Labels, pub(crate) locals: Box<[RawWasmValue]>, - pub(crate) local_count: usize, } impl CallFrame { #[inline] /// Push a new label to the label stack and ensure the stack has the correct values pub(crate) fn enter_label(&mut self, label_frame: LabelFrame, stack: &mut super::ValueStack) { - if label_frame.args.params > 0 { - stack.extend_from_within((label_frame.stack_ptr - label_frame.args.params)..label_frame.stack_ptr); + if label_frame.params > 0 { + stack.extend_from_within((label_frame.stack_ptr - label_frame.params)..label_frame.stack_ptr); } self.labels.push(label_frame); @@ -80,7 +79,7 @@ impl CallFrame { BlockType::Loop => { // this is a loop, so we want to jump back to the start of the loop // We also want to push the params to the stack - value_stack.break_to(break_to.stack_ptr, break_to.args.params); + value_stack.break_to(break_to.stack_ptr, break_to.params); self.instr_ptr = break_to.instr_ptr; @@ -90,7 +89,7 @@ impl CallFrame { BlockType::Block | BlockType::If | BlockType::Else => { // this is a block, so we want to jump to the next instruction after the block ends // We also want to push the block's results to the stack - value_stack.break_to(break_to.stack_ptr, break_to.args.results); + value_stack.break_to(break_to.stack_ptr, break_to.results); // (the inst_ptr will be incremented by 1 before the next instruction is executed) self.instr_ptr = break_to.end_instr_ptr; @@ -103,46 +102,30 @@ impl CallFrame { Some(()) } - // TOOD: perf: this function is pretty hot - // Especially the two `extend` calls - pub(crate) fn new_raw( - func_instance_ptr: Rc, - params: &[RawWasmValue], - local_types: Vec, - ) -> Self { - let mut locals = vec![RawWasmValue::default(); local_types.len() + params.len()]; - locals[..params.len()].copy_from_slice(params); - - Self { - instr_ptr: 0, - func_instance: func_instance_ptr, - local_count: locals.len(), - locals: locals.into_boxed_slice(), - labels: Labels::default(), - } - } - + #[inline] pub(crate) fn new( func_instance_ptr: Rc, - params: &[WasmValue], + params: impl Iterator + ExactSizeIterator, local_types: Vec, ) -> Self { - CallFrame::new_raw( - func_instance_ptr, - ¶ms.iter().map(|v| RawWasmValue::from(*v)).collect::>(), - local_types, - ) + let locals = { + let total_size = local_types.len() + params.len(); + let mut locals = Vec::with_capacity(total_size); + locals.extend(params); + locals.resize_with(total_size, RawWasmValue::default); + locals.into_boxed_slice() + }; + + Self { instr_ptr: 0, func_instance: func_instance_ptr, locals, labels: Labels::new() } } #[inline] pub(crate) fn set_local(&mut self, local_index: usize, value: RawWasmValue) { - assert!(local_index < self.local_count, "Invalid local index"); self.locals[local_index] = value; } #[inline] pub(crate) fn get_local(&self, local_index: usize) -> RawWasmValue { - assert!(local_index < self.local_count, "Invalid local index"); self.locals[local_index] } } diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index e1c3107..3c5f48b 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -1,11 +1,12 @@ use core::ops::Range; use crate::{cold, runtime::RawWasmValue, unlikely, Error, Result}; +use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; -// minimum stack size -pub(crate) const STACK_SIZE: usize = 1024; +pub(crate) const MIN_VALUE_STACK_SIZE: usize = 1024; +// pub(crate) const MAX_VALUE_STACK_SIZE: usize = 1024 * 1024; #[derive(Debug)] pub(crate) struct ValueStack { @@ -14,7 +15,7 @@ pub(crate) struct ValueStack { impl Default for ValueStack { fn default() -> Self { - Self { stack: Vec::with_capacity(STACK_SIZE) } + Self { stack: vec![RawWasmValue::default(); MIN_VALUE_STACK_SIZE] } } } @@ -97,6 +98,7 @@ impl ValueStack { Ok(res) } + #[inline] pub(crate) fn break_to(&mut self, new_stack_size: usize, result_count: usize) { self.stack.drain(new_stack_size..(self.stack.len() - result_count)); } diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs index 7230830..329a60b 100644 --- a/crates/tinywasm/src/runtime/value.rs +++ b/crates/tinywasm/src/runtime/value.rs @@ -8,6 +8,7 @@ use tinywasm_types::{ValType, WasmValue}; /// /// See [`WasmValue`] for the public representation. #[derive(Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] pub struct RawWasmValue(u64); impl Debug for RawWasmValue { diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index ff40b33..dd8112d 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -46,10 +46,13 @@ impl Store { /// Get a module instance by the internal id pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { - log::debug!("existing module instances: {:?}", self.module_instances.len()); self.module_instances.get(addr as usize) } + pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> ModuleInstance { + self.module_instances[addr as usize].clone() + } + /// Create a new store with the given runtime pub(crate) fn runtime(&self) -> runtime::InterpreterRuntime { match self.runtime { @@ -118,12 +121,8 @@ impl Store { let func_count = self.data.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); - for (i, (type_idx, func)) in funcs.into_iter().enumerate() { - self.data.funcs.push(Rc::new(FunctionInstance { - func: Function::Wasm(func), - _type_idx: type_idx, - owner: idx, - })); + for (i, (_, func)) in funcs.into_iter().enumerate() { + self.data.funcs.push(Rc::new(FunctionInstance { func: Function::Wasm(func), owner: idx })); func_addrs.push((i + func_count) as FuncAddr); } @@ -222,7 +221,7 @@ impl Store { ) -> Result<(Box<[Addr]>, Option)> { let elem_count = self.data.elements.len(); let mut elem_addrs = Vec::with_capacity(elem_count); - for (i, element) in elements.into_iter().enumerate() { + for (i, element) in elements.iter().enumerate() { let init = element .items .iter() @@ -344,8 +343,8 @@ impl Store { Ok(self.data.memories.len() as MemAddr - 1) } - pub(crate) fn add_func(&mut self, func: Function, type_idx: TypeAddr, idx: ModuleInstanceAddr) -> Result { - self.data.funcs.push(Rc::new(FunctionInstance { func, _type_idx: type_idx, owner: idx })); + pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { + self.data.funcs.push(Rc::new(FunctionInstance { func, owner: idx })); Ok(self.data.funcs.len() as FuncAddr - 1) } @@ -445,7 +444,6 @@ impl Store { /// See pub(crate) struct FunctionInstance { pub(crate) func: Function, - pub(crate) _type_idx: TypeAddr, pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 9af8e11..2ccf869 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; // log for logging (optional). #[cfg(feature = "logging")] +#[allow(clippy::single_component_path_imports)] use log; #[cfg(not(feature = "logging"))] @@ -173,7 +174,7 @@ impl TryFrom for i32 { match value { WasmValue::I32(i) => Ok(i), _ => { - log::error!("i32: try_from failed: {:?}", value); + crate::log::error!("i32: try_from failed: {:?}", value); Err(()) } } @@ -187,7 +188,7 @@ impl TryFrom for i64 { match value { WasmValue::I64(i) => Ok(i), _ => { - log::error!("i64: try_from failed: {:?}", value); + crate::log::error!("i64: try_from failed: {:?}", value); Err(()) } } @@ -201,7 +202,7 @@ impl TryFrom for f32 { match value { WasmValue::F32(i) => Ok(i), _ => { - log::error!("f32: try_from failed: {:?}", value); + crate::log::error!("f32: try_from failed: {:?}", value); Err(()) } } @@ -215,7 +216,7 @@ impl TryFrom for f64 { match value { WasmValue::F64(i) => Ok(i), _ => { - log::error!("f64: try_from failed: {:?}", value); + crate::log::error!("f64: try_from failed: {:?}", value); Err(()) } } From 3e7548c1e0b541f087e9ae36e53658956503e27b Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 27 Jan 2024 17:37:02 +0100 Subject: [PATCH 13/27] chore: simplify interpreter::exec Signed-off-by: Henry Gressmann --- benches/fibonacci.rs | 2 +- crates/tinywasm/src/func.rs | 55 ++++---- crates/tinywasm/src/imports.rs | 20 +-- crates/tinywasm/src/instance.rs | 8 +- .../tinywasm/src/runtime/interpreter/mod.rs | 126 ++++++++---------- crates/tinywasm/src/runtime/stack.rs | 8 +- crates/tinywasm/src/runtime/stack/blocks.rs | 2 +- .../tinywasm/src/runtime/stack/call_stack.rs | 30 +++-- crates/tinywasm/src/store.rs | 27 +--- 9 files changed, 128 insertions(+), 150 deletions(-) diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index b0a4834..7ccde02 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -30,7 +30,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("fibonacci"); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - // group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); } criterion_group!( diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 1c5129b..a0c1212 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,17 +1,17 @@ -use crate::{log, runtime::RawWasmValue}; +use crate::{log, runtime::RawWasmValue, unlikely, Function}; use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; -use tinywasm_types::{FuncAddr, FuncType, ValType, WasmValue}; +use tinywasm_types::{FuncType, ModuleInstanceAddr, ValType, WasmValue}; use crate::{ runtime::{CallFrame, Stack}, - Error, FuncContext, ModuleInstance, Result, Store, + Error, FuncContext, Result, Store, }; #[derive(Debug)] /// A function handle pub struct FuncHandle { - pub(crate) module: ModuleInstance, - pub(crate) addr: FuncAddr, + pub(crate) module_addr: ModuleInstanceAddr, + pub(crate) addr: u32, pub(crate) ty: FuncType, /// The name of the function, if it has one @@ -22,18 +22,13 @@ impl FuncHandle { /// Call a function (Invocation) /// /// See + #[inline] pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { - let mut stack = Stack::default(); - - // 1. Assert: funcs[func_addr] exists - // 2. let func_inst be the functiuon instance funcs[func_addr] - let func_inst = store.get_func(self.addr as usize)?.clone(); - // 3. Let func_ty be the function type let func_ty = &self.ty; // 4. If the length of the provided argument values is different from the number of expected arguments, then fail - if func_ty.params.len() != params.len() { + if unlikely(func_ty.params.len() != params.len()) { log::info!("func_ty.params: {:?}", func_ty.params); return Err(Error::Other(format!( "param count mismatch: expected {}, got {}", @@ -43,31 +38,34 @@ impl FuncHandle { } // 5. For each value type and the corresponding value, check if types match - for (i, (ty, param)) in func_ty.params.iter().zip(params).enumerate() { + if !unlikely(func_ty.params.iter().zip(params).enumerate().all(|(i, (ty, param))| { if ty != ¶m.val_type() { - return Err(Error::Other(format!( - "param type mismatch at index {}: expected {:?}, got {:?}", - i, ty, param - ))); + log::error!("param type mismatch at index {}: expected {:?}, got {:?}", i, ty, param); + false + } else { + true } + })) { + return Err(Error::Other("Type mismatch".into())); } - let locals = match &func_inst.func { - crate::Function::Host(h) => { - let func = h.func.clone(); - let ctx = FuncContext { store, module: &self.module }; + let func_inst = store.get_func(self.addr as usize)?; + let wasm_func = match &func_inst.func { + Function::Host(host_func) => { + let func = &host_func.clone().func; + let ctx = FuncContext { store, module_addr: self.module_addr }; return (func)(ctx, params); } - crate::Function::Wasm(ref f) => f.locals.to_vec(), + Function::Wasm(wasm_func) => wasm_func, }; // 6. Let f be the dummy frame - log::debug!("locals: {:?}", locals); - let call_frame = CallFrame::new(func_inst, params.iter().map(|v| RawWasmValue::from(*v)), locals); + let call_frame = + CallFrame::new(wasm_func.clone(), func_inst.owner, params.iter().map(|v| RawWasmValue::from(*v))); // 7. Push the frame f to the call stack // & 8. Push the values to the stack (Not needed since the call frame owns the values) - stack.call_stack.push(call_frame)?; + let mut stack = Stack::new(call_frame); // 9. Invoke the function instance let runtime = store.runtime(); @@ -125,6 +123,7 @@ macro_rules! impl_into_wasm_value_tuple { $($T: Into),* { #[allow(non_snake_case)] + #[inline] fn into_wasm_value_tuple(self) -> Vec { let ($($T,)*) = self; vec![$($T.into(),)*] @@ -136,6 +135,7 @@ macro_rules! impl_into_wasm_value_tuple { macro_rules! impl_into_wasm_value_tuple_single { ($T:ident) => { impl IntoWasmValueTuple for $T { + #[inline] fn into_wasm_value_tuple(self) -> Vec { vec![self.into()] } @@ -164,6 +164,7 @@ macro_rules! impl_from_wasm_value_tuple { where $($T: TryFrom),* { + #[inline] fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { #[allow(unused_variables, unused_mut)] let mut iter = values.iter(); @@ -186,6 +187,7 @@ macro_rules! impl_from_wasm_value_tuple { macro_rules! impl_from_wasm_value_tuple_single { ($T:ident) => { impl FromWasmValueTuple for $T { + #[inline] fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { #[allow(unused_variables, unused_mut)] let mut iter = values.iter(); @@ -254,6 +256,7 @@ macro_rules! impl_val_types_from_tuple { where $($t: ToValType,)+ { + #[inline] fn val_types() -> Box<[ValType]> { Box::new([$($t::to_val_type(),)+]) } @@ -262,6 +265,7 @@ macro_rules! impl_val_types_from_tuple { } impl ValTypesFromTuple for () { + #[inline] fn val_types() -> Box<[ValType]> { Box::new([]) } @@ -271,6 +275,7 @@ impl ValTypesFromTuple for T1 where T1: ToValType, { + #[inline] fn val_types() -> Box<[ValType]> { Box::new([T1::to_val_type()]) } diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 32140a7..0b16970 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -7,6 +7,7 @@ use crate::{ log, LinkingError, Result, }; use alloc::{ + boxed::Box, collections::BTreeMap, rc::Rc, string::{String, ToString}, @@ -18,10 +19,10 @@ use tinywasm_types::*; #[derive(Debug, Clone)] pub enum Function { /// A host function - Host(HostFunction), + Host(Rc), /// A function defined in WebAssembly - Wasm(WasmFunction), + Wasm(Rc), } impl Function { @@ -34,7 +35,6 @@ impl Function { } /// A host function -#[derive(Clone)] pub struct HostFunction { pub(crate) ty: tinywasm_types::FuncType, pub(crate) func: HostFuncInner, @@ -52,13 +52,13 @@ impl HostFunction { } } -pub(crate) type HostFuncInner = Rc, &[WasmValue]) -> Result>>; +pub(crate) type HostFuncInner = Box, &[WasmValue]) -> Result>>; /// The context of a host-function call #[derive(Debug)] pub struct FuncContext<'a> { pub(crate) store: &'a mut crate::Store, - pub(crate) module: &'a crate::ModuleInstance, + pub(crate) module_addr: ModuleInstanceAddr, } impl FuncContext<'_> { @@ -73,13 +73,13 @@ impl FuncContext<'_> { } /// Get a reference to the module instance - pub fn module(&self) -> &crate::ModuleInstance { - self.module + pub fn module(&self) -> crate::ModuleInstance { + self.store.get_module_instance_raw(self.module_addr) } /// Get a reference to an exported memory pub fn memory(&mut self, name: &str) -> Result { - self.module.exported_memory(self.store, name) + self.module().exported_memory(self.store, name) } } @@ -140,7 +140,7 @@ impl Extern { ty: &tinywasm_types::FuncType, func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static, ) -> Self { - Self::Function(Function::Host(HostFunction { func: Rc::new(func), ty: ty.clone() })) + Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(func), ty: ty.clone() }))) } /// Create a new typed function import @@ -159,7 +159,7 @@ impl Extern { let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; - Self::Function(Function::Host(HostFunction { func: Rc::new(inner_func), ty })) + Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty }))) } pub(crate) fn kind(&self) -> ExternalKind { diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index b79f551..ecd6ce8 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -42,6 +42,10 @@ impl ModuleInstance { self.0 = other.0; } + pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { + self.swap(store.get_module_instance_raw(other_addr)) + } + /// Get the module instance's address pub fn id(&self) -> ModuleInstanceAddr { self.0.idx @@ -172,7 +176,7 @@ impl ModuleInstance { let func_inst = store.get_func(func_addr as usize)?; let ty = func_inst.func.ty(); - Ok(FuncHandle { addr: func_addr, module: self.clone(), name: Some(name.to_string()), ty: ty.clone() }) + Ok(FuncHandle { addr: func_addr, module_addr: self.id(), name: Some(name.to_string()), ty: ty.clone() }) } /// Get a typed exported function by name @@ -230,7 +234,7 @@ impl ModuleInstance { let func_inst = store.get_func(*func_addr as usize)?; let ty = func_inst.func.ty(); - Ok(Some(FuncHandle { module: self.clone(), addr: *func_addr, ty: ty.clone(), name: None })) + Ok(Some(FuncHandle { module_addr: self.id(), addr: *func_addr, ty: ty.clone(), name: None })) } /// Invoke the start function of the module diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index a59b544..fdbe697 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -7,7 +7,7 @@ use crate::{ use alloc::format; use alloc::{string::ToString, vec::Vec}; use core::ops::{BitAnd, BitOr, BitXor, Neg}; -use tinywasm_types::{ElementKind, Instruction, ValType}; +use tinywasm_types::{ElementKind, ValType}; #[cfg(not(feature = "std"))] mod no_std_floats; @@ -28,51 +28,24 @@ impl InterpreterRuntime { // The current call frame, gets updated inside of exec_one let mut cf = stack.call_stack.pop()?; - let mut func_inst = cf.func_instance.clone(); - let mut wasm_func = func_inst.assert_wasm()?; - // The function to execute, gets updated from ExecResult::Call - let mut instrs = &wasm_func.instructions; - let mut instr_count = instrs.len(); - let mut current_module = store.get_module_instance_raw(func_inst.owner); + let mut current_module = store.get_module_instance_raw(cf.func_instance.1); loop { - if unlikely(cf.instr_ptr >= instr_count) { - cold(); - log::error!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instr_count); - return Err(Error::Other(format!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instr_count))); - } - let instr = &instrs[cf.instr_ptr]; - - match exec_one(&mut cf, instr, instrs, stack, store, ¤t_module)? { + match exec_one(&mut cf, stack, store, ¤t_module)? { // Continue execution at the new top of the call stack ExecResult::Call => { cf = stack.call_stack.pop()?; - func_inst = cf.func_instance.clone(); - wasm_func = - func_inst.assert_wasm().map_err(|_| Error::Other("call expected wasm function".to_string()))?; - instrs = &wasm_func.instructions; - instr_count = instrs.len(); - - if cf.func_instance.owner != current_module.id() { - current_module.swap( - store - .get_module_instance(cf.func_instance.owner) - .ok_or_else(|| Error::Other("call expected module instance".to_string()))? - .clone(), - ); + if cf.func_instance.1 != current_module.id() { + current_module.swap_with(cf.func_instance.1, store); } - - continue; } // return from the function ExecResult::Return => return Ok(()), // continue to the next instruction and increment the instruction pointer - ExecResult::Ok => { - cf.instr_ptr += 1; - } + ExecResult::Ok => cf.instr_ptr += 1, // trap the program ExecResult::Trap(trap) => { @@ -114,18 +87,17 @@ macro_rules! break_to { /// Run a single step of the interpreter /// A seperate function is used so later, we can more easily implement /// a step-by-step debugger (using generators once they're stable?) -// TODO: perf: don't push then pop the call frame, just pass it via ExecResult::Call instead #[inline(always)] // this improves performance by more than 20% in some cases -fn exec_one( - cf: &mut CallFrame, - instr: &Instruction, - instrs: &[Instruction], - stack: &mut Stack, - store: &mut Store, - module: &ModuleInstance, -) -> Result { +fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &ModuleInstance) -> Result { + let instrs = &cf.func_instance.0.instructions; + if unlikely(cf.instr_ptr >= instrs.len() || instrs.is_empty()) { + cold(); + log::error!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instrs.len()); + return Err(Error::Other(format!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instrs.len()))); + } + use tinywasm_types::Instruction::*; - match instr { + match &instrs[cf.instr_ptr] { Nop => { /* do nothing */ } Unreachable => { cold(); @@ -152,19 +124,19 @@ fn exec_one( let func_idx = module.resolve_func_addr(*v); let func_inst = store.get_func(func_idx as usize)?.clone(); - let (locals, ty) = match &func_inst.func { - crate::Function::Wasm(ref f) => (f.locals.to_vec(), f.ty.clone()), + let wasm_func = match &func_inst.func { + crate::Function::Wasm(wasm_func) => wasm_func.clone(), crate::Function::Host(host_func) => { - let func = host_func.func.clone(); + let func = &host_func.func; let params = stack.values.pop_params(&host_func.ty.params)?; - let res = (func)(FuncContext { store, module }, ¶ms)?; + let res = (func)(FuncContext { store, module_addr: module.id() }, ¶ms)?; stack.values.extend_from_typed(&res); return Ok(ExecResult::Ok); } }; - let params = stack.values.pop_n_rev(ty.params.len())?; - let call_frame = CallFrame::new(func_inst, params, locals); + let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?; + let call_frame = CallFrame::new(wasm_func, func_inst.owner, params); // push the call frame cf.instr_ptr += 1; // skip the call instruction @@ -191,29 +163,37 @@ fn exec_one( }; let func_inst = store.get_func(func_ref as usize)?.clone(); - let func_ty = func_inst.func.ty(); let call_ty = module.func_ty(*type_addr); - if unlikely(func_ty != call_ty) { - log::error!("indirect call type mismatch: {:?} != {:?}", func_ty, call_ty); - return Err( - Trap::IndirectCallTypeMismatch { actual: func_ty.clone(), expected: call_ty.clone() }.into() - ); - } - - let locals = match &func_inst.func { - crate::Function::Wasm(ref f) => f.locals.to_vec(), + let wasm_func = match func_inst.func { + crate::Function::Wasm(ref f) => f.clone(), crate::Function::Host(host_func) => { - let func = host_func.func.clone(); - let params = stack.values.pop_params(&func_ty.params)?; - let res = (func)(FuncContext { store, module }, ¶ms)?; + if unlikely(host_func.ty != *call_ty) { + log::error!("indirect call type mismatch: {:?} != {:?}", host_func.ty, call_ty); + return Err(Trap::IndirectCallTypeMismatch { + actual: host_func.ty.clone(), + expected: call_ty.clone(), + } + .into()); + } + + let host_func = host_func.clone(); + let params = stack.values.pop_params(&host_func.ty.params)?; + let res = (host_func.func)(FuncContext { store, module_addr: module.id() }, ¶ms)?; stack.values.extend_from_typed(&res); return Ok(ExecResult::Ok); } }; - let params = stack.values.pop_n_rev(func_ty.params.len())?; - let call_frame = CallFrame::new(func_inst, params, locals); + if unlikely(wasm_func.ty != *call_ty) { + log::error!("indirect call type mismatch: {:?} != {:?}", wasm_func.ty, call_ty); + return Err( + Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into() + ); + } + + let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?; + let call_frame = CallFrame::new(wasm_func, func_inst.owner, params); // push the call frame cf.instr_ptr += 1; // skip the call instruction @@ -243,18 +223,16 @@ fn exec_one( // falsy value is on the top of the stack if let Some(else_offset) = else_offset { - cf.enter_label( - LabelFrame::new( - cf.instr_ptr + *else_offset, - cf.instr_ptr + *end_offset, - stack.values.len(), // - params, - BlockType::Else, - args, - module, - ), - &mut stack.values, + let label = LabelFrame::new( + cf.instr_ptr + *else_offset, + cf.instr_ptr + *end_offset, + stack.values.len(), // - params, + BlockType::Else, + args, + module, ); cf.instr_ptr += *else_offset; + cf.enter_label(label, &mut stack.values); } else { cf.instr_ptr += *end_offset; } diff --git a/crates/tinywasm/src/runtime/stack.rs b/crates/tinywasm/src/runtime/stack.rs index 285d967..31c41f0 100644 --- a/crates/tinywasm/src/runtime/stack.rs +++ b/crates/tinywasm/src/runtime/stack.rs @@ -7,8 +7,14 @@ pub(crate) use blocks::{BlockType, LabelFrame}; pub(crate) use call_stack::CallFrame; /// A WebAssembly Stack -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Stack { pub(crate) values: ValueStack, pub(crate) call_stack: CallStack, } + +impl Stack { + pub(crate) fn new(call_frame: CallFrame) -> Self { + Self { values: ValueStack::default(), call_stack: CallStack::new(call_frame) } + } +} diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs index ff77cf8..f302e59 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/blocks.rs @@ -4,7 +4,7 @@ use tinywasm_types::BlockArgs; use crate::{unlikely, ModuleInstance}; #[derive(Debug, Clone)] -pub(crate) struct Labels(Vec); +pub(crate) struct Labels(Vec); // TODO: maybe Box<[LabelFrame]> by analyzing the lable count when parsing the module? impl Labels { pub(crate) fn new() -> Self { diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index ebec142..9ba6ad2 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -1,10 +1,10 @@ use crate::unlikely; use crate::{ runtime::{BlockType, RawWasmValue}, - Error, FunctionInstance, Result, Trap, + Error, Result, Trap, }; use alloc::{boxed::Box, rc::Rc, vec::Vec}; -use tinywasm_types::ValType; +use tinywasm_types::{ModuleInstanceAddr, WasmFunction}; use super::{blocks::Labels, LabelFrame}; @@ -17,13 +17,14 @@ pub(crate) struct CallStack { stack: Vec, } -impl Default for CallStack { - fn default() -> Self { - Self { stack: Vec::with_capacity(CALL_STACK_SIZE) } +impl CallStack { + #[inline] + pub(crate) fn new(initial_frame: CallFrame) -> Self { + let mut stack = Self { stack: Vec::with_capacity(CALL_STACK_SIZE) }; + stack.push(initial_frame).unwrap(); + stack } -} -impl CallStack { #[inline] pub(crate) fn is_empty(&self) -> bool { self.stack.is_empty() @@ -51,14 +52,13 @@ impl CallStack { pub(crate) struct CallFrame { pub(crate) instr_ptr: usize, // pub(crate) module: ModuleInstanceAddr, - pub(crate) func_instance: Rc, - + pub(crate) func_instance: (Rc, ModuleInstanceAddr), pub(crate) labels: Labels, pub(crate) locals: Box<[RawWasmValue]>, } impl CallFrame { - #[inline] + // TOOD: perf: this is called a lot, and it's a bit slow /// Push a new label to the label stack and ensure the stack has the correct values pub(crate) fn enter_label(&mut self, label_frame: LabelFrame, stack: &mut super::ValueStack) { if label_frame.params > 0 { @@ -102,13 +102,15 @@ impl CallFrame { Some(()) } - #[inline] + // TODO: perf: a lot of time is spent here + #[inline(always)] // about 10% faster with this pub(crate) fn new( - func_instance_ptr: Rc, + wasm_func_inst: Rc, + owner: ModuleInstanceAddr, params: impl Iterator + ExactSizeIterator, - local_types: Vec, ) -> Self { let locals = { + let local_types = &wasm_func_inst.locals; let total_size = local_types.len() + params.len(); let mut locals = Vec::with_capacity(total_size); locals.extend(params); @@ -116,7 +118,7 @@ impl CallFrame { locals.into_boxed_slice() }; - Self { instr_ptr: 0, func_instance: func_instance_ptr, locals, labels: Labels::new() } + Self { instr_ptr: 0, func_instance: (wasm_func_inst, owner), locals, labels: Labels::new() } } #[inline] diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index dd8112d..a4f9b70 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -87,7 +87,7 @@ impl Default for Store { /// Data should only be addressable by the module that owns it /// See pub(crate) struct StoreData { - pub(crate) funcs: Vec>, + pub(crate) funcs: Vec, pub(crate) tables: Vec>>, pub(crate) memories: Vec>>, pub(crate) globals: Vec>>, @@ -122,7 +122,7 @@ impl Store { let mut func_addrs = Vec::with_capacity(func_count); for (i, (_, func)) in funcs.into_iter().enumerate() { - self.data.funcs.push(Rc::new(FunctionInstance { func: Function::Wasm(func), owner: idx })); + self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func)), owner: idx }); func_addrs.push((i + func_count) as FuncAddr); } @@ -344,7 +344,7 @@ impl Store { } pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { - self.data.funcs.push(Rc::new(FunctionInstance { func, owner: idx })); + self.data.funcs.push(FunctionInstance { func, owner: idx }); Ok(self.data.funcs.len() as FuncAddr - 1) } @@ -396,7 +396,7 @@ impl Store { } /// Get the function at the actual index in the store - pub(crate) fn get_func(&self, addr: usize) -> Result<&Rc> { + pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { self.data.funcs.get(addr).ok_or_else(|| Error::Other(format!("function {} not found", addr))) } @@ -438,7 +438,7 @@ impl Store { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// A WebAssembly Function Instance /// /// See @@ -447,23 +447,6 @@ pub(crate) struct FunctionInstance { pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions } -// TODO: check if this actually helps -#[inline(always)] -#[cold] -const fn cold() {} - -impl FunctionInstance { - pub(crate) fn assert_wasm(&self) -> Result<&WasmFunction> { - match &self.func { - Function::Wasm(w) => Ok(w), - Function::Host(_) => { - cold(); - Err(Error::Other("expected wasm function".to_string())) - } - } - } -} - #[derive(Debug, Clone, Copy)] pub(crate) enum TableElement { Uninitialized, From 6b7596e571314a595a52df1ac1c97798529ca339 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sat, 27 Jan 2024 19:11:15 +0100 Subject: [PATCH 14/27] pref: optimize loops Signed-off-by: Henry Gressmann --- benches/fibonacci.rs | 14 +++++++------- crates/tinywasm/src/runtime/stack/call_stack.rs | 17 +++++++++++------ crates/tinywasm/tests/generated/2.0.csv | 2 +- examples/rust/src/fibonacci.rs | 12 ++++++++---- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index 7ccde02..5d8a0bd 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -1,19 +1,19 @@ mod util; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; use tinywasm::types::TinyWasmModule; use util::tinywasm_module; -fn run_tinywasm(module: TinyWasmModule) { +fn run_tinywasm(module: TinyWasmModule, iterations: i32) { use tinywasm::*; let module = Module::from(module); let mut store = Store::default(); let imports = Imports::default(); let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); let hello = instance.exported_func::(&mut store, "fibonacci").expect("exported_func"); - hello.call(&mut store, 28).expect("call"); + hello.call(&mut store, iterations).expect("call"); } -fn run_wasmi() { +fn run_wasmi(iterations: i32) { use wasmi::*; let engine = Engine::default(); let module = wasmi::Module::new(&engine, FIBONACCI).expect("wasmi::Module::new"); @@ -21,7 +21,7 @@ fn run_wasmi() { let linker = >::new(&engine); let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); let hello = instance.get_typed_func::(&mut store, "fibonacci").expect("get_typed_func"); - hello.call(&mut store, 28).expect("call"); + hello.call(&mut store, iterations).expect("call"); } const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); @@ -29,8 +29,8 @@ fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_module(FIBONACCI); let mut group = c.benchmark_group("fibonacci"); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone(), black_box(50)))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(black_box(50)))); } criterion_group!( diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index 9ba6ad2..dbe420d 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -77,15 +77,20 @@ impl CallFrame { // will increment it by 1 since we're changing the "current" instr_ptr match break_to.ty { BlockType::Loop => { - // this is a loop, so we want to jump back to the start of the loop - // We also want to push the params to the stack - value_stack.break_to(break_to.stack_ptr, break_to.params); - self.instr_ptr = break_to.instr_ptr; - // we also want to trim the label stack to the loop (but not including the loop) - self.labels.truncate(self.labels.len() - break_to_relative as usize); + // check if we're breaking to the loop + if break_to_relative != 0 { + // this is a loop, so we want to jump back to the start of the loop + // We also want to push the params to the stack + value_stack.break_to(break_to.stack_ptr, break_to.params); + + // we also want to trim the label stack to the loop (but not including the loop) + self.labels.truncate(self.labels.len() - break_to_relative as usize); + return Some(()); + } } + BlockType::Block | BlockType::If | BlockType::Else => { // this is a block, so we want to jump to the next instruction after the block ends // We also want to push the block's results to the stack diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/2.0.csv index 0d233ee..b5ea4cc 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/2.0.csv @@ -1,2 +1,2 @@ 0.3.0,26722,1161,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":1},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":171,"failed":12},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":594,"failed":186},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.4.0-alpha.0,26841,1042,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":700,"failed":80},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0-alpha.0,26861,1022,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs index 7493132..3e4be73 100644 --- a/examples/rust/src/fibonacci.rs +++ b/examples/rust/src/fibonacci.rs @@ -8,10 +8,14 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { } #[no_mangle] -// The rust compiler will convert this to an iterative algorithm. pub extern "C" fn fibonacci(n: i32) -> i32 { - if n <= 1 { - return n; + let mut sum = 0; + let mut last = 0; + let mut curr = 1; + for _i in 1..n { + sum = last + curr; + last = curr; + curr = sum; } - fibonacci(n - 1) + fibonacci(n - 2) + sum } From f26a0caadab5da373c00ed1269c98d58fec3dec0 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 28 Jan 2024 13:46:00 +0100 Subject: [PATCH 15/27] feat: improve memory perf + basic bulk memory access Signed-off-by: Henry Gressmann --- crates/parser/src/conversion.rs | 6 + crates/tinywasm/Cargo.toml | 1 + crates/tinywasm/src/lib.rs | 2 +- .../src/runtime/interpreter/macros.rs | 18 +- .../tinywasm/src/runtime/interpreter/mod.rs | 38 +- .../tinywasm/src/runtime/stack/call_stack.rs | 9 +- .../tinywasm/src/runtime/stack/value_stack.rs | 3 +- crates/tinywasm/src/runtime/value.rs | 1 + crates/tinywasm/src/store/memory.rs | 326 ++++++++++++++++++ .../tinywasm/src/{store.rs => store/mod.rs} | 128 +------ crates/tinywasm/tests/generated/2.0.csv | 2 +- crates/types/src/instructions.rs | 8 +- examples/rust/Cargo.toml | 2 +- 13 files changed, 396 insertions(+), 148 deletions(-) create mode 100644 crates/tinywasm/src/store/memory.rs rename crates/tinywasm/src/{store.rs => store/mod.rs} (83%) diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 835842f..79069e1 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -332,6 +332,12 @@ pub fn process_operators<'a>( GlobalSet { global_index } => Instruction::GlobalSet(global_index), MemorySize { mem, mem_byte } => Instruction::MemorySize(mem, mem_byte), MemoryGrow { mem, mem_byte } => Instruction::MemoryGrow(mem, mem_byte), + + MemoryCopy { dst_mem, src_mem } => Instruction::MemoryCopy(src_mem, dst_mem), + MemoryFill { mem } => Instruction::MemoryFill(mem), + MemoryInit { data_index, mem } => Instruction::MemoryInit(data_index, mem), + DataDrop { data_index } => Instruction::DataDrop(data_index), + I32Const { value } => Instruction::I32Const(value), I64Const { value } => Instruction::I64Const(value), F32Const { value } => Instruction::F32Const(f32::from_bits(value.bits())), diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index f1a3242..a9e24bf 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -33,6 +33,7 @@ default=["std", "parser", "logging"] logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] +unsafe=[] [[test]] name="generate-charts" diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index bc64ac4..d786ab8 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -1,11 +1,11 @@ #![no_std] -#![forbid(unsafe_code)] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] #![cfg_attr(nightly, feature(error_in_core))] +#![cfg_attr(not(feature = "unsafe"), deny(unsafe_code))] //! A tiny WebAssembly Runtime written in Rust //! diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs index eba866c..a769b12 100644 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ b/crates/tinywasm/src/runtime/interpreter/macros.rs @@ -14,14 +14,14 @@ macro_rules! mem_load { // TODO: there could be a lot of performance improvements here let mem_idx = $module.resolve_mem_addr($arg.mem_addr); let mem = $store.get_mem(mem_idx as usize)?; + let mem_ref = mem.borrow_mut(); let addr = $stack.values.pop()?.raw_value(); - let addr = $arg.offset.checked_add(addr).ok_or_else(|| { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: $arg.offset as usize, len: core::mem::size_of::<$load_type>(), - max: mem.borrow().max_pages(), + max: mem_ref.max_pages(), }) })?; @@ -29,18 +29,14 @@ macro_rules! mem_load { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: $arg.offset as usize, len: core::mem::size_of::<$load_type>(), - max: mem.borrow().max_pages(), + max: mem_ref.max_pages(), }) })?; - let val: [u8; core::mem::size_of::<$load_type>()] = { - let mem = mem.borrow_mut(); - let val = mem.load(addr, $arg.align as usize, core::mem::size_of::<$load_type>())?; - val.try_into().expect("slice with incorrect length") - }; - - let loaded_value = <$load_type>::from_le_bytes(val); - $stack.values.push((loaded_value as $target_type).into()); + const LEN: usize = core::mem::size_of::<$load_type>(); + let val = mem_ref.load_as::(addr, $arg.align as usize)?; + // let loaded_value = mem_ref.load_as::<$load_type>(addr, $arg.align as usize)?; + $stack.values.push((val as $target_type).into()); }}; } diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index fdbe697..3c3b776 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -23,7 +23,7 @@ use macros::*; use traits::*; impl InterpreterRuntime { - #[inline(always)] // a small 2-3% performance improvement in some cases + // #[inline(always)] // a small 2-3% performance improvement in some cases pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack) -> Result<()> { // The current call frame, gets updated inside of exec_one let mut cf = stack.call_stack.pop()?; @@ -388,6 +388,42 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M } } + // Bulk memory operations + MemoryCopy(from, to) => { + let size = stack.values.pop_t::()?; + let src = stack.values.pop_t::()?; + let dst = stack.values.pop_t::()?; + + let mem = store.get_mem(module.resolve_mem_addr(*from) as usize)?; + let mut mem = mem.borrow_mut(); + + if from == to { + // copy within the same memory + mem.copy_within(dst as usize, src as usize, size as usize)?; + } else { + // copy between two memories + let mem2 = store.get_mem(module.resolve_mem_addr(*to) as usize)?; + let mut mem2 = mem2.borrow_mut(); + mem2.copy_from_slice(dst as usize, mem.load(src as usize, 0, size as usize)?)?; + } + } + + MemoryFill(addr) => { + let size = stack.values.pop_t::()?; + let val = stack.values.pop_t::()?; + let dst = stack.values.pop_t::()?; + + let mem = store.get_mem(module.resolve_mem_addr(*addr) as usize)?; + let mut mem = mem.borrow_mut(); + mem.fill(dst as usize, size as usize, val as u8)?; + } + + // MemoryInit(data_index, mem_index) => {} + // DataDrop(data_index) => { + // // let data_idx = module.resolve_data_addr(*data_index); + // // let data = store.get_data(data_idx as usize)?; + // // data.borrow_mut().drop()?; + // } I32Store(arg) => mem_store!(i32, arg, stack, store, module), I64Store(arg) => mem_store!(i64, arg, stack, store, module), F32Store(arg) => mem_store!(f32, arg, stack, store, module), diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index dbe420d..dcbfcac 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -58,7 +58,6 @@ pub(crate) struct CallFrame { } impl CallFrame { - // TOOD: perf: this is called a lot, and it's a bit slow /// Push a new label to the label stack and ensure the stack has the correct values pub(crate) fn enter_label(&mut self, label_frame: LabelFrame, stack: &mut super::ValueStack) { if label_frame.params > 0 { @@ -77,14 +76,14 @@ impl CallFrame { // will increment it by 1 since we're changing the "current" instr_ptr match break_to.ty { BlockType::Loop => { + // this is a loop, so we want to jump back to the start of the loop self.instr_ptr = break_to.instr_ptr; + // We also want to push the params to the stack + value_stack.break_to(break_to.stack_ptr, break_to.params); + // check if we're breaking to the loop if break_to_relative != 0 { - // this is a loop, so we want to jump back to the start of the loop - // We also want to push the params to the stack - value_stack.break_to(break_to.stack_ptr, break_to.params); - // we also want to trim the label stack to the loop (but not including the loop) self.labels.truncate(self.labels.len() - break_to_relative as usize); return Some(()); diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index 3c5f48b..9b8f82d 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -1,7 +1,6 @@ use core::ops::Range; use crate::{cold, runtime::RawWasmValue, unlikely, Error, Result}; -use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; @@ -15,7 +14,7 @@ pub(crate) struct ValueStack { impl Default for ValueStack { fn default() -> Self { - Self { stack: vec![RawWasmValue::default(); MIN_VALUE_STACK_SIZE] } + Self { stack: Vec::with_capacity(MIN_VALUE_STACK_SIZE) } } } diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs index 329a60b..5341361 100644 --- a/crates/tinywasm/src/runtime/value.rs +++ b/crates/tinywasm/src/runtime/value.rs @@ -18,6 +18,7 @@ impl Debug for RawWasmValue { } impl RawWasmValue { + #[inline(always)] pub fn raw_value(&self) -> u64 { self.0 } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs new file mode 100644 index 0000000..a087b1d --- /dev/null +++ b/crates/tinywasm/src/store/memory.rs @@ -0,0 +1,326 @@ +use alloc::vec; +use alloc::vec::Vec; +use tinywasm_types::{MemoryType, ModuleInstanceAddr}; + +use crate::{cold, unlikely, Error, Result}; + +pub(crate) const PAGE_SIZE: usize = 65536; +pub(crate) const MAX_PAGES: usize = 65536; +pub(crate) const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; + +/// A WebAssembly Memory Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct MemoryInstance { + pub(crate) kind: MemoryType, + pub(crate) data: Vec, + pub(crate) page_count: usize, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl MemoryInstance { + pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { + assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); + log::debug!("initializing memory with {} pages", kind.page_count_initial); + + Self { + kind, + data: vec![0; PAGE_SIZE * kind.page_count_initial as usize], + page_count: kind.page_count_initial as usize, + _owner: owner, + } + } + + pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> { + let Some(end) = addr.checked_add(len) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len: data.len(), + max: self.data.len(), + })); + }; + + if unlikely(end > self.data.len() || end < addr) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len: data.len(), + max: self.data.len(), + })); + } + + // WebAssembly doesn't require alignment for stores + #[cfg(not(feature = "unsafe"))] + self.data[addr..end].copy_from_slice(data); + + #[cfg(feature = "unsafe")] + // SAFETY: we checked that `end` is in bounds above, this is the same as `copy_from_slice` + // src must is for reads of count * size_of::() bytes. + // dst must is for writes of count * size_of::() bytes. + // Both src and dst are properly aligned. + // The region of memory beginning at src does not overlap with the region of memory beginning at dst with the same size. + unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), self.data[addr..end].as_mut_ptr(), len); + } + + Ok(()) + } + + pub(crate) fn max_pages(&self) -> usize { + self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize + } + + pub(crate) fn load(&self, addr: usize, _align: usize, len: usize) -> Result<&[u8]> { + let Some(end) = addr.checked_add(len) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + }; + + if unlikely(end > self.data.len() || end < addr) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + } + + Ok(&self.data[addr..end]) + } + + // this is a workaround since we can't use generic const expressions yet (https://github.com/rust-lang/rust/issues/76560) + pub(crate) fn load_as>(&self, addr: usize, _align: usize) -> Result { + let Some(end) = addr.checked_add(SIZE) else { + cold(); + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: SIZE, max: self.max_pages() })); + }; + + if unlikely(end > self.data.len()) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: SIZE, max: self.data.len() })); + } + + #[cfg(feature = "unsafe")] + // WebAssembly doesn't require alignment for loads + // SAFETY: we checked that `end` is in bounds above. All types that implement `Into` are valid + // to load from unaligned addresses. + let val = unsafe { core::ptr::read_unaligned(self.data[addr..end].as_ptr() as *const T) }; + + #[cfg(not(feature = "unsafe"))] + let val = T::from_le_bytes(self.data[addr..end].try_into().expect("slice size mismatch")); + + Ok(val) + } + + pub(crate) fn page_count(&self) -> usize { + self.page_count + } + + pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { + let end = addr + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?; + if unlikely(end > self.data.len()) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + } + + self.data[addr..end].fill(val); + Ok(()) + } + + pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[u8]) -> Result<()> { + let end = dst.checked_add(src.len()).ok_or_else(|| { + Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len: src.len(), max: self.data.len() }) + })?; + if unlikely(end > self.data.len()) { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: dst, + len: src.len(), + max: self.data.len(), + })); + } + + self.data[dst..end].copy_from_slice(src); + Ok(()) + } + + pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { + // Calculate the end of the source slice + let src_end = src + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?; + if src_end > self.data.len() { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() })); + } + + // Calculate the end of the destination slice + let dst_end = dst + .checked_add(len) + .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?; + if dst_end > self.data.len() { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() })); + } + + // Perform the copy + self.data.copy_within(src..src_end, dst); + Ok(()) + } + + pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { + let current_pages = self.page_count(); + let new_pages = current_pages as i64 + pages_delta as i64; + + if new_pages < 0 || new_pages > MAX_PAGES as i64 { + return None; + } + + if new_pages as usize > self.max_pages() { + log::info!("memory size out of bounds: {}", new_pages); + return None; + } + + let new_size = new_pages as usize * PAGE_SIZE; + if new_size as u64 > MAX_SIZE { + return None; + } + + // Zero initialize the new pages + self.data.resize(new_size, 0); + self.page_count = new_pages as usize; + + log::debug!("memory was {} pages", current_pages); + log::debug!("memory grown by {} pages", pages_delta); + log::debug!("memory grown to {} pages", self.page_count); + + Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier")) + } +} + +#[allow(unsafe_code)] +/// A trait for types that can be loaded from memory +/// +/// # Safety +/// Only implemented for primitive types, unsafe to not allow it for other types. +/// Only actually unsafe to implement if the `unsafe` feature is enabled since there might be +/// UB for loading things things like packed structs +pub(crate) unsafe trait MemLoadable: Sized + Copy { + /// Load a value from memory + fn from_le_bytes(bytes: [u8; T]) -> Self; + /// Load a value from memory + fn from_be_bytes(bytes: [u8; T]) -> Self; +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<1> for u8 { + fn from_le_bytes(bytes: [u8; 1]) -> Self { + bytes[0] + } + fn from_be_bytes(bytes: [u8; 1]) -> Self { + bytes[0] + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<2> for u16 { + fn from_le_bytes(bytes: [u8; 2]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 2]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<4> for u32 { + fn from_le_bytes(bytes: [u8; 4]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 4]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<8> for u64 { + fn from_le_bytes(bytes: [u8; 8]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<16> for u128 { + fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<1> for i8 { + fn from_le_bytes(bytes: [u8; 1]) -> Self { + bytes[0] as i8 + } + fn from_be_bytes(bytes: [u8; 1]) -> Self { + bytes[0] as i8 + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<2> for i16 { + fn from_le_bytes(bytes: [u8; 2]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 2]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<4> for i32 { + fn from_le_bytes(bytes: [u8; 4]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 4]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<8> for i64 { + fn from_le_bytes(bytes: [u8; 8]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<16> for i128 { + fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<4> for f32 { + fn from_le_bytes(bytes: [u8; 4]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 4]) -> Self { + Self::from_be_bytes(bytes) + } +} + +#[allow(unsafe_code)] +unsafe impl MemLoadable<8> for f64 { + fn from_le_bytes(bytes: [u8; 8]) -> Self { + Self::from_le_bytes(bytes) + } + fn from_be_bytes(bytes: [u8; 8]) -> Self { + Self::from_be_bytes(bytes) + } +} diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store/mod.rs similarity index 83% rename from crates/tinywasm/src/store.rs rename to crates/tinywasm/src/store/mod.rs index a4f9b70..f89b931 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -11,6 +11,9 @@ use crate::{ Error, Function, ModuleInstance, Result, Trap, }; +mod memory; +pub(crate) use memory::*; + // global store id counter static STORE_ID: AtomicUsize = AtomicUsize::new(0); @@ -562,131 +565,6 @@ impl TableInstance { } } -pub(crate) const PAGE_SIZE: usize = 65536; -pub(crate) const MAX_PAGES: usize = 65536; -pub(crate) const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; - -/// A WebAssembly Memory Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct MemoryInstance { - pub(crate) kind: MemoryType, - pub(crate) data: Vec, - pub(crate) page_count: usize, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl MemoryInstance { - pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { - assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); - log::debug!("initializing memory with {} pages", kind.page_count_initial); - - Self { - kind, - data: vec![0; PAGE_SIZE * kind.page_count_initial as usize], - page_count: kind.page_count_initial as usize, - _owner: owner, - } - } - - pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> { - let end = addr.checked_add(len).ok_or_else(|| { - Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: data.len(), max: self.data.len() }) - })?; - - if end > self.data.len() || end < addr { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: addr, - len: data.len(), - max: self.data.len(), - })); - } - - // WebAssembly doesn't require alignment for stores - self.data[addr..end].copy_from_slice(data); - Ok(()) - } - - pub(crate) fn max_pages(&self) -> usize { - self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize - } - - pub(crate) fn load(&self, addr: usize, _align: usize, len: usize) -> Result<&[u8]> { - let end = addr - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.max_pages() }))?; - - if end > self.data.len() { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); - } - - // WebAssembly doesn't require alignment for loads - Ok(&self.data[addr..end]) - } - - pub(crate) fn page_count(&self) -> usize { - self.page_count - } - - pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { - let end = addr - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?; - if end > self.data.len() { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); - } - self.data[addr..end].fill(val); - Ok(()) - } - - pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { - let end = src - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?; - if end > self.data.len() || end < src { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() })); - } - let end = dst - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?; - if end > self.data.len() || end < dst { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() })); - } - self.data[dst..end].copy_within(src..end, len); - Ok(()) - } - - pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { - let current_pages = self.page_count(); - let new_pages = current_pages as i64 + pages_delta as i64; - - if new_pages < 0 || new_pages > MAX_PAGES as i64 { - return None; - } - - if new_pages as usize > self.max_pages() { - log::info!("memory size out of bounds: {}", new_pages); - return None; - } - - let new_size = new_pages as usize * PAGE_SIZE; - if new_size as u64 > MAX_SIZE { - return None; - } - - // Zero initialize the new pages - self.data.resize(new_size, 0); - self.page_count = new_pages as usize; - - log::debug!("memory was {} pages", current_pages); - log::debug!("memory grown by {} pages", pages_delta); - log::debug!("memory grown to {} pages", self.page_count); - - Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier")) - } -} - /// A WebAssembly Global Instance /// /// See diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/2.0.csv index b5ea4cc..404fe16 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/2.0.csv @@ -1,2 +1,2 @@ 0.3.0,26722,1161,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":1},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":171,"failed":12},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":594,"failed":186},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.4.0-alpha.0,26861,1022,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0-alpha.0,27464,419,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":53,"failed":64},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index ba13979..9098812 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,4 +1,4 @@ -use crate::{ElemAddr, MemAddr}; +use crate::{DataAddr, ElemAddr, MemAddr}; use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; @@ -266,4 +266,10 @@ pub enum Instruction { TableGrow(TableAddr), TableSize(TableAddr), TableFill(TableAddr), + + // Bulk Memory Instructions + MemoryInit(MemAddr, DataAddr), + MemoryCopy(MemAddr, MemAddr), + MemoryFill(MemAddr), + DataDrop(DataAddr), } diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 8a608d0..2d06488 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -10,7 +10,7 @@ forced-target="wasm32-unknown-unknown" edition="2021" [dependencies] -tinywasm={path="../../crates/tinywasm", features=["parser", "std"]} +tinywasm={path="../../crates/tinywasm", features=["parser", "std", "unsafe"]} [[bin]] name="hello" From 51d0c63f7e6d65204d6fe4ec3c378ef3fbedf119 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 28 Jan 2024 14:30:39 +0100 Subject: [PATCH 16/27] docs: architecture Signed-off-by: Henry Gressmann --- ARCHITECTURE.md | 26 ++++++++++++++++++++++++++ README.md | 7 ++++--- benches/README.md | 13 +++++++++++++ crates/types/src/instructions.rs | 2 +- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 benches/README.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 8705e5a..9665745 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1 +1,27 @@ # TinyWasm's Architecture + +TinyWasm follows the general Runtime Structure described in the [WebAssembly Specification](https://webassembly.github.io/spec/core/exec/runtime.html). +Some key differences are: + +- Values are stored without their type, (as `u64`), and the type is inferred from the instruction that uses them. This is possible because the instructions are validated before execution and the type of each value can be inferred from the instruction. +- TinyWasm has a explicit stack for values, labels and frames. This is mostly for simplicity in the implementation, but also allows for some optimizations. +- Floats always use a canonical NaN representation, the spec allows for multiple NaN representations. +- TinyWasm uses a custom bytecode format (see [Bytecode Format](#bytecode-format) for more details) +- Global state in the `Store` can be addressed from module instances other than the owning module. This is to allow more efficient access to imports and exports. Ownership is still enforced implicitly by requiring a reference to the instance to access it which can not be changed using the WebAssembly instructions. +- The `Store` is not thread-safe. This is to allow for more efficient access to the `Store` and its contents. When later adding support for threads, a `Mutex` can be used to make it thread-safe but the overhead of requiring a lock for every access is not necessary for single-threaded applications. +- TinyWasm is architectured to allow for a JIT compiler to be added later. Functions are stored as FunctionInstances which can contain either a `WasmFunction` or a `HostFunction`. A third variant `JitFunction` could be added later to store a pointer to the compiled function. This would allow for the JIT to be used transparently without changing the rest of the runtime. +- TinyWasm is designed to be used in `no_std` environments. The `std` feature is enabled by default, but can be disabled to remove the dependency on `std` and `std::io`. This is done by disabling the `std` and `parser` features. The `logging` feature can also be disabled to remove the dependency on `log`. This is not recommended, since `libm` is not as performant as the compiler's math intrinsics, especially on wasm32 targets, but can be useful for resource-constrained devices or other environments where `std` is not available such as OS kernels. +- Call Frames are executed in a loop instead of recursively. This allows the use of a single stack for all frames and makes it easier to pause execution and resume it later, or to step through the code one instruction at a time. + +## Bytecode Format + +To improve performance and reduce code size, instructions are encoded as enum variants instead of opcodes. +This allows preprocessing the bytecode into a more compact format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. + +Some instructions are split into multiple variants to reduce the size of the enum (e.g. `br_table` and `br_label`). +Additionally, label instructions contain offsets relative to the current instruction to make branching faster and easier to implement. +Also, `End` instructions are split into `End` and `EndBlock`. + +See [instructions.rs](./crates/types/src/instructions.rs) for the full list of instructions. + +This is a area that can still be improved. While being able to load pre-processes bytecode directly into memory is nice, in-place decoding could achieve similar speeds, see [A fast in-place interpreter for WebAssembly](https://arxiv.org/abs/2205.01183). diff --git a/README.md b/README.md index 57728a4..9fd38ca 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

TinyWasm

A tiny WebAssembly Runtime written in Rust - +
[![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg)](./LICENSE-APACHE) @@ -18,7 +18,6 @@ Some APIs to interact with the runtime are not yet exposed, and the existing one Results of the tests can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). TinyWasm is not designed for performance, but rather for size and portability. However, it is still reasonably fast. -There are a couple of low-hanging fruits on the performance side, but they are not a priority at the moment. ## Supported Proposals @@ -55,9 +54,11 @@ $ cargo add tinywasm Enables logging using the `log` crate. This is enabled by default. - **`parser`**\ Enables the `tinywasm-parser` crate. This is enabled by default. +- **`unsafe`**\ + Uses `unsafe` code to improve performance, particularly in Memory access 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 built-in math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)). +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. ## Performance diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 0000000..6ed8cc5 --- /dev/null +++ b/benches/README.md @@ -0,0 +1,13 @@ +# Benchmark results + +Coming soon. + +# Benchmarking + +Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs) and can be found in the `benches` directory. + +## Running benchmarks + +```sh +$ cargo bench --bench +``` diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 9098812..1061d10 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -43,7 +43,7 @@ pub enum ConstInstruction { /// * `br_table` stores the jump lables in the following `br_label` instructions to keep this enum small. /// * Lables/Blocks: we store the label end offset in the instruction itself and /// have seperate EndBlockFrame and EndFunc instructions to mark the end of a block or function. -/// This makes it easier to implement the label stack (we call it BlockFrameStack) iteratively. +/// This makes it easier to implement the label stack iteratively. /// /// See #[derive(Debug, Clone, Copy)] From 716b7631ed955a808180c28b4ab985d3b0300122 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 28 Jan 2024 22:50:31 +0100 Subject: [PATCH 17/27] docs: tests Signed-off-by: Henry Gressmann --- crates/tinywasm/tests/generate-charts.rs | 8 ++- crates/tinywasm/tests/generated/README.md | 7 +++ .../tinywasm/tests/generated/progress-2.0.svg | 54 +++++++++++++++++++ .../tinywasm/tests/generated/progress-mvp.svg | 2 +- 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 crates/tinywasm/tests/generated/README.md create mode 100644 crates/tinywasm/tests/generated/progress-2.0.svg diff --git a/crates/tinywasm/tests/generate-charts.rs b/crates/tinywasm/tests/generate-charts.rs index e8e323d..27c0782 100644 --- a/crates/tinywasm/tests/generate-charts.rs +++ b/crates/tinywasm/tests/generate-charts.rs @@ -11,7 +11,6 @@ fn generate_charts() -> Result<()> { return Ok(()); } - // 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"), @@ -19,5 +18,12 @@ fn generate_charts() -> Result<()> { println!("created progress chart: ./tests/generated/progress-mvp.svg"); + charts::create_progress_chart( + std::path::Path::new("./tests/generated/2.0.csv"), + std::path::Path::new("./tests/generated/progress-2.0.svg"), + )?; + + println!("created progress chart: ./tests/generated/progress-2.0.svg"); + Ok(()) } diff --git a/crates/tinywasm/tests/generated/README.md b/crates/tinywasm/tests/generated/README.md new file mode 100644 index 0000000..40acee5 --- /dev/null +++ b/crates/tinywasm/tests/generated/README.md @@ -0,0 +1,7 @@ +# WebAssembly 1.0 Test Results (out of 20254 tests) + +![](./progress-mvp.svg) + +# WebAssembly 2.0 Test Results (out of 27883 tests) + +![](./progress-2.0.svg) diff --git a/crates/tinywasm/tests/generated/progress-2.0.svg b/crates/tinywasm/tests/generated/progress-2.0.svg new file mode 100644 index 0000000..e8e9313 --- /dev/null +++ b/crates/tinywasm/tests/generated/progress-2.0.svg @@ -0,0 +1,54 @@ + + + +MVP TESTSUITE + + +Tests Passed + + +TinyWasm Version + + + + + + + + + +0 + + + +5000 + + + +10000 + + + +15000 + + + +20000 + + + +25000 + + + + +v0.3.0 (26722) + + + +v0.4.0-alpha.0 (27464) + + + + + diff --git a/crates/tinywasm/tests/generated/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg index c1200f5..d9697ed 100644 --- a/crates/tinywasm/tests/generated/progress-mvp.svg +++ b/crates/tinywasm/tests/generated/progress-mvp.svg @@ -56,9 +56,9 @@ v0.2.0 (19344) v0.3.0 (20254) - + From fd91a1c273b7595f74e4eebac9497b10e0a75e9f Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Sun, 28 Jan 2024 22:52:23 +0100 Subject: [PATCH 18/27] docs: fix chart title Signed-off-by: Henry Gressmann --- crates/tinywasm/tests/charts/progress.rs | 4 ++-- crates/tinywasm/tests/generate-charts.rs | 2 ++ crates/tinywasm/tests/generated/progress-2.0.svg | 2 +- crates/tinywasm/tests/generated/progress-mvp.svg | 8 ++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/tinywasm/tests/charts/progress.rs b/crates/tinywasm/tests/charts/progress.rs index 0bc66a6..1ecc09c 100644 --- a/crates/tinywasm/tests/charts/progress.rs +++ b/crates/tinywasm/tests/charts/progress.rs @@ -6,7 +6,7 @@ use std::path::Path; const FONT: &str = "Victor Mono"; -pub fn create_progress_chart(csv_path: &Path, output_path: &Path) -> Result<()> { +pub fn create_progress_chart(name: &str, csv_path: &Path, output_path: &Path) -> Result<()> { let file = File::open(csv_path)?; let reader = io::BufReader::new(file); @@ -41,7 +41,7 @@ pub fn create_progress_chart(csv_path: &Path, output_path: &Path) -> Result<()> .y_label_area_size(70) .margin(10) .margin_top(20) - .caption("MVP TESTSUITE", (FONT, 30.0, FontStyle::Bold)) + .caption(name, (FONT, 30.0, FontStyle::Bold)) .build_cartesian_2d((0..(versions.len() - 1) as u32).into_segmented(), 0..max_tests)?; chart diff --git a/crates/tinywasm/tests/generate-charts.rs b/crates/tinywasm/tests/generate-charts.rs index 27c0782..ec48703 100644 --- a/crates/tinywasm/tests/generate-charts.rs +++ b/crates/tinywasm/tests/generate-charts.rs @@ -12,6 +12,7 @@ fn generate_charts() -> Result<()> { } charts::create_progress_chart( + "WebAssembly 1.0 Test Suite", std::path::Path::new("./tests/generated/mvp.csv"), std::path::Path::new("./tests/generated/progress-mvp.svg"), )?; @@ -19,6 +20,7 @@ fn generate_charts() -> Result<()> { println!("created progress chart: ./tests/generated/progress-mvp.svg"); charts::create_progress_chart( + "WebAssembly 2.0 Test Suite", std::path::Path::new("./tests/generated/2.0.csv"), std::path::Path::new("./tests/generated/progress-2.0.svg"), )?; diff --git a/crates/tinywasm/tests/generated/progress-2.0.svg b/crates/tinywasm/tests/generated/progress-2.0.svg index e8e9313..c869fcb 100644 --- a/crates/tinywasm/tests/generated/progress-2.0.svg +++ b/crates/tinywasm/tests/generated/progress-2.0.svg @@ -1,7 +1,7 @@ -MVP TESTSUITE +WebAssembly 2.0 Test Suite Tests Passed diff --git a/crates/tinywasm/tests/generated/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg index d9697ed..b8a3e35 100644 --- a/crates/tinywasm/tests/generated/progress-mvp.svg +++ b/crates/tinywasm/tests/generated/progress-mvp.svg @@ -1,7 +1,7 @@ -MVP TESTSUITE +WebAssembly 1.0 Test Suite Tests Passed @@ -56,9 +56,9 @@ v0.2.0 (19344) v0.3.0 (20254) - - - + + + From b0b4eba3b7ff5bfbcb8835c473ac3945c5160332 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 29 Jan 2024 17:31:01 +0100 Subject: [PATCH 19/27] feat: Bulk Memory Operations Proposal Signed-off-by: Henry Gressmann --- README.md | 4 +- crates/parser/src/conversion.rs | 1 - crates/parser/src/module.rs | 8 +- crates/tinywasm/src/imports.rs | 15 ++- crates/tinywasm/src/instance.rs | 30 ++++- crates/tinywasm/src/reference.rs | 126 +++++++++++------- .../tinywasm/src/runtime/interpreter/mod.rs | 39 +++++- .../src/runtime/interpreter/no_std_floats.rs | 16 +++ crates/tinywasm/src/store/mod.rs | 76 +++++++---- crates/tinywasm/tests/generated/2.0.csv | 2 +- examples/rust/build.sh | 2 +- examples/wasm-rust.rs | 6 +- 12 files changed, 223 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 9fd38ca..c899701 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ # Status -TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress (notably `simd` and `bulk-memory-operations` are not implemented yet). This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). +TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete. Results of the tests can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). @@ -24,7 +24,7 @@ TinyWasm is not designed for performance, but rather for size and portability. H - [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) - **Fully implemented** - [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) - **Fully implemented** - [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented** -- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **_Partially implemented_** +- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **Fully implemented** (as of version `0.4.0`) - [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_** - [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** (not tested yet) - [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_** (only 32-bit addressing is supported at the moment, but larger memories can be created) diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 79069e1..7c10d62 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -107,7 +107,6 @@ pub(crate) fn convert_module_tables Result> { let table_type = table_types.into_iter().map(|table| convert_module_table(table?)).collect::>>()?; - Ok(table_type) } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 5bc6a7e..f5c01ac 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -112,7 +112,6 @@ impl ModuleReader { 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)?; @@ -140,6 +139,13 @@ impl ModuleReader { validator.data_section(&reader)?; self.data = conversion::convert_module_data_sections(reader)?; } + DataCountSection { count, range } => { + debug!("Found data count section"); + if !self.data.is_empty() { + return Err(ParseError::DuplicateSection("Data count section".into())); + } + validator.data_count_section(count, &range)?; + } CodeSectionStart { count, range, .. } => { debug!("Found code section ({} functions)", count); if !self.code.is_empty() { diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 0b16970..38d0707 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -62,13 +62,13 @@ pub struct FuncContext<'a> { } impl FuncContext<'_> { - /// Get a mutable reference to the store - pub fn store_mut(&mut self) -> &mut crate::Store { + /// Get a reference to the store + pub fn store(&self) -> &crate::Store { self.store } - /// Get a reference to the store - pub fn store(&self) -> &crate::Store { + /// Get a mutable reference to the store + pub fn store_mut(&mut self) -> &mut crate::Store { self.store } @@ -78,9 +78,14 @@ impl FuncContext<'_> { } /// Get a reference to an exported memory - pub fn memory(&mut self, name: &str) -> Result { + pub fn exported_memory(&mut self, name: &str) -> Result> { self.module().exported_memory(self.store, name) } + + /// Get a reference to an exported memory + pub fn exported_memory_mut(&mut self, name: &str) -> Result> { + self.module().exported_memory_mut(self.store, name) + } } impl Debug for HostFunction { diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index ecd6ce8..ad6c0f1 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -3,7 +3,7 @@ use tinywasm_types::*; use crate::{ func::{FromWasmValueTuple, IntoWasmValueTuple}, - log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, Module, Result, Store, + log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store, }; /// An instanciated WebAssembly module @@ -152,6 +152,11 @@ impl ModuleInstance { *self.0.mem_addrs.get(addr as usize).expect("No mem addr for mem, this is a bug") } + // resolve a data address to the global store address + pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> MemAddr { + *self.0.data_addrs.get(addr as usize).expect("No data addr for data, this is a bug") + } + // resolve a memory address to the global store address pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { *self.0.elem_addrs.get(addr as usize).expect("No elem addr for elem, this is a bug") @@ -190,7 +195,7 @@ impl ModuleInstance { } /// Get an exported memory by name - pub fn exported_memory(&self, store: &mut Store, name: &str) -> Result { + pub fn exported_memory<'a>(&self, store: &'a mut Store, name: &str) -> Result> { let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?; let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {}", name))); @@ -199,11 +204,28 @@ impl ModuleInstance { Ok(mem) } + /// Get an exported memory by name + pub fn exported_memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?; + let ExternVal::Memory(mem_addr) = export else { + return Err(Error::Other(format!("Export is not a memory: {}", name))); + }; + let mem = self.memory_mut(store, mem_addr)?; + Ok(mem) + } + /// Get a memory by address - pub fn memory(&self, store: &Store, addr: MemAddr) -> Result { + pub fn memory<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { + let addr = self.resolve_mem_addr(addr); + let mem = store.get_mem(addr as usize)?; + Ok(MemoryRef { instance: mem.borrow() }) + } + + /// Get a memory by address (mutable) + pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { let addr = self.resolve_mem_addr(addr); let mem = store.get_mem(addr as usize)?; - Ok(MemoryRef { instance: mem.clone() }) + Ok(MemoryRefMut { instance: mem.borrow_mut() }) } /// Get the start function of the module diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index ed09530..a34e30b 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,5 +1,5 @@ use core::{ - cell::{Ref, RefCell}, + cell::{Ref, RefCell, RefMut}, ffi::CStr, }; @@ -15,91 +15,121 @@ use tinywasm_types::WasmValue; // This module essentially contains the public APIs to interact with the data stored in the store /// A reference to a memory instance -#[derive(Debug, Clone)] -pub struct MemoryRef { - pub(crate) instance: Rc>, +#[derive(Debug)] +pub struct MemoryRef<'a> { + pub(crate) instance: Ref<'a, MemoryInstance>, } /// A borrowed reference to a memory instance #[derive(Debug)] -pub struct BorrowedMemory<'a> { - pub(crate) instance: Ref<'a, MemoryInstance>, +pub struct MemoryRefMut<'a> { + pub(crate) instance: RefMut<'a, MemoryInstance>, } -impl<'a> BorrowedMemory<'a> { +impl<'a> MemoryRefLoad for MemoryRef<'a> { /// Load a slice of memory - pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { self.instance.load(offset, 0, len) } +} - /// Load a C-style string from memory - pub fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> { - let bytes = self.load(offset, len)?; - CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) +impl<'a> MemoryRefLoad for MemoryRefMut<'a> { + /// Load a slice of memory + fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.instance.load(offset, 0, len) } +} - /// Load a C-style string from memory, stopping at the first nul byte - pub fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> { - let bytes = self.load(offset, max_len)?; - CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) +impl MemoryRef<'_> { + /// Load a slice of memory + pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.instance.load(offset, 0, len) } -} -impl MemoryRef { - /// Borrow the memory instance - /// - /// This is useful for when you want to load only a reference to a slice of memory - /// without copying the data. The borrow should be dropped before any other memory - /// operations are performed. - pub fn borrow(&self) -> BorrowedMemory<'_> { - BorrowedMemory { instance: self.instance.borrow() } + /// Load a slice of memory as a vector + pub fn load_vec(&self, offset: usize, len: usize) -> Result> { + self.load(offset, len).map(|x| x.to_vec()) } +} +impl MemoryRefMut<'_> { /// Load a slice of memory + pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.instance.load(offset, 0, len) + } + + /// Load a slice of memory as a vector pub fn load_vec(&self, offset: usize, len: usize) -> Result> { - self.instance.borrow().load(offset, 0, len).map(|x| x.to_vec()) + self.load(offset, len).map(|x| x.to_vec()) } /// Grow the memory by the given number of pages - pub fn grow(&self, delta_pages: i32) -> Option { - self.instance.borrow_mut().grow(delta_pages) + pub fn grow(&mut self, delta_pages: i32) -> Option { + self.instance.grow(delta_pages) } /// Get the current size of the memory in pages - pub fn page_count(&self) -> usize { - self.instance.borrow().page_count() + pub fn page_count(&mut self) -> usize { + self.instance.page_count() } /// Copy a slice of memory to another place in memory - pub fn copy_within(&self, src: usize, dst: usize, len: usize) -> Result<()> { - self.instance.borrow_mut().copy_within(src, dst, len) + pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> { + self.instance.copy_within(src, dst, len) } /// Fill a slice of memory with a value - pub fn fill(&self, offset: usize, len: usize, val: u8) -> Result<()> { - self.instance.borrow_mut().fill(offset, len, val) + pub fn fill(&mut self, offset: usize, len: usize, val: u8) -> Result<()> { + self.instance.fill(offset, len, val) + } + + /// Store a slice of memory + pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> { + self.instance.store(offset, 0, data, len) + } +} + +#[doc(hidden)] +pub trait MemoryRefLoad { + fn load(&self, offset: usize, len: usize) -> Result<&[u8]>; + fn load_vec(&self, offset: usize, len: usize) -> Result> { + self.load(offset, len).map(|x| x.to_vec()) + } +} + +/// Convenience methods for loading strings from memory +pub trait MemoryStringExt: MemoryRefLoad { + /// Load a C-style string from memory + fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> { + let bytes = self.load(offset, len)?; + CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) + } + + /// Load a C-style string from memory, stopping at the first nul byte + fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> { + let bytes = self.load(offset, max_len)?; + CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) } /// Load a UTF-8 string from memory - pub fn load_string(&self, offset: usize, len: usize) -> Result { - let bytes = self.load_vec(offset, len)?; - String::from_utf8(bytes).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string())) + fn load_string(&self, offset: usize, len: usize) -> Result { + let bytes = self.load(offset, len)?; + String::from_utf8(bytes.to_vec()).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string())) } /// Load a C-style string from memory - pub fn load_cstring(&self, offset: usize, len: usize) -> Result { - Ok(CString::from(self.borrow().load_cstr(offset, len)?)) + fn load_cstring(&self, offset: usize, len: usize) -> Result { + Ok(CString::from(self.load_cstr(offset, len)?)) } /// Load a C-style string from memory, stopping at the first nul byte - pub fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result { - Ok(CString::from(self.borrow().load_cstr_until_nul(offset, max_len)?)) + fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result { + Ok(CString::from(self.load_cstr_until_nul(offset, max_len)?)) } /// Load a JavaScript-style utf-16 string from memory - pub fn load_js_string(&self, offset: usize, len: usize) -> Result { - let memref = self.borrow(); - let bytes = memref.load(offset, len)?; + fn load_js_string(&self, offset: usize, len: usize) -> Result { + let bytes = self.load(offset, len)?; let mut string = String::new(); for i in 0..(len / 2) { let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]); @@ -109,13 +139,11 @@ impl MemoryRef { } Ok(string) } - - /// Store a slice of memory - pub fn store(&self, offset: usize, len: usize, data: &[u8]) -> Result<()> { - self.instance.borrow_mut().store(offset, 0, data, len) - } } +impl MemoryStringExt for MemoryRef<'_> {} +impl MemoryStringExt for MemoryRefMut<'_> {} + /// A reference to a global instance #[derive(Debug, Clone)] pub struct GlobalRef { diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index 3c3b776..79abbf3 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -418,12 +418,39 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M mem.fill(dst as usize, size as usize, val as u8)?; } - // MemoryInit(data_index, mem_index) => {} - // DataDrop(data_index) => { - // // let data_idx = module.resolve_data_addr(*data_index); - // // let data = store.get_data(data_idx as usize)?; - // // data.borrow_mut().drop()?; - // } + MemoryInit(data_index, mem_index) => { + let size = stack.values.pop_t::()? as usize; + let offset = stack.values.pop_t::()? as usize; + let dst = stack.values.pop_t::()? as usize; + + let data_idx = module.resolve_data_addr(*data_index); + let Some(ref data) = store.get_data(data_idx as usize)?.data else { + cold(); + return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()); + }; + + let mem_idx = module.resolve_mem_addr(*mem_index); + let mem = store.get_mem(mem_idx as usize)?; + + let data_len = data.len(); + if offset + size > data_len { + cold(); + return Err(Trap::MemoryOutOfBounds { offset, len: size, max: data_len }.into()); + } + + let mut mem = mem.borrow_mut(); + let data = &data[offset..(offset + size)]; + + // mem.store checks bounds + mem.store(dst, 0, data, size)?; + } + + DataDrop(data_index) => { + let data_idx = module.resolve_data_addr(*data_index); + let data = store.get_data_mut(data_idx as usize)?; + data.drop(); + } + I32Store(arg) => mem_store!(i32, arg, stack, store, module), I64Store(arg) => mem_store!(i64, arg, stack, store, module), F32Store(arg) => mem_store!(f32, arg, stack, store, module), diff --git a/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs b/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs index 095fe9f..5620249 100644 --- a/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs @@ -10,67 +10,83 @@ pub(super) trait FExt { } impl FExt for f64 { + #[inline] fn round(self) -> Self { libm::round(self) } + #[inline] fn abs(self) -> Self { libm::fabs(self) } + #[inline] fn signum(self) -> Self { libm::copysign(1.0, self) } + #[inline] fn ceil(self) -> Self { libm::ceil(self) } + #[inline] fn floor(self) -> Self { libm::floor(self) } + #[inline] fn trunc(self) -> Self { libm::trunc(self) } + #[inline] fn sqrt(self) -> Self { libm::sqrt(self) } + #[inline] fn copysign(self, other: Self) -> Self { libm::copysign(self, other) } } impl FExt for f32 { + #[inline] fn round(self) -> Self { libm::roundf(self) } + #[inline] fn abs(self) -> Self { libm::fabsf(self) } + #[inline] fn signum(self) -> Self { libm::copysignf(1.0, self) } + #[inline] fn ceil(self) -> Self { libm::ceilf(self) } + #[inline] fn floor(self) -> Self { libm::floorf(self) } + #[inline] fn trunc(self) -> Self { libm::truncf(self) } + #[inline] fn sqrt(self) -> Self { libm::sqrtf(self) } + #[inline] fn copysign(self, other: Self) -> Self { libm::copysignf(self, other) } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index f89b931..1526eb1 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -287,40 +287,38 @@ impl Store { let data_count = self.data.datas.len(); let mut data_addrs = Vec::with_capacity(data_count); for (i, data) in datas.into_iter().enumerate() { - use tinywasm_types::DataKind::*; - match data.kind { - Active { mem: mem_addr, offset } => { - // a. Assert: memidx == 0 - if mem_addr != 0 { - return Err(Error::UnsupportedFeature("data segments for non-zero memories".to_string())); - } + let data_val = + match data.kind { + tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { + // a. Assert: memidx == 0 + if mem_addr != 0 { + return Err(Error::UnsupportedFeature("data segments for non-zero memories".to_string())); + } - let mem_addr = mem_addrs - .get(mem_addr as usize) - .copied() - .ok_or_else(|| Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)))?; + let mem_addr = mem_addrs.get(mem_addr as usize).copied().ok_or_else(|| { + Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)) + })?; - let offset = self.eval_i32_const(&offset)?; + let offset = self.eval_i32_const(&offset)?; - let mem = - self.data.memories.get_mut(mem_addr as usize).ok_or_else(|| { + let mem = self.data.memories.get_mut(mem_addr as usize).ok_or_else(|| { Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)) })?; - // See comment for active element sections in the function above why we need to do this here - if let Err(Error::Trap(trap)) = - mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len()) - { - return Ok((data_addrs.into_boxed_slice(), Some(trap))); - } + // See comment for active element sections in the function above why we need to do this here + if let Err(Error::Trap(trap)) = + mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len()) + { + return Ok((data_addrs.into_boxed_slice(), Some(trap))); + } - // drop the data - continue; - } - Passive => {} - } + // drop the data + None + } + tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), + }; - self.data.datas.push(DataInstance::new(data.data.to_vec(), idx)); + self.data.datas.push(DataInstance::new(data_val, idx)); data_addrs.push((i + data_count) as Addr); } @@ -413,6 +411,16 @@ impl Store { self.data.tables.get(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) } + /// Get the data at the actual index in the store + pub(crate) fn get_data(&self, addr: usize) -> Result<&DataInstance> { + self.data.datas.get(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) + } + + /// Get the data at the actual index in the store + pub(crate) fn get_data_mut(&mut self, addr: usize) -> Result<&mut DataInstance> { + self.data.datas.get_mut(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) + } + /// Get the element at the actual index in the store pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { self.data.elements.get(addr).ok_or_else(|| Error::Other(format!("element {} not found", addr))) @@ -621,12 +629,22 @@ impl ElementInstance { /// See #[derive(Debug)] pub(crate) struct DataInstance { - pub(crate) _data: Vec, + pub(crate) data: Option>, pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl DataInstance { - pub(crate) fn new(data: Vec, owner: ModuleInstanceAddr) -> Self { - Self { _data: data, _owner: owner } + pub(crate) fn new(data: Option>, owner: ModuleInstanceAddr) -> Self { + Self { data, _owner: owner } + } + + pub(crate) fn drop(&mut self) -> Option<()> { + match self.data { + None => None, + Some(_) => { + let _ = self.data.take(); + Some(()) + } + } } } diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/2.0.csv index 404fe16..fbf18ae 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/2.0.csv @@ -1,2 +1,2 @@ 0.3.0,26722,1161,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":1},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":171,"failed":12},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":594,"failed":186},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.4.0-alpha.0,27464,419,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":53,"failed":64},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0-alpha.0,27549,334,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/examples/rust/build.sh b/examples/rust/build.sh index a13357d..5450345 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -10,7 +10,7 @@ dest_dir="out" mkdir -p "$dest_dir" for bin in "${bins[@]}"; do - RUSTFLAGS="-C target-feature=+reference-types -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" + RUSTFLAGS="-C target-feature=+reference-types,+bulk-memory -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O --intrinsic-lowering -O diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 468ab2e..96e3419 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,5 +1,5 @@ use color_eyre::eyre::Result; -use tinywasm::{Extern, FuncContext, Imports, Module, Store}; +use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; fn main() -> Result<()> { let args = std::env::args().collect::>(); @@ -56,7 +56,7 @@ fn hello() -> Result<()> { "env", "print_utf8", Extern::typed_func(|mut ctx: FuncContext<'_>, args: (i64, i32)| { - let mem = ctx.memory("memory")?; + let mem = ctx.exported_memory("memory")?; let ptr = args.0 as usize; let len = args.1 as usize; let string = mem.load_string(ptr, len)?; @@ -69,7 +69,7 @@ fn hello() -> Result<()> { let arg_ptr = instance.exported_func::<(), i32>(&mut store, "arg_ptr")?.call(&mut store, ())?; let arg = b"world"; - instance.exported_memory(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; + instance.exported_memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; let hello = instance.exported_func::(&mut store, "hello")?; hello.call(&mut store, arg.len() as i32)?; From 4c9385df1bcc0098c7e1db7a2f7c7e3dbeb00c8b Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 29 Jan 2024 17:53:39 +0100 Subject: [PATCH 20/27] chore: improve docs Signed-off-by: Henry Gressmann --- ARCHITECTURE.md | 1 + README.md | 10 ++++++++-- benches/README.md | 20 +++++++++++++++++++- examples/rust/build.sh | 4 +++- examples/rust/src/fibonacci.rs | 8 ++++++++ 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9665745..28607da 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -12,6 +12,7 @@ Some key differences are: - TinyWasm is architectured to allow for a JIT compiler to be added later. Functions are stored as FunctionInstances which can contain either a `WasmFunction` or a `HostFunction`. A third variant `JitFunction` could be added later to store a pointer to the compiled function. This would allow for the JIT to be used transparently without changing the rest of the runtime. - TinyWasm is designed to be used in `no_std` environments. The `std` feature is enabled by default, but can be disabled to remove the dependency on `std` and `std::io`. This is done by disabling the `std` and `parser` features. The `logging` feature can also be disabled to remove the dependency on `log`. This is not recommended, since `libm` is not as performant as the compiler's math intrinsics, especially on wasm32 targets, but can be useful for resource-constrained devices or other environments where `std` is not available such as OS kernels. - Call Frames are executed in a loop instead of recursively. This allows the use of a single stack for all frames and makes it easier to pause execution and resume it later, or to step through the code one instruction at a time. +- While other interpreters convert `locals` to be register-based when parsing the function body, TinyWasm keeps them in a stack. This is mostly for simplicity in the implementation, but performance is still comparable or better than other interpreters. ## Bytecode Format diff --git a/README.md b/README.md index c899701..e7a66ae 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,17 @@

TinyWasm

A tiny WebAssembly Runtime written in Rust - +
[![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg)](./LICENSE-APACHE) +# Why TinyWasm? + +- **Tiny** - Designed to be as small as possible without sacrificing too much performance or functionality. +- **Fast enough** - TinyWasm is reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details. +- **Portable** - Runs on any platform llvm supports, including WebAssembly. Minimal external dependencies. + # Status TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). @@ -17,7 +23,7 @@ TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete. Results of the tests can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). -TinyWasm is not designed for performance, but rather for size and portability. However, it is still reasonably fast. +TinyWasm is not designed for performance, but rather for simplicity, size and portability. However, it is still reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details. ## Supported Proposals diff --git a/benches/README.md b/benches/README.md index 6ed8cc5..39ba321 100644 --- a/benches/README.md +++ b/benches/README.md @@ -1,8 +1,26 @@ # Benchmark results +All benchmarks are run on a Ryzen 7 5800X, with 32GB of RAM, running Linux 6.6 with `intel_pstate=passive split_lock_detect=off mitigations=off`. + +## Results + Coming soon. -# Benchmarking +## WebAssembly Settings + +All WebAssembly files are compiled with the following settings: + +- `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. +- `reference-types`, `bulk-memory`, `mutable-globals` proposals are enabled. + +## Runtime Settings + +All runtimes are compiled with the following settings: + +- `unsafe` features are enabled +- `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. + +## Benchmarking Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs) and can be found in the `benches` directory. diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 5450345..cabf00e 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -6,11 +6,13 @@ exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" +features="+reference-types,+bulk-memory,+mutable-globals" + # ensure out dir exists mkdir -p "$dest_dir" for bin in "${bins[@]}"; do - RUSTFLAGS="-C target-feature=+reference-types,+bulk-memory -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" + RUSTFLAGS="-C target-feature=$features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O --intrinsic-lowering -O diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs index 3e4be73..7924aef 100644 --- a/examples/rust/src/fibonacci.rs +++ b/examples/rust/src/fibonacci.rs @@ -19,3 +19,11 @@ pub extern "C" fn fibonacci(n: i32) -> i32 { } sum } + +#[no_mangle] +pub extern "C" fn fibonacci_recursive(n: i32) -> i32 { + if n <= 1 { + return n; + } + fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2) +} From f8651619e576ac97209b72660144568b54eb965c Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 29 Jan 2024 20:39:31 +0100 Subject: [PATCH 21/27] feat: pre-processed wasm Signed-off-by: Henry Gressmann --- ARCHITECTURE.md | 3 +- Cargo.lock | 31 +++- benches/fibonacci.rs | 6 +- benches/selfhosted.rs | 4 +- crates/parser/Cargo.toml | 4 +- crates/parser/src/lib.rs | 22 +-- crates/tinywasm/Cargo.toml | 5 +- crates/tinywasm/src/store/data.rs | 27 +++ crates/tinywasm/src/store/element.rs | 19 +++ crates/tinywasm/src/store/function.rs | 11 ++ crates/tinywasm/src/store/global.rs | 39 +++++ crates/tinywasm/src/store/memory.rs | 162 ++++++++---------- crates/tinywasm/src/store/mod.rs | 218 ++----------------------- crates/tinywasm/src/store/table.rs | 123 ++++++++++++++ crates/tinywasm/tests/testsuite/run.rs | 6 +- crates/types/Cargo.toml | 10 +- crates/types/src/archive.rs | 92 +++++++++++ crates/types/src/instructions.rs | 14 +- crates/types/src/lib.rs | 114 ++++++++++--- examples/rust/src/tinywasm_no_std.rs | 33 ++++ examples/wasm-rust.rs | 18 +- 21 files changed, 588 insertions(+), 373 deletions(-) create mode 100644 crates/tinywasm/src/store/data.rs create mode 100644 crates/tinywasm/src/store/element.rs create mode 100644 crates/tinywasm/src/store/function.rs create mode 100644 crates/tinywasm/src/store/global.rs create mode 100644 crates/tinywasm/src/store/table.rs create mode 100644 crates/types/src/archive.rs create mode 100644 examples/rust/src/tinywasm_no_std.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 28607da..d3816d0 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -17,7 +17,8 @@ Some key differences are: ## Bytecode Format To improve performance and reduce code size, instructions are encoded as enum variants instead of opcodes. -This allows preprocessing the bytecode into a more compact format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. +This allows preprocessing the bytecode into a more compact format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. See this [blog post](https://wasmer.io/posts/improving-with-zero-copy-deserialization) by Wasmer +for more details which inspired this design. Some instructions are split into multiple variants to reduce the size of the enum (e.g. `br_table` and `br_label`). Additionally, label instructions contain offsets relative to the current instruction to make branching faster and easier to implement. diff --git a/Cargo.lock b/Cargo.lock index 941a5e1..6da632f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -227,7 +227,18 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ - "bytecheck_derive", + "bytecheck_derive 0.6.11", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41502630fe304ce54cbb2f8389e017784dee2b0328147779fcbe43b9db06d35d" +dependencies = [ + "bytecheck_derive 0.7.0", "ptr_meta", "simdutf8", ] @@ -243,6 +254,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytecheck_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda88c587085bc07dc201ab9df871bd9baa5e07f7754b745e4d7194b43ac1eda" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.14.0" @@ -1931,7 +1953,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", ] [[package]] @@ -1941,7 +1963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" dependencies = [ "bitvec", - "bytecheck", + "bytecheck 0.6.11", "bytes", "hashbrown 0.12.3", "indexmap 1.9.3", @@ -2339,6 +2361,7 @@ dependencies = [ name = "tinywasm-types" version = "0.3.0" dependencies = [ + "bytecheck 0.7.0", "log", "rkyv", ] @@ -2710,7 +2733,7 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae2c892882f0b416783fb4310e5697f5c30587f6f9555f9d4f2be85ab39d5d3d" dependencies = [ - "bytecheck", + "bytecheck 0.6.11", "enum-iterator", "enumset", "indexmap 1.9.3", diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index 5d8a0bd..7c77ebf 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -9,7 +9,7 @@ fn run_tinywasm(module: TinyWasmModule, iterations: i32) { let mut store = Store::default(); let imports = Imports::default(); let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::(&mut store, "fibonacci").expect("exported_func"); + let hello = instance.exported_func::(&store, "fibonacci").expect("exported_func"); hello.call(&mut store, iterations).expect("call"); } @@ -29,8 +29,8 @@ fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_module(FIBONACCI); let mut group = c.benchmark_group("fibonacci"); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone(), black_box(50)))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(black_box(50)))); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone(), black_box(60)))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(black_box(60)))); } criterion_group!( diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index f78d958..b9df1af 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -10,7 +10,7 @@ fn run_tinywasm(module: TinyWasmModule) { let mut imports = Imports::default(); imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::<(), ()>(&mut store, "hello").expect("exported_func"); + let hello = instance.exported_func::<(), ()>(&store, "hello").expect("exported_func"); hello.call(&mut store, ()).expect("call"); } @@ -32,7 +32,7 @@ fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("selfhosted"); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi())); + group.bench_function("wasmi", |b| b.iter(run_wasmi)); } criterion_group!( diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 1592f85..df8acce 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -11,9 +11,9 @@ repository.workspace=true # fork of wasmparser with no_std support, see https://github.com/bytecodealliance/wasmtime/issues/3495 wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} log={version="0.4", optional=true} -tinywasm-types={version="0.3.0-alpha.0", path="../types"} +tinywasm-types={version="0.3.0-alpha.0", path="../types", default-features=false} [features] default=["std", "logging"] logging=["log"] -std=[] +std=["tinywasm-types/std"] diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index edcd280..c608232 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -25,7 +25,7 @@ mod module; use alloc::{string::ToString, vec::Vec}; pub use error::*; use module::ModuleReader; -use tinywasm_types::WasmFunction; +use tinywasm_types::{TypedWasmFunction, WasmFunction}; use wasmparser::Validator; pub use tinywasm_types::TinyWasmModule; @@ -116,19 +116,13 @@ impl TryFrom for TinyWasmModule { .code .into_iter() .zip(code_type_addrs) - .map(|(f, ty_idx)| { - ( - ty_idx, - WasmFunction { - instructions: f.body, - locals: f.locals, - ty: reader - .func_types - .get(ty_idx as usize) - .expect("No func type for func, this is a bug") - .clone(), - }, - ) + .map(|(f, ty_idx)| TypedWasmFunction { + type_addr: ty_idx, + wasm_function: WasmFunction { + instructions: f.body, + locals: f.locals, + ty: reader.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(), + }, }) .collect::>(); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index a9e24bf..dd76f3d 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -29,11 +29,12 @@ plotters={version="0.3"} pretty_env_logger="0.5" [features] -default=["std", "parser", "logging"] +default=["std", "parser", "logging", "archive"] logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] -unsafe=[] +unsafe=["tinywasm-types/unsafe"] +archive=["tinywasm-types/archive"] [[test]] name="generate-charts" diff --git a/crates/tinywasm/src/store/data.rs b/crates/tinywasm/src/store/data.rs new file mode 100644 index 0000000..efbb858 --- /dev/null +++ b/crates/tinywasm/src/store/data.rs @@ -0,0 +1,27 @@ +use alloc::vec::Vec; +use tinywasm_types::*; + +/// A WebAssembly Data Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct DataInstance { + pub(crate) data: Option>, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl DataInstance { + pub(crate) fn new(data: Option>, owner: ModuleInstanceAddr) -> Self { + Self { data, _owner: owner } + } + + pub(crate) fn drop(&mut self) -> Option<()> { + match self.data { + None => None, + Some(_) => { + let _ = self.data.take(); + Some(()) + } + } + } +} diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs new file mode 100644 index 0000000..6563dff --- /dev/null +++ b/crates/tinywasm/src/store/element.rs @@ -0,0 +1,19 @@ +use crate::TableElement; +use alloc::vec::Vec; +use tinywasm_types::*; + +/// A WebAssembly Element Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct ElementInstance { + pub(crate) kind: ElementKind, + pub(crate) items: Option>, // none is the element was dropped + _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl ElementInstance { + pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option>) -> Self { + Self { kind, _owner: owner, items } + } +} diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs new file mode 100644 index 0000000..7508d00 --- /dev/null +++ b/crates/tinywasm/src/store/function.rs @@ -0,0 +1,11 @@ +use crate::Function; +use tinywasm_types::*; + +#[derive(Debug, Clone)] +/// A WebAssembly Function Instance +/// +/// See +pub(crate) struct FunctionInstance { + pub(crate) func: Function, + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions +} diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs new file mode 100644 index 0000000..fbcc402 --- /dev/null +++ b/crates/tinywasm/src/store/global.rs @@ -0,0 +1,39 @@ +use alloc::{format, string::ToString}; +use tinywasm_types::*; + +use crate::{runtime::RawWasmValue, Error, Result}; + +/// A WebAssembly Global Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct GlobalInstance { + pub(crate) value: RawWasmValue, + pub(crate) ty: GlobalType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl GlobalInstance { + pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { + Self { ty, value, _owner: owner } + } + + pub(crate) fn get(&self) -> WasmValue { + self.value.attach_type(self.ty.ty) + } + + pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { + if val.val_type() != self.ty.ty { + return Err(Error::Other(format!( + "global type mismatch: expected {:?}, got {:?}", + self.ty.ty, + val.val_type() + ))); + } + if !self.ty.mutable { + return Err(Error::Other("global is immutable".to_string())); + } + self.value = val.into(); + Ok(()) + } +} diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index a087b1d..9b527d3 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -205,122 +205,92 @@ pub(crate) unsafe trait MemLoadable: Sized + Copy { fn from_be_bytes(bytes: [u8; T]) -> Self; } -#[allow(unsafe_code)] -unsafe impl MemLoadable<1> for u8 { - fn from_le_bytes(bytes: [u8; 1]) -> Self { - bytes[0] - } - fn from_be_bytes(bytes: [u8; 1]) -> Self { - bytes[0] +macro_rules! impl_mem_loadable_for_primitive { + ($($type:ty, $size:expr),*) => { + $( + #[allow(unsafe_code)] + unsafe impl MemLoadable<$size> for $type { + fn from_le_bytes(bytes: [u8; $size]) -> Self { + <$type>::from_le_bytes(bytes) + } + + fn from_be_bytes(bytes: [u8; $size]) -> Self { + <$type>::from_be_bytes(bytes) + } + } + )* } } -#[allow(unsafe_code)] -unsafe impl MemLoadable<2> for u16 { - fn from_le_bytes(bytes: [u8; 2]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 2]) -> Self { - Self::from_be_bytes(bytes) - } -} +impl_mem_loadable_for_primitive!( + u8, 1, i8, 1, u16, 2, i16, 2, u32, 4, i32, 4, f32, 4, u64, 8, i64, 8, f64, 8, u128, 16, i128, 16 +); -#[allow(unsafe_code)] -unsafe impl MemLoadable<4> for u32 { - fn from_le_bytes(bytes: [u8; 4]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 4]) -> Self { - Self::from_be_bytes(bytes) - } -} +#[cfg(test)] +mod memory_instance_tests { + use super::*; + use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; -#[allow(unsafe_code)] -unsafe impl MemLoadable<8> for u64 { - fn from_le_bytes(bytes: [u8; 8]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 8]) -> Self { - Self::from_be_bytes(bytes) + fn create_test_memory() -> MemoryInstance { + let kind = MemoryType { arch: MemoryArch::I32, page_count_initial: 1, page_count_max: Some(2) }; + let owner = ModuleInstanceAddr::default(); + MemoryInstance::new(kind, owner) } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<16> for u128 { - fn from_le_bytes(bytes: [u8; 16]) -> Self { - Self::from_le_bytes(bytes) + #[test] + fn test_memory_store_and_load() { + let mut memory = create_test_memory(); + let data_to_store = [1, 2, 3, 4]; + assert!(memory.store(0, 0, &data_to_store, data_to_store.len()).is_ok()); + let loaded_data = memory.load(0, 0, data_to_store.len()).unwrap(); + assert_eq!(loaded_data, &data_to_store); } - fn from_be_bytes(bytes: [u8; 16]) -> Self { - Self::from_be_bytes(bytes) - } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<1> for i8 { - fn from_le_bytes(bytes: [u8; 1]) -> Self { - bytes[0] as i8 - } - fn from_be_bytes(bytes: [u8; 1]) -> Self { - bytes[0] as i8 + #[test] + fn test_memory_store_out_of_bounds() { + let mut memory = create_test_memory(); + let data_to_store = [1, 2, 3, 4]; + assert!(memory.store(memory.data.len(), 0, &data_to_store, data_to_store.len()).is_err()); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<2> for i16 { - fn from_le_bytes(bytes: [u8; 2]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 2]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_fill() { + let mut memory = create_test_memory(); + assert!(memory.fill(0, 10, 42).is_ok()); + assert_eq!(&memory.data[0..10], &[42; 10]); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<4> for i32 { - fn from_le_bytes(bytes: [u8; 4]) -> Self { - Self::from_le_bytes(bytes) + #[test] + fn test_memory_fill_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.fill(memory.data.len(), 10, 42).is_err()); } - fn from_be_bytes(bytes: [u8; 4]) -> Self { - Self::from_be_bytes(bytes) - } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<8> for i64 { - fn from_le_bytes(bytes: [u8; 8]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 8]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_copy_within() { + let mut memory = create_test_memory(); + memory.fill(0, 10, 1).unwrap(); + assert!(memory.copy_within(10, 0, 10).is_ok()); + assert_eq!(&memory.data[10..20], &[1; 10]); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<16> for i128 { - fn from_le_bytes(bytes: [u8; 16]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 16]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_copy_within_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.copy_within(memory.data.len(), 0, 10).is_err()); } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<4> for f32 { - fn from_le_bytes(bytes: [u8; 4]) -> Self { - Self::from_le_bytes(bytes) + #[test] + fn test_memory_grow() { + let mut memory = create_test_memory(); + let original_pages = memory.page_count(); + assert_eq!(memory.grow(1), Some(original_pages as i32)); + assert_eq!(memory.page_count(), original_pages + 1); } - fn from_be_bytes(bytes: [u8; 4]) -> Self { - Self::from_be_bytes(bytes) - } -} -#[allow(unsafe_code)] -unsafe impl MemLoadable<8> for f64 { - fn from_le_bytes(bytes: [u8; 8]) -> Self { - Self::from_le_bytes(bytes) - } - fn from_be_bytes(bytes: [u8; 8]) -> Self { - Self::from_be_bytes(bytes) + #[test] + fn test_memory_grow_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.grow(MAX_PAGES as i32 + 1).is_none()); } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 1526eb1..be885a7 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,5 +1,5 @@ use crate::log; -use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec, vec::Vec}; +use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec::Vec}; use core::{ cell::RefCell, sync::atomic::{AtomicUsize, Ordering}, @@ -11,8 +11,18 @@ use crate::{ Error, Function, ModuleInstance, Result, Trap, }; +mod data; +mod element; +mod function; +mod global; mod memory; +mod table; +pub(crate) use data::*; +pub(crate) use element::*; +pub(crate) use function::*; +pub(crate) use global::*; pub(crate) use memory::*; +pub(crate) use table::*; // global store id counter static STORE_ID: AtomicUsize = AtomicUsize::new(0); @@ -118,14 +128,14 @@ impl Store { /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs( &mut self, - funcs: Vec<(u32, WasmFunction)>, + funcs: Vec, idx: ModuleInstanceAddr, ) -> Result> { let func_count = self.data.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); - for (i, (_, func)) in funcs.into_iter().enumerate() { - self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func)), owner: idx }); + for (i, func) in funcs.into_iter().enumerate() { + self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func.wasm_function)), owner: idx }); func_addrs.push((i + func_count) as FuncAddr); } @@ -448,203 +458,3 @@ impl Store { .map(|global| global.borrow_mut().value = value) } } - -#[derive(Debug, Clone)] -/// A WebAssembly Function Instance -/// -/// See -pub(crate) struct FunctionInstance { - pub(crate) func: Function, - pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum TableElement { - Uninitialized, - Initialized(Addr), -} - -impl From> for TableElement { - fn from(addr: Option) -> Self { - match addr { - None => TableElement::Uninitialized, - Some(addr) => TableElement::Initialized(addr), - } - } -} - -impl TableElement { - pub(crate) fn addr(&self) -> Option { - match self { - TableElement::Uninitialized => None, - TableElement::Initialized(addr) => Some(*addr), - } - } - - pub(crate) fn map Addr>(self, f: F) -> Self { - match self { - TableElement::Uninitialized => TableElement::Uninitialized, - TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), - } - } -} - -const MAX_TABLE_SIZE: u32 = 10000000; - -/// A WebAssembly Table Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct TableInstance { - pub(crate) elements: Vec, - pub(crate) kind: TableType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl TableInstance { - pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { - Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } - } - - pub(crate) fn get_wasm_val(&self, addr: usize) -> Result { - let val = self.get(addr)?.addr(); - - Ok(match self.kind.element_type { - ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), - ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), - _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), - }) - } - - pub(crate) fn get(&self, addr: usize) -> Result<&TableElement> { - self.elements.get(addr).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr })) - } - - pub(crate) fn set(&mut self, table_idx: usize, value: Addr) -> Result<()> { - self.grow_to_fit(table_idx + 1).map(|_| self.elements[table_idx] = TableElement::Initialized(value)) - } - - pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { - if new_size > self.elements.len() { - if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { - return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); - } - - self.elements.resize(new_size, TableElement::Uninitialized); - } - Ok(()) - } - - pub(crate) fn size(&self) -> i32 { - self.elements.len() as i32 - } - - fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { - if self.kind.element_type != ValType::RefFunc { - return addr; - } - - *func_addrs - .get(addr as usize) - .expect("error initializing table: function not found. This should have been caught by the validator") - } - - // Initialize the table with the given elements - pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { - let offset = offset as usize; - let end = offset.checked_add(init.len()).ok_or_else(|| { - Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) - })?; - - if end > self.elements.len() || end < offset { - return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); - } - - self.elements[offset..end].copy_from_slice(init); - log::debug!("table: {:?}", self.elements); - Ok(()) - } - - // Initialize the table with the given elements (resolves function references) - pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { - let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::>(); - - self.init_raw(offset, &init) - } -} - -/// A WebAssembly Global Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct GlobalInstance { - pub(crate) value: RawWasmValue, - pub(crate) ty: GlobalType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl GlobalInstance { - pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { - Self { ty, value, _owner: owner } - } - - pub(crate) fn get(&self) -> WasmValue { - self.value.attach_type(self.ty.ty) - } - - pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { - if val.val_type() != self.ty.ty { - return Err(Error::Other(format!( - "global type mismatch: expected {:?}, got {:?}", - self.ty.ty, - val.val_type() - ))); - } - if !self.ty.mutable { - return Err(Error::Other("global is immutable".to_string())); - } - self.value = val.into(); - Ok(()) - } -} - -/// A WebAssembly Element Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct ElementInstance { - pub(crate) kind: ElementKind, - pub(crate) items: Option>, // none is the element was dropped - _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl ElementInstance { - pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option>) -> Self { - Self { kind, _owner: owner, items } - } -} - -/// A WebAssembly Data Instance -/// -/// See -#[derive(Debug)] -pub(crate) struct DataInstance { - pub(crate) data: Option>, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances -} - -impl DataInstance { - pub(crate) fn new(data: Option>, owner: ModuleInstanceAddr) -> Self { - Self { data, _owner: owner } - } - - pub(crate) fn drop(&mut self) -> Option<()> { - match self.data { - None => None, - Some(_) => { - let _ = self.data.take(); - Some(()) - } - } - } -} diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs new file mode 100644 index 0000000..7b4c568 --- /dev/null +++ b/crates/tinywasm/src/store/table.rs @@ -0,0 +1,123 @@ +use crate::log; +use alloc::{vec, vec::Vec}; + +use tinywasm_types::*; + +use crate::{ + Error, Result, Trap, +}; + +const MAX_TABLE_SIZE: u32 = 10000000; + +/// A WebAssembly Table Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct TableInstance { + pub(crate) elements: Vec, + pub(crate) kind: TableType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl TableInstance { + pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { + Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } + } + + pub(crate) fn get_wasm_val(&self, addr: usize) -> Result { + let val = self.get(addr)?.addr(); + + Ok(match self.kind.element_type { + ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), + ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), + _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), + }) + } + + pub(crate) fn get(&self, addr: usize) -> Result<&TableElement> { + self.elements.get(addr).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr })) + } + + pub(crate) fn set(&mut self, table_idx: usize, value: Addr) -> Result<()> { + self.grow_to_fit(table_idx + 1).map(|_| self.elements[table_idx] = TableElement::Initialized(value)) + } + + pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { + if new_size > self.elements.len() { + if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { + return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); + } + + self.elements.resize(new_size, TableElement::Uninitialized); + } + Ok(()) + } + + pub(crate) fn size(&self) -> i32 { + self.elements.len() as i32 + } + + fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { + if self.kind.element_type != ValType::RefFunc { + return addr; + } + + *func_addrs + .get(addr as usize) + .expect("error initializing table: function not found. This should have been caught by the validator") + } + + // Initialize the table with the given elements + pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { + let offset = offset as usize; + let end = offset.checked_add(init.len()).ok_or_else(|| { + Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) + })?; + + if end > self.elements.len() || end < offset { + return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); + } + + self.elements[offset..end].copy_from_slice(init); + log::debug!("table: {:?}", self.elements); + Ok(()) + } + + // Initialize the table with the given elements (resolves function references) + pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { + let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::>(); + + self.init_raw(offset, &init) + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum TableElement { + Uninitialized, + Initialized(TableAddr), +} + +impl From> for TableElement { + fn from(addr: Option) -> Self { + match addr { + None => TableElement::Uninitialized, + Some(addr) => TableElement::Initialized(addr), + } + } +} + +impl TableElement { + pub(crate) fn addr(&self) -> Option { + match self { + TableElement::Uninitialized => None, + TableElement::Initialized(addr) => Some(*addr), + } + } + + pub(crate) fn map Addr>(self, f: F) -> Self { + match self { + TableElement::Uninitialized => TableElement::Uninitialized, + TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), + } + } +} diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index de5d5ab..125a9d6 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -137,7 +137,7 @@ impl TestSuite { for (name, addr) in modules { log::debug!("registering module: {}", name); - imports.link_module(&name, *addr)?; + imports.link_module(name, *addr)?; } Ok(imports) @@ -199,7 +199,7 @@ impl TestSuite { QuoteWat::QuoteModule(_, quoted_wat) => { let wat = quoted_wat .iter() - .map(|(_, s)| std::str::from_utf8(&s).expect("failed to convert wast to utf8")) + .map(|(_, s)| std::str::from_utf8(s).expect("failed to convert wast to utf8")) .collect::>() .join("\n"); @@ -444,7 +444,7 @@ impl TestSuite { continue; } }; - let expected = expected.get(0).expect("expected global value"); + let expected = expected.first().expect("expected global value"); let module_global = module_global.attach_type(expected.val_type()); if !module_global.eq_loose(expected) { diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index bc6769a..76e5679 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -9,10 +9,12 @@ repository.workspace=true [dependencies] log={version="0.4", optional=true} -rkyv={version="0.7", optional=true, default-features=false, features=["size_32"]} +rkyv={version="0.7", optional=true, default-features=false, features=["size_32", "validation"]} +bytecheck={version="0.7", optional=true} [features] -default=["std", "logging"] -std=["rkyv/std"] -serialize=["dep:rkyv", "dep:log"] +default=["std", "logging", "archive", "unsafe"] +std=["rkyv?/std"] +archive=["dep:rkyv", "dep:bytecheck"] logging=["dep:log"] +unsafe=[] diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs new file mode 100644 index 0000000..00a9911 --- /dev/null +++ b/crates/types/src/archive.rs @@ -0,0 +1,92 @@ +use crate::TinyWasmModule; +use rkyv::{ + check_archived_root, + ser::{serializers::AllocSerializer, Serializer}, + Deserialize, +}; + +// 16 bytes +const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; +const TWASM_VERSION: &[u8; 2] = b"01"; + +#[rustfmt::skip] +const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TWASM_MAGIC_PREFIX[2], TWASM_MAGIC_PREFIX[3], TWASM_VERSION[0], TWASM_VERSION[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +pub use rkyv::AlignedVec; + +fn validate_magic(wasm: &[u8]) -> Result { + if wasm.len() < TWASM_MAGIC.len() { + return Err("Invalid twasm: too short"); + } + if &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { + return Err("Invalid twasm: invalid magic number"); + } + if &wasm[TWASM_MAGIC_PREFIX.len()..TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()] != TWASM_VERSION { + return Err("Invalid twasm: invalid version"); + } + if wasm[TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()..TWASM_MAGIC.len()] != [0; 10] { + return Err("Invalid twasm: invalid padding"); + } + + Ok(TWASM_MAGIC.len()) +} + +impl TinyWasmModule { + /// Creates a TinyWasmModule from a slice of bytes. + pub fn from_twasm(wasm: &[u8]) -> Result { + let len = validate_magic(wasm)?; + let root = check_archived_root::(&wasm[len..]).map_err(|e| { + log::error!("Error checking archived root: {}", e); + "Error checking archived root" + })?; + + Ok(root.deserialize(&mut rkyv::Infallible).unwrap()) + } + + #[cfg(feature = "unsafe")] + #[allow(unsafe_code)] + /// Creates a TinyWasmModule from a slice of bytes. + /// + /// # Safety + /// This function is only safe to call if the bytes have been created by + /// a trusted source. Otherwise, it may cause undefined behavior. + pub unsafe fn from_twasm_unchecked(wasm: &[u8]) -> Self { + let len = validate_magic(wasm).unwrap(); + rkyv::archived_root::(&wasm[len..]).deserialize(&mut rkyv::Infallible).unwrap() + } + + /// Serializes the TinyWasmModule into a vector of bytes. + /// AlignedVec can be deferenced as a slice of bytes and + /// implements io::Write when the `std` feature is enabled. + pub fn serialize_twasm(&self) -> rkyv::AlignedVec { + let mut serializer = AllocSerializer::<0>::default(); + serializer.pad(TWASM_MAGIC.len()).unwrap(); + serializer.serialize_value(self).unwrap(); + let mut out = serializer.into_serializer().into_inner(); + out[..TWASM_MAGIC.len()].copy_from_slice(&TWASM_MAGIC); + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize() { + let wasm = TinyWasmModule::default(); + let twasm = wasm.serialize_twasm(); + let wasm2 = TinyWasmModule::from_twasm(&twasm).unwrap(); + assert_eq!(wasm, wasm2); + } + + #[cfg(feature = "unsafe")] + #[test] + fn test_serialize_unchecked() { + let wasm = TinyWasmModule::default(); + let twasm = wasm.serialize_twasm(); + #[allow(unsafe_code)] + let wasm2 = unsafe { TinyWasmModule::from_twasm_unchecked(&twasm) }; + assert_eq!(wasm, wasm2); + } +} diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 1061d10..0e2eafe 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -3,6 +3,8 @@ use crate::{DataAddr, ElemAddr, MemAddr}; use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum BlockArgs { Empty, Type(ValType), @@ -10,7 +12,9 @@ pub enum BlockArgs { } /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct MemoryArg { pub mem_addr: MemAddr, pub align: u8, @@ -23,7 +27,9 @@ type BrTableLen = usize; type EndOffset = usize; type ElseOffset = usize; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ConstInstruction { I32Const(i32), I64Const(i64), @@ -46,7 +52,9 @@ pub enum ConstInstruction { /// This makes it easier to implement the label stack iteratively. /// /// See -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum Instruction { // Custom Instructions BrLabel(LabelAddr), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 2ccf869..547379c 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -1,10 +1,11 @@ -#![no_std] -#![forbid(unsafe_code)] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "unsafe"), forbid(unsafe_code))] +#![cfg_attr(feature = "unsafe", deny(unused_unsafe))] //! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser). @@ -28,22 +29,25 @@ use core::{fmt::Debug, ops::Range}; use alloc::boxed::Box; pub use instructions::*; +#[cfg(feature = "archive")] +pub mod archive; + /// A TinyWasm WebAssembly Module /// /// 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, Clone)] +#[derive(Debug, Clone, Default, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct TinyWasmModule { /// The version of the WebAssembly module. pub version: Option, - /// The start function of the WebAssembly module. pub start_func: Option, /// The functions of the WebAssembly module. - pub funcs: Box<[(u32, WasmFunction)]>, - + pub funcs: Box<[TypedWasmFunction]>, /// The types of the WebAssembly module. pub func_types: Box<[FuncType]>, @@ -90,6 +94,7 @@ pub enum WasmValue { } impl WasmValue { + #[inline] pub fn const_instr(&self) -> ConstInstruction { match self { Self::I32(i) => ConstInstruction::I32Const(*i), @@ -106,6 +111,7 @@ impl WasmValue { } /// Get the default value for a given type. + #[inline] pub fn default_for(ty: ValType) -> Self { match ty { ValType::I32 => Self::I32(0), @@ -117,6 +123,7 @@ impl WasmValue { } } + #[inline] pub fn eq_loose(&self, other: &Self) -> bool { match (self, other) { (Self::I32(a), Self::I32(b)) => a == b, @@ -144,36 +151,45 @@ impl WasmValue { } impl From for WasmValue { + #[inline] fn from(i: i32) -> Self { Self::I32(i) } } impl From for WasmValue { + #[inline] fn from(i: i64) -> Self { Self::I64(i) } } impl From for WasmValue { + #[inline] fn from(i: f32) -> Self { Self::F32(i) } } impl From for WasmValue { + #[inline] fn from(i: f64) -> Self { Self::F64(i) } } +#[cold] +fn cold() {} + impl TryFrom for i32 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result { match value { WasmValue::I32(i) => Ok(i), _ => { + cold(); crate::log::error!("i32: try_from failed: {:?}", value); Err(()) } @@ -184,10 +200,12 @@ impl TryFrom for i32 { impl TryFrom for i64 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result { match value { WasmValue::I64(i) => Ok(i), _ => { + cold(); crate::log::error!("i64: try_from failed: {:?}", value); Err(()) } @@ -198,10 +216,12 @@ impl TryFrom for i64 { impl TryFrom for f32 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result { match value { WasmValue::F32(i) => Ok(i), _ => { + cold(); crate::log::error!("f32: try_from failed: {:?}", value); Err(()) } @@ -212,10 +232,12 @@ impl TryFrom for f32 { impl TryFrom for f64 { type Error = (); + #[inline] fn try_from(value: WasmValue) -> Result { match value { WasmValue::F64(i) => Ok(i), _ => { + cold(); crate::log::error!("f64: try_from failed: {:?}", value); Err(()) } @@ -240,6 +262,7 @@ impl Debug for WasmValue { impl WasmValue { /// Get the type of a [`WasmValue`] + #[inline] pub fn val_type(&self) -> ValType { match self { Self::I32(_) => ValType::I32, @@ -255,6 +278,8 @@ impl WasmValue { /// Type of a WebAssembly value. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ValType { /// A 32-bit integer. I32, @@ -271,6 +296,7 @@ pub enum ValType { } impl ValType { + #[inline] pub fn default_value(&self) -> WasmValue { WasmValue::default_for(*self) } @@ -280,6 +306,8 @@ impl ValType { /// /// See #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ExternalKind { /// A WebAssembly Function. Func, @@ -322,6 +350,7 @@ pub enum ExternVal { } impl ExternVal { + #[inline] pub fn kind(&self) -> ExternalKind { match self { Self::Func(_) => ExternalKind::Func, @@ -331,6 +360,7 @@ impl ExternVal { } } + #[inline] pub fn new(kind: ExternalKind, addr: Addr) -> Self { match kind { ExternalKind::Func => Self::Func(addr), @@ -345,6 +375,8 @@ impl ExternVal { /// /// See #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct FuncType { pub params: Box<[ValType]>, pub results: Box<[ValType]>, @@ -352,20 +384,33 @@ pub struct FuncType { impl FuncType { /// Get the number of parameters of a function type. + #[inline] pub fn empty() -> Self { Self { params: Box::new([]), results: Box::new([]) } } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct WasmFunction { pub instructions: Box<[Instruction]>, pub locals: Box<[ValType]>, pub ty: FuncType, } +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] +pub struct TypedWasmFunction { + pub type_addr: u32, + pub wasm_function: WasmFunction, +} + /// A WebAssembly Module Export -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Export { /// The name of the export. pub name: Box, @@ -375,19 +420,25 @@ pub struct Export { pub index: u32, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Global { pub ty: GlobalType, pub init: ConstInstruction, } #[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct TableType { pub element_type: ValType, pub size_initial: u32, @@ -404,10 +455,12 @@ impl TableType { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] /// Represents a memory's type. #[derive(Copy)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct MemoryType { pub arch: MemoryArch, pub page_count_initial: u64, @@ -418,30 +471,28 @@ impl MemoryType { pub fn new_32(page_count_initial: u64, page_count_max: Option) -> Self { Self { arch: MemoryArch::I32, page_count_initial, page_count_max } } - - // pub fn new_64(page_count_initial: u64, page_count_max: Option) -> Self { - // Self { - // arch: MemoryArch::I64, - // page_count_initial, - // page_count_max, - // } - // } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum MemoryArch { I32, I64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Import { pub module: Box, pub name: Box, pub kind: ImportKind, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ImportKind { Function(TypeAddr), Table(TableType), @@ -450,6 +501,7 @@ pub enum ImportKind { } impl From<&ImportKind> for ExternalKind { + #[inline] fn from(kind: &ImportKind) -> Self { match kind { ImportKind::Function(_) => Self::Func, @@ -460,20 +512,26 @@ impl From<&ImportKind> for ExternalKind { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Data { pub data: Box<[u8]>, pub range: Range, pub kind: DataKind, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum DataKind { Active { mem: MemAddr, offset: ConstInstruction }, Passive, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub struct Element { pub kind: ElementKind, pub items: Box<[ElementItem]>, @@ -481,14 +539,18 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, Declared, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] pub enum ElementItem { Func(FuncAddr), Expr(ConstInstruction), diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs new file mode 100644 index 0000000..9f2b28c --- /dev/null +++ b/examples/rust/src/tinywasm_no_std.rs @@ -0,0 +1,33 @@ +#![no_main] +#![no_std] +use tinywasm::{Extern, FuncContext}; + +#[link(wasm_import_module = "env")] +extern "C" { + fn printi32(x: i32); +} + +#[no_mangle] +pub extern "C" fn hello() { + let _ = run(); +} + +fn run() -> tinywasm::Result<()> { + let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; + let mut store = tinywasm::Store::default(); + let mut imports = tinywasm::Imports::new(); + + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, v: i32| { + unsafe { printi32(v) } + Ok(()) + }), + )?; + let instance = module.instantiate(&mut store, Some(imports))?; + + let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; + Ok(()) +} diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 96e3419..112222c 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -26,7 +26,7 @@ fn main() -> Result<()> { fn tinywasm() -> Result<()> { const TINYWASM: &[u8] = include_bytes!("./rust/out/tinywasm.wasm"); - let module = Module::parse_bytes(&TINYWASM)?; + let module = Module::parse_bytes(TINYWASM)?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -40,7 +40,7 @@ fn tinywasm() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let hello = instance.exported_func::<(), ()>(&mut store, "hello")?; + let hello = instance.exported_func::<(), ()>(&store, "hello")?; hello.call(&mut store, ())?; Ok(()) @@ -48,7 +48,7 @@ fn tinywasm() -> Result<()> { fn hello() -> Result<()> { const HELLO_WASM: &[u8] = include_bytes!("./rust/out/hello.wasm"); - let module = Module::parse_bytes(&HELLO_WASM)?; + let module = Module::parse_bytes(HELLO_WASM)?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -66,11 +66,11 @@ fn hello() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let arg_ptr = instance.exported_func::<(), i32>(&mut store, "arg_ptr")?.call(&mut store, ())?; + let arg_ptr = instance.exported_func::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; let arg = b"world"; instance.exported_memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; - let hello = instance.exported_func::(&mut store, "hello")?; + let hello = instance.exported_func::(&store, "hello")?; hello.call(&mut store, arg.len() as i32)?; Ok(()) @@ -78,7 +78,7 @@ fn hello() -> Result<()> { fn printi32() -> Result<()> { const HELLO_WASM: &[u8] = include_bytes!("./rust/out/print.wasm"); - let module = Module::parse_bytes(&HELLO_WASM)?; + let module = Module::parse_bytes(HELLO_WASM)?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -92,7 +92,7 @@ fn printi32() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) @@ -100,11 +100,11 @@ fn printi32() -> Result<()> { fn fibonacci() -> Result<()> { const FIBONACCI_WASM: &[u8] = include_bytes!("./rust/out/fibonacci.wasm"); - let module = Module::parse_bytes(&FIBONACCI_WASM)?; + let module = Module::parse_bytes(FIBONACCI_WASM)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let fibonacci = instance.exported_func::(&mut store, "fibonacci")?; + let fibonacci = instance.exported_func::(&store, "fibonacci")?; let n = 30; let result = fibonacci.call(&mut store, n)?; println!("fibonacci({}) = {}", n, result); From 963ddd26a89490458e31d9d553dffafe5e350e96 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 29 Jan 2024 22:38:06 +0100 Subject: [PATCH 22/27] chore: add more examples, refactoring Signed-off-by: Henry Gressmann --- Cargo.lock | 1 + Cargo.toml | 1 + README.md | 27 ++-- crates/cli/README.md | 10 ++ crates/parser/README.md | 10 +- crates/tinywasm/Cargo.toml | 2 +- crates/tinywasm/src/func.rs | 1 - crates/tinywasm/src/imports.rs | 31 ++-- crates/tinywasm/src/instance.rs | 6 - crates/tinywasm/src/lib.rs | 2 + crates/tinywasm/src/store/global.rs | 2 + crates/tinywasm/src/store/mod.rs | 12 +- crates/tinywasm/src/store/table.rs | 8 +- crates/tinywasm/tests/test-wast.rs | 4 +- crates/types/README.md | 4 +- crates/types/src/archive.rs | 45 ++++-- crates/types/src/lib.rs | 236 +--------------------------- crates/types/src/value.rs | 175 +++++++++++++++++++++ examples/archive.rs | 29 ++++ examples/linking.rs | 41 +++++ examples/simple.rs | 22 +++ examples/wasm-rust.rs | 16 ++ 22 files changed, 378 insertions(+), 307 deletions(-) create mode 100644 crates/cli/README.md create mode 100644 crates/types/src/value.rs create mode 100644 examples/archive.rs create mode 100644 examples/linking.rs create mode 100644 examples/simple.rs diff --git a/Cargo.lock b/Cargo.lock index 6da632f..8f97f2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2355,6 +2355,7 @@ dependencies = [ "wasmer", "wasmi", "wasmtime", + "wat", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f52bc22..3bbbb35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ color-eyre="0.6" criterion={version="0.5", features=["html_reports"]} tinywasm={path="crates/tinywasm"} +wat={version="1.0"} wasmi={version="0.31", features=["std"]} wasmer={version="4.2", features=["cranelift", "singlepass"]} wasmtime={version="17.0", features=["cranelift"]} diff --git a/README.md b/README.md index e7a66ae..8a4aa93 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,17 @@ [![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg)](./LICENSE-APACHE) -# Why TinyWasm? +## Why TinyWasm? - **Tiny** - Designed to be as small as possible without sacrificing too much performance or functionality. - **Fast enough** - TinyWasm is reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details. - **Portable** - Runs on any platform llvm supports, including WebAssembly. Minimal external dependencies. -# Status +## Status -TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). +TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). Results of the testsuite can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete. -Results of the tests can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). TinyWasm is not designed for performance, but rather for simplicity, size and portability. However, it is still reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details. @@ -32,24 +31,26 @@ TinyWasm is not designed for performance, but rather for simplicity, size and po - [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented** - [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **Fully implemented** (as of version `0.4.0`) - [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_** -- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** (not tested yet) -- [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_** (only 32-bit addressing is supported at the moment, but larger memories can be created) +- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** +- [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_** ## Usage TinyWasm can be used through the `tinywasm-cli` CLI tool or as a library in your Rust project. Documentation can be found [here](https://docs.rs/tinywasm). -### CLI +### Library ```sh -$ cargo install tinywasm-cli -$ tinywasm-cli --help +$ cargo add tinywasm ``` -### Library +### CLI + +The CLI is mainly available for testing purposes, but can also be used to run WebAssembly programs. ```sh -$ cargo add tinywasm +$ cargo install tinywasm-cli +$ tinywasm-cli --help ``` ## Feature Flags @@ -60,6 +61,8 @@ $ cargo add tinywasm Enables logging using the `log` crate. This is enabled by default. - **`parser`**\ Enables the `tinywasm-parser` crate. This is enabled by default. +- **`archive`**\ + Enables pre-parsing of archives. This is enabled by default. - **`unsafe`**\ Uses `unsafe` code to improve performance, particularly in Memory access @@ -70,7 +73,7 @@ Since `libm` is not as performant as the compiler's math intrinsics, it is recom > Benchmarks are coming soon. -# 📄 License +## License Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option. diff --git a/crates/cli/README.md b/crates/cli/README.md new file mode 100644 index 0000000..1f7a1bb --- /dev/null +++ b/crates/cli/README.md @@ -0,0 +1,10 @@ +# `tinywasm-cli` + +The `tinywasm-cli` crate contains the command line interface for the `tinywasm` project. See [`tinywasm`](https://crates.io/crates/tinywasm) for more information. + +## Usage + +```bash +$ cargo install tinywasm-cli +$ tinywasm-cli --help +``` diff --git a/crates/parser/README.md b/crates/parser/README.md index 6cf2234..8ac7a30 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -1,6 +1,6 @@ # `tinywasm-parser` -This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. It is based on +This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. It is based on [`wasmparser_nostd`](https://crates.io/crates/wasmparser_nostd) and used by [`tinywasm`](https://crates.io/crates/tinywasm). ## Features @@ -11,11 +11,11 @@ This crate provides a parser that can parse WebAssembly modules into a TinyWasm ## Usage ```rust -use tinywasm_parser::{Parser, TinyWasmModule}; +use tinywasm_parser::Parser; let bytes = include_bytes!("./file.wasm"); let parser = Parser::new(); -let module: TinyWasmModule = parser.parse_module_bytes(bytes).unwrap(); -let mudule: TinyWasmModule = parser.parse_module_file("path/to/file.wasm").unwrap(); -let module: TinyWasmModule = parser.parse_module_stream(&mut stream).unwrap(); +let module = parser.parse_module_bytes(bytes).unwrap(); +let mudule = parser.parse_module_file("path/to/file.wasm").unwrap(); +let module = parser.parse_module_stream(&mut stream).unwrap(); ``` diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index dd76f3d..bbc85e9 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -30,7 +30,7 @@ pretty_env_logger="0.5" [features] default=["std", "parser", "logging", "archive"] -logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] +logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] unsafe=["tinywasm-types/unsafe"] diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index a0c1212..2088494 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -29,7 +29,6 @@ impl FuncHandle { // 4. If the length of the provided argument values is different from the number of expected arguments, then fail if unlikely(func_ty.params.len() != params.len()) { - log::info!("func_ty.params: {:?}", func_ty.params); return Err(Error::Other(format!( "param count mismatch: expected {}, got {}", func_ty.params.len(), diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 38d0707..e273838 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -286,7 +286,6 @@ impl Imports { if let Some(v) = self.values.get(&name) { return Some(ResolvedExtern::Extern(v.clone())); } - if let Some(addr) = self.modules.get(&name.module) { let instance = store.get_module_instance(*addr)?; return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?)); @@ -295,15 +294,11 @@ impl Imports { None } - fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> - where - T: Debug + PartialEq, - { + fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { if expected != actual { log::error!("failed to link import {}, expected {:?}, got {:?}", import.name, expected, actual); return Err(LinkingError::incompatible_import_type(import).into()); } - Ok(()) } @@ -333,22 +328,20 @@ impl Imports { ) -> Result<()> { Self::compare_types(import, &expected.arch, &actual.arch)?; - if actual.page_count_initial > expected.page_count_initial { - if let Some(real_size) = real_size { - if actual.page_count_initial > real_size as u64 { - return Err(LinkingError::incompatible_import_type(import).into()); - } - } else { - return Err(LinkingError::incompatible_import_type(import).into()); - } + if actual.page_count_initial > expected.page_count_initial + && real_size.map_or(true, |size| actual.page_count_initial > size as u64) + { + return Err(LinkingError::incompatible_import_type(import).into()); } - match (expected.page_count_max, actual.page_count_max) { - (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()), - (Some(expected_max), Some(actual_max)) if actual_max < expected_max => { - return Err(LinkingError::incompatible_import_type(import).into()) + if expected.page_count_max.is_none() && actual.page_count_max.is_some() { + return Err(LinkingError::incompatible_import_type(import).into()); + } + + if let (Some(expected_max), Some(actual_max)) = (expected.page_count_max, actual.page_count_max) { + if actual_max < expected_max { + return Err(LinkingError::incompatible_import_type(import).into()); } - _ => {} } Ok(()) diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index ad6c0f1..cb12f1b 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -124,11 +124,6 @@ impl ModuleInstance { &self.0.func_addrs } - /// Get the module's function types - pub fn func_tys(&self) -> &[FuncType] { - &self.0.types - } - pub(crate) fn new(inner: ModuleInstanceInner) -> Self { Self(Rc::new(inner)) } @@ -232,7 +227,6 @@ impl ModuleInstance { /// /// Returns None if the module has no start function /// If no start function is specified, also checks for a _start function in the exports - /// (which is not part of the spec, but used by some compilers) /// /// See pub fn start_func(&self, store: &Store) -> Result> { diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index d786ab8..79b111c 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -58,6 +58,8 @@ //! # Ok::<(), tinywasm::Error>(()) //! ``` //! +//! For more examples, see the [`examples`](https://github.com/explodingcamera/tinywasm/tree/main/examples) directory. +//! //! ## Imports //! //! To provide imports to a module, you can use the [`Imports`] struct. diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index fbcc402..298a31e 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -30,9 +30,11 @@ impl GlobalInstance { val.val_type() ))); } + if !self.ty.mutable { return Err(Error::Other("global is immutable".to_string())); } + self.value = val.into(); Ok(()) } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index be885a7..c8c5d8a 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -17,12 +17,7 @@ mod function; mod global; mod memory; mod table; -pub(crate) use data::*; -pub(crate) use element::*; -pub(crate) use function::*; -pub(crate) use global::*; -pub(crate) use memory::*; -pub(crate) use table::*; +pub(crate) use {data::*, element::*, function::*, global::*, memory::*, table::*}; // global store id counter static STORE_ID: AtomicUsize = AtomicUsize::new(0); @@ -205,12 +200,11 @@ impl Store { let addr = globals.get(*addr as usize).copied().ok_or_else(|| { Error::Other(format!("global {} not found. This should have been caught by the validator", addr)) })?; - let global = self.data.globals[addr as usize].clone(); let val = i64::from(global.borrow().value); - log::error!("global: {}", val); + + // check if the global is actually a null reference if val < 0 { - // the global is actually a null reference None } else { Some(val as u32) diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index 7b4c568..ea520b8 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,12 +1,8 @@ use crate::log; +use crate::{Error, Result, Trap}; use alloc::{vec, vec::Vec}; - use tinywasm_types::*; -use crate::{ - Error, Result, Trap, -}; - const MAX_TABLE_SIZE: u32 = 10000000; /// A WebAssembly Table Instance @@ -30,7 +26,7 @@ impl TableInstance { Ok(match self.kind.element_type { ValType::RefFunc => val.map(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), - _ => unimplemented!("unsupported table type: {:?}", self.kind.element_type), + _ => Err(Error::UnsupportedFeature("non-ref table".into()))?, }) } diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs index a50a612..1d3fbe3 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -13,8 +13,8 @@ fn main() -> Result<()> { } if args.len() < 3 { - bail!("usage: cargo test-wast "); - } + bail!("usage: cargo test-wast ") + }; // cwd for relative paths, absolute paths are kept as-is let cwd = std::env::current_dir()?; diff --git a/crates/types/README.md b/crates/types/README.md index f2d048b..5a4431e 100644 --- a/crates/types/README.md +++ b/crates/types/README.md @@ -1,3 +1,3 @@ -# `tinywasm_types` +# `tinywasm-types` -This crate contains the types used by the [`tinywasm`](https://crates.io/crates/tinywasm) crate. It is also used by the [`tinywasm_parser`](https://crates.io/crates/tinywasm_parser) crate to parse WebAssembly binaries. +This crate contains the types used by the [`tinywasm`](https://crates.io/crates/tinywasm) crate. It is also used by the [`tinywasm-parser`](https://crates.io/crates/tinywasm-parser) crate to parse WebAssembly binaries. diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 00a9911..bbd2206 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -1,3 +1,5 @@ +use core::fmt::{Display, Formatter}; + use crate::TinyWasmModule; use rkyv::{ check_archived_root, @@ -14,30 +16,49 @@ const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TW pub use rkyv::AlignedVec; -fn validate_magic(wasm: &[u8]) -> Result { - if wasm.len() < TWASM_MAGIC.len() { - return Err("Invalid twasm: too short"); - } - if &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { - return Err("Invalid twasm: invalid magic number"); +fn validate_magic(wasm: &[u8]) -> Result { + if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { + return Err(TwasmError::InvalidMagic); } if &wasm[TWASM_MAGIC_PREFIX.len()..TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()] != TWASM_VERSION { - return Err("Invalid twasm: invalid version"); + return Err(TwasmError::InvalidVersion); } if wasm[TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()..TWASM_MAGIC.len()] != [0; 10] { - return Err("Invalid twasm: invalid padding"); + return Err(TwasmError::InvalidPadding); } Ok(TWASM_MAGIC.len()) } +#[derive(Debug)] +pub enum TwasmError { + InvalidMagic, + InvalidVersion, + InvalidPadding, + InvalidArchive, +} + +impl Display for TwasmError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + TwasmError::InvalidMagic => write!(f, "Invalid twasm: invalid magic number"), + TwasmError::InvalidVersion => write!(f, "Invalid twasm: invalid version"), + TwasmError::InvalidPadding => write!(f, "Invalid twasm: invalid padding"), + TwasmError::InvalidArchive => write!(f, "Invalid twasm: invalid archive"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TwasmError {} + impl TinyWasmModule { /// Creates a TinyWasmModule from a slice of bytes. - pub fn from_twasm(wasm: &[u8]) -> Result { + pub fn from_twasm(wasm: &[u8]) -> Result { let len = validate_magic(wasm)?; - let root = check_archived_root::(&wasm[len..]).map_err(|e| { - log::error!("Error checking archived root: {}", e); - "Error checking archived root" + let root = check_archived_root::(&wasm[len..]).map_err(|_e| { + crate::log::error!("Invalid archive: {}", _e); + TwasmError::InvalidArchive })?; Ok(root.deserialize(&mut rkyv::Infallible).unwrap()) diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 547379c..205ec5a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -10,6 +10,8 @@ //! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser). extern crate alloc; +use alloc::boxed::Box; +use core::{fmt::Debug, ops::Range}; // log for logging (optional). #[cfg(feature = "logging")] @@ -24,10 +26,9 @@ pub(crate) mod log { } mod instructions; -use core::{fmt::Debug, ops::Range}; - -use alloc::boxed::Box; +mod value; pub use instructions::*; +pub use value::*; #[cfg(feature = "archive")] pub mod archive; @@ -73,235 +74,6 @@ pub struct TinyWasmModule { pub elements: Box<[Element]>, } -/// A WebAssembly value. -/// -/// See -#[derive(Clone, Copy)] -pub enum WasmValue { - // Num types - /// A 32-bit integer. - I32(i32), - /// A 64-bit integer. - I64(i64), - /// A 32-bit float. - F32(f32), - /// A 64-bit float. - F64(f64), - - RefExtern(ExternAddr), - RefFunc(FuncAddr), - RefNull(ValType), -} - -impl WasmValue { - #[inline] - pub fn const_instr(&self) -> ConstInstruction { - match self { - Self::I32(i) => ConstInstruction::I32Const(*i), - Self::I64(i) => ConstInstruction::I64Const(*i), - Self::F32(i) => ConstInstruction::F32Const(*i), - Self::F64(i) => ConstInstruction::F64Const(*i), - - Self::RefFunc(i) => ConstInstruction::RefFunc(*i), - Self::RefNull(ty) => ConstInstruction::RefNull(*ty), - - // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr), - _ => unimplemented!("no const_instr for {:?}", self), - } - } - - /// Get the default value for a given type. - #[inline] - pub fn default_for(ty: ValType) -> Self { - match ty { - ValType::I32 => Self::I32(0), - ValType::I64 => Self::I64(0), - ValType::F32 => Self::F32(0.0), - ValType::F64 => Self::F64(0.0), - ValType::RefFunc => Self::RefNull(ValType::RefFunc), - ValType::RefExtern => Self::RefNull(ValType::RefExtern), - } - } - - #[inline] - 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::RefNull(v), Self::RefNull(v2)) => v == v2, - (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2, - (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2, - (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 { - #[inline] - fn from(i: i32) -> Self { - Self::I32(i) - } -} - -impl From for WasmValue { - #[inline] - fn from(i: i64) -> Self { - Self::I64(i) - } -} - -impl From for WasmValue { - #[inline] - fn from(i: f32) -> Self { - Self::F32(i) - } -} - -impl From for WasmValue { - #[inline] - fn from(i: f64) -> Self { - Self::F64(i) - } -} - -#[cold] -fn cold() {} - -impl TryFrom for i32 { - type Error = (); - - #[inline] - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::I32(i) => Ok(i), - _ => { - cold(); - crate::log::error!("i32: try_from failed: {:?}", value); - Err(()) - } - } - } -} - -impl TryFrom for i64 { - type Error = (); - - #[inline] - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::I64(i) => Ok(i), - _ => { - cold(); - crate::log::error!("i64: try_from failed: {:?}", value); - Err(()) - } - } - } -} - -impl TryFrom for f32 { - type Error = (); - - #[inline] - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::F32(i) => Ok(i), - _ => { - cold(); - crate::log::error!("f32: try_from failed: {:?}", value); - Err(()) - } - } - } -} - -impl TryFrom for f64 { - type Error = (); - - #[inline] - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::F64(i) => Ok(i), - _ => { - cold(); - crate::log::error!("f64: try_from failed: {:?}", value); - Err(()) - } - } - } -} - -impl Debug for WasmValue { - fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { - match self { - WasmValue::I32(i) => write!(f, "i32({})", i), - WasmValue::I64(i) => write!(f, "i64({})", i), - WasmValue::F32(i) => write!(f, "f32({})", i), - WasmValue::F64(i) => write!(f, "f64({})", i), - WasmValue::RefExtern(addr) => write!(f, "ref.extern({:?})", addr), - WasmValue::RefFunc(addr) => write!(f, "ref.func({:?})", addr), - WasmValue::RefNull(ty) => write!(f, "ref.null({:?})", ty), - // WasmValue::V128(i) => write!(f, "v128({})", i), - } - } -} - -impl WasmValue { - /// Get the type of a [`WasmValue`] - #[inline] - pub fn val_type(&self) -> ValType { - match self { - Self::I32(_) => ValType::I32, - Self::I64(_) => ValType::I64, - Self::F32(_) => ValType::F32, - Self::F64(_) => ValType::F64, - Self::RefExtern(_) => ValType::RefExtern, - Self::RefFunc(_) => ValType::RefFunc, - Self::RefNull(ty) => *ty, - } - } -} - -/// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] -pub enum ValType { - /// A 32-bit integer. - I32, - /// A 64-bit integer. - I64, - /// A 32-bit float. - F32, - /// A 64-bit float. - F64, - /// A reference to a function. - RefFunc, - /// A reference to an external value. - RefExtern, -} - -impl ValType { - #[inline] - pub fn default_value(&self) -> WasmValue { - WasmValue::default_for(*self) - } -} - /// A WebAssembly External Kind. /// /// See diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs new file mode 100644 index 0000000..e46092b --- /dev/null +++ b/crates/types/src/value.rs @@ -0,0 +1,175 @@ +use core::fmt::Debug; + +use crate::{ConstInstruction, ExternAddr, FuncAddr}; + +/// A WebAssembly value. +/// +/// See +#[derive(Clone, Copy)] +pub enum WasmValue { + // Num types + /// A 32-bit integer. + I32(i32), + /// A 64-bit integer. + I64(i64), + /// A 32-bit float. + F32(f32), + /// A 64-bit float. + F64(f64), + + RefExtern(ExternAddr), + RefFunc(FuncAddr), + RefNull(ValType), +} + +impl WasmValue { + #[inline] + pub fn const_instr(&self) -> ConstInstruction { + match self { + Self::I32(i) => ConstInstruction::I32Const(*i), + Self::I64(i) => ConstInstruction::I64Const(*i), + Self::F32(i) => ConstInstruction::F32Const(*i), + Self::F64(i) => ConstInstruction::F64Const(*i), + + Self::RefFunc(i) => ConstInstruction::RefFunc(*i), + Self::RefNull(ty) => ConstInstruction::RefNull(*ty), + + // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr), + _ => unimplemented!("no const_instr for {:?}", self), + } + } + + /// Get the default value for a given type. + #[inline] + pub fn default_for(ty: ValType) -> Self { + match ty { + ValType::I32 => Self::I32(0), + ValType::I64 => Self::I64(0), + ValType::F32 => Self::F32(0.0), + ValType::F64 => Self::F64(0.0), + ValType::RefFunc => Self::RefNull(ValType::RefFunc), + ValType::RefExtern => Self::RefNull(ValType::RefExtern), + } + } + + #[inline] + 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::RefNull(v), Self::RefNull(v2)) => v == v2, + (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2, + (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2, + (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, + } + } +} + +#[cold] +fn cold() {} + +impl Debug for WasmValue { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { + match self { + WasmValue::I32(i) => write!(f, "i32({})", i), + WasmValue::I64(i) => write!(f, "i64({})", i), + WasmValue::F32(i) => write!(f, "f32({})", i), + WasmValue::F64(i) => write!(f, "f64({})", i), + WasmValue::RefExtern(addr) => write!(f, "ref.extern({:?})", addr), + WasmValue::RefFunc(addr) => write!(f, "ref.func({:?})", addr), + WasmValue::RefNull(ty) => write!(f, "ref.null({:?})", ty), + } + } +} + +impl WasmValue { + /// Get the type of a [`WasmValue`] + #[inline] + pub fn val_type(&self) -> ValType { + match self { + Self::I32(_) => ValType::I32, + Self::I64(_) => ValType::I64, + Self::F32(_) => ValType::F32, + Self::F64(_) => ValType::F64, + Self::RefExtern(_) => ValType::RefExtern, + Self::RefFunc(_) => ValType::RefFunc, + Self::RefNull(ty) => *ty, + } + } +} + +/// Type of a WebAssembly value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg_attr(feature = "archive", archive(check_bytes))] +pub enum ValType { + /// A 32-bit integer. + I32, + /// A 64-bit integer. + I64, + /// A 32-bit float. + F32, + /// A 64-bit float. + F64, + /// A reference to a function. + RefFunc, + /// A reference to an external value. + RefExtern, +} + +impl ValType { + #[inline] + pub fn default_value(&self) -> WasmValue { + WasmValue::default_for(*self) + } +} + +macro_rules! impl_conversion_for_wasmvalue { + ($($t:ty => $variant:ident),*) => { + $( + // Implementing From<$t> for WasmValue + impl From<$t> for WasmValue { + #[inline] + fn from(i: $t) -> Self { + Self::$variant(i) + } + } + + // Implementing TryFrom for $t + impl TryFrom for $t { + type Error = (); + + #[inline] + fn try_from(value: WasmValue) -> Result { + if let WasmValue::$variant(i) = value { + Ok(i) + } else { + cold(); + Err(()) + } + } + } + )* + } +} + +impl_conversion_for_wasmvalue! { + i32 => I32, + i64 => I64, + f32 => F32, + f64 => F64 +} diff --git a/examples/archive.rs b/examples/archive.rs new file mode 100644 index 0000000..7c93205 --- /dev/null +++ b/examples/archive.rs @@ -0,0 +1,29 @@ +use color_eyre::eyre::Result; +use tinywasm::{parser::Parser, types::TinyWasmModule, Module, Store}; + +const WASM: &str = r#" +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + local.get $lhs + local.get $rhs + i32.add) + (export "add" (func $add))) +"#; + +fn main() -> Result<()> { + 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 + + let module: Module = TinyWasmModule::from_twasm(&twasm)?.into(); + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + + assert_eq!(add.call(&mut store, (1, 2))?, 3); + + Ok(()) +} diff --git a/examples/linking.rs b/examples/linking.rs new file mode 100644 index 0000000..f278266 --- /dev/null +++ b/examples/linking.rs @@ -0,0 +1,41 @@ +use color_eyre::eyre::Result; +use tinywasm::{Module, Store}; + +const WASM_ADD: &str = r#" +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + local.get $lhs + local.get $rhs + i32.add) + (export "add" (func $add))) +"#; + +const WASM_IMPORT: &str = r#" +(module + (import "adder" "add" (func $add (param i32 i32) (result i32))) + (func $main (result i32) + i32.const 1 + i32.const 2 + call $add) + (export "main" (func $main)) +) +"#; + +fn main() -> Result<()> { + let wasm_add = wat::parse_str(WASM_ADD).expect("failed to parse wat"); + let wasm_import = wat::parse_str(WASM_IMPORT).expect("failed to parse wat"); + + let add_module = Module::parse_bytes(&wasm_add)?; + let import_module = Module::parse_bytes(&wasm_import)?; + + let mut store = Store::default(); + let add_instance = add_module.instantiate(&mut store, None)?; + + let mut imports = tinywasm::Imports::new(); + imports.link_module("adder", add_instance.id())?; + let import_instance = import_module.instantiate(&mut store, Some(imports))?; + + let main = import_instance.exported_func::<(), i32>(&store, "main")?; + assert_eq!(main.call(&mut store, ())?, 3); + Ok(()) +} diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..6f79c0f --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,22 @@ +use color_eyre::eyre::Result; +use tinywasm::{Module, Store}; + +const WASM: &str = r#" +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + local.get $lhs + local.get $rhs + i32.add) + (export "add" (func $add))) +"#; + +fn main() -> Result<()> { + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + + assert_eq!(add.call(&mut store, (1, 2))?, 3); + Ok(()) +} diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 112222c..b57a1da 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,6 +1,22 @@ use color_eyre::eyre::Result; use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; +/// Examples of using WebAssembly compiled from Rust with tinywasm. +/// +/// These examples are meant to be run with `cargo run --example wasm-rust `. +/// For example, `cargo run --example wasm-rust hello`. +/// +/// To run these, you first need to compile the Rust examples to WebAssembly: +/// +/// ```sh +/// ./examples/rust/build.sh +/// ``` +/// +/// 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 +/// fn main() -> Result<()> { let args = std::env::args().collect::>(); if args.len() < 2 { From 4919d8c2f86f69f669a1afba2a28d5c344fe7197 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 29 Jan 2024 23:12:20 +0100 Subject: [PATCH 23/27] perf: improve benchmarks Signed-off-by: Henry Gressmann --- benches/README.md => BENCHMARKS.md | 19 +++++++++++--- Cargo.toml | 2 +- README.md | 6 +---- benches/fibonacci.rs | 42 +++++++++++++++--------------- benches/selfhosted.rs | 18 ++++++------- benches/util/mod.rs | 30 +++++++++++++++++++-- examples/README.md | 23 ---------------- 7 files changed, 75 insertions(+), 65 deletions(-) rename benches/README.md => BENCHMARKS.md (55%) delete mode 100644 examples/README.md diff --git a/benches/README.md b/BENCHMARKS.md similarity index 55% rename from benches/README.md rename to BENCHMARKS.md index 39ba321..c86679c 100644 --- a/benches/README.md +++ b/BENCHMARKS.md @@ -20,12 +20,23 @@ All runtimes are compiled with the following settings: - `unsafe` features are enabled - `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. -## Benchmarking +# Running benchmarks -Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs) and can be found in the `benches` directory. - -## Running benchmarks +Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs). To run a benchmark, use the following command: ```sh $ cargo bench --bench ``` + +## Profiling + +To profile a benchmark, use the following command: + +```sh +$ cargo flamegraph --bench -- --bench +``` + +This will generate a flamegraph in `flamegraph.svg` and a `perf.data` file. +You can use [hotspot](https://github.com/KDAB/hotspot) to analyze the `perf.data` file. +Since a lot of functions are inlined, you probably want to remove the `#[inline]` attribute from the functions you care about. +Note that this will make the benchmark considerably slower, 2-10x slower in some cases. diff --git a/Cargo.toml b/Cargo.toml index 3bbbb35..fae5388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ debug=true color-eyre="0.6" criterion={version="0.5", features=["html_reports"]} -tinywasm={path="crates/tinywasm"} +tinywasm={path="crates/tinywasm", features=["unsafe"]} wat={version="1.0"} wasmi={version="0.31", features=["std"]} wasmer={version="4.2", features=["cranelift", "singlepass"]} diff --git a/README.md b/README.md index 8a4aa93..17e966f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## Why TinyWasm? - **Tiny** - Designed to be as small as possible without sacrificing too much performance or functionality. -- **Fast enough** - TinyWasm is reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details. +- **Fast enough** - TinyWasm is reasonably fast, especially when compared to other interpreters. See [Benchmarks](./BENCHMARKS.md) for more details. - **Portable** - Runs on any platform llvm supports, including WebAssembly. Minimal external dependencies. ## Status @@ -69,10 +69,6 @@ $ tinywasm-cli --help 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. -## Performance - -> Benchmarks are coming soon. - ## License Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option. diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index 7c77ebf..ca83869 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -1,41 +1,41 @@ mod util; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use tinywasm::types::TinyWasmModule; -use util::tinywasm_module; +use util::wasm_to_twasm; -fn run_tinywasm(module: TinyWasmModule, iterations: i32) { - use tinywasm::*; - let module = Module::from(module); - let mut store = Store::default(); - let imports = Imports::default(); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::(&store, "fibonacci").expect("exported_func"); +fn run_tinywasm(twasm: &[u8], iterations: i32, name: &str) { + let (mut store, instance) = util::tinywasm(twasm); + let hello = instance.exported_func::(&store, name).expect("exported_func"); hello.call(&mut store, iterations).expect("call"); } -fn run_wasmi(iterations: i32) { - use wasmi::*; - let engine = Engine::default(); - let module = wasmi::Module::new(&engine, FIBONACCI).expect("wasmi::Module::new"); - let mut store = Store::new(&engine, ()); - let linker = >::new(&engine); +fn run_wasmi(wasm: &[u8], iterations: i32, name: &str) { + let (module, mut store, linker) = util::wasmi(wasm); let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let hello = instance.get_typed_func::(&mut store, "fibonacci").expect("get_typed_func"); + let hello = instance.get_typed_func::(&mut store, name).expect("get_typed_func"); hello.call(&mut store, iterations).expect("call"); } const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); fn criterion_benchmark(c: &mut Criterion) { - let module = tinywasm_module(FIBONACCI); + let twasm = wasm_to_twasm(FIBONACCI); - let mut group = c.benchmark_group("fibonacci"); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone(), black_box(60)))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(black_box(60)))); + { + let mut group = c.benchmark_group("fibonacci"); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(60), "fibonacci"))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(&FIBONACCI, black_box(60), "fibonacci"))); + } + + { + let mut group = c.benchmark_group("fibonacci-recursive"); + group.measurement_time(std::time::Duration::from_secs(5)); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(26), "fibonacci_recursive"))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(&FIBONACCI, black_box(26), "fibonacci_recursive"))); + } } criterion_group!( name = benches; - config = Criterion::default().sample_size(50).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + config = Criterion::default().significance_level(0.1); targets = criterion_benchmark ); diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index b9df1af..4464579 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -1,11 +1,11 @@ mod util; use criterion::{criterion_group, criterion_main, Criterion}; -use tinywasm::types::TinyWasmModule; -use util::tinywasm_module; -fn run_tinywasm(module: TinyWasmModule) { +use crate::util::twasm_to_module; + +fn run_tinywasm(twasm: &[u8]) { use tinywasm::*; - let module = Module::from(module); + let module = twasm_to_module(twasm); let mut store = Store::default(); let mut imports = Imports::default(); imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); @@ -14,10 +14,10 @@ fn run_tinywasm(module: TinyWasmModule) { hello.call(&mut store, ()).expect("call"); } -fn run_wasmi() { +fn run_wasmi(wasm: &[u8]) { use wasmi::*; let engine = Engine::default(); - let module = wasmi::Module::new(&engine, TINYWASM).expect("wasmi::Module::new"); + let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); let mut store = Store::new(&engine, ()); let mut linker = >::new(&engine); linker.define("env", "printi32", Func::wrap(&mut store, |_: Caller<'_, ()>, _: i32| {})).expect("define"); @@ -28,11 +28,11 @@ fn run_wasmi() { const TINYWASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); fn criterion_benchmark(c: &mut Criterion) { - let module = tinywasm_module(TINYWASM); + let twasm = util::wasm_to_twasm(TINYWASM); let mut group = c.benchmark_group("selfhosted"); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(module.clone()))); - group.bench_function("wasmi", |b| b.iter(run_wasmi)); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); } criterion_group!( diff --git a/benches/util/mod.rs b/benches/util/mod.rs index 7baddb7..69510a5 100644 --- a/benches/util/mod.rs +++ b/benches/util/mod.rs @@ -1,6 +1,32 @@ +#![allow(dead_code)] + use tinywasm::{self, parser::Parser, types::TinyWasmModule}; -pub fn tinywasm_module(wasm: &[u8]) -> TinyWasmModule { +pub fn wasm_to_twasm(wasm: &[u8]) -> Vec { let parser = Parser::new(); - parser.parse_module_bytes(wasm).expect("parse_module_bytes") + let res = parser.parse_module_bytes(wasm).expect("parse_module_bytes"); + res.serialize_twasm().to_vec() +} + +#[inline] +pub fn twasm_to_module(twasm: &[u8]) -> tinywasm::Module { + unsafe { TinyWasmModule::from_twasm_unchecked(&twasm) }.into() +} + +pub fn tinywasm(twasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) { + use tinywasm::*; + let module = twasm_to_module(twasm); + let mut store = Store::default(); + let imports = Imports::default(); + let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); + (store, instance) +} + +pub fn wasmi(wasm: &[u8]) -> (wasmi::Module, wasmi::Store<()>, wasmi::Linker<()>) { + use wasmi::*; + let engine = Engine::default(); + let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); + let store = Store::new(&engine, ()); + let linker = >::new(&engine); + (module, store, linker) } diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 94f974b..0000000 --- a/examples/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Examples - -## Wasm-Rust - -These are examples using WebAssembly generated from Rust code. -To run these, you first need to build the Rust code, since the resulting wasm files are not included in the repository to keep it small. -This requires the `wasm32-unknown-unknown` target and `wasm-opt` to be installed (available via [Binaryen](https://github.com/WebAssembly/binaryen)). - -```bash -$ ./examples/rust/build.sh -``` - -Then you can run the examples: - -```bash -$ cargo run --example wasm-rust -``` - -Where `` is one of the following: - -- `hello`: A simple example that prints a number to the console. -- `tinywasm`: Runs `hello` using TinyWasm - inside of TinyWasm itself! -- `fibonacci`: Calculates the x-th Fibonacci number. From 893396aa3ce270280a2a1a87007a4b84edc8b898 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Tue, 30 Jan 2024 01:12:38 +0100 Subject: [PATCH 24/27] perf: add more benchmarks Signed-off-by: Henry Gressmann --- Cargo.lock | 52 +++++++++++++++++++++++++++++ Cargo.toml | 6 ++++ README.md | 2 +- benches/argon2id.rs | 60 ++++++++++++++++++++++++++++++++++ benches/fibonacci.rs | 42 +++++++++++++++++++++--- benches/selfhosted.rs | 30 ++++++++++++++++- benches/util/mod.rs | 10 ++++++ examples/rust/Cargo.toml | 5 +++ examples/rust/build.sh | 4 +-- examples/rust/src/argon2id.rs | 14 ++++++++ examples/rust/src/fibonacci.rs | 8 +---- examples/rust/src/print.rs | 7 ---- 12 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 benches/argon2id.rs create mode 100644 examples/rust/src/argon2id.rs diff --git a/Cargo.lock b/Cargo.lock index 8f97f2b..396573a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,18 @@ dependencies = [ "serde", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -163,6 +175,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bincode" version = "1.3.3" @@ -196,6 +214,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -855,6 +882,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1652,6 +1680,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -1841,6 +1880,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rayon" version = "1.8.1" @@ -2208,6 +2253,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -2349,6 +2400,7 @@ dependencies = [ name = "tinywasm-root" version = "0.0.0" dependencies = [ + "argon2", "color-eyre", "criterion", "tinywasm", diff --git a/Cargo.toml b/Cargo.toml index fae5388..104c809 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,11 @@ harness=false name="fibonacci" harness=false + +[[bench]] +name="argon2id" +harness=false + [profile.bench] opt-level=3 lto="thin" @@ -48,3 +53,4 @@ wat={version="1.0"} wasmi={version="0.31", features=["std"]} wasmer={version="4.2", features=["cranelift", "singlepass"]} wasmtime={version="17.0", features=["cranelift"]} +argon2={version="0.5"} diff --git a/README.md b/README.md index 17e966f..ec1648b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete. -TinyWasm is not designed for performance, but rather for simplicity, size and portability. However, it is still reasonably fast, especially when compared to other interpreters. See [Performance](#performance) for more details. +TinyWasm is not (yet) designed for performance, but rather for simplicity, size and portability. See [Performance](#performance) for more details. ## Supported Proposals diff --git a/benches/argon2id.rs b/benches/argon2id.rs new file mode 100644 index 0000000..4504812 --- /dev/null +++ b/benches/argon2id.rs @@ -0,0 +1,60 @@ +mod util; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use util::wasm_to_twasm; + +fn run_tinywasm(twasm: &[u8], params: (i32, i32, i32), name: &str) { + let (mut store, instance) = util::tinywasm(twasm); + let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, name).expect("exported_func"); + argon2.call(&mut store, params).expect("call"); +} + +fn run_wasmi(wasm: &[u8], params: (i32, i32, i32), name: &str) { + let (module, mut store, linker) = util::wasmi(wasm); + let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); + let argon2 = instance.get_typed_func::<(i32, i32, i32), i32>(&mut store, name).expect("get_typed_func"); + argon2.call(&mut store, params).expect("call"); +} + +fn run_wasmer(wasm: &[u8], params: (i32, i32, i32), name: &str) { + use wasmer::Value; + let (mut store, instance) = util::wasmer(wasm); + let argon2 = instance.exports.get_function(name).expect("get_function"); + argon2.call(&mut store, &[Value::I32(params.0), Value::I32(params.1), Value::I32(params.2)]).expect("call"); +} + +fn run_native(params: (i32, i32, i32)) { + fn run_native(m_cost: i32, t_cost: i32, p_cost: i32) { + let password = b"password"; + let salt = b"some random salt"; + + let params = argon2::Params::new(m_cost as u32, t_cost as u32, p_cost as u32, None).unwrap(); + let argon = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); + + let mut hash = [0u8; 32]; + argon.hash_password_into(password, salt, &mut hash).unwrap(); + } + run_native(params.0, params.1, params.2) +} + +const ARGON2ID: &[u8] = include_bytes!("../examples/rust/out/argon2id.wasm"); +fn criterion_benchmark(c: &mut Criterion) { + let twasm = wasm_to_twasm(ARGON2ID); + let params = (1000, 2, 1); + + let mut group = c.benchmark_group("argon2id"); + group.measurement_time(std::time::Duration::from_secs(7)); + group.sample_size(10); + + group.bench_function("native", |b| b.iter(|| run_native(black_box(params)))); + group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(params), "argon2id"))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(&ARGON2ID, black_box(params), "argon2id"))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(&ARGON2ID, black_box(params), "argon2id"))); +} + +criterion_group!( + name = benches; + config = Criterion::default().significance_level(0.1); + targets = criterion_benchmark +); + +criterion_main!(benches); diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index ca83869..1d1134a 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -4,15 +4,45 @@ use util::wasm_to_twasm; fn run_tinywasm(twasm: &[u8], iterations: i32, name: &str) { let (mut store, instance) = util::tinywasm(twasm); - let hello = instance.exported_func::(&store, name).expect("exported_func"); - hello.call(&mut store, iterations).expect("call"); + let fib = instance.exported_func::(&store, name).expect("exported_func"); + fib.call(&mut store, iterations).expect("call"); } fn run_wasmi(wasm: &[u8], iterations: i32, name: &str) { let (module, mut store, linker) = util::wasmi(wasm); let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let hello = instance.get_typed_func::(&mut store, name).expect("get_typed_func"); - hello.call(&mut store, iterations).expect("call"); + let fib = instance.get_typed_func::(&mut store, name).expect("get_typed_func"); + fib.call(&mut store, iterations).expect("call"); +} + +fn run_wasmer(wasm: &[u8], iterations: i32, name: &str) { + use wasmer::*; + let engine: Engine = wasmer::Singlepass::default().into(); + let mut store = Store::default(); + let import_object = imports! {}; + let module = wasmer::Module::from_binary(&engine, &wasm).expect("wasmer::Module::from_binary"); + let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); + let fib = instance.exports.get_typed_function::(&mut store, name).expect("get_function"); + fib.call(&mut store, iterations).expect("call"); +} + +fn run_native(n: i32) -> i32 { + let mut sum = 0; + let mut last = 0; + let mut curr = 1; + for _i in 1..n { + sum = last + curr; + last = curr; + curr = sum; + } + sum +} + +fn run_native_recursive(n: i32) -> i32 { + if n <= 1 { + return n; + } + run_native_recursive(n - 1) + run_native_recursive(n - 2) } const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); @@ -21,15 +51,19 @@ fn criterion_benchmark(c: &mut Criterion) { { let mut group = c.benchmark_group("fibonacci"); + group.bench_function("native", |b| b.iter(|| run_native(black_box(60)))); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(60), "fibonacci"))); group.bench_function("wasmi", |b| b.iter(|| run_wasmi(&FIBONACCI, black_box(60), "fibonacci"))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(&FIBONACCI, black_box(60), "fibonacci"))); } { let mut group = c.benchmark_group("fibonacci-recursive"); group.measurement_time(std::time::Duration::from_secs(5)); + group.bench_function("native", |b| b.iter(|| run_native_recursive(black_box(26)))); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, black_box(26), "fibonacci_recursive"))); group.bench_function("wasmi", |b| b.iter(|| run_wasmi(&FIBONACCI, black_box(26), "fibonacci_recursive"))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(&FIBONACCI, black_box(26), "fibonacci_recursive"))); } } diff --git a/benches/selfhosted.rs b/benches/selfhosted.rs index 4464579..57146e7 100644 --- a/benches/selfhosted.rs +++ b/benches/selfhosted.rs @@ -3,6 +3,17 @@ use criterion::{criterion_group, criterion_main, Criterion}; use crate::util::twasm_to_module; +fn run_native() { + use tinywasm::*; + let module = tinywasm::Module::parse_bytes(include_bytes!("../examples/rust/out/print.wasm")).expect("parse"); + let mut store = Store::default(); + let mut imports = Imports::default(); + imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); + let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); + let hello = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print").expect("exported_func"); + hello.call(&mut store, (2, 3)).expect("call"); +} + fn run_tinywasm(twasm: &[u8]) { use tinywasm::*; let module = twasm_to_module(twasm); @@ -26,18 +37,35 @@ fn run_wasmi(wasm: &[u8]) { hello.call(&mut store, ()).expect("call"); } +fn run_wasmer(wasm: &[u8]) { + use wasmer::*; + let engine = wasmer::Engine::default(); + let mut store = Store::default(); + let import_object = imports! { + "env" => { + "printi32" => Function::new_typed(&mut store, |_: i32| {}), + }, + }; + let module = wasmer::Module::from_binary(&engine, &wasm).expect("wasmer::Module::from_binary"); + let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); + let hello = instance.exports.get_function("hello").expect("get_function"); + hello.call(&mut store, &[]).expect("call"); +} + const TINYWASM: &[u8] = include_bytes!("../examples/rust/out/tinywasm.wasm"); fn criterion_benchmark(c: &mut Criterion) { let twasm = util::wasm_to_twasm(TINYWASM); let mut group = c.benchmark_group("selfhosted"); + group.bench_function("native", |b| b.iter(|| run_native())); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm))); group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM))); } criterion_group!( name = benches; - config = Criterion::default().sample_size(500).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); + config = Criterion::default().sample_size(100).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); targets = criterion_benchmark ); diff --git a/benches/util/mod.rs b/benches/util/mod.rs index 69510a5..aa7b418 100644 --- a/benches/util/mod.rs +++ b/benches/util/mod.rs @@ -30,3 +30,13 @@ pub fn wasmi(wasm: &[u8]) -> (wasmi::Module, wasmi::Store<()>, wasmi::Linker<()> let linker = >::new(&engine); (module, store, linker) } + +pub fn wasmer(wasm: &[u8]) -> (wasmer::Store, wasmer::Instance) { + use wasmer::*; + let engine: Engine = wasmer::Singlepass::default().into(); + let mut store = Store::default(); + let import_object = imports! {}; + let module = wasmer::Module::from_binary(&engine, &wasm).expect("wasmer::Module::from_binary"); + let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); + (store, instance) +} diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 2d06488..f3f475e 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -11,6 +11,7 @@ edition="2021" [dependencies] tinywasm={path="../../crates/tinywasm", features=["parser", "std", "unsafe"]} +argon2={version="0.5"} [[bin]] name="hello" @@ -28,6 +29,10 @@ path="src/tinywasm.rs" name="fibonacci" path="src/fibonacci.rs" +[[bin]] +name="argon2id" +path="src/argon2id.rs" + [profile.wasm] opt-level=3 lto="thin" diff --git a/examples/rust/build.sh b/examples/rust/build.sh index cabf00e..e6d3b0d 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash cd "$(dirname "$0")" -bins=("hello" "fibonacci" "print" "tinywasm") +bins=("hello" "fibonacci" "print" "tinywasm" "argon2id") exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" @@ -15,7 +15,7 @@ for bin in "${bins[@]}"; do RUSTFLAGS="-C target-feature=$features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" - wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O --intrinsic-lowering -O + wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -Oz --enable-bulk-memory --enable-multivalue --enable-reference-types --enable-mutable-globals if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" diff --git a/examples/rust/src/argon2id.rs b/examples/rust/src/argon2id.rs new file mode 100644 index 0000000..01ea7ca --- /dev/null +++ b/examples/rust/src/argon2id.rs @@ -0,0 +1,14 @@ +#![no_main] + +#[no_mangle] +pub extern "C" fn argon2id(m_cost: i32, t_cost: i32, p_cost: i32) -> i32 { + let password = b"password"; + let salt = b"some random salt"; + + let params = argon2::Params::new(m_cost as u32, t_cost as u32, p_cost as u32, None).unwrap(); + let argon = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); + + let mut hash = [0u8; 32]; + argon.hash_password_into(password, salt, &mut hash).unwrap(); + hash[0] as i32 +} diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs index 7924aef..8de496d 100644 --- a/examples/rust/src/fibonacci.rs +++ b/examples/rust/src/fibonacci.rs @@ -1,11 +1,5 @@ -#![no_std] #![no_main] - -#[cfg(not(test))] -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - core::arch::wasm32::unreachable() -} +#![allow(non_snake_case)] #[no_mangle] pub extern "C" fn fibonacci(n: i32) -> i32 { diff --git a/examples/rust/src/print.rs b/examples/rust/src/print.rs index 34f3c7f..d04daa3 100644 --- a/examples/rust/src/print.rs +++ b/examples/rust/src/print.rs @@ -1,12 +1,5 @@ -#![no_std] #![no_main] -#[cfg(not(test))] -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - core::arch::wasm32::unreachable() -} - #[link(wasm_import_module = "env")] extern "C" { fn printi32(x: i32); From 7d9c1d0a7478109c70a69c970e9ca037262ee6d3 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Tue, 30 Jan 2024 12:49:53 +0100 Subject: [PATCH 25/27] pref: benchmark results Signed-off-by: Henry Gressmann --- BENCHMARKS.md | 47 ++- Cargo.lock | 771 ++------------------------------------------ Cargo.toml | 1 - benches/util/mod.rs | 6 +- 4 files changed, 69 insertions(+), 756 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index c86679c..13cbda9 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,10 +1,8 @@ # Benchmark results -All benchmarks are run on a Ryzen 7 5800X, with 32GB of RAM, running Linux 6.6 with `intel_pstate=passive split_lock_detect=off mitigations=off`. - -## Results - -Coming soon. +All benchmarks are run on a Ryzen 7 5800X, with 32GB of RAM, running Linux 6.6. +WebAssembly files are optimized using [wasm-opt](https://github.com/WebAssembly/binaryen) +and the benchmark code is available in the `benches` folder. ## WebAssembly Settings @@ -20,6 +18,43 @@ All runtimes are compiled with the following settings: - `unsafe` features are enabled - `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. +## Results + +| Benchmark | Native | TinyWasm | Wasmi | Wasmer (Single Pass) | +| ------------ | ------ | -------- | -------- | -------------------- | +| `argon2id` | 0.52ms | 110.08ms | 44.408ms | 4.76ms | +| `fib` | 6ns | 44.76µs | 48.96µs | 52µs | +| `fib-rec` | 284ns | 25.565ms | 5.11ms | 0.50ms | +| `selfhosted` | 45µs | 2.18ms | 4.25ms | 258.87ms | + +### Argon2id + +This benchmark runs the Argon2id hashing algorithm, with 2 iterations, 1KB of memory, and 1 parallel lane. +I had to decrease the memory usage from the default to 1KB, because especially the interpreters were struggling to finish in a reasonable amount of time. +This is something where `simd` instructions would be really useful, and it also highlights some of the issues with the current implementation of TinyWasm's Value Stack and Memory Instances. + +### Fib + +The first benchmark is a simple optimized Fibonacci function, which is a good way to show the overhead of calling functions and parsing the bytecode. +TinyWasm is slightly faster then Wasmi here, but that's probably because of the overhead of parsing the bytecode as TinyWasm uses a custom bytecode to pre-process the WebAssembly bytecode. + +### Fib-Rec + +This benchmark is a recursive Fibonacci function, which highlights some of the issues with the current implementation of TinyWasm's Call Stack. +TinyWasm is a lot slower here, but that's because there's currently no way to reuse the same Call Frame for recursive calls, so a new Call Frame is allocated for every call. This is not a problem for most programs, and the upcoming `tail-call` proposal will make this a lot easier to implement. + +### Selfhosted + +This benchmark runs TinyWasm itself in the VM, and parses and executes the `print.wasm` example from the `examples` folder. +This is a godd way to show some of TinyWasm's strengths - the code is pretty large at 702KB and Wasmer struggles massively with it, even with the Single Pass compiler. I think it's a decent real-world performance benchmark, but definitely favors TinyWasm a bit. + +Wasmer also offers a pre-parsed module format, so keep in mind that this number could be a bit lower if that was used (but probably still on the same order of magnitude). This number seems so high that I'm not sure if I'm doing something wrong, so I will be looking into this in the future. + +### Conclusion + +After profiling and fixing some low hanging fruits, I found the biggest bottleneck to be Vector operations, especially for the Value Stack, and having shared access to Memory Instances using RefCell. These are the two areas I will be focusing on improving in the future, trying out to use +Arena Allocation and other data structures to improve performance. Still, I'm quite happy with the results, especially considering the use of standard Rust data structures. Additionally, typed FuncHandles have a significant overhead over the untyped ones, so I will be looking into improving that as well. + # Running benchmarks Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs). To run a benchmark, use the following command: @@ -28,7 +63,7 @@ Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs $ cargo bench --bench ``` -## Profiling +# Profiling To profile a benchmark, use the following command: diff --git a/Cargo.lock b/Cargo.lock index 396573a..19db99d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,18 +28,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.2" @@ -76,18 +64,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" -[[package]] -name = "anyhow" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" - [[package]] name = "argh" version = "0.1.12" @@ -137,17 +113,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "async-trait" -version = "0.1.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -169,27 +134,12 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -322,7 +272,6 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "jobserver", "libc", ] @@ -502,15 +451,6 @@ dependencies = [ "windows-sys 0.33.0", ] -[[package]] -name = "cpp_demangle" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" -dependencies = [ - "cfg-if", -] - [[package]] name = "cpufeatures" version = "0.2.12" @@ -526,16 +466,7 @@ version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" dependencies = [ - "cranelift-entity 0.91.1", -] - -[[package]] -name = "cranelift-bforest" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d819feeda4c420a18f1e28236ca0ce1177b22bf7c8a44ddee92dfe40de15bcf0" -dependencies = [ - "cranelift-entity 0.104.0", + "cranelift-entity", ] [[package]] @@ -546,36 +477,15 @@ checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" dependencies = [ "arrayvec", "bumpalo", - "cranelift-bforest 0.91.1", - "cranelift-codegen-meta 0.91.1", - "cranelift-codegen-shared 0.91.1", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", "cranelift-egraph", - "cranelift-entity 0.91.1", - "cranelift-isle 0.91.1", + "cranelift-entity", + "cranelift-isle", "gimli 0.26.2", "log", - "regalloc2 0.5.1", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b8d03d5bdbca7e5f72b0e0a0f69933ed1f09e24be6c075aa6fe3f802b0cc0c" -dependencies = [ - "bumpalo", - "cranelift-bforest 0.104.0", - "cranelift-codegen-meta 0.104.0", - "cranelift-codegen-shared 0.104.0", - "cranelift-control", - "cranelift-entity 0.104.0", - "cranelift-isle 0.104.0", - "gimli 0.28.1", - "hashbrown 0.14.3", - "log", - "regalloc2 0.9.3", + "regalloc2", "smallvec", "target-lexicon", ] @@ -586,16 +496,7 @@ version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" dependencies = [ - "cranelift-codegen-shared 0.91.1", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3fd3664e38e51649b17dc30cfdd561273fe2f590dcd013fb75d9eabc6272dfb" -dependencies = [ - "cranelift-codegen-shared 0.104.0", + "cranelift-codegen-shared", ] [[package]] @@ -604,31 +505,16 @@ version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" -[[package]] -name = "cranelift-codegen-shared" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b031ec5e605828975952622b5a77d49126f20ffe88d33719a0af66b23a0fc36" - -[[package]] -name = "cranelift-control" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fada054d017cf2ed8f7ed2336e0517fc1b19e6825be1790de9eb00c94788362b" -dependencies = [ - "arbitrary", -] - [[package]] name = "cranelift-egraph" version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" dependencies = [ - "cranelift-entity 0.91.1", + "cranelift-entity", "fxhash", "hashbrown 0.12.3", - "indexmap 1.9.3", + "indexmap", "log", "smallvec", ] @@ -639,35 +525,13 @@ version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" -[[package]] -name = "cranelift-entity" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177b6f94ae8de6348eb45bf977c79ab9e3c40fc3ac8cb7ed8109560ea39bee7d" -dependencies = [ - "serde", - "serde_derive", -] - [[package]] name = "cranelift-frontend" version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" dependencies = [ - "cranelift-codegen 0.91.1", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-frontend" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebebd23a69a23e3ddea78e98ff3a2de222e88c8e045d81ef4a72f042e0d79dbd" -dependencies = [ - "cranelift-codegen 0.104.0", + "cranelift-codegen", "log", "smallvec", "target-lexicon", @@ -679,39 +543,6 @@ version = "0.91.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" -[[package]] -name = "cranelift-isle" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1571bfc14df8966d12c6121b5325026591a4b4009e22fea0fe3765ab7cd33b96" - -[[package]] -name = "cranelift-native" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a69c37e0c10b46fe5527f2397ac821046efbf5f7ec112c8b84df25712f465b" -dependencies = [ - "cranelift-codegen 0.104.0", - "libc", - "target-lexicon", -] - -[[package]] -name = "cranelift-wasm" -version = "0.104.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3fef8bbceb8cb56d3f1778b0418d75c5cf12ec571a35fc01eb41abb0227a25" -dependencies = [ - "cranelift-codegen 0.104.0", - "cranelift-entity 0.104.0", - "cranelift-frontend 0.104.0", - "itertools", - "log", - "smallvec", - "wasmparser 0.118.1", - "wasmtime-types", -] - [[package]] name = "crc32fast" version = "1.3.2" @@ -854,15 +685,6 @@ dependencies = [ "parking_lot_core", ] -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "uuid", -] - [[package]] name = "derivative" version = "2.2.0" @@ -885,16 +707,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -1029,12 +841,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "errno" version = "0.3.8" @@ -1061,12 +867,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "fdeflate" version = "0.3.4" @@ -1183,19 +983,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "fxprof-processed-profile" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" -dependencies = [ - "bitflags 2.4.2", - "debugid", - "fxhash", - "serde", - "serde_json", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1233,8 +1020,8 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ - "fallible-iterator 0.2.0", - "indexmap 1.9.3", + "fallible-iterator", + "indexmap", "stable_deref_trait", ] @@ -1243,11 +1030,6 @@ name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -dependencies = [ - "fallible-iterator 0.3.0", - "indexmap 2.1.0", - "stable_deref_trait", -] [[package]] name = "globset" @@ -1278,16 +1060,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.7", + "ahash", ] [[package]] @@ -1295,15 +1068,6 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash 0.8.7", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1340,12 +1104,6 @@ dependencies = [ "cc", ] -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - [[package]] name = "ident_case" version = "1.0.1" @@ -1392,17 +1150,6 @@ dependencies = [ "hashbrown 0.12.3", ] -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] - [[package]] name = "indexmap-nostd" version = "0.4.0" @@ -1435,35 +1182,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "ittapi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" -dependencies = [ - "anyhow", - "ittapi-sys", - "log", -] - -[[package]] -name = "ittapi-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" -dependencies = [ - "cc", -] - -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.3.1" @@ -1561,15 +1279,6 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" -[[package]] -name = "memfd" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" -dependencies = [ - "rustix", -] - [[package]] name = "memmap2" version = "0.5.10" @@ -1597,15 +1306,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1637,9 +1337,6 @@ version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "crc32fast", - "hashbrown 0.14.3", - "indexmap 2.1.0", "memchr", ] @@ -1836,15 +1533,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "ptr_meta" version = "0.1.4" @@ -1938,19 +1626,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "regalloc2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" -dependencies = [ - "hashbrown 0.13.2", - "log", - "rustc-hash", - "slice-group-by", - "smallvec", -] - [[package]] name = "regex" version = "1.10.3" @@ -2011,7 +1686,7 @@ dependencies = [ "bytecheck 0.6.11", "bytes", "hashbrown 0.12.3", - "indexmap 1.9.3", + "indexmap", "ptr_meta", "rend", "rkyv_derive", @@ -2072,12 +1747,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc_version" version = "0.4.0" @@ -2241,12 +1910,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "sptr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2406,7 +2069,6 @@ dependencies = [ "tinywasm", "wasmer", "wasmi", - "wasmtime", "wat", ] @@ -2419,15 +2081,6 @@ dependencies = [ "rkyv", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "tracing" version = "0.1.40" @@ -2520,12 +2173,6 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "url" version = "2.5.0" @@ -2648,15 +2295,6 @@ version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" -[[package]] -name = "wasm-encoder" -version = "0.38.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.39.0" @@ -2682,7 +2320,7 @@ dependencies = [ "bytes", "cfg-if", "derivative", - "indexmap 1.9.3", + "indexmap", "js-sys", "more-asserts", "rustc-demangle", @@ -2726,7 +2364,7 @@ dependencies = [ "thiserror", "wasmer-types", "wasmer-vm", - "wasmparser 0.95.0", + "wasmparser", "winapi", ] @@ -2736,9 +2374,9 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d96bce6fad15a954edcfc2749b59e47ea7de524b6ef3df392035636491a40b4" dependencies = [ - "cranelift-codegen 0.91.1", - "cranelift-entity 0.91.1", - "cranelift-frontend 0.91.1", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", "gimli 0.26.2", "more-asserts", "rayon", @@ -2789,7 +2427,7 @@ dependencies = [ "bytecheck 0.6.11", "enum-iterator", "enumset", - "indexmap 1.9.3", + "indexmap", "more-asserts", "rkyv", "target-lexicon", @@ -2811,11 +2449,11 @@ dependencies = [ "derivative", "enum-iterator", "fnv", - "indexmap 1.9.3", + "indexmap", "lazy_static", "libc", "mach", - "memoffset 0.8.0", + "memoffset", "more-asserts", "region", "scopeguard", @@ -2861,20 +2499,10 @@ version = "0.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a" dependencies = [ - "indexmap 1.9.3", + "indexmap", "url", ] -[[package]] -name = "wasmparser" -version = "0.118.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9" -dependencies = [ - "indexmap 2.1.0", - "semver", -] - [[package]] name = "wasmparser-nostd" version = "0.100.1" @@ -2884,289 +2512,6 @@ dependencies = [ "indexmap-nostd", ] -[[package]] -name = "wasmtime" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910fabce77e660f0e0e41cfd5f69fc8bf020a025f059718846e918db7177f469" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "bumpalo", - "cfg-if", - "fxprof-processed-profile", - "indexmap 2.1.0", - "libc", - "log", - "object", - "once_cell", - "paste", - "rayon", - "serde", - "serde_derive", - "serde_json", - "target-lexicon", - "wasm-encoder 0.38.1", - "wasmparser 0.118.1", - "wasmtime-cache", - "wasmtime-component-macro", - "wasmtime-cranelift", - "wasmtime-environ", - "wasmtime-fiber", - "wasmtime-jit", - "wasmtime-runtime", - "wat", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-asm-macros" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37288142e9b4a61655a3bcbdc7316c2e4bb9e776b10ce3dd758f8186b4469572" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "wasmtime-cache" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45cbd74a636f09d2108f9405c79857f061e19323e4abeed22e837cfe7b08a22b" -dependencies = [ - "anyhow", - "base64", - "bincode", - "directories-next", - "log", - "rustix", - "serde", - "serde_derive", - "sha2", - "toml", - "windows-sys 0.52.0", - "zstd", -] - -[[package]] -name = "wasmtime-component-macro" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad63de18eb42e586386b6091f787c82707cbd5ac5e9343216dba1976190cd03a" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn 2.0.48", - "wasmtime-component-util", - "wasmtime-wit-bindgen", - "wit-parser", -] - -[[package]] -name = "wasmtime-component-util" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0a160c0c44369aa4bee6d311a8e4366943bab1651040cc8b0fcec2c9eb8906" - -[[package]] -name = "wasmtime-cranelift" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3734cc01b7cd37bc62fdbcd9529ca9547440052d4b3886cfdec3b8081a5d3647" -dependencies = [ - "anyhow", - "cfg-if", - "cranelift-codegen 0.104.0", - "cranelift-control", - "cranelift-entity 0.104.0", - "cranelift-frontend 0.104.0", - "cranelift-native", - "cranelift-wasm", - "gimli 0.28.1", - "log", - "object", - "target-lexicon", - "thiserror", - "wasmparser 0.118.1", - "wasmtime-cranelift-shared", - "wasmtime-environ", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-cranelift-shared" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0eb33cd30c47844aa228d4d0030587e65c1108343f311fe9f7248b5bd9cb65c" -dependencies = [ - "anyhow", - "cranelift-codegen 0.104.0", - "cranelift-control", - "cranelift-native", - "gimli 0.28.1", - "object", - "target-lexicon", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-environ" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a056b041fdea604f0972e2fae97958e7748d629a55180228348baefdfc217" -dependencies = [ - "anyhow", - "cranelift-entity 0.104.0", - "gimli 0.28.1", - "indexmap 2.1.0", - "log", - "object", - "serde", - "serde_derive", - "target-lexicon", - "thiserror", - "wasmparser 0.118.1", - "wasmtime-types", -] - -[[package]] -name = "wasmtime-fiber" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43987d0977c07f15c3608c2f255870c127ffd19e35eeedb1ac1dccedf9932a42" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "rustix", - "wasmtime-asm-macros", - "wasmtime-versioned-export-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-jit" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3e48395ac672b386ed588d97a9612aa13a345008f26466f0dfb2a91628aa9f" -dependencies = [ - "addr2line", - "anyhow", - "bincode", - "cfg-if", - "cpp_demangle", - "gimli 0.28.1", - "ittapi", - "log", - "object", - "rustc-demangle", - "rustix", - "serde", - "serde_derive", - "target-lexicon", - "wasmtime-environ", - "wasmtime-jit-debug", - "wasmtime-jit-icache-coherence", - "wasmtime-runtime", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-jit-debug" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd21fd0f5ca68681d3d5b636eea00f182d0f9d764144469e9257fd7e3f55ae0e" -dependencies = [ - "object", - "once_cell", - "rustix", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-jit-icache-coherence" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc26415bb89e9ccd3bdc498fef63aabf665c4c0dd710c107691deb9694955da" -dependencies = [ - "cfg-if", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-runtime" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abddaf17912aabaf39be0802d5eba9a002e956e902d1ebd438a2fe1c88769a2" -dependencies = [ - "anyhow", - "cc", - "cfg-if", - "indexmap 2.1.0", - "libc", - "log", - "mach", - "memfd", - "memoffset 0.9.0", - "paste", - "psm", - "rustix", - "sptr", - "wasm-encoder 0.38.1", - "wasmtime-asm-macros", - "wasmtime-environ", - "wasmtime-fiber", - "wasmtime-jit-debug", - "wasmtime-versioned-export-macros", - "wasmtime-wmemcheck", - "windows-sys 0.52.0", -] - -[[package]] -name = "wasmtime-types" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35a95cdc1433729085beab42c0a5c742b431f25b17c40d7718e46df63d5ffc7" -dependencies = [ - "cranelift-entity 0.104.0", - "serde", - "serde_derive", - "thiserror", - "wasmparser 0.118.1", -] - -[[package]] -name = "wasmtime-versioned-export-macros" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad322733fe67e45743784d8b1df452bcb54f581572a4f1a646a4332deecbcc2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "wasmtime-wit-bindgen" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e5675998fdc74495afdd90ad2bd221206a258075b23048af0535a969b07893" -dependencies = [ - "anyhow", - "heck", - "indexmap 2.1.0", - "wit-parser", -] - -[[package]] -name = "wasmtime-wmemcheck" -version = "17.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b20a19e10d8cb50b45412fb21192982b7ce85c0122dc33bb71f1813e25dc6e52" - [[package]] name = "wast" version = "70.0.0" @@ -3176,7 +2521,7 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.39.0", + "wasm-encoder", ] [[package]] @@ -3428,23 +2773,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "wit-parser" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.1.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", -] - [[package]] name = "wyz" version = "0.5.1" @@ -3465,52 +2793,3 @@ dependencies = [ "once_cell", "pkg-config", ] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 104c809..d2613d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,5 +52,4 @@ tinywasm={path="crates/tinywasm", features=["unsafe"]} wat={version="1.0"} wasmi={version="0.31", features=["std"]} wasmer={version="4.2", features=["cranelift", "singlepass"]} -wasmtime={version="17.0", features=["cranelift"]} argon2={version="0.5"} diff --git a/benches/util/mod.rs b/benches/util/mod.rs index aa7b418..03e2075 100644 --- a/benches/util/mod.rs +++ b/benches/util/mod.rs @@ -33,10 +33,10 @@ pub fn wasmi(wasm: &[u8]) -> (wasmi::Module, wasmi::Store<()>, wasmi::Linker<()> pub fn wasmer(wasm: &[u8]) -> (wasmer::Store, wasmer::Instance) { use wasmer::*; - let engine: Engine = wasmer::Singlepass::default().into(); - let mut store = Store::default(); + let compiler = Singlepass::default(); + let mut store = Store::new(compiler); let import_object = imports! {}; - let module = wasmer::Module::from_binary(&engine, &wasm).expect("wasmer::Module::from_binary"); + let module = Module::new(&store, wasm).expect("wasmer::Module::new"); let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); (store, instance) } From d9190b5e7c391672604cba487802857eac89f6b7 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Tue, 30 Jan 2024 13:08:42 +0100 Subject: [PATCH 26/27] docs: update readme Signed-off-by: Henry Gressmann --- BENCHMARKS.md | 35 ++++++++----- Cargo.lock | 138 +++++++++++++++++++++++++------------------------- README.md | 28 +++++----- 3 files changed, 103 insertions(+), 98 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 13cbda9..78f05d7 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,9 +1,11 @@ # Benchmark results -All benchmarks are run on a Ryzen 7 5800X, with 32GB of RAM, running Linux 6.6. -WebAssembly files are optimized using [wasm-opt](https://github.com/WebAssembly/binaryen) +All benchmarks are run on a Ryzen 7 5800X with 32GB of RAM, running Linux 6.6. +WebAssembly files are optimized using [wasm-opt](https://github.com/WebAssembly/binaryen), and the benchmark code is available in the `benches` folder. +These are mainly preliminary benchmarks, and I will be adding more in the future that are also looking into memory usage and other metrics. + ## WebAssembly Settings All WebAssembly files are compiled with the following settings: @@ -15,45 +17,50 @@ All WebAssembly files are compiled with the following settings: All runtimes are compiled with the following settings: -- `unsafe` features are enabled +- `unsafe` features are enabled. - `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. +## Versions + +- `tinywasm`: `0.4.0` +- `wasmi`: `0.31.0` +- `wasmer`: `4.2.0` + ## Results | Benchmark | Native | TinyWasm | Wasmi | Wasmer (Single Pass) | | ------------ | ------ | -------- | -------- | -------------------- | -| `argon2id` | 0.52ms | 110.08ms | 44.408ms | 4.76ms | | `fib` | 6ns | 44.76µs | 48.96µs | 52µs | | `fib-rec` | 284ns | 25.565ms | 5.11ms | 0.50ms | +| `argon2id` | 0.52ms | 110.08ms | 44.408ms | 4.76ms | | `selfhosted` | 45µs | 2.18ms | 4.25ms | 258.87ms | -### Argon2id - -This benchmark runs the Argon2id hashing algorithm, with 2 iterations, 1KB of memory, and 1 parallel lane. -I had to decrease the memory usage from the default to 1KB, because especially the interpreters were struggling to finish in a reasonable amount of time. -This is something where `simd` instructions would be really useful, and it also highlights some of the issues with the current implementation of TinyWasm's Value Stack and Memory Instances. - ### Fib The first benchmark is a simple optimized Fibonacci function, which is a good way to show the overhead of calling functions and parsing the bytecode. -TinyWasm is slightly faster then Wasmi here, but that's probably because of the overhead of parsing the bytecode as TinyWasm uses a custom bytecode to pre-process the WebAssembly bytecode. +TinyWasm is slightly faster than Wasmi here, but that's probably because of the overhead of parsing the bytecode, as TinyWasm uses a custom bytecode to pre-process the WebAssembly bytecode. ### Fib-Rec This benchmark is a recursive Fibonacci function, which highlights some of the issues with the current implementation of TinyWasm's Call Stack. TinyWasm is a lot slower here, but that's because there's currently no way to reuse the same Call Frame for recursive calls, so a new Call Frame is allocated for every call. This is not a problem for most programs, and the upcoming `tail-call` proposal will make this a lot easier to implement. +### Argon2id + +This benchmark runs the Argon2id hashing algorithm, with 2 iterations, 1KB of memory, and 1 parallel lane. +I had to decrease the memory usage from the default to 1KB, because especially the interpreters were struggling to finish in a reasonable amount of time. +This is where `simd` instructions would be really useful, and it also highlights some of the issues with the current implementation of TinyWasm's Value Stack and Memory Instances. + ### Selfhosted This benchmark runs TinyWasm itself in the VM, and parses and executes the `print.wasm` example from the `examples` folder. -This is a godd way to show some of TinyWasm's strengths - the code is pretty large at 702KB and Wasmer struggles massively with it, even with the Single Pass compiler. I think it's a decent real-world performance benchmark, but definitely favors TinyWasm a bit. +This is a good way to show some of TinyWasm's strengths - the code is quite large at 702KB and Wasmer struggles massively with it, even with the Single Pass compiler. I think it's a decent real-world performance benchmark, but it definitely favors TinyWasm a bit. Wasmer also offers a pre-parsed module format, so keep in mind that this number could be a bit lower if that was used (but probably still on the same order of magnitude). This number seems so high that I'm not sure if I'm doing something wrong, so I will be looking into this in the future. ### Conclusion -After profiling and fixing some low hanging fruits, I found the biggest bottleneck to be Vector operations, especially for the Value Stack, and having shared access to Memory Instances using RefCell. These are the two areas I will be focusing on improving in the future, trying out to use -Arena Allocation and other data structures to improve performance. Still, I'm quite happy with the results, especially considering the use of standard Rust data structures. Additionally, typed FuncHandles have a significant overhead over the untyped ones, so I will be looking into improving that as well. +After profiling and fixing some low-hanging fruits, I found the biggest bottleneck to be Vector operations, especially for the Value Stack, and having shared access to Memory Instances using RefCell. These are the two areas I will be focusing on improving in the future, trying out Arena Allocation and other data structures to improve performance. Additionally, typed FuncHandles have a significant overhead over the untyped ones, so I will be looking into improving that as well. Still, I'm quite happy with the results, especially considering the use of standard Rust data structures. # Running benchmarks diff --git a/Cargo.lock b/Cargo.lock index 19db99d..dd41279 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,9 +60,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" [[package]] name = "argh" @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" [[package]] name = "byteorder" @@ -283,9 +283,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "android-tzdata", "iana-time-zone", @@ -640,9 +640,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "da01daa5f6d41c91358398e8db4dde38e292378da1f28300b59ef4732b879454" dependencies = [ "darling_core", "darling_macro", @@ -650,9 +650,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "f44f6238b948a3c6c3073cdf53bb0c2d5e024ee27e0f35bfe9d556a12395808a" dependencies = [ "fnv", "ident_case", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "0d2d88bd93979b1feb760a6b5c531ac5ba06bd63e74894c377af02faee07b9cd" dependencies = [ "darling_core", "quote", @@ -1299,9 +1299,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -1640,9 +1640,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -1810,9 +1810,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] @@ -1830,9 +1830,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -1841,9 +1841,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -2035,7 +2035,7 @@ dependencies = [ "tinywasm-parser", "tinywasm-types", "wasm-testsuite", - "wast", + "wast 70.0.2", ] [[package]] @@ -2047,7 +2047,7 @@ dependencies = [ "log", "pretty_env_logger", "tinywasm", - "wast", + "wast 70.0.2", ] [[package]] @@ -2243,29 +2243,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-downcast" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dac026d43bcca6e7ce1c0956ba68f59edf6403e8e930a5d891be72c31a44340" -dependencies = [ - "js-sys", - "once_cell", - "wasm-bindgen", - "wasm-bindgen-downcast-macros", -] - -[[package]] -name = "wasm-bindgen-downcast-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5020cfa87c7cecefef118055d44e3c1fc122c7ec25701d528ee458a0b45f38f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.90" @@ -2297,9 +2274,18 @@ checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-encoder" -version = "0.39.0" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" +checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071" dependencies = [ "leb128", ] @@ -2313,9 +2299,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e626f958755a90a6552b9528f59b58a62ae288e6c17fcf40e99495bc33c60f0" +checksum = "5467c7a23f9be04d5691590bea509dbea27e5ba5810d0020bef908456a495f33" dependencies = [ "bytes", "cfg-if", @@ -2330,7 +2316,6 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", - "wasm-bindgen-downcast", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", @@ -2343,9 +2328,9 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848e1922694cf97f4df680a0534c9d72c836378b5eb2313c1708fe1a75b40044" +checksum = "510ad01a668d774f3a103a7c219bbc0970be93e8f1b27e2fdb48d1f4ccd1deff" dependencies = [ "backtrace", "bytes", @@ -2370,9 +2355,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-cranelift" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d96bce6fad15a954edcfc2749b59e47ea7de524b6ef3df392035636491a40b4" +checksum = "54bf93078990d83960d798de3c5935bddaba771fc2fefb9ed6bab9c0bbdea5c1" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -2389,9 +2374,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-singlepass" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebaa865b40ffb3351b03dab9fe9930a5248c25daebd55b464b79b862d9b55ccd" +checksum = "8f4d6359d66a8bcefac26d48fcb0f3f0882bdf122b52121a1ae21f918706e040" dependencies = [ "byteorder", "dynasm", @@ -2408,9 +2393,9 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f08f80d166a9279671b7af7a09409c28ede2e0b4e3acabbf0e3cb22c8038ba7" +checksum = "1b374fd34d97b1c091d8675f9bc472df52dc6787d139d3762d42c7dc84813a9b" dependencies = [ "proc-macro-error", "proc-macro2", @@ -2420,9 +2405,9 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2c892882f0b416783fb4310e5697f5c30587f6f9555f9d4f2be85ab39d5d3d" +checksum = "0caf1c87937b52aba8e9f920a278e1beda282f7439612c0b48f51a58e7a87bab" dependencies = [ "bytecheck 0.6.11", "enum-iterator", @@ -2436,9 +2421,9 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0a9a57b627fb39e5a491058d4365f099bc9b140031c000fded24a3306d9480" +checksum = "58315c25492bc72a33f47a7d7fb0869a0106fc0164ec051e349a9e1eddba9a01" dependencies = [ "backtrace", "cc", @@ -2514,23 +2499,36 @@ dependencies = [ [[package]] name = "wast" -version = "70.0.0" +version = "64.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee4bc54bbe1c6924160b9f75e374a1d07532e7580eb632c0ee6cdd109bb217e" +checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.32.0", +] + +[[package]] +name = "wast" +version = "70.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d5061300042ff5065123dae1e27d00c03f567d34a2937c8472255148a216dc" +dependencies = [ + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.41.0", ] [[package]] name = "wat" -version = "1.0.83" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0dce8cdc288c717cf01e461a1e451a7b8445d53451123536ba576e423a101a" +checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" dependencies = [ - "wast", + "wast 64.0.0", ] [[package]] diff --git a/README.md b/README.md index ec1648b..da4d738 100644 --- a/README.md +++ b/README.md @@ -12,27 +12,27 @@ ## Why TinyWasm? -- **Tiny** - Designed to be as small as possible without sacrificing too much performance or functionality. -- **Fast enough** - TinyWasm is reasonably fast, especially when compared to other interpreters. See [Benchmarks](./BENCHMARKS.md) for more details. -- **Portable** - Runs on any platform llvm supports, including WebAssembly. Minimal external dependencies. +- **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality. +- **Portable**: TinyWasm runs on any platform that LLVM supports, including WebAssembly itself, with minimal external dependencies. +- **Lightweight**: TinyWasm is easy to integrate and has a low call overhead, making it suitable for scripting and embedding. ## Status -TinyWasm, starting from version `0.3.0`, passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). The 2.0 tests are in progress. This is enough to run most WebAssembly programs, including TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). Results of the testsuite can be found [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). +As of version `0.3.0`, TinyWasm successfully passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). Work on the 2.0 tests is ongoing. This achievement ensures that TinyWasm can run most WebAssembly programs, including versions of TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). The results of the testsuite are available [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). -Some APIs to interact with the runtime are not yet exposed, and the existing ones are subject to change, but the core functionality is mostly complete. - -TinyWasm is not (yet) designed for performance, but rather for simplicity, size and portability. See [Performance](#performance) for more details. +The API is still unstable and may change at any time, so don't use it in production _yet_. Note that TinyWasm isn't primarily designed for high performance; its focus lies more on simplicity, size, and portability. More details on its performance aspects can be found in [BENCHMARKS.md](./BENCHMARKS.md). ## Supported Proposals -- [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) - **Fully implemented** -- [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) - **Fully implemented** -- [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) - **Fully implemented** -- [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) - **Fully implemented** (as of version `0.4.0`) -- [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) - **_Partially implemented_** -- [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) - **_Partially implemented_** -- [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) - **_Partially implemented_** +| Proposal | Implementation Status | Version | +| -------------------------------------------------------------------------------------------------------------------------- | --------------------- | ------- | +| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | Fully implemented | 0.2.0 | +| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | Fully implemented | 0.2.0 | +| [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) | Fully implemented | 0.2.0 | +| [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) | Fully implemented | 0.4.0 | +| [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | Partially implemented | N/A | +| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | Partially implemented | N/A | +| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | Partially implemented | N/A | ## Usage From 712b7da4f6b20a9754dd78be632988bbb66f2cbe Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Tue, 30 Jan 2024 13:09:19 +0100 Subject: [PATCH 27/27] Release 0.4.0 tinywasm@0.4.0 tinywasm-cli@0.4.0 tinywasm-parser@0.4.0 tinywasm-types@0.4.0 Generated by cargo-workspaces --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- crates/cli/Cargo.toml | 2 +- crates/parser/Cargo.toml | 2 +- crates/tinywasm/Cargo.toml | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd41279..e147db6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2022,7 +2022,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tinywasm" -version = "0.3.0" +version = "0.4.0" dependencies = [ "eyre", "libm", @@ -2040,7 +2040,7 @@ dependencies = [ [[package]] name = "tinywasm-cli" -version = "0.3.0" +version = "0.4.0" dependencies = [ "argh", "color-eyre", @@ -2052,7 +2052,7 @@ dependencies = [ [[package]] name = "tinywasm-parser" -version = "0.3.0" +version = "0.4.0" dependencies = [ "log", "tinywasm-types", @@ -2074,7 +2074,7 @@ dependencies = [ [[package]] name = "tinywasm-types" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bytecheck 0.7.0", "log", diff --git a/Cargo.toml b/Cargo.toml index d2613d0..49b73a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ panic="abort" inherits="release" [workspace.package] -version="0.3.0" +version="0.4.0" edition="2021" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 9cde64e..11914ef 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.3.0-alpha.0", path="../tinywasm", features=["std", "parser"]} +tinywasm={version="0.4.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 df8acce..0fee8e1 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace=true # fork of wasmparser with no_std support, see https://github.com/bytecodealliance/wasmtime/issues/3495 wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} log={version="0.4", optional=true} -tinywasm-types={version="0.3.0-alpha.0", path="../types", default-features=false} +tinywasm-types={version="0.4.0", path="../types", default-features=false} [features] default=["std", "logging"] diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index bbc85e9..ad58b9e 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -14,8 +14,8 @@ path="src/lib.rs" [dependencies] log={version="0.4", optional=true} -tinywasm-parser={version="0.3.0-alpha.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.3.0-alpha.0", path="../types", default-features=false} +tinywasm-parser={version="0.4.0", path="../parser", default-features=false, optional=true} +tinywasm-types={version="0.4.0", path="../types", default-features=false} libm={version="0.2", default-features=false} [dev-dependencies] 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