From 27004a8738c7a3c32ac4fb6966f32647b4c96db7 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 26 Apr 2026 17:55:48 +0200 Subject: docs: rework documentation, prepare pre-release Signed-off-by: Henry --- ARCHITECTURE.md | 71 ++++++++++++++----- CHANGELOG.md | 97 ++++++++++++-------------- CONTRIBUTING.md | 2 +- Cargo.toml | 4 +- README.md | 87 ++++++++++++++++------- crates/cli/Cargo.toml | 3 +- crates/cli/README.md | 2 + crates/cli/src/args.rs | 9 ++- crates/cli/src/bin.rs | 10 +-- crates/parser/Cargo.toml | 3 +- crates/parser/README.md | 19 +++-- crates/tinywasm/Cargo.toml | 8 +-- crates/tinywasm/src/instance.rs | 22 +++--- crates/tinywasm/src/interpreter/num_helpers.rs | 12 ++-- crates/tinywasm/src/interpreter/simd/utils.rs | 8 +-- crates/tinywasm/src/lib.rs | 45 +++++++----- crates/tinywasm/tests/internal_refs.rs | 2 +- crates/types/Cargo.toml | 3 +- crates/types/README.md | 4 +- examples/rust/README.md | 3 + 20 files changed, 258 insertions(+), 156 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c5a5a0e..422dbb5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,23 +1,62 @@ -# TinyWasm's Architecture +# TinyWasm Architecture -TinyWasm follows the general Runtime Structure described in the [WebAssembly Specification](https://webassembly.github.io/spec/core/exec/runtime.html). +TinyWasm follows the general runtime model described in the [WebAssembly specification](https://webassembly.github.io/spec/core/exec/runtime.html), but lowers validated WebAssembly into a compact internal instruction format before execution. -Key runtime layout: +## Runtime Layout -- Values are stored in four fixed-capacity typed stacks: `stack_32` (`i32`/`f32`/`funcref`/`externref`), `stack_64` (`i64`/`f64`), `stack_128` (`v128`) -- Locals are allocated in those value stacks. Each `CallFrame` stores `locals_base`, and local ops index from that base. -- Calls use a separate fixed-capacity `CallStack` of `CallFrame`s. -- Structured control (`block`/`loop`/`if`/`br*`) is lowered during parsing to jump-oriented instructions: `Jump`, `JumpIfZero`, `BranchTable*`, `DropKeep*`, and `Return`. -- The interpreter executes this lowered bytecode in a single iterative loop. +- Values are stored in untyped stacks: + - `stack_32` for `i32`, `f32`, `funcref`, and `externref` + - `stack_64` for `i64` and `f64` + - `stack_128` for `v128` +- Locals are stored directly in the value stacks. Each `CallFrame` stores a `locals_base`, and local instructions index from that base. +- Structured control flow (`block`, `loop`, `if`, `br*`) is lowered during parsing to jump-oriented internal instructions such as `Jump`, `JumpIfZero`, `BranchTable*`, `DropKeep*`, and `Return`. +- Execution is a single iterative interpreter loop over the lowered instruction stream. -## Precompiled Modules +## Internal Bytecode -Modules can be serialized to `.twasm` (`serialize_twasm`) and loaded later (`from_twasm`). -This allows deployments that execute precompiled modules without enabling the parser in the runtime binary. +TinyWasm does not interpret WebAssembly instructions directly. During parsing and validation, WebAssembly is translated into TinyWasm's internal bytecode format. -See: +This internal representation is designed to make execution simpler and cheaper: -- [visit.rs](./crates/parser/src/visit.rs) -- [instructions.rs](./crates/types/src/instructions.rs) -- [value_stack.rs](./crates/tinywasm/src/interpreter/stack/value_stack.rs) -- [call_stack.rs](./crates/tinywasm/src/interpreter/stack/call_stack.rs) +- structured control flow is resolved ahead of time +- stack effects are made explicit +- common instruction sequences can be fused into superinstructions +- modules can optionally be serialized as `.twasm` for reuse + +## Optimizer + +During parsing, a peephole optimizer (`optimize.rs`) fuses common instruction sequences into superinstructions. These reduce interpreter dispatch overhead by combining multiple logical operations into one internal instruction. + +Examples include: + +- **Fused binops**: `BinOpLocalLocal*`, `BinOpLocalConst*`, `BinOpStackGlobal*` + Combine local/global access, a binary operation, and sometimes a store/tee. +- **Fused jumps**: `JumpCmpLocalConst*`, `JumpCmpLocalLocal*`, `JumpCmpStackConst*` + Combine comparison and conditional branch logic. + +## Memory Backends + +Linear memory is implemented through the `LinearMemory` trait. The backend is selected with `engine::Config::with_memory_backend()`. + +Available backends: + +- `VecMemory` - contiguous `Vec` backing; the default backend. +- `PagedMemory` - chunk-based allocation, useful when growing memory without reallocating one large buffer. +- `LazyLinearMemory` - wraps another backend and allocates memory on first access. +- Custom backends through `MemoryBackend::custom()`. + +## Future Experiments + +TinyWasm's interpreter is intentionally simple today: validated WebAssembly is lowered to internal instructions, optimized with peephole fusion, and executed by an iterative dispatch loop. + +Future work may explore additional dispatch and code-generation strategies, including Rust's experimental `loop_match` state-machine work, explicit tail calls, more aggressive superinstruction fusion, top-of-stack register allocation, or even optional JIT compilation. + +## Code Map + +- [visit.rs](./crates/parser/src/visit.rs) - WebAssembly binary visitor +- [optimize.rs](./crates/parser/src/optimize.rs) - peephole optimizer and superinstruction fusion +- [parallel.rs](./crates/parser/src/parallel.rs) - multithreaded function parsing +- [instructions.rs](./crates/types/src/instructions.rs) - internal instruction set +- [value_stack.rs](./crates/tinywasm/src/interpreter/stack/value_stack.rs) - typed value stacks +- [call_stack.rs](./crates/tinywasm/src/interpreter/stack/call_stack.rs) - call frame stack +- [memory/mod.rs](./crates/tinywasm/src/store/memory/mod.rs) - memory backend trait and implementations diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b2220..1ddae8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,67 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +This release is a major runtime and API rework. It adds support for several newer WebAssembly proposals, introduces the new `Engine` configuration API, rewrites large parts of execution and validation, and changes the internal `twasm` archive format. Benchmarks in the repository currently show roughly 30-90% improvement over 0.8.0 depending on workload and execution mode. + ### Added - Support for the `custom_page_sizes` proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14)) -- Support for the `tail_call` proposal -- Support for the `memory64` proposal -- Support for the `simd` proposal -- Support for the `relaxed_simd` proposal -- Support for the `wide_arithmetic` proposal +- Support for the `tail_call`, `memory64`, `simd`, `relaxed_simd`, `wide_arithmetic`, and `extended_const` proposals ([#37](https://github.com/explodingcamera/tinywasm/pull/37), [#38](https://github.com/explodingcamera/tinywasm/pull/38), [#39](https://github.com/explodingcamera/tinywasm/pull/39)) +- Parse-only support for the `annotations` proposal - New `Engine` API (`tinywasm::Engine` and `engine::Config`) for runtime configuration -- Resumable function execution with fuel/time-budget APIs (`call_resumable`, `resume_with_fuel`, `resume_with_time_budget`, `ExecProgress`) +- Resumable execution APIs: `call_resumable`, `resume_with_fuel`, `resume_with_time_budget`, and `ExecProgress` - Host-function fuel APIs: `FuncContext::charge_fuel` and `FuncContext::remaining_fuel` -- `engine::FuelPolicy` and `engine::Config::fuel_policy` for fuel accounting behavior -- New `canonicalize_nans` feature flag to enable canonicalizing NaN values in the `f32`, `f64`, and `v128` types -- Public API rework for runtime object access: - - export lookups: `func_untyped`/`func`, `memory`, `table`, `global` - - table/global value access: `global_get`, `global_set` - - generic export access: `extern_item` and `ExternItem` - - export iteration: `ModuleInstance::exports` - - module descriptors: `Module::imports`, `Module::exports` - - handle-based runtime objects with explicit store access: `Memory`, `Table`, `Global`, `Function` +- `engine::Config` support for fuel policy, stack sizing, memory backend selection, and trap-on-OOM behavior +- New feature flags: `canonicalize-nans`, `simd-x86`, `guest-debug`, `debug`, and `parallel-parser` +- Top-level parser re-exports behind the `parser` feature: `parse_bytes`, `parse_file`, and `parse_stream` ### Changed -- Locals are now stored in the typed value stacks instead of a separate locals structure -- Structured control flow is fully lowered to jump-oriented internal instructions during parsing -- Stack and call-stack limits can now be configured via `engine::Config` -- Module-internal by-index inspection APIs are now gated behind the `guest_debug` feature - -### Breaking Changes - -- New backwards-incompatible version of the twasm format based on `postcard` (thanks [@dragonnn](https://github.com/dragonnn)) -- `RefNull` has been removed and replaced with new `FuncRef` and `ExternRef` structs -- `Store::new` now takes an `Engine`; use `Store::default()` for default settings -- `Error`, `Trap`, and `LinkingError` are now `#[non_exhaustive]` -- `Trap` variant discriminant values changed (if you cast variants to integers) -- `tinywasm::interpreter` is no longer a public module; `InterpreterRuntime` and `TinyWasmValue` are no longer public API -- `FuncHandle::name` was removed -- Cargo feature `simd` was removed -- Cargo feature `tinywasm-parser` was renamed to`parser` -- Cargo feature `logging` was renamed to `log` -- Increased MSRV to 1.90 -- `Error::ParseError` was renamed to `Error::Parser`, and `Error::Twasm` was added -- `ModuleInstance` export lookup APIs were renamed: - - `exported_func_untyped` -> `func_untyped` - - `exported_func` -> `func` - - `exported_memory` -> `memory` -- `ModuleInstance` mutable export lookup variants were removed: - - `memory_mut`, `table_mut`, `global_mut` - - `extern_item_mut` -- `FuncHandle` / `FuncHandleTyped` were renamed to `Function` / `FunctionTyped` -- `HostFunction::new` / `HostFunction::func` / `HostFunction::typed` now require `&mut Store` -- `Imports::link_module` now takes a `ModuleInstance` instead of a raw module instance id -- `func_typed` now validates the exact wasm signature at lookup time and fails immediately on mismatches +- `Store::new` now takes an `Engine`; use `Store::default()` for default settings. +- `ModuleInstance::func` now validates exact Wasm signatures at lookup time and fails immediately on mismatches. +- Stack and call-stack limits now come from `engine::Config`, and memory allocation is lazy until first access. +- Module-internal by-index inspection APIs are now gated behind `guest-debug`, and runtime `Debug` implementations are gated behind `debug`. +- `Module` is now re-exported directly from `tinywasm_types`; the `module` submodule was removed. +- MSRV increased to 1.95 and the crate now uses Rust 2024. +- `Error`, `Trap`, and `LinkingError` are now `#[non_exhaustive]`. +- `Trap` variant discriminants changed; do not rely on casting variants to integers. +- `HostFunction::new`, `HostFunction::func`, and `HostFunction::typed` now require `&mut Store`, and `Imports::link_module` now takes a `ModuleInstance` instead of a raw module instance id. +- Cargo features were renamed from `tinywasm-parser` to `parser` and from `logging` to `log`. +- `Error::ParseError` was renamed to `Error::Parser`, and `Error::Twasm` was added. +- `FuncHandle` and `FuncHandleTyped` were renamed to `Function` and `FunctionTyped`, and module export lookups moved from `exported_*` to `func_untyped`, `func`, and `memory`. +- The `twasm` archive format is now postcard-based and backwards-incompatible with previous versions (thanks [@dragonnn](https://github.com/dragonnn)). +- The interpreter was refactored around more superinstruction fusion, lower-overhead dispatch, typed-stack locals, jump-oriented lowering, and optional parallel parsing. + +### Removed + +- Cargo feature `simd` was removed. +- `RefNull` was removed and replaced with `FuncRef` and `ExternRef`. +- `tinywasm::interpreter` is no longer a public module. +- `InterpreterRuntime` and `TinyWasmValue` are no longer public API. +- `FuncHandle::name` was removed. +- Mutable `ModuleInstance` export lookup variants `memory_mut`, `table_mut`, `global_mut`, and `extern_item_mut` were removed. ### Fixed -- Fixed archive **no_std** support which was broken in the previous release, and added more tests to ensure it stays working -- `ModuleInstance::exported_memory` and `FuncContext::exported_memory` are now actually immutable ([#41](https://github.com/explodingcamera/tinywasm/pull/41)) -- Check returns in untyped host functions ([#27](https://github.com/explodingcamera/tinywasm/pull/27)) (thanks [@WhaleKit](https://github.com/WhaleKit)) -- `MemoryRefMut::copy_within(src, dst, len)` now follows its documented argument order -- Imported tables created with `Extern::table(ty, init)` now honor the provided init value +- Fixed archive **no_std** support, which was broken in the previous release, and added tests to ensure it stays working. +- `ModuleInstance::memory` and `FuncContext::memory` are now actually immutable ([#41](https://github.com/explodingcamera/tinywasm/pull/41)). +- Untyped host functions now check return values correctly ([#27](https://github.com/explodingcamera/tinywasm/pull/27)) by [@WhaleKit](https://github.com/WhaleKit). +- `MemoryRefMut::copy_within(src, dst, len)` now follows its documented argument order. +- Imported tables created with `Extern::table(ty, init)` now honor the provided init value. +- Fixed unchecked memory offsets causing issues on 32-bit platforms. + +### Migration Notes + +- Replace `Store::new()` with `Store::default()` for default settings, or `Store::new(Engine::new(config))` for custom runtime configuration. +- Rename the cargo features `tinywasm-parser` to `parser` and `logging` to `log`. +- Rename `FuncHandle` to `Function` and `FuncHandleTyped` to `FunctionTyped`. +- Rename module export lookups from `exported_*` methods to `func`, `func_untyped`, and `memory`. +- Regenerate any persisted `twasm` archives; the format is now postcard-based and not backwards compatible with earlier releases. ## [0.8.0] - 2024-08-29 @@ -184,7 +179,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Lots of bug fixes - Full `no_std` support -## [0.3.0] - 2024-01-11 +## [0.2.0] - 2024-01-11 **All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.1.0...v0.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c713bf..6cb7761 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ Example usage: ```bash cargo install --locked samply -samply record -- cargo run --release --example wasm-rust -- selfhosted +samply record -- cargo run --release --example wasm-rust -- tinywasm ``` ## Commits diff --git a/Cargo.toml b/Cargo.toml index 499a0c7..c3e1ae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ edition="2024" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] repository="https://github.com/explodingcamera/tinywasm" -categories=["wasm", "no-std"] -keywords=["tinywasm"] +categories=["wasm", "no-std", "compilers", "virtualization", "embedded"] +keywords=["tinywasm", "wasm", "webassembly", "interpreter", "no-std"] [package] name="tinywasm-root" diff --git a/README.md b/README.md index faa6700..b6eb19a 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,42 @@ ## Why `tinywasm`? -- **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality -- **Portable**: Runs anywhere Rust can target, supports `no_std`, and keeps external dependencies to a minimum. -- **Safe**: Written entirely safe Rust and designed to prevent untrusted code from crashing the runtime +- **Tiny**: Small by design, without significantly compromising performance or functionality. +- **Portable**: Runs anywhere Rust can target, supports `no_std`, has minimal dependencies, and can itself compile to WebAssembly. +- **Safe**: Written in safe Rust, with optional `unsafe` limited to the `simd-x86` feature. Its sandbox is designed to prevent untrusted Wasm from accessing host memory or escaping the runtime. -## Current Status +## Installation -`tinywasm` passes 100% of WebAssembly MVP and WebAssembly 2.0 tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, support for WebAssembly 3.0 is on the way. See the [Supported Proposals](#supported-proposals) section for more information. +```toml +[dependencies] +tinywasm = { git = "https://github.com/explodingcamera/tinywasm", branch = "next" } +``` ## Usage -See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information on how to use `tinywasm`. -For testing purposes, you can also use the `tinywasm-cli` tool: +```rust +use tinywasm::{ModuleInstance, Store}; + +// Load a module from bytes +let wasm = include_bytes!("../examples/wasm/add.wasm"); +let module = tinywasm::parse_bytes(wasm)?; + +// Create a new store +let mut store = Store::default(); -```sh -$ cargo install tinywasm-cli -$ tinywasm-cli --help +// Instantiate the module +let instance = ModuleInstance::instantiate(&mut store, &module, None)?; + +// Call an exported function with typed parameters +let func = instance.func::<(i32, i32), i32>(&mut store, "add")?; +let result = func.call(&mut store, (1, 2))?; + +assert_eq!(result, 3); ``` -## Feature Flags +See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information. + +## Cargo Features - **`std`**\ Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. @@ -34,13 +51,35 @@ $ tinywasm-cli --help - **`parser`**\ Enables the `tinywasm-parser` crate. This is enabled by default. - **`archive`**\ - Enables pre-parsing of archives. This is enabled by default. + Enables serialization/deserialization of compiled modules to the internal `twasm` bytecode format. This is enabled by default. +- **`canonicalize-nans`**\ + Canonicalizes NaN values to a single representation. This is enabled by default. +- **`debug`**\ + Derives `Debug` for runtime types. This is enabled by default. +- **`parallel-parser`**\ + Parallelizes function parsing and validation across threads (requires `std`). This is enabled by default. +- **`guest-debug`**\ + Exposes module-internal by-index inspection APIs (`*_by_index`). +- **`simd-x86`**\ + Enables x86-specific SIMD intrinsics for `i8x16_swizzle` and `i8x16_shuffle` (uses `unsafe` code). -With all these features disabled, `tinywasm` only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. +With default features disabled, `tinywasm` depends only on `core`, `alloc`, and `libm`[^libm], making it usable in `no_std + alloc` environments. + +Use `Engine` and `engine::Config` when you need non-default runtime settings such as fuel accounting, stack sizing, memory backend selection, or trap-on-OOM behavior. + +[^libm]: [rust-lang/rust#137578](https://github.com/rust-lang/rust/issues/137578) — tracking issue for `libm` as a fallback in `core`. + +## Current Status + +`tinywasm` passes the WebAssembly MVP and WebAssembly 2.0 core testsuites. WebAssembly 3.0 support is still in progress, and some newer proposal suites are tracked in-repo as experimental coverage rather than release guarantees; see [Supported Proposals](#supported-proposals) for details. + +TinyWasm also has its own internal bytecode format, `twasm`. WebAssembly modules can be compiled to `twasm`, which stores TinyWasm's validated and optimized instruction representation for faster loading and reuse. ## Safety -Untrusted WebAssembly code should not be able to crash the runtime or access memory outside of its sandbox. Unvalidated Wasm and untrusted, precompiled twasm bytecode is safe to run as well, but can lead to panics if the bytecode is malformed. In general, it is recommended to validate Wasm bytecode before running it, and to only run trusted twasm bytecode. +TinyWasm only uses safe Rust by default. The optional `simd-x86` feature enables x86-specific SIMD intrinsics and uses `unsafe` internally. WebAssembly input is validated by TinyWasm before execution and runs inside a sandbox: untrusted Wasm should not be able to access host memory, escape the sandbox, or cause undefined behavior in the runtime. + +The internal `twasm` bytecode format is not currently validated as an untrusted input format. Malformed `twasm` may panic, but should not compromise memory safety or allow sandbox escape. Only run trusted `twasm` bytecode, or generate it through TinyWasm from Wasm input. ## Supported Proposals @@ -53,16 +92,13 @@ Untrusted WebAssembly code should not be able to crash the runtime or access mem | [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) | 🟢 | 0.4.0 | | [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | 🟢 | 0.7.0 | | [**Multi-memory**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 | -| [**Annotations**](https://github.com/WebAssembly/annotations/blob/main/proposals/annotations/Overview.md) | 🟢 | `next` | -| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` | -| [**Extended Const**](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md) | 🟢 | `next` | -| [**Fixed-Width SIMD**](https://github.com/WebAssembly/simd/blob/main/proposals/simd/Overview.md) | 🟢 | `next` | -| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | -| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | -| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🟢 | `next` | -| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🟢 | `next` | -| [**Branch Hinting**](https://github.com/WebAssembly/branch-hinting/blob/master/proposals/branch-hinting/Overview.md) | 🌑 | - | -| [**Custom Descriptors**](https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md) | 🌑 | - | +| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | 0.9.0 | +| [**Extended Const**](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md) | 🟢 | 0.9.0 | +| [**Fixed-Width SIMD**](https://github.com/WebAssembly/simd/blob/main/proposals/simd/Overview.md) | 🟢 | 0.9.0 | +| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | 0.9.0 | +| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | 0.9.0 | +| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🟢 | 0.9.0 | +| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🟢 | 0.9.0 | | [**Exception Handling**](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) | 🌑 | - | | [**Typed Function References**](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md) | 🌑 | - | | [**Garbage Collection**](https://github.com/WebAssembly/gc/blob/main/proposals/gc/Overview.md) | 🌑 | - | @@ -76,12 +112,11 @@ Untrusted WebAssembly code should not be able to crash the runtime or access mem ## See Also -I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes: +If you need a more mature, production-tested, or performance-focused WebAssembly runtime today, consider one of these projects: - [wasmi](https://github.com/wasmi-labs/wasmi) - efficient and versatile WebAssembly interpreter for embedded systems - [wasm3](https://github.com/wasm3/wasm3) - a fast WebAssembly interpreter written in C - [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in Go -- [wain](https://github.com/rhysd/wain) - a zero-dependency WebAssembly interpreter written in Rust ## License diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index caf541b..8472afb 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name="tinywasm-cli" version.workspace=true -description="TinyWasm CLI" +description="Command-line interface for TinyWasm" edition.workspace=true license.workspace=true authors.workspace=true @@ -9,6 +9,7 @@ repository.workspace=true rust-version.workspace=true keywords.workspace=true categories=["wasm"] +readme="README.md" [[bin]] name="tinywasm-cli" diff --git a/crates/cli/README.md b/crates/cli/README.md index 70b2cff..aa4d0c8 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -8,4 +8,6 @@ It is recommended to use the library directly instead of the CLI. ```bash $ cargo install tinywasm-cli $ tinywasm-cli --help +$ tinywasm-cli run ./module.wasm +$ tinywasm-cli run ./module.wasm -f add -a i32:1 -a i32:2 ``` diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 1fec2d5..0333c92 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -17,8 +17,11 @@ impl From for WasmValue { impl FromStr for WasmArg { type Err = String; fn from_str(s: &str) -> std::prelude::v1::Result { - let [ty, val]: [&str; 2] = - s.split(':').collect::>().try_into().map_err(|e| format!("invalid arguments: {e:?}"))?; + let [ty, val]: [&str; 2] = s + .split(':') + .collect::>() + .try_into() + .map_err(|_e| "invalid argument format; expected type:value".to_string())?; let arg: WasmValue = match ty { "i32" => val.parse::().map_err(|e| format!("invalid argument value for i32: {e:?}"))?.into(), @@ -26,7 +29,7 @@ impl FromStr for WasmArg { "f32" => val.parse::().map_err(|e| format!("invalid argument value for f32: {e:?}"))?.into(), "f64" => val.parse::().map_err(|e| format!("invalid argument value for f64: {e:?}"))?.into(), "v128" => val.parse::().map_err(|e| format!("invalid argument value for v128: {e:?}"))?.into(), - t => return Err(format!("Invalid arg type: {t}")), + t => return Err(format!("invalid arg type `{t}`; expected one of i32, i64, f32, f64, v128")), }; Ok(WasmArg(arg)) diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 9b283c3..7e32fed 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -19,7 +19,7 @@ struct TinyWasmCli { #[argh(subcommand)] nested: TinyWasmSubcommand, - /// log level + /// log level: trace, debug, info, warn, or error #[argh(option, short = 'l', default = "\"info\".to_string()")] log_level: String, } @@ -53,15 +53,15 @@ struct Run { #[argh(positional)] wasm_file: String, - /// function to run + /// exported function to run; omit to only instantiate and run start #[argh(option, short = 'f')] func: Option, - /// arguments to pass to the wasm file + /// arguments passed to the function in type:value form #[argh(option, short = 'a')] args: Vec, - /// engine to use + /// engine to use (currently only `main`) #[argh(option, short = 'e', default = "Engine::Main")] engine: Engine, } @@ -74,7 +74,7 @@ fn main() -> Result<()> { "warn" => log::LevelFilter::Warn, "error" => log::LevelFilter::Error, "info" => log::LevelFilter::Info, - _ => log::LevelFilter::Info, + other => return Err(eyre::eyre!("invalid log level `{other}`; expected trace, debug, info, warn, or error")), }; pretty_env_logger::formatted_builder().filter_level(level).init(); diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 26bdd51..1c228c4 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -1,7 +1,7 @@ [package] name="tinywasm-parser" version.workspace=true -description="TinyWasm parser" +description="Parser and lowering pipeline for TinyWasm" edition.workspace=true license.workspace=true authors.workspace=true @@ -9,6 +9,7 @@ repository.workspace=true rust-version.workspace=true keywords.workspace=true categories.workspace=true +readme="README.md" [dependencies] wasmparser={workspace=true, features=["validate", "features", "simd"]} diff --git a/crates/parser/README.md b/crates/parser/README.md index c7eef8d..91145f1 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -1,20 +1,29 @@ # `tinywasm-parser` -This crate provides a compiler that can convert WebAssembly modules into a `tinywasm` modules. +This crate provides the parser and lowering pipeline that converts WebAssembly binaries into `tinywasm` modules. ## Features - `std`: Enables the use of `std` and `std::io` for parsing from files and streams. - `log`: Enables logging of the parsing process using the `log` crate. +- `parallel`: Enables multithreaded parsing and validation when `std` is available. ## Usage ```rust -use tinywasm_parser::Parser; +use tinywasm_parser::{Parser, ParserOptions}; + let bytes = include_bytes!("./file.wasm"); let parser = Parser::new(); -let module = parser.parse_module_bytes(bytes).unwrap(); -let module = parser.parse_module_file("path/to/file.wasm").unwrap(); -let module = parser.parse_module_stream(&mut stream).unwrap(); +let module = parser.parse_module_bytes(bytes)?; + +let parser = Parser::with_options(ParserOptions::default().with_rewrite_optimization(false)); +let module = parser.parse_module_bytes(bytes)?; + +let module = parser.parse_module_file("path/to/file.wasm")?; +let mut stream = std::fs::File::open("path/to/file.wasm")?; +let module = parser.parse_module_stream(&mut stream)?; ``` + +If you just want the default configuration, the top-level `parse_bytes`, `parse_file`, and `parse_stream` helpers are thin wrappers around `Parser::new()`. diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 0eb939e..2f80695 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -16,7 +16,7 @@ name="tinywasm" path="src/lib.rs" [package.metadata.docs.rs] -features=["std", "parser", "archive", "log", "canonicalize_nans", "debug", "guest_debug"] +features=["std", "parser", "archive", "log", "canonicalize-nans", "debug", "guest-debug"] rustdoc-args=["--cfg", "docsrs"] [dependencies] @@ -38,7 +38,7 @@ serde_json.workspace=true serde.workspace=true [features] -default=["std", "parser", "log", "archive", "canonicalize_nans", "debug", "parallel-parser"] +default=["std", "parser", "log", "archive", "canonicalize-nans", "debug", "parallel-parser"] log=["dep:log", "tinywasm-parser?/log", "tinywasm-types/log"] std=["tinywasm-parser?/std", "tinywasm-types/std"] @@ -53,13 +53,13 @@ parallel-parser=["parser", "tinywasm-parser?/parallel"] archive=["tinywasm-types/archive"] # canonicalize all NaN values to a single representation -canonicalize_nans=[] +canonicalize-nans=[] # derive Debug for runtime/types structs debug=["tinywasm-types/debug"] # expose module-internal by-index inspection APIs for non-exported entities (for testing and debugging) -guest_debug=[] +guest-debug=[] # enable x86-specific SIMD intrinsics in Value128 (uses unsafe code) # note: for x86 backend selection, compile with x86-64-v3 target features diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 2ec7916..8ef572d 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -260,7 +260,7 @@ impl ModuleInstance { } #[inline] - #[cfg(feature = "guest_debug")] + #[cfg(feature = "guest-debug")] fn index_addr(slice: &[T], idx: u32, kind: &str) -> Result { match slice.get(idx as usize) { Some(addr) => Ok(*addr), @@ -316,8 +316,8 @@ impl ModuleInstance { /// normal export boundary. It is mainly intended for tooling and /// introspection. Calling private functions can change behavior in ways the /// module author did not expose as part of the public API. - #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] - #[cfg(feature = "guest_debug")] + #[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))] + #[cfg(feature = "guest-debug")] pub fn func_by_index(&self, store: &Store, func_index: FuncAddr) -> Result { self.validate_store(store)?; let func_addr = Self::index_addr(&self.0.func_addrs, func_index, "function")?; @@ -343,8 +343,8 @@ impl ModuleInstance { } /// Get a typed function by its module-local index. - #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] - #[cfg(feature = "guest_debug")] + #[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))] + #[cfg(feature = "guest-debug")] pub fn func_typed_by_index< P: IntoWasmValueTuple + WasmTypesFromTuple, R: FromWasmValueTuple + WasmTypesFromTuple, @@ -396,8 +396,8 @@ impl ModuleInstance { /// normal export boundary. It is mainly intended for tooling and /// inspection. Mutating a private memory can change module behavior in ways /// that are not part of the module's public API. - #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] - #[cfg(feature = "guest_debug")] + #[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))] + #[cfg(feature = "guest-debug")] pub fn memory_by_index(&self, memory_index: MemAddr) -> Result { Ok(Memory::from_store_addr(self.0.store_id, Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?)) } @@ -416,8 +416,8 @@ impl ModuleInstance { /// normal export boundary. It is mainly intended for tooling and /// inspection. Mutating a private table can change module behavior in ways /// that are not part of the module's public API. - #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] - #[cfg(feature = "guest_debug")] + #[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))] + #[cfg(feature = "guest-debug")] pub fn table_by_index(&self, table_index: TableAddr) -> Result { Ok(Table::from_store_addr(self.0.store_id, Self::index_addr(&self.0.table_addrs, table_index, "table")?)) } @@ -446,8 +446,8 @@ impl ModuleInstance { /// normal export boundary. It is mainly intended for tooling and /// inspection. Mutating a private global can change module behavior in ways /// that are not part of the module's public API. - #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] - #[cfg(feature = "guest_debug")] + #[cfg_attr(docsrs, doc(cfg(feature = "guest-debug")))] + #[cfg(feature = "guest-debug")] pub fn global_by_index(&self, global_index: GlobalAddr) -> Result { Ok(Global::from_store_addr(self.0.store_id, Self::index_addr(&self.0.global_addrs, global_index, "global")?)) } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 9651ce0..c88d7e4 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -67,9 +67,9 @@ macro_rules! impl_wasm_float_ops { #[inline] fn tw_nearest(self) -> Self { match self { - #[cfg(not(feature = "canonicalize_nans"))] + #[cfg(not(feature = "canonicalize-nans"))] x if x.is_nan() => x, // preserve NaN - #[cfg(feature = "canonicalize_nans")] + #[cfg(feature = "canonicalize-nans")] x if x.is_nan() => Self::NAN, // Do not preserve NaN x if x.is_infinite() || x == 0.0 => x, // preserve infinities and zeros x if (0.0..=0.5).contains(&x) => 0.0, @@ -95,9 +95,9 @@ macro_rules! impl_wasm_float_ops { Some(core::cmp::Ordering::Less) => self, Some(core::cmp::Ordering::Greater) => other, Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { self } else { other }, - #[cfg(not(feature = "canonicalize_nans"))] + #[cfg(not(feature = "canonicalize-nans"))] None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. - #[cfg(feature = "canonicalize_nans")] + #[cfg(feature = "canonicalize-nans")] None => Self::NAN, // Do not preserve NaN } } @@ -110,9 +110,9 @@ macro_rules! impl_wasm_float_ops { Some(core::cmp::Ordering::Greater) => self, Some(core::cmp::Ordering::Less) => other, Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { other } else { self }, - #[cfg(not(feature = "canonicalize_nans"))] + #[cfg(not(feature = "canonicalize-nans"))] None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. - #[cfg(feature = "canonicalize_nans")] + #[cfg(feature = "canonicalize-nans")] None => Self::NAN, // Do not preserve NaN } } diff --git a/crates/tinywasm/src/interpreter/simd/utils.rs b/crates/tinywasm/src/interpreter/simd/utils.rs index 6feabe1..e02bac3 100644 --- a/crates/tinywasm/src/interpreter/simd/utils.rs +++ b/crates/tinywasm/src/interpreter/simd/utils.rs @@ -28,24 +28,24 @@ impl Value128 { } pub(super) const fn canonicalize_simd_f32_nan(x: f32) -> f32 { - #[cfg(feature = "canonicalize_nans")] + #[cfg(feature = "canonicalize-nans")] if x.is_nan() { f32::NAN } else { x } - #[cfg(not(feature = "canonicalize_nans"))] + #[cfg(not(feature = "canonicalize-nans"))] x } pub(super) const fn canonicalize_simd_f64_nan(x: f64) -> f64 { - #[cfg(feature = "canonicalize_nans")] + #[cfg(feature = "canonicalize-nans")] if x.is_nan() { f64::NAN } else { x } - #[cfg(not(feature = "canonicalize_nans"))] + #[cfg(not(feature = "canonicalize-nans"))] x } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index b4dad9e..4b6b360 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -7,26 +7,33 @@ #![cfg_attr(not(feature = "simd-x86"), forbid(unsafe_code))] #![cfg_attr(feature = "simd-x86", deny(unsafe_code))] -//! A tiny WebAssembly Runtime written in Rust -//! -//! `TinyWasm` provides a minimal WebAssembly runtime for executing WebAssembly modules. -//! It currently supports all features of the WebAssembly MVP specification and is -//! designed to be easy to use and integrate in other projects. +//! `tinywasm` provides a small, portable WebAssembly interpreter with support for +//! the WebAssembly MVP, WebAssembly 2.0, and a growing set of newer proposals. +//! It is designed to stay lightweight while still being practical to embed in +//! applications, tools, and `no_std + alloc` environments. //! //! ## Features -//!- **`std`**\ -//! Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. -//!- **`log`**\ -//! 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. -//!- **`guest_debug`**\ -//! Enables module-internal by-index inspection APIs (`*_by_index`). +//! - **`std`**\ +//! Enables parsing from files and streams. Enabled by default. +//! - **`log`**\ +//! Enables integration with the `log` crate. Enabled by default. +//! - **`parser`**\ +//! Enables the bundled `tinywasm-parser` crate and top-level parse helpers. Enabled by default. +//! - **`archive`**\ +//! Enables serialization and deserialization of compiled modules in the internal `twasm` format. Enabled by default. +//! - **`canonicalize-nans`**\ +//! Canonicalizes NaN values to a single representation. Enabled by default. +//! - **`debug`**\ +//! Derives `Debug` for runtime types. Enabled by default. +//! - **`parallel-parser`**\ +//! Parallelizes function parsing and validation across threads when `std` is enabled. Enabled by default. +//! - **`guest-debug`**\ +//! Exposes module-internal by-index inspection APIs (`*_by_index`). +//! - **`simd-x86`**\ +//! Enables x86-specific SIMD intrinsics for selected operations and uses `unsafe` internally. //! -//! With all these features disabled, `TinyWasm` only depends on `core`, `alloc` and `libm`. -//! By disabling `std`, you can use `TinyWasm` in `no_std` environments. This requires +//! With default features disabled, `tinywasm` only depends on `core`, `alloc`, and `libm`. +//! By disabling `std`, you can use `tinywasm` in `no_std` environments. This requires //! a custom allocator and removes support for parsing from files and streams, but otherwise the API is the same. #![cfg_attr(docsrs, feature(doc_cfg))] @@ -64,6 +71,10 @@ //! # Ok::<(), tinywasm::Error>(()) //! ``` //! +//! For non-default runtime behavior, construct a [`Store`] with a custom [`Engine`] +//! and [`engine::Config`] to control stack sizing, fuel accounting, memory backends, +//! and trap-on-OOM behavior. +//! //! For more examples, see the [`examples`](https://github.com/explodingcamera/tinywasm/tree/main/examples) directory. //! //! ## Imports diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs index eb0a7c2..9e96d4c 100644 --- a/crates/tinywasm/tests/internal_refs.rs +++ b/crates/tinywasm/tests/internal_refs.rs @@ -3,7 +3,7 @@ use tinywasm::types::{FuncRef, WasmValue}; use tinywasm::{ExternItem, ModuleInstance, Store}; #[test] -#[cfg(feature = "guest_debug")] +#[cfg(feature = "guest-debug")] fn private_items_are_accessible_by_index() -> Result<()> { let wasm = wat::parse_str( r#" diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 77e0be7..9f2dc8c 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -1,7 +1,7 @@ [package] name="tinywasm-types" version.workspace=true -description="TinyWasm types" +description="Shared runtime, module, and archive types for TinyWasm" edition.workspace=true license.workspace=true authors.workspace=true @@ -9,6 +9,7 @@ repository.workspace=true rust-version.workspace=true keywords.workspace=true categories.workspace=true +readme="README.md" [dependencies] log={workspace=true, optional=true} diff --git a/crates/types/README.md b/crates/types/README.md index 5a4431e..315a340 100644 --- a/crates/types/README.md +++ b/crates/types/README.md @@ -1,3 +1,5 @@ # `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 shared module, instruction, value, and archive types used by [`tinywasm`](https://crates.io/crates/tinywasm) and [`tinywasm-parser`](https://crates.io/crates/tinywasm-parser). + +Most users should depend on `tinywasm` directly. This crate is useful when you need to work with parsed modules, serialized `twasm` archives, or shared type definitions without pulling in the runtime. diff --git a/examples/rust/README.md b/examples/rust/README.md index 6742ca4..8d3178a 100644 --- a/examples/rust/README.md +++ b/examples/rust/README.md @@ -4,3 +4,6 @@ This is a separate crate that generates WebAssembly from Rust code. It is used by the `wasm-rust` example. Requires the `wasm32-unknown-unknown` target to be installed. + +To build the example artifacts used by `cargo run --example wasm-rust -- `, run `./examples/rust/build.sh`. +That script also requires `binaryen` and `wabt` to be installed. -- cgit v1.3.1