diff --git a/.cargo/config.toml b/.cargo/config.toml index b27192b5..01909990 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,7 @@ test-wasm-1="test --package tinywasm --test test-wasm-1 --release" test-wasm-2="test --package tinywasm --test test-wasm-2 --release" test-wasm-3="test --package tinywasm --test test-wasm-3 --release" test-wast="test --package tinywasm --test test-wast" +test-wasm-custom="test --package tinywasm --test test-wasm-custom --release" + +[target.x86_64-unknown-linux-gnu] +rustflags=["-C", "target-cpu=x86-64-v3"] diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0a75fb2b..0e0c3c59 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,21 +13,31 @@ jobs: name: Build wasm runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4 with: - toolchain: stable + toolchain: nightly + components: rust-src rustflags: "" target: wasm32-unknown-unknown - - name: Install Binaryen and WABT - run: sudo apt-get install -y binaryen wabt + - name: Install Binaryen + run: | + BINARYEN_VERSION=version_129 + BINARYEN_DIR="binaryen-${BINARYEN_VERSION}" + BINARYEN_ARCHIVE="${BINARYEN_DIR}-x86_64-linux.tar.gz" + curl -L "https://github.com/WebAssembly/binaryen/releases/download/${BINARYEN_VERSION}/${BINARYEN_ARCHIVE}" -o "/tmp/${BINARYEN_ARCHIVE}" + echo "50b9fa62b9abea752da92ec57e0c555fee578760cd237c40107957715d2976ba /tmp/${BINARYEN_ARCHIVE}" | sha256sum -c - + tar -xzf "/tmp/${BINARYEN_ARCHIVE}" -C /tmp + sudo install "/tmp/${BINARYEN_DIR}/bin/wasm-opt" /usr/local/bin/wasm-opt + - name: Install WABT + run: sudo apt-get install -y wabt - name: Build wasm run: ./examples/rust/build.sh - name: Save artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: wasm path: examples/rust/out @@ -67,12 +77,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4 with: toolchain: ${{ matrix.rust }} rustflags: "" @@ -80,7 +90,7 @@ jobs: if: matrix.target == '' - name: Load wasm - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: wasm path: examples/rust/out @@ -94,7 +104,7 @@ jobs: if: matrix.target == '' - name: Run tests (${{ matrix.target }}) - uses: houseabsolute/actions-rust-cross@v0.0.13 + uses: houseabsolute/actions-rust-cross@a8cc74d61047fa553b4e908b4b10e70029f00ca6 # v1.0.6 with: command: test target: ${{ matrix.target }} diff --git a/.gitignore b/.gitignore index 14456f4b..807cb82f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ flamegraph.svg /.idea /*.iml profile.json +profile.json.gz diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 0fa174a9..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "crates/wasm-testsuite/data"] - path = crates/wasm-testsuite/data - url = https://github.com/WebAssembly/testsuite.git diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 48a2e0d4..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "search.exclude": { - "**/wasm-testsuite/data": true - }, - "rust-analyzer.linkedProjects": [ - "./Cargo.toml", - ], -} \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7aaa99c2..28900892 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,27 +1,23 @@ # 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: -- **Type Storage**: Types are inferred from usage context rather than stored explicitly, with all values held as `u64`. -- **Stack Design**: Implements a specific stack for values, labels, and frames to simplify the implementation and enable optimizations. -- **Bytecode Format**: Adopts a custom bytecode format to reduce memory usage and improve performance by allowing direct execution without the need for decoding. -- **Global State Access**: Allows cross-module access to the `Store`'s global state, optimizing imports and exports access. Access requires a module instance reference, maintaining implicit ownership through a reference count. -- **Non-thread-safe Store**: Designed for efficiency in single-threaded applications. -- **JIT Compilation Support**: Prepares for JIT compiler integration with function instances designed to accommodate `WasmFunction`, `HostFunction`, or future `JitFunction`. -- **`no_std` Environment Support**: Offers compatibility with `no_std` environments by allowing disabling of `std` feature -- **Call Frame Execution**: Executes call frames in a single loop rather than recursively, using a single stack for all frames, facilitating easier pause, resume, and step-through. +Key runtime layout: -## Bytecode Format +- Values are stored in four fixed-capacity typed stacks: `stack_32` (`i32`/`f32`), `stack_64` (`i64`/`f64`), `stack_128` (`v128`), and `stack_ref` (`funcref`/`externref`). +- 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. -To improve performance and reduce code size, instructions are encoded as enum variants instead of opcodes. -This allows preprocessing the bytecode into a more memory aligned 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. +## Precompiled Modules -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`. Others are also combined, especially in cases where the stack can be skipped. +`TinyWasmModule` 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. -See [instructions.rs](./crates/types/src/instructions.rs) for the full list of instructions. +See: -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). +- [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) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c47dead..b9b5fcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,19 +12,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for the custom memory 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 -- Groundwork for the `simd` proposal +- Support for the fixed-width `simd` proposal +- Support for the `relaxed_simd` proposal +- Support for the `wide_arithmetic` 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`) +- 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 +### 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 are configured via `engine::Config` + ### 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 -- Increased MSRV to 1.83.0 -- `tinywasm::Error` is now `non_exhaustive`, `Error::ParseError` has been rename to `Error::Parser` and `Error::Twasm` has been added. +- `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 ### 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)) ## [0.8.0] - 2024-08-29 @@ -41,7 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extern tables now correctly update their type after growing - Increased MSRV to 1.80.0 - Simplify and optimize the interpreter loop -- Use a seperate stack and locals for 32, 64 and 128 bit values and references (#21) +- Use a separate stack and locals for 32, 64 and 128 bit values and references (#21) - Updated to latest `wasmparser` version - Removed benchmarks comparing TinyWasm to other WebAssembly runtimes to reduce build dependencies - Memory and Data Instances are no longer reference counted diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2302bef8..9c713bf8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,12 @@ # Contributing -Thank you for considering contributing to this project! This document outlines the process for contributing to this project. For small changes or bug fixes, feel free to open a pull request directly. For larger changes, please open an issue first to discuss the proposed changes. Also, please ensure that you open up your pull request against the `next` branch and [allow maintainers of the project to edit your code](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). +Thank you for considering a contribution. For small fixes, feel free to open a pull request directly. For larger changes, please open an issue first so we can discuss the approach. Please target the `next` branch and [allow maintainers to edit your PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). -## 1. Clone the Repository +## Code of Conduct -Ensure you clone this repository with the `--recursive` flag to include the submodules: +This project follows the [Contributor Covenant 3.0 Code of Conduct](https://www.contributor-covenant.org/version/3/0/code_of_conduct/). -```bash -git clone --recursive https://github.com/explodingcamera/tinywasm.git -``` - -If you have already cloned the repository, you can initialize the submodules with: - -```bash -git submodule update --init --recursive -``` - -This is required to run the WebAssembly test suite. - -## 2. Set up the Development Environment +## Development This project mostly uses a pretty standard Rust setup. Some common tasks: @@ -32,14 +20,17 @@ $ cargo test # Run only the WebAssembly MVP (1.0) test suite $ cargo test-wasm-1 -# Run only the full WebAssembly test suite (2.0) +# Run only the full WebAssembly 2.0 test suite $ cargo test-wasm-2 -# Run a specific test (run without arguments to see available tests) -$ cargo test --test {test_name} +# Run only the full WebAssembly 3.0 test suite +$ cargo test-wasm-3 # Run a single WAST test file -$ cargo test-wast {path} +$ cargo test-wast ./wasm-testsuite/data/wasm-v1/{file}.wast + +# Run custom wasm tests from crates/tinywasm/tests/wasm-custom +$ cargo test-wasm-custom # Run a specific example (run without arguments to see available examples) # The wasm test files required to run the `wasm-rust` examples are not @@ -51,20 +42,20 @@ $ cargo run --example {example_name} ### Profiling -Either [samply](https://github.com/mstange/samply/) or [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph) are recommended for profiling. +Use [samply](https://github.com/mstange/samply/) for profiling. Example usage: ```bash cargo install --locked samply -cargo samply --example wasm-rust -- selfhosted +samply record -- cargo run --release --example wasm-rust -- selfhosted ``` -# Commits +## Commits This project uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. For pull requests, the commit messages will be squashed so you don't need to worry about this too much. However, it is still recommended to follow this convention for consistency. -# Branches +## Branches - `main`: The main branch. This branch is used for the latest stable release. - `next`: The next branch. Development happens here. diff --git a/Cargo.lock b/Cargo.lock index 39e9f077..cac58c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,22 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -19,26 +28,25 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "argh" -version = "0.1.13" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" +checksum = "211818e820cda9ca6f167a64a5c808837366a6dfd807157c64c1304c486cd033" dependencies = [ "argh_derive", "argh_shared", - "rust-fuzzy-search", ] [[package]] name = "argh_derive" -version = "0.1.13" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" +checksum = "c442a9d18cef5dde467405d27d461d080d68972d6d0dfd0408265b6749ec427d" dependencies = [ "argh_shared", "proc-macro2", @@ -48,9 +56,9 @@ dependencies = [ [[package]] name = "argh_shared" -version = "0.1.13" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" +checksum = "e5ade012bac4db278517a0132c8c10c6427025868dca16c801087c28d5a411f1" dependencies = [ "serde", ] @@ -63,15 +71,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cast" @@ -79,11 +87,21 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" @@ -114,18 +132,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -133,9 +151,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cobs" @@ -148,10 +166,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -160,6 +179,7 @@ dependencies = [ "itertools", "num-traits", "oorandom", + "page_size", "rayon", "regex", "serde", @@ -170,9 +190,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools", @@ -256,21 +276,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hermit-abi" @@ -311,9 +338,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -321,13 +348,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -341,9 +368,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "leb128fmt" @@ -353,27 +380,27 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "num-traits" @@ -386,9 +413,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "oorandom" @@ -398,9 +425,19 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "page_size" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "postcard" @@ -426,18 +463,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -464,9 +501,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -476,9 +513,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -487,21 +524,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" - -[[package]] -name = "rust-fuzzy-search" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "same-file" @@ -514,9 +539,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -550,22 +575,28 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" -version = "2.0.106" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -583,18 +614,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -673,15 +704,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "walkdir" @@ -695,9 +726,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.239.0" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" dependencies = [ "leb128fmt", "wasmparser", @@ -705,9 +736,9 @@ dependencies = [ [[package]] name = "wasm-testsuite" -version = "0.5.12" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc4691cd6c251229ed1580c7547800f4ffb4ec9586d7ed5f2a6e1f130d65aa5" +checksum = "dc638d7adcaae61bdd173f053d018555843c5deca669b5caaeda9ae37402a602" dependencies = [ "include_dir", "wast", @@ -715,9 +746,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.239.0" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" dependencies = [ "bitflags", "indexmap", @@ -726,9 +757,9 @@ dependencies = [ [[package]] name = "wast" -version = "239.0.0" +version = "246.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9139176fe8a2590e0fb174cdcaf373b224cb93c3dde08e4297c1361d2ba1ea5d" +checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62" dependencies = [ "bumpalo", "leb128fmt", @@ -739,106 +770,81 @@ dependencies = [ [[package]] name = "wat" -version = "1.239.0" +version = "1.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1c941927d34709f255558166f8901a2005f8ab4a9650432e9281b7cc6f3b75" +checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c" dependencies = [ "wast", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys", ] [[package]] -name = "windows-link" -version = "0.2.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.61.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "zerocopy" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "zerocopy-derive", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "zerocopy-derive" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 77702726..ffd4d895 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,16 +4,16 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="239" -wat="1.239" -wasmparser={version="0.239", default-features=false} +wast="246" +wat="1.246" +wasmparser={version="0.246", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" -criterion={version="0.7", default-features=false, features=["cargo_bench_support", "rayon"]} -wasm-testsuite={version="0.5"} -indexmap="2.11" -owo-colors={version="4.2"} +criterion={version="0.8", default-features=false, features=["cargo_bench_support", "rayon"]} +wasm-testsuite={version="0.7"} +indexmap="2.14" +owo-colors={version="4.3"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} @@ -22,7 +22,7 @@ version="0.9.0-alpha.0" rust-version="1.90" edition="2024" license="MIT OR Apache-2.0" -authors=["Henry Gressmann "] +authors=["Henry Gressmann "] repository="https://github.com/explodingcamera/tinywasm" categories=["wasm", "no-std"] keywords=["tinywasm"] diff --git a/README.md b/README.md index ee1028fd..06d11a63 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,18 @@ -> [!NOTE] -> TinyWasm is currently on pause and not actively developed. _Eventually_ this may change but for now it is archived (as of October 2025). +# `tinywasm`  [![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust&style=flat-square)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust&style=flat-square)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg?style=flat-square)](./LICENSE-APACHE) -
-
- -
-

TinyWasm

- A tiny WebAssembly Runtime written in safe Rust -
+## Why `tinywasm`? -
- -[![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**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality (< 4000 LLOC). -- **Portable**: TinyWasm runs on any platform that Rust can target, including `no_std`, with minimal external dependencies. -- **Safe**: No unsafe code is used in the runtime +- **Tiny**: Keeps the runtime small and focused while still being practical for real workloads. +- **Portable**: Runs anywhere Rust runs, supports `no_std`, and keeps external dependencies to a minimum. +- **Secure**: Written entirely safe Rust (`#[deny(unsafe_code)]`) and designed to prevent untrusted code from crashing the runtime ## Current Status -TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, the current 2.0 WebAssembly is mostly supported, with the exception of the SIMD and Memory64 proposals. See the [Supported Proposals](#supported-proposals) section for more information. - -## Safety - -Safety wise, TinyWasm doesn't use any unsafe code and is designed to be completely memory-safe. Untrusted WebAssembly code should not be able to crash the runtime or access memory outside of its sandbox, however currently there is no protection against infinite loops or excessive memory usage. Unvalidated Wasm and untrusted, precompilled twasm bytecode is safe to run too but can crash the runtime. - -## Supported Proposals - -**Legend**\ -🌑 -- not available\ -🚧 -- in development/partially supported\ -🟢 -- fully supported - -| Proposal | Status | TinyWasm Version | -| --------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------- | -| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | 🟢 | 0.2.0 | -| [**Non-trapping float-to-int Conversion**](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) | 🟢 | 0.2.0 | -| [**Sign-extension operators**](https://github.com/WebAssembly/sign-extension-ops) | 🟢 | 0.2.0 | -| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | 🟢 | 0.2.0 | -| [**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 | -| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 | -| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` | -| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | -| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | -| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🚧 | N/A | +`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. ## Usage -See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information on how to use TinyWasm. +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: ```sh @@ -62,28 +24,60 @@ $ tinywasm-cli --help - **`std`**\ Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. -- **`logging`**\ +- **`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. -With all these features disabled, TinyWasm only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. +With all these features disabled, `tinywasm` only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. + +## Safety -## Inspiration +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. -Big thanks to the authors of the following projects, which have inspired and influenced TinyWasm: +## Supported Proposals -- [wasmi](https://github.com/wasmi-labs/wasmi) - an efficient and lightweight WebAssembly interpreter that also runs in `no_std` environments -- [wasm3](https://github.com/wasm3/wasm3) - a high-performance WebAssembly interpreter written in C +| Proposal | Status | `tinywasm` Version | +| --------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------ | +| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | 🟢 | 0.2.0 | +| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | 🟢 | 0.2.0 | +| [**Non-trapping float-to-int Conversion**](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) | 🟢 | 0.2.0 | +| [**Sign-extension operators**](https://github.com/WebAssembly/sign-extension-ops) | 🟢 | 0.2.0 | +| [**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` | +| [**Custom Descriptors**](https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md) | 🌑 | - | +| [**Exception Handling**](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) | 🌑 | - | +| [**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) | 🌑 | - | +| [**Threads**](https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md) | 🌑 | - | + +**Legend**\ +🌑 -- not available\ +🚧 -- in development/partially supported\ +🟢 -- fully supported + +## See Also + +I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes: + +- [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 -I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes. - ## License Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option. -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in TinyWasm by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `tinywasm` by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index d00848e3..235498c6 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -84,7 +84,7 @@ fn main() -> Result<()> { TinyWasmSubcommand::Run(Run { wasm_file, engine, args, func }) => { debug!("args: {args:?}"); - let path = cwd.join(wasm_file.clone()); + let path = cwd.join(&wasm_file); let module = match wasm_file.ends_with(".wat") { #[cfg(feature = "wat")] true => { diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 3cbeee10..598a5b7e 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -16,6 +16,6 @@ log={workspace=true, optional=true} tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} [features] -default=["std", "logging"] -logging=["log"] +default=["std", "log"] +log=["dep:log"] std=["tinywasm-types/std", "wasmparser/std"] diff --git a/crates/parser/README.md b/crates/parser/README.md index 563cd6bd..104cd506 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -6,7 +6,7 @@ It uses [my fork](https://crates.io/crates/tinywasm-wasmparser) of the [`wasmpar ## Features - `std`: Enables the use of `std` and `std::io` for parsing from files and streams. -- `logging`: Enables logging of the parsing process using the `log` crate. +- `log`: Enables logging of the parsing process using the `log` crate. ## Usage diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index a4e3b353..570d0a18 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -70,32 +70,33 @@ pub(crate) fn convert_module_imports<'a, T: IntoIterator) -> Result { - Ok(Import { - module: import.module.to_string().into_boxed_str(), - name: import.name.to_string().into_boxed_str(), - kind: match import.ty { - wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), - wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { - element_type: convert_reftype(ty.element_type), - size_initial: ty.initial.try_into().map_err(|_| { - crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial)) - })?, - size_max: match ty.maximum { - Some(max) => Some(max.try_into().map_err(|_| { - crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")) - })?), - None => None, - }, - }), - wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), - wasmparser::TypeRef::Global(ty) => { - ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) - } - wasmparser::TypeRef::Tag(ty) => { - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); - } - }, - }) + let kind = match import.ty { + wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), + wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { + element_type: convert_reftype(ty.element_type), + size_initial: ty.initial.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial)) + })?, + size_max: match ty.maximum { + Some(max) => Some(max.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")) + })?), + None => None, + }, + }), + wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), + wasmparser::TypeRef::Global(ty) => { + ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) + } + wasmparser::TypeRef::Tag(ty) => { + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); + } + _ => { + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {:?}", import.ty))); + } + }; + + Ok(Import { module: import.module.into(), name: import.name.into(), kind }) } pub(crate) fn convert_module_memories>>( @@ -124,21 +125,16 @@ pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result Some( - max.try_into() - .map_err(|_| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")))?, - ), - None => None, - }; - + let size_max = table.ty.maximum.map(|max| max.try_into()).transpose(); + let size_max = + size_max.map_err(|e| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {e}")))?; Ok(TableType { element_type: convert_reftype(table.ty.element_type), size_initial, size_max }) } pub(crate) fn convert_module_globals( globals: wasmparser::SectionLimited<'_, wasmparser::Global<'_>>, ) -> Result> { - let globals = globals + globals .into_iter() .map(|global| { let global = global?; @@ -146,8 +142,7 @@ pub(crate) fn convert_module_globals( let ops = global.init_expr.get_operators_reader(); Ok(Global { init: process_const_operators(ops)?, ty: GlobalType { mutable: global.ty.mutable, ty } }) }) - .collect::>>()?; - Ok(globals) + .collect::>>() } pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result { @@ -156,7 +151,7 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result ExternalKind::Table, wasmparser::ExternalKind::Memory => ExternalKind::Memory, wasmparser::ExternalKind::Global => ExternalKind::Global, - wasmparser::ExternalKind::Tag => { + wasmparser::ExternalKind::Tag | wasmparser::ExternalKind::FuncExact => { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported export kind: {:?}", export.kind))); } }; @@ -209,7 +204,6 @@ pub(crate) fn convert_module_code( pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { let mut types = ty.types(); - if types.len() != 1 { return Err(crate::ParseError::UnsupportedOperator( "Expected exactly one type in the type section".to_string(), @@ -219,7 +213,6 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result let ty = types.next().unwrap().unwrap_func(); let params = ty.params().iter().map(convert_valtype).collect::>().into_boxed_slice(); let results = ty.results().iter().map(convert_valtype).collect::>().into_boxed_slice(); - Ok(FuncType { params, results }) } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 3f45db87..a0c0c7e0 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -3,7 +3,7 @@ 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)] +#![warn(missing_docs, rust_2018_idioms, unreachable_pub)] #![forbid(unsafe_code)] //! See [`tinywasm`](https://docs.rs/tinywasm) for documentation. @@ -13,12 +13,12 @@ extern crate alloc; extern crate std; // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] #[allow(clippy::single_component_path_imports, unused_imports)] use log; // noop fallback if logging is disabled. -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] #[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); @@ -32,71 +32,79 @@ pub(crate) mod log { mod conversion; mod error; mod module; +mod optimize; mod visit; pub use error::*; use module::ModuleReader; -use wasmparser::{Validator, WasmFeaturesInflated}; +use wasmparser::{Validator, WasmFeatures}; pub use tinywasm_types::TinyWasmModule; +/// Parser optimization and lowering options. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct ParserOptions { + /// Enable post-lowering DCE pass. + /// Should be enabled by default, since the parser performs some optimizations that can result in dead code. + /// Disabling this may result in larger modules, but faster parsing time. + pub dce: bool, +} + +impl Default for ParserOptions { + fn default() -> Self { + Self { dce: true } + } +} + /// A WebAssembly parser -#[derive(Default, Debug)] -pub struct Parser {} +#[derive(Debug, Default)] +pub struct Parser { + options: ParserOptions, +} impl Parser { /// Create a new parser instance pub fn new() -> Self { - Self {} + Self::default() } - fn create_validator() -> Validator { - let features = WasmFeaturesInflated { - bulk_memory: true, - floats: true, - multi_value: true, - mutable_global: true, - reference_types: true, - sign_extension: true, - saturating_float_to_int: true, - function_references: true, - tail_call: true, - multi_memory: true, - simd: true, - memory64: true, - custom_page_sizes: true, - bulk_memory_opt: true, - call_indirect_overlong: true, - - cm_threading: false, - extended_const: false, - wide_arithmetic: false, - gc_types: true, - stack_switching: false, - component_model: false, - exceptions: false, - gc: false, - memory_control: false, - relaxed_simd: false, - threads: false, - shared_everything_threads: false, - legacy_exceptions: false, - cm_async: false, - cm_async_builtins: false, - cm_async_stackful: false, - cm_nested_names: false, - cm_values: false, - cm_error_context: false, - cm_fixed_size_list: false, - cm_gc: false, - }; - Validator::new_with_features(features.into()) + /// Create a new parser with explicit options. + pub fn with_options(options: ParserOptions) -> Self { + Self { options } + } + + /// Read back parser options. + pub const fn options(&self) -> &ParserOptions { + &self.options + } + + fn create_validator(_options: ParserOptions) -> Validator { + let features = WasmFeatures::CALL_INDIRECT_OVERLONG + | WasmFeatures::BULK_MEMORY_OPT + | WasmFeatures::RELAXED_SIMD + | WasmFeatures::GC_TYPES + | WasmFeatures::REFERENCE_TYPES + | WasmFeatures::MUTABLE_GLOBAL + | WasmFeatures::MULTI_VALUE + | WasmFeatures::FLOATS + | WasmFeatures::BULK_MEMORY + | WasmFeatures::SATURATING_FLOAT_TO_INT + | WasmFeatures::SIGN_EXTENSION + | WasmFeatures::FUNCTION_REFERENCES + | WasmFeatures::TAIL_CALL + | WasmFeatures::MULTI_MEMORY + | WasmFeatures::SIMD + | WasmFeatures::MEMORY64 + | WasmFeatures::CUSTOM_PAGE_SIZES + | WasmFeatures::WIDE_ARITHMETIC; + Validator::new_with_features(features) } /// Parse a [`TinyWasmModule`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); - let mut validator = Self::create_validator(); - let mut reader = ModuleReader::new(); + let mut validator = Self::create_validator(self.options.clone()); + let mut reader = ModuleReader::default(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { reader.process_payload(payload?, &mut validator)?; @@ -106,27 +114,22 @@ impl Parser { return Err(ParseError::EndNotReached); } - reader.into_module() + reader.into_module(&self.options) } #[cfg(feature = "std")] /// Parse a [`TinyWasmModule`] from a file. Requires `std` feature. pub fn parse_module_file(&self, path: impl AsRef + Clone) -> Result { - use alloc::format; - let f = crate::std::fs::File::open(path.clone()) - .map_err(|e| ParseError::Other(format!("Error opening file {:?}: {}", path.as_ref(), e)))?; - - let mut reader = crate::std::io::BufReader::new(f); - self.parse_module_stream(&mut reader) + let file = crate::std::fs::File::open(&path) + .map_err(|e| ParseError::Other(alloc::format!("Error opening file {:?}: {}", path.as_ref(), e)))?; + self.parse_module_stream(&mut crate::std::io::BufReader::new(file)) } #[cfg(feature = "std")] /// Parse a [`TinyWasmModule`] from a stream. Requires `std` feature. pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { - use alloc::format; - - let mut validator = Self::create_validator(); - let mut reader = ModuleReader::new(); + let mut validator = Self::create_validator(self.options.clone()); + let mut reader = ModuleReader::default(); let mut buffer = alloc::vec::Vec::new(); let mut parser = wasmparser::Parser::new(0); let mut eof = false; @@ -138,7 +141,7 @@ impl Parser { buffer.extend((0..hint).map(|_| 0u8)); let read_bytes = stream .read(&mut buffer[len..]) - .map_err(|e| ParseError::Other(format!("Error reading from stream: {e}")))?; + .map_err(|e| ParseError::Other(alloc::format!("Error reading from stream: {e}")))?; buffer.truncate(len + read_bytes); eof = read_bytes == 0; } @@ -146,7 +149,7 @@ impl Parser { reader.process_payload(payload, &mut validator)?; buffer.drain(..consumed); if eof || reader.end_reached { - return reader.into_module(); + return reader.into_module(&self.options); } } }; @@ -158,6 +161,6 @@ impl TryFrom for TinyWasmModule { type Error = ParseError; fn try_from(reader: ModuleReader) -> Result { - reader.into_module() + reader.into_module(&ParserOptions::default()) } } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 1651bf06..b53dfd67 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,14 +1,11 @@ use crate::log::debug; -use crate::{ParseError, Result, conversion}; +use crate::{ParseError, ParserOptions, Result, conversion, optimize}; use alloc::string::ToString; -use alloc::{boxed::Box, format, vec::Vec}; -use tinywasm_types::{ - Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, ValueCounts, - ValueCountsSmall, WasmFunction, WasmFunctionData, -}; +use alloc::{format, vec::Vec}; +use tinywasm_types::*; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Box<[Instruction]>, WasmFunctionData, ValueCounts); +pub(crate) type Code = (Vec, WasmFunctionData, ValueCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -30,23 +27,16 @@ pub(crate) struct ModuleReader { } impl ModuleReader { - pub(crate) fn new() -> Self { - Self::default() - } - pub(crate) fn process_payload(&mut self, payload: Payload<'_>, validator: &mut Validator) -> Result<()> { - use wasmparser::Payload::*; - match payload { - Version { num, encoding, range } => { + Payload::Version { num, encoding, range } => { validator.version(num, encoding, &range)?; self.version = Some(num); - match encoding { - wasmparser::Encoding::Module => {} - wasmparser::Encoding::Component => return Err(ParseError::InvalidEncoding(encoding)), + if let wasmparser::Encoding::Component = encoding { + return Err(ParseError::InvalidEncoding(encoding)); } } - StartSection { func, range } => { + Payload::StartSection { func, range } => { if self.start_func.is_some() { return Err(ParseError::DuplicateSection("Start section".into())); } @@ -55,7 +45,7 @@ impl ModuleReader { validator.start_section(func, &range)?; self.start_func = Some(func); } - TypeSection(reader) => { + Payload::TypeSection(reader) => { if !self.func_types.is_empty() { return Err(ParseError::DuplicateSection("Type section".into())); } @@ -68,7 +58,7 @@ impl ModuleReader { .collect::>>()?; } - GlobalSection(reader) => { + Payload::GlobalSection(reader) => { if !self.globals.is_empty() { return Err(ParseError::DuplicateSection("Global section".into())); } @@ -77,7 +67,7 @@ impl ModuleReader { validator.global_section(&reader)?; self.globals = conversion::convert_module_globals(reader)?; } - TableSection(reader) => { + Payload::TableSection(reader) => { if !self.table_types.is_empty() { return Err(ParseError::DuplicateSection("Table section".into())); } @@ -85,7 +75,7 @@ impl ModuleReader { validator.table_section(&reader)?; self.table_types = conversion::convert_module_tables(reader)?; } - MemorySection(reader) => { + Payload::MemorySection(reader) => { if !self.memory_types.is_empty() { return Err(ParseError::DuplicateSection("Memory section".into())); } @@ -94,12 +84,12 @@ impl ModuleReader { validator.memory_section(&reader)?; self.memory_types = conversion::convert_module_memories(reader)?; } - ElementSection(reader) => { + Payload::ElementSection(reader) => { debug!("Found element section"); validator.element_section(&reader)?; self.elements = conversion::convert_module_elements(reader)?; } - DataSection(reader) => { + Payload::DataSection(reader) => { if !self.data.is_empty() { return Err(ParseError::DuplicateSection("Data section".into())); } @@ -108,14 +98,14 @@ impl ModuleReader { validator.data_section(&reader)?; self.data = conversion::convert_module_data_sections(reader)?; } - DataCountSection { count, range } => { + Payload::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)?; } - FunctionSection(reader) => { + Payload::FunctionSection(reader) => { if !self.code_type_addrs.is_empty() { return Err(ParseError::DuplicateSection("Function section".into())); } @@ -124,7 +114,7 @@ impl ModuleReader { validator.function_section(&reader)?; self.code_type_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; } - CodeSectionStart { count, range, .. } => { + Payload::CodeSectionStart { count, range, .. } => { debug!("Found code section ({count} functions)"); if !self.code.is_empty() { return Err(ParseError::DuplicateSection("Code section".into())); @@ -132,7 +122,7 @@ impl ModuleReader { self.code.reserve(count as usize); validator.code_section_start(&range)?; } - CodeSectionEntry(function) => { + Payload::CodeSectionEntry(function) => { debug!("Found code section entry"); let v = validator.code_section_entry(&function)?; let func_validator = v.into_validator(self.func_validator_allocations.take().unwrap_or_default()); @@ -140,16 +130,16 @@ impl ModuleReader { self.code.push(code); self.func_validator_allocations = Some(allocations); } - ImportSection(reader) => { + Payload::ImportSection(reader) => { if !self.imports.is_empty() { return Err(ParseError::DuplicateSection("Import section".into())); } debug!("Found import section"); validator.import_section(&reader)?; - self.imports = conversion::convert_module_imports(reader)?; + self.imports = conversion::convert_module_imports(reader.into_imports())?; } - ExportSection(reader) => { + Payload::ExportSection(reader) => { if !self.exports.is_empty() { return Err(ParseError::DuplicateSection("Export section".into())); } @@ -159,7 +149,7 @@ impl ModuleReader { self.exports = reader.into_iter().map(|e| conversion::convert_module_export(e?)).collect::>>()?; } - End(offset) => { + Payload::End(offset) => { debug!("Reached end of module"); if self.end_reached { return Err(ParseError::DuplicateSection("End section".into())); @@ -168,18 +158,17 @@ impl ModuleReader { validator.end(offset)?; self.end_reached = true; } - CustomSection(_reader) => { + Payload::CustomSection(_reader) => { debug!("Found custom section"); debug!("Skipping custom section: {:?}", _reader.name()); } - UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), + Payload::UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {section:?}"))), }; - Ok(()) } - pub(crate) fn into_module(self) -> Result { + pub(crate) fn into_module(self, options: &ParserOptions) -> Result { if !self.end_reached { return Err(ParseError::EndNotReached); } @@ -188,32 +177,28 @@ impl ModuleReader { return Err(ParseError::Other("Code and code type address count mismatch".to_string())); } - let funcs = self - .code - .into_iter() - .zip(self.code_type_addrs) - .map(|((instructions, data, locals), ty_idx)| { + let imported_func_count = self.imports.iter().filter(|i| matches!(&i.kind, ImportKind::Function(_))).count(); + let funcs = self.code.into_iter().zip(self.code_type_addrs).enumerate().map( + |(func_idx, ((instructions, mut data, locals), ty_idx))| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); - let params = ValueCountsSmall::from(&ty.params); - WasmFunction { instructions, data, locals, params, ty } - }) - .collect::>() - .into_boxed_slice(); - - let globals = self.globals; - let table_types = self.table_types; + let params = ValueCounts::from_iter(&ty.params); + let self_func = (imported_func_count + func_idx) as u32; + let instructions = optimize::optimize_instructions(instructions, &mut data, self_func, options); + WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } + }, + ); Ok(TinyWasmModule { - funcs, - func_types: self.func_types.into_boxed_slice(), - globals: globals.into_boxed_slice(), - table_types: table_types.into_boxed_slice(), - imports: self.imports.into_boxed_slice(), + funcs: funcs.collect(), + func_types: self.func_types.into(), + globals: self.globals.into(), + table_types: self.table_types.into(), + imports: self.imports.into(), start_func: self.start_func, - data: self.data.into_boxed_slice(), - exports: self.exports.into_boxed_slice(), - elements: self.elements.into_boxed_slice(), - memory_types: self.memory_types.into_boxed_slice(), + data: self.data.into(), + exports: self.exports.into(), + elements: self.elements.into(), + memory_types: self.memory_types.into(), }) } } diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs new file mode 100644 index 00000000..1369d7a8 --- /dev/null +++ b/crates/parser/src/optimize.rs @@ -0,0 +1,384 @@ +use crate::ParserOptions; +use alloc::vec::Vec; +use tinywasm_types::{Instruction, WasmFunctionData}; + +pub(crate) fn optimize_instructions( + mut instructions: Vec, + function_data: &mut WasmFunctionData, + self_func_addr: u32, + options: &ParserOptions, +) -> Vec { + rewrite(&mut instructions, self_func_addr); + if options.dce { + dce(&mut instructions, function_data); + } + instructions +} + +fn rewrite(instructions: &mut [Instruction], self_func_addr: u32) { + for read in 0..instructions.len() { + match instructions[read] { + Instruction::LocalCopy32(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopy64(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopy128(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopyRef(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::Call(addr) if addr == self_func_addr => instructions[read] = Instruction::CallSelf, + Instruction::ReturnCall(addr) if addr == self_func_addr => instructions[read] = Instruction::ReturnCallSelf, + Instruction::I32Add => { + if read > 1 + && let (Instruction::LocalGet32(a), Instruction::LocalGet32(b)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddLocals(a, b); + } + + if read > 0 { + match instructions[read - 1] { + Instruction::I32Const(c) if read > 1 => { + if let Instruction::LocalGet32(local) = instructions[read - 2] { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::LocalGet32(local); + instructions[read] = Instruction::I32AddConst(c); + } else { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddConst(c); + } + } + Instruction::I32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddConst(c); + } + _ => {} + } + } + } + Instruction::I64Add => { + if read > 1 + && let (Instruction::LocalGet64(a), Instruction::LocalGet64(b)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddLocals(a, b); + } + + if read > 0 { + match instructions[read - 1] { + Instruction::I64Const(c) if read > 1 => { + if let Instruction::LocalGet64(local) = instructions[read - 2] { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::LocalGet64(local); + instructions[read] = Instruction::I64AddConst(c); + } else { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddConst(c); + } + } + Instruction::I64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddConst(c); + } + _ => {} + } + } + } + Instruction::I64Rotl => { + if read > 1 + && let (Instruction::I64Xor, Instruction::I64Const(c)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64XorRotlConst(c); + } + } + Instruction::I32Store(memarg) => { + if read > 1 + && let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32StoreLocalLocal(memarg, addr_local, value_local); + } + } + Instruction::I64Store(memarg) => { + if read > 1 + && let (Instruction::LocalGet32(addr_local), Instruction::LocalGet64(value_local)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64StoreLocalLocal(memarg, addr_local, value_local); + } + } + Instruction::MemoryFill(mem) => { + if read > 1 + && let (Instruction::I32Const(val), Instruction::I32Const(size)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::MemoryFillImm(mem, val as u8, size); + } + } + + Instruction::LocalGet32(dst) => { + if read > 0 + && let Instruction::LocalSet32(src) = instructions[read - 1] + && src == dst + { + instructions[read - 1] = Instruction::LocalTee32(src); + instructions[read] = Instruction::Nop; + } + } + Instruction::LocalGet64(dst) => { + if read > 0 + && let Instruction::LocalSet64(src) = instructions[read - 1] + && src == dst + { + instructions[read - 1] = Instruction::LocalTee64(src); + instructions[read] = Instruction::Nop; + } + } + Instruction::LocalGet128(dst) => { + if read > 0 + && let Instruction::LocalSet128(src) = instructions[read - 1] + && src == dst + { + instructions[read - 1] = Instruction::LocalTee128(src); + instructions[read] = Instruction::Nop; + } + } + Instruction::LocalGetRef(dst) => { + if read > 0 + && let Instruction::LocalSetRef(src) = instructions[read - 1] + && src == dst + { + instructions[read - 1] = Instruction::LocalTeeRef(src); + instructions[read] = Instruction::Nop; + } + } + + Instruction::LocalSet32(dst) => { + if read > 0 { + match instructions[read - 1] { + Instruction::LocalGet32(src) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy32(src, dst) }; + } + Instruction::I32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalSetConst32(dst, c); + } + Instruction::F32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + Instruction::LocalSetConst32(dst, i32::from_ne_bytes(c.to_bits().to_ne_bytes())); + } + _ => {} + } + } + + if read > 1 { + match (instructions[read - 2], instructions[read - 1]) { + (Instruction::LocalGet32(src), Instruction::I32AddConst(c)) if src == dst => { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalAddConst32(dst, c); + } + (Instruction::LocalGet32(addr), Instruction::I32Load(memarg)) => { + if let (Ok(addr), Ok(dst)) = (u8::try_from(addr), u8::try_from(dst)) { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32LoadLocalSet(memarg, addr, dst); + } + } + _ => {} + } + } + } + Instruction::LocalSet64(dst) => { + if read > 0 { + match instructions[read - 1] { + Instruction::LocalGet64(src) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy64(src, dst) }; + } + Instruction::I64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalSetConst64(dst, c); + } + Instruction::F64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + Instruction::LocalSetConst64(dst, i64::from_ne_bytes(c.to_bits().to_ne_bytes())); + } + _ => {} + } + } + + if read > 1 + && let (Instruction::LocalGet64(src), Instruction::I64AddConst(c)) = + (instructions[read - 2], instructions[read - 1]) + && src == dst + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalAddConst64(dst, c); + } + } + Instruction::LocalSet128(dst) => { + if read > 0 + && let Instruction::LocalGet128(src) = instructions[read - 1] + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy128(src, dst) }; + } + } + Instruction::LocalSetRef(dst) => { + if read > 0 + && let Instruction::LocalGetRef(src) = instructions[read - 1] + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopyRef(src, dst) }; + } + } + + Instruction::LocalTee32(dst) => { + if read > 0 + && let Instruction::LocalGet32(src) = instructions[read - 1] + && src == dst + { + instructions[read] = Instruction::Nop; + } + + if read > 1 + && let (Instruction::LocalGet32(addr), Instruction::I32Load(memarg)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr), Ok(dst)) = (u8::try_from(addr), u8::try_from(dst)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32LoadLocalTee(memarg, addr, dst); + } + } + Instruction::LocalTee64(dst) if read > 0 => match instructions[read - 1] { + Instruction::LocalGet64(src) if src == dst => { + instructions[read] = Instruction::Nop; + } + Instruction::I64XorRotlConst(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64XorRotlConstTee(c, dst); + } + _ => {} + }, + Instruction::LocalTee128(dst) => { + if read > 0 + && let Instruction::LocalGet128(src) = instructions[read - 1] + && src == dst + { + instructions[read] = Instruction::Nop; + } + } + Instruction::LocalTeeRef(dst) => { + if read > 0 + && let Instruction::LocalGetRef(src) = instructions[read - 1] + && src == dst + { + instructions[read] = Instruction::Nop; + } + } + + Instruction::Drop32 => { + if read > 0 + && let Instruction::LocalTee32(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSet32(local); + instructions[read] = Instruction::Nop; + } + } + Instruction::Drop64 => { + if read > 0 + && let Instruction::LocalTee64(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSet64(local); + instructions[read] = Instruction::Nop; + } + } + Instruction::Drop128 => { + if read > 0 + && let Instruction::LocalTee128(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSet128(local); + instructions[read] = Instruction::Nop; + } + } + Instruction::DropRef => { + if read > 0 + && let Instruction::LocalTeeRef(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSetRef(local); + instructions[read] = Instruction::Nop; + } + } + _ => {} + } + } +} + +fn dce(instructions: &mut Vec, function_data: &mut WasmFunctionData) { + let old_len = instructions.len(); + if old_len == 0 { + return; + } + + let mut removed_before = Vec::with_capacity(old_len + 1); + removed_before.push(0u32); + instructions.iter().for_each(|instr| { + let removed = removed_before.last().copied().unwrap_or(0) + u32::from(matches!(instr, Instruction::Nop)); + removed_before.push(removed); + }); + + let removed_total = removed_before[old_len]; + if removed_total == 0 { + return; + } + + let compacted_len = old_len as u32 - removed_total; + + function_data.branch_table_targets.iter_mut().for_each(|ip| { + let old_target = *ip as usize; + if old_target <= old_len { + *ip -= removed_before[old_target]; + debug_assert!(*ip < compacted_len, "remapped jump target points past end of function"); + } + }); + + instructions.retain_mut(|instr| { + let ip = match instr { + Instruction::Jump(ip) + | Instruction::JumpIfZero(ip) + | Instruction::JumpIfNonZero(ip) + | Instruction::BranchTable(ip, _, _) => ip, + _ => return !matches!(instr, Instruction::Nop), + }; + + let old_target = *ip as usize; + if old_target > old_len { + return !matches!(instr, Instruction::Nop); + } + + *ip -= removed_before[old_target]; + debug_assert!(*ip < compacted_len, "remapped jump target points past end of function"); + !matches!(instr, Instruction::Nop) + }); +} diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 4ae2cc17..8313d9f1 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -1,13 +1,52 @@ use crate::Result; -use crate::conversion::{convert_heaptype, convert_valtype}; +use crate::conversion::convert_heaptype; use alloc::string::ToString; -use alloc::{boxed::Box, vec::Vec}; +use alloc::vec; +use alloc::vec::Vec; use tinywasm_types::{Instruction, MemoryArg, WasmFunctionData}; use wasmparser::{ - FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, VisitSimdOperator, WasmModuleResources, + FrameKind, FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, VisitSimdOperator, + WasmModuleResources, }; +#[derive(Debug, Clone, Copy)] +enum BlockKind { + Block, + Loop, + If, +} + +#[derive(Debug, Clone, Copy, Default)] +struct StackBase { + s32: u16, + s64: u16, + s128: u16, + sref: u16, +} + +struct LoweringCtx { + kind: BlockKind, + has_else: bool, + start_ip: usize, + branch_jumps: Vec, +} + +#[derive(Default)] +struct FunctionDataBuilder { + v128_constants: Vec, + branch_table_targets: Vec, +} + +impl FunctionDataBuilder { + fn finish(self) -> WasmFunctionData { + WasmFunctionData { + v128_constants: self.v128_constants.into_boxed_slice(), + branch_table_targets: self.branch_table_targets.into_boxed_slice(), + } + } +} + struct ValidateThenVisit<'a, R: WasmModuleResources>(usize, &'a mut FunctionBuilder); macro_rules! validate_then_visit { @@ -36,8 +75,8 @@ impl VisitSimdOperator<'_> for ValidateThenVisit<'_, R> pub(crate) fn process_operators_and_validate( validator: FuncValidator, body: FunctionBody<'_>, - local_addr_map: Vec, -) -> Result<(Box<[Instruction]>, WasmFunctionData, FuncValidatorAllocations)> { + local_addr_map: Vec, +) -> Result<(Vec, WasmFunctionData, FuncValidatorAllocations)> { let mut reader = body.get_operators_reader()?; let remaining = reader.get_binary_reader().bytes_remaining(); let mut builder = FunctionBuilder::new(remaining, validator, local_addr_map); @@ -51,11 +90,7 @@ pub(crate) fn process_operators_and_validate( return Err(builder.errors.remove(0)); } - Ok(( - builder.instructions.into_boxed_slice(), - WasmFunctionData { v128_constants: builder.v128_constants.into_boxed_slice() }, - builder.validator.into_allocations(), - )) + Ok((builder.instructions, builder.data.finish(), builder.validator.into_allocations())) } macro_rules! define_operand { @@ -111,38 +146,12 @@ macro_rules! define_mem_operands_simd_lane { pub(crate) struct FunctionBuilder { validator: FuncValidator, instructions: Vec, - v128_constants: Vec, - label_ptrs: Vec, - local_addr_map: Vec, + data: FunctionDataBuilder, + ctx_stack: Vec, + local_addr_map: Vec, errors: Vec, } -impl FunctionBuilder { - pub(crate) fn validator_visitor( - &mut self, - offset: usize, - ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { - self.validator.simd_visitor(offset) - } -} - -impl FunctionBuilder { - pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { - Self { - validator, - local_addr_map, - instructions: Vec::with_capacity(instr_capacity), - v128_constants: Vec::new(), - label_ptrs: Vec::with_capacity(256), - errors: Vec::new(), - } - } - - fn unsupported(&mut self, name: &str) { - self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); - } -} - macro_rules! impl_visit_operator { ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit ($($ann:tt)*));)* @@ -154,11 +163,12 @@ macro_rules! impl_visit_operator { (@@saturating_float_to_int $($rest:tt)* ) => {}; (@@bulk_memory $($rest:tt)* ) => {}; (@@simd $($rest:tt)* ) => {}; + (@@wide_arithmetic $($rest:tt)* ) => {}; + (@@relaxed_simd $($rest:tt)* ) => {}; (@@tail_call $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { - #[cold] - fn $visit(&mut self $($(,$arg: $argty)*)?) { + fn $visit(&mut self $($(,_: $argty)*)?) { self.unsupported(stringify!($visit)) } }; @@ -173,12 +183,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } define_mem_operands! { - visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_i32_store( I32Store), visit_i64_store(I64Store), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32) + visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32), visit_i32_store(I32Store), visit_i64_store(I64Store) } define_operands! { // basic instructions - visit_br(Br, u32), visit_br_if(BrIf, u32), visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_return(Return), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_add(I32Add), visit_i64_add(I64Add), visit_i64_rotl(I64Rotl), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -190,34 +200,35 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild visit_ref_func(RefFunc, u32), visit_table_fill(TableFill, u32), visit_table_get(TableGet, u32), visit_table_set(TableSet, u32), visit_table_grow(TableGrow, u32), visit_table_size(TableSize, u32), // Bulk Memory - visit_memory_init(MemoryInit, u32, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32) + visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), + + // Wide Arithmetic + visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU) } fn visit_global_set(&mut self, global_index: u32) -> Self::Output { - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::GlobalSet32(global_index), wasmparser::ValType::F32 => Instruction::GlobalSet32(global_index), wasmparser::ValType::I64 => Instruction::GlobalSet64(global_index), wasmparser::ValType::F64 => Instruction::GlobalSet64(global_index), wasmparser::ValType::V128 => Instruction::GlobalSet128(global_index), wasmparser::ValType::Ref(_) => Instruction::GlobalSetRef(global_index), - }), - _ => self.visit_unreachable(), + }) } } fn visit_drop(&mut self) -> Self::Output { - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::Drop32, wasmparser::ValType::F32 => Instruction::Drop32, wasmparser::ValType::I64 => Instruction::Drop64, wasmparser::ValType::F64 => Instruction::Drop64, wasmparser::ValType::V128 => Instruction::Drop128, wasmparser::ValType::Ref(_) => Instruction::DropRef, - }), - _ => self.visit_unreachable(), + }) } } @@ -225,205 +236,197 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild match self.validator.get_operand_type(1) { Some(Some(t)) => self.visit_typed_select(t), _ => self.visit_unreachable(), - } - } - - fn visit_local_get(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; }; - - match self.validator.get_local_type(idx) { - Some(t) => self.instructions.push(match t { - wasmparser::ValType::I32 => Instruction::LocalGet32(resolved_idx), - wasmparser::ValType::F32 => Instruction::LocalGet32(resolved_idx), - wasmparser::ValType::I64 => Instruction::LocalGet64(resolved_idx), - wasmparser::ValType::F64 => Instruction::LocalGet64(resolved_idx), - wasmparser::ValType::V128 => Instruction::LocalGet128(resolved_idx), - wasmparser::ValType::Ref(_) => Instruction::LocalGetRef(resolved_idx), - }), - _ => self.visit_unreachable(), - } } - fn visit_local_set(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; + fn visit_return(&mut self) -> Self::Output { + self.instructions.push(Instruction::Return); + } - if let Some( - Instruction::LocalGet32(from) - | Instruction::LocalGet64(from) - | Instruction::LocalGet128(from) - | Instruction::LocalGetRef(from), - ) = self.instructions.last() - { - let from = *from; - self.instructions.pop(); - // validation will ensure that the last instruction is the correct local.get - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { - wasmparser::ValType::I32 => Instruction::LocalCopy32(from, resolved_idx), - wasmparser::ValType::F32 => Instruction::LocalCopy32(from, resolved_idx), - wasmparser::ValType::I64 => Instruction::LocalCopy64(from, resolved_idx), - wasmparser::ValType::F64 => Instruction::LocalCopy64(from, resolved_idx), - wasmparser::ValType::V128 => Instruction::LocalCopy128(from, resolved_idx), - wasmparser::ValType::Ref(_) => Instruction::LocalCopyRef(from, resolved_idx), - }), - _ => self.visit_unreachable(), + fn visit_local_get(&mut self, idx: u32) -> Self::Output { + let resolved_idx = self.local_addr_map[idx as usize]; + if let Some(t) = self.validator.get_local_type(idx) { + match t { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => { + self.instructions.push(Instruction::LocalGet32(resolved_idx)); + } + wasmparser::ValType::I64 | wasmparser::ValType::F64 => { + self.instructions.push(Instruction::LocalGet64(resolved_idx)); + } + wasmparser::ValType::V128 => { + self.instructions.push(Instruction::LocalGet128(resolved_idx)); + } + wasmparser::ValType::Ref(_) => { + self.instructions.push(Instruction::LocalGetRef(resolved_idx)); + } } - return; } + } - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + fn visit_local_set(&mut self, idx: u32) -> Self::Output { + let resolved_idx = self.local_addr_map[idx as usize]; + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalSet32(resolved_idx), wasmparser::ValType::F32 => Instruction::LocalSet32(resolved_idx), wasmparser::ValType::I64 => Instruction::LocalSet64(resolved_idx), wasmparser::ValType::F64 => Instruction::LocalSet64(resolved_idx), wasmparser::ValType::V128 => Instruction::LocalSet128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalSetRef(resolved_idx), - }), - _ => self.visit_unreachable(), + }) } } fn visit_local_tee(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; - - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + let resolved_idx = self.local_addr_map[idx as usize]; + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), wasmparser::ValType::F32 => Instruction::LocalTee32(resolved_idx), wasmparser::ValType::I64 => Instruction::LocalTee64(resolved_idx), wasmparser::ValType::F64 => Instruction::LocalTee64(resolved_idx), wasmparser::ValType::V128 => Instruction::LocalTee128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalTeeRef(resolved_idx), - }), - _ => self.visit_unreachable(), + }) } } - fn visit_block(&mut self, blockty: wasmparser::BlockType) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(match blockty { - wasmparser::BlockType::Empty => Instruction::Block(0), - wasmparser::BlockType::FuncType(idx) => Instruction::BlockWithFuncType(idx, 0), - wasmparser::BlockType::Type(ty) => Instruction::BlockWithType(convert_valtype(&ty), 0), + fn visit_block(&mut self, _blockty: wasmparser::BlockType) -> Self::Output { + let start_ip = self.instructions.len(); + self.ctx_stack.push(LoweringCtx { + kind: BlockKind::Block, + has_else: false, + start_ip, + branch_jumps: Vec::new(), }); } - fn visit_loop(&mut self, ty: wasmparser::BlockType) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(match ty { - wasmparser::BlockType::Empty => Instruction::Loop(0), - wasmparser::BlockType::FuncType(idx) => Instruction::LoopWithFuncType(idx, 0), - wasmparser::BlockType::Type(ty) => Instruction::LoopWithType(convert_valtype(&ty), 0), - }); - } - - fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(match ty { - wasmparser::BlockType::Empty => Instruction::If(0, 0), - wasmparser::BlockType::FuncType(idx) => Instruction::IfWithFuncType(idx, 0, 0), - wasmparser::BlockType::Type(ty) => Instruction::IfWithType(convert_valtype(&ty), 0, 0), + fn visit_loop(&mut self, _ty: wasmparser::BlockType) -> Self::Output { + if !matches!(self.instructions.last(), Some(Instruction::Nop)) { + self.instructions.push(Instruction::Nop); // add nop to ensure that no superinstruction can be merged across block boundaries + } + let start_ip = self.instructions.len(); + self.ctx_stack.push(LoweringCtx { kind: BlockKind::Loop, has_else: false, start_ip, branch_jumps: Vec::new() }); + } + + fn visit_if(&mut self, _ty: wasmparser::BlockType) -> Self::Output { + let cond_jump_ip = self.instructions.len(); + self.instructions.push(Instruction::JumpIfZero(0)); + let start_ip = self.instructions.len(); + self.ctx_stack.push(LoweringCtx { + kind: BlockKind::If, + has_else: false, + start_ip, + branch_jumps: alloc::vec![cond_jump_ip], }); } fn visit_else(&mut self) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(Instruction::Else(0)); + let last_if = self.ctx_stack.last().filter(|ctx| matches!(ctx.kind, BlockKind::If)); + if let Some(cond_jump_ip) = last_if.map(|ctx| ctx.branch_jumps[0]) { + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + if let Some(ctx) = self.ctx_stack.last_mut() { + ctx.has_else = true; + ctx.branch_jumps.push(jump_ip); + self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); + if !matches!(self.instructions.last(), Some(Instruction::Nop)) { + self.instructions.push(Instruction::Nop); // add nop to ensure that no superinstruction can be merged across block boundaries + } + }; + }; } fn visit_end(&mut self) -> Self::Output { - let Some(label_pointer) = self.label_ptrs.pop() else { - return self.instructions.push(Instruction::Return); - }; + if let Some(ctx) = self.ctx_stack.pop() { + self.patch_end_jumps(ctx, self.instructions.len()); + if !matches!(self.instructions.last(), Some(Instruction::Nop)) { + self.instructions.push(Instruction::Nop); // add nop to ensure that no superinstruction can be merged across block boundaries + } + } else { + self.instructions.push(Instruction::Return); + } + } - let current_instr_ptr = self.instructions.len(); - match self.instructions.get_mut(label_pointer) { - Some(Instruction::Else(else_instr_end_offset)) => { - *else_instr_end_offset = (current_instr_ptr - label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support if blocks that large"); + fn visit_br(&mut self, depth: u32) -> Self::Output { + self.emit_dropkeep_to_label(depth); + self.emit_branch_jump_or_return(depth); + } - // since we're ending an else block, we need to end the if block as well - let Some(if_label_pointer) = self.label_ptrs.pop() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but there was no if block to end".to_string(), - )); + fn visit_br_if(&mut self, depth: u32) -> Self::Output { + let cond_jump_ip = self.instructions.len(); + self.instructions.push(Instruction::JumpIfZero(0)); - return; - }; + let branch_side_start = self.instructions.len(); + self.emit_dropkeep_to_label(depth); - let if_instruction = &mut self.instructions[if_label_pointer]; + if self.instructions.len() == branch_side_start + && let Some(ctx_idx) = self.get_ctx_idx(depth) + { + self.instructions[cond_jump_ip] = Instruction::JumpIfNonZero(0); + self.ctx_stack[ctx_idx].branch_jumps.push(cond_jump_ip); + return; + } - let (Instruction::If(else_offset, end_offset) - | Instruction::IfWithFuncType(_, else_offset, end_offset) - | Instruction::IfWithType(_, else_offset, end_offset)) = if_instruction - else { - return self.errors.push(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - )); - }; + self.emit_branch_jump_or_return(depth); + self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); + } - *else_offset = (label_pointer - if_label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { + let ts = targets + .targets() + .collect::, wasmparser::BinaryReaderError>>() + .expect("visit_br_table: BrTable targets are invalid"); + + let default_depth = targets.default(); + let len = ts.len() as u32; + let target_depths: Vec = ts; + + let header_ip = self.instructions.len(); + let branch_table_start = self.data.branch_table_targets.len() as u32; + self.instructions.push(Instruction::BranchTable(0, branch_table_start, len)); + + let mut seen = alloc::collections::BTreeMap::::new(); + struct PadInfo { + depth: u32, + pad_start: usize, + jump_or_ret_ip: usize, + is_return: bool, + } + let mut pads: Vec = Vec::new(); - *end_offset = (current_instr_ptr - if_label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); - } - Some( - Instruction::Block(end_offset) - | Instruction::BlockWithType(_, end_offset) - | Instruction::BlockWithFuncType(_, end_offset) - | Instruction::Loop(end_offset) - | Instruction::LoopWithFuncType(_, end_offset) - | Instruction::LoopWithType(_, end_offset) - | Instruction::If(_, end_offset) - | Instruction::IfWithFuncType(_, _, end_offset) - | Instruction::IfWithType(_, _, end_offset), - ) => { - *end_offset = (current_instr_ptr - label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); - } - _ => { - unreachable!("Expected to end a block, but the last label was not a block") + for &depth in target_depths.iter().chain(core::iter::once(&default_depth)) { + if seen.contains_key(&depth) { + continue; } - }; + seen.insert(depth, pads.len()); - self.instructions.push(Instruction::EndBlockFrame); - } + let (pad_start, jump_or_ret_ip, is_return) = self.emit_br_table_pad(depth); + pads.push(PadInfo { depth, pad_start, jump_or_ret_ip, is_return }); + } - fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { - let def = targets.default(); - let instrs = targets - .targets() - .map(|t| t.map(Instruction::BrLabel)) - .collect::, wasmparser::BinaryReaderError>>() - .expect("visit_br_table: BrTable targets are invalid, this should have been caught by the validator"); + for &depth in &target_depths { + let pad_idx = seen[&depth]; + self.data.branch_table_targets.push(pads[pad_idx].pad_start as u32); + } + + let default_pad_idx = seen[&default_depth]; + if let Instruction::BranchTable(default_ip, _, _) = &mut self.instructions[header_ip] { + *default_ip = pads[default_pad_idx].pad_start as u32; + } - self.instructions.extend(([Instruction::BrTable(def, instrs.len() as u32)].into_iter()).chain(instrs)); + for pad in &pads { + if pad.is_return { + continue; + } + self.patch_branch_jump_or_return(pad.depth, pad.jump_or_ret_ip); + } } fn visit_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { self.instructions.push(Instruction::CallIndirect(ty, table)); } + fn visit_return_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { self.instructions.push(Instruction::ReturnCallIndirect(ty, table)); } @@ -449,19 +452,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild self.instructions.push(Instruction::RefIsNull); } - fn visit_typed_select_multi(&mut self, _tys: Vec) -> Self::Output { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Typed select with multiple types is not supported".to_string(), - )); - - // self.instructions.extend(tys.into_iter().map(|ty| match ty { - // wasmparser::ValType::I32 => Instruction::Select32, - // wasmparser::ValType::F32 => Instruction::Select32, - // wasmparser::ValType::I64 => Instruction::Select64, - // wasmparser::ValType::F64 => Instruction::Select64, - // wasmparser::ValType::V128 => Instruction::Select128, - // wasmparser::ValType::Ref(_) => Instruction::SelectRef, - // })); + fn visit_typed_select_multi(&mut self, tys: Vec) -> Self::Output { + let (c32, c64, c128, cref) = Self::label_keep_counts(&tys); + self.instructions.push(Instruction::SelectMulti(tinywasm_types::ValueCounts { c32, c64, c128, cref })); } fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { @@ -474,6 +467,11 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::Ref(_) => Instruction::SelectRef, }); } + + fn visit_f32_reinterpret_i32(&mut self) -> Self::Output {} + fn visit_f64_reinterpret_i64(&mut self) -> Self::Output {} + fn visit_i32_reinterpret_f32(&mut self) -> Self::Output {} + fn visit_i64_reinterpret_f64(&mut self) -> Self::Output {} } macro_rules! impl_visit_simd_operator { @@ -482,8 +480,8 @@ macro_rules! impl_visit_simd_operator { }; (@@simd $($rest:tt)* ) => {}; + (@@relaxed_simd $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { - #[cold] fn $visit(&mut self $($(,$arg: $argty)*)?) { self.unsupported(stringify!($visit)) } @@ -532,6 +530,19 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuild visit_f64x2_convert_low_i32x4_s(F64x2ConvertLowI32x4S), visit_f64x2_convert_low_i32x4_u(F64x2ConvertLowI32x4U), visit_f32x4_demote_f64x2_zero(F32x4DemoteF64x2Zero), visit_f64x2_promote_low_f32x4(F64x2PromoteLowF32x4), + visit_i8x16_relaxed_swizzle(I8x16RelaxedSwizzle), + visit_i32x4_relaxed_trunc_f32x4_s(I32x4RelaxedTruncF32x4S), visit_i32x4_relaxed_trunc_f32x4_u(I32x4RelaxedTruncF32x4U), + visit_i32x4_relaxed_trunc_f64x2_s_zero(I32x4RelaxedTruncF64x2SZero), visit_i32x4_relaxed_trunc_f64x2_u_zero(I32x4RelaxedTruncF64x2UZero), + visit_f32x4_relaxed_madd(F32x4RelaxedMadd), visit_f32x4_relaxed_nmadd(F32x4RelaxedNmadd), + visit_f64x2_relaxed_madd(F64x2RelaxedMadd), visit_f64x2_relaxed_nmadd(F64x2RelaxedNmadd), + visit_i8x16_relaxed_laneselect(I8x16RelaxedLaneselect), visit_i16x8_relaxed_laneselect(I16x8RelaxedLaneselect), + visit_i32x4_relaxed_laneselect(I32x4RelaxedLaneselect), visit_i64x2_relaxed_laneselect(I64x2RelaxedLaneselect), + visit_f32x4_relaxed_min(F32x4RelaxedMin), visit_f32x4_relaxed_max(F32x4RelaxedMax), + visit_f64x2_relaxed_min(F64x2RelaxedMin), visit_f64x2_relaxed_max(F64x2RelaxedMax), + visit_i16x8_relaxed_q15mulr_s(I16x8RelaxedQ15mulrS), + visit_i16x8_relaxed_dot_i8x16_i7x16_s(I16x8RelaxedDotI8x16I7x16S), + visit_i32x4_relaxed_dot_i8x16_i7x16_add_s(I32x4RelaxedDotI8x16I7x16AddS), + visit_i8x16_extract_lane_s(I8x16ExtractLaneS, u8), visit_i8x16_extract_lane_u(I8x16ExtractLaneU, u8), visit_i8x16_replace_lane(I8x16ReplaceLane, u8), visit_i16x8_extract_lane_s(I16x8ExtractLaneS, u8), visit_i16x8_extract_lane_u(I16x8ExtractLaneU, u8), visit_i16x8_replace_lane(I16x8ReplaceLane, u8), visit_i32x4_extract_lane(I32x4ExtractLane, u8), visit_i32x4_replace_lane(I32x4ReplaceLane, u8), @@ -541,12 +552,238 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuild } fn visit_i8x16_shuffle(&mut self, lanes: [u8; 16]) -> Self::Output { - self.instructions.push(Instruction::I8x16Shuffle(self.v128_constants.len() as u32)); - self.v128_constants.push(i128::from_le_bytes(lanes)); + self.instructions.push(Instruction::I8x16Shuffle(self.data.v128_constants.len() as u32)); + self.data.v128_constants.push(i128::from_le_bytes(lanes)); } fn visit_v128_const(&mut self, value: wasmparser::V128) -> Self::Output { - self.instructions.push(Instruction::V128Const(self.v128_constants.len() as u32)); - self.v128_constants.push(value.i128()); + self.instructions.push(Instruction::V128Const(self.data.v128_constants.len() as u32)); + self.data.v128_constants.push(value.i128()); + } +} + +impl FunctionBuilder { + pub(crate) fn validator_visitor( + &mut self, + offset: usize, + ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { + self.validator.simd_visitor(offset) + } + + pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { + Self { + validator, + local_addr_map, + instructions: Vec::with_capacity(instr_capacity), + data: FunctionDataBuilder::default(), + ctx_stack: Vec::with_capacity(256), + errors: Vec::new(), + } + } + + fn stack_base_at_frame(&self, depth: usize) -> StackBase { + let Some(frame) = self.validator.get_control_frame(depth) else { return StackBase::default() }; + let mut base = StackBase::default(); + for i in 0..frame.height { + let depth_from_top = self.validator.operand_stack_height() as usize - 1 - i; + if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => base.s64 += 1, + wasmparser::ValType::V128 => base.s128 += 1, + wasmparser::ValType::Ref(_) => base.sref += 1, + } + } + } + + base + } + + fn unsupported(&mut self, name: &str) { + self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); + } + + fn is_unreachable(&self) -> bool { + self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) + } + + fn get_ctx_idx(&self, depth: u32) -> Option { + let len = self.ctx_stack.len(); + let idx = len.checked_sub(depth as usize + 1)?; + Some(idx) + } + + fn emit_dropkeep(&mut self, base: StackBase, c32: u16, c64: u16, c128: u16, cref: u16) { + if base.s32 == 0 + && c32 == 0 + && base.s64 == 0 + && c64 == 0 + && base.s128 == 0 + && c128 == 0 + && base.sref == 0 + && cref == 0 + { + return; + } + + let fits_u8 = base.s32 <= u8::MAX as u16 + && c32 <= u8::MAX as u16 + && base.s64 <= u8::MAX as u16 + && c64 <= u8::MAX as u16 + && base.s128 <= u8::MAX as u16 + && c128 <= u8::MAX as u16 + && base.sref <= u8::MAX as u16 + && cref <= u8::MAX as u16; + + if fits_u8 { + self.instructions.push(Instruction::DropKeepSmall { + base32: base.s32 as u8, + keep32: c32 as u8, + base64: base.s64 as u8, + keep64: c64 as u8, + base128: base.s128 as u8, + keep128: c128 as u8, + base_ref: base.sref as u8, + keep_ref: cref as u8, + }); + } else { + self.instructions.push(Instruction::DropKeep32(base.s32, c32)); + self.instructions.push(Instruction::DropKeep64(base.s64, c64)); + self.instructions.push(Instruction::DropKeep128(base.s128, c128)); + self.instructions.push(Instruction::DropKeepRef(base.sref, cref)); + } + } + + fn patch_jump(&mut self, jump_ip: usize, target: usize) { + match &mut self.instructions[jump_ip] { + Instruction::Jump(ip) | Instruction::JumpIfNonZero(ip) => { + *ip = target as u32; + } + _ => {} + } + } + + fn patch_jump_if_zero(&mut self, jump_ip: usize, target: usize) { + if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { + *ip = target as u32; + } + } + + fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { + let (mut c32, mut c64, mut c128, mut cref) = (0, 0, 0, 0); + for ty in label_types { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => c64 += 1, + wasmparser::ValType::V128 => c128 += 1, + wasmparser::ValType::Ref(_) => cref += 1, + } + } + + (c32, c64, c128, cref) + } + + fn emit_dropkeep_to_label(&mut self, label_depth: u32) { + if self.is_unreachable() { + return; + } + + let Some(frame) = self.validator.get_control_frame(label_depth as usize) else { + return; + }; + + let base = self.stack_base_at_frame(label_depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + + self.emit_dropkeep(base, c32, c64, c128, cref); + } + + fn label_types_for_frame(&self, frame: &wasmparser::Frame) -> Vec { + let ty = &frame.block_type; + match ty { + wasmparser::BlockType::Empty => Vec::new(), + wasmparser::BlockType::Type(ty) => match frame.kind { + FrameKind::Loop => Vec::new(), + _ => vec![*ty], + }, + wasmparser::BlockType::FuncType(idx) => { + let sub_type = self.validator.resources().sub_type_at(*idx); + let func_ty = match sub_type { + Some(st) => st.composite_type.unwrap_func(), + None => return Vec::new(), + }; + match frame.kind { + FrameKind::Loop => func_ty.params().to_vec(), + _ => func_ty.results().to_vec(), + } + } + } + } + + fn emit_branch_jump_or_return(&mut self, depth: u32) { + if let Some(ctx_idx) = self.get_ctx_idx(depth) { + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); + } else { + self.instructions.push(Instruction::Return); + } + } + + fn emit_br_table_pad(&mut self, depth: u32) -> (usize, usize, bool) { + let pad_start = self.instructions.len(); + let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; + let Some(frame) = frame else { + let ip = self.instructions.len(); + self.instructions.push(Instruction::Return); + return (pad_start, ip, true); + }; + + let base = self.stack_base_at_frame(depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + self.emit_dropkeep(base, c32, c64, c128, cref); + + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + (pad_start, jump_ip, false) + } + + fn patch_branch_jump_or_return(&mut self, depth: u32, jump_ip: usize) { + let Some(frame) = self.validator.get_control_frame(depth as usize) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + let Some(ctx_idx) = self.get_ctx_idx(depth) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + + match frame.kind { + FrameKind::Loop => self.patch_jump(jump_ip, self.ctx_stack[ctx_idx].start_ip), + _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), + } + } + + fn patch_end_jumps(&mut self, ctx: LoweringCtx, end_ip: usize) { + match ctx.kind { + BlockKind::Block | BlockKind::Loop => { + let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip } else { end_ip }; + for jump_ip in ctx.branch_jumps { + self.patch_jump(jump_ip, target); + } + } + BlockKind::If => { + if let Some((&cond_jump_ip, branch_jumps)) = ctx.branch_jumps.split_first() { + if !ctx.has_else { + self.patch_jump_if_zero(cond_jump_ip, end_ip); + } + for &jump_ip in branch_jumps { + self.patch_jump(jump_ip, end_ip); + } + } + } + } } } diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 2315b9b1..21455698 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -34,9 +34,9 @@ serde_json.workspace=true serde.workspace=true [features] -default=["std", "parser", "logging", "archive", "canonicalize_nans"] +default=["std", "parser", "log", "archive", "canonicalize_nans", "debug"] -logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"] +log=["dep:log", "tinywasm-parser?/log", "tinywasm-types/log"] std=["tinywasm-parser?/std", "tinywasm-types/std"] # support for parsing WebAssembly @@ -48,8 +48,13 @@ archive=["tinywasm-types/archive"] # canonicalize all NaN values to a single representation canonicalize_nans=[] -# enable simd support (unstable / unfinished) -unstable-simd=[] +# derive Debug for runtime/types structs +debug=["tinywasm-types/debug"] + +# enable x86-specific SIMD intrinsics in Value128 (uses unsafe code) +# note: for x86 backend selection, compile with x86-64-v3 target features +# (for example: `RUSTFLAGS="-C target-cpu=x86-64-v3"`) +simd-x86=[] [[test]] name="test-wasm-1" @@ -59,10 +64,6 @@ harness=false name="test-wasm-2" harness=false -[[test]] -name="test-wasm-3" -harness=false - [[test]] name="test-wasm-multi-memory" harness=false @@ -75,6 +76,10 @@ harness=false name="test-wasm-custom-page-sizes" harness=false +[[test]] +name="test-wasm-custom" +harness=false + [[test]] name="test-wasm-tail-call" harness=false @@ -82,7 +87,6 @@ harness=false [[test]] name="test-wasm-memory64" harness=false -test=false [[test]] name="test-wasm-extended-const" @@ -92,17 +96,22 @@ test=false [[test]] name="test-wasm-relaxed-simd" harness=false -test=false [[test]] name="test-wasm-simd" harness=false -test=false [[test]] -name="test-wast" +name="test-wasm-wide-arithmetic" +harness=false + +[[test]] +name="test-wasm-sign-extension-op" +harness=false + +[[test]] +name="test-wasm-nontrapping-float-to-int-conversions" harness=false -test=false [[bench]] name="argon2id" @@ -115,3 +124,43 @@ harness=false [[bench]] name="tinywasm" harness=false + +[[bench]] +name="tinywasm_modes" +harness=false + + +[[test]] +name="test-wasm-3" +harness=false +test=false + +[[test]] +name="test-wasm-latest" +harness=false +test=false + +[[test]] +name="test-wasm-threads" +harness=false +test=false + +[[test]] +name="test-wast" +harness=false +test=false + +[[test]] +name="test-wasm-function-references" +harness=false +test=false + +[[test]] +name="test-wasm-gc" +harness=false +test=false + +[[test]] +name="test-wasm-reference-types" +harness=false +test=false diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index aa8b38b6..74c51e3b 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -3,7 +3,7 @@ use eyre::Result; use tinywasm::{ModuleInstance, Store, types}; use types::TinyWasmModule; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.opt.wasm"); +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.wasm"); fn argon2id_parse() -> Result { let parser = tinywasm_parser::Parser::new(); diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index a5f1c726..75d17a43 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -3,7 +3,7 @@ use eyre::Result; use tinywasm::{ModuleInstance, Store, types}; use types::TinyWasmModule; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.opt.wasm"); +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); fn fibonacci_parse() -> Result { let parser = tinywasm_parser::Parser::new(); diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 4848a4a7..73096a50 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -3,7 +3,7 @@ use eyre::Result; use tinywasm::{Extern, FuncContext, Imports, ModuleInstance, Store, types}; use types::TinyWasmModule; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.opt.wasm"); +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); fn tinywasm_parse() -> Result { let parser = tinywasm_parser::Parser::new(); @@ -34,11 +34,13 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); + let mut group = c.benchmark_group("tinywasm"); + group.measurement_time(std::time::Duration::from_secs(10)); - c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); - c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); - c.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); + group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); + group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); + group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + group.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } criterion_group!(benches, criterion_benchmark); diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs new file mode 100644 index 00000000..edb472dc --- /dev/null +++ b/crates/tinywasm/benches/tinywasm_modes.rs @@ -0,0 +1,102 @@ +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; +use eyre::Result; +use tinywasm::engine::{Config, FuelPolicy}; +use tinywasm::types::TinyWasmModule; +use tinywasm::{Engine, ExecProgress, Extern, FuncContext, FuncHandleTyped, Imports, ModuleInstance, Store}; + +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); +const FUEL_PER_ROUND: u32 = 512; +const TIME_BUDGET_PER_ROUND: core::time::Duration = core::time::Duration::from_micros(50); +const BENCH_MEASUREMENT_TIME: core::time::Duration = core::time::Duration::from_secs(10); + +fn tinywasm_parse() -> Result { + let parser = tinywasm_parser::Parser::new(); + Ok(parser.parse_module_bytes(WASM)?) +} + +fn setup_typed_func(module: TinyWasmModule, engine: Option) -> Result<(Store, FuncHandleTyped<(), ()>)> { + let mut store = match engine { + Some(engine) => Store::new(engine), + None => Store::default(), + }; + + let mut imports = Imports::default(); + imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?; + + let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports))?; + let func = instance.exported_func::<(), ()>(&store, "hello")?; + Ok((store, func)) +} + +fn run_call(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { + func.call(store, ())?; + Ok(()) +} + +fn run_resume_with_fuel(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { + let mut execution = func.call_resumable(store, ())?; + loop { + match execution.resume_with_fuel(FUEL_PER_ROUND)? { + ExecProgress::Completed(_) => return Ok(()), + ExecProgress::Suspended => {} + } + } +} + +fn run_resume_with_time_budget(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { + let mut execution = func.call_resumable(store, ())?; + loop { + match execution.resume_with_time_budget(TIME_BUDGET_PER_ROUND)? { + ExecProgress::Completed(_) => return Ok(()), + ExecProgress::Suspended => {} + } + } +} + +fn criterion_benchmark(c: &mut Criterion) { + let module = tinywasm_parse().expect("tinywasm_parse"); + let mut group = c.benchmark_group("tinywasm_modes"); + group.measurement_time(BENCH_MEASUREMENT_TIME); + + let per_instruction_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::PerInstruction)); + group.bench_function("resume_fuel_per_instruction", |b| { + b.iter_batched_ref( + || { + setup_typed_func(module.clone(), Some(per_instruction_engine.clone())) + .expect("setup fuel per-instruction") + }, + |(store, func)| run_resume_with_fuel(store, func).expect("run fuel per-instruction"), + BatchSize::LargeInput, + ) + }); + + let weighted_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted)); + group.bench_function("resume_fuel_weighted", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), Some(weighted_engine.clone())).expect("setup fuel weighted"), + |(store, func)| run_resume_with_fuel(store, func).expect("run fuel weighted"), + BatchSize::LargeInput, + ) + }); + + group.bench_function("resume_time_budget", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), None).expect("setup time budget"), + |(store, func)| run_resume_with_time_budget(store, func).expect("run time budget"), + BatchSize::LargeInput, + ) + }); + + group.bench_function("call", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), None).expect("setup call"), + |(store, func)| run_call(store, func).expect("run call"), + BatchSize::LargeInput, + ) + }); + + group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/crates/tinywasm/src/config.rs b/crates/tinywasm/src/config.rs deleted file mode 100644 index 70ca4db4..00000000 --- a/crates/tinywasm/src/config.rs +++ /dev/null @@ -1,115 +0,0 @@ -use core::fmt; - -/// Default initial size for the 32-bit value stack (i32, f32 values). -pub const DEFAULT_VALUE_STACK_32_INIT_SIZE: usize = 32 * 1024; // 32KB - -/// Default initial size for the 64-bit value stack (i64, f64 values). -pub const DEFAULT_VALUE_STACK_64_INIT_SIZE: usize = 16 * 1024; // 16KB - -/// Default initial size for the 128-bit value stack (v128 values). -pub const DEFAULT_VALUE_STACK_128_INIT_SIZE: usize = 8 * 1024; // 8KB - -/// Default initial size for the reference value stack (funcref, externref values). -pub const DEFAULT_VALUE_STACK_REF_INIT_SIZE: usize = 1024; // 1KB - -/// Default initial size for the block stack. -pub const DEFAULT_BLOCK_STACK_INIT_SIZE: usize = 128; - -/// Configuration for the WebAssembly interpreter's stack preallocation. -/// -/// This struct allows you to configure how much space is preallocated for the -/// different parts of the stack that the interpreter uses to store values. -#[derive(Debug, Clone)] -pub struct StackConfig { - value_stack_32_init_size: Option, - value_stack_64_init_size: Option, - value_stack_128_init_size: Option, - value_stack_ref_init_size: Option, - block_stack_init_size: Option, -} - -impl StackConfig { - /// Create a new stack configuration with default settings. - pub fn new() -> Self { - Self { - value_stack_32_init_size: None, - value_stack_64_init_size: None, - value_stack_128_init_size: None, - value_stack_ref_init_size: None, - block_stack_init_size: None, - } - } - - /// Get the initial size for the 32-bit value stack. - pub fn value_stack_32_init_size(&self) -> usize { - self.value_stack_32_init_size.unwrap_or(DEFAULT_VALUE_STACK_32_INIT_SIZE) - } - - /// Get the initial size for the 64-bit value stack. - pub fn value_stack_64_init_size(&self) -> usize { - self.value_stack_64_init_size.unwrap_or(DEFAULT_VALUE_STACK_64_INIT_SIZE) - } - - /// Get the initial size for the 128-bit value stack. - pub fn value_stack_128_init_size(&self) -> usize { - self.value_stack_128_init_size.unwrap_or(DEFAULT_VALUE_STACK_128_INIT_SIZE) - } - - /// Get the initial size for the reference value stack. - pub fn value_stack_ref_init_size(&self) -> usize { - self.value_stack_ref_init_size.unwrap_or(DEFAULT_VALUE_STACK_REF_INIT_SIZE) - } - - /// Get the initial size for the block stack. - pub fn block_stack_init_size(&self) -> usize { - self.block_stack_init_size.unwrap_or(DEFAULT_BLOCK_STACK_INIT_SIZE) - } - - /// Set the initial capacity for the 32-bit value stack. - pub fn with_value_stack_32_init_size(mut self, capacity: usize) -> Self { - self.value_stack_32_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the 64-bit value stack. - pub fn with_value_stack_64_init_size(mut self, capacity: usize) -> Self { - self.value_stack_64_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the 128-bit value stack. - pub fn with_value_stack_128_init_size(mut self, capacity: usize) -> Self { - self.value_stack_128_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the reference value stack. - pub fn with_value_stack_ref_init_size(mut self, capacity: usize) -> Self { - self.value_stack_ref_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the block stack. - pub fn with_block_stack_init_size(mut self, capacity: usize) -> Self { - self.block_stack_init_size = Some(capacity); - self - } -} - -impl Default for StackConfig { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Display for StackConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "StackConfig {{ ")?; - write!(f, "value_stack_32: {}, ", self.value_stack_32_init_size())?; - write!(f, "value_stack_64: {}, ", self.value_stack_64_init_size())?; - write!(f, "value_stack_128: {}, ", self.value_stack_128_init_size())?; - write!(f, "value_stack_ref: {}, ", self.value_stack_ref_init_size())?; - write!(f, "block_stack: {} }}", self.block_stack_init_size())?; - Ok(()) - } -} diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs new file mode 100644 index 00000000..ce84f3d6 --- /dev/null +++ b/crates/tinywasm/src/engine.rs @@ -0,0 +1,100 @@ +use alloc::sync::Arc; + +/// Global configuration for the WebAssembly interpreter +/// +/// Can be cheaply cloned and shared across multiple executions and threads. +#[derive(Clone, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct Engine { + pub(crate) inner: Arc, +} + +impl Engine { + /// Create a new engine with the given configuration + pub fn new(config: Config) -> Self { + Self { inner: Arc::new(EngineInner { config }) } + } + + /// Get a reference to the engine's configuration + pub fn config(&self) -> &Config { + &self.inner.config + } +} + +#[derive(Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct EngineInner { + pub(crate) config: Config, +} + +/// Fuel accounting policy for budgeted execution. +#[non_exhaustive] +#[derive(Default, Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub enum FuelPolicy { + /// Charge one fuel unit per retired instruction. + #[default] + PerInstruction, + /// Charge one fuel unit per instruction plus predefined extra cost for specific operations. + Weighted, +} + +/// Default size for the 32-bit value stack (i32, f32 values). +pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 32 * 1024; // 32k slots + +/// Default size for the 64-bit value stack (i64, f64 values). +pub const DEFAULT_VALUE_STACK_64_SIZE: usize = 32 * 1024; // 32k slots + +/// Default size for the 128-bit value stack (v128 values). +pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots + +/// Default size for the reference value stack (funcref, externref values). +pub const DEFAULT_VALUE_STACK_REF_SIZE: usize = 4 * 1024; // 4k slots + +/// Default maximum size for the call stack (function frames). +pub const DEFAULT_MAX_CALL_STACK_SIZE: usize = 1024; // 1024 frames + +/// Configuration for the WebAssembly interpreter +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[non_exhaustive] +pub struct Config { + /// Size of the 32-bit value stack (i32, f32 values). + pub stack_32_size: usize, + /// Size of the 64-bit value stack (i64, f64 values). + pub stack_64_size: usize, + /// Size of the 128-bit value stack (v128 values). + pub stack_128_size: usize, + /// Size of the reference value stack (funcref, externref values). + pub stack_ref_size: usize, + /// Maximum size of the call stack + pub max_call_stack_size: usize, + /// Fuel accounting policy used by budgeted execution. + pub fuel_policy: FuelPolicy, +} + +impl Config { + /// Create a new stack configuration with default settings. + pub fn new() -> Self { + Self::default() + } + + /// Set the fuel accounting policy for budgeted execution. + pub fn fuel_policy(mut self, fuel_policy: FuelPolicy) -> Self { + self.fuel_policy = fuel_policy; + self + } +} + +impl Default for Config { + fn default() -> Self { + Self { + stack_32_size: DEFAULT_VALUE_STACK_32_SIZE, + stack_64_size: DEFAULT_VALUE_STACK_64_SIZE, + stack_128_size: DEFAULT_VALUE_STACK_128_SIZE, + stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE, + max_call_stack_size: DEFAULT_MAX_CALL_STACK_SIZE, + fuel_policy: FuelPolicy::default(), + } + } +} diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 4b3a9696..591ba99f 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,6 +1,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::{fmt::Display, ops::ControlFlow}; +use core::fmt::Debug; +use core::fmt::Display; use tinywasm_types::FuncType; use tinywasm_types::archive::TwasmError; @@ -8,7 +9,6 @@ use tinywasm_types::archive::TwasmError; pub use tinywasm_parser::ParseError; /// Errors that can occur for `TinyWasm` operations -#[derive(Debug)] #[non_exhaustive] pub enum Error { /// A WebAssembly trap occurred @@ -49,9 +49,9 @@ pub enum Error { Twasm(TwasmError), } -#[derive(Debug)] /// Errors that can occur when linking a WebAssembly module #[non_exhaustive] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum LinkingError { /// An unknown import was encountered UnknownImport { @@ -80,11 +80,11 @@ impl LinkingError { } } -#[derive(Debug)] /// A WebAssembly trap /// /// See #[non_exhaustive] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum Trap { /// An unreachable instruction was executed Unreachable, @@ -121,6 +121,9 @@ pub enum Trap { /// Call stack overflow CallStackOverflow, + /// Value stack overflow + ValueStackOverflow, + /// An undefined element was encountered UndefinedElement { /// The element index @@ -153,6 +156,7 @@ impl Trap { Self::InvalidConversionToInt => "invalid conversion to integer", Self::IntegerOverflow => "integer overflow", Self::CallStackOverflow => "call stack exhausted", + Self::ValueStackOverflow => "value stack exhausted", Self::UndefinedElement { .. } => "undefined element", Self::UninitializedElement { .. } => "uninitialized element", Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch", @@ -202,9 +206,12 @@ impl Display for Error { Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {message}"), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {feature}"), + #[cfg(feature = "debug")] Self::InvalidHostFnReturn { expected, actual } => { write!(f, "invalid host function return: expected={expected:?}, actual={actual:?}") } + #[cfg(not(feature = "debug"))] + Self::InvalidHostFnReturn { .. } => write!(f, "invalid host function return"), Self::InvalidStore => write!(f, "invalid store"), } } @@ -235,17 +242,27 @@ impl Display for Trap { Self::InvalidConversionToInt => write!(f, "invalid conversion to integer"), Self::IntegerOverflow => write!(f, "integer overflow"), Self::CallStackOverflow => write!(f, "call stack exhausted"), + Self::ValueStackOverflow => write!(f, "value stack exhausted"), Self::UndefinedElement { index } => write!(f, "undefined element: index={index}"), Self::UninitializedElement { index } => { write!(f, "uninitialized element: index={index}") } + #[cfg(feature = "debug")] Self::IndirectCallTypeMismatch { expected, actual } => { write!(f, "indirect call type mismatch: expected={expected:?}, actual={actual:?}") } + #[cfg(not(feature = "debug"))] + Self::IndirectCallTypeMismatch { .. } => write!(f, "indirect call type mismatch"), } } } +impl Debug for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self) + } +} + impl core::error::Error for Error {} #[cfg(feature = "parser")] @@ -257,16 +274,3 @@ impl From for Error { /// A wrapper around [`core::result::Result`] for tinywasm operations pub type Result = crate::std::result::Result; - -pub(crate) trait Controlify { - fn to_cf(self) -> ControlFlow, T>; -} - -impl Controlify for Result { - fn to_cf(self) -> ControlFlow, T> { - match self { - Ok(value) => ControlFlow::Continue(value), - Err(err) => ControlFlow::Break(Some(err)), - } - } -} diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index e8055d19..75a094a1 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,18 +1,50 @@ -use crate::interpreter::stack::{CallFrame, Stack}; -use crate::{Error, FuncContext, Result, Store}; -use crate::{Function, log, unlikely}; -use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; +use crate::interpreter::stack::CallFrame; +use crate::{Error, FuncContext, InterpreterRuntime, Result, Store}; +use crate::{Function, unlikely}; +use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec}; use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, ValType, WasmValue}; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Progress for fuel-limited function execution. +pub enum ExecProgress { + /// Execution completed and produced a result. + Completed(T), + /// Execution suspended after exhausting fuel or time budget. + Suspended, +} + +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct ExecutionState { + pub(crate) callframe: CallFrame, +} + /// A function handle +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncHandle { pub(crate) module_addr: ModuleInstanceAddr, pub(crate) addr: u32, pub(crate) ty: FuncType, +} + +/// Resumable execution for an untyped function call. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct FuncExecution<'store> { + store: &'store mut Store, + state: FuncExecutionState, +} + +#[cfg_attr(feature = "debug", derive(Debug))] +enum FuncExecutionState { + Running { exec_state: ExecutionState, root_func_addr: u32 }, + Completed { result: Option> }, +} - /// The name of the function, if it has one - pub name: Option, +/// Resumable execution for a typed function call. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct FuncExecutionTyped<'store, R> { + execution: FuncExecution<'store>, + marker: core::marker::PhantomData, } impl FuncHandle { @@ -21,70 +53,167 @@ impl FuncHandle { /// See #[inline] pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { - // Comments are ordered by the steps in the spec - // In this implementation, some steps are combined and ordered differently for performance reasons - - // 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 unlikely(func_ty.params.len() != params.len()) { - return Err(Error::Other(format!( - "param count mismatch: expected {}, got {}", - func_ty.params.len(), - params.len() - ))); - } - - // 5. For each value type and the corresponding value, check if types match - if !(func_ty.params.iter().zip(params).enumerate().all(|(_i, (ty, param))| { - if ty == ¶m.val_type() { - true - } else { - log::error!("param type mismatch at index {_i}: expected {ty:?}, got {param:?}"); - false - } - })) { - return Err(Error::Other("Type mismatch".into())); - } + validate_call_params(&self.ty, params)?; - let func_inst = store.get_func(self.addr); + let func_inst = store.state.get_func(self.addr); let wasm_func = match &func_inst.func { Function::Host(host_func) => { - let host_func = host_func.clone(); - let ctx = FuncContext { store, module_addr: self.module_addr }; - return host_func.call(ctx, params); + return host_func.clone().call(FuncContext { store, module_addr: self.module_addr }, params); } - Function::Wasm(wasm_func) => wasm_func, + Function::Wasm(wasm_func) => wasm_func.clone(), }; - // 6. Let f be the dummy frame - let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, 0); + // Reset stack, push args, allocate locals, create entry frame. + store.stack.clear(); + store.stack.values.extend_from_wasmvalues(params)?; + let locals_base = store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals)?; + let stack_offset = wasm_func.locals; + let callframe = CallFrame::new(self.addr, func_inst.owner, locals_base, stack_offset); + + // Execute until completion and then collect result values from the stack. + InterpreterRuntime::exec(store, callframe)?; + + collect_call_results(store, &self.ty) + } + + /// Call a function and return a resumable execution handle. + /// + /// The returned handle keeps a mutable borrow of the [`Store`] until it + /// completes. Use [`FuncExecution::resume_with_fuel`] (or + /// [`FuncExecution::resume_with_time_budget`] with `std`) to continue. + pub fn call_resumable<'store>( + &self, + store: &'store mut Store, + params: &[WasmValue], + ) -> Result> { + validate_call_params(&self.ty, params)?; + + let func_inst = store.state.get_func(self.addr); + let func_inst_owner = func_inst.owner; + let func = func_inst.func.clone(); + + match func { + Function::Host(host_func) => { + let result = host_func.call(FuncContext { store, module_addr: self.module_addr }, params)?; + Ok(FuncExecution { store, state: FuncExecutionState::Completed { result: Some(result) } }) + } + Function::Wasm(wasm_func) => { + store.stack.clear(); + store.stack.values.extend_from_wasmvalues(params)?; + let locals_base = store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals)?; + let stack_offset = wasm_func.locals; + let callframe = CallFrame::new(self.addr, func_inst_owner, locals_base, stack_offset); + + Ok(FuncExecution { + store, + state: FuncExecutionState::Running { + exec_state: ExecutionState { callframe }, + root_func_addr: self.addr, + }, + }) + } + } + } +} - // 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) - let mut stack = Stack::new(call_frame, &store.config); +impl<'store> FuncExecution<'store> { + /// Resume execution with up to `fuel` units of fuel. + /// + /// Fuel is accounted in chunks, so execution may overshoot the requested + /// fuel before returning [`ExecProgress::Suspended`]. + /// + /// Returns [`ExecProgress::Suspended`] when fuel is exhausted, or + /// [`ExecProgress::Completed`] with the final values once the invocation + /// returns. + pub fn resume_with_fuel(&mut self, fuel: u32) -> Result>> { + let FuncExecutionState::Running { exec_state, root_func_addr } = &mut self.state else { + let FuncExecutionState::Completed { result } = &mut self.state else { + unreachable!("invalid function execution state") + }; + return result + .take() + .map(ExecProgress::Completed) + .ok_or_else(|| Error::Other("execution already completed".to_string())); + }; - // 9. Invoke the function instance - let runtime = store.runtime(); - runtime.exec(store, &mut stack)?; + match InterpreterRuntime::exec_with_fuel(self.store, exec_state.callframe, fuel)? { + crate::interpreter::ExecState::Completed => { + let result_ty = self.store.state.get_func(*root_func_addr).func.ty().clone(); + let result = collect_call_results(self.store, &result_ty)?; + self.state = FuncExecutionState::Completed { result: None }; + Ok(ExecProgress::Completed(result)) + } + crate::interpreter::ExecState::Suspended(callframe) => { + exec_state.callframe = callframe; + Ok(ExecProgress::Suspended) + } + } + } - // Once the function returns: - // let result_m = func_ty.results.len(); + #[cfg(feature = "std")] + /// Resume execution for at most `time_budget` wall-clock time. + /// + /// Time is checked periodically, so execution may overshoot the requested + /// time budget before returning [`ExecProgress::Suspended`]. + /// + /// Returns [`ExecProgress::Suspended`] when the budget is exhausted, or + /// [`ExecProgress::Completed`] with the final values once the invocation + /// returns. + pub fn resume_with_time_budget( + &mut self, + time_budget: crate::std::time::Duration, + ) -> Result>> { + let FuncExecutionState::Running { exec_state, root_func_addr } = &mut self.state else { + let FuncExecutionState::Completed { result } = &mut self.state else { + unreachable!("invalid function execution state") + }; + return result + .take() + .map(ExecProgress::Completed) + .ok_or_else(|| Error::Other("execution already completed".to_string())); + }; - // 1. Assert: m values are on the top of the stack (Ensured by validation) - // assert!(stack.values.len() >= result_m); + match InterpreterRuntime::exec_with_time_budget(self.store, exec_state.callframe, time_budget)? { + crate::interpreter::ExecState::Completed => { + let result_ty = self.store.state.get_func(*root_func_addr).func.ty().clone(); + let result = collect_call_results(self.store, &result_ty)?; + self.state = FuncExecutionState::Completed { result: None }; + Ok(ExecProgress::Completed(result)) + } + crate::interpreter::ExecState::Suspended(callframe) => { + exec_state.callframe = callframe; + Ok(ExecProgress::Suspended) + } + } + } +} - // 2. Pop m values from the stack - let res = stack.values.pop_results(&func_ty.results); +fn validate_call_params(func_ty: &FuncType, params: &[WasmValue]) -> Result<()> { + if unlikely(func_ty.params.len() != params.len()) { + return Err(Error::Other(format!( + "param count mismatch: expected {}, got {}", + func_ty.params.len(), + params.len() + ))); + } - // The values are returned as the results of the invocation. - Ok(res) + if !(func_ty.params.iter().zip(params).all(|(ty, param)| ty == ¶m.val_type())) { + return Err(Error::Other("Type mismatch".into())); } + + Ok(()) +} + +fn collect_call_results(store: &mut Store, func_ty: &FuncType) -> Result> { + // m values are on the top of the stack (Ensured by validation) + debug_assert!(store.stack.values.len() >= func_ty.results.len()); + let mut res: Vec<_> = store.stack.values.pop_types(func_ty.results.iter().rev()).collect(); // pop in reverse order since the stack is LIFO + res.reverse(); // reverse to get the original order + Ok(res) } -#[derive(Debug)] /// A typed function handle +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncHandleTyped { /// The underlying function handle pub func: FuncHandle, @@ -113,44 +242,122 @@ impl FuncHandleTyped { // Convert the Vec back to R R::from_wasm_value_tuple(&result) } + + /// Call a typed function and return a resumable execution handle. + /// + /// The handle keeps a mutable borrow of the [`Store`] until completion. + pub fn call_resumable<'store>(&self, store: &'store mut Store, params: P) -> Result> { + let wasm_values = params.into_wasm_value_tuple(); + let execution = self.func.call_resumable(store, &wasm_values)?; + Ok(FuncExecutionTyped { execution, marker: core::marker::PhantomData }) + } +} + +impl<'store, R: FromWasmValueTuple> FuncExecutionTyped<'store, R> { + /// Resume typed execution with up to `fuel` units of fuel. + /// + /// Fuel is accounted in chunks, so execution may overshoot the requested + /// fuel before returning [`ExecProgress::Suspended`]. + pub fn resume_with_fuel(&mut self, fuel: u32) -> Result> { + match self.execution.resume_with_fuel(fuel)? { + ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_value_tuple(&values)?)), + ExecProgress::Suspended => Ok(ExecProgress::Suspended), + } + } + + #[cfg(feature = "std")] + /// Resume typed execution for at most `time_budget` wall-clock time. + /// + /// Time is checked periodically, so execution may overshoot the requested + /// time budget before returning [`ExecProgress::Suspended`]. + pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result> { + match self.execution.resume_with_time_budget(time_budget)? { + ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_value_tuple(&values)?)), + ExecProgress::Suspended => Ok(ExecProgress::Suspended), + } + } +} + +pub trait ValTypesFromTuple { + fn val_types() -> Box<[ValType]>; } -macro_rules! impl_into_wasm_value_tuple { - ($($T:ident),*) => { - impl<$($T),*> IntoWasmValueTuple for ($($T,)*) +pub trait ToValType { + fn to_val_type() -> ValType; +} + +macro_rules! impl_scalar_wasm_traits { + ($($T:ty => $val_ty:ident),+ $(,)?) => { + $( + impl ToValType for $T { + #[inline] + fn to_val_type() -> ValType { + ValType::$val_ty + } + } + + impl ValTypesFromTuple for $T { + #[inline] + fn val_types() -> Box<[ValType]> { + Box::new([ValType::$val_ty]) + } + } + + impl IntoWasmValueTuple for $T { + #[inline] + fn into_wasm_value_tuple(self) -> Vec { + vec![self.into()] + } + } + + impl FromWasmValueTuple for $T { + #[inline] + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { + let value = *values + .first() + .ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?; + <$T>::try_from(value).map_err(|e| { + Error::Other(format!( + "FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", + e + )) + }) + } + } + )+ + }; +} + +macro_rules! impl_tuple_traits { + ($($T:ident),+) => { + impl<$($T),+> ValTypesFromTuple for ($($T,)+) where - $($T: Into),* + $($T: ToValType,)+ { - #[allow(non_snake_case)] #[inline] - fn into_wasm_value_tuple(self) -> Vec { - let ($($T,)*) = self; - vec![$($T.into(),)*] + fn val_types() -> Box<[ValType]> { + Box::new([$($T::to_val_type(),)+]) } } - } -} -macro_rules! impl_into_wasm_value_tuple_single { - ($T:ident) => { - impl IntoWasmValueTuple for $T { + impl<$($T),+> IntoWasmValueTuple for ($($T,)+) + where + $($T: Into,)+ + { + #[allow(non_snake_case)] #[inline] fn into_wasm_value_tuple(self) -> Vec { - vec![self.into()] + let ($($T,)+) = self; + vec![$($T.into(),)+] } } - }; -} -macro_rules! impl_from_wasm_value_tuple { - ($($T:ident),*) => { - impl<$($T),*> FromWasmValueTuple for ($($T,)*) + impl<$($T),+> FromWasmValueTuple for ($($T,)+) where - $($T: TryFrom),* + $($T: TryFrom,)+ { #[inline] fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { - #[allow(unused_variables, unused_mut)] let mut iter = values.iter(); Ok(( @@ -159,91 +366,43 @@ macro_rules! impl_from_wasm_value_tuple { *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, - )))?, - )* + .map_err(|e| Error::Other(format!( + "FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", + e, + )))?, + )+ )) } } } } -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(); - $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: {:?}", - e - )) - }) - } - } +macro_rules! impl_tuple { + ($macro:ident) => { + $macro!(T1); + $macro!(T1, T2); + $macro!(T1, T2, T3); + $macro!(T1, T2, T3, T4); + $macro!(T1, T2, T3, T4, T5); + $macro!(T1, T2, T3, T4, T5, T6); + $macro!(T1, T2, T3, T4, T5, T6, T7); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); }; } -pub trait ValTypesFromTuple { - fn val_types() -> Box<[ValType]>; -} - -pub trait ToValType { - fn to_val_type() -> ValType; -} - -impl ToValType for i32 { - fn to_val_type() -> ValType { - ValType::I32 - } -} - -impl ToValType for i64 { - fn to_val_type() -> ValType { - ValType::I64 - } -} - -impl ToValType for f32 { - fn to_val_type() -> ValType { - ValType::F32 - } -} - -impl ToValType for f64 { - fn to_val_type() -> ValType { - ValType::F64 - } -} - -impl ToValType for FuncRef { - fn to_val_type() -> ValType { - ValType::RefFunc - } -} - -impl ToValType for ExternRef { - fn to_val_type() -> ValType { - ValType::RefExtern - } -} - -macro_rules! impl_val_types_from_tuple { - ($($t:ident),+) => { - impl<$($t),+> ValTypesFromTuple for ($($t,)+) - where - $($t: ToValType,)+ - { - #[inline] - fn val_types() -> Box<[ValType]> { - Box::new([$($t::to_val_type(),)+]) - } - } - }; -} +impl_scalar_wasm_traits!( + i32 => I32, + i64 => I64, + f32 => F32, + f64 => F64, + FuncRef => RefFunc, + ExternRef => RefExtern, +); +impl_tuple!(impl_tuple_traits); impl ValTypesFromTuple for () { #[inline] @@ -252,46 +411,16 @@ impl ValTypesFromTuple for () { } } -impl ValTypesFromTuple for T { +impl IntoWasmValueTuple for () { #[inline] - fn val_types() -> Box<[ValType]> { - Box::new([T::to_val_type()]) + fn into_wasm_value_tuple(self) -> Vec { + vec![] } } -impl_from_wasm_value_tuple_single!(i32); -impl_from_wasm_value_tuple_single!(i64); -impl_from_wasm_value_tuple_single!(f32); -impl_from_wasm_value_tuple_single!(f64); -impl_from_wasm_value_tuple_single!(FuncRef); -impl_from_wasm_value_tuple_single!(ExternRef); - -impl_into_wasm_value_tuple_single!(i32); -impl_into_wasm_value_tuple_single!(i64); -impl_into_wasm_value_tuple_single!(f32); -impl_into_wasm_value_tuple_single!(f64); -impl_into_wasm_value_tuple_single!(FuncRef); -impl_into_wasm_value_tuple_single!(ExternRef); - -impl_val_types_from_tuple!(T1); -impl_val_types_from_tuple!(T1, T2); -impl_val_types_from_tuple!(T1, T2, T3); -impl_val_types_from_tuple!(T1, T2, T3, T4); -impl_val_types_from_tuple!(T1, T2, T3, T4, T5); -impl_val_types_from_tuple!(T1, T2, T3, T4, T5, T6); - -impl_from_wasm_value_tuple!(); -impl_from_wasm_value_tuple!(T1); -impl_from_wasm_value_tuple!(T1, T2); -impl_from_wasm_value_tuple!(T1, T2, T3); -impl_from_wasm_value_tuple!(T1, T2, T3, T4); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); - -impl_into_wasm_value_tuple!(); -impl_into_wasm_value_tuple!(T1); -impl_into_wasm_value_tuple!(T1, T2); -impl_into_wasm_value_tuple!(T1, T2, T3); -impl_into_wasm_value_tuple!(T1, T2, T3, T4); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); +impl FromWasmValueTuple for () { + #[inline] + fn from_wasm_value_tuple(_values: &[WasmValue]) -> Result { + Ok(()) + } +} diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 563e586d..3bf35e6d 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -10,7 +10,8 @@ use crate::{LinkingError, MemoryRef, MemoryRefMut, Result, log}; use tinywasm_types::*; /// The internal representation of a function -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum Function { /// A host function Host(Rc), @@ -49,7 +50,7 @@ impl HostFunction { pub(crate) type HostFuncInner = Box, &[WasmValue]) -> Result>>; /// The context of a host-function call -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncContext<'a> { pub(crate) store: &'a mut crate::Store, pub(crate) module_addr: ModuleInstanceAddr, @@ -68,7 +69,9 @@ impl FuncContext<'_> { /// Get a reference to the module instance pub fn module(&self) -> crate::ModuleInstance { - self.store.get_module_instance_raw(self.module_addr) + self.store.get_module_instance(self.module_addr).unwrap_or_else(|| { + unreachable!("invalid module instance address in host function context: {}", self.module_addr) + }) } /// Get a reference to an exported memory @@ -80,15 +83,32 @@ impl FuncContext<'_> { pub fn exported_memory_mut(&mut self, name: &str) -> Result> { self.module().exported_memory_mut(self.store, name) } + + /// Charge additional fuel from the currently running resumable invocation. + /// + /// This is a no-op when the current invocation is not using fuel-based + /// resumption. + pub fn charge_fuel(&mut self, fuel: u32) { + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(fuel); + } + + /// Get remaining fuel for the current invocation. + /// + /// Returns `0` when fuel-based resumption is not active. + pub fn remaining_fuel(&self) -> u32 { + self.store.execution_fuel + } } +#[cfg(feature = "debug")] impl Debug for HostFunction { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("HostFunction").field("ty", &self.ty).field("func", &"...").finish() } } -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] /// An external value pub enum Extern { @@ -120,17 +140,17 @@ pub enum Extern { impl Extern { /// Create a new global import - pub fn global(val: WasmValue, mutable: bool) -> Self { + pub const fn global(val: WasmValue, mutable: bool) -> Self { Self::Global { ty: GlobalType { ty: val.val_type(), mutable }, val } } /// Create a new table import - pub fn table(ty: TableType, init: WasmValue) -> Self { + pub const fn table(ty: TableType, init: WasmValue) -> Self { Self::Table { ty, init } } /// Create a new memory import - pub fn memory(ty: MemoryType) -> Self { + pub const fn memory(ty: MemoryType) -> Self { Self::Memory { ty } } @@ -139,18 +159,18 @@ impl Extern { ty: &tinywasm_types::FuncType, func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static, ) -> Self { - let _ty = ty.clone(); + let ty_inner = ty.clone(); let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { - let _ty = _ty.clone(); + let ty = ty_inner.clone(); let result = func(ctx, args)?; - if result.len() != _ty.results.len() { - return Err(crate::Error::InvalidHostFnReturn { expected: _ty.clone(), actual: result }); + if result.len() != ty.results.len() { + return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result }); }; - result.iter().zip(_ty.results.iter()).try_for_each(|(val, ty)| { - if val.val_type() != *ty { - return Err(crate::Error::InvalidHostFnReturn { expected: _ty.clone(), actual: result.clone() }); + result.iter().zip(ty.results.iter()).try_for_each(|(val, res_ty)| { + if val.val_type() != *res_ty { + return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result.clone() }); } Ok(()) })?; @@ -173,12 +193,13 @@ impl Extern { Ok(result.into_wasm_value_tuple()) }; - let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; + let results = R::val_types(); + let ty = tinywasm_types::FuncType { params: P::val_types(), results }; Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty }))) } /// Get the kind of the external value - pub fn kind(&self) -> ExternalKind { + pub const fn kind(&self) -> ExternalKind { match self { Self::Global { .. } => ExternalKind::Global, Self::Table { .. } => ExternalKind::Table, @@ -201,7 +222,6 @@ impl From<&Import> for ExternName { } } -#[derive(Debug, Default)] /// Imports for a module instance /// /// This is used to link a module instance to its imports @@ -236,7 +256,8 @@ impl From<&Import> for ExternName { /// /// Note that module instance addresses for [`Imports::link_module`] can be obtained from [`crate::ModuleInstance::id`]. /// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`]. -#[derive(Clone)] +#[derive(Default, Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct Imports { values: BTreeMap, modules: BTreeMap, @@ -255,14 +276,14 @@ pub(crate) struct ResolvedImports { } impl ResolvedImports { - pub(crate) fn new() -> Self { + pub(crate) const fn new() -> Self { Self { globals: Vec::new(), tables: Vec::new(), memories: Vec::new(), funcs: Vec::new() } } } impl Imports { /// Create a new empty import set - pub fn new() -> Self { + pub const fn new() -> Self { Self { values: BTreeMap::new(), modules: BTreeMap::new() } } @@ -304,9 +325,19 @@ impl Imports { None } - fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { + #[cfg(not(feature = "debug"))] + 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); + log::error!("failed to link import {}", import.name); + return Err(LinkingError::incompatible_import_type(import).into()); + } + Ok(()) + } + + #[cfg(feature = "debug")] + 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(()) @@ -314,20 +345,17 @@ impl Imports { fn compare_table_types(import: &Import, expected: &TableType, actual: &TableType) -> Result<()> { Self::compare_types(import, &actual.element_type, &expected.element_type)?; - if actual.size_initial > expected.size_initial { return Err(LinkingError::incompatible_import_type(import).into()); } match (expected.size_max, actual.size_max) { - (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()), + (None, Some(_)) => 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()); + Err(LinkingError::incompatible_import_type(import).into()) } - _ => {} + _ => Ok(()), } - - Ok(()) } fn compare_memory_types( @@ -363,10 +391,8 @@ impl Imports { ) -> Result { let mut imports = ResolvedImports::new(); - for import in &module.0.imports { - let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?; - - match val { + for import in &*module.0.imports { + match self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))? { // A link to something that needs to be added to the store ResolvedExtern::Extern(ex) => match (ex, &import.kind) { (Extern::Global { ty, val }, ImportKind::Global(import_ty)) => { @@ -403,25 +429,25 @@ impl Imports { match (val, &import.kind) { (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { - let global = store.get_global(global_addr); + let global = store.state.get_global(global_addr); Self::compare_types(import, &global.ty, ty)?; imports.globals.push(global_addr); } (ExternVal::Table(table_addr), ImportKind::Table(ty)) => { - let table = store.get_table(table_addr); + let table = store.state.get_table(table_addr); let mut kind = table.kind.clone(); kind.size_initial = table.size() as u32; Self::compare_table_types(import, &kind, ty)?; imports.tables.push(table_addr); } (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => { - let mem = store.get_mem(memory_addr); + let mem = store.state.get_mem(memory_addr); let (size, kind) = { (mem.page_count, mem.kind) }; Self::compare_memory_types(import, &kind, ty, Some(size))?; imports.memories.push(memory_addr); } (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { - let func = store.get_func(func_addr); + let func = store.state.get_func(func_addr); let import_func_type = module .0 .func_types diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index aaa3407d..b95a94d1 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,55 +1,105 @@ -use alloc::{boxed::Box, format, rc::Rc, string::ToString}; +use alloc::boxed::Box; +use alloc::{format, rc::Rc}; use tinywasm_types::*; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store}; -/// An instanciated WebAssembly module +/// An instantiated WebAssembly module /// /// Backed by an Rc, so cloning is cheap /// /// See -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct ModuleInstance(pub(crate) Rc); -#[expect(dead_code)] -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ModuleInstanceInner { - pub(crate) failed_to_instantiate: bool, - pub(crate) store_id: usize, pub(crate) idx: ModuleInstanceAddr, - - pub(crate) types: Box<[FuncType]>, - + pub(crate) types: ArcSlice, pub(crate) func_addrs: Box<[FuncAddr]>, pub(crate) table_addrs: Box<[TableAddr]>, pub(crate) mem_addrs: Box<[MemAddr]>, pub(crate) global_addrs: Box<[GlobalAddr]>, pub(crate) elem_addrs: Box<[ElemAddr]>, pub(crate) data_addrs: Box<[DataAddr]>, - pub(crate) func_start: Option, - pub(crate) imports: Box<[Import]>, - pub(crate) exports: Box<[Export]>, + pub(crate) exports: ArcSlice, } -impl ModuleInstance { - // drop the module instance reference and swap it with another one +impl ModuleInstanceInner { + #[inline] + pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { + match self.types.get(addr as usize) { + Some(ty) => ty, + None => unreachable!("invalid function address: {addr}"), + } + } + + #[inline] + pub(crate) fn func_addrs(&self) -> &[FuncAddr] { + &self.func_addrs + } + + // resolve a function address to the global store address #[inline] - pub(crate) fn swap(&mut self, other: Self) { - self.0 = other.0; + pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { + match self.func_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid function address: {addr}"), + } } + // resolve a table address to the global store address #[inline] - pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { - if other_addr != self.id() { - self.swap(store.get_module_instance_raw(other_addr)) + pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { + match self.table_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid table address: {addr}"), } } - /// Get the module instance's address + // resolve a memory address to the global store address + #[inline] + pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { + match self.mem_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid memory address: {addr}"), + } + } + + // resolve a data address to the global store address + #[inline] + pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { + match self.data_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid data address: {addr}"), + } + } + + // resolve a memory address to the global store address + #[inline] + pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { + match self.elem_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid element address: {addr}"), + } + } + + // resolve a global address to the global store address #[inline] + pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { + match self.global_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid global address: {addr}"), + } + } +} + +impl ModuleInstance { + /// Get the module instance's address pub fn id(&self) -> ModuleInstanceAddr { self.0.idx } @@ -58,27 +108,21 @@ impl ModuleInstance { /// /// See pub fn instantiate(store: &mut Store, module: Module, imports: Option) -> Result { - // This doesn't completely follow the steps in the spec, but the end result is the same - // Constant expressions are evaluated directly where they are used, so we - // don't need to create a auxiliary frame etc. - let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?; - addrs.funcs.extend(store.init_funcs(module.0.funcs.into(), idx)?); - addrs.tables.extend(store.init_tables(module.0.table_types.into(), idx)?); - addrs.memories.extend(store.init_memories(module.0.memory_types.into(), idx)?); - - let global_addrs = store.init_globals(addrs.globals, module.0.globals.into(), &addrs.funcs, idx)?; + addrs.funcs.extend(store.init_funcs(&module.0.funcs, idx)?); + addrs.tables.extend(store.init_tables(&module.0.table_types, idx)?); + addrs.memories.extend(store.init_memories(&module.0.memory_types, idx)?); + let global_addrs = store.init_globals(addrs.globals, &module.0.globals, &addrs.funcs, idx)?; let (elem_addrs, elem_trapped) = store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?; - let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, module.0.data.into(), idx)?; + let (data_addrs, data_trapped) = store.init_data(&addrs.memories, &module.0.data, idx)?; let instance = ModuleInstanceInner { - failed_to_instantiate: elem_trapped.is_some() || data_trapped.is_some(), store_id: store.id(), idx, - types: module.0.func_types, + types: module.0.func_types.clone(), func_addrs: addrs.funcs.into_boxed_slice(), table_addrs: addrs.tables.into_boxed_slice(), mem_addrs: addrs.memories.into_boxed_slice(), @@ -86,16 +130,15 @@ impl ModuleInstance { elem_addrs, data_addrs, func_start: module.0.start_func, - imports: module.0.imports, - exports: module.0.exports, + exports: module.0.exports.clone(), }; - let instance = Self::new(instance); + let instance = Rc::new(instance); store.add_instance(instance.clone()); match (elem_trapped, data_trapped) { (Some(trap), _) | (_, Some(trap)) => Err(trap.into()), - _ => Ok(instance), + _ => Ok(ModuleInstance(instance)), } } @@ -108,61 +151,9 @@ impl ModuleInstance { ExternalKind::Memory => self.0.mem_addrs.get(exports.index as usize)?, ExternalKind::Global => self.0.global_addrs.get(exports.index as usize)?, }; - Some(ExternVal::new(exports.kind, *addr)) } - #[inline] - pub(crate) fn new(inner: ModuleInstanceInner) -> Self { - Self(Rc::new(inner)) - } - - #[inline] - pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { - &self.0.types[addr as usize] - } - - #[inline] - pub(crate) fn func_addrs(&self) -> &[FuncAddr] { - &self.0.func_addrs - } - - // resolve a function address to the global store address - #[inline] - pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { - self.0.func_addrs[addr as usize] - } - - // resolve a table address to the global store address - #[inline] - pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { - self.0.table_addrs[addr as usize] - } - - // resolve a memory address to the global store address - #[inline] - pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { - self.0.mem_addrs[addr as usize] - } - - // resolve a data address to the global store address - #[inline] - pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { - self.0.data_addrs[addr as usize] - } - - // resolve a memory address to the global store address - #[inline] - pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { - self.0.elem_addrs[addr as usize] - } - - // resolve a global address to the global store address - #[inline] - pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { - self.0.global_addrs[addr as usize] - } - /// Get an exported function by name pub fn exported_func_untyped(&self, store: &Store, name: &str) -> Result { if self.0.store_id != store.id() { @@ -174,16 +165,16 @@ impl ModuleInstance { return Err(Error::Other(format!("Export is not a function: {name}"))); }; - let ty = store.get_func(func_addr).func.ty(); - Ok(FuncHandle { addr: func_addr, module_addr: self.id(), name: Some(name.to_string()), ty: ty.clone() }) + let ty = store.state.get_func(func_addr).func.ty(); + Ok(FuncHandle { addr: func_addr, module_addr: self.id(), ty: ty.clone() }) } /// Get a typed exported function by name - pub fn exported_func(&self, store: &Store, name: &str) -> Result> - where - P: IntoWasmValueTuple, - R: FromWasmValueTuple, - { + pub fn exported_func( + &self, + store: &Store, + name: &str, + ) -> Result> { let func = self.exported_func_untyped(store, name)?; Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) } @@ -194,7 +185,6 @@ impl ModuleInstance { let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - self.memory(store, mem_addr) } @@ -204,26 +194,23 @@ impl ModuleInstance { let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - self.memory_mut(store, mem_addr) } /// Get a memory by address pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result> { - let mem = store.get_mem(self.resolve_mem_addr(addr)); - Ok(MemoryRef(mem)) + Ok(MemoryRef(store.state.get_mem(self.0.resolve_mem_addr(addr)))) } /// Get a memory by address (mutable) pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { - let mem = store.get_mem_mut(self.resolve_mem_addr(addr)); - Ok(MemoryRefMut(mem)) + Ok(MemoryRefMut(store.state.get_mem_mut(self.0.resolve_mem_addr(addr)))) } /// 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 + /// If no start function is specified, also checks for a `_start` function in the exports /// /// See pub fn start_func(&self, store: &Store) -> Result> { @@ -243,24 +230,20 @@ impl ModuleInstance { } }; - let func_addr = self.resolve_func_addr(func_index); - let func_inst = store.get_func(func_addr); - let ty = func_inst.func.ty(); - - Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone(), name: None })) + let func_addr = self.0.resolve_func_addr(func_index); + let ty = store.state.get_func(func_addr).func.ty(); + Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone() })) } /// Invoke the start function of the module /// - /// Returns None if the module has no start function + /// Returns `None` if the module has no start function /// /// See pub fn start(&self, store: &mut Store) -> Result> { let Some(func) = self.start_func(store)? else { return Ok(None); }; - - let _ = func.call(store, &[])?; - Ok(Some(())) + func.call(store, &[]).map(|_| Some(())) } } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 2e979bc9..12f28f4a 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -2,1013 +2,971 @@ #[allow(unused_imports)] use super::no_std_floats::NoStdFloatExt; -use alloc::{format, rc::Rc, string::ToString}; -use core::ops::ControlFlow; +use alloc::boxed::Box; +use alloc::{rc::Rc, string::ToString}; use interpreter::stack::CallFrame; use tinywasm_types::*; -#[cfg(all(feature = "std", feature = "unstable-simd"))] -use crate::std::simd::StdFloat; -#[cfg(feature = "unstable-simd")] -use core::simd::{cmp::*, num::*, *}; - -#[cfg(feature = "unstable-simd")] -use core::ops::{Index, IndexMut, Shl, Shr}; - +use super::ExecState; use super::num_helpers::*; -use super::stack::{BlockFrame, BlockType, Stack}; use super::values::*; +use crate::engine::FuelPolicy; +use crate::instance::ModuleInstanceInner; +use crate::interpreter::Value128; use crate::*; -pub(crate) struct Executor<'store, 'stack> { - pub(crate) cf: CallFrame, - pub(crate) module: ModuleInstance, - pub(crate) store: &'store mut Store, - pub(crate) stack: &'stack mut Stack, +#[cfg(feature = "std")] +const TIME_BUDGET_CHECK_INTERVAL: usize = 2048; +const FUEL_ACCOUNTING_INTERVAL: usize = 1024; +const FUEL_COST_CALL_TOTAL: u32 = 5; + +pub(crate) struct Executor<'store, const BUDGETED: bool> { + cf: CallFrame, + func: Rc, + module: Rc, + store: &'store mut Store, } -impl<'store, 'stack> Executor<'store, 'stack> { - pub(crate) fn new(store: &'store mut Store, stack: &'stack mut Stack) -> Result { - let current_frame = stack.call_stack.pop().expect("no call frame, this is a bug"); - let current_module = store.get_module_instance_raw(current_frame.module_addr()); - Ok(Self { cf: current_frame, module: current_module, stack, store }) +impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { + pub(crate) fn new(store: &'store mut Store, cf: CallFrame) -> Result { + let module = store.get_module_instance_raw(cf.module_addr).clone(); + let func = store.state.get_wasm_func(cf.func_addr).clone(); + Ok(Self { module, store, cf, func }) } #[inline(always)] - pub(crate) fn run_to_completion(&mut self) -> Result<()> { - loop { - if let ControlFlow::Break(res) = self.exec_next() { - return match res { - Some(e) => Err(e), - None => Ok(()), - }; - } + fn charge_call_fuel(&mut self, total_fuel_cost: u32) { + if BUDGETED { + let extra = match self.store.engine.config().fuel_policy { + FuelPolicy::PerInstruction => 0, + FuelPolicy::Weighted => total_fuel_cost.saturating_sub(1), + }; + + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(extra); } } #[inline(always)] - fn exec_next(&mut self) -> ControlFlow> { - use tinywasm_types::Instruction::*; - - #[rustfmt::skip] - match self.cf.fetch_instr() { - Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} - Unreachable => self.exec_unreachable()?, - - Drop32 => self.stack.values.drop::(), - Drop64 => self.stack.values.drop::(), - Drop128 => self.stack.values.drop::(), - DropRef => self.stack.values.drop::(), - - Select32 => self.stack.values.select::(), - Select64 => self.stack.values.select::(), - Select128 => self.stack.values.select::(), - SelectRef => self.stack.values.select::(), - - Call(v) => return self.exec_call_direct::(*v), - CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - - ReturnCall(v) => return self.exec_call_direct::(*v), - ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - - If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())), - IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())), - IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)), - Else(end_offset) => self.exec_else(*end_offset), - Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())), - LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())), - LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)), - Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())), - BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())), - BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)), - Br(v) => return self.exec_br(*v), - BrIf(v) => return self.exec_br_if(*v), - BrTable(default, len) => return self.exec_brtable(*default, *len), - Return => return self.exec_return(), - EndBlockFrame => self.exec_end_block(), - - LocalGet32(local_index) => self.exec_local_get::(*local_index), - LocalGet64(local_index) => self.exec_local_get::(*local_index), - LocalGet128(local_index) => self.exec_local_get::(*local_index), - LocalGetRef(local_index) => self.exec_local_get::(*local_index), - - LocalSet32(local_index) => self.exec_local_set::(*local_index), - LocalSet64(local_index) => self.exec_local_set::(*local_index), - LocalSet128(local_index) => self.exec_local_set::(*local_index), - LocalSetRef(local_index) => self.exec_local_set::(*local_index), - - LocalTee32(local_index) => self.exec_local_tee::(*local_index), - LocalTee64(local_index) => self.exec_local_tee::(*local_index), - LocalTee128(local_index) => self.exec_local_tee::(*local_index), - LocalTeeRef(local_index) => self.exec_local_tee::(*local_index), - - GlobalGet(global_index) => self.exec_global_get(*global_index), - GlobalSet32(global_index) => self.exec_global_set::(*global_index), - GlobalSet64(global_index) => self.exec_global_set::(*global_index), - GlobalSet128(global_index) => self.exec_global_set::(*global_index), - GlobalSetRef(global_index) => self.exec_global_set::(*global_index), - - I32Const(val) => self.exec_const(*val), - I64Const(val) => self.exec_const(*val), - F32Const(val) => self.exec_const(*val), - F64Const(val) => self.exec_const(*val), - RefFunc(func_idx) => self.exec_const::(Some(*func_idx)), - RefNull(_) => self.exec_const::(None), - RefIsNull => self.exec_ref_is_null(), - - MemorySize(addr) => self.exec_memory_size(*addr), - MemoryGrow(addr) => self.exec_memory_grow(*addr), - - // Bulk memory operations - MemoryCopy(from, to) => self.exec_memory_copy(*from, *to).to_cf()?, - MemoryFill(addr) => self.exec_memory_fill(*addr).to_cf()?, - MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx).to_cf()?, - DataDrop(data_index) => self.exec_data_drop(*data_index), - ElemDrop(elem_index) => self.exec_elem_drop(*elem_index), - TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, - - I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - F64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - I32Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, - I32Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, - I64Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, - I64Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, - I64Store32(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i32)?, - - I32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - I64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - F32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - F64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - I32Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - - I64Eqz => self.stack.values.replace_top::(|v| Ok(i32::from(v == 0))).to_cf()?, - I32Eqz => self.stack.values.replace_top_same::(|v| Ok(i32::from(v == 0))).to_cf()?, - I32Eq => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a == b))).to_cf()?, - I64Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, - F32Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, - F64Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, - - I32Ne => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a != b))).to_cf()?, - I64Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, - F32Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, - F64Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, - - I32LtS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a < b))).to_cf()?, - I64LtS => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - I32LtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - I64LtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - F32Lt => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - F64Lt => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - - I32LeS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - I64LeS => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - I32LeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - I64LeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - F32Le => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - F64Le => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - - I32GeS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - I64GeS => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - I32GeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - I64GeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - F32Ge => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - F64Ge => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - - I32GtS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a > b))).to_cf()?, - I64GtS => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - I32GtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - I64GtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - F32Gt => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - F64Gt => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - - I32Add => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_add(b))).to_cf()?, - I64Add => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_add(b))).to_cf()?, - F32Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - F64Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - - I32Sub => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_sub(b))).to_cf()?, - I64Sub => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_sub(b))).to_cf()?, - F32Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - F64Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - - F32Div => self.stack.values.calculate_same::(|a, b| Ok(a / b)).to_cf()?, - F64Div => self.stack.values.calculate_same::(|a, b| Ok(a / b)).to_cf()?, - - I32Mul => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_mul(b))).to_cf()?, - I64Mul => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_mul(b))).to_cf()?, - F32Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - F64Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - - I32DivS => self.stack.values.calculate_same::(|a, b| a.wasm_checked_div(b)).to_cf()?, - I64DivS => self.stack.values.calculate_same::(|a, b| a.wasm_checked_div(b)).to_cf()?, - I32DivU => self.stack.values.calculate_same::(|a, b| a.checked_div(b).ok_or_else(trap_0)).to_cf()?, - I64DivU => self.stack.values.calculate_same::(|a, b| a.checked_div(b).ok_or_else(trap_0)).to_cf()?, - I32RemS => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - I64RemS => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - I32RemU => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - I64RemU => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - - I32And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - I64And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - I32Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - I64Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - I32Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - I64Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - I32Shl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shl(b))).to_cf()?, - I64Shl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shl(b))).to_cf()?, - I32ShrS => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I64ShrS => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I32ShrU => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I64ShrU => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I32Rotl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotl(b))).to_cf()?, - I64Rotl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotl(b))).to_cf()?, - I32Rotr => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotr(b))).to_cf()?, - I64Rotr => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotr(b))).to_cf()?, - - I32Clz => self.stack.values.replace_top_same::(|v| Ok(v.leading_zeros() as i32)).to_cf()?, - I64Clz => self.stack.values.replace_top_same::(|v| Ok(i64::from(v.leading_zeros()))).to_cf()?, - I32Ctz => self.stack.values.replace_top_same::(|v| Ok(v.trailing_zeros() as i32)).to_cf()?, - I64Ctz => self.stack.values.replace_top_same::(|v| Ok(i64::from(v.trailing_zeros()))).to_cf()?, - I32Popcnt => self.stack.values.replace_top_same::(|v| Ok(v.count_ones() as i32)).to_cf()?, - I64Popcnt => self.stack.values.replace_top_same::(|v| Ok(i64::from(v.count_ones()))).to_cf()?, - - F32ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F32ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F64ConvertI32S => self.stack.values.replace_top::(|v| Ok(f64::from(v))).to_cf()?, - F64ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, - F32ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F32ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F64ConvertI32U => self.stack.values.replace_top::(|v| Ok(f64::from(v))).to_cf()?, - F64ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, - - I32Extend8S => self.stack.values.replace_top_same::(|v| Ok(i32::from(v as i8))).to_cf()?, - I32Extend16S => self.stack.values.replace_top_same::(|v| Ok(i32::from(v as i16))).to_cf()?, - I64Extend8S => self.stack.values.replace_top_same::(|v| Ok(i64::from(v as i8))).to_cf()?, - I64Extend16S => self.stack.values.replace_top_same::(|v| Ok(i64::from(v as i16))).to_cf()?, - I64Extend32S => self.stack.values.replace_top_same::(|v| Ok(i64::from(v as i32))).to_cf()?, - I64ExtendI32U => self.stack.values.replace_top::(|v| Ok(i64::from(v))).to_cf()?, - I64ExtendI32S => self.stack.values.replace_top::(|v| Ok(i64::from(v))).to_cf()?, - I32WrapI64 => self.stack.values.replace_top::(|v| Ok(v as i32)).to_cf()?, - - F32DemoteF64 => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F64PromoteF32 => self.stack.values.replace_top::(|v| Ok(f64::from(v))).to_cf()?, - - F32Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - F64Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - F32Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - F64Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - F32Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - F64Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - F32Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - F64Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - F32Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - F64Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - F32Nearest => self.stack.values.replace_top_same::(|v| Ok(v.tw_nearest())).to_cf()?, - F64Nearest => self.stack.values.replace_top_same::(|v| Ok(v.tw_nearest())).to_cf()?, - F32Sqrt => self.stack.values.replace_top_same::(|v| Ok(v.sqrt())).to_cf()?, - F64Sqrt => self.stack.values.replace_top_same::(|v| Ok(v.sqrt())).to_cf()?, - F32Min => self.stack.values.calculate_same::(|a, b| Ok(a.tw_minimum(b))).to_cf()?, - F64Min => self.stack.values.calculate_same::(|a, b| Ok(a.tw_minimum(b))).to_cf()?, - F32Max => self.stack.values.calculate_same::(|a, b| Ok(a.tw_maximum(b))).to_cf()?, - F64Max => self.stack.values.calculate_same::(|a, b| Ok(a.tw_maximum(b))).to_cf()?, - F32Copysign => self.stack.values.calculate_same::(|a, b| Ok(a.copysign(b))).to_cf()?, - F64Copysign => self.stack.values.calculate_same::(|a, b| Ok(a.copysign(b))).to_cf()?, - - I32TruncF32S => checked_conv_float!(f32, i32, self), - I32TruncF64S => checked_conv_float!(f64, i32, self), - I32TruncF32U => checked_conv_float!(f32, u32, i32, self), - I32TruncF64U => checked_conv_float!(f64, u32, i32, self), - I64TruncF32S => checked_conv_float!(f32, i64, self), - I64TruncF64S => checked_conv_float!(f64, i64, self), - I64TruncF32U => checked_conv_float!(f32, u64, i64, self), - I64TruncF64U => checked_conv_float!(f64, u64, i64, self), - - TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, - TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, - TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, - TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, - TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, - TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, - - I32TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32)).to_cf()?, - I32TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32)).to_cf()?, - I32TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32)).to_cf()?, - I32TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32)).to_cf()?, - I64TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64)).to_cf()?, - I64TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64)).to_cf()?, - I64TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64)).to_cf()?, - I64TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64)).to_cf()?, - - LocalCopy32(from, to) => self.exec_local_copy::(*from, *to), - LocalCopy64(from, to) => self.exec_local_copy::(*from, *to), - LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), - LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), - - #[cfg(feature = "unstable-simd")] V128Not => self.stack.values.replace_top_same::(|v| Ok(!v)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128AndNot => self.stack.values.calculate_same::(|a, b| Ok(a & (!b))).to_cf()?, - #[cfg(feature = "unstable-simd")] V128Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128Bitselect => self.stack.values.calculate_same_3::(|v1, v2, c| Ok((v1 & c) | (v2 & !c))).to_cf()?, - #[cfg(feature = "unstable-simd")] V128AnyTrue => self.stack.values.replace_top::(|v| Ok((v.reduce_or() != 0) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16Swizzle => self.stack.values.calculate_same::(|a, s| Ok(a.swizzle_dyn(s))).to_cf()?, - - #[cfg(feature = "unstable-simd")] V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, - #[cfg(feature = "unstable-simd")] V128Load8x8S(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load8x8U(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load16x4S(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load16x4U(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load32x2S(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load32x2U(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load8Splat(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load16Splat(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load32Splat(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load64Splat(_arg) => unimplemented!(), - - #[cfg(feature = "unstable-simd")] V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, - - #[cfg(feature = "unstable-simd")] V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. - #[cfg(feature = "unstable-simd")] V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - let bytes = v.to_le_bytes(); - u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - })?, - #[cfg(feature = "unstable-simd")] V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - let bytes = v.to_le_bytes(); - u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 0, 0, 0, 0, 0, 0, 0, 0]) - })?, - - #[cfg(feature = "unstable-simd")] V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].to_le_bytes().into()), - - #[cfg(feature = "unstable-simd")] I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - - #[cfg(feature = "unstable-simd")] V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - #[cfg(feature = "unstable-simd")] I8x16ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2ReplaceLane(_lane) => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] F32x4Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i64))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i64))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u32))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u64))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16NarrowI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I8x16NarrowI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8NarrowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8NarrowI32x4U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16AvgrU => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8AvgrU => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I16x8ExtAddPairwiseI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtAddPairwiseI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtAddPairwiseI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtAddPairwiseI16x8U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I16x8ExtMulLowI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtMulLowI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtMulHighI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtMulHighI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulLowI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulLowI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulHighI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulHighI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulLowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulLowI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulHighI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulHighI32x4U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I16x8ExtendLowI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtendLowI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtendHighI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtendHighI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendLowI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendLowI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendHighI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendHighI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendLowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendLowI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendHighI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendHighI32x4U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones())).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16Shuffle(_idx) => unimplemented!(), - - #[cfg(feature = "unstable-simd")] - I16x8Q15MulrSatS => self.stack.values.calculate_same::(|a, b| { - let subq15mulr = |a,b| { - let a = a as i32; - let b = b as i32; - let r = (a * b + 0x4000) >> 15; - if r > i16::MAX as i32 { - i16::MAX - } else if r < i16::MIN as i32 { - i16::MIN - } else { - r as i16 - } - }; - Ok(Simd::::from_array([ - subq15mulr(a[0], b[0]), - subq15mulr(a[1], b[1]), - subq15mulr(a[2], b[2]), - subq15mulr(a[3], b[3]), - subq15mulr(a[4], b[4]), - subq15mulr(a[5], b[5]), - subq15mulr(a[6], b[6]), - subq15mulr(a[7], b[7]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - I32x4DotI16x8S => self.stack.values.calculate::(|a, b| { - Ok(Simd::::from_array([ - i32::from(a[0] * b[0] + a[1] * b[1]), - i32::from(a[2] * b[2] + a[3] * b[3]), - i32::from(a[4] * b[4] + a[5] * b[5]), - i32::from(a[6] * b[6] + a[7] * b[7]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] F32x4Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f32x4(v.sqrt()))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f64x2(v.sqrt()))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a + b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a + b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a - b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a - b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a * b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a * b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a / b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a / b))).to_cf()?, - #[cfg(feature = "unstable-simd")] - F32x4Min => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_minimum(a[0]), - b[1].tw_minimum(a[1]), - b[2].tw_minimum(a[2]), - b[3].tw_minimum(a[3]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2Min => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_minimum(a[0]), - b[1].tw_minimum(a[1]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F32x4Max => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_maximum(a[0]), - b[1].tw_maximum(a[1]), - b[2].tw_maximum(a[2]), - b[3].tw_maximum(a[3]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2Max => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_maximum(a[0]), - b[1].tw_maximum(a[1]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F32x4PMin => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] < a[0] { b[0] } else { a[0]}, - if b[1] < a[1] { b[1] } else { a[1]}, - if b[2] < a[2] { b[2] } else { a[2]}, - if b[3] < a[3] { b[3] } else { a[3]}, - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F32x4PMax => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] > a[0] { b[0] } else { a[0]}, - if b[1] > a[1] { b[1] } else { a[1]}, - if b[2] > a[2] { b[2] } else { a[2]}, - if b[3] > a[3] { b[3] } else { a[3]}, - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2PMin => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] < a[0] { b[0] } else { a[0]}, - if b[1] < a[1] { b[1] } else { a[1]}, - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2PMax => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] > a[0] { b[0] } else { a[0]}, - if b[1] > a[1] { b[1] } else { a[1]}, - ])) - }).to_cf()?, - - // not correct - #[cfg(feature = "unstable-simd")] I32x4TruncSatF32x4S => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4TruncSatF32x4U => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4ConvertI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4ConvertI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2ConvertLowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2ConvertLowI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4DemoteF64x2Zero => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2PromoteLowF32x4 => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4TruncSatF64x2SZero => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4TruncSatF64x2UZero => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16RelaxedSwizzle => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF64x2SZero => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF64x2UZero => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedMadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedNmadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedMadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedNmadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] I8x16RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedMin => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedMax => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedMin => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedMax => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8RelaxedQ15mulrS => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8RelaxedDotI8x16I7x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedDotI8x16I7x16AddS => unimplemented!(), - - #[allow(unreachable_patterns)] - i => return ControlFlow::Break(Some(Error::UnsupportedFeature(format!("unimplemented opcode: {i:?}")))), - }; + fn exec(&mut self) -> Result> { + macro_rules! stack_op { + (unary $ty:ty, |$v:ident| $expr:expr) => {{ + let $v = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr)?; + }}; + (binary $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$ty>(); + let $lhs = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr)?; + }}; + (binary try $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$ty>(); + let $lhs = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr?)?; + }}; + (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => {{ + let $v = self.store.stack.values.pop::<$from>(); + self.store.stack.values.push::<$to>($expr)?; + }}; + (binary $from:ty => $to:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$from>(); + let $lhs = self.store.stack.values.pop::<$from>(); + self.store.stack.values.push::<$to>($expr)?; + }}; + (binary_into2 $from:ty => $to:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$from>(); + let $lhs = self.store.stack.values.pop::<$from>(); + let out = $expr; + self.store.stack.values.push::<$to>(out.0)?; + self.store.stack.values.push::<$to>(out.1)?; + }}; + (binary $lhs_ty:ty, $rhs_ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { + stack_op!(binary $lhs_ty, $rhs_ty => $rhs_ty, |$lhs, $rhs| $expr) + }; + (binary $lhs_ty:ty, $rhs_ty:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$rhs_ty>(); + let $lhs = self.store.stack.values.pop::<$lhs_ty>(); + self.store.stack.values.push::<$res>($expr)?; + }}; + (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => {{ + let $c = self.store.stack.values.pop::<$ty>(); + let $b = self.store.stack.values.pop::<$ty>(); + let $a = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr)?; + }}; + (quaternary_into2 $from:ty => $to:ty, |$a:ident, $b:ident, $c:ident, $d:ident| $expr:expr) => {{ + let $d = self.store.stack.values.pop::<$from>(); + let $c = self.store.stack.values.pop::<$from>(); + let $b = self.store.stack.values.pop::<$from>(); + let $a = self.store.stack.values.pop::<$from>(); + let out = $expr; + self.store.stack.values.push::<$to>(out.0)?; + self.store.stack.values.push::<$to>(out.1)?; + }}; + (local_set_pop $ty:ty, $local_index:expr) => {{ + let val = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.local_set(&self.cf, *$local_index, val); + }}; + (local_tee $ty:ty, $local_index:expr) => {{ + let val = self.store.stack.values.peek::<$ty>(); + self.store.stack.values.local_set(&self.cf, *$local_index, val); + }}; + } - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) + for _ in 0..ITERATIONS { + use tinywasm_types::Instruction::*; + + let next = match self.func.instructions.0.get(self.cf.instr_ptr as usize) { + Some(instr) => instr, + None => unreachable!( + "Instruction pointer out of bounds: {} ({} instructions)", + self.cf.instr_ptr, + self.func.instructions.0.len() + ), + }; + + #[rustfmt::skip] + match next { + Nop => {} + Unreachable => return Err(Trap::Unreachable.into()), + Drop32 => self.store.stack.values.drop::(), + Drop64 => self.store.stack.values.drop::(), + Drop128 => self.store.stack.values.drop::(), + DropRef => self.store.stack.values.drop::(), + Select32 => self.store.stack.values.select::()?, + Select64 => self.store.stack.values.select::()?, + Select128 => self.store.stack.values.select::()?, + SelectRef => self.store.stack.values.select::()?, + SelectMulti(counts) => self.store.stack.values.select_multi(*counts), + Call(v) => { self.exec_call_direct::(*v)?; continue; } + CallSelf => { self.exec_call_self::()?; continue; } + CallIndirect(ty, table) => { self.exec_call_indirect::(*ty, *table)?; continue; } + ReturnCall(v) => { self.exec_call_direct::(*v)?; continue; } + ReturnCallSelf => { self.exec_call_self::()?; continue; } + ReturnCallIndirect(ty, table) => { self.exec_call_indirect::(*ty, *table)?; continue; } + Jump(ip) => { self.exec_jump(*ip); continue; } + JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { continue; }, + JumpIfNonZero(ip) => if self.exec_jump_if_non_zero(*ip) { continue; }, + DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { + let mut base = self.cf.stack_base(); base.s32 += *base32 as u32; base.s64 += *base64 as u32; base.s128 += *base128 as u32; base.sref += *base_ref as u32; + self.store.stack.values.truncate_keep_counts(base, ValueCounts { c32: *keep32 as u16, c64: *keep64 as u16, c128: *keep128 as u16, cref: *keep_ref as u16 }); + } + DropKeep32(base, keep) => self.store.stack.values.stack_32.truncate_keep((self.cf.stack_base().s32 + *base as u32) as usize, *keep as usize), + DropKeep64(base, keep) => self.store.stack.values.stack_64.truncate_keep((self.cf.stack_base().s64 + *base as u32) as usize, *keep as usize), + DropKeep128(base, keep) => self.store.stack.values.stack_128.truncate_keep((self.cf.stack_base().s128 + *base as u32) as usize, *keep as usize), + DropKeepRef(base, keep) => self.store.stack.values.stack_ref.truncate_keep((self.cf.stack_base().sref + *base as u32) as usize, *keep as usize), + BranchTable(default_ip, start, len) => { self.exec_branch_table(*default_ip, *start, *len); continue; } + Return => { if self.exec_return() { return Ok(Some(())); } continue; } + LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalGet128(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalGetRef(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalSet32(local_index) => stack_op!(local_set_pop Value32, local_index), + LocalSet64(local_index) => stack_op!(local_set_pop Value64, local_index), + LocalSet128(local_index) => stack_op!(local_set_pop Value128, local_index), + LocalSetRef(local_index) => stack_op!(local_set_pop ValueRef, local_index), + LocalCopy32(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopy64(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopy128(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopyRef(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + I32AddLocals(a, b) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)))?, + I64AddLocals(a, b) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)))?, + I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), + I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), + LocalAddConst32(local_index, c) => self.store.stack.values.local_update::(&self.cf, *local_index, |local| *local = local.wrapping_add(*c as u32)), + LocalAddConst64(local_index, c) => self.store.stack.values.local_update::(&self.cf, *local_index, |local| *local = local.wrapping_add(*c as u64)), + LocalSetConst32(local_index, c) => self.store.stack.values.local_set::(&self.cf, *local_index, *c), + LocalSetConst64(local_index, c) => self.store.stack.values.local_set::(&self.cf, *local_index, *c), + I32StoreLocalLocal(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local, |v| v)?, + I64StoreLocalLocal(m, addr_local, value_local) =>self.exec_store_local_local::(*m, *addr_local, *value_local, |v| v)?, + I32LoadLocalTee(m, addr_local, dst_local) => self.exec_i32_load_local_tee(*m, *addr_local, *dst_local)?, + I32LoadLocalSet(m, addr_local, dst_local) => self.exec_i32_load_local_set(*m, *addr_local, *dst_local)?, + I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), + I64XorRotlConstTee(c, local_index) => { + stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)); + stack_op!(local_tee i64, local_index); + } + LocalTee32(local_index) => stack_op!(local_tee Value32, local_index), + LocalTee64(local_index) => stack_op!(local_tee Value64, local_index), + LocalTee128(local_index) => stack_op!(local_tee Value128, local_index), + LocalTeeRef(local_index) => stack_op!(local_tee ValueRef, local_index), + GlobalGet(global_index) => self.exec_global_get(*global_index)?, + GlobalSet32(global_index) => self.exec_global_set::(*global_index), + GlobalSet64(global_index) => self.exec_global_set::(*global_index), + GlobalSet128(global_index) => self.exec_global_set::(*global_index), + GlobalSetRef(global_index) => self.exec_global_set::(*global_index), + I32Const(val) => self.exec_const(*val)?, + I64Const(val) => self.exec_const(*val)?, + F32Const(val) => self.exec_const(*val)?, + F64Const(val) => self.exec_const(*val)?, + I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), + I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), + I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), + I64Eq => stack_op!(binary i64 => i32, |a, b| i32::from(a == b)), + F32Eq => stack_op!(binary f32 => i32, |a, b| i32::from(a == b)), + F64Eq => stack_op!(binary f64 => i32, |a, b| i32::from(a == b)), + I32Ne => stack_op!(binary i32, |a, b| i32::from(a != b)), + I64Ne => stack_op!(binary i64 => i32, |a, b| i32::from(a != b)), + F32Ne => stack_op!(binary f32 => i32, |a, b| i32::from(a != b)), + F64Ne => stack_op!(binary f64 => i32, |a, b| i32::from(a != b)), + I32LtS => stack_op!(binary i32, |a, b| i32::from(a < b)), + I64LtS => stack_op!(binary i64 => i32, |a, b| i32::from(a < b)), + I32LtU => stack_op!(binary u32 => i32, |a, b| i32::from(a < b)), + I64LtU => stack_op!(binary u64 => i32, |a, b| i32::from(a < b)), + F32Lt => stack_op!(binary f32 => i32, |a, b| i32::from(a < b)), + F64Lt => stack_op!(binary f64 => i32, |a, b| i32::from(a < b)), + I32LeS => stack_op!(binary i32, |a, b| i32::from(a <= b)), + I64LeS => stack_op!(binary i64 => i32, |a, b| i32::from(a <= b)), + I32LeU => stack_op!(binary u32 => i32, |a, b| i32::from(a <= b)), + I64LeU => stack_op!(binary u64 => i32, |a, b| i32::from(a <= b)), + F32Le => stack_op!(binary f32 => i32, |a, b| i32::from(a <= b)), + F64Le => stack_op!(binary f64 => i32, |a, b| i32::from(a <= b)), + I32GeS => stack_op!(binary i32, |a, b| i32::from(a >= b)), + I64GeS => stack_op!(binary i64 => i32, |a, b| i32::from(a >= b)), + I32GeU => stack_op!(binary u32 => i32, |a, b| i32::from(a >= b)), + I64GeU => stack_op!(binary u64 => i32, |a, b| i32::from(a >= b)), + F32Ge => stack_op!(binary f32 => i32, |a, b| i32::from(a >= b)), + F64Ge => stack_op!(binary f64 => i32, |a, b| i32::from(a >= b)), + I32GtS => stack_op!(binary i32, |a, b| i32::from(a > b)), + I64GtS => stack_op!(binary i64 => i32, |a, b| i32::from(a > b)), + I32GtU => stack_op!(binary u32 => i32, |a, b| i32::from(a > b)), + I64GtU => stack_op!(binary u64 => i32, |a, b| i32::from(a > b)), + F32Gt => stack_op!(binary f32 => i32, |a, b| i32::from(a > b)), + F64Gt => stack_op!(binary f64 => i32, |a, b| i32::from(a > b)), + I32Add => stack_op!(binary i32, |a, b| a.wrapping_add(b)), + I64Add => stack_op!(binary i64, |a, b| a.wrapping_add(b)), + F32Add => stack_op!(binary f32, |a, b| a + b), + F64Add => stack_op!(binary f64, |a, b| a + b), + I32Sub => stack_op!(binary i32, |a, b| a.wrapping_sub(b)), + I64Sub => stack_op!(binary i64, |a, b| a.wrapping_sub(b)), + F32Sub => stack_op!(binary f32, |a, b| a - b), + F64Sub => stack_op!(binary f64, |a, b| a - b), + F32Div => stack_op!(binary f32, |a, b| a / b), + F64Div => stack_op!(binary f64, |a, b| a / b), + I32Mul => stack_op!(binary i32, |a, b| a.wrapping_mul(b)), + I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), + F32Mul => stack_op!(binary f32, |a, b| a * b), + F64Mul => stack_op!(binary f64, |a, b| a * b), + I32DivS => stack_op!(binary try i32, |a, b| a.wasm_checked_div(b)), + I64DivS => stack_op!(binary try i64, |a, b| a.wasm_checked_div(b)), + I32DivU => stack_op!(binary try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I64DivU => stack_op!(binary try u64, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I32RemS => stack_op!(binary try i32, |a, b| a.checked_wrapping_rem(b)), + I64RemS => stack_op!(binary try i64, |a, b| a.checked_wrapping_rem(b)), + I32RemU => stack_op!(binary try u32, |a, b| a.checked_wrapping_rem(b)), + I64RemU => stack_op!(binary try u64, |a, b| a.checked_wrapping_rem(b)), + I32And => stack_op!(binary i32, |a, b| a & b), + I64And => stack_op!(binary i64, |a, b| a & b), + I32Or => stack_op!(binary i32, |a, b| a | b), + I64Or => stack_op!(binary i64, |a, b| a | b), + I32Xor => stack_op!(binary i32, |a, b| a ^ b), + I64Xor => stack_op!(binary i64, |a, b| a ^ b), + I32Shl => stack_op!(binary i32, |a, b| a.wasm_shl(b)), + I64Shl => stack_op!(binary i64, |a, b| a.wasm_shl(b)), + I32ShrS => stack_op!(binary i32, |a, b| a.wasm_shr(b)), + I64ShrS => stack_op!(binary i64, |a, b| a.wasm_shr(b)), + I32ShrU => stack_op!(binary u32, |a, b| a.wasm_shr(b)), + I64ShrU => stack_op!(binary u64, |a, b| a.wasm_shr(b)), + I32Rotl => stack_op!(binary i32, |a, b| a.wasm_rotl(b)), + I64Rotl => stack_op!(binary i64, |a, b| a.wasm_rotl(b)), + I32Rotr => stack_op!(binary i32, |a, b| a.wasm_rotr(b)), + I64Rotr => stack_op!(binary i64, |a, b| a.wasm_rotr(b)), + I64Add128 => stack_op!(quaternary_into2 i64 => i64, |a_lo, a_hi, b_lo, b_hi| { + let lo = a_lo.wrapping_add(b_lo); + let carry = u64::from((lo as u64) < (a_lo as u64)); + let hi = a_hi.wrapping_add(b_hi).wrapping_add(carry as i64); + (lo, hi) + }), + I64Sub128 => stack_op!(quaternary_into2 i64 => i64, |a_lo, a_hi, b_lo, b_hi| { + let lo = a_lo.wrapping_sub(b_lo); + let borrow = u64::from((a_lo as u64) < (b_lo as u64)); + let hi = a_hi.wrapping_sub(b_hi).wrapping_sub(borrow as i64); + (lo, hi) + }), + I64MulWideS => stack_op!(binary_into2 i64 => i64, |a, b| { + let product = (a as i128).wrapping_mul(b as i128); + (product as i64, (product >> 64) as i64) + }), + I64MulWideU => stack_op!(binary_into2 i64 => i64, |a, b| { + let product = (a as u64 as u128).wrapping_mul(b as u64 as u128); + (product as u64 as i64, (product >> 64) as u64 as i64) + }), + I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), + I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), + I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), + I64Ctz => stack_op!(unary i64, |v| i64::from(v.trailing_zeros())), + I32Popcnt => stack_op!(unary i32, |v| v.count_ones() as i32), + I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), + + // Reference types + RefFunc(func_idx) => self.exec_const::(Some(*func_idx))?, + RefNull(_) => self.exec_const::(None)?, + RefIsNull => self.exec_ref_is_null()?, + MemorySize(addr) => self.exec_memory_size(*addr)?, + MemoryGrow(addr) => self.exec_memory_grow(*addr)?, + + // Bulk memory operations + MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, + MemoryFill(addr) => self.exec_memory_fill(*addr)?, + MemoryFillImm(addr, val, size) => self.exec_memory_fill_imm(*addr, *val, *size)?, + MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, + DataDrop(data_index) => self.store.state.get_data_mut(self.module.resolve_data_addr(*data_index)).drop(), + ElemDrop(elem_index) => self.store.state.get_elem_mut(self.module.resolve_elem_addr(*elem_index)).drop(), + + // Table instructions + TableGet(table_idx) => self.exec_table_get(*table_idx)?, + TableSet(table_idx) => self.exec_table_set(*table_idx)?, + TableSize(table_idx) => self.exec_table_size(*table_idx)?, + TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx)?, + TableGrow(table_idx) => self.exec_table_grow(*table_idx)?, + TableFill(table_idx) => self.exec_table_fill(*table_idx)?, + TableCopy { from, to } => self.exec_table_copy(*from, *to)?, + + // Core memory load/store operations + I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + F64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + I32Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, + I32Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, + I64Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, + I64Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, + I64Store32(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i32)?, + I32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + I64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + F32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + F64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + I32Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + + // Numeric conversion operations + F32ConvertI32S => stack_op!(unary i32 => f32, |v| v as f32), + F32ConvertI64S => stack_op!(unary i64 => f32, |v| v as f32), + F64ConvertI32S => stack_op!(unary i32 => f64, |v| f64::from(v)), + F64ConvertI64S => stack_op!(unary i64 => f64, |v| v as f64), + F32ConvertI32U => stack_op!(unary u32 => f32, |v| v as f32), + F32ConvertI64U => stack_op!(unary u64 => f32, |v| v as f32), + F64ConvertI32U => stack_op!(unary u32 => f64, |v| f64::from(v)), + F64ConvertI64U => stack_op!(unary u64 => f64, |v| v as f64), + + // Sign-extension operations + I32Extend8S => stack_op!(unary i32, |v| i32::from(v as i8)), + I32Extend16S => stack_op!(unary i32, |v| i32::from(v as i16)), + I64Extend8S => stack_op!(unary i64, |v| i64::from(v as i8)), + I64Extend16S => stack_op!(unary i64, |v| i64::from(v as i16)), + I64Extend32S => stack_op!(unary i64, |v| i64::from(v as i32)), + I64ExtendI32U => stack_op!(unary u32 => i64, |v| i64::from(v)), + I64ExtendI32S => stack_op!(unary i32 => i64, |v| i64::from(v)), + I32WrapI64 => stack_op!(unary i64 => i32, |v| v as i32), + F32DemoteF64 => stack_op!(unary f64 => f32, |v| v as f32), + F64PromoteF32 => stack_op!(unary f32 => f64, |v| f64::from(v)), + F32Abs => stack_op!(unary f32, |v| v.abs()), + F64Abs => stack_op!(unary f64, |v| v.abs()), + F32Neg => stack_op!(unary f32, |v| -v), + F64Neg => stack_op!(unary f64, |v| -v), + F32Ceil => stack_op!(unary f32, |v| v.ceil()), + F64Ceil => stack_op!(unary f64, |v| v.ceil()), + F32Floor => stack_op!(unary f32, |v| v.floor()), + F64Floor => stack_op!(unary f64, |v| v.floor()), + F32Trunc => stack_op!(unary f32, |v| v.trunc()), + F64Trunc => stack_op!(unary f64, |v| v.trunc()), + F32Nearest => stack_op!(unary f32, |v| v.tw_nearest()), + F64Nearest => stack_op!(unary f64, |v| v.tw_nearest()), + F32Sqrt => stack_op!(unary f32, |v| v.sqrt()), + F64Sqrt => stack_op!(unary f64, |v| v.sqrt()), + F32Min => stack_op!(binary f32, |a, b| a.tw_minimum(b)), + F64Min => stack_op!(binary f64, |a, b| a.tw_minimum(b)), + F32Max => stack_op!(binary f32, |a, b| a.tw_maximum(b)), + F64Max => stack_op!(binary f64, |a, b| a.tw_maximum(b)), + F32Copysign => stack_op!(binary f32, |a, b| a.copysign(b)), + F64Copysign => stack_op!(binary f64, |a, b| a.copysign(b)), + I32TruncF32S => checked_conv_float!(f32, i32, self), + I32TruncF64S => checked_conv_float!(f64, i32, self), + I32TruncF32U => checked_conv_float!(f32, u32, i32, self), + I32TruncF64U => checked_conv_float!(f64, u32, i32, self), + I64TruncF32S => checked_conv_float!(f32, i64, self), + I64TruncF64S => checked_conv_float!(f64, i64, self), + I64TruncF32U => checked_conv_float!(f32, u64, i64, self), + I64TruncF64U => checked_conv_float!(f64, u64, i64, self), + + // Non-trapping float-to-int conversions + I32TruncSatF32S => stack_op!(unary f32 => i32, |v| v.trunc() as i32), + I32TruncSatF32U => stack_op!(unary f32 => u32, |v| v.trunc() as u32), + I32TruncSatF64S => stack_op!(unary f64 => i32, |v| v.trunc() as i32), + I32TruncSatF64U => stack_op!(unary f64 => u32, |v| v.trunc() as u32), + I64TruncSatF32S => stack_op!(unary f32 => i64, |v| v.trunc() as i64), + I64TruncSatF32U => stack_op!(unary f32 => u64, |v| v.trunc() as u64), + I64TruncSatF64S => stack_op!(unary f64 => i64, |v| v.trunc() as i64), + I64TruncSatF64U => stack_op!(unary f64 => u64, |v| v.trunc() as u64), + + // SIMD extension + V128Not => stack_op!(unary Value128, |v| v.v128_not()), + V128And => stack_op!(binary Value128, |a, b| a.v128_and(b)), + V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), + V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), + V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), + V128Bitselect => stack_op!(ternary Value128, |a, b, c| Value128::v128_bitselect(a, b, c)), + V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), + I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), + I8x16RelaxedSwizzle => stack_op!(binary Value128, |a, s| a.i8x16_relaxed_swizzle(s)), + V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, + V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, + V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, + V128Load16x4S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_s(v.to_le_bytes()))?, + V128Load16x4U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_u(v.to_le_bytes()))?, + V128Load32x2S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_s(v.to_le_bytes()))?, + V128Load32x2U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_u(v.to_le_bytes()))?, + V128Load8Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i8)?, + V128Load16Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i16)?, + V128Load32Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i32)?, + V128Load64Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i64)?, + V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, + V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, + V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, + V128Const(arg) => self.exec_const::(self.func.data.v128_constants[*arg as usize].into())?, + I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), + I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), + I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), + I16x8ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u16(*lane) as i32), + I32x4ExtractLane(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i32(*lane)), + I64x2ExtractLane(lane) => stack_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), + F32x4ExtractLane(lane) => stack_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), + F64x2ExtractLane(lane) => stack_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), + V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + I8x16ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), + I16x8ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), + I32x4ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), + I64x2ReplaceLane(lane) => stack_op!(binary i64, Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), + F32x4ReplaceLane(lane) => stack_op!(binary f32, Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), + F64x2ReplaceLane(lane) => stack_op!(binary f64, Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), + I8x16Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), + I16x8Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), + I32x4Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), + I64x2Splat => stack_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), + F32x4Splat => stack_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), + F64x2Splat => stack_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), + I8x16Eq => stack_op!(binary Value128, |a, b| a.i8x16_eq(b)), + I16x8Eq => stack_op!(binary Value128, |a, b| a.i16x8_eq(b)), + I32x4Eq => stack_op!(binary Value128, |a, b| a.i32x4_eq(b)), + I64x2Eq => stack_op!(binary Value128, |a, b| a.i64x2_eq(b)), + F32x4Eq => stack_op!(binary Value128, |a, b| a.f32x4_eq(b)), + F64x2Eq => stack_op!(binary Value128, |a, b| a.f64x2_eq(b)), + I8x16Ne => stack_op!(binary Value128, |a, b| a.i8x16_ne(b)), + I16x8Ne => stack_op!(binary Value128, |a, b| a.i16x8_ne(b)), + I32x4Ne => stack_op!(binary Value128, |a, b| a.i32x4_ne(b)), + I64x2Ne => stack_op!(binary Value128, |a, b| a.i64x2_ne(b)), + F32x4Ne => stack_op!(binary Value128, |a, b| a.f32x4_ne(b)), + F64x2Ne => stack_op!(binary Value128, |a, b| a.f64x2_ne(b)), + I8x16LtS => stack_op!(binary Value128, |a, b| a.i8x16_lt_s(b)), + I16x8LtS => stack_op!(binary Value128, |a, b| a.i16x8_lt_s(b)), + I32x4LtS => stack_op!(binary Value128, |a, b| a.i32x4_lt_s(b)), + I64x2LtS => stack_op!(binary Value128, |a, b| a.i64x2_lt_s(b)), + I8x16LtU => stack_op!(binary Value128, |a, b| a.i8x16_lt_u(b)), + I16x8LtU => stack_op!(binary Value128, |a, b| a.i16x8_lt_u(b)), + I32x4LtU => stack_op!(binary Value128, |a, b| a.i32x4_lt_u(b)), + F32x4Lt => stack_op!(binary Value128, |a, b| a.f32x4_lt(b)), + F64x2Lt => stack_op!(binary Value128, |a, b| a.f64x2_lt(b)), + F32x4Gt => stack_op!(binary Value128, |a, b| a.f32x4_gt(b)), + F64x2Gt => stack_op!(binary Value128, |a, b| a.f64x2_gt(b)), + I8x16GtS => stack_op!(binary Value128, |a, b| a.i8x16_gt_s(b)), + I16x8GtS => stack_op!(binary Value128, |a, b| a.i16x8_gt_s(b)), + I32x4GtS => stack_op!(binary Value128, |a, b| a.i32x4_gt_s(b)), + I64x2GtS => stack_op!(binary Value128, |a, b| a.i64x2_gt_s(b)), + I64x2LeS => stack_op!(binary Value128, |a, b| a.i64x2_le_s(b)), + F32x4Le => stack_op!(binary Value128, |a, b| a.f32x4_le(b)), + F64x2Le => stack_op!(binary Value128, |a, b| a.f64x2_le(b)), + I8x16GtU => stack_op!(binary Value128, |a, b| a.i8x16_gt_u(b)), + I16x8GtU => stack_op!(binary Value128, |a, b| a.i16x8_gt_u(b)), + I32x4GtU => stack_op!(binary Value128, |a, b| a.i32x4_gt_u(b)), + F32x4Ge => stack_op!(binary Value128, |a, b| a.f32x4_ge(b)), + F64x2Ge => stack_op!(binary Value128, |a, b| a.f64x2_ge(b)), + I8x16LeS => stack_op!(binary Value128, |a, b| a.i8x16_le_s(b)), + I16x8LeS => stack_op!(binary Value128, |a, b| a.i16x8_le_s(b)), + I32x4LeS => stack_op!(binary Value128, |a, b| a.i32x4_le_s(b)), + I8x16LeU => stack_op!(binary Value128, |a, b| a.i8x16_le_u(b)), + I16x8LeU => stack_op!(binary Value128, |a, b| a.i16x8_le_u(b)), + I32x4LeU => stack_op!(binary Value128, |a, b| a.i32x4_le_u(b)), + I8x16GeS => stack_op!(binary Value128, |a, b| a.i8x16_ge_s(b)), + I16x8GeS => stack_op!(binary Value128, |a, b| a.i16x8_ge_s(b)), + I32x4GeS => stack_op!(binary Value128, |a, b| a.i32x4_ge_s(b)), + I64x2GeS => stack_op!(binary Value128, |a, b| a.i64x2_ge_s(b)), + I8x16GeU => stack_op!(binary Value128, |a, b| a.i8x16_ge_u(b)), + I16x8GeU => stack_op!(binary Value128, |a, b| a.i16x8_ge_u(b)), + I32x4GeU => stack_op!(binary Value128, |a, b| a.i32x4_ge_u(b)), + I8x16Abs => stack_op!(unary Value128, |a| a.i8x16_abs()), + I16x8Abs => stack_op!(unary Value128, |a| a.i16x8_abs()), + I32x4Abs => stack_op!(unary Value128, |a| a.i32x4_abs()), + I64x2Abs => stack_op!(unary Value128, |a| a.i64x2_abs()), + I8x16Neg => stack_op!(unary Value128, |a| a.i8x16_neg()), + I16x8Neg => stack_op!(unary Value128, |a| a.i16x8_neg()), + I32x4Neg => stack_op!(unary Value128, |a| a.i32x4_neg()), + I64x2Neg => stack_op!(unary Value128, |a| a.i64x2_neg()), + I8x16AllTrue => stack_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), + I16x8AllTrue => stack_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), + I32x4AllTrue => stack_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), + I64x2AllTrue => stack_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), + I8x16Bitmask => stack_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), + I16x8Bitmask => stack_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), + I32x4Bitmask => stack_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), + I64x2Bitmask => stack_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), + I8x16Shl => stack_op!(binary i32, Value128, |a, b| b.i8x16_shl(a as u32)), + I16x8Shl => stack_op!(binary i32, Value128, |a, b| b.i16x8_shl(a as u32)), + I32x4Shl => stack_op!(binary i32, Value128, |a, b| b.i32x4_shl(a as u32)), + I64x2Shl => stack_op!(binary i32, Value128, |a, b| b.i64x2_shl(a as u32)), + I8x16ShrS => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_s(a as u32)), + I16x8ShrS => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_s(a as u32)), + I32x4ShrS => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_s(a as u32)), + I64x2ShrS => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_s(a as u32)), + I8x16ShrU => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_u(a as u32)), + I16x8ShrU => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_u(a as u32)), + I32x4ShrU => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_u(a as u32)), + I64x2ShrU => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_u(a as u32)), + I8x16Add => stack_op!(binary Value128, |a, b| a.i8x16_add(b)), + I16x8Add => stack_op!(binary Value128, |a, b| a.i16x8_add(b)), + I32x4Add => stack_op!(binary Value128, |a, b| a.i32x4_add(b)), + I64x2Add => stack_op!(binary Value128, |a, b| a.i64x2_add(b)), + I8x16Sub => stack_op!(binary Value128, |a, b| a.i8x16_sub(b)), + I16x8Sub => stack_op!(binary Value128, |a, b| a.i16x8_sub(b)), + I32x4Sub => stack_op!(binary Value128, |a, b| a.i32x4_sub(b)), + I64x2Sub => stack_op!(binary Value128, |a, b| a.i64x2_sub(b)), + I8x16MinS => stack_op!(binary Value128, |a, b| a.i8x16_min_s(b)), + I16x8MinS => stack_op!(binary Value128, |a, b| a.i16x8_min_s(b)), + I32x4MinS => stack_op!(binary Value128, |a, b| a.i32x4_min_s(b)), + I8x16MinU => stack_op!(binary Value128, |a, b| a.i8x16_min_u(b)), + I16x8MinU => stack_op!(binary Value128, |a, b| a.i16x8_min_u(b)), + I32x4MinU => stack_op!(binary Value128, |a, b| a.i32x4_min_u(b)), + I8x16MaxS => stack_op!(binary Value128, |a, b| a.i8x16_max_s(b)), + I16x8MaxS => stack_op!(binary Value128, |a, b| a.i16x8_max_s(b)), + I32x4MaxS => stack_op!(binary Value128, |a, b| a.i32x4_max_s(b)), + I8x16MaxU => stack_op!(binary Value128, |a, b| a.i8x16_max_u(b)), + I16x8MaxU => stack_op!(binary Value128, |a, b| a.i16x8_max_u(b)), + I32x4MaxU => stack_op!(binary Value128, |a, b| a.i32x4_max_u(b)), + I64x2Mul => stack_op!(binary Value128, |a, b| a.i64x2_mul(b)), + I16x8Mul => stack_op!(binary Value128, |a, b| a.i16x8_mul(b)), + I32x4Mul => stack_op!(binary Value128, |a, b| a.i32x4_mul(b)), + I8x16NarrowI16x8S => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), + I8x16NarrowI16x8U => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), + I16x8NarrowI32x4S => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), + I16x8NarrowI32x4U => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), + I8x16AddSatS => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_s(b)), + I16x8AddSatS => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_s(b)), + I8x16AddSatU => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_u(b)), + I16x8AddSatU => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_u(b)), + I8x16SubSatS => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_s(b)), + I16x8SubSatS => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_s(b)), + I8x16SubSatU => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_u(b)), + I16x8SubSatU => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_u(b)), + I8x16AvgrU => stack_op!(binary Value128, |a, b| a.i8x16_avgr_u(b)), + I16x8AvgrU => stack_op!(binary Value128, |a, b| a.i16x8_avgr_u(b)), + I16x8ExtAddPairwiseI8x16S => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), + I16x8ExtAddPairwiseI8x16U => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), + I32x4ExtAddPairwiseI16x8S => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), + I32x4ExtAddPairwiseI16x8U => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), + I16x8ExtMulLowI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), + I16x8ExtMulLowI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), + I16x8ExtMulHighI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), + I16x8ExtMulHighI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_u(b)), + I32x4ExtMulLowI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_s(b)), + I32x4ExtMulLowI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_u(b)), + I32x4ExtMulHighI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_s(b)), + I32x4ExtMulHighI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_u(b)), + I64x2ExtMulLowI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_s(b)), + I64x2ExtMulLowI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), + I64x2ExtMulHighI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), + I64x2ExtMulHighI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), + I16x8ExtendLowI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_s()), + I16x8ExtendLowI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_u()), + I16x8ExtendHighI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_s()), + I16x8ExtendHighI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_u()), + I32x4ExtendLowI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_s()), + I32x4ExtendLowI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_u()), + I32x4ExtendHighI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_s()), + I32x4ExtendHighI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_u()), + I64x2ExtendLowI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_s()), + I64x2ExtendLowI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_u()), + I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), + I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), + I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), + I8x16Shuffle(idx) => { let idx = self.func.data.v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } + I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), + I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), + I8x16RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i8x16_relaxed_laneselect(a, b, c)), + I16x8RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i16x8_relaxed_laneselect(a, b, c)), + I32x4RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i32x4_relaxed_laneselect(a, b, c)), + I64x2RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i64x2_relaxed_laneselect(a, b, c)), + I16x8RelaxedQ15mulrS => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_q15mulr_s(b)), + I16x8RelaxedDotI8x16I7x16S => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_dot_i8x16_i7x16_s(b)), + I32x4RelaxedDotI8x16I7x16AddS => stack_op!(ternary Value128, |a, b, c| a.i32x4_relaxed_dot_i8x16_i7x16_add_s(b, c)), + F32x4Ceil => stack_op!(unary Value128, |v| v.f32x4_ceil()), + F64x2Ceil => stack_op!(unary Value128, |v| v.f64x2_ceil()), + F32x4Floor => stack_op!(unary Value128, |v| v.f32x4_floor()), + F64x2Floor => stack_op!(unary Value128, |v| v.f64x2_floor()), + F32x4Trunc => stack_op!(unary Value128, |v| v.f32x4_trunc()), + F64x2Trunc => stack_op!(unary Value128, |v| v.f64x2_trunc()), + F32x4Nearest => stack_op!(unary Value128, |v| v.f32x4_nearest()), + F64x2Nearest => stack_op!(unary Value128, |v| v.f64x2_nearest()), + F32x4Abs => stack_op!(unary Value128, |v| v.f32x4_abs()), + F64x2Abs => stack_op!(unary Value128, |v| v.f64x2_abs()), + F32x4Neg => stack_op!(unary Value128, |v| v.f32x4_neg()), + F64x2Neg => stack_op!(unary Value128, |v| v.f64x2_neg()), + F32x4Sqrt => stack_op!(unary Value128, |v| v.f32x4_sqrt()), + F64x2Sqrt => stack_op!(unary Value128, |v| v.f64x2_sqrt()), + F32x4Add => stack_op!(binary Value128, |a, b| a.f32x4_add(b)), + F64x2Add => stack_op!(binary Value128, |a, b| a.f64x2_add(b)), + F32x4Sub => stack_op!(binary Value128, |a, b| a.f32x4_sub(b)), + F64x2Sub => stack_op!(binary Value128, |a, b| a.f64x2_sub(b)), + F32x4Mul => stack_op!(binary Value128, |a, b| a.f32x4_mul(b)), + F64x2Mul => stack_op!(binary Value128, |a, b| a.f64x2_mul(b)), + F32x4Div => stack_op!(binary Value128, |a, b| a.f32x4_div(b)), + F64x2Div => stack_op!(binary Value128, |a, b| a.f64x2_div(b)), + F32x4Min => stack_op!(binary Value128, |a, b| a.f32x4_min(b)), + F64x2Min => stack_op!(binary Value128, |a, b| a.f64x2_min(b)), + F32x4Max => stack_op!(binary Value128, |a, b| a.f32x4_max(b)), + F64x2Max => stack_op!(binary Value128, |a, b| a.f64x2_max(b)), + F32x4PMin => stack_op!(binary Value128, |a, b| a.f32x4_pmin(b)), + F32x4PMax => stack_op!(binary Value128, |a, b| a.f32x4_pmax(b)), + F64x2PMin => stack_op!(binary Value128, |a, b| a.f64x2_pmin(b)), + F64x2PMax => stack_op!(binary Value128, |a, b| a.f64x2_pmax(b)), + F32x4RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_madd(b, c)), + F32x4RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_nmadd(b, c)), + F64x2RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_madd(b, c)), + F64x2RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_nmadd(b, c)), + F32x4RelaxedMin => stack_op!(binary Value128, |a, b| a.f32x4_relaxed_min(b)), + F32x4RelaxedMax => stack_op!(binary Value128, |a, b| a.f32x4_relaxed_max(b)), + F64x2RelaxedMin => stack_op!(binary Value128, |a, b| a.f64x2_relaxed_min(b)), + F64x2RelaxedMax => stack_op!(binary Value128, |a, b| a.f64x2_relaxed_max(b)), + I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), + I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), + F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), + F32x4ConvertI32x4U => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_u()), + F64x2ConvertLowI32x4S => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_s()), + F64x2ConvertLowI32x4U => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_u()), + F32x4DemoteF64x2Zero => stack_op!(unary Value128, |v| v.f32x4_demote_f64x2_zero()), + F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), + I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), + I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), + I32x4RelaxedTruncF32x4S => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f32x4_s()), + I32x4RelaxedTruncF32x4U => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f32x4_u()), + I32x4RelaxedTruncF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_s_zero()), + I32x4RelaxedTruncF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_u_zero()), + }; + + self.cf.incr_instr_ptr(); + } + + Ok(None) } - #[cold] - fn exec_unreachable(&self) -> ControlFlow> { - ControlFlow::Break(Some(Trap::Unreachable.into())) + #[inline(always)] + fn exec_jump(&mut self, ip: u32) { + self.cf.instr_ptr = ip; + } + + #[inline(always)] + fn exec_jump_if_zero(&mut self, ip: u32) -> bool { + if self.store.stack.values.pop::() == 0 { + self.cf.instr_ptr = ip; + return true; + } + false + } + + #[inline(always)] + fn exec_jump_if_non_zero(&mut self, ip: u32) -> bool { + if self.store.stack.values.pop::() != 0 { + self.cf.instr_ptr = ip; + return true; + } + false + } + + #[inline(always)] + fn exec_branch_table(&mut self, default_ip: u32, start: u32, len: u32) { + let idx = self.store.stack.values.pop::(); + let target_ip = if idx >= 0 && (idx as u32) < len { + self.func.data.branch_table_targets.get((start + idx as u32) as usize).copied().unwrap_or(default_ip) + } else { + default_ip + }; + + self.cf.instr_ptr = target_ip; } fn exec_call( &mut self, wasm_func: Rc, + func_addr: FuncAddr, owner: ModuleInstanceAddr, - ) -> ControlFlow> { - let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals); + ) -> Result<()> { + if !Rc::ptr_eq(&self.func, &wasm_func) { + self.func = wasm_func.clone(); + } if IS_RETURN_CALL { - self.cf.reuse_for(wasm_func, locals, self.stack.blocks.len() as u32, owner); - } else { - let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32); + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, wasm_func.params); + } + + let res = self.store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals); + let locals_base = res.map_err(|err| if IS_RETURN_CALL { err } else { Error::Trap(Trap::CallStackOverflow) })?; + let new_call_frame = CallFrame::new(func_addr, owner, locals_base, wasm_func.locals); + + if !IS_RETURN_CALL { self.cf.incr_instr_ptr(); // skip the call instruction - self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; + self.store.stack.call_stack.push(self.cf)?; + } + self.cf = new_call_frame; + + if self.cf.module_addr != self.module.idx { + self.module = self.store.get_module_instance_raw(self.cf.module_addr).clone(); } - self.module.swap_with(self.cf.module_addr(), self.store); - ControlFlow::Continue(()) + Ok(()) } - fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { - let params = self.stack.values.pop_params(&host_func.ty.params); - let res = host_func - .clone() - .call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms) - .to_cf()?; - self.stack.values.extend_from_wasmvalues(&res); + fn exec_call_host(&mut self, host_func: Rc) -> Result<()> { + let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::>(); + let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.idx }, ¶ms)?; + self.store.stack.values.extend_from_wasmvalues(&res)?; self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) + Ok(()) } - fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { - let func_inst = self.store.get_func(self.module.resolve_func_addr(v)); - match func_inst.func.clone() { - crate::Function::Wasm(wasm_func) => self.exec_call::(wasm_func, func_inst.owner), - crate::Function::Host(host_func) => self.exec_call_host(host_func), + fn exec_call_direct(&mut self, v: u32) -> Result<()> { + self.charge_call_fuel(FUEL_COST_CALL_TOTAL); + let addr = self.module.resolve_func_addr(v); + let func_inst = self.store.state.get_func(addr); + match &func_inst.func { + crate::Function::Wasm(wasm_func) => { + self.exec_call::(wasm_func.clone(), addr, func_inst.owner) + } + crate::Function::Host(host_func) => self.exec_call_host(host_func.clone()), } } - fn exec_call_indirect( - &mut self, - type_addr: u32, - table_addr: u32, - ) -> ControlFlow> { + + fn exec_call_self(&mut self) -> Result<()> { + self.charge_call_fuel(FUEL_COST_CALL_TOTAL); + let params = self.func.params; + let locals = self.func.locals; + + if IS_RETURN_CALL { + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, params); + } + + let res = self.store.stack.values.enter_locals(¶ms, &locals); + let locals_base = res.map_err(|err| if IS_RETURN_CALL { err } else { Error::Trap(Trap::CallStackOverflow) })?; + let new_call_frame = CallFrame::new(self.cf.func_addr, self.cf.module_addr, locals_base, locals); + + if !IS_RETURN_CALL { + self.cf.incr_instr_ptr(); + self.store.stack.call_stack.push(self.cf)?; + } + self.cf = new_call_frame; + Ok(()) + } + + fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> Result<()> { + self.charge_call_fuel(FUEL_COST_CALL_TOTAL); // verify that the table is of the right type, this should be validated by the parser already let func_ref = { - let table = self.store.get_table(self.module.resolve_table_addr(table_addr)); - let table_idx: u32 = self.stack.values.pop::() as u32; + let table_idx: u32 = self.store.stack.values.pop::() as u32; + let table = self.store.state.get_table(self.module.resolve_table_addr(table_addr)); assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); - let table = table.get(table_idx).map_err(|_| Trap::UndefinedElement { index: table_idx as usize }.into()); - let table = table.to_cf()?; - table.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize }.into()).to_cf()? + let table = + table.get(table_idx).map_err(|_| Error::from(Trap::UndefinedElement { index: table_idx as usize }))?; + table.addr().ok_or_else(|| Error::from(Trap::UninitializedElement { index: table_idx as usize }))? }; - let func_inst = self.store.get_func(func_ref); + let func_inst = self.store.state.get_func(func_ref); let call_ty = self.module.func_ty(type_addr); - match func_inst.func.clone() { + match &func_inst.func { crate::Function::Wasm(wasm_func) => { - if unlikely(wasm_func.ty != *call_ty) { - return ControlFlow::Break(Some( - Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() } - .into(), - )); + if wasm_func.ty != *call_ty { + return Err(Trap::IndirectCallTypeMismatch { + actual: wasm_func.ty.clone(), + expected: call_ty.clone(), + } + .into()); } - self.exec_call::(wasm_func, func_inst.owner) + self.exec_call::(wasm_func.clone(), func_ref, func_inst.owner) } crate::Function::Host(host_func) => { - if unlikely(host_func.ty != *call_ty) { - return ControlFlow::Break(Some( - Trap::IndirectCallTypeMismatch { actual: host_func.ty.clone(), expected: call_ty.clone() } - .into(), - )); + if host_func.ty != *call_ty { + return Err(Trap::IndirectCallTypeMismatch { + actual: host_func.ty.clone(), + expected: call_ty.clone(), + } + .into()); } - self.exec_call_host(host_func) + self.exec_call_host(host_func.clone()) } } } - fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) { - // truthy value is on the top of the stack, so enter the then block - if self.stack.values.pop::() != 0 { - self.enter_block(end_offset, BlockType::If, (params, results)); - return; - } + fn exec_return(&mut self) -> bool { + let results = ValueCounts::from_iter(&self.func.ty.results); + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, results); + let Some(cf) = self.store.stack.call_stack.pop() else { return true }; - // falsy value is on the top of the stack - if else_offset == 0 { - self.cf.jump(end_offset as usize); - return; - } + if cf.func_addr != self.cf.func_addr { + self.func = self.store.state.get_wasm_func(cf.func_addr).clone(); - self.cf.jump(else_offset as usize); - self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)); - } - fn exec_else(&mut self, end_offset: u32) { - self.exec_end_block(); - self.cf.jump(end_offset as usize); - } - fn resolve_functype(&self, idx: u32) -> (StackHeight, StackHeight) { - let ty = self.module.func_ty(idx); - ((&*ty.params).into(), (&*ty.results).into()) - } - fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { - self.stack.blocks.push(BlockFrame { - instr_ptr: self.cf.instr_ptr(), - end_instr_offset, - stack_ptr: self.stack.values.height(), - results, - params, - ty, - }); - } - fn exec_br(&mut self, to: u32) -> ControlFlow> { - if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { - return self.exec_return(); - } - - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) - } - fn exec_br_if(&mut self, to: u32) -> ControlFlow> { - if self.stack.values.pop::() != 0 - && self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() - { - return self.exec_return(); + if cf.module_addr != self.module.idx { + self.module = self.store.get_module_instance_raw(cf.module_addr).clone(); + } } - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) + self.cf = cf; + false } - fn exec_brtable(&mut self, default: u32, len: u32) -> ControlFlow> { - let start = self.cf.instr_ptr() + 1; - let end = start + len as usize; - if end > self.cf.instructions().len() { - return ControlFlow::Break(Some(Error::Other(format!( - "br_table out of bounds: {} >= {}", - end, - self.cf.instructions().len() - )))); - } - let idx = self.stack.values.pop::(); - let to = match self.cf.instructions()[start..end].get(idx as usize) { - None => default, - Some(Instruction::BrLabel(to)) => *to, - _ => return ControlFlow::Break(Some(Error::Other("br_table out of bounds".to_string()))), + #[inline(always)] + fn local_mem_addr(&self, memarg: MemoryArg, addr_local: u8) -> Result { + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, u16::from(addr_local))); + let Some(Ok(addr)) = memarg.offset().checked_add(addr).map(|a| a.try_into()) else { + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: addr as usize, len: N, max: 0 })); }; - - if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { - return self.exec_return(); - } - - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) + Ok(addr) } - fn exec_return(&mut self) -> ControlFlow> { - let old = self.cf.block_ptr(); - match self.stack.call_stack.pop() { - None => return ControlFlow::Break(None), - Some(cf) => self.cf = cf, - } - if old > self.cf.block_ptr() { - self.stack.blocks.truncate(old); - } - - self.module.swap_with(self.cf.module_addr(), self.store); - ControlFlow::Continue(()) - } - fn exec_end_block(&mut self) { - let block = self.stack.blocks.pop(); - self.stack.values.truncate_keep(block.stack_ptr, block.results); + #[inline(always)] + fn exec_store_local_local, const N: usize>( + &mut self, + memarg: MemoryArg, + addr_local: u8, + value_local: u8, + cast: fn(T) -> U, + ) -> Result<()> { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(memarg.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, u16::from(addr_local))); + let value = cast(self.store.stack.values.local_get::(&self.cf, u16::from(value_local))).to_mem_bytes(); + mem.store((memarg.offset() + addr) as usize, value.len(), &value)?; + Ok(()) } - fn exec_local_get(&mut self, local_index: u16) { - let v = self.cf.locals.get::(local_index); - self.stack.values.push(v); + + #[inline(always)] + fn exec_i32_load_local_value(&self, memarg: MemoryArg, addr_local: u8) -> Result { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(memarg.mem_addr())); + mem.load_as::<4, i32>(self.local_mem_addr::<4>(memarg, addr_local)?) } - fn exec_local_set(&mut self, local_index: u16) { - let v = self.stack.values.pop::(); - self.cf.locals.set(local_index, v); + + #[inline(always)] + fn exec_i32_load_local_tee(&mut self, memarg: MemoryArg, addr_local: u8, dst_local: u8) -> Result<()> { + let value = self.exec_i32_load_local_value(memarg, addr_local)?; + self.store.stack.values.local_set(&self.cf, u16::from(dst_local), value); + self.store.stack.values.push(value)?; + Ok(()) } - fn exec_local_tee(&mut self, local_index: u16) { - let v = self.stack.values.peek::(); - self.cf.locals.set(local_index, v); + + #[inline(always)] + fn exec_i32_load_local_set(&mut self, memarg: MemoryArg, addr_local: u8, dst_local: u8) -> Result<()> { + let value = self.exec_i32_load_local_value(memarg, addr_local)?; + self.store.stack.values.local_set(&self.cf, u16::from(dst_local), value); + Ok(()) } - fn exec_global_get(&mut self, global_index: u32) { - self.stack.values.push_dyn(self.store.get_global_val(self.module.resolve_global_addr(global_index))); + fn exec_global_get(&mut self, global_index: u32) -> Result<()> { + self.store.stack.values.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) } + fn exec_global_set(&mut self, global_index: u32) { - self.store.set_global_val(self.module.resolve_global_addr(global_index), self.stack.values.pop::().into()); + let val = self.store.stack.values.pop::().into(); + self.store.state.set_global_val(self.module.resolve_global_addr(global_index), val); } - fn exec_const(&mut self, val: T) { - self.stack.values.push(val); + fn exec_const(&mut self, val: T) -> Result<()> { + self.store.stack.values.push(val) } - fn exec_ref_is_null(&mut self) { - let is_null = i32::from(self.stack.values.pop::().is_none()); - self.stack.values.push::(is_null); + fn exec_ref_is_null(&mut self) -> Result<()> { + let is_null = i32::from(self.store.stack.values.pop::().is_none()); + self.store.stack.values.push::(is_null) } - fn exec_memory_size(&mut self, addr: u32) { - let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)); - + fn exec_memory_size(&mut self, addr: u32) -> Result<()> { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(addr)); match mem.is_64bit() { - true => self.stack.values.push::(mem.page_count as i64), - false => self.stack.values.push::(mem.page_count as i32), + true => self.store.stack.values.push::(mem.page_count as i64), + false => self.store.stack.values.push::(mem.page_count as i32), } } - fn exec_memory_grow(&mut self, addr: u32) { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(addr)); - let prev_size = mem.page_count; - - let pages_delta = match mem.is_64bit() { - true => self.stack.values.pop::(), - false => i64::from(self.stack.values.pop::()), + fn exec_memory_grow(&mut self, addr: u32) -> Result<()> { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); + let is_64bit = mem.is_64bit(); + let pages_delta = match is_64bit { + true => self.store.stack.values.pop::(), + false => i64::from(self.store.stack.values.pop::()), }; - match ( - mem.is_64bit(), - match mem.grow(pages_delta) { - Some(_) => prev_size as i64, - None => -1_i64, - }, - ) { - (true, size) => self.stack.values.push::(size), - (false, size) => self.stack.values.push::(size as i32), + let size = mem.grow(pages_delta).unwrap_or(-1); + match is_64bit { + true => self.store.stack.values.push::(size)?, + false => self.store.stack.values.push::(size as i32)?, }; + + Ok(()) } fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let src: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let src: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); if from == to { - let mem_from = self.store.get_mem_mut(self.module.resolve_mem_addr(from)); + let mem_from = self.store.state.get_mem_mut(self.module.resolve_mem_addr(from)); // copy within the same memory mem_from.copy_within(dst as usize, src as usize, size as usize)?; } else { // copy between two memories let (mem_from, mem_to) = - self.store.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?; + self.store.state.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?; mem_from.copy_from_slice(dst as usize, mem_to.load(src as usize, size as usize)?)?; } Ok(()) } fn exec_memory_fill(&mut self, addr: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let val: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let val: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(addr)); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); mem.fill(dst as usize, size as usize, val as u8) } + + fn exec_memory_fill_imm(&mut self, addr: u32, val: u8, size: i32) -> Result<()> { + let dst: i32 = self.store.stack.values.pop(); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); + mem.fill(dst as usize, size as usize, val) + } + fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let offset: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let offset: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); - let data = self - .store - .data - .datas - .get(self.module.resolve_data_addr(data_index) as usize) - .ok_or_else(|| Error::Other("data not found".to_string()))?; + let data_addr = self.module.resolve_data_addr(data_index) as usize; + let data = self.store.state.data.get(data_addr).ok_or_else(|| Error::Other("data not found".to_string()))?; - let mem = self - .store - .data - .memories - .get_mut(self.module.resolve_mem_addr(mem_index) as usize) - .ok_or_else(|| Error::Other("memory not found".to_string()))?; + let mem_addr = self.module.resolve_mem_addr(mem_index) as usize; + let mem = + self.store.state.memories.get_mut(mem_addr).ok_or_else(|| Error::Other("memory not found".to_string()))?; let data_len = data.data.as_ref().map_or(0, |d| d.len()); - if unlikely(((size + offset) as usize > data_len) || ((dst + size) as usize > mem.len())) { + if ((size + offset) as usize > data_len) || ((dst + size) as usize > mem.len()) { return Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len }.into()); } @@ -1019,164 +977,152 @@ impl<'store, 'stack> Executor<'store, 'stack> { let Some(data) = &data.data else { return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()) }; mem.store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)]) } - fn exec_data_drop(&mut self, data_index: u32) { - self.store.get_data_mut(self.module.resolve_data_addr(data_index)).drop(); - } - fn exec_elem_drop(&mut self, elem_index: u32) { - self.store.get_elem_mut(self.module.resolve_elem_addr(elem_index)).drop(); - } fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let src: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let src: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); if from == to { // copy within the same memory - self.store.get_table_mut(self.module.resolve_table_addr(from)).copy_within( + self.store.state.get_table_mut(self.module.resolve_table_addr(from)).copy_within( dst as usize, src as usize, size as usize, ) } else { // copy between two memories - let (table_from, table_to) = - self.store.get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?; + let (table_from, table_to) = self + .store + .state + .get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?; table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?) } } - #[cfg(feature = "unstable-simd")] - fn exec_mem_load_lane< - LOAD: MemLoadable, - INTO: InternalValue + IndexMut, - const LOAD_SIZE: usize, - >( + fn exec_mem_load_lane, const LOAD_SIZE: usize>( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, lane: u8, - ) -> ControlFlow> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); - let mut imm = self.stack.values.pop::(); - let val = self.stack.values.pop::() as u64; + ) -> Result<()> { + let mut imm = self.store.stack.values.pop::().to_mem_bytes(); + let val = self.store.stack.values.pop::() as u64; + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { - cold(); - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: val as usize, - len: LOAD_SIZE, - max: 0, - }))); + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: val as usize, len: LOAD_SIZE, max: 0 })); }; - let val = mem.load_as::(addr).to_cf()?; - imm[lane as usize] = val; - self.stack.values.push(imm); - ControlFlow::Continue(()) + let val = mem.load_as::(addr)?.to_mem_bytes(); + + let offset = lane as usize * LOAD_SIZE; + imm[offset..offset + LOAD_SIZE].copy_from_slice(&val); + + self.store.stack.values.push(Value128::from_mem_bytes(imm))?; + Ok(()) } - fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( + #[inline(always)] + fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, cast: fn(LOAD) -> TARGET, - ) -> ControlFlow> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); + ) -> Result<()> { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); - let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => u64::from(self.stack.values.pop::() as u32), + let base: u64 = if mem.is_64bit() { + self.store.stack.values.pop::() as u64 + } else { + self.store.stack.values.pop::() as u32 as u64 }; - let Some(Ok(addr)) = offset.checked_add(addr).map(|a| a.try_into()) else { - cold(); - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: addr as usize, - len: LOAD_SIZE, - max: 0, - }))); + let Some(addr) = base.checked_add(offset) else { + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: base as usize, len: LOAD_SIZE, max: 0 })); }; - let val = mem.load_as::(addr).to_cf()?; - self.stack.values.push(cast(val)); - ControlFlow::Continue(()) + + let Ok(addr) = usize::try_from(addr) else { + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: base as usize, len: LOAD_SIZE, max: 0 })); + }; + + let val = mem.load_as::(addr)?; + self.store.stack.values.push(cast(val))?; + Ok(()) } - #[cfg(feature = "unstable-simd")] - fn exec_mem_store_lane, U: MemStorable + Copy, const N: usize>( + fn exec_mem_store_lane + Copy, const N: usize>( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, lane: u8, - ) -> ControlFlow> { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - let val = self.stack.values.pop::(); - let val = val[lane as usize].to_mem_bytes(); + ) -> Result<()> { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); + let bytes = self.store.stack.values.pop::().to_mem_bytes(); + let lane_offset = lane as usize * N; + let mut val = [0u8; N]; + val.copy_from_slice(&bytes[lane_offset..lane_offset + N]); let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => self.stack.values.pop::() as u32 as u64, + true => self.store.stack.values.pop::() as u64, + false => self.store.stack.values.pop::() as u32 as u64, }; - if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return ControlFlow::Break(Some(e)); - } + mem.store((offset + addr) as usize, val.len(), &val)?; - ControlFlow::Continue(()) + Ok(()) } - fn exec_mem_store, const N: usize>( + fn exec_mem_store, const N: usize>( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, cast: fn(T) -> U, - ) -> ControlFlow> { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - let val = self.stack.values.pop::(); + ) -> Result<()> { + let val = self.store.stack.values.pop::(); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); let val = (cast(val)).to_mem_bytes(); let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => u64::from(self.stack.values.pop::() as u32), + true => self.store.stack.values.pop::() as u64, + false => u64::from(self.store.stack.values.pop::() as u32), }; - if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return ControlFlow::Break(Some(e)); - } + mem.store((offset + addr) as usize, val.len(), &val)?; - ControlFlow::Continue(()) + Ok(()) } fn exec_table_get(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)); - let idx: i32 = self.stack.values.pop::(); + let idx: i32 = self.store.stack.values.pop::(); + let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); let v = table.get_wasm_val(idx as u32)?; - self.stack.values.push_dyn(v.into()); + self.store.stack.values.push_dyn(v.into())?; Ok(()) } fn exec_table_set(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); - let val = self.stack.values.pop::(); - let idx = self.stack.values.pop::() as u32; + let val = self.store.stack.values.pop::(); + let idx = self.store.stack.values.pop::() as u32; + let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); table.set(idx, val.into()) } fn exec_table_size(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)); - self.stack.values.push_dyn(table.size().into()); + let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); + self.store.stack.values.push(table.size())?; Ok(()) } fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); // n - let offset: i32 = self.stack.values.pop(); // s - let dst: i32 = self.stack.values.pop(); // d + let size: i32 = self.store.stack.values.pop(); // n + let offset: i32 = self.store.stack.values.pop(); // s + let dst: i32 = self.store.stack.values.pop(); // d let elem = self .store - .data + .state .elements .get(self.module.resolve_elem_addr(elem_index) as usize) .ok_or_else(|| Error::Other("element not found".to_string()))?; let table = self .store - .data + .state .tables .get_mut(self.module.resolve_table_addr(table_index) as usize) .ok_or_else(|| Error::Other("table not found".to_string()))?; @@ -1184,7 +1130,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { let elem_len = elem.items.as_ref().map_or(0, alloc::vec::Vec::len); let table_len = table.size(); - if unlikely(size < 0 || ((size + offset) as usize > elem_len) || ((dst + size) > table_len)) { + if size < 0 || ((size + offset) as usize > elem_len) || ((dst + size) > table_len) { return Err(Trap::TableOutOfBounds { offset: offset as usize, len: size as usize, max: elem_len }.into()); } @@ -1203,27 +1149,27 @@ impl<'store, 'stack> Executor<'store, 'stack> { table.init(i64::from(dst), &items[offset as usize..(offset + size) as usize]) } fn exec_table_grow(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); let sz = table.size(); - let n = self.stack.values.pop::(); - let val = self.stack.values.pop::(); + let n = self.store.stack.values.pop::(); + let val = self.store.stack.values.pop::(); match table.grow(n, val.into()) { - Ok(()) => self.stack.values.push(sz), - Err(_) => self.stack.values.push(-1_i32), + Ok(()) => self.store.stack.values.push(sz)?, + Err(_) => self.store.stack.values.push(-1_i32)?, } Ok(()) } fn exec_table_fill(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); - let n = self.stack.values.pop::(); - let val = self.stack.values.pop::(); - let i = self.stack.values.pop::(); + let n = self.store.stack.values.pop::(); + let val = self.store.stack.values.pop::(); + let i = self.store.stack.values.pop::(); - if unlikely(i + n > table.size()) { + if i + n > table.size() { return Err(Error::Trap(Trap::TableOutOfBounds { offset: i as usize, len: n as usize, @@ -1237,9 +1183,55 @@ impl<'store, 'stack> Executor<'store, 'stack> { table.fill(self.module.func_addrs(), i as usize, n as usize, val.into()) } +} + +impl<'store> Executor<'store, false> { + #[inline(always)] + pub(crate) fn run_to_completion(&mut self) -> Result<()> { + if self.exec::<{ usize::MAX }>()?.is_some() { + return Ok(()); + } + unreachable!(); + } + + #[cfg(feature = "std")] + #[inline(always)] + pub(crate) fn run_with_time_budget(&mut self, time_budget: core::time::Duration) -> Result { + use crate::std::time::Instant; + let start = Instant::now(); + if time_budget.is_zero() { + return Ok(ExecState::Suspended(self.cf)); + } + + loop { + if self.exec::()?.is_some() { + return Ok(ExecState::Completed); + } + + if start.elapsed() >= time_budget { + return Ok(ExecState::Suspended(self.cf)); + } + } + } +} - fn exec_local_copy(&mut self, from: u16, to: u16) { - let v = self.cf.locals.get::(from); - self.cf.locals.set(to, v); +impl<'store> Executor<'store, true> { + #[inline(always)] + pub(crate) fn run_with_fuel(&mut self, fuel: u32) -> Result { + self.store.execution_fuel = fuel; + if self.store.execution_fuel == 0 { + return Ok(ExecState::Suspended(self.cf)); + } + + loop { + if self.exec::()?.is_some() { + return Ok(ExecState::Completed); + } + + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(FUEL_ACCOUNTING_INTERVAL as u32); + if self.store.execution_fuel == 0 { + return Ok(ExecState::Suspended(self.cf)); + } + } } } diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 0b7df2f9..ab94d473 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -1,22 +1,45 @@ pub(crate) mod executor; pub(crate) mod num_helpers; +pub(crate) mod simd; pub(crate) mod stack; -mod values; +pub(crate) mod values; #[cfg(not(feature = "std"))] mod no_std_floats; -use crate::{Result, Store}; -pub use values::*; +use crate::{Result, Store, interpreter::stack::CallFrame}; +pub(crate) use simd::*; +pub(crate) use values::*; + +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) enum ExecState { + Completed, + Suspended(CallFrame), +} /// The main `TinyWasm` runtime. /// /// This is the default runtime used by `TinyWasm`. -#[derive(Debug, Default)] -pub struct InterpreterRuntime {} +#[derive(Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct InterpreterRuntime; impl InterpreterRuntime { - pub(crate) fn exec(&self, store: &mut Store, stack: &mut stack::Stack) -> Result<()> { - executor::Executor::new(store, stack)?.run_to_completion() + pub(crate) fn exec(store: &mut Store, cf: CallFrame) -> Result<()> { + executor::Executor::::new(store, cf)?.run_to_completion() + } + + pub(crate) fn exec_with_fuel(store: &mut Store, cf: CallFrame, fuel: u32) -> Result { + executor::Executor::::new(store, cf)?.run_with_fuel(fuel) + } + + #[cfg(feature = "std")] + pub(crate) fn exec_with_time_budget( + store: &mut Store, + cf: CallFrame, + time_budget: core::time::Duration, + ) -> Result { + executor::Executor::::new(store, cf)?.run_with_time_budget(time_budget) } } diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs index e2731844..b6ce998a 100644 --- a/crates/tinywasm/src/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -1,3 +1,4 @@ +// see https://github.com/rust-lang/rust/issues/137578 :( pub(super) trait NoStdFloatExt { fn round(self) -> Self; fn ceil(self) -> Self; diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index da27f3de..20882b34 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -9,20 +9,17 @@ where /// Doing the actual conversion from float to int is a bit tricky, because /// we need to check for overflow. This macro generates the min/max values /// for a specific conversion, which are then used in the actual conversion. -/// Rust sadly doesn't have wrapping casts for floats yet, maybe never. -/// Alternatively, could be used for this but -/// it's not worth the dependency. -#[rustfmt::skip] +/// Rust sadly doesn't have wrapping casts for floats yet. +#[rustfmt::skip] macro_rules! float_min_max { - (f32, i32) => {(-2147483904.0_f32, 2147483648.0_f32)}; - (f64, i32) => {(-2147483649.0_f64, 2147483648.0_f64)}; - (f32, u32) => {(-1.0_f32, 4294967296.0_f32)}; // 2^32 - (f64, u32) => {(-1.0_f64, 4294967296.0_f64)}; // 2^32 - (f32, i64) => {(-9223373136366403584.0_f32, 9223372036854775808.0_f32)}; // 2^63 + 2^40 | 2^63 - (f64, i64) => {(-9223372036854777856.0_f64, 9223372036854775808.0_f64)}; // 2^63 + 2^40 | 2^63 - (f32, u64) => {(-1.0_f32, 18446744073709551616.0_f32)}; // 2^64 - (f64, u64) => {(-1.0_f64, 18446744073709551616.0_f64)}; // 2^64 - // other conversions are not allowed + (f32, i32) => {(i32::MIN as f32 - (1 << 8) as f32, i32::MAX as f32 + 1.0)}; // 2^8: f32 precision margin + (f64, i32) => {(i32::MIN as f64 - 1.0, i32::MAX as f64 + 1.0)}; + (f32, u32) => {(-1.0_f32, u32::MAX as f32 + 1.0)}; + (f64, u32) => {(-1.0_f64, u32::MAX as f64 + 1.0)}; + (f32, i64) => {(i64::MIN as f32 - (1i64 << 40) as f32, i64::MAX as f32 + 1.0)}; // 2^40: f32 has 23 mantissa bits + (f64, i64) => {(i64::MIN as f64 - (1i64 << 11) as f64, i64::MAX as f64 + 1.0)}; // 2^11: f64 precision margin + (f32, u64) => {(-1.0_f32, u64::MAX as f32 + 1.0)}; + (f64, u64) => {(-1.0_f64, u64::MAX as f64 + 1.0)}; ($from:ty, $to:ty) => {compile_error!("invalid float conversion")}; } @@ -33,22 +30,17 @@ macro_rules! checked_conv_float { checked_conv_float!($from, $to, $to, $self) }; // Conversion with an intermediate unsigned type and error checking (three types) - ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { - $self - .stack - .values - .replace_top::<$from, $to>(|v| { - let (min, max) = float_min_max!($from, $intermediate); - if unlikely(v.is_nan()) { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - if unlikely(v <= min || v >= max) { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - Ok((v as $intermediate as $to).into()) - }) - .to_cf()? - }; + ($from:tt, $intermediate:tt, $to:tt, $self:expr) => {{ + let v = $self.store.stack.values.pop::<$from>(); + let (min, max) = float_min_max!($from, $intermediate); + if unlikely(v.is_nan()) { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + if unlikely(v <= min || v >= max) { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + $self.store.stack.values.push::<$to>((v as $intermediate as $to).into())?; + }}; } pub(crate) use checked_conv_float; @@ -168,7 +160,6 @@ impl_wrapping_self_sh! { i32 i64 u32 u64 } macro_rules! impl_checked_wrapping_rem { ($($t:ty)*) => ($( impl TinywasmIntExt for $t { - #[inline] fn checked_wrapping_rem(self, rhs: Self) -> Result { if rhs == 0 { Err(Error::Trap(crate::Trap::DivisionByZero)) @@ -177,7 +168,6 @@ macro_rules! impl_checked_wrapping_rem { } } - #[inline] fn wasm_checked_div(self, rhs: Self) -> Result { if rhs == 0 { Err(Error::Trap(crate::Trap::DivisionByZero)) @@ -190,35 +180,3 @@ macro_rules! impl_checked_wrapping_rem { } impl_checked_wrapping_rem! { i32 i64 u32 u64 } - -#[cfg(all(feature = "unstable-simd", not(feature = "canonicalize_nans")))] -#[inline] -pub(crate) fn canonicalize_f32x4(x: core::simd::f32x4) -> core::simd::f32x4 { - x // No need to do anything, as we are not replacing NaNs -} - -#[cfg(all(feature = "unstable-simd", feature = "canonicalize_nans"))] -/// replace all NaNs in a f32x4 with f32::NAN -#[inline] -pub(crate) fn canonicalize_f32x4(x: core::simd::f32x4) -> core::simd::f32x4 { - use core::simd::{Simd, num::SimdFloat}; - let nan = Simd::splat(f32::NAN); - let mask = x.is_nan(); - mask.select(nan, x) -} - -#[cfg(all(feature = "unstable-simd", not(feature = "canonicalize_nans")))] -#[inline] -pub(crate) fn canonicalize_f64x2(x: core::simd::f64x2) -> core::simd::f64x2 { - x // No need to do anything, as we are not replacing NaNs -} - -#[cfg(feature = "unstable-simd")] -/// replace all NaNs in a f64x2 with f64::NAN -#[inline] -pub(crate) fn canonicalize_f64x2(x: core::simd::f64x2) -> core::simd::f64x2 { - use core::simd::{Simd, num::SimdFloat}; - let nan = Simd::splat(f64::NAN); - let mask = x.is_nan(); - mask.select(nan, x) -} diff --git a/crates/tinywasm/src/interpreter/simd/instructions.rs b/crates/tinywasm/src/interpreter/simd/instructions.rs new file mode 100644 index 00000000..97e2360f --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/instructions.rs @@ -0,0 +1,1390 @@ +use super::super::num_helpers::TinywasmFloatExt; +use super::Value128; +use super::utils::*; + +#[cfg(not(feature = "std"))] +use super::super::no_std_floats::NoStdFloatExt; +#[cfg(target_arch = "wasm32")] +use core::arch::wasm32 as wasm; +#[cfg(target_arch = "wasm64")] +use core::arch::wasm64 as wasm; +#[cfg(all( + feature = "simd-x86", + target_arch = "x86_64", + target_feature = "sse4.2", + target_feature = "avx", + target_feature = "avx2", + target_feature = "bmi1", + target_feature = "bmi2", + target_feature = "fma", + target_feature = "lzcnt", + target_feature = "movbe", + target_feature = "popcnt" +))] +use core::arch::x86_64 as x86; + +impl Value128 { + #[doc(alias = "v128.any_true")] + pub fn v128_any_true(self) -> bool { + simd_impl! { + wasm => { wasm::v128_any_true(self.to_wasm_v128()) } + generic => { self.0 != 0 } + } + } + + #[doc(alias = "v128.not")] + pub fn v128_not(self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_not(self.to_wasm_v128())) } + generic => { Self(!self.0) } + } + } + + #[doc(alias = "v128.and")] + pub fn v128_and(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_and(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 & rhs.0) } + } + } + + #[doc(alias = "v128.andnot")] + pub fn v128_andnot(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_andnot(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 & !rhs.0) } + } + } + + #[doc(alias = "v128.or")] + pub fn v128_or(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_or(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 | rhs.0) } + } + } + + #[doc(alias = "v128.xor")] + pub fn v128_xor(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_xor(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 ^ rhs.0) } + } + } + + #[doc(alias = "v128.bitselect")] + pub fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_bitselect(v1.to_wasm_v128(), v2.to_wasm_v128(), c.to_wasm_v128())) } + generic => { Self((v1.0 & c.0) | (v2.0 & !c.0)) } + } + } + + #[doc(alias = "v128.load8x8_s")] + pub const fn v128_load8x8_s(src: [u8; 8]) -> Self { + Self::from_i16x8([ + src[0] as i8 as i16, + src[1] as i8 as i16, + src[2] as i8 as i16, + src[3] as i8 as i16, + src[4] as i8 as i16, + src[5] as i8 as i16, + src[6] as i8 as i16, + src[7] as i8 as i16, + ]) + } + + #[doc(alias = "v128.load8x8_u")] + pub const fn v128_load8x8_u(src: [u8; 8]) -> Self { + Self::from_u16x8([ + src[0] as u16, + src[1] as u16, + src[2] as u16, + src[3] as u16, + src[4] as u16, + src[5] as u16, + src[6] as u16, + src[7] as u16, + ]) + } + + #[doc(alias = "v128.load16x4_s")] + pub const fn v128_load16x4_s(src: [u8; 8]) -> Self { + Self::from_i32x4([ + i16::from_le_bytes([src[0], src[1]]) as i32, + i16::from_le_bytes([src[2], src[3]]) as i32, + i16::from_le_bytes([src[4], src[5]]) as i32, + i16::from_le_bytes([src[6], src[7]]) as i32, + ]) + } + + #[doc(alias = "v128.load16x4_u")] + pub const fn v128_load16x4_u(src: [u8; 8]) -> Self { + Self::from_u32x4([ + u16::from_le_bytes([src[0], src[1]]) as u32, + u16::from_le_bytes([src[2], src[3]]) as u32, + u16::from_le_bytes([src[4], src[5]]) as u32, + u16::from_le_bytes([src[6], src[7]]) as u32, + ]) + } + + #[doc(alias = "v128.load32x2_s")] + pub const fn v128_load32x2_s(src: [u8; 8]) -> Self { + Self::from_i64x2([ + i32::from_le_bytes([src[0], src[1], src[2], src[3]]) as i64, + i32::from_le_bytes([src[4], src[5], src[6], src[7]]) as i64, + ]) + } + + #[doc(alias = "v128.load32x2_u")] + pub const fn v128_load32x2_u(src: [u8; 8]) -> Self { + Self::from_u64x2([ + u32::from_le_bytes([src[0], src[1], src[2], src[3]]) as u64, + u32::from_le_bytes([src[4], src[5], src[6], src[7]]) as u64, + ]) + } + + #[doc(alias = "i8x16.swizzle")] + pub fn i8x16_swizzle(self, s: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())) } + x86 => { + let a = self.to_le_bytes(); + let idx = s.to_le_bytes(); + let mut mask = [0u8; 16]; + for i in 0..16 { + let j = idx[i]; + mask[i] = if j < 16 { j & 0x0f } else { 0x80 }; + } + + // SAFETY: `a`, `mask`, and `out` are valid 16-byte buffers, and `_mm_loadu/_mm_storeu` support unaligned accesses. + #[allow(unsafe_code)] + let out = unsafe { + let a_vec = x86::_mm_loadu_si128(a.as_ptr().cast::()); + let mask_vec = x86::_mm_loadu_si128(mask.as_ptr().cast::()); + let result = x86::_mm_shuffle_epi8(a_vec, mask_vec); + let mut out = [0u8; 16]; + x86::_mm_storeu_si128(out.as_mut_ptr().cast::(), result); + out + }; + Self::from_le_bytes(out) + } + generic => { + let a = self.to_le_bytes(); + let idx = s.to_le_bytes(); + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i]; + let lane = a[(j & 0x0f) as usize]; + out[i] = if j < 16 { lane } else { 0 }; + } + Self::from_le_bytes(out) + } + } + } + + #[doc(alias = "i8x16.relaxed_swizzle")] + pub fn i8x16_relaxed_swizzle(self, s: Self) -> Self { + self.i8x16_swizzle(s) + } + + #[doc(alias = "i8x16.shuffle")] + pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { + simd_impl! { + x86 => { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + let mut mask_a = [0u8; 16]; + let mut mask_b = [0u8; 16]; + for i in 0..16 { + let j = idx[i] & 31; + mask_a[i] = if j < 16 { j } else { 0x80 }; + mask_b[i] = if j < 16 { 0x80 } else { j & 0x0f }; + } + + // SAFETY: all inputs are valid 16-byte buffers, and `_mm_loadu/_mm_storeu` support unaligned accesses. + #[allow(unsafe_code)] + let out = unsafe { + let a_vec = x86::_mm_loadu_si128(a_bytes.as_ptr().cast::()); + let b_vec = x86::_mm_loadu_si128(b_bytes.as_ptr().cast::()); + let mask_a_vec = x86::_mm_loadu_si128(mask_a.as_ptr().cast::()); + let mask_b_vec = x86::_mm_loadu_si128(mask_b.as_ptr().cast::()); + let a_part = x86::_mm_shuffle_epi8(a_vec, mask_a_vec); + let b_part = x86::_mm_shuffle_epi8(b_vec, mask_b_vec); + let result = x86::_mm_or_si128(a_part, b_part); + let mut out = [0u8; 16]; + x86::_mm_storeu_si128(out.as_mut_ptr().cast::(), result); + out + }; + Self::from_le_bytes(out) + } + generic => { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i] & 31; + out[i] = if j < 16 { a_bytes[j as usize] } else { b_bytes[(j & 0x0f) as usize] }; + } + Self::from_le_bytes(out) + } + } + } + + #[doc(alias = "i8x16.splat")] + pub fn splat_i8(src: i8) -> Self { + Self::from_le_bytes([src as u8; 16]) + } + + #[doc(alias = "i8x16.replace_lane")] + pub fn i8x16_replace_lane(self, lane: u8, value: i8) -> Self { + self.replace_lane_bytes::<1>(lane, [value as u8], 16) + } + + #[doc(alias = "i16x8.replace_lane")] + pub fn i16x8_replace_lane(self, lane: u8, value: i16) -> Self { + self.replace_lane_bytes::<2>(lane, value.to_le_bytes(), 8) + } + + #[doc(alias = "i32x4.replace_lane")] + pub fn i32x4_replace_lane(self, lane: u8, value: i32) -> Self { + self.replace_lane_bytes::<4>(lane, value.to_le_bytes(), 4) + } + + #[doc(alias = "i64x2.replace_lane")] + pub fn i64x2_replace_lane(self, lane: u8, value: i64) -> Self { + self.replace_lane_bytes::<8>(lane, value.to_le_bytes(), 2) + } + + #[doc(alias = "f32x4.replace_lane")] + pub fn f32x4_replace_lane(self, lane: u8, value: f32) -> Self { + self.replace_lane_bytes::<4>(lane, value.to_bits().to_le_bytes(), 4) + } + + #[doc(alias = "f64x2.replace_lane")] + pub fn f64x2_replace_lane(self, lane: u8, value: f64) -> Self { + self.replace_lane_bytes::<8>(lane, value.to_bits().to_le_bytes(), 2) + } + + #[doc(alias = "i8x16.all_true")] + pub fn i8x16_all_true(self) -> bool { + for byte in self.to_le_bytes() { + if byte == 0 { + return false; + } + } + true + } + + #[doc(alias = "i16x8.all_true")] + pub fn i16x8_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(2) { + if u16::from_le_bytes([lane[0], lane[1]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i32x4.all_true")] + pub fn i32x4_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(4) { + if u32::from_le_bytes([lane[0], lane[1], lane[2], lane[3]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i64x2.all_true")] + pub fn i64x2_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(8) { + if u64::from_le_bytes([lane[0], lane[1], lane[2], lane[3], lane[4], lane[5], lane[6], lane[7]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i8x16.bitmask")] + pub fn i8x16_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, byte) in bytes.into_iter().enumerate() { + if (byte & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i16x8.bitmask")] + pub fn i16x8_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, lane) in bytes.chunks_exact(2).enumerate() { + if (lane[1] & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i32x4.bitmask")] + pub fn i32x4_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, lane) in bytes.chunks_exact(4).enumerate() { + if (lane[3] & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i64x2.bitmask")] + pub fn i64x2_bitmask(self) -> u32 { + let x = u128::from_le_bytes(self.to_le_bytes()); + (((x >> 63) & 1) as u32) | ((((x >> 127) & 1) as u32) << 1) + } + + #[doc(alias = "i8x16.popcnt")] + pub fn i8x16_popcnt(self) -> Self { + let lanes = self.to_le_bytes(); + let mut out = [0u8; 16]; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane.count_ones() as u8; + } + Self::from_le_bytes(out) + } + + #[doc(alias = "i8x16.shl")] + pub fn i8x16_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i8, 16, as_i8x16, from_i8x16, 7) + } + #[doc(alias = "i16x8.shl")] + pub fn i16x8_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i16, 8, as_i16x8, from_i16x8, 15) + } + #[doc(alias = "i32x4.shl")] + pub fn i32x4_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i32, 4, as_i32x4, from_i32x4, 31) + } + #[doc(alias = "i64x2.shl")] + pub fn i64x2_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i64, 2, as_i64x2, from_i64x2, 63) + } + + #[doc(alias = "i8x16.shr_s")] + pub fn i8x16_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i8, 16, as_i8x16, from_i8x16, 7) + } + #[doc(alias = "i16x8.shr_s")] + pub fn i16x8_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i16, 8, as_i16x8, from_i16x8, 15) + } + #[doc(alias = "i32x4.shr_s")] + pub fn i32x4_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i32, 4, as_i32x4, from_i32x4, 31) + } + #[doc(alias = "i64x2.shr_s")] + pub fn i64x2_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i64, 2, as_i64x2, from_i64x2, 63) + } + + #[doc(alias = "i8x16.shr_u")] + pub fn i8x16_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u8, 16, as_u8x16, from_u8x16, 7) + } + #[doc(alias = "i16x8.shr_u")] + pub fn i16x8_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u16, 8, as_u16x8, from_u16x8, 15) + } + #[doc(alias = "i32x4.shr_u")] + pub fn i32x4_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u32, 4, as_u32x4, from_u32x4, 31) + } + #[doc(alias = "i64x2.shr_u")] + pub fn i64x2_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u64, 2, as_u64x2, from_u64x2, 63) + } + + #[doc(alias = "i8x16.add")] + pub fn i8x16_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i8x16_add, i8, 16, as_i8x16, from_i8x16, wrapping_add) + } + #[doc(alias = "i16x8.add")] + pub fn i16x8_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i16x8_add, i16, 8, as_i16x8, from_i16x8, wrapping_add) + } + #[doc(alias = "i32x4.add")] + pub fn i32x4_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i32x4_add, i32, 4, as_i32x4, from_i32x4, wrapping_add) + } + #[doc(alias = "i64x2.add")] + pub fn i64x2_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i64x2_add, i64, 2, as_i64x2, from_i64x2, wrapping_add) + } + #[doc(alias = "i8x16.sub")] + pub fn i8x16_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i8x16_sub, i8, 16, as_i8x16, from_i8x16, wrapping_sub) + } + #[doc(alias = "i16x8.sub")] + pub fn i16x8_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i16x8_sub, i16, 8, as_i16x8, from_i16x8, wrapping_sub) + } + #[doc(alias = "i32x4.sub")] + pub fn i32x4_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i32x4_sub, i32, 4, as_i32x4, from_i32x4, wrapping_sub) + } + #[doc(alias = "i64x2.sub")] + pub fn i64x2_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i64x2_sub, i64, 2, as_i64x2, from_i64x2, wrapping_sub) + } + #[doc(alias = "i16x8.mul")] + pub fn i16x8_mul(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i16x8_mul, i16, 8, as_i16x8, from_i16x8, wrapping_mul) + } + #[doc(alias = "i32x4.mul")] + pub fn i32x4_mul(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i32x4_mul, i32, 4, as_i32x4, from_i32x4, wrapping_mul) + } + #[doc(alias = "i64x2.mul")] + pub fn i64x2_mul(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i64x2_mul, i64, 2, as_i64x2, from_i64x2, wrapping_mul) + } + + #[doc(alias = "i8x16.add_sat_s")] + pub fn i8x16_add_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i8x16_add_sat, i8, 16, as_i8x16, from_i8x16, saturating_add) + } + #[doc(alias = "i16x8.add_sat_s")] + pub fn i16x8_add_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i16x8_add_sat, i16, 8, as_i16x8, from_i16x8, saturating_add) + } + #[doc(alias = "i8x16.add_sat_u")] + pub fn i8x16_add_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u8x16_add_sat, u8, 16, as_u8x16, from_u8x16, saturating_add) + } + #[doc(alias = "i16x8.add_sat_u")] + pub fn i16x8_add_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u16x8_add_sat, u16, 8, as_u16x8, from_u16x8, saturating_add) + } + #[doc(alias = "i8x16.sub_sat_s")] + pub fn i8x16_sub_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i8x16_sub_sat, i8, 16, as_i8x16, from_i8x16, saturating_sub) + } + #[doc(alias = "i16x8.sub_sat_s")] + pub fn i16x8_sub_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i16x8_sub_sat, i16, 8, as_i16x8, from_i16x8, saturating_sub) + } + #[doc(alias = "i8x16.sub_sat_u")] + pub fn i8x16_sub_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u8x16_sub_sat, u8, 16, as_u8x16, from_u8x16, saturating_sub) + } + #[doc(alias = "i16x8.sub_sat_u")] + pub fn i16x8_sub_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u16x8_sub_sat, u16, 8, as_u16x8, from_u16x8, saturating_sub) + } + + #[doc(alias = "i8x16.avgr_u")] + pub fn i8x16_avgr_u(self, rhs: Self) -> Self { + simd_avgr_u!(self, rhs, u8x16_avgr, u8, u16, 16, as_u8x16, from_u8x16) + } + #[doc(alias = "i16x8.avgr_u")] + pub fn i16x8_avgr_u(self, rhs: Self) -> Self { + simd_avgr_u!(self, rhs, u16x8_avgr, u16, u32, 8, as_u16x8, from_u16x8) + } + + #[doc(alias = "i8x16.narrow_i16x8_s")] + pub fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0i8; 16]; + let (lo, hi) = out.split_at_mut(8); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i16_to_i8(a_lane); + *dst_hi = saturate_i16_to_i8(b_lane); + } + Self::from_i8x16(out) + } + + #[doc(alias = "i8x16.narrow_i16x8_u")] + pub fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0u8; 16]; + let (lo, hi) = out.split_at_mut(8); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i16_to_u8(a_lane); + *dst_hi = saturate_i16_to_u8(b_lane); + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.narrow_i32x4_s")] + pub fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0i16; 8]; + let (lo, hi) = out.split_at_mut(4); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i32_to_i16(a_lane); + *dst_hi = saturate_i32_to_i16(b_lane); + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.narrow_i32x4_u")] + pub fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0u16; 8]; + let (lo, hi) = out.split_at_mut(4); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i32_to_u16(a_lane); + *dst_hi = saturate_i32_to_u16(b_lane); + } + Self::from_u16x8(out) + } + + #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] + pub fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { + let lanes = self.as_i8x16(); + let mut out = [0i16; 8]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as i16 + pair[1] as i16; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] + pub fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { + let lanes = self.as_u8x16(); + let mut out = [0u16; 8]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as u16 + pair[1] as u16; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] + pub fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { + let lanes = self.as_i16x8(); + let mut out = [0i32; 4]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as i32 + pair[1] as i32; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] + pub fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { + let lanes = self.as_u16x8(); + let mut out = [0u32; 4]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as u32 + pair[1] as u32; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i16x8.extend_low_i8x16_s")] + pub fn i16x8_extend_low_i8x16_s(self) -> Self { + simd_extend_cast!(self, as_i8x16, from_i16x8, i16, 8, 0) + } + #[doc(alias = "i16x8.extend_low_i8x16_u")] + pub fn i16x8_extend_low_i8x16_u(self) -> Self { + simd_extend_cast!(self, as_u8x16, from_u16x8, u16, 8, 0) + } + #[doc(alias = "i16x8.extend_high_i8x16_s")] + pub fn i16x8_extend_high_i8x16_s(self) -> Self { + simd_extend_cast!(self, as_i8x16, from_i16x8, i16, 8, 8) + } + #[doc(alias = "i16x8.extend_high_i8x16_u")] + pub fn i16x8_extend_high_i8x16_u(self) -> Self { + simd_extend_cast!(self, as_u8x16, from_u16x8, u16, 8, 8) + } + #[doc(alias = "i32x4.extend_low_i16x8_s")] + pub fn i32x4_extend_low_i16x8_s(self) -> Self { + simd_extend_cast!(self, as_i16x8, from_i32x4, i32, 4, 0) + } + #[doc(alias = "i32x4.extend_low_i16x8_u")] + pub fn i32x4_extend_low_i16x8_u(self) -> Self { + simd_extend_cast!(self, as_u16x8, from_u32x4, u32, 4, 0) + } + #[doc(alias = "i32x4.extend_high_i16x8_s")] + pub fn i32x4_extend_high_i16x8_s(self) -> Self { + simd_extend_cast!(self, as_i16x8, from_i32x4, i32, 4, 4) + } + #[doc(alias = "i32x4.extend_high_i16x8_u")] + pub fn i32x4_extend_high_i16x8_u(self) -> Self { + simd_extend_cast!(self, as_u16x8, from_u32x4, u32, 4, 4) + } + #[doc(alias = "i64x2.extend_low_i32x4_s")] + pub fn i64x2_extend_low_i32x4_s(self) -> Self { + simd_extend_cast!(self, as_i32x4, from_i64x2, i64, 2, 0) + } + #[doc(alias = "i64x2.extend_low_i32x4_u")] + pub fn i64x2_extend_low_i32x4_u(self) -> Self { + simd_extend_cast!(self, as_u32x4, from_u64x2, u64, 2, 0) + } + #[doc(alias = "i64x2.extend_high_i32x4_s")] + pub fn i64x2_extend_high_i32x4_s(self) -> Self { + simd_extend_cast!(self, as_i32x4, from_i64x2, i64, 2, 2) + } + #[doc(alias = "i64x2.extend_high_i32x4_u")] + pub fn i64x2_extend_high_i32x4_u(self) -> Self { + simd_extend_cast!(self, as_u32x4, from_u64x2, u64, 2, 2) + } + + #[doc(alias = "i16x8.extmul_low_i8x16_s")] + pub fn i16x8_extmul_low_i8x16_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i8x16, from_i16x8, i16, 8, 0) + } + #[doc(alias = "i16x8.extmul_low_i8x16_u")] + pub fn i16x8_extmul_low_i8x16_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u8x16, from_u16x8, u16, 8, 0) + } + #[doc(alias = "i16x8.extmul_high_i8x16_s")] + pub fn i16x8_extmul_high_i8x16_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i8x16, from_i16x8, i16, 8, 8) + } + #[doc(alias = "i16x8.extmul_high_i8x16_u")] + pub fn i16x8_extmul_high_i8x16_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u8x16, from_u16x8, u16, 8, 8) + } + #[doc(alias = "i32x4.extmul_low_i16x8_s")] + pub fn i32x4_extmul_low_i16x8_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i16x8, from_i32x4, i32, 4, 0) + } + #[doc(alias = "i32x4.extmul_low_i16x8_u")] + pub fn i32x4_extmul_low_i16x8_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u16x8, from_u32x4, u32, 4, 0) + } + #[doc(alias = "i32x4.extmul_high_i16x8_s")] + pub fn i32x4_extmul_high_i16x8_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i16x8, from_i32x4, i32, 4, 4) + } + #[doc(alias = "i32x4.extmul_high_i16x8_u")] + pub fn i32x4_extmul_high_i16x8_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u16x8, from_u32x4, u32, 4, 4) + } + #[doc(alias = "i64x2.extmul_low_i32x4_s")] + pub fn i64x2_extmul_low_i32x4_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i32x4, from_i64x2, i64, 2, 0) + } + #[doc(alias = "i64x2.extmul_low_i32x4_u")] + pub fn i64x2_extmul_low_i32x4_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u32x4, from_u64x2, u64, 2, 0) + } + #[doc(alias = "i64x2.extmul_high_i32x4_s")] + pub fn i64x2_extmul_high_i32x4_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i32x4, from_i64x2, i64, 2, 2) + } + #[doc(alias = "i64x2.extmul_high_i32x4_u")] + pub fn i64x2_extmul_high_i32x4_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u32x4, from_u64x2, u64, 2, 2) + } + + #[doc(alias = "i16x8.q15mulr_sat_s")] + pub fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + let r = ((lhs as i32 * rhs as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding + *dst = if r > i16::MAX as i32 { + i16::MAX + } else if r < i16::MIN as i32 { + i16::MIN + } else { + r as i16 + }; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.dot_i16x8_s")] + pub fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i32; 4]; + for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { + *dst = (a_pair[0] as i32) + .wrapping_mul(b_pair[0] as i32) + .wrapping_add((a_pair[1] as i32).wrapping_mul(b_pair[1] as i32)); + } + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.relaxed_laneselect")] + pub fn i8x16_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i16x8.relaxed_laneselect")] + pub fn i16x8_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i32x4.relaxed_laneselect")] + pub fn i32x4_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i64x2.relaxed_laneselect")] + pub fn i64x2_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i16x8.relaxed_q15mulr_s")] + pub fn i16x8_relaxed_q15mulr_s(self, rhs: Self) -> Self { + self.i16x8_q15mulr_sat_s(rhs) + } + + #[doc(alias = "i16x8.relaxed_dot_i8x16_i7x16_s")] + pub fn i16x8_relaxed_dot_i8x16_i7x16_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i16; 8]; + + for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { + let prod0 = (a_pair[0] as i16) * (b_pair[0] as i16); + let prod1 = (a_pair[1] as i16) * (b_pair[1] as i16); + *dst = prod0.wrapping_add(prod1); + } + + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.relaxed_dot_i8x16_i7x16_add_s")] + pub fn i32x4_relaxed_dot_i8x16_i7x16_add_s(self, rhs: Self, acc: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let c = acc.as_i32x4(); + let mut out = [0i32; 4]; + + for (i, dst) in out.iter_mut().enumerate() { + let base = i * 4; + let mut sum = 0i32; + for j in 0..4 { + sum = sum.wrapping_add((a[base + j] as i32).wrapping_mul(b[base + j] as i32)); + } + *dst = sum.wrapping_add(c[i]); + } + + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.eq")] + pub fn i8x16_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_eq, i8, 16, as_i8x16, from_i8x16, ==) + } + #[doc(alias = "i16x8.eq")] + pub fn i16x8_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_eq, i16, 8, as_i16x8, from_i16x8, ==) + } + #[doc(alias = "i32x4.eq")] + pub fn i32x4_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_eq, i32, 4, as_i32x4, from_i32x4, ==) + } + #[doc(alias = "i64x2.eq")] + pub fn i64x2_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_eq, i64, 2, as_i64x2, from_i64x2, ==) + } + #[doc(alias = "i8x16.ne")] + pub fn i8x16_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_ne, i8, 16, as_i8x16, from_i8x16, !=) + } + #[doc(alias = "i16x8.ne")] + pub fn i16x8_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_ne, i16, 8, as_i16x8, from_i16x8, !=) + } + #[doc(alias = "i32x4.ne")] + pub fn i32x4_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_ne, i32, 4, as_i32x4, from_i32x4, !=) + } + #[doc(alias = "i64x2.ne")] + pub fn i64x2_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_ne, i64, 2, as_i64x2, from_i64x2, !=) + } + #[doc(alias = "i8x16.lt_s")] + pub fn i8x16_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_lt, i8, 16, as_i8x16, from_i8x16, <) + } + #[doc(alias = "i16x8.lt_s")] + pub fn i16x8_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_lt, i16, 8, as_i16x8, from_i16x8, <) + } + #[doc(alias = "i32x4.lt_s")] + pub fn i32x4_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_lt, i32, 4, as_i32x4, from_i32x4, <) + } + #[doc(alias = "i64x2.lt_s")] + pub fn i64x2_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_lt, i64, 2, as_i64x2, from_i64x2, <) + } + #[doc(alias = "i8x16.lt_u")] + pub fn i8x16_lt_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u8x16_lt, i8, 16, as_u8x16, from_i8x16, <) + } + #[doc(alias = "i16x8.lt_u")] + pub fn i16x8_lt_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u16x8_lt, i16, 8, as_u16x8, from_i16x8, <) + } + #[doc(alias = "i32x4.lt_u")] + pub fn i32x4_lt_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u32x4_lt, i32, 4, as_u32x4, from_i32x4, <) + } + + #[doc(alias = "i8x16.gt_s")] + pub fn i8x16_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_lt_s) + } + #[doc(alias = "i16x8.gt_s")] + pub fn i16x8_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_lt_s) + } + #[doc(alias = "i32x4.gt_s")] + pub fn i32x4_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_lt_s) + } + #[doc(alias = "i64x2.gt_s")] + pub fn i64x2_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i64x2_lt_s) + } + #[doc(alias = "i8x16.gt_u")] + pub fn i8x16_gt_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_lt_u) + } + #[doc(alias = "i16x8.gt_u")] + pub fn i16x8_gt_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_lt_u) + } + #[doc(alias = "i32x4.gt_u")] + pub fn i32x4_gt_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_lt_u) + } + #[doc(alias = "i8x16.le_s")] + pub fn i8x16_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_ge_s) + } + #[doc(alias = "i16x8.le_s")] + pub fn i16x8_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_ge_s) + } + #[doc(alias = "i32x4.le_s")] + pub fn i32x4_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_ge_s) + } + #[doc(alias = "i64x2.le_s")] + pub fn i64x2_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i64x2_ge_s) + } + #[doc(alias = "i8x16.le_u")] + pub fn i8x16_le_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_ge_u) + } + #[doc(alias = "i16x8.le_u")] + pub fn i16x8_le_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_ge_u) + } + #[doc(alias = "i32x4.le_u")] + pub fn i32x4_le_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_ge_u) + } + + #[doc(alias = "i8x16.ge_s")] + pub fn i8x16_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_ge, i8, 16, as_i8x16, from_i8x16, >=) + } + #[doc(alias = "i16x8.ge_s")] + pub fn i16x8_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_ge, i16, 8, as_i16x8, from_i16x8, >=) + } + #[doc(alias = "i32x4.ge_s")] + pub fn i32x4_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_ge, i32, 4, as_i32x4, from_i32x4, >=) + } + #[doc(alias = "i64x2.ge_s")] + pub fn i64x2_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_ge, i64, 2, as_i64x2, from_i64x2, >=) + } + #[doc(alias = "i8x16.ge_u")] + pub fn i8x16_ge_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u8x16_ge, i8, 16, as_u8x16, from_i8x16, >=) + } + #[doc(alias = "i16x8.ge_u")] + pub fn i16x8_ge_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u16x8_ge, i16, 8, as_u16x8, from_i16x8, >=) + } + #[doc(alias = "i32x4.ge_u")] + pub fn i32x4_ge_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u32x4_ge, i32, 4, as_u32x4, from_i32x4, >=) + } + + #[doc(alias = "i8x16.abs")] + pub fn i8x16_abs(self) -> Self { + simd_abs_const!(self, i8, 16, as_i8x16, from_i8x16) + } + #[doc(alias = "i16x8.abs")] + pub fn i16x8_abs(self) -> Self { + simd_abs_const!(self, i16, 8, as_i16x8, from_i16x8) + } + #[doc(alias = "i32x4.abs")] + pub fn i32x4_abs(self) -> Self { + simd_abs_const!(self, i32, 4, as_i32x4, from_i32x4) + } + #[doc(alias = "i64x2.abs")] + pub fn i64x2_abs(self) -> Self { + simd_abs_const!(self, i64, 2, as_i64x2, from_i64x2) + } + + #[doc(alias = "i8x16.neg")] + pub fn i8x16_neg(self) -> Self { + simd_neg!(self, i8x16_neg, i8, 16, as_i8x16, from_i8x16) + } + #[doc(alias = "i16x8.neg")] + pub fn i16x8_neg(self) -> Self { + simd_neg!(self, i16x8_neg, i16, 8, as_i16x8, from_i16x8) + } + #[doc(alias = "i32x4.neg")] + pub fn i32x4_neg(self) -> Self { + simd_neg!(self, i32x4_neg, i32, 4, as_i32x4, from_i32x4) + } + #[doc(alias = "i64x2.neg")] + pub fn i64x2_neg(self) -> Self { + simd_neg!(self, i64x2_neg, i64, 2, as_i64x2, from_i64x2) + } + + #[doc(alias = "i8x16.min_s")] + pub fn i8x16_min_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i8x16_min, i8, 16, as_i8x16, from_i8x16, <) + } + #[doc(alias = "i16x8.min_s")] + pub fn i16x8_min_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i16x8_min, i16, 8, as_i16x8, from_i16x8, <) + } + #[doc(alias = "i32x4.min_s")] + pub fn i32x4_min_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i32x4_min, i32, 4, as_i32x4, from_i32x4, <) + } + #[doc(alias = "i8x16.min_u")] + pub fn i8x16_min_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u8x16_min, u8, 16, as_u8x16, from_u8x16, <) + } + #[doc(alias = "i16x8.min_u")] + pub fn i16x8_min_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u16x8_min, u16, 8, as_u16x8, from_u16x8, <) + } + #[doc(alias = "i32x4.min_u")] + pub fn i32x4_min_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u32x4_min, u32, 4, as_u32x4, from_u32x4, <) + } + #[doc(alias = "i8x16.max_s")] + pub fn i8x16_max_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i8x16_max, i8, 16, as_i8x16, from_i8x16, >) + } + #[doc(alias = "i16x8.max_s")] + pub fn i16x8_max_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i16x8_max, i16, 8, as_i16x8, from_i16x8, >) + } + #[doc(alias = "i32x4.max_s")] + pub fn i32x4_max_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i32x4_max, i32, 4, as_i32x4, from_i32x4, >) + } + #[doc(alias = "i8x16.max_u")] + pub fn i8x16_max_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u8x16_max, u8, 16, as_u8x16, from_u8x16, >) + } + #[doc(alias = "i16x8.max_u")] + pub fn i16x8_max_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u16x8_max, u16, 8, as_u16x8, from_u16x8, >) + } + #[doc(alias = "i32x4.max_u")] + pub fn i32x4_max_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u32x4_max, u32, 4, as_u32x4, from_u32x4, >) + } + + #[doc(alias = "f32x4.eq")] + pub fn f32x4_eq(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, ==) + } + #[doc(alias = "f64x2.eq")] + pub fn f64x2_eq(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, ==) + } + #[doc(alias = "f32x4.ne")] + pub fn f32x4_ne(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, !=) + } + #[doc(alias = "f64x2.ne")] + pub fn f64x2_ne(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, !=) + } + #[doc(alias = "f32x4.lt")] + pub fn f32x4_lt(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, <) + } + #[doc(alias = "f64x2.lt")] + pub fn f64x2_lt(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, <) + } + + #[doc(alias = "f32x4.gt")] + pub fn f32x4_gt(self, rhs: Self) -> Self { + rhs.f32x4_lt(self) + } + + #[doc(alias = "f64x2.gt")] + pub fn f64x2_gt(self, rhs: Self) -> Self { + rhs.f64x2_lt(self) + } + + #[doc(alias = "f32x4.le")] + pub fn f32x4_le(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, <=) + } + #[doc(alias = "f64x2.le")] + pub fn f64x2_le(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, <=) + } + #[doc(alias = "f32x4.ge")] + pub fn f32x4_ge(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, >=) + } + #[doc(alias = "f64x2.ge")] + pub fn f64x2_ge(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, >=) + } + + #[doc(alias = "f32x4.ceil")] + pub fn f32x4_ceil(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.ceil())) + } + #[doc(alias = "f64x2.ceil")] + pub fn f64x2_ceil(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.ceil())) + } + #[doc(alias = "f32x4.floor")] + pub fn f32x4_floor(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.floor())) + } + #[doc(alias = "f64x2.floor")] + pub fn f64x2_floor(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.floor())) + } + #[doc(alias = "f32x4.trunc")] + pub fn f32x4_trunc(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.trunc())) + } + #[doc(alias = "f64x2.trunc")] + pub fn f64x2_trunc(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.trunc())) + } + #[doc(alias = "f32x4.nearest")] + pub fn f32x4_nearest(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(TinywasmFloatExt::tw_nearest(x))) + } + #[doc(alias = "f64x2.nearest")] + pub fn f64x2_nearest(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(TinywasmFloatExt::tw_nearest(x))) + } + #[doc(alias = "f32x4.abs")] + pub fn f32x4_abs(self) -> Self { + simd_float_unary!(self, map_f32x4, f32::abs) + } + #[doc(alias = "f64x2.abs")] + pub fn f64x2_abs(self) -> Self { + simd_float_unary!(self, map_f64x2, f64::abs) + } + #[doc(alias = "f32x4.neg")] + pub fn f32x4_neg(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| -x) + } + #[doc(alias = "f64x2.neg")] + pub fn f64x2_neg(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| -x) + } + #[doc(alias = "f32x4.sqrt")] + pub fn f32x4_sqrt(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.sqrt())) + } + #[doc(alias = "f64x2.sqrt")] + pub fn f64x2_sqrt(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.sqrt())) + } + + #[doc(alias = "f32x4.add")] + pub fn f32x4_add(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a + b)) + } + #[doc(alias = "f64x2.add")] + pub fn f64x2_add(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a + b)) + } + #[doc(alias = "f32x4.sub")] + pub fn f32x4_sub(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a - b)) + } + #[doc(alias = "f64x2.sub")] + pub fn f64x2_sub(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a - b)) + } + #[doc(alias = "f32x4.mul")] + pub fn f32x4_mul(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a * b)) + } + #[doc(alias = "f64x2.mul")] + pub fn f64x2_mul(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a * b)) + } + #[doc(alias = "f32x4.div")] + pub fn f32x4_div(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a / b)) + } + #[doc(alias = "f64x2.div")] + pub fn f64x2_div(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a / b)) + } + #[doc(alias = "f32x4.min")] + pub fn f32x4_min(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, TinywasmFloatExt::tw_minimum) + } + #[doc(alias = "f64x2.min")] + pub fn f64x2_min(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, TinywasmFloatExt::tw_minimum) + } + #[doc(alias = "f32x4.max")] + pub fn f32x4_max(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, TinywasmFloatExt::tw_maximum) + } + #[doc(alias = "f64x2.max")] + pub fn f64x2_max(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, TinywasmFloatExt::tw_maximum) + } + #[doc(alias = "f32x4.pmin")] + pub fn f32x4_pmin(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| if b < a { b } else { a }) + } + #[doc(alias = "f64x2.pmin")] + pub fn f64x2_pmin(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| if b < a { b } else { a }) + } + #[doc(alias = "f32x4.pmax")] + pub fn f32x4_pmax(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| if b > a { b } else { a }) + } + #[doc(alias = "f64x2.pmax")] + pub fn f64x2_pmax(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| if b > a { b } else { a }) + } + + #[doc(alias = "f32x4.relaxed_madd")] + pub fn f32x4_relaxed_madd(self, b: Self, c: Self) -> Self { + self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(x * y)) + .zip_f32x4(c, |xy, z| canonicalize_simd_f32_nan(xy + z)) + } + + #[doc(alias = "f32x4.relaxed_nmadd")] + pub fn f32x4_relaxed_nmadd(self, b: Self, c: Self) -> Self { + self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(-(x * y))) + .zip_f32x4(c, |neg_xy, z| canonicalize_simd_f32_nan(neg_xy + z)) + } + + #[doc(alias = "f64x2.relaxed_madd")] + pub fn f64x2_relaxed_madd(self, b: Self, c: Self) -> Self { + self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(x * y)) + .zip_f64x2(c, |xy, z| canonicalize_simd_f64_nan(xy + z)) + } + + #[doc(alias = "f64x2.relaxed_nmadd")] + pub fn f64x2_relaxed_nmadd(self, b: Self, c: Self) -> Self { + self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(-(x * y))) + .zip_f64x2(c, |neg_xy, z| canonicalize_simd_f64_nan(neg_xy + z)) + } + + #[doc(alias = "f32x4.relaxed_min")] + pub fn f32x4_relaxed_min(self, rhs: Self) -> Self { + self.f32x4_min(rhs) + } + + #[doc(alias = "f64x2.relaxed_min")] + pub fn f64x2_relaxed_min(self, rhs: Self) -> Self { + self.f64x2_min(rhs) + } + + #[doc(alias = "f32x4.relaxed_max")] + pub fn f32x4_relaxed_max(self, rhs: Self) -> Self { + self.f32x4_max(rhs) + } + + #[doc(alias = "f64x2.relaxed_max")] + pub fn f64x2_relaxed_max(self, rhs: Self) -> Self { + self.f64x2_max(rhs) + } + + #[doc(alias = "i32x4.relaxed_trunc_f32x4_s")] + pub fn i32x4_relaxed_trunc_f32x4_s(self) -> Self { + self.i32x4_trunc_sat_f32x4_s() + } + + #[doc(alias = "i32x4.relaxed_trunc_f32x4_u")] + pub fn i32x4_relaxed_trunc_f32x4_u(self) -> Self { + self.i32x4_trunc_sat_f32x4_u() + } + + #[doc(alias = "i32x4.relaxed_trunc_f64x2_s_zero")] + pub fn i32x4_relaxed_trunc_f64x2_s_zero(self) -> Self { + self.i32x4_trunc_sat_f64x2_s_zero() + } + + #[doc(alias = "i32x4.relaxed_trunc_f64x2_u_zero")] + pub fn i32x4_relaxed_trunc_f64x2_u_zero(self) -> Self { + self.i32x4_trunc_sat_f64x2_u_zero() + } + + #[doc(alias = "i32x4.trunc_sat_f32x4_s")] + pub fn i32x4_trunc_sat_f32x4_s(self) -> Self { + let v = self.as_f32x4(); + Self::from_i32x4([ + trunc_sat_f32_to_i32(v[0]), + trunc_sat_f32_to_i32(v[1]), + trunc_sat_f32_to_i32(v[2]), + trunc_sat_f32_to_i32(v[3]), + ]) + } + + #[doc(alias = "i32x4.trunc_sat_f32x4_u")] + pub fn i32x4_trunc_sat_f32x4_u(self) -> Self { + let v = self.as_f32x4(); + Self::from_u32x4([ + trunc_sat_f32_to_u32(v[0]), + trunc_sat_f32_to_u32(v[1]), + trunc_sat_f32_to_u32(v[2]), + trunc_sat_f32_to_u32(v[3]), + ]) + } + + #[doc(alias = "i32x4.trunc_sat_f64x2_s_zero")] + pub fn i32x4_trunc_sat_f64x2_s_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_i32x4([trunc_sat_f64_to_i32(v[0]), trunc_sat_f64_to_i32(v[1]), 0, 0]) + } + + #[doc(alias = "i32x4.trunc_sat_f64x2_u_zero")] + pub fn i32x4_trunc_sat_f64x2_u_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_u32x4([trunc_sat_f64_to_u32(v[0]), trunc_sat_f64_to_u32(v[1]), 0, 0]) + } + + #[doc(alias = "f32x4.convert_i32x4_s")] + pub fn f32x4_convert_i32x4_s(self) -> Self { + let v = self.as_i32x4(); + Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) + } + + #[doc(alias = "f32x4.convert_i32x4_u")] + pub fn f32x4_convert_i32x4_u(self) -> Self { + let v = self.as_u32x4(); + Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) + } + + #[doc(alias = "f64x2.convert_low_i32x4_s")] + pub fn f64x2_convert_low_i32x4_s(self) -> Self { + let v = self.as_i32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "f64x2.convert_low_i32x4_u")] + pub fn f64x2_convert_low_i32x4_u(self) -> Self { + let v = self.as_u32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "f32x4.demote_f64x2_zero")] + pub fn f32x4_demote_f64x2_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_f32x4([v[0] as f32, v[1] as f32, 0.0, 0.0]) + } + + #[doc(alias = "f64x2.promote_low_f32x4")] + pub fn f64x2_promote_low_f32x4(self) -> Self { + let v = self.as_f32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "i16x8.splat")] + pub fn splat_i16(src: i16) -> Self { + Self::from_i16x8([src; 8]) + } + + #[doc(alias = "i32x4.splat")] + pub fn splat_i32(src: i32) -> Self { + Self::from_i32x4([src; 4]) + } + + #[doc(alias = "i64x2.splat")] + pub fn splat_i64(src: i64) -> Self { + Self::from_i64x2([src; 2]) + } + + #[doc(alias = "f32x4.splat")] + pub fn splat_f32(src: f32) -> Self { + Self::splat_i32(src.to_bits() as i32) + } + + #[doc(alias = "f64x2.splat")] + pub fn splat_f64(src: f64) -> Self { + Self::splat_i64(src.to_bits() as i64) + } + + #[doc(alias = "i8x16.extract_lane_s")] + pub fn extract_lane_i8(self, lane: u8) -> i8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] as i8 + } + + #[doc(alias = "i8x16.extract_lane_u")] + pub fn extract_lane_u8(self, lane: u8) -> u8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] + } + + #[doc(alias = "i16x8.extract_lane_s")] + pub fn extract_lane_i16(self, lane: u8) -> i16 { + i16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) + } + + #[doc(alias = "i16x8.extract_lane_u")] + pub fn extract_lane_u16(self, lane: u8) -> u16 { + u16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) + } + + #[doc(alias = "i32x4.extract_lane")] + pub fn extract_lane_i32(self, lane: u8) -> i32 { + i32::from_le_bytes(self.extract_lane_bytes::<4>(lane, 4)) + } + + #[doc(alias = "i64x2.extract_lane")] + pub fn extract_lane_i64(self, lane: u8) -> i64 { + i64::from_le_bytes(self.extract_lane_bytes::<8>(lane, 2)) + } + + #[doc(alias = "f32x4.extract_lane")] + pub fn extract_lane_f32(self, lane: u8) -> f32 { + f32::from_bits(self.extract_lane_i32(lane) as u32) + } + + #[doc(alias = "f64x2.extract_lane")] + pub fn extract_lane_f64(self, lane: u8) -> f64 { + f64::from_bits(self.extract_lane_i64(lane) as u64) + } +} diff --git a/crates/tinywasm/src/interpreter/simd/macros.rs b/crates/tinywasm/src/interpreter/simd/macros.rs new file mode 100644 index 00000000..8cb5a3a9 --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/macros.rs @@ -0,0 +1,350 @@ +#![allow(unused_macros)] + +macro_rules! simd_impl { + ($(wasm => $wasm:block)? $(x86 => $x86:block)? generic => $generic:block) => {{ + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + simd_impl!(@pick_wasm $( $wasm )? ; $generic) + } + + #[cfg(all( + not(any(target_arch = "wasm32", target_arch = "wasm64")), + feature = "simd-x86", + target_arch = "x86_64", + target_feature = "sse4.2", + target_feature = "avx", + target_feature = "avx2", + target_feature = "bmi1", + target_feature = "bmi2", + target_feature = "fma", + target_feature = "lzcnt", + target_feature = "movbe", + target_feature = "popcnt" + ))] + { + simd_impl!(@pick_x86 $( $x86 )? ; $generic) + } + + #[allow(unreachable_code)] + #[cfg(not(any( + any(target_arch = "wasm32", target_arch = "wasm64"), + all( + feature = "simd-x86", + target_arch = "x86_64", + target_feature = "sse4.2", + target_feature = "avx", + target_feature = "avx2", + target_feature = "bmi1", + target_feature = "bmi2", + target_feature = "fma", + target_feature = "lzcnt", + target_feature = "movbe", + target_feature = "popcnt" + ) + )))] + { + $generic + } + }}; + + (@pick_wasm $wasm:block ; $generic:block) => { + $wasm + }; + + (@pick_wasm ; $generic:block) => { + $generic + }; + + (@pick_x86 $x86:block ; $generic:block) => { + $x86 + }; + + (@pick_x86 ; $generic:block) => { + $generic + }; +} + +macro_rules! simd_wrapping_binop_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $lane_count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = lhs.$op(rhs); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_wrapping_binop { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_wrapping_binop_generic!($lhs, $rhs, $lane_ty, $lane_count, $as_lanes, $from_lanes, $op) } + } + }}; +} + +macro_rules! simd_sat_binop_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $lane_count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = lhs.$op(rhs); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_sat_binop { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_sat_binop_generic!($lhs, $rhs, $lane_ty, $lane_count, $as_lanes, $from_lanes, $op) } + } + }}; +} + +macro_rules! simd_shift_left { + ($value:expr, $shift:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => {{ + let lanes = $value.$as_lanes(); + let s = $shift & $mask; + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane.wrapping_shl(s); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_shift_right { + ($value:expr, $shift:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => {{ + let lanes = $value.$as_lanes(); + let s = $shift & $mask; + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane >> s; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_avgr_u_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $wide_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = ((lhs as $wide_ty + rhs as $wide_ty + 1) >> 1) as $lane_ty; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_avgr_u { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $wide_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_avgr_u_generic!($lhs, $rhs, $lane_ty, $wide_ty, $count, $as_lanes, $from_lanes) } + } + }}; +} + +macro_rules! simd_extend_cast { + ($value:expr, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => {{ + let lanes = $value.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + for (dst, src) in out.iter_mut().zip(lanes[$offset..($offset + $dst_count)].iter()) { + *dst = *src as $dst_ty; + } + Self::$dst_from(out) + }}; +} + +macro_rules! simd_extmul_signed { + ($lhs:expr, $rhs:expr, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => {{ + let a = $lhs.$src_as(); + let b = $rhs.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + for ((dst, lhs), rhs) in + out.iter_mut().zip(a[$offset..($offset + $dst_count)].iter()).zip(b[$offset..($offset + $dst_count)].iter()) + { + *dst = (*lhs as $dst_ty).wrapping_mul(*rhs as $dst_ty); + } + Self::$dst_from(out) + }}; +} + +macro_rules! simd_extmul_unsigned { + ($lhs:expr, $rhs:expr, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => {{ + let a = $lhs.$src_as(); + let b = $rhs.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + for ((dst, lhs), rhs) in + out.iter_mut().zip(a[$offset..($offset + $dst_count)].iter()).zip(b[$offset..($offset + $dst_count)].iter()) + { + *dst = (*lhs as $dst_ty) * (*rhs as $dst_ty); + } + Self::$dst_from(out) + }}; +} + +macro_rules! simd_cmp_mask_generic { + ($lhs:expr, $rhs:expr, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $out_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { -1 } else { 0 }; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_cmp_mask { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_cmp_mask_generic!($lhs, $rhs, $out_ty, $count, $as_lanes, $from_lanes, $cmp) } + } + }}; +} + +macro_rules! simd_cmp_mask_const { + ($lhs:expr, $rhs:expr, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $out_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { -1 } else { 0 }; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_cmp_delegate { + ($lhs:expr, $rhs:expr, $delegate:ident) => {{ $rhs.$delegate($lhs) }}; +} + +macro_rules! simd_abs_const { + ($value:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + let a = $value.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(a) { + *dst = lane.wrapping_abs(); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_neg_generic { + ($value:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + let a = $value.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(a) { + *dst = lane.wrapping_neg(); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_neg { + ($value:expr, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($value.to_wasm_v128())) } + generic => { simd_neg_generic!($value, $lane_ty, $count, $as_lanes, $from_lanes) } + } + }}; +} + +macro_rules! simd_minmax_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { lhs } else { rhs }; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_minmax { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_minmax_generic!($lhs, $rhs, $lane_ty, $count, $as_lanes, $from_lanes, $cmp) } + } + }}; +} + +macro_rules! simd_float_unary { + ($value:expr, $map:ident, $op:expr) => {{ $value.$map($op) }}; +} + +macro_rules! simd_float_binary { + ($lhs:expr, $rhs:expr, $zip:ident, $op:expr) => {{ $lhs.$zip($rhs, $op) }}; +} + +#[rustfmt::skip] +macro_rules! lane_read { + (i8, $bytes:expr, $offset:expr) => { $bytes[$offset] as i8 }; + (u8, $bytes:expr, $offset:expr) => { $bytes[$offset] }; + (i16, $bytes:expr, $offset:expr) => { i16::from_le_bytes([$bytes[$offset], $bytes[$offset + 1]]) }; + (u16, $bytes:expr, $offset:expr) => { u16::from_le_bytes([$bytes[$offset], $bytes[$offset + 1]]) }; + (i32, $bytes:expr, $offset:expr) => { i32::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3]]) }; + (u32, $bytes:expr, $offset:expr) => { u32::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3]]) }; + (i64, $bytes:expr, $offset:expr) => { i64::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3], $bytes[$offset + 4], $bytes[$offset + 5], $bytes[$offset + 6], $bytes[$offset + 7]]) }; + (u64, $bytes:expr, $offset:expr) => { u64::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3], $bytes[$offset + 4], $bytes[$offset + 5], $bytes[$offset + 6], $bytes[$offset + 7]]) }; + (f32, $bytes:expr, $offset:expr) => { f32::from_bits(u32::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3]])) }; + (f64, $bytes:expr, $offset:expr) => { f64::from_bits(u64::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3], $bytes[$offset + 4], $bytes[$offset + 5], $bytes[$offset + 6], $bytes[$offset + 7]])) }; +} + +#[rustfmt::skip] +macro_rules! lane_write { + (i8, $value:expr) => { [$value as u8] }; + (u8, $value:expr) => { [$value] }; + (i16, $value:expr) => { $value.to_le_bytes() }; + (u16, $value:expr) => { $value.to_le_bytes() }; + (i32, $value:expr) => { $value.to_le_bytes() }; + (u32, $value:expr) => { $value.to_le_bytes() }; + (i64, $value:expr) => { $value.to_le_bytes() }; + (u64, $value:expr) => { $value.to_le_bytes() }; + (f32, $value:expr) => { $value.to_bits().to_le_bytes() }; + (f64, $value:expr) => { $value.to_bits().to_le_bytes() }; +} + +macro_rules! impl_lane_accessors { + ($( $as_vis:vis $as_name:ident => $from_vis:vis $from_name:ident : $lane_ty:tt, $lane_count:expr, $lane_bytes:expr; )*) => { + $( + #[inline] + $as_vis const fn $as_name(self) -> [$lane_ty; $lane_count] { + let bytes = self.to_le_bytes(); + let mut out = [0 as $lane_ty; $lane_count]; + let mut i = 0; + while i < $lane_count { + out[i] = lane_read!($lane_ty, bytes, i * $lane_bytes); + i += 1; + } + out + } + + #[inline] + $from_vis const fn $from_name(lanes: [$lane_ty; $lane_count]) -> Self { + let mut bytes = [0u8; 16]; + let mut i = 0; + while i < $lane_count { + let lane = lane_write!($lane_ty, lanes[i]); + let mut j = 0; + while j < $lane_bytes { + bytes[i * $lane_bytes + j] = lane[j]; + j += 1; + } + i += 1; + } + Self::from_le_bytes(bytes) + } + )* + }; +} diff --git a/crates/tinywasm/src/interpreter/simd/mod.rs b/crates/tinywasm/src/interpreter/simd/mod.rs new file mode 100644 index 00000000..4bf9d93d --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/mod.rs @@ -0,0 +1,124 @@ +#![cfg_attr(feature = "simd-x86", allow(unsafe_code))] + +#[macro_use] +mod macros; +mod instructions; +#[cfg(test)] +mod tests; +mod utils; + +#[cfg(target_arch = "wasm32")] +use core::arch::wasm32 as wasm; +#[cfg(target_arch = "wasm64")] +use core::arch::wasm64 as wasm; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +/// A 128-bit SIMD value +pub struct Value128(i128); + +impl From for i128 { + fn from(val: Value128) -> Self { + val.0 + } +} + +impl From for Value128 { + fn from(value: i128) -> Self { + Self(value) + } +} + +#[cfg_attr(any(target_arch = "wasm32", target_arch = "wasm64"), allow(unreachable_code))] +impl Value128 { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + #[inline(always)] + fn to_wasm_v128(self) -> wasm::v128 { + let b = self.to_le_bytes(); + wasm::u8x16( + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], + ) + } + + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + #[inline(always)] + #[rustfmt::skip] + fn from_wasm_v128(value: wasm::v128) -> Self { + Self::from_le_bytes([ wasm::u8x16_extract_lane::<0>(value), wasm::u8x16_extract_lane::<1>(value), wasm::u8x16_extract_lane::<2>(value), wasm::u8x16_extract_lane::<3>(value), wasm::u8x16_extract_lane::<4>(value), wasm::u8x16_extract_lane::<5>(value), wasm::u8x16_extract_lane::<6>(value), wasm::u8x16_extract_lane::<7>(value), wasm::u8x16_extract_lane::<8>(value), wasm::u8x16_extract_lane::<9>(value), wasm::u8x16_extract_lane::<10>(value), wasm::u8x16_extract_lane::<11>(value), wasm::u8x16_extract_lane::<12>(value), wasm::u8x16_extract_lane::<13>(value), wasm::u8x16_extract_lane::<14>(value), wasm::u8x16_extract_lane::<15>(value)]) + } + + #[inline] + pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self(i128::from_le_bytes(bytes)) + } + + #[inline] + pub const fn to_le_bytes(self) -> [u8; 16] { + self.0.to_le_bytes() + } + + impl_lane_accessors! { + as_i8x16 => from_i8x16: i8, 16, 1; + as_u8x16 => from_u8x16: u8, 16, 1; + as_i16x8 => from_i16x8: i16, 8, 2; + as_u16x8 => from_u16x8: u16, 8, 2; + as_i32x4 => pub from_i32x4: i32, 4, 4; + as_u32x4 => from_u32x4: u32, 4, 4; + as_f32x4 => from_f32x4: f32, 4, 4; + as_i64x2 => pub from_i64x2: i64, 2, 8; + as_u64x2 => from_u64x2: u64, 2, 8; + as_f64x2 => from_f64x2: f64, 2, 8; + } + + #[inline] + fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { + let bytes = self.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + for (src, dst) in bytes.chunks_exact(4).zip(out_bytes.chunks_exact_mut(4)) { + let lane = f32::from_bits(u32::from_le_bytes([src[0], src[1], src[2], src[3]])); + dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); + } + Self::from_le_bytes(out_bytes) + } + + #[inline] + fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { + let a_bytes = self.to_le_bytes(); + let b_bytes = rhs.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + + for ((a, b), dst) in a_bytes.chunks_exact(4).zip(b_bytes.chunks_exact(4)).zip(out_bytes.chunks_exact_mut(4)) { + let a_lane = f32::from_bits(u32::from_le_bytes([a[0], a[1], a[2], a[3]])); + let b_lane = f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])); + dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); + } + + Self::from_le_bytes(out_bytes) + } + + #[inline] + fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { + let bytes = self.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + for (src, dst) in bytes.chunks_exact(8).zip(out_bytes.chunks_exact_mut(8)) { + let lane = + f64::from_bits(u64::from_le_bytes([src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7]])); + dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); + } + Self::from_le_bytes(out_bytes) + } + + #[inline] + fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { + let a_bytes = self.to_le_bytes(); + let b_bytes = rhs.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + + for ((a, b), dst) in a_bytes.chunks_exact(8).zip(b_bytes.chunks_exact(8)).zip(out_bytes.chunks_exact_mut(8)) { + let a_lane = f64::from_bits(u64::from_le_bytes([a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]])); + let b_lane = f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])); + dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); + } + + Self::from_le_bytes(out_bytes) + } +} diff --git a/crates/tinywasm/src/interpreter/simd/tests.rs b/crates/tinywasm/src/interpreter/simd/tests.rs new file mode 100644 index 00000000..98a60cfa --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/tests.rs @@ -0,0 +1,60 @@ +use super::Value128; + +fn ref_swizzle(a: [u8; 16], idx: [u8; 16]) -> [u8; 16] { + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i]; + out[i] = if j < 16 { a[(j & 0x0f) as usize] } else { 0 }; + } + out +} + +fn ref_shuffle(a: [u8; 16], b: [u8; 16], idx: [u8; 16]) -> [u8; 16] { + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i] & 31; + out[i] = if j < 16 { a[j as usize] } else { b[(j & 0x0f) as usize] }; + } + out +} + +#[test] +fn swizzle_matches_reference() { + let a = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; + + for seed in 0u32..512 { + let mut s = [0u8; 16]; + let mut x = seed.wrapping_mul(0x9e37_79b9).wrapping_add(0x7f4a_7c15); + for byte in &mut s { + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *byte = (x & 0xff) as u8; + } + + let got = Value128::from_le_bytes(a).i8x16_swizzle(Value128::from_le_bytes(s)).to_le_bytes(); + let expected = ref_swizzle(a, s); + assert_eq!(got, expected, "seed={seed}"); + } +} + +#[test] +fn shuffle_matches_reference() { + let a = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f]; + let b = [0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf]; + + for seed in 0u32..512 { + let mut idx = [0u8; 16]; + let mut x = seed.wrapping_mul(0x85eb_ca6b).wrapping_add(0xc2b2_ae35); + for byte in &mut idx { + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *byte = (x & 0xff) as u8; + } + + let got = Value128::i8x16_shuffle(Value128::from_le_bytes(a), Value128::from_le_bytes(b), idx).to_le_bytes(); + let expected = ref_shuffle(a, b, idx); + assert_eq!(got, expected, "seed={seed}"); + } +} diff --git a/crates/tinywasm/src/interpreter/simd/utils.rs b/crates/tinywasm/src/interpreter/simd/utils.rs new file mode 100644 index 00000000..7f798719 --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/utils.rs @@ -0,0 +1,116 @@ +use super::Value128; + +#[cfg(not(feature = "std"))] +use crate::interpreter::no_std_floats::NoStdFloatExt; + +impl Value128 { + pub(super) fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { + debug_assert!(lane < lane_count); + let bytes = self.to_le_bytes(); + let start = lane as usize * LANE_BYTES; + let mut out = [0u8; LANE_BYTES]; + out.copy_from_slice(&bytes[start..start + LANE_BYTES]); + out + } + + pub(super) fn replace_lane_bytes( + self, + lane: u8, + value: [u8; LANE_BYTES], + lane_count: u8, + ) -> Self { + debug_assert!(lane < lane_count); + let mut bytes = self.to_le_bytes(); + let start = lane as usize * LANE_BYTES; + bytes[start..start + LANE_BYTES].copy_from_slice(&value); + Self::from_le_bytes(bytes) + } +} + +pub(super) const fn canonicalize_simd_f32_nan(x: f32) -> f32 { + #[cfg(feature = "canonicalize_nans")] + if x.is_nan() { + f32::NAN + } else { + x + } + #[cfg(not(feature = "canonicalize_nans"))] + x +} + +pub(super) const fn canonicalize_simd_f64_nan(x: f64) -> f64 { + #[cfg(feature = "canonicalize_nans")] + if x.is_nan() { + f64::NAN + } else { + x + } + #[cfg(not(feature = "canonicalize_nans"))] + x +} + +pub(super) const fn saturate_i16_to_i8(x: i16) -> i8 { + match x { + v if v > i8::MAX as i16 => i8::MAX, + v if v < i8::MIN as i16 => i8::MIN, + v => v as i8, + } +} + +pub(super) const fn saturate_i16_to_u8(x: i16) -> u8 { + match x { + v if v <= 0 => 0, + v if v > u8::MAX as i16 => u8::MAX, + v => v as u8, + } +} + +pub(super) const fn saturate_i32_to_i16(x: i32) -> i16 { + match x { + v if v > i16::MAX as i32 => i16::MAX, + v if v < i16::MIN as i32 => i16::MIN, + v => v as i16, + } +} + +pub(super) const fn saturate_i32_to_u16(x: i32) -> u16 { + match x { + v if v <= 0 => 0, + v if v > u16::MAX as i32 => u16::MAX, + v => v as u16, + } +} + +pub(super) fn trunc_sat_f32_to_i32(v: f32) -> i32 { + match v { + x if x.is_nan() => 0, + x if x <= i32::MIN as f32 - (1 << 8) as f32 => i32::MIN, + x if x >= (i32::MAX as f32 + 1.0) => i32::MAX, + x => x.trunc() as i32, + } +} + +pub(super) fn trunc_sat_f32_to_u32(v: f32) -> u32 { + match v { + x if x.is_nan() || x <= -1.0_f32 => 0, + x if x >= (u32::MAX as f32 + 1.0) => u32::MAX, + x => x.trunc() as u32, + } +} + +pub(super) fn trunc_sat_f64_to_i32(v: f64) -> i32 { + match v { + x if x.is_nan() => 0, + x if x <= i32::MIN as f64 - 1.0_f64 => i32::MIN, + x if x >= (i32::MAX as f64 + 1.0) => i32::MAX, + x => x.trunc() as i32, + } +} + +pub(super) fn trunc_sat_f64_to_u32(v: f64) -> u32 { + match v { + x if x.is_nan() || x <= -1.0_f64 => 0, + x if x >= (u32::MAX as f64 + 1.0) => u32::MAX, + x => x.trunc() as u32, + } +} diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs deleted file mode 100644 index e5d87a88..00000000 --- a/crates/tinywasm/src/interpreter/stack/block_stack.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{StackConfig, unlikely}; -use alloc::vec::Vec; - -use crate::interpreter::values::{StackHeight, StackLocation}; - -#[derive(Debug)] -pub(crate) struct BlockStack(Vec); - -impl BlockStack { - pub(crate) fn new(config: &StackConfig) -> Self { - Self(Vec::with_capacity(config.block_stack_init_size())) - } - - #[inline(always)] - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - #[inline(always)] - pub(crate) fn push(&mut self, block: BlockFrame) { - self.0.push(block); - } - - #[inline] - /// get the label at the given index, where 0 is the top of the stack - pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { - let len = (self.0.len() as u32) - offset; - - // the vast majority of wasm functions don't use break to return - if unlikely(index >= len) { - return None; - } - - Some(&self.0[self.0.len() - index as usize - 1]) - } - - #[inline(always)] - pub(crate) fn pop(&mut self) -> BlockFrame { - self.0.pop().expect("block stack underflow, this is a bug") - } - - /// keep the top `len` blocks and discard the rest - #[inline(always)] - pub(crate) fn truncate(&mut self, len: u32) { - self.0.truncate(len as usize); - } -} - -#[derive(Debug)] -pub(crate) struct BlockFrame { - pub(crate) instr_ptr: usize, // position of the instruction pointer when the block was entered - pub(crate) end_instr_offset: u32, // position of the end instruction of the block - - pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered - pub(crate) results: StackHeight, - pub(crate) params: StackHeight, - - pub(crate) ty: BlockType, -} - -#[derive(Debug)] -pub(crate) enum BlockType { - Loop, - If, - Else, - Block, -} diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index c7f02b66..dfc71bc6 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,222 +1,78 @@ -use core::ops::ControlFlow; +use crate::{Result, Trap, unlikely}; -use super::BlockType; -use crate::Trap; -use crate::interpreter::values::*; -use crate::{Error, unlikely}; +use alloc::vec::Vec; +use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCounts}; -use alloc::boxed::Box; -use alloc::{rc::Rc, vec, vec::Vec}; -use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; - -pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; - -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct CallStack { stack: Vec, } impl CallStack { - #[inline] - pub(crate) fn new(initial_frame: CallFrame) -> Self { - Self { stack: vec![initial_frame] } + pub(crate) fn new(config: &crate::engine::Config) -> Self { + Self { stack: Vec::with_capacity(config.max_call_stack_size) } } - #[inline] + pub(crate) fn clear(&mut self) { + self.stack.clear(); + } + + #[inline(always)] pub(crate) fn pop(&mut self) -> Option { self.stack.pop() } - #[inline] - pub(crate) fn push(&mut self, call_frame: CallFrame) -> ControlFlow> { - if unlikely((self.stack.len() + 1) >= MAX_CALL_STACK_SIZE) { - return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + #[inline(always)] + pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { + if unlikely(self.stack.len() == self.stack.capacity()) { + return Err(Trap::CallStackOverflow.into()); } self.stack.push(call_frame); - ControlFlow::Continue(()) + Ok(()) } } -#[derive(Debug)] +#[derive(Clone, Copy, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct CallFrame { - instr_ptr: usize, - func_instance: Rc, - block_ptr: u32, - module_addr: ModuleInstanceAddr, - pub(crate) locals: Locals, -} - -#[derive(Debug)] -pub(crate) struct Locals { - pub(crate) locals_32: Box<[Value32]>, - pub(crate) locals_64: Box<[Value64]>, - pub(crate) locals_128: Box<[Value128]>, - pub(crate) locals_ref: Box<[ValueRef]>, + pub(crate) instr_ptr: u32, + pub(crate) module_addr: ModuleInstanceAddr, + pub(crate) func_addr: FuncAddr, + pub(crate) locals_base: StackBase, + pub(crate) stack_offset: ValueCounts, } -impl Locals { - pub(crate) fn get(&self, local_index: LocalAddr) -> T { - T::local_get(self, local_index) - } - - pub(crate) fn set(&mut self, local_index: LocalAddr, value: T) { - T::local_set(self, local_index, value); - } +#[derive(Clone, Copy, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct StackBase { + pub(crate) s32: u32, + pub(crate) s64: u32, + pub(crate) s128: u32, + pub(crate) sref: u32, } impl CallFrame { - #[inline] - pub(crate) fn instr_ptr(&self) -> usize { - self.instr_ptr - } - - #[inline] - #[allow(dead_code)] - pub(crate) fn data(&self) -> &WasmFunctionData { - &self.func_instance.data - } - - #[inline] - pub(crate) fn incr_instr_ptr(&mut self) { - self.instr_ptr += 1; - } - - #[inline] - pub(crate) fn jump(&mut self, offset: usize) { - self.instr_ptr += offset; - } - - #[inline] - pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { - self.module_addr - } - - #[inline] - pub(crate) fn block_ptr(&self) -> u32 { - self.block_ptr - } - - #[inline(always)] - pub(crate) fn fetch_instr(&self) -> &Instruction { - match self.func_instance.instructions.get(self.instr_ptr) { - Some(instr) => instr, - None => unreachable!("Instruction out of bounds, this is a bug"), - } - } - - pub(crate) fn reuse_for( - &mut self, - func: Rc, - locals: Locals, - block_depth: u32, - module_addr: ModuleInstanceAddr, - ) { - self.func_instance = func; - self.module_addr = module_addr; - self.locals = locals; - self.block_ptr = block_depth; - self.instr_ptr = 0; // Reset to function entry - } - - /// 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, - values: &mut super::ValueStack, - blocks: &mut super::BlockStack, - ) -> Option<()> { - let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?; - - // instr_ptr points to the label instruction, but the next step - // 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 - values.truncate_keep(break_to.stack_ptr, break_to.params); - - // check if we're breaking to the loop - if break_to_relative != 0 { - // we also want to trim the label stack to the loop (but not including the loop) - blocks.truncate(blocks.len() as u32 - break_to_relative); - 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 - values.truncate_keep(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.instr_ptr + break_to.end_instr_offset as usize; - - // we also want to trim the label stack, including the block - blocks.truncate(blocks.len() as u32 - (break_to_relative + 1)); - } - } - - Some(()) - } - - #[inline] pub(crate) fn new( - wasm_func_inst: Rc, - owner: ModuleInstanceAddr, - params: &[WasmValue], - block_ptr: u32, + func_addr: FuncAddr, + module_addr: ModuleInstanceAddr, + locals_base: StackBase, + stack_offset: ValueCounts, ) -> Self { - let locals = { - let mut locals_32 = Vec::new(); - locals_32.reserve_exact(wasm_func_inst.locals.c32 as usize); - let mut locals_64 = Vec::new(); - locals_64.reserve_exact(wasm_func_inst.locals.c64 as usize); - let mut locals_128 = Vec::new(); - locals_128.reserve_exact(wasm_func_inst.locals.c128 as usize); - let mut locals_ref = Vec::new(); - locals_ref.reserve_exact(wasm_func_inst.locals.cref as usize); - - for p in params { - match p.into() { - TinyWasmValue::Value32(v) => locals_32.push(v), - TinyWasmValue::Value64(v) => locals_64.push(v), - TinyWasmValue::Value128(v) => locals_128.push(v), - TinyWasmValue::ValueRef(v) => locals_ref.push(v), - } - } - - locals_32.resize_with(wasm_func_inst.locals.c32 as usize, Default::default); - locals_64.resize_with(wasm_func_inst.locals.c64 as usize, Default::default); - locals_128.resize_with(wasm_func_inst.locals.c128 as usize, Default::default); - locals_ref.resize_with(wasm_func_inst.locals.cref as usize, Default::default); - - Locals { - locals_32: locals_32.into_boxed_slice(), - locals_64: locals_64.into_boxed_slice(), - locals_128: locals_128.into_boxed_slice(), - locals_ref: locals_ref.into_boxed_slice(), - } - }; - - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + Self { instr_ptr: 0, func_addr, module_addr, locals_base, stack_offset } } #[inline] - pub(crate) fn new_raw( - wasm_func_inst: Rc, - owner: ModuleInstanceAddr, - locals: Locals, - block_ptr: u32, - ) -> Self { - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + pub(crate) fn stack_base(&self) -> StackBase { + StackBase { + s32: self.locals_base.s32 + self.stack_offset.c32 as u32, + s64: self.locals_base.s64 + self.stack_offset.c64 as u32, + s128: self.locals_base.s128 + self.stack_offset.c128 as u32, + sref: self.locals_base.sref + self.stack_offset.cref as u32, + } } - #[inline] - pub(crate) fn instructions(&self) -> &[Instruction] { - &self.func_instance.instructions + #[inline(always)] + pub(crate) fn incr_instr_ptr(&mut self) { + self.instr_ptr += 1; } } diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index a4706f18..bb18fd3a 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -1,27 +1,25 @@ -mod block_stack; mod call_stack; mod value_stack; -pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; -pub(crate) use call_stack::{CallFrame, CallStack, Locals}; +pub(crate) use call_stack::{CallFrame, CallStack, StackBase}; pub(crate) use value_stack::ValueStack; -use crate::StackConfig; +use crate::engine::Config; /// A WebAssembly Stack -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct Stack { pub(crate) values: ValueStack, - pub(crate) blocks: BlockStack, pub(crate) call_stack: CallStack, } impl Stack { - pub(crate) fn new(call_frame: CallFrame, config: &StackConfig) -> Self { - Self { - values: ValueStack::new(config), - blocks: BlockStack::new(config), - call_stack: CallStack::new(call_frame), - } + pub(crate) fn new(config: &Config) -> Self { + Self { values: ValueStack::new(config), call_stack: CallStack::new(config) } + } + + pub(crate) fn clear(&mut self) { + self.values.clear(); + self.call_stack.clear(); } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 054ffe3c..12039290 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,183 +1,275 @@ +use alloc::boxed::Box; use alloc::vec::Vec; -use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; +use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCounts, WasmValue}; -use crate::{Result, StackConfig, interpreter::*}; +use crate::{Result, Trap, engine::Config, interpreter::*, unlikely}; -use super::Locals; +use super::{CallFrame, StackBase}; -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ValueStack { - pub(crate) stack_32: Vec, - pub(crate) stack_64: Vec, - pub(crate) stack_128: Vec, - pub(crate) stack_ref: Vec, + pub(crate) stack_32: Stack, + pub(crate) stack_64: Stack, + pub(crate) stack_128: Stack, + pub(crate) stack_ref: Stack, +} + +#[cfg_attr(feature = "debug", derive(Debug))] +pub(crate) struct Stack { + data: Box<[T]>, + len: usize, +} + +impl Stack { + pub(crate) fn with_size(size: usize) -> Self { + let mut data = Vec::with_capacity(size); + data.resize_with(size, T::default); + Self { data: data.into_boxed_slice(), len: 0 } + } + + pub(crate) fn clear(&mut self) { + self.len = 0; + } + + #[inline(always)] + pub(crate) fn push(&mut self, value: T) -> Result<()> { + if unlikely(self.len >= self.data.len()) { + return Err(Trap::ValueStackOverflow.into()); + } + self.data[self.len] = value; + self.len += 1; + Ok(()) + } + + #[inline(always)] + pub(crate) fn pop(&mut self) -> T { + if self.len == 0 { + unreachable!("ValueStack underflow, this is a bug"); + } + self.len -= 1; + self.data[self.len] + } + + #[inline(always)] + pub(crate) fn last(&self) -> &T { + if self.len == 0 { + unreachable!("ValueStack underflow, this is a bug"); + } + &self.data[self.len - 1] + } + + #[inline(always)] + pub(crate) fn get(&self, index: usize) -> T { + match self.data.get(index) { + Some(v) => *v, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + + #[inline(always)] + pub(crate) fn set(&mut self, index: usize, value: T) { + match self.data.get_mut(index) { + Some(v) => *v = value, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + + #[inline(always)] + pub(crate) fn get_mut(&mut self, index: usize) -> &mut T { + match self.data.get_mut(index) { + Some(v) => v, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + + pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { + debug_assert!(n <= self.len); + let len = self.len; + if n >= len { + return; + } + + if end_keep == 0 { + self.len = n; + return; + } + + let keep = (len - n).min(end_keep); + self.data.copy_within((len - keep)..len, n); + self.len = n + keep; + } + + pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result { + debug_assert!(param_count <= local_count); + let start = self.len - param_count; + let end = start + local_count; + + if unlikely(end > self.data.len()) { + return Err(Trap::ValueStackOverflow.into()); + } + + let init_start = start + param_count; + if init_start != end { + self.data[init_start..end].fill(T::default()); + } + self.len = end; + Ok(start as u32) + } + + pub(crate) fn select_many(&mut self, count: usize, condition: bool) { + if count == 0 { + return; + } + if self.len < count * 2 { + unreachable!("Stack underflow, this is a bug"); + } + + if !condition { + let start = self.len - (count * 2); + let second_start = self.len - count; + self.data.copy_within(second_start..self.len, start); + } + self.len -= count; + } } impl ValueStack { - pub(crate) fn new(config: &StackConfig) -> Self { + pub(crate) fn new(config: &Config) -> Self { Self { - stack_32: Vec::with_capacity(config.value_stack_32_init_size()), - stack_64: Vec::with_capacity(config.value_stack_64_init_size()), - stack_128: Vec::with_capacity(config.value_stack_128_init_size()), - stack_ref: Vec::with_capacity(config.value_stack_ref_init_size()), + stack_32: Stack::with_size(config.stack_32_size), + stack_64: Stack::with_size(config.stack_64_size), + stack_128: Stack::with_size(config.stack_128_size), + stack_ref: Stack::with_size(config.stack_ref_size), } } - pub(crate) fn height(&self) -> StackLocation { - StackLocation { - s32: self.stack_32.len() as u32, - s64: self.stack_64.len() as u32, - s128: self.stack_128.len() as u32, - sref: self.stack_ref.len() as u32, - } + pub(crate) fn clear(&mut self) { + self.stack_32.clear(); + self.stack_64.clear(); + self.stack_128.clear(); + self.stack_ref.clear(); } - #[inline] + #[inline(always)] + pub(crate) fn len(&self) -> usize { + self.stack_32.len + self.stack_64.len + self.stack_128.len + self.stack_ref.len + } + + #[inline(always)] pub(crate) fn peek(&self) -> T { T::stack_peek(self) } - #[inline] + #[inline(always)] pub(crate) fn pop(&mut self) -> T { T::stack_pop(self) } - #[inline] - pub(crate) fn push(&mut self, value: T) { - T::stack_push(self, value); + #[inline(always)] + pub(crate) fn push(&mut self, value: T) -> Result<()> { + T::stack_push(self, value) } - #[inline] + #[inline(always)] pub(crate) fn drop(&mut self) { T::stack_pop(self); } - #[inline] - pub(crate) fn select(&mut self) { + #[inline(always)] + pub(crate) fn select(&mut self) -> Result<()> { let cond: i32 = self.pop(); let val2: T = self.pop(); if cond == 0 { self.drop::(); - self.push(val2); + self.push(val2)?; } + Ok(()) } #[inline] - pub(crate) fn calculate_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { - T::stack_calculate(self, func) + pub(crate) fn select_multi(&mut self, counts: ValueCounts) { + let condition = self.pop::() != 0; + self.stack_32.select_many(counts.c32 as usize, condition); + self.stack_64.select_many(counts.c64 as usize, condition); + self.stack_128.select_many(counts.c128 as usize, condition); + self.stack_ref.select_many(counts.cref as usize, condition); } - #[inline] - #[allow(dead_code)] - pub(crate) fn calculate_same_3(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { - T::stack_calculate3(self, func) + pub(crate) fn pop_types<'a>( + &'a mut self, + val_types: impl IntoIterator, + ) -> impl core::iter::Iterator { + val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) } - #[inline] - pub(crate) fn calculate( - &mut self, - func: impl FnOnce(T, T) -> Result, - ) -> Result<()> { - let v2 = T::stack_pop(self); - let v1 = T::stack_pop(self); - U::stack_push(self, func(v1, v2)?); - Ok(()) - } + pub(crate) fn enter_locals(&mut self, params: &ValueCounts, locals: &ValueCounts) -> Result { + let locals_base32 = if params.c32 == 0 && locals.c32 == 0 { + self.stack_32.len as u32 + } else { + self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)? + }; + let locals_base64 = if params.c64 == 0 && locals.c64 == 0 { + self.stack_64.len as u32 + } else { + self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize)? + }; + let locals_base128 = if params.c128 == 0 && locals.c128 == 0 { + self.stack_128.len as u32 + } else { + self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize)? + }; + let locals_baseref = if params.cref == 0 && locals.cref == 0 { + self.stack_ref.len as u32 + } else { + self.stack_ref.enter_locals(params.cref as usize, locals.cref as usize)? + }; - #[inline] - #[allow(dead_code)] - pub(crate) fn calculate_diff( - &mut self, - func: impl FnOnce(A, B) -> Result, - ) -> Result<()> { - let v2 = B::stack_pop(self); - let v1 = A::stack_pop(self); - RES::stack_push(self, func(v1, v2)?); - Ok(()) + Ok(StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128, sref: locals_baseref }) } - #[inline] - pub(crate) fn replace_top( - &mut self, - func: impl FnOnce(T) -> Result, - ) -> Result<()> { - let v1 = T::stack_pop(self); - U::stack_push(self, func(v1)?); - Ok(()) - } + pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCounts) { + if keep.is_empty() { + self.stack_32.len = base.s32 as usize; + self.stack_64.len = base.s64 as usize; + self.stack_128.len = base.s128 as usize; + self.stack_ref.len = base.sref as usize; + return; + } - #[inline] - pub(crate) fn replace_top_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { - T::replace_top(self, func) + self.stack_32.truncate_keep(base.s32 as usize, keep.c32 as usize); + self.stack_64.truncate_keep(base.s64 as usize, keep.c64 as usize); + self.stack_128.truncate_keep(base.s128 as usize, keep.c128 as usize); + self.stack_ref.truncate_keep(base.sref as usize, keep.cref as usize); } - pub(crate) fn pop_params(&mut self, val_types: &[ValType]) -> Vec { - val_types.iter().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>() + #[inline] + pub(crate) fn local_get(&self, frame: &CallFrame, index: LocalAddr) -> T { + T::local_get(self, frame, index) } - pub(crate) fn pop_results(&mut self, val_types: &[ValType]) -> Vec { - let mut results = val_types.iter().rev().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>(); - results.reverse(); - results + #[inline] + pub(crate) fn local_update( + &mut self, + frame: &CallFrame, + index: LocalAddr, + func: impl FnOnce(&mut T), + ) { + T::local_update(self, frame, index, func) } #[inline] - pub(crate) fn pop_locals(&mut self, pc: ValueCountsSmall, lc: ValueCounts) -> Locals { - Locals { - locals_32: { - let mut locals_32 = { alloc::vec![Value32::default(); lc.c32 as usize].into_boxed_slice() }; - locals_32[0..pc.c32 as usize] - .copy_from_slice(&self.stack_32[(self.stack_32.len() - pc.c32 as usize)..]); - self.stack_32.truncate(self.stack_32.len() - pc.c32 as usize); - locals_32 - }, - locals_64: { - let mut locals_64 = { alloc::vec![Value64::default(); lc.c64 as usize].into_boxed_slice() }; - locals_64[0..pc.c64 as usize] - .copy_from_slice(&self.stack_64[(self.stack_64.len() - pc.c64 as usize)..]); - self.stack_64.truncate(self.stack_64.len() - pc.c64 as usize); - locals_64 - }, - locals_128: { - let mut locals_128 = { alloc::vec![Value128::default(); lc.c128 as usize].into_boxed_slice() }; - locals_128[0..pc.c128 as usize] - .copy_from_slice(&self.stack_128[(self.stack_128.len() - pc.c128 as usize)..]); - self.stack_128.truncate(self.stack_128.len() - pc.c128 as usize); - locals_128 - }, - locals_ref: { - let mut locals_ref = { alloc::vec![ValueRef::default(); lc.cref as usize].into_boxed_slice() }; - locals_ref[0..pc.cref as usize] - .copy_from_slice(&self.stack_ref[(self.stack_ref.len() - pc.cref as usize)..]); - self.stack_ref.truncate(self.stack_ref.len() - pc.cref as usize); - locals_ref - }, - } - } - - pub(crate) fn truncate_keep(&mut self, to: StackLocation, keep: StackHeight) { - #[inline(always)] - fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { - let len = data.len() as u32; - if len <= n { - return; // No need to truncate if the current size is already less than or equal to total_to_keep - } - data.drain((n as usize)..(len - end_keep) as usize); - } - - truncate_keep(&mut self.stack_32, to.s32, u32::from(keep.s32)); - truncate_keep(&mut self.stack_64, to.s64, u32::from(keep.s64)); - truncate_keep(&mut self.stack_128, to.s128, u32::from(keep.s128)); - truncate_keep(&mut self.stack_ref, to.sref, u32::from(keep.sref)); + pub(crate) fn local_set(&mut self, frame: &CallFrame, index: LocalAddr, value: T) { + T::local_set(self, frame, index, value); } - pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) { + pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<()> { match value { - TinyWasmValue::Value32(v) => self.stack_32.push(v), - TinyWasmValue::Value64(v) => self.stack_64.push(v), - TinyWasmValue::Value128(v) => self.stack_128.push(v), - TinyWasmValue::ValueRef(v) => self.stack_ref.push(v), + TinyWasmValue::Value32(v) => self.stack_32.push(v)?, + TinyWasmValue::Value64(v) => self.stack_64.push(v)?, + TinyWasmValue::Value128(v) => self.stack_128.push(v)?, + TinyWasmValue::ValueRef(v) => self.stack_ref.push(v)?, } + Ok(()) } pub(crate) fn pop_wasmvalue(&mut self, val_type: ValType) -> WasmValue { @@ -188,18 +280,22 @@ impl ValueStack { ValType::F64 => WasmValue::F64(self.pop()), ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(self.pop())), ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(self.pop())), - - #[cfg(not(feature = "unstable-simd"))] - ValType::V128 => WasmValue::V128(self.pop()), - - #[cfg(feature = "unstable-simd")] - ValType::V128 => WasmValue::V128(i128::from_le_bytes(self.pop::().to_array())), + ValType::V128 => WasmValue::V128(self.pop::().into()), } } - pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { + pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) -> Result<()> { for value in values { - self.push_dyn(value.into()); + match value { + WasmValue::I32(v) => self.stack_32.push(*v as u32)?, + WasmValue::I64(v) => self.stack_64.push(*v as u64)?, + WasmValue::F32(v) => self.stack_32.push(v.to_bits())?, + WasmValue::F64(v) => self.stack_64.push(v.to_bits())?, + WasmValue::RefExtern(v) => self.stack_ref.push(v.addr())?, + WasmValue::RefFunc(v) => self.stack_ref.push(v.addr())?, + WasmValue::V128(v) => self.stack_128.push((*v).into())?, + } } + Ok(()) } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 9cf90caa..54ba00f5 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,17 +1,13 @@ -use crate::Result; +use crate::{Result, interpreter::simd::Value128}; -use super::stack::{Locals, ValueStack}; -use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, WasmValue}; +use super::stack::{CallFrame, ValueStack}; +use tinywasm_types::LocalAddr; +use tinywasm_types::{ExternRef, FuncRef, ValType, WasmValue}; pub(crate) type Value32 = u32; pub(crate) type Value64 = u64; pub(crate) type ValueRef = Option; -#[cfg(feature = "unstable-simd")] -pub(crate) type Value128 = core::simd::u8x16; -#[cfg(not(feature = "unstable-simd"))] -pub(crate) type Value128 = i128; - #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// A untyped WebAssembly value pub enum TinyWasmValue { @@ -25,57 +21,12 @@ pub enum TinyWasmValue { ValueRef(ValueRef), } -#[derive(Debug, Clone, Copy)] -pub(crate) struct StackLocation { - pub(crate) s32: u32, - pub(crate) s64: u32, - pub(crate) s128: u32, - pub(crate) sref: u32, -} - -#[derive(Debug, Clone, Copy, Default)] -pub(crate) struct StackHeight { - pub(crate) s32: u16, - pub(crate) s64: u16, - pub(crate) s128: u16, - pub(crate) sref: u16, -} - -impl From for StackHeight { - fn from(value: ValType) -> Self { - match value { - ValType::I32 | ValType::F32 => Self { s32: 1, ..Default::default() }, - ValType::I64 | ValType::F64 => Self { s64: 1, ..Default::default() }, - ValType::V128 => Self { s128: 1, ..Default::default() }, - ValType::RefExtern | ValType::RefFunc => Self { sref: 1, ..Default::default() }, - } - } -} - -impl From<&[ValType]> for StackHeight { - fn from(value: &[ValType]) -> Self { - let mut s32 = 0; - let mut s64 = 0; - let mut s128 = 0; - let mut sref = 0; - for val_type in value { - match val_type { - ValType::I32 | ValType::F32 => s32 += 1, - ValType::I64 | ValType::F64 => s64 += 1, - ValType::V128 => s128 += 1, - ValType::RefExtern | ValType::RefFunc => sref += 1, - } - } - Self { s32, s64, s128, sref } - } -} - impl TinyWasmValue { /// Asserts that the value is a 32-bit value and returns it (panics if the value is the wrong size) pub fn unwrap_32(&self) -> Value32 { match self { Self::Value32(v) => *v, - _ => unreachable!("Expected Value32"), + _ => panic!("Expected Value32"), } } @@ -83,7 +34,7 @@ impl TinyWasmValue { pub fn unwrap_64(&self) -> Value64 { match self { Self::Value64(v) => *v, - _ => unreachable!("Expected Value64"), + _ => panic!("Expected Value64"), } } @@ -91,7 +42,7 @@ impl TinyWasmValue { pub fn unwrap_128(&self) -> Value128 { match self { Self::Value128(v) => *v, - _ => unreachable!("Expected Value128"), + _ => panic!("Expected Value128"), } } @@ -99,25 +50,25 @@ impl TinyWasmValue { pub fn unwrap_ref(&self) -> ValueRef { match self { Self::ValueRef(v) => *v, - _ => unreachable!("Expected ValueRef"), + _ => panic!("Expected ValueRef"), } } /// Attaches a type to the value (panics if the size of the value is not the same as the type) pub fn attach_type(&self, ty: ValType) -> WasmValue { - match ty { - ValType::I32 => WasmValue::I32(self.unwrap_32() as i32), - ValType::I64 => WasmValue::I64(self.unwrap_64() as i64), - ValType::F32 => WasmValue::F32(f32::from_bits(self.unwrap_32())), - ValType::F64 => WasmValue::F64(f64::from_bits(self.unwrap_64())), - ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(self.unwrap_ref())), - ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(self.unwrap_ref())), - - #[cfg(feature = "unstable-simd")] - ValType::V128 => WasmValue::V128(i128::from_le_bytes(self.unwrap_128().to_array())), - - #[cfg(not(feature = "unstable-simd"))] - ValType::V128 => WasmValue::V128(self.unwrap_128()), + match (self, ty) { + (Self::Value32(v), ValType::I32) => WasmValue::I32(*v as i32), + (Self::Value64(v), ValType::I64) => WasmValue::I64(*v as i64), + (Self::Value32(v), ValType::F32) => WasmValue::F32(f32::from_bits(*v)), + (Self::Value64(v), ValType::F64) => WasmValue::F64(f64::from_bits(*v)), + (Self::ValueRef(v), ValType::RefExtern) => WasmValue::RefExtern(ExternRef::new(*v)), + (Self::ValueRef(v), ValType::RefFunc) => WasmValue::RefFunc(FuncRef::new(*v)), + (Self::Value128(v), ValType::V128) => WasmValue::V128((*v).into()), + + (_, ValType::I32 | ValType::F32) => panic!("Expected Value32"), + (_, ValType::I64 | ValType::F64) => panic!("Expected Value64"), + (_, ValType::RefExtern | ValType::RefFunc) => panic!("Expected ValueRef"), + (_, ValType::V128) => panic!("Expected Value128"), } } } @@ -131,12 +82,7 @@ impl From<&WasmValue> for TinyWasmValue { WasmValue::F64(v) => Self::Value64(v.to_bits()), WasmValue::RefExtern(v) => Self::ValueRef(v.addr()), WasmValue::RefFunc(v) => Self::ValueRef(v.addr()), - - #[cfg(not(feature = "unstable-simd"))] - WasmValue::V128(v) => Self::Value128(*v), - - #[cfg(feature = "unstable-simd")] - WasmValue::V128(v) => TinyWasmValue::Value128(v.to_le_bytes().into()), + WasmValue::V128(v) => Self::Value128((*v).into()), } } } @@ -147,35 +93,28 @@ impl From for TinyWasmValue { } } +impl From for TinyWasmValue { + fn from(value: i128) -> Self { + Self::Value128(Value128::from(value)) + } +} + mod sealed { #[expect(unreachable_pub)] pub trait Sealed {} } -pub(crate) trait InternalValue: sealed::Sealed + Into { - fn stack_push(stack: &mut ValueStack, value: Self); - fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> - where - Self: Sized; - fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> - where - Self: Sized; - fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> - where - Self: Sized; - - fn stack_pop(stack: &mut ValueStack) -> Self - where - Self: Sized; - fn stack_peek(stack: &ValueStack) -> Self - where - Self: Sized; - fn local_get(locals: &Locals, index: LocalAddr) -> Self; - fn local_set(locals: &mut Locals, index: LocalAddr, value: Self); +pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + Default { + fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; + fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self; + fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, func: impl FnOnce(&mut Self)); + fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); + fn stack_pop(stack: &mut ValueStack) -> Self; + fn stack_peek(stack: &ValueStack) -> Self; } macro_rules! impl_internalvalue { - ($( $variant:ident, $stack:ident, $locals:ident, $internal:ty, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { + ($( $variant:ident, $stack:ident, $stack_base:ident, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { $( impl sealed::Sealed for $outer {} @@ -187,75 +126,36 @@ macro_rules! impl_internalvalue { impl InternalValue for $outer { #[inline(always)] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.$stack.push($to_internal(value)); - } - - #[inline(always)] - fn stack_pop(stack: &mut ValueStack) -> Self { - match stack.$stack.pop() { - Some(v) => $to_outer(v), - None => unreachable!("ValueStack underflow, this is a bug"), - } - } - - #[inline(always)] - fn stack_peek(stack: &ValueStack) -> Self { - match stack.$stack.last() { - Some(v) => $to_outer(*v), - None => unreachable!("ValueStack underflow, this is a bug"), - } + fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()> { + stack.$stack.push($to_internal(value)) } #[inline(always)] - fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { - let v2 = stack.$stack.pop(); - let v1 = stack.$stack.last_mut(); - let (Some(v1), Some(v2)) = (v1, v2) else { - unreachable!("ValueStack underflow, this is a bug"); - }; - - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2))?); - return Ok(()) + fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self { + $to_outer(stack.$stack.get(frame.locals_base.$stack_base as usize + index as usize)) } #[inline(always)] - fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { - let v3 = stack.$stack.pop(); - let v2 = stack.$stack.pop(); - let v1 = stack.$stack.last_mut(); - let (Some(v1), Some(v2), Some(v3)) = (v1, v2, v3) else { - unreachable!("ValueStack underflow, this is a bug"); - }; - - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2), $to_outer(v3))?); - return Ok(()) + fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, func: impl FnOnce(&mut Self)) { + let slot = stack.$stack.get_mut(frame.locals_base.$stack_base as usize + index as usize); + let mut value = $to_outer(*slot); + func(&mut value); + *slot = $to_internal(value); } #[inline(always)] - fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { - let Some(v) = stack.$stack.last_mut() else { - unreachable!("ValueStack underflow, this is a bug"); - }; - - *v = $to_internal(func($to_outer(*v))?); - Ok(()) + fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) { + stack.$stack.set(frame.locals_base.$stack_base as usize + index as usize, $to_internal(value)); } #[inline(always)] - fn local_get(locals: &Locals, index: LocalAddr) -> Self { - match locals.$locals.get(index as usize) { - Some(v) => $to_outer(*v), - None => unreachable!("Local variable out of bounds, this is a bug"), - } + fn stack_pop(stack: &mut ValueStack) -> Self { + $to_outer(stack.$stack.pop()) } #[inline(always)] - fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) { - match locals.$locals.get_mut(index as usize) { - Some(v) => *v = $to_internal(value), - None => unreachable!("Local variable out of bounds, this is a bug"), - } + fn stack_peek(stack: &ValueStack) -> Self { + $to_outer(*stack.$stack.last()) } } )* @@ -263,31 +163,12 @@ macro_rules! impl_internalvalue { } impl_internalvalue! { - Value32, stack_32, locals_32, u32, u32, |v| v, |v| v - Value64, stack_64, locals_64, u64, u64, |v| v, |v| v - Value32, stack_32, locals_32, u32, i32, |v: i32| u32::from_ne_bytes(v.to_ne_bytes()), |v: u32| i32::from_ne_bytes(v.to_ne_bytes()) - Value64, stack_64, locals_64, u64, i64, |v: i64| u64::from_ne_bytes(v.to_ne_bytes()), |v: u64| i64::from_ne_bytes(v.to_ne_bytes()) - Value32, stack_32, locals_32, u32, f32, f32::to_bits, f32::from_bits - Value64, stack_64, locals_64, u64, f64, f64::to_bits, f64::from_bits - ValueRef, stack_ref, locals_ref, ValueRef, ValueRef, |v| v, |v| v - Value128, stack_128, locals_128, Value128, Value128, |v| v, |v| v -} - -#[cfg(feature = "unstable-simd")] -use core::simd::{num::SimdUint, *}; - -#[cfg(feature = "unstable-simd")] -impl_internalvalue! { - Value128, stack_128, locals_128, u8x16, i128, |v: i128| v.to_le_bytes().into(), |v: u8x16| i128::from_le_bytes(v.into()) - Value128, stack_128, locals_128, u8x16, u128, |v: u128| v.to_le_bytes().into(), |v: u8x16| u128::from_le_bytes(v.into()) - Value128, stack_128, locals_128, u8x16, i8x16, |v: i8x16| v.to_le_bytes(), |v: u8x16| v.cast() - Value128, stack_128, locals_128, u8x16, i16x8, |v: i16x8| v.to_le_bytes(), |v: u8x16| i16x8::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, i32x4, |v: i32x4| v.to_le_bytes(), |v: u8x16| i32x4::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, i64x2, |v: i64x2| v.to_le_bytes(), |v: u8x16| i64x2::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, f32x4, |v: f32x4| v.to_le_bytes(), |v: u8x16| f32x4::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, f64x2, |v: f64x2| v.to_le_bytes(), |v: u8x16| f64x2::from_le_bytes(v) - - Value128, stack_128, locals_128, u8x16, u16x8, |v: u16x8| v.to_le_bytes(), |v: u8x16| u16x8::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, u32x4, |v: u32x4| v.to_le_bytes(), |v: u8x16| u32x4::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, u64x2, |v: u64x2| v.to_le_bytes(), |v: u8x16| u64x2::from_le_bytes(v) + Value32, stack_32, s32, u32, |v| v, |v| v + Value64, stack_64, s64, u64, |v| v, |v| v + Value32, stack_32, s32, i32, |v: i32| u32::from_ne_bytes(v.to_ne_bytes()), |v: u32| i32::from_ne_bytes(v.to_ne_bytes()) + Value64, stack_64, s64, i64, |v: i64| u64::from_ne_bytes(v.to_ne_bytes()), |v: u64| i64::from_ne_bytes(v.to_ne_bytes()) + Value32, stack_32, s32, f32, f32::to_bits, f32::from_bits + Value64, stack_64, s64, f64, f64::to_bits, f64::from_bits + ValueRef, stack_ref, sref, ValueRef, |v| v, |v| v + Value128, stack_128, s128, Value128, |v| v, |v| v } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 00378294..7afc2f65 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -3,9 +3,9 @@ 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)] -#![forbid(unsafe_code)] -#![cfg_attr(feature = "unstable-simd", feature(portable_simd))] +#![warn(missing_docs, rust_2018_idioms, unreachable_pub)] +#![cfg_attr(not(feature = "simd-x86"), forbid(unsafe_code))] +#![cfg_attr(feature = "simd-x86", deny(unsafe_code))] //! A tiny WebAssembly Runtime written in Rust //! @@ -16,7 +16,7 @@ //! ## Features //!- **`std`**\ //! Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. -//!- **`logging`**\ +//!- **`log`**\ //! Enables logging using the `log` crate. This is enabled by default. //!- **`parser`**\ //! Enables the `tinywasm-parser` crate. This is enabled by default. @@ -69,40 +69,17 @@ //! and other modules to be linked into the module when it is instantiated. //! //! See the [`Imports`] documentation for more information. -//! -//! ## Runtime Configuration -//! -//! For resource-constrained targets, you can configure the initial memory allocation: -//! -//! ```rust -//! use tinywasm::{Store, StackConfig}; -//! -//! // Create a store with minimal initial allocation (90% reduction in pre-allocated memory) -//! let config = StackConfig::new() -//! .with_value_stack_32_init_size(1024) // 1KB instead of 32KB -//! .with_value_stack_64_init_size(512) // 512B instead of 16KB -//! .with_value_stack_128_init_size(256) // 256B instead of 8KB -//! .with_value_stack_ref_init_size(128) // 128B instead of 1KB - -//! .with_block_stack_init_size(32); // 32 instead of 128 -//! let store = Store::with_config(config); -//! -//! // Or create a partial configuration (only override what you need) -//! let config = StackConfig::new() -//! .with_value_stack_32_init_size(2048); // Only override 32-bit stack size -//! let store = Store::with_config(config); -//! ``` mod std; extern crate alloc; // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] #[expect(clippy::single_component_path_imports)] use log; // noop fallback if logging is disabled. -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] #[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); @@ -115,7 +92,7 @@ pub(crate) mod log { mod error; pub use error::*; -pub use func::{FuncHandle, FuncHandleTyped}; +pub use func::{ExecProgress, FuncExecution, FuncExecutionTyped, FuncHandle, FuncHandleTyped}; pub use imports::*; pub use instance::ModuleInstance; pub use module::Module; @@ -129,13 +106,12 @@ mod module; mod reference; mod store; -/// Runtime for executing WebAssembly modules. -pub mod interpreter; -pub use interpreter::InterpreterRuntime; +mod interpreter; +use interpreter::InterpreterRuntime; -/// Configuration for the WebAssembly interpreter's stack preallocation. -pub mod config; -pub use config::StackConfig; +/// Global configuration for the WebAssembly interpreter +pub mod engine; +pub use engine::Engine; #[cfg(feature = "parser")] /// Re-export of [`tinywasm_parser`]. Requires `parser` feature. diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 26cea9c9..1ce3ff0c 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -4,18 +4,19 @@ use tinywasm_types::TinyWasmModule; /// A WebAssembly Module /// /// See -#[derive(Debug, Clone)] -pub struct Module(pub(crate) TinyWasmModule); +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct Module(pub(crate) alloc::sync::Arc); impl From<&TinyWasmModule> for Module { fn from(data: &TinyWasmModule) -> Self { - Self(data.clone()) + Self(alloc::sync::Arc::new(data.clone())) } } impl From for Module { fn from(data: TinyWasmModule) -> Self { - Self(data) + Self(alloc::sync::Arc::new(data)) } } @@ -23,24 +24,21 @@ impl Module { #[cfg(feature = "parser")] /// Parse a module from bytes. Requires `parser` feature. pub fn parse_bytes(wasm: &[u8]) -> Result { - let parser = tinywasm_parser::Parser::new(); - let data = parser.parse_module_bytes(wasm)?; + let data = tinywasm_parser::Parser::new().parse_module_bytes(wasm)?; Ok(data.into()) } #[cfg(all(feature = "parser", feature = "std"))] /// Parse a module from a file. Requires `parser` and `std` features. pub fn parse_file(path: impl AsRef + Clone) -> Result { - let parser = tinywasm_parser::Parser::new(); - let data = parser.parse_module_file(path)?; + let data = tinywasm_parser::Parser::new().parse_module_file(path)?; Ok(data.into()) } #[cfg(all(feature = "parser", feature = "std"))] /// Parse a module from a stream. Requires `parser` and `std` features. pub fn parse_stream(stream: impl crate::std::io::Read) -> Result { - let parser = tinywasm_parser::Parser::new(); - let data = parser.parse_module_stream(stream)?; + let data = tinywasm_parser::Parser::new().parse_module_stream(stream)?; Ok(data.into()) } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 790c3139..7901e572 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -8,11 +8,11 @@ use crate::{MemoryInstance, Result}; // This module essentially contains the public APIs to interact with the data stored in the store /// A reference to a memory instance -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); /// A borrowed reference to a memory instance -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance); impl MemoryRefLoad for MemoryRef<'_> { diff --git a/crates/tinywasm/src/store/data.rs b/crates/tinywasm/src/store/data.rs index 935e7612..fe6c4e7a 100644 --- a/crates/tinywasm/src/store/data.rs +++ b/crates/tinywasm/src/store/data.rs @@ -1,18 +1,16 @@ use alloc::vec::Vec; -use tinywasm_types::*; /// A WebAssembly Data Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", 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 new(data: Option>) -> Self { + Self { data } } pub(crate) fn drop(&mut self) { diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs index 40301a5b..c643f80c 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -5,16 +5,15 @@ use tinywasm_types::*; /// A WebAssembly Element Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ElementInstance { pub(crate) kind: ElementKind, pub(crate) items: Option>, // none is the element was dropped - pub(crate) _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 } + pub(crate) fn new(kind: ElementKind, items: Option>) -> Self { + Self { kind, items } } pub(crate) fn drop(&mut self) { diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index ef370c23..3067a8e8 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -2,10 +2,11 @@ use crate::Function; use alloc::rc::Rc; use tinywasm_types::*; -#[derive(Debug, Clone)] /// A WebAssembly Function Instance /// /// See +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] 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 index b7a47d65..95af73fb 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -5,15 +5,14 @@ use tinywasm_types::*; /// A WebAssembly Global Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct GlobalInstance { pub(crate) value: Cell, pub(crate) ty: GlobalType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl GlobalInstance { - pub(crate) fn new(ty: GlobalType, value: TinyWasmValue, owner: ModuleInstanceAddr) -> Self { - Self { ty, value: value.into(), _owner: owner } + pub(crate) fn new(ty: GlobalType, value: TinyWasmValue) -> Self { + Self { ty, value: value.into() } } } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index b8ca1e84..d20fac0f 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -1,49 +1,35 @@ use alloc::vec; use alloc::vec::Vec; -use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; +use tinywasm_types::{MemoryArch, MemoryType}; use crate::{Error, Result, cold, interpreter::Value128, log}; -#[cfg(feature = "unstable-simd")] -use core::simd::ToBytes; - /// A WebAssembly Memory Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", 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 { + pub(crate) fn new(kind: MemoryType) -> Self { assert!(kind.page_count_initial() <= kind.page_count_max()); log::debug!("initializing memory with {} pages of {} bytes", kind.page_count_initial(), kind.page_size()); - - Self { - kind, - data: vec![0; kind.initial_size() as usize], - page_count: kind.page_count_initial() as usize, - _owner: owner, - } + Self { kind, data: vec![0; kind.initial_size() as usize], page_count: kind.page_count_initial() as usize } } - #[inline] - pub(crate) fn is_64bit(&self) -> bool { + pub(crate) const fn is_64bit(&self) -> bool { matches!(self.kind.arch(), MemoryArch::I64) } - #[inline(always)] - pub(crate) fn len(&self) -> usize { + pub(crate) const fn len(&self) -> usize { self.data.len() } - #[inline(never)] - #[cold] - fn trap_oob(&self, addr: usize, len: usize) -> Error { + const fn trap_oob(&self, addr: usize, len: usize) -> Error { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }) } @@ -61,10 +47,6 @@ impl MemoryInstance { Ok(()) } - pub(crate) fn max_pages(&self) -> usize { - self.kind.page_count_max().try_into().unwrap_or(usize::MAX) - } - pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[u8]> { let Some(end) = addr.checked_add(len) else { cold(); @@ -79,7 +61,7 @@ impl MemoryInstance { Ok(&self.data[addr..end]) } - pub(crate) fn load_as>(&self, addr: usize) -> Result { + pub(crate) fn load_as>(&self, addr: usize) -> Result { let Some(end) = addr.checked_add(SIZE) else { return Err(self.trap_oob(addr, SIZE)); }; @@ -99,7 +81,7 @@ impl MemoryInstance { if end > self.data.len() { return Err(self.trap_oob(addr, len)); } - self.data[addr..end].fill_with(|| val); + self.data[addr..end].fill(val); Ok(()) } @@ -131,38 +113,44 @@ impl MemoryInstance { Ok(()) } - #[inline] pub(crate) fn grow(&mut self, pages_delta: i64) -> Option { + if pages_delta < 0 { + log::debug!("memory.grow failed: negative delta {}", pages_delta); + return None; + } + let current_pages = self.page_count; - let new_pages = current_pages as i64 + pages_delta; + let pages_delta = usize::try_from(pages_delta).ok()?; + let new_pages = current_pages.checked_add(pages_delta)?; + let max_pages = self.kind.page_count_max().try_into().unwrap_or(usize::MAX); - if new_pages < 0 || new_pages as usize > self.max_pages() { - log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, self.max_pages()); - log::debug!("{} {}", self.kind.page_count_max(), self.kind.page_size()); + if new_pages > max_pages { + log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, max_pages); return None; } - let new_size = (new_pages as u64 * self.kind.page_size()) as usize; - if new_size as u64 > self.kind.max_size() { + let new_size = (new_pages as u64).checked_mul(self.kind.page_size())?; + if new_size > self.kind.max_size() { + log::debug!("memory.grow failed: new_size={}, max_size={}", new_size, self.kind.max_size()); return None; } - // Zero initialize the new pages - self.data.reserve_exact(new_size); - self.data.resize_with(new_size, Default::default); - self.page_count = new_pages as usize; - Some(current_pages as i64) + let new_size = usize::try_from(new_size).ok()?; + if new_size == self.data.len() { + return i64::try_from(current_pages).ok(); + } + + self.data.resize(new_size, 0); + self.page_count = new_pages; + i64::try_from(current_pages).ok() } } -/// A trait for types that can be stored in memory -pub(crate) trait MemStorable { +/// A trait for types that can be converted to and from static byte arrays +pub(crate) trait MemValue: Copy + Default { /// Store a value in memory fn to_mem_bytes(self) -> [u8; N]; -} -/// A trait for types that can be loaded from memory -pub(crate) trait MemLoadable: Sized + Copy { /// Load a value from memory fn from_mem_bytes(bytes: [u8; N]) -> Self; } @@ -170,17 +158,15 @@ pub(crate) trait MemLoadable: Sized + Copy { macro_rules! impl_mem_traits { ($($ty:ty, $size:expr),*) => { $( - impl MemLoadable<$size> for $ty { + impl MemValue<$size> for $ty { #[inline(always)] fn from_mem_bytes(bytes: [u8; $size]) -> Self { - <$ty>::from_le_bytes(bytes.into()) + <$ty>::from_le_bytes(bytes) } - } - impl MemStorable<$size> for $ty { #[inline(always)] fn to_mem_bytes(self) -> [u8; $size] { - self.to_le_bytes().into() + self.to_le_bytes() } } )* @@ -189,28 +175,6 @@ macro_rules! impl_mem_traits { impl_mem_traits!(u8, 1, i8, 1, u16, 2, i16, 2, u32, 4, i32, 4, f32, 4, u64, 8, i64, 8, f64, 8, Value128, 16); -#[cfg(feature = "unstable-simd")] -impl_mem_traits!( - core::simd::i8x16, - 16, - core::simd::i16x8, - 16, - core::simd::i32x4, - 16, - core::simd::i64x2, - 16, - core::simd::u16x8, - 16, - core::simd::u32x4, - 16, - core::simd::u64x2, - 16, - core::simd::f32x4, - 16, - core::simd::f64x2, - 16 -); - #[cfg(test)] mod memory_instance_tests { use super::*; @@ -218,8 +182,7 @@ mod memory_instance_tests { fn create_test_memory() -> MemoryInstance { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let owner = ModuleInstanceAddr::default(); - MemoryInstance::new(kind, owner) + MemoryInstance::new(kind) } #[test] @@ -286,11 +249,19 @@ mod memory_instance_tests { assert_eq!(memory.grow(1), None); } + #[test] + fn test_memory_grow_negative_delta() { + let mut memory = create_test_memory(); + let original_pages = memory.page_count; + + assert_eq!(memory.grow(-1), None); + assert_eq!(memory.page_count, original_pages); + } + #[test] fn test_memory_custom_page_size_out_of_bounds() { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); - let owner = ModuleInstanceAddr::default(); - let mut memory = MemoryInstance::new(kind, owner); + let mut memory = MemoryInstance::new(kind); let data_to_store = [1, 2]; assert!(memory.store(0, data_to_store.len(), &data_to_store).is_err()); @@ -299,8 +270,7 @@ mod memory_instance_tests { #[test] fn test_memory_custom_page_size_grow() { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); - let owner = ModuleInstanceAddr::default(); - let mut memory = MemoryInstance::new(kind, owner); + let mut memory = MemoryInstance::new(kind); assert_eq!(memory.grow(1), Some(1)); diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 8446297d..aae8b351 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,10 +1,12 @@ +use alloc::rc::Rc; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; -use core::fmt::Debug; use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; -use crate::interpreter::{self, InterpreterRuntime, TinyWasmValue}; -use crate::{Error, Function, ModuleInstance, Result, StackConfig, Trap, cold}; +use crate::instance::ModuleInstanceInner; +use crate::interpreter::TinyWasmValue; +use crate::interpreter::stack::Stack; +use crate::{Engine, Error, Function, ModuleInstance, Result, Trap}; mod data; mod element; @@ -29,54 +31,50 @@ static STORE_ID: AtomicUsize = AtomicUsize::new(0); /// See pub struct Store { id: usize, - module_instances: Vec, + module_instances: Vec>, - pub(crate) data: StoreData, - pub(crate) runtime: Runtime, - pub(crate) config: StackConfig, + pub(crate) engine: Engine, + pub(crate) execution_fuel: u32, + pub(crate) state: State, + pub(crate) stack: Stack, } -impl Debug for Store { +#[cfg(feature = "debug")] +impl core::fmt::Debug for Store { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Store") .field("id", &self.id) .field("module_instances", &self.module_instances) .field("data", &"...") - .field("runtime", &self.runtime) + .field("engine", &self.engine) .finish() } } -#[derive(Debug, Clone, Copy)] -pub(crate) enum Runtime { - Default, -} - impl Store { /// Create a new store - pub fn new() -> Self { - Self::default() - } - - /// Create a new store with the given stack configuration - pub fn with_config(config: StackConfig) -> Self { + pub fn new(engine: Engine) -> Self { let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - Self { id, module_instances: Vec::new(), data: StoreData::default(), runtime: Runtime::Default, config } + Self { + id, + module_instances: Vec::new(), + state: State::default(), + stack: Stack::new(engine.config()), + engine, + execution_fuel: 0, + } } /// Get a module instance by the internal id - pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { - 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() + pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option { + Some(ModuleInstance(self.module_instances.get(addr as usize)?.clone())) } - /// Create a new store with the given runtime - pub(crate) fn runtime(&self) -> interpreter::InterpreterRuntime { - match self.runtime { - Runtime::Default => InterpreterRuntime::default(), + #[inline] + pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> &Rc { + match self.module_instances.get(addr as usize) { + Some(instance) => instance, + None => unreachable!("module instance {addr} not found. This should be unreachable"), } } } @@ -89,14 +87,7 @@ impl PartialEq for Store { impl Default for Store { fn default() -> Self { - let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - Self { - id, - module_instances: Vec::new(), - data: StoreData::default(), - runtime: Runtime::Default, - config: StackConfig::default(), - } + Self::new(Engine::default()) } } @@ -105,158 +96,192 @@ impl Default for Store { /// /// Data should only be addressable by the module that owns it /// See -pub(crate) struct StoreData { +pub(crate) struct State { pub(crate) funcs: Vec, pub(crate) tables: Vec, pub(crate) memories: Vec, pub(crate) globals: Vec, pub(crate) elements: Vec, - pub(crate) datas: Vec, + pub(crate) data: Vec, } -impl Store { - /// Get the store's ID (unique per process) - pub fn id(&self) -> usize { - self.id - } - - pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { - self.module_instances.len() as ModuleInstanceAddr - } - - pub(crate) fn add_instance(&mut self, instance: ModuleInstance) { - assert!(instance.id() == self.module_instances.len() as ModuleInstanceAddr); - self.module_instances.push(instance); - } - - #[cold] - fn not_found_error(name: &str) -> Error { - Error::Other(format!("{name} not found")) - } - +impl State { /// Get the function at the actual index in the store - #[inline] pub(crate) fn get_func(&self, addr: FuncAddr) -> &FunctionInstance { - &self.data.funcs[addr as usize] + match self.funcs.get(addr as usize) { + Some(func) => func, + None => unreachable!("function {addr} not found. This should be unreachable"), + } + } + + /// Get a wasm function at the actual index in the store, panicking if it's a host function (which should be guaranteed by the validator) + pub(crate) fn get_wasm_func(&self, addr: FuncAddr) -> &Rc { + match self.funcs.get(addr as usize) { + Some(func) => match &func.func { + Function::Wasm(wasm_func) => wasm_func, + Function::Host(_) => unreachable!( + "expected a wasm function at address {addr}, but found a host function. This should be unreachable" + ), + }, + None => unreachable!("function {addr} not found. This should be unreachable"), + } } /// Get the memory at the actual index in the store - #[inline] pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { - &self.data.memories[addr as usize] + match self.memories.get(addr as usize) { + Some(mem) => mem, + None => unreachable!("memory {addr} not found. This should be unreachable"), + } } /// Get the memory at the actual index in the store - #[inline(always)] pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { - &mut self.data.memories[addr as usize] + match self.memories.get_mut(addr as usize) { + Some(mem) => mem, + None => unreachable!("memory {addr} not found. This should be unreachable"), + } } /// Get the memory at the actual index in the store - #[inline(always)] pub(crate) fn get_mems_mut( &mut self, addr: MemAddr, addr2: MemAddr, ) -> Result<(&mut MemoryInstance, &mut MemoryInstance)> { - match get_pair_mut(&mut self.data.memories, addr as usize, addr2 as usize) { + match get_pair_mut(&mut self.memories, addr as usize, addr2 as usize) { Some(mems) => Ok(mems), - None => { - cold(); - Err(Self::not_found_error("memory")) - } + None => unreachable!("memory {addr} or {addr2} not found. This should be unreachable"), } } /// Get the table at the actual index in the store - #[inline] pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { - &self.data.tables[addr as usize] + match self.tables.get(addr as usize) { + Some(table) => table, + None => unreachable!("table {addr} not found. This should be unreachable"), + } } /// Get the table at the actual index in the store - #[inline] pub(crate) fn get_table_mut(&mut self, addr: TableAddr) -> &mut TableInstance { - &mut self.data.tables[addr as usize] + match self.tables.get_mut(addr as usize) { + Some(table) => table, + None => unreachable!("table {addr} not found. This should be unreachable"), + } } /// Get two mutable tables at the actual index in the store - #[inline] pub(crate) fn get_tables_mut( &mut self, addr: TableAddr, addr2: TableAddr, ) -> Result<(&mut TableInstance, &mut TableInstance)> { - match get_pair_mut(&mut self.data.tables, addr as usize, addr2 as usize) { + match get_pair_mut(&mut self.tables, addr as usize, addr2 as usize) { Some(tables) => Ok(tables), - None => { - cold(); - Err(Self::not_found_error("table")) - } + None => unreachable!("table {addr} or {addr2} not found. This should be unreachable"), } } /// Get the data at the actual index in the store - #[inline] pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { - &mut self.data.datas[addr as usize] + match self.data.get_mut(addr as usize) { + Some(data) => data, + None => unreachable!("data {addr} not found. This should be unreachable"), + } } /// Get the element at the actual index in the store - #[inline] pub(crate) fn get_elem_mut(&mut self, addr: ElemAddr) -> &mut ElementInstance { - &mut self.data.elements[addr as usize] + match self.elements.get_mut(addr as usize) { + Some(elem) => elem, + None => unreachable!("element {addr} not found. This should be unreachable"), + } } /// Get the global at the actual index in the store - #[inline] pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { - &self.data.globals[addr as usize] + match self.globals.get(addr as usize) { + Some(global) => global, + None => unreachable!("global {addr} not found. This should be unreachable"), + } + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { + match self.globals.get(addr as usize) { + Some(global) => global.value.get(), + None => unreachable!("global {addr} not found. This should be unreachable"), + } + } + + /// Set the global at the actual index in the store + pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { + match self.globals.get_mut(addr as usize) { + Some(global) => global.value.set(value), + None => unreachable!("global {addr} not found. This should be unreachable"), + } + } +} + +impl Store { + /// Get the store's ID (unique per process) + pub fn id(&self) -> usize { + self.id + } + + pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { + self.module_instances.len() as ModuleInstanceAddr + } + + pub(crate) fn add_instance(&mut self, instance: Rc) { + assert!(instance.idx == self.module_instances.len() as ModuleInstanceAddr); + self.module_instances.push(instance); } /// Get the global at the actual index in the store #[doc(hidden)] pub fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { - self.data.globals[addr as usize].value.get() + self.state.get_global_val(addr) } /// Set the global at the actual index in the store #[doc(hidden)] pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { - self.data.globals[addr as usize].value.set(value); + self.state.set_global_val(addr, value); } } // Linking related functions impl Store { /// Add functions to the store, returning their addresses in the store - pub(crate) fn init_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Result> { - let func_count = self.data.funcs.len(); + pub(crate) fn init_funcs(&mut self, funcs: &[WasmFunction], idx: ModuleInstanceAddr) -> Result> { + let func_count = self.state.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); - for (i, func) in funcs.into_iter().enumerate() { - self.data.funcs.push(FunctionInstance::new_wasm(func, idx)); + for (i, func) in funcs.iter().enumerate() { + self.state.funcs.push(FunctionInstance::new_wasm(func.clone(), idx)); func_addrs.push((i + func_count) as FuncAddr); } Ok(func_addrs) } /// Add tables to the store, returning their addresses in the store - pub(crate) fn init_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Result> { - let table_count = self.data.tables.len(); + pub(crate) fn init_tables(&mut self, tables: &[TableType], _idx: ModuleInstanceAddr) -> Result> { + let table_count = self.state.tables.len(); let mut table_addrs = Vec::with_capacity(table_count); - for (i, table) in tables.into_iter().enumerate() { - self.data.tables.push(TableInstance::new(table, idx)); + for (i, table) in tables.iter().enumerate() { + self.state.tables.push(TableInstance::new(table.clone())); table_addrs.push((i + table_count) as TableAddr); } Ok(table_addrs) } /// Add memories to the store, returning their addresses in the store - pub(crate) fn init_memories(&mut self, memories: Vec, idx: ModuleInstanceAddr) -> Result> { - let mem_count = self.data.memories.len(); + pub(crate) fn init_memories(&mut self, memories: &[MemoryType], _idx: ModuleInstanceAddr) -> Result> { + let mem_count = self.state.memories.len(); let mut mem_addrs = Vec::with_capacity(mem_count); - for (i, mem) in memories.into_iter().enumerate() { - self.data.memories.push(MemoryInstance::new(mem, idx)); + for (i, mem) in memories.iter().enumerate() { + self.state.memories.push(MemoryInstance::new(*mem)); mem_addrs.push((i + mem_count) as MemAddr); } Ok(mem_addrs) @@ -266,20 +291,17 @@ impl Store { pub(crate) fn init_globals( &mut self, mut imported_globals: Vec, - new_globals: Vec, + new_globals: &[Global], func_addrs: &[FuncAddr], - idx: ModuleInstanceAddr, + _idx: ModuleInstanceAddr, ) -> Result> { - let global_count = self.data.globals.len(); + let global_count = self.state.globals.len(); imported_globals.reserve_exact(new_globals.len()); let mut global_addrs = imported_globals; for (i, global) in new_globals.iter().enumerate() { - self.data.globals.push(GlobalInstance::new( - global.ty, - self.eval_const(&global.init, &global_addrs, func_addrs)?, - idx, - )); + let value = self.eval_const(&global.init, &global_addrs, func_addrs)?; + self.state.globals.push(GlobalInstance::new(global.ty, value)); global_addrs.push((i + global_count) as Addr); } @@ -299,11 +321,16 @@ impl Store { let addr = globals.get(*addr as usize).copied().ok_or_else(|| { Error::Other(format!("global {addr} not found. This should have been caught by the validator")) })?; - self.data.globals[addr as usize].value.get().unwrap_ref() + self.state.globals[addr as usize].value.get().unwrap_ref() } + #[cfg(feature = "debug")] ElementItem::Expr(item) => { return Err(Error::UnsupportedFeature(format!("const expression other than ref: {item:?}"))); } + #[cfg(not(feature = "debug"))] + ElementItem::Expr(_) => { + return Err(Error::UnsupportedFeature("const expression other than ref".to_string())); + } }; Ok(res) @@ -317,9 +344,9 @@ impl Store { func_addrs: &[FuncAddr], global_addrs: &[Addr], elements: &[Element], - idx: ModuleInstanceAddr, + _idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { - let elem_count = self.data.elements.len(); + let elem_count = self.state.elements.len(); let mut elem_addrs = Vec::with_capacity(elem_count); for (i, element) in elements.iter().enumerate() { let init = element @@ -343,7 +370,7 @@ impl Store { .copied() .ok_or_else(|| Error::Other(format!("table {table} not found for element {i}")))?; - let Some(table) = self.data.tables.get_mut(table_addr as usize) else { + let Some(table) = self.state.tables.get_mut(table_addr as usize) else { return Err(Error::Other(format!("table {table} not found for element {i}"))); }; @@ -361,7 +388,7 @@ impl Store { } }; - self.data.elements.push(ElementInstance::new(element.kind, idx, items)); + self.state.elements.push(ElementInstance::new(element.kind, items)); elem_addrs.push((i + elem_count) as Addr); } @@ -370,15 +397,15 @@ impl Store { } /// Add data to the store, returning their addresses in the store - pub(crate) fn init_datas( + pub(crate) fn init_data( &mut self, mem_addrs: &[MemAddr], - datas: Vec, - idx: ModuleInstanceAddr, + data: &[Data], + _idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { - let data_count = self.data.datas.len(); + let data_count = self.state.data.len(); let mut data_addrs = Vec::with_capacity(data_count); - for (i, data) in datas.into_iter().enumerate() { + for (i, data) in data.iter().enumerate() { let data_val = match data.kind { tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { let Some(mem_addr) = mem_addrs.get(mem_addr as usize) else { @@ -386,7 +413,7 @@ impl Store { }; let offset = self.eval_size_const(offset)?; - let Some(mem) = self.data.memories.get_mut(*mem_addr as usize) else { + let Some(mem) = self.state.memories.get_mut(*mem_addr as usize) else { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; @@ -399,7 +426,7 @@ impl Store { tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), }; - self.data.datas.push(DataInstance::new(data_val, idx)); + self.state.data.push(DataInstance::new(data_val)); data_addrs.push((i + data_count) as Addr); } @@ -407,45 +434,53 @@ impl Store { Ok((data_addrs.into_boxed_slice(), None)) } - pub(crate) fn add_global(&mut self, ty: GlobalType, value: TinyWasmValue, idx: ModuleInstanceAddr) -> Result { - self.data.globals.push(GlobalInstance::new(ty, value, idx)); - Ok(self.data.globals.len() as Addr - 1) + pub(crate) fn add_global( + &mut self, + ty: GlobalType, + value: TinyWasmValue, + _idx: ModuleInstanceAddr, + ) -> Result { + self.state.globals.push(GlobalInstance::new(ty, value)); + Ok(self.state.globals.len() as Addr - 1) } - pub(crate) fn add_table(&mut self, table: TableType, idx: ModuleInstanceAddr) -> Result { - self.data.tables.push(TableInstance::new(table, idx)); - Ok(self.data.tables.len() as TableAddr - 1) + pub(crate) fn add_table(&mut self, table: TableType, _idx: ModuleInstanceAddr) -> Result { + self.state.tables.push(TableInstance::new(table)); + Ok(self.state.tables.len() as TableAddr - 1) } - pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { + pub(crate) fn add_mem(&mut self, mem: MemoryType, _idx: ModuleInstanceAddr) -> Result { if let MemoryArch::I64 = mem.arch() { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } - self.data.memories.push(MemoryInstance::new(mem, idx)); - Ok(self.data.memories.len() as MemAddr - 1) + self.state.memories.push(MemoryInstance::new(mem)); + Ok(self.state.memories.len() as MemAddr - 1) } pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { - self.data.funcs.push(FunctionInstance { func, owner: idx }); - Ok(self.data.funcs.len() as FuncAddr - 1) + self.state.funcs.push(FunctionInstance { func, owner: idx }); + Ok(self.state.funcs.len() as FuncAddr - 1) } /// Evaluate a constant expression that's either a i32 or a i64 as a global or a const instruction - pub(crate) fn eval_size_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result { + fn eval_size_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result { Ok(match const_instr { ConstInstruction::I32Const(i) => i64::from(i), ConstInstruction::I64Const(i) => i, - ConstInstruction::GlobalGet(addr) => match self.data.globals[addr as usize].value.get() { + ConstInstruction::GlobalGet(addr) => match self.state.globals[addr as usize].value.get() { TinyWasmValue::Value32(i) => i64::from(i), TinyWasmValue::Value64(i) => i as i64, - o => return Err(Error::Other(format!("expected i32 or i64, got {o:?}"))), + other => return Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), }, - o => return Err(Error::Other(format!("expected i32, got {o:?}"))), + #[cfg(feature = "debug")] + other => return Err(Error::Other(format!("expected i32, got {other:?}"))), + #[cfg(not(feature = "debug"))] + _ => return Err(Error::Other("expected i32 or i64".to_string())), }) } /// Evaluate a constant expression - pub(crate) fn eval_const( + fn eval_const( &self, const_instr: &tinywasm_types::ConstInstruction, module_global_addrs: &[Addr], @@ -464,7 +499,7 @@ impl Store { })?; let global = - self.data.globals.get(*addr as usize).expect("global not found. This should be unreachable"); + self.state.globals.get(*addr as usize).expect("global not found. This should be unreachable"); global.value.get() } RefFunc(None) => TinyWasmValue::ValueRef(None), diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index bc4429da..7bbda838 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,4 +1,3 @@ -use crate::log; use crate::{Error, Result, Trap}; use alloc::{vec, vec::Vec}; use tinywasm_types::*; @@ -8,16 +7,15 @@ const MAX_TABLE_SIZE: u32 = 10_000_000; /// A WebAssembly Table Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", 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 new(kind: TableType) -> Self { + Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind } } #[inline(never)] @@ -143,14 +141,13 @@ impl TableInstance { 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(()) } } -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) enum TableElement { Uninitialized, Initialized(TableAddr), @@ -193,14 +190,14 @@ mod tests { #[test] fn test_table_instance_creation() { let kind = dummy_table_type(); - let table_instance = TableInstance::new(kind.clone(), 0); + let table_instance = TableInstance::new(kind.clone()); assert_eq!(table_instance.size(), kind.size_initial as i32, "Table instance creation failed: size mismatch"); } #[test] fn test_get_wasm_val() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); + let mut table_instance = TableInstance::new(kind); table_instance.set(0, TableElement::Initialized(0)).expect("Setting table element failed"); table_instance.set(1, TableElement::Uninitialized).expect("Setting table element failed"); @@ -224,7 +221,7 @@ mod tests { #[test] fn test_set_and_get() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); + let mut table_instance = TableInstance::new(kind); let result = table_instance.set(0, TableElement::Initialized(1)); assert!(result.is_ok(), "Setting table element failed"); @@ -239,7 +236,7 @@ mod tests { #[test] fn test_table_init() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); + let mut table_instance = TableInstance::new(kind); let init_elements = vec![TableElement::Initialized(0); 5]; let result = table_instance.init(0, &init_elements); diff --git a/crates/tinywasm/tests/generated/wasm-2.csv b/crates/tinywasm/tests/generated/wasm-2.csv index 8e228b45..267ea738 100644 --- a/crates/tinywasm/tests/generated/wasm-2.csv +++ b/crates/tinywasm/tests/generated/wasm-2.csv @@ -10,4 +10,4 @@ 0.6.1,20278,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"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":"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":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":179,"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":186,"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_grow.wast","passed":96,"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":"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.wast","passed":19,"failed":0},{"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.7.0,20278,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"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":"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":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":179,"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":186,"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_grow.wast","passed":96,"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":"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.wast","passed":19,"failed":0},{"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.8.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"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":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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":104,"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":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"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":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"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.9.0-alpha.0,27822,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":79,"failed":0},{"name":"binary.wast","passed":160,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"comments.wast","passed":4,"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":58,"failed":0},{"name":"elem.wast","passed":74,"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":161,"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":108,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":239,"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":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":147,"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":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":50,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":2,"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":6,"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.9.0-alpha.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":104,"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":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"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":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"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/tinywasm/tests/generated/wasm-annotations.csv b/crates/tinywasm/tests/generated/wasm-annotations.csv index 1b990330..b1bfe45d 100644 --- a/crates/tinywasm/tests/generated/wasm-annotations.csv +++ b/crates/tinywasm/tests/generated/wasm-annotations.csv @@ -1,2 +1,2 @@ 0.8.0,142,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"annotations/simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"token.wast","passed":61,"failed":0}] -0.9.0-alpha.0,142,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"token.wast","passed":61,"failed":0}] +0.9.0-alpha.0,617,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"token.wast","passed":61,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv index 5ba121e3..cdfeeb52 100644 --- a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv +++ b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv @@ -1,2 +1,2 @@ 0.8.0,57,0,[{"name":"custom-page-sizes-invalid.wast","passed":22,"failed":0},{"name":"custom-page-sizes.wast","passed":35,"failed":0}] -0.9.0-alpha.0,76,0,[{"name":"custom-page-sizes-invalid.wast","passed":23,"failed":0},{"name":"custom-page-sizes.wast","passed":45,"failed":0},{"name":"memory_max.wast","passed":4,"failed":0},{"name":"memory_max_i64.wast","passed":4,"failed":0}] +0.9.0-alpha.0,211,0,[{"name":"binary.wast","passed":127,"failed":0},{"name":"custom-page-sizes-invalid.wast","passed":23,"failed":0},{"name":"custom-page-sizes.wast","passed":45,"failed":0},{"name":"memory_max.wast","passed":8,"failed":0},{"name":"memory_max_i64.wast","passed":8,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-function-references.csv b/crates/tinywasm/tests/generated/wasm-function-references.csv new file mode 100644 index 00000000..0f67ba14 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-function-references.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,1536,331,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":0,"failed":9},{"name":"br_on_null.wast","passed":0,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_ref.wast","passed":3,"failed":31},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":146,"failed":21},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":4},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":10,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":35,"failed":8},{"name":"type-equivalence.wast","passed":7,"failed":7},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":10}] diff --git a/crates/tinywasm/tests/generated/wasm-gc.csv b/crates/tinywasm/tests/generated/wasm-gc.csv new file mode 100644 index 00000000..f09637b7 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-gc.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,80,703,[{"name":"array.wast","passed":6,"failed":48},{"name":"array_copy.wast","passed":4,"failed":31},{"name":"array_fill.wast","passed":3,"failed":27},{"name":"array_init_data.wast","passed":2,"failed":44},{"name":"array_init_elem.wast","passed":3,"failed":33},{"name":"array_new_data.wast","passed":0,"failed":28},{"name":"array_new_elem.wast","passed":0,"failed":24},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"br_on_cast.wast","passed":6,"failed":31},{"name":"br_on_cast_fail.wast","passed":6,"failed":31},{"name":"extern.wast","passed":0,"failed":18},{"name":"i31.wast","passed":2,"failed":71},{"name":"ref_cast.wast","passed":0,"failed":45},{"name":"ref_eq.wast","passed":6,"failed":83},{"name":"ref_test.wast","passed":0,"failed":71},{"name":"struct.wast","passed":5,"failed":25},{"name":"type-subtyping.wast","passed":36,"failed":93}] diff --git a/crates/tinywasm/tests/generated/wasm-latest.csv b/crates/tinywasm/tests/generated/wasm-latest.csv index 27a4e864..93e892b9 100644 --- a/crates/tinywasm/tests/generated/wasm-latest.csv +++ b/crates/tinywasm/tests/generated/wasm-latest.csv @@ -1 +1 @@ -0.9.0-alpha.0,28010,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":128,"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":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"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":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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"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":178,"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":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":104,"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":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"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":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"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.9.0-alpha.0,20698,531,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"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":59,"failed":6},{"name":"elem.wast","passed":137,"failed":14},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"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":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":53,"failed":71},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"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":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory_grow.wast","passed":106,"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":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"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.wast","passed":37,"failed":9},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"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/tinywasm/tests/generated/wasm-memory64.csv b/crates/tinywasm/tests/generated/wasm-memory64.csv index 039fe8cf..20afc3b8 100644 --- a/crates/tinywasm/tests/generated/wasm-memory64.csv +++ b/crates/tinywasm/tests/generated/wasm-memory64.csv @@ -1,2 +1,2 @@ 0.8.0,15081,3214,[{"name":"address.wast","passed":260,"failed":0},{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"address64.wast","passed":0,"failed":242},{"name":"align.wast","passed":161,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"align64.wast","passed":83,"failed":73},{"name":"annotations.wast","passed":74,"failed":0},{"name":"array_copy.wast","passed":4,"failed":31},{"name":"array_fill.wast","passed":3,"failed":14},{"name":"array_init_data.wast","passed":2,"failed":31},{"name":"array_init_elem.wast","passed":3,"failed":20},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"binary-leb128.wast","passed":92,"failed":1},{"name":"binary.wast","passed":124,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_cast.wast","passed":6,"failed":31},{"name":"br_on_cast_fail.wast","passed":6,"failed":31},{"name":"br_on_non_null.wast","passed":1,"failed":9},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_indirect.wast","passed":47,"failed":124},{"name":"call_ref.wast","passed":4,"failed":31},{"name":"data.wast","passed":59,"failed":6},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"elem.wast","passed":137,"failed":14},{"name":"endianness64.wast","passed":0,"failed":69},{"name":"exports.wast","passed":97,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"float_memory64.wast","passed":0,"failed":90},{"name":"func.wast","passed":175,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":99,"failed":82},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking.wast","passed":122,"failed":41},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"load64.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory64.wast","passed":10,"failed":53},{"name":"memory64/array.wast (skipped)","passed":0,"failed":0},{"name":"memory64/extern.wast (skipped)","passed":0,"failed":0},{"name":"memory64/global.wast (skipped)","passed":0,"failed":0},{"name":"memory64/i31.wast (skipped)","passed":0,"failed":0},{"name":"memory64/ref_null.wast (skipped)","passed":0,"failed":0},{"name":"memory64/select.wast (skipped)","passed":0,"failed":0},{"name":"memory64/simd_address.wast (skipped)","passed":0,"failed":0},{"name":"memory64/simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"memory64/struct.wast (skipped)","passed":0,"failed":0},{"name":"memory64/table.wast (skipped)","passed":0,"failed":0},{"name":"memory_copy.wast","passed":8385,"failed":515},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill.wast","passed":164,"failed":36},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_grow64.wast","passed":0,"failed":49},{"name":"memory_init.wast","passed":307,"failed":173},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_redundancy64.wast","passed":0,"failed":8},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"memory_trap64.wast","passed":0,"failed":172},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_cast.wast","passed":0,"failed":45},{"name":"ref_eq.wast","passed":6,"failed":83},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_test.wast","passed":0,"failed":71},{"name":"return_call.wast","passed":18,"failed":27},{"name":"return_call_indirect.wast","passed":31,"failed":45},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"simd_memory-multi.wast","passed":0,"failed":1},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table_copy.wast","passed":1742,"failed":30},{"name":"table_copy_mixed.wast","passed":3,"failed":1},{"name":"table_fill.wast","passed":9,"failed":71},{"name":"table_get.wast","passed":5,"failed":12},{"name":"table_grow.wast","passed":36,"failed":43},{"name":"table_init.wast","passed":588,"failed":288},{"name":"table_set.wast","passed":7,"failed":21},{"name":"table_size.wast","passed":2,"failed":38},{"name":"tag.wast","passed":1,"failed":8},{"name":"throw.wast","passed":3,"failed":10},{"name":"throw_ref.wast","passed":2,"failed":13},{"name":"token.wast","passed":61,"failed":0},{"name":"traps0.wast","passed":15,"failed":0},{"name":"try_table.wast","passed":11,"failed":51},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":6,"failed":14},{"name":"type-subtyping.wast","passed":16,"failed":86},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11}] -0.9.0-alpha.0,1556,42,[{"name":"address.wast","passed":260,"failed":0},{"name":"address64.wast","passed":242,"failed":0},{"name":"align64.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":93,"failed":0},{"name":"binary.wast","passed":169,"failed":0},{"name":"endianness64.wast","passed":69,"failed":0},{"name":"float_memory64.wast","passed":90,"failed":0},{"name":"load64.wast","passed":97,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory64.wast","passed":65,"failed":0},{"name":"memory_grow64.wast","passed":49,"failed":0},{"name":"memory_redundancy64.wast","passed":8,"failed":0},{"name":"memory_trap64.wast","passed":172,"failed":0},{"name":"simd_address.wast","passed":7,"failed":42}] +0.9.0-alpha.0,1598,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"address64.wast","passed":242,"failed":0},{"name":"align64.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":93,"failed":0},{"name":"binary.wast","passed":169,"failed":0},{"name":"endianness64.wast","passed":69,"failed":0},{"name":"float_memory64.wast","passed":90,"failed":0},{"name":"load64.wast","passed":97,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory64.wast","passed":65,"failed":0},{"name":"memory_grow64.wast","passed":49,"failed":0},{"name":"memory_redundancy64.wast","passed":8,"failed":0},{"name":"memory_trap64.wast","passed":172,"failed":0},{"name":"simd_address.wast","passed":49,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-multi-memory.csv b/crates/tinywasm/tests/generated/wasm-multi-memory.csv index 33debd64..9036cebf 100644 --- a/crates/tinywasm/tests/generated/wasm-multi-memory.csv +++ b/crates/tinywasm/tests/generated/wasm-multi-memory.csv @@ -1,2 +1,2 @@ 0.8.0,1872,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"multi-memory/simd_memory-multi.wast (skipped)","passed":0,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] -0.9.0-alpha.0,1871,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":6,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"simd_memory-multi.wast (skipped)","passed":0,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] +0.9.0-alpha.0,912,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":51,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_size_import.wast","passed":7,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"store2.wast","passed":25,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv b/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv new file mode 100644 index 00000000..3dff47cb --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,615,0,[{"name":"conversions.wast","passed":615,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-reference-types.csv b/crates/tinywasm/tests/generated/wasm-reference-types.csv new file mode 100644 index 00000000..c81aecca --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-reference-types.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,8971,161,[{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":134,"failed":8},{"name":"br_table.wast","passed":172,"failed":0},{"name":"bulk.wast","passed":111,"failed":6},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":31,"failed":14},{"name":"elem.wast","passed":46,"failed":14},{"name":"exports.wast","passed":84,"failed":0},{"name":"global.wast","passed":86,"failed":0},{"name":"imports.wast","passed":158,"failed":0},{"name":"linking.wast","passed":108,"failed":24},{"name":"memory_copy.wast","passed":4432,"failed":18},{"name":"memory_fill.wast","passed":96,"failed":4},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":226,"failed":14},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"select.wast","passed":141,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1703,"failed":25},{"name":"table_fill.wast","passed":42,"failed":3},{"name":"table_get.wast","passed":12,"failed":4},{"name":"table_grow.wast","passed":50,"failed":0},{"name":"table_init.wast","passed":762,"failed":18},{"name":"table_set.wast","passed":18,"failed":8},{"name":"table_size.wast","passed":39,"failed":0},{"name":"unreached-invalid.wast","passed":110,"failed":1}] diff --git a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv index a6e6228e..5520022a 100644 --- a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv @@ -1 +1 @@ -0.9.0-alpha.0,0,93,[{"name":"i16x8_relaxed_q15mulr_s.wast","passed":0,"failed":3},{"name":"i32x4_relaxed_trunc.wast","passed":0,"failed":17},{"name":"i8x16_relaxed_swizzle.wast","passed":0,"failed":6},{"name":"relaxed_dot_product.wast","passed":0,"failed":11},{"name":"relaxed_laneselect.wast","passed":0,"failed":12},{"name":"relaxed_madd_nmadd.wast","passed":0,"failed":19},{"name":"relaxed_min_max.wast","passed":0,"failed":25}] +0.9.0-alpha.0,77,0,[{"name":"i16x8_relaxed_q15mulr_s.wast","passed":3,"failed":0},{"name":"i32x4_relaxed_trunc.wast","passed":1,"failed":0},{"name":"i8x16_relaxed_swizzle.wast","passed":6,"failed":0},{"name":"relaxed_dot_product.wast","passed":11,"failed":0},{"name":"relaxed_laneselect.wast","passed":12,"failed":0},{"name":"relaxed_madd_nmadd.wast","passed":19,"failed":0},{"name":"relaxed_min_max.wast","passed":25,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv b/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv new file mode 100644 index 00000000..e883c5e7 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,872,0,[{"name":"i32.wast","passed":458,"failed":0},{"name":"i64.wast","passed":414,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv index 73e3b920..0ed87b6b 100644 --- a/crates/tinywasm/tests/generated/wasm-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -1,2 +1,2 @@ 0.8.0,1300,24679,[{"name":"simd_address.wast","passed":4,"failed":45},{"name":"simd_align.wast","passed":46,"failed":54},{"name":"simd_bit_shift.wast","passed":39,"failed":213},{"name":"simd_bitwise.wast","passed":28,"failed":141},{"name":"simd_boolean.wast","passed":16,"failed":261},{"name":"simd_const.wast","passed":301,"failed":456},{"name":"simd_conversions.wast","passed":48,"failed":234},{"name":"simd_f32x4.wast","passed":16,"failed":774},{"name":"simd_f32x4_arith.wast","passed":16,"failed":1806},{"name":"simd_f32x4_cmp.wast","passed":24,"failed":2583},{"name":"simd_f32x4_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f32x4_rounding.wast","passed":24,"failed":177},{"name":"simd_f64x2.wast","passed":8,"failed":795},{"name":"simd_f64x2_arith.wast","passed":16,"failed":1809},{"name":"simd_f64x2_cmp.wast","passed":24,"failed":2661},{"name":"simd_f64x2_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f64x2_rounding.wast","passed":24,"failed":177},{"name":"simd_i16x8_arith.wast","passed":11,"failed":183},{"name":"simd_i16x8_arith2.wast","passed":19,"failed":153},{"name":"simd_i16x8_cmp.wast","passed":30,"failed":435},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":4,"failed":17},{"name":"simd_i16x8_extmul_i8x16.wast","passed":12,"failed":105},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":3,"failed":27},{"name":"simd_i16x8_sat_arith.wast","passed":16,"failed":206},{"name":"simd_i32x4_arith.wast","passed":11,"failed":183},{"name":"simd_i32x4_arith2.wast","passed":26,"failed":123},{"name":"simd_i32x4_cmp.wast","passed":40,"failed":435},{"name":"simd_i32x4_dot_i16x8.wast","passed":3,"failed":27},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":4,"failed":17},{"name":"simd_i32x4_extmul_i16x8.wast","passed":12,"failed":105},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":4,"failed":103},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":4,"failed":103},{"name":"simd_i64x2_arith.wast","passed":11,"failed":189},{"name":"simd_i64x2_arith2.wast","passed":2,"failed":23},{"name":"simd_i64x2_cmp.wast","passed":10,"failed":103},{"name":"simd_i64x2_extmul_i32x4.wast","passed":12,"failed":105},{"name":"simd_i8x16_arith.wast","passed":8,"failed":123},{"name":"simd_i8x16_arith2.wast","passed":25,"failed":186},{"name":"simd_i8x16_cmp.wast","passed":30,"failed":415},{"name":"simd_i8x16_sat_arith.wast","passed":24,"failed":190},{"name":"simd_int_to_int_extend.wast","passed":24,"failed":229},{"name":"simd_lane.wast","passed":189,"failed":286},{"name":"simd_linking.wast","passed":0,"failed":3},{"name":"simd_load.wast","passed":8,"failed":31},{"name":"simd_load16_lane.wast","passed":3,"failed":33},{"name":"simd_load32_lane.wast","passed":3,"failed":21},{"name":"simd_load64_lane.wast","passed":3,"failed":13},{"name":"simd_load8_lane.wast","passed":3,"failed":49},{"name":"simd_load_extend.wast","passed":18,"failed":86},{"name":"simd_load_splat.wast","passed":12,"failed":114},{"name":"simd_load_zero.wast","passed":10,"failed":29},{"name":"simd_splat.wast","passed":23,"failed":162},{"name":"simd_store.wast","passed":9,"failed":19},{"name":"simd_store16_lane.wast","passed":3,"failed":33},{"name":"simd_store32_lane.wast","passed":3,"failed":21},{"name":"simd_store64_lane.wast","passed":3,"failed":13},{"name":"simd_store8_lane.wast","passed":3,"failed":49}] -0.9.0-alpha.0,24450,1539,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":757,"failed":0},{"name":"simd_conversions.wast","passed":50,"failed":232},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":187,"failed":14},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1824,"failed":1},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":187,"failed":14},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":142,"failed":30},{"name":"simd_i16x8_cmp.wast","passed":436,"failed":29},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":5,"failed":16},{"name":"simd_i16x8_extmul_i8x16.wast","passed":13,"failed":104},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":450,"failed":25},{"name":"simd_i32x4_dot_i16x8.wast","passed":14,"failed":18},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":5,"failed":16},{"name":"simd_i32x4_extmul_i16x8.wast","passed":13,"failed":104},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":17,"failed":90},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":5,"failed":102},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":13,"failed":104},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":179,"failed":32},{"name":"simd_i8x16_cmp.wast","passed":409,"failed":36},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":25,"failed":228},{"name":"simd_lane.wast","passed":331,"failed":144},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":37,"failed":2},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":20,"failed":84},{"name":"simd_load_splat.wast","passed":14,"failed":112},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":183,"failed":2},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] +0.9.0-alpha.0,25990,0,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":758,"failed":0},{"name":"simd_conversions.wast","passed":282,"failed":0},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":201,"failed":0},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1825,"failed":0},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":201,"failed":0},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":172,"failed":0},{"name":"simd_i16x8_cmp.wast","passed":465,"failed":0},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":21,"failed":0},{"name":"simd_i16x8_extmul_i8x16.wast","passed":117,"failed":0},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":475,"failed":0},{"name":"simd_i32x4_dot_i16x8.wast","passed":32,"failed":0},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":21,"failed":0},{"name":"simd_i32x4_extmul_i16x8.wast","passed":117,"failed":0},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":107,"failed":0},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":107,"failed":0},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":117,"failed":0},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":211,"failed":0},{"name":"simd_i8x16_cmp.wast","passed":445,"failed":0},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":253,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":39,"failed":0},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":104,"failed":0},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":185,"failed":0},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-threads.csv b/crates/tinywasm/tests/generated/wasm-threads.csv new file mode 100644 index 00000000..e43e34a4 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-threads.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,357,262,[{"name":"atomic.wast","passed":48,"failed":249},{"name":"exports.wast","passed":82,"failed":6},{"name":"imports.wast","passed":147,"failed":5},{"name":"memory.wast","passed":80,"failed":2}] diff --git a/crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv b/crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv new file mode 100644 index 00000000..10d76858 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,109,0,[{"name":"wide-arithmetic.wast","passed":109,"failed":0}] diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 93894359..0e4ba699 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -9,69 +9,62 @@ use tinywasm_types::ExternRef; const VAL_LISTS: &[&[WasmValue]] = &[ &[], &[WasmValue::I32(0)], - &[WasmValue::I32(0), WasmValue::I32(0)], // 2 of the same - &[WasmValue::I32(0), WasmValue::I32(0), WasmValue::F64(0.0)], // add another type - &[WasmValue::I32(0), WasmValue::F64(0.0), WasmValue::I32(0)], // reorder - &[WasmValue::RefExtern(ExternRef::null()), WasmValue::F64(0.0), WasmValue::I32(0)], // all different types + &[WasmValue::I32(0), WasmValue::I32(0)], + &[WasmValue::I32(0), WasmValue::I32(0), WasmValue::F64(0.0)], + &[WasmValue::I32(0), WasmValue::F64(0.0), WasmValue::I32(0)], + &[WasmValue::RefExtern(ExternRef::null()), WasmValue::F64(0.0), WasmValue::I32(0)], ]; -// (f64, i32, i32) and (f64) can be used to "match_none" -fn get_type_lists() -> impl Iterator + Clone> + Clone { - VAL_LISTS.iter().map(|l| l.iter().map(WasmValue::val_type)) +fn value_types(values: &[WasmValue]) -> Box<[ValType]> { + values.iter().map(WasmValue::val_type).collect() } -fn get_modules() -> Vec<(Module, FuncType, Vec)> { - let mut result = Vec::<(Module, FuncType, Vec)>::new(); - let val_and_tys = get_type_lists().zip(VAL_LISTS); - for res_types in get_type_lists() { - for (arg_types, arg_vals) in val_and_tys.clone() { - let ty = FuncType { results: res_types.clone().collect(), params: arg_types.collect() }; - result.push((proxy_module(&ty), ty, arg_vals.to_vec())); + +fn module_cases() -> Vec<(Module, FuncType, Vec)> { + let mut cases = Vec::<(Module, FuncType, Vec)>::new(); + for results in VAL_LISTS { + for params in VAL_LISTS { + let func_ty = FuncType { results: value_types(results), params: value_types(params) }; + cases.push((proxy_module(&func_ty), func_ty, params.to_vec())); } } - result + cases } #[test] fn test_return_invalid_type() -> Result<()> { - // try to return from host functions types that don't match their signatures - let mod_list = get_modules(); + let cases = module_cases(); - for (module, func_ty, test_args) in mod_list { - for result_to_try in VAL_LISTS { + for (module, func_ty, args) in cases { + for returned_values in VAL_LISTS { let mut store = Store::default(); let mut imports = Imports::new(); imports - .define("host", "hfn", Extern::func(&func_ty, |_: FuncContext<'_>, _| Ok(result_to_try.to_vec()))) + .define("host", "hfn", Extern::func(&func_ty, |_: FuncContext<'_>, _| Ok(returned_values.to_vec()))) .unwrap(); let instance = module.clone().instantiate(&mut store, Some(imports)).unwrap(); let caller = instance.exported_func_untyped(&store, "call_hfn").unwrap(); - let res_types_returned = result_to_try.iter().map(WasmValue::val_type); - dbg!(&res_types_returned, &func_ty); - let res_types_expected = &func_ty.results; - let should_succeed = res_types_returned.eq(res_types_expected.iter().cloned()); - // Extern::func that returns wrong type(s) can only be detected when it runs - let call_res = caller.call(&mut store, &test_args); - dbg!(&call_res); + // Return-type mismatch is only observable at call time. + let should_succeed = returned_values.iter().map(WasmValue::val_type).eq(func_ty.results.iter().copied()); + let call_res = caller.call(&mut store, &args); assert_eq!(call_res.is_ok(), should_succeed); - println!("this time ok"); } } + Ok(()) } #[test] fn test_linking_invalid_untyped_func() -> Result<()> { - // try to import host functions with function types no matching those expected by modules - let mod_list = get_modules(); - for (module, actual_func_ty, _) in &mod_list { - for (_, func_ty_to_try, _) in &mod_list { + let cases = module_cases(); + for (module, expected_func_ty, _) in &cases { + for (_, func_ty_to_try, _) in &cases { let tried_fn = Extern::func(func_ty_to_try, |_: FuncContext<'_>, _| panic!("not intended to be called")); let mut store = Store::default(); let mut imports = Imports::new(); imports.define("host", "hfn", tried_fn).unwrap(); - let should_succeed = func_ty_to_try == actual_func_ty; + let should_succeed = func_ty_to_try == expected_func_ty; let link_res = module.clone().instantiate(&mut store, Some(imports)); assert_eq!(link_res.is_ok(), should_succeed); @@ -83,37 +76,35 @@ fn test_linking_invalid_untyped_func() -> Result<()> { #[test] fn test_linking_invalid_typed_func() -> Result<()> { type Existing = (i32, i32, f64); - type NonMatchingOne = f64; - type NonMatchingMul = (f64, i32, i32); + type NonMatchingSingle = f64; + type NonMatchingTuple = (f64, i32, i32); const DONT_CALL: &str = "not meant to be called"; - // they don't match any signature from get_modules() - #[rustfmt::skip] // to make it table-like - let matching_none= &[ - Extern::typed_func(|_, _: NonMatchingMul| -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingMul| -> tinywasm::Result<()> { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result<()> { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: Existing | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: Existing | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: () | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: () | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result { panic!("{DONT_CALL}") } ), + // None of these typed host signatures are produced by module_cases(). + let matching_none = vec![ + Extern::typed_func(|_, _: NonMatchingTuple| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingTuple| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: Existing| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: Existing| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), ]; - let mod_list = get_modules(); - for (module, _, _) in mod_list { - for typed_fn in matching_none.clone() { + let cases = module_cases(); + for (module, _, _) in cases { + for typed_fn in matching_none.iter().cloned() { let mut store = Store::default(); let mut imports = Imports::new(); imports.define("host", "hfn", typed_fn).unwrap(); let link_failure = module.clone().instantiate(&mut store, Some(imports)); - link_failure.expect_err("no func in matching_none list should link to any mod"); + assert!(link_failure.is_err(), "Expected linking to fail for mismatched typed func, but it succeeded"); } } - // the valid cases are well-checked in other tests Ok(()) } @@ -129,9 +120,6 @@ fn to_name(ty: &ValType) -> &str { } } -// make a module with imported function {module:"host", name:"hfn"} that takes specified results and returns specified params -// and 2 wasm functions: call_hfn takes params, passes them to hfn and returns it's results -// and 2 wasm functions: call_hfn_discard takes params, passes them to hfn and drops it's results fn proxy_module(func_ty: &FuncType) -> Module { let results = func_ty.results.as_ref(); let params = func_ty.params.as_ref(); @@ -139,7 +127,7 @@ fn proxy_module(func_ty: &FuncType) -> Module { if list.is_empty() { return "".to_string(); } - let step = list.iter().map(|ty| format!("{} ", to_name(ty)).to_string()).collect::(); + let step = list.iter().map(|ty| format!("{} ", to_name(ty))).collect::(); format!("({keyword} {step})") }; @@ -151,7 +139,7 @@ fn proxy_module(func_ty: &FuncType) -> Module { acc }); - let result_drops = "(drop)\n".repeat(results.len()).to_string(); + let result_drops = "(drop)\n".repeat(results.len()); let wasm_text = format!( r#"(module (import "host" "hfn" (func $host_fn {params_text} {results_text})) @@ -162,6 +150,7 @@ fn proxy_module(func_ty: &FuncType) -> Module { (func (export "call_hfn_discard") {params_text} {params_gets} (call $host_fn) + ;; Keep stack balanced for arbitrary result arity. {result_drops} ) ) diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs new file mode 100644 index 00000000..93b1c27f --- /dev/null +++ b/crates/tinywasm/tests/resume_execution.rs @@ -0,0 +1,118 @@ +use eyre::Result; +use tinywasm::engine::{Config, FuelPolicy}; +use tinywasm::{ExecProgress, Module, types::WasmValue}; + +#[cfg(feature = "std")] +use std::time::Duration; + +const FIBONACCI_WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); +const ADD_WASM: &[u8] = include_bytes!("../../../examples/wasm/add.wasm"); + +#[test] +fn typed_resume_matches_non_budgeted_call() -> Result<()> { + let module = Module::parse_bytes(FIBONACCI_WASM)?; + + let mut store_full = tinywasm::Store::default(); + let instance_full = module.clone().instantiate(&mut store_full, None)?; + let func_full = instance_full.exported_func::(&store_full, "fibonacci_recursive")?; + let expected = func_full.call(&mut store_full, 20)?; + + let mut store_budgeted = tinywasm::Store::default(); + let instance_budgeted = module.instantiate(&mut store_budgeted, None)?; + let func_budgeted = instance_budgeted.exported_func::(&store_budgeted, "fibonacci_recursive")?; + + let mut exec = func_budgeted.call_resumable(&mut store_budgeted, 20)?; + let mut saw_suspended = false; + let actual = loop { + match exec.resume_with_fuel(64)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => saw_suspended = true, + } + }; + + assert!(saw_suspended, "expected at least one suspension for recursive fibonacci"); + assert_eq!(actual, expected); + + Ok(()) +} + +#[test] +fn untyped_resume_supports_zero_fuel() -> Result<()> { + let module = Module::parse_bytes(ADD_WASM)?; + let mut store = tinywasm::Store::default(); + let instance = module.instantiate(&mut store, None)?; + let func = instance.exported_func_untyped(&store, "add")?; + + let mut exec = func.call_resumable(&mut store, &[WasmValue::I32(20), WasmValue::I32(22)])?; + assert!(matches!(exec.resume_with_fuel(0)?, ExecProgress::Suspended)); + + match exec.resume_with_fuel(16)? { + ExecProgress::Completed(values) => { + assert_eq!(values, vec![WasmValue::I32(42)]) + } + ExecProgress::Suspended => panic!("expected completion"), + } + + Ok(()) +} + +#[test] +fn weighted_call_fuel_requires_more_rounds() -> Result<()> { + let module = Module::parse_bytes(FIBONACCI_WASM)?; + + let mut per_instr_store = tinywasm::Store::default(); + let instance_per_instr = module.clone().instantiate(&mut per_instr_store, None)?; + let func_per_instr = instance_per_instr.exported_func::(&per_instr_store, "fibonacci_recursive")?; + + let mut weighted_store = + tinywasm::Store::new(tinywasm::Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted))); + let instance_weighted = module.instantiate(&mut weighted_store, None)?; + let func_weighted = instance_weighted.exported_func::(&weighted_store, "fibonacci_recursive")?; + + let fuel = 64; + let n = 20; + + let mut per_exec = func_per_instr.call_resumable(&mut per_instr_store, n)?; + let mut per_rounds = 0; + let per_result = loop { + per_rounds += 1; + match per_exec.resume_with_fuel(fuel)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => {} + } + }; + + let mut weighted_exec = func_weighted.call_resumable(&mut weighted_store, n)?; + let mut weighted_rounds = 0; + let weighted_result = loop { + weighted_rounds += 1; + match weighted_exec.resume_with_fuel(fuel)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => {} + } + }; + + assert_eq!(weighted_result, per_result); + assert!(weighted_rounds >= per_rounds, "weighted call fuel should not use fewer rounds than per-instruction"); + + Ok(()) +} + +#[cfg(feature = "std")] +#[test] +fn time_budget_zero_suspends_then_completes() -> Result<()> { + let module = Module::parse_bytes(ADD_WASM)?; + let mut store = tinywasm::Store::default(); + let instance = module.instantiate(&mut store, None)?; + let func = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + + let mut exec = func.call_resumable(&mut store, (20, 22))?; + assert!(matches!(exec.resume_with_time_budget(Duration::ZERO)?, ExecProgress::Suspended)); + + match exec.resume_with_time_budget(Duration::from_millis(1))? { + ExecProgress::Completed(value) => assert_eq!(value, 42), + ExecProgress::Suspended => panic!("expected completion"), + } + + Ok(()) +} diff --git a/crates/tinywasm/tests/test-wasm-3.rs b/crates/tinywasm/tests/test-wasm-3.rs index 3e63dc5b..11ebbc84 100644 --- a/crates/tinywasm/tests/test-wasm-3.rs +++ b/crates/tinywasm/tests/test-wasm-3.rs @@ -4,11 +4,6 @@ use testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; fn main() -> Result<()> { - if !std::env::args().any(|x| &x == "--enable") { - println!("Skipping wasm-3 tests, use --enable to run"); - return Ok(()); - } - TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-annotations.rs b/crates/tinywasm/tests/test-wasm-annotations.rs index beac9eba..8655d152 100644 --- a/crates/tinywasm/tests/test-wasm-annotations.rs +++ b/crates/tinywasm/tests/test-wasm-annotations.rs @@ -7,7 +7,6 @@ fn main() -> Result<()> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); - test_suite.skip("simd_lane.wast"); test_suite.run_files(proposal(&Proposal::Annotations))?; test_suite.save_csv("./tests/generated/wasm-annotations.csv", env!("CARGO_PKG_VERSION"))?; test_suite.report_status() diff --git a/crates/tinywasm/tests/test-wasm-custom.rs b/crates/tinywasm/tests/test-wasm-custom.rs new file mode 100644 index 00000000..ae081420 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-custom.rs @@ -0,0 +1,19 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let custom_dir = std::path::Path::new("./tests/wasm-custom"); + let mut files: Vec = std::fs::read_dir(custom_dir)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|p| p.extension().is_some_and(|ext| ext == "wast")) + .collect(); + files.sort(); + + let mut test_suite = TestSuite::new(); + test_suite.run_paths(&files)?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-function-references.rs b/crates/tinywasm/tests/test-wasm-function-references.rs new file mode 100644 index 00000000..d91ff82b --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-function-references.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let mut test_suite = TestSuite::new(); + test_suite.run_files(proposal(&Proposal::FunctionReferences))?; + test_suite.save_csv("./tests/generated/wasm-function-references.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-gc.rs b/crates/tinywasm/tests/test-wasm-gc.rs new file mode 100644 index 00000000..a80d347e --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-gc.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::GC))?; + test_suite.save_csv("./tests/generated/wasm-gc.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-latest.rs b/crates/tinywasm/tests/test-wasm-latest.rs new file mode 100644 index 00000000..af83c2f3 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-latest.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{SpecVersion, spec}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let mut test_suite = TestSuite::new(); + test_suite.run_files(spec(&SpecVersion::Latest))?; + test_suite.save_csv("./tests/generated/wasm-latest.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-multi-memory.rs b/crates/tinywasm/tests/test-wasm-multi-memory.rs index 30052b07..e4487f11 100644 --- a/crates/tinywasm/tests/test-wasm-multi-memory.rs +++ b/crates/tinywasm/tests/test-wasm-multi-memory.rs @@ -7,7 +7,6 @@ fn main() -> Result<()> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); - test_suite.skip("simd_memory-multi.wast"); test_suite.run_files(proposal(&Proposal::MultiMemory))?; test_suite.save_csv("./tests/generated/wasm-multi-memory.csv", env!("CARGO_PKG_VERSION"))?; test_suite.report_status() diff --git a/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs b/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs new file mode 100644 index 00000000..0475fac7 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs @@ -0,0 +1,14 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::NontrappingFloatToIntConversions))?; + test_suite + .save_csv("./tests/generated/wasm-nontrapping-float-to-int-conversions.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-reference-types.rs b/crates/tinywasm/tests/test-wasm-reference-types.rs new file mode 100644 index 00000000..b6f11553 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-reference-types.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::ReferenceTypes))?; + test_suite.save_csv("./tests/generated/wasm-reference-types.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-sign-extension-op.rs b/crates/tinywasm/tests/test-wasm-sign-extension-op.rs new file mode 100644 index 00000000..c0ef1436 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-sign-extension-op.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::SignExtensionOps))?; + test_suite.save_csv("./tests/generated/wasm-sign-extension-ops.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-tail-call.rs b/crates/tinywasm/tests/test-wasm-tail-call.rs index f888f388..b6d8b2bf 100644 --- a/crates/tinywasm/tests/test-wasm-tail-call.rs +++ b/crates/tinywasm/tests/test-wasm-tail-call.rs @@ -8,6 +8,7 @@ fn main() -> Result<()> { let mut test_suite = TestSuite::new(); test_suite.run_files(proposal(&Proposal::TailCall))?; + test_suite.print_errors(); test_suite.save_csv("./tests/generated/wasm-tail-call.csv", env!("CARGO_PKG_VERSION"))?; test_suite.report_status() } diff --git a/crates/tinywasm/tests/test-wasm-threads.rs b/crates/tinywasm/tests/test-wasm-threads.rs new file mode 100644 index 00000000..2fd5a9b6 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-threads.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::Threads))?; + test_suite.save_csv("./tests/generated/wasm-threads.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs b/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs new file mode 100644 index 00000000..2aa6b435 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let mut test_suite = TestSuite::new(); + test_suite.run_files(proposal(&Proposal::WideArithmetic))?; + test_suite.save_csv("./tests/generated/wasm-wide-arithmetic.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs index 40b0e043..ff2b2ed7 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; -use eyre::{Result, bail, eyre}; -use owo_colors::OwoColorize; +use eyre::{Result, bail}; use testsuite::TestSuite; mod testsuite; diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index 2d443ba7..0221c6aa 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -2,6 +2,7 @@ use eyre::{Result, eyre}; use indexmap::IndexMap; use owo_colors::OwoColorize; +use std::fmt::Display; use std::io::{BufRead, Seek, SeekFrom}; use std::{ collections::BTreeMap, @@ -31,9 +32,9 @@ impl TestSuite { pub fn report_status(&self) -> Result<()> { if self.failed() { println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), self))) + Err(eyre!(format!("{}:\n{self}", "failed one or more tests".red().bold()))) } else { - println!("\n\npassed all tests:\n{self:#?}"); + println!("\n\npassed all tests:\n{self}"); Ok(()) } } @@ -119,7 +120,7 @@ fn link(name: &str, file: &str, line: Option) -> String { format!("\x1b]8;;file://{path}\x1b\\{name}\x1b]8;;\x1b\\") } -impl Debug for TestSuite { +impl Display for TestSuite { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut total_passed = 0; let mut total_failed = 0; @@ -145,6 +146,7 @@ impl Debug for TestSuite { } } +#[derive(Debug)] struct TestGroup { tests: IndexMap, file: String, diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index c46a2760..d13c59b1 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -57,16 +57,12 @@ impl ModuleRegistry { } } - fn get<'a>( - &self, - module_id: Option>, - store: &'a tinywasm::Store, - ) -> Option<&'a ModuleInstance> { + fn get(&self, module_id: Option>, store: &tinywasm::Store) -> Option { let addr = self.get_idx(module_id)?; store.get_module_instance(*addr) } - fn last<'a>(&self, store: &'a tinywasm::Store) -> Option<&'a ModuleInstance> { + fn last(&self, store: &tinywasm::Store) -> Option { store.get_module_instance(*self.last_module.as_ref()?) } } @@ -414,7 +410,7 @@ impl TestSuite { AssertReturn { span, exec, results } => { info!("AssertReturn: {exec:?}"); - let expected = match convert_wastret(results.into_iter()) { + let expected_alternatives = match convert_wastret(results.into_iter()) { Err(err) => { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), @@ -453,14 +449,19 @@ impl TestSuite { continue; } }; - let expected = expected.first().expect("expected global value"); - let module_global = module_global.attach_type(expected.val_type()); + let expected = expected_alternatives + .iter() + .filter_map(|alts| alts.first()) + .find(|exp| module_global.attach_type(exp.val_type()).eq_loose(exp)); - if !module_global.eq_loose(expected) { + if expected.is_none() { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!("global value did not match: {:?} != {:?}", module_global, expected)), + Err(eyre!( + "global value did not match any expected alternative: {:?}", + module_global + )), ); continue; } @@ -497,20 +498,23 @@ impl TestSuite { e })?; - if outcomes.len() != expected.len() { + if !expected_alternatives.iter().any(|expected| expected.len() == outcomes.len()) { return Err(eyre!( "span: {:?} expected {} results, got {}", span, - expected.len(), + expected_alternatives.first().map_or(0, |v| v.len()), outcomes.len() )); } - outcomes.iter().zip(expected).enumerate().try_for_each(|(i, (outcome, exp))| { - (outcome.eq_loose(&exp)) - .then_some(()) - .ok_or_else(|| eyre!(" result {} did not match: {:?} != {:?}", i, outcome, exp)) - }) + if expected_alternatives.iter().any(|expected| { + expected.len() == outcomes.len() + && outcomes.iter().zip(expected.iter()).all(|(outcome, exp)| outcome.eq_loose(exp)) + }) { + Ok(()) + } else { + Err(eyre!("results did not match any expected alternative")) + } }); let res = res.map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))).and_then(|r| r); diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 59013adc..851fa202 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -40,7 +40,7 @@ pub fn exec_fn( return Err(tinywasm::Error::Other("no module found".to_string())); }; - let mut store = tinywasm::Store::new(); + let mut store = tinywasm::Store::default(); let module = tinywasm::Module::from(module); let instance = module.instantiate(&mut store, imports)?; instance.exported_func_untyped(&store, name)?.call(&mut store, args) @@ -87,8 +87,27 @@ pub fn convert_wastargs(args: Vec) -> Result(args: impl Iterator>) -> Result> { - args.map(|a| wastret2tinywasmvalue(a)).collect() +pub fn convert_wastret<'a>( + args: impl Iterator>, +) -> Result>> { + let mut alternatives = vec![Vec::new()]; + + for arg in args { + let choices = wastret2tinywasmvalues(arg)?; + let mut next = Vec::with_capacity(alternatives.len() * choices.len()); + + for prefix in alternatives { + for choice in &choices { + let mut candidate = prefix.clone(); + candidate.push(*choice); + next.push(candidate); + } + } + + alternatives = next; + } + + Ok(alternatives) } fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { @@ -134,11 +153,20 @@ fn wast_i128_to_i128(i: wast::core::V128Pattern) -> i128 { i128::from_le_bytes(res.try_into().unwrap()) } -fn wastret2tinywasmvalue(ret: wast::WastRet) -> Result { +fn wastret2tinywasmvalues(ret: wast::WastRet) -> Result> { let wast::WastRet::Core(ret) = ret else { bail!("unsupported arg type"); }; + match ret { + wast::core::WastRetCore::Either(options) => { + options.into_iter().map(wastretcore2tinywasmvalue).collect::>>() + } + ret => Ok(vec![wastretcore2tinywasmvalue(ret)?]), + } +} + +fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result { use wast::core::WastRetCore::{F32, F64, I32, I64, RefExtern, RefFunc, RefNull, V128}; Ok(match ret { F32(f) => nanpattern2tinywasmvalue(f)?, diff --git a/crates/tinywasm/tests/wasm-custom/debug-if-then.wast b/crates/tinywasm/tests/wasm-custom/debug-if-then.wast new file mode 100644 index 00000000..6faf3392 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/debug-if-then.wast @@ -0,0 +1,12 @@ +(module + (func (export "as-if-then") (param i32 i32) (result i32) + (block (result i32) + (if (result i32) (local.get 0) + (then (br 1 (i32.const 3))) + (else (local.get 1)) + ) + ) + ) +) +(assert_return (invoke "as-if-then" (i32.const 0) (i32.const 6)) (i32.const 6)) +(assert_return (invoke "as-if-then" (i32.const 1) (i32.const 6)) (i32.const 3)) diff --git a/crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast b/crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast new file mode 100644 index 00000000..01a4f6be --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast @@ -0,0 +1,38 @@ +(module + (func $leak (result i32) + (block + (i64.const 7) + (i32.const 1) + (br_if 0) + (unreachable) + ) + (i32.const 0) + ) + + (func (export "run") (param i32) (result i32) + (local i32) + (local.get 0) + (local.set 1) + + (block + (loop + (local.get 1) + (i32.eqz) + (br_if 1) + + (call $leak) + (drop) + + (local.get 1) + (i32.const 1) + (i32.sub) + (local.set 1) + (br 0) + ) + ) + + (i32.const 0) + ) +) + +(assert_return (invoke "run" (i32.const 40000)) (i32.const 0)) diff --git a/crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast b/crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast new file mode 100644 index 00000000..86471636 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast @@ -0,0 +1,26 @@ +(module + (func (export "i32-add-locals-across-loop-boundary") (param i32) (result i32) + (block (result i32) + (local.get 0) + (loop (param i32) (result i32) + (local.get 0) + (i32.add) + (br 1) + ) + ) + ) + + (func (export "i64-add-locals-across-loop-boundary") (param i64) (result i64) + (block (result i64) + (local.get 0) + (loop (param i64) (result i64) + (local.get 0) + (i64.add) + (br 1) + ) + ) + ) +) + +(assert_return (invoke "i32-add-locals-across-loop-boundary" (i32.const 7)) (i32.const 14)) +(assert_return (invoke "i64-add-locals-across-loop-boundary" (i64.const 9)) (i64.const 18)) diff --git a/crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast b/crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast new file mode 100644 index 00000000..6f55b152 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast @@ -0,0 +1,17 @@ +(module + (table 1 funcref) + (func $f) + (elem (i32.const 0) func $f) + + (func (export "select-funcref") (param i32) (result i32) + (ref.null func) + (i32.const 0) + (table.get 0) + (local.get 0) + (select (result funcref)) + (ref.is_null) + ) +) + +(assert_return (invoke "select-funcref" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "select-funcref" (i32.const 0)) (i32.const 0)) diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index b986c2a3..94832c17 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -16,8 +16,9 @@ postcard={version="1.1", optional=true, default-features=false, features=["alloc serde={version="1.0", optional=true, default-features=false, features=["alloc"]} [features] -default=["std", "logging", "archive"] +default=["std", "log", "archive", "debug"] std=["serde?/std"] archive=["dep:postcard", "dep:serde"] -logging=["dep:log"] +log=["dep:log"] +debug=[] diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 7d52049d..dc98e309 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -4,10 +4,10 @@ use alloc::vec::Vec; use crate::TinyWasmModule; -const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; -const TWASM_VERSION: &[u8; 2] = b"03"; #[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]; +const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; +const TWASM_VERSION: &[u8; 2] = b"03"; fn validate_magic(wasm: &[u8]) -> Result { if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { @@ -51,7 +51,6 @@ impl TinyWasmModule { /// Creates a `TinyWasmModule` from a slice of bytes. pub fn from_twasm(wasm: &[u8]) -> Result { let len = validate_magic(wasm)?; - postcard::from_bytes(&wasm[len..]).map_err(TwasmError::InvalidArchive) } @@ -66,14 +65,6 @@ impl TinyWasmModule { mod tests { use super::*; - #[test] - fn test_serialize() { - let wasm = TinyWasmModule::default(); - let twasm = wasm.serialize_twasm().expect("should serialize"); - let wasm2 = TinyWasmModule::from_twasm(&twasm).unwrap(); - assert_eq!(wasm, wasm2); - } - #[test] fn test_invalid_magic() { let wasm = TinyWasmModule::default(); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index aa57dfa4..0cbcad79 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,34 +1,35 @@ -use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; +use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType, ValueCounts}; use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -pub struct MemoryArg([u8; 12]); +#[repr(Rust, packed)] +pub struct MemoryArg { + offset: u64, + mem_addr: MemAddr, +} impl MemoryArg { - pub fn new(offset: u64, mem_addr: MemAddr) -> Self { - let mut bytes = [0; 12]; - bytes[0..8].copy_from_slice(&offset.to_le_bytes()); - bytes[8..12].copy_from_slice(&mem_addr.to_le_bytes()); - Self(bytes) + #[inline] + pub const fn new(offset: u64, mem_addr: MemAddr) -> Self { + Self { offset, mem_addr } } - pub fn offset(&self) -> u64 { - u64::from_le_bytes(self.0[0..8].try_into().expect("invalid offset")) + #[inline] + pub const fn offset(self) -> u64 { + self.offset } - pub fn mem_addr(&self) -> MemAddr { - MemAddr::from_le_bytes(self.0[8..12].try_into().expect("invalid mem_addr")) + #[inline] + pub const fn mem_addr(self) -> MemAddr { + self.mem_addr } } -type BrTableDefault = u32; -type BrTableLen = u32; -type EndOffset = u32; -type ElseOffset = u32; - -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ConstInstruction { I32Const(i32), @@ -46,55 +47,52 @@ pub enum ConstInstruction { /// These are our own internal bytecode instructions so they may not match the spec exactly. /// Wasm Bytecode can map to multiple of these instructions. /// -/// # Differences to the spec -/// * `br_table` stores the jump labels in the following `br_label` instructions to keep this enum small. -/// * Lables/Blocks: we store the label end offset in the instruction itself and use `EndBlockFrame` to mark the end of a block. -/// This makes it easier to implement the label stack iteratively. -/// /// See -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -// should be kept as small as possible (16 bytes max) #[rustfmt::skip] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), - // LocalsStore32(LocalAddr, LocalAddr, u32, MemAddr), LocalsStore64(LocalAddr, LocalAddr, u32, MemAddr), LocalsStore128(LocalAddr, LocalAddr, u32, MemAddr), LocalsStoreRef(LocalAddr, LocalAddr, u32, MemAddr), - - // > Control Instructions + I32AddLocals(LocalAddr, LocalAddr), I64AddLocals(LocalAddr, LocalAddr), + I32AddConst(i32), I64AddConst(i64), + LocalAddConst32(LocalAddr, i32), LocalAddConst64(LocalAddr, i64), + LocalSetConst32(LocalAddr, i32), LocalSetConst64(LocalAddr, i64), + I32StoreLocalLocal(MemoryArg, u8, u8), + I64StoreLocalLocal(MemoryArg, u8, u8), + I32LoadLocalTee(MemoryArg, u8, u8), + I32LoadLocalSet(MemoryArg, u8, u8), + I64XorRotlConst(i64), + I64XorRotlConstTee(i64, LocalAddr), + + // > Control Instructions (jump-oriented, lowered from structured control during parsing) // See Unreachable, Nop, - - Block(EndOffset), - BlockWithType(ValType, EndOffset), - BlockWithFuncType(TypeAddr, EndOffset), - - Loop(EndOffset), - LoopWithType(ValType, EndOffset), - LoopWithFuncType(TypeAddr, EndOffset), - - If(ElseOffset, EndOffset), - IfWithType(ValType, ElseOffset, EndOffset), - IfWithFuncType(TypeAddr, ElseOffset, EndOffset), - - Else(EndOffset), - EndBlockFrame, - Br(LabelAddr), - BrIf(LabelAddr), - BrTable(BrTableDefault, BrTableLen), // has to be followed by multiple BrLabel instructions - BrLabel(LabelAddr), + Jump(u32), + JumpIfZero(u32), + JumpIfNonZero(u32), + DropKeepSmall { base32: u8, keep32: u8, base64: u8, keep64: u8, base128: u8, keep128: u8, base_ref: u8, keep_ref: u8 }, + DropKeep32(u16, u16), + DropKeep64(u16, u16), + DropKeep128(u16, u16), + DropKeepRef(u16, u16), + BranchTable(u32, u32, u32), // (default_landing_pad_ip, branch_table_start, target_count) Return, Call(FuncAddr), + CallSelf, CallIndirect(TypeAddr, TableAddr), ReturnCall(FuncAddr), + ReturnCallSelf, ReturnCallIndirect(TypeAddr, TableAddr), - + // > Parametric Instructions // See Drop32, Select32, Drop64, Select64, Drop128, Select128, DropRef, SelectRef, + SelectMulti(ValueCounts), // > Variable Instructions // See @@ -141,19 +139,22 @@ pub enum Instruction { RefNull(ValType), RefFunc(FuncAddr), RefIsNull, - + // > Numeric Instructions // See I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU, I64Eqz, I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU, + // Comparisons F32Eq, F32Ne, F32Lt, F32Gt, F32Le, F32Ge, F64Eq, F64Ne, F64Lt, F64Gt, F64Le, F64Ge, I32Clz, I32Ctz, I32Popcnt, I32Add, I32Sub, I32Mul, I32DivS, I32DivU, I32RemS, I32RemU, I64Clz, I64Ctz, I64Popcnt, I64Add, I64Sub, I64Mul, I64DivS, I64DivU, I64RemS, I64RemU, + // Bitwise I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr, I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr, + // Floating Point F32Abs, F32Neg, F32Ceil, F32Floor, F32Trunc, F32Nearest, F32Sqrt, F32Add, F32Sub, F32Mul, F32Div, F32Min, F32Max, F32Copysign, F64Abs, F64Neg, F64Ceil, F64Floor, F64Trunc, F64Nearest, F64Sqrt, F64Add, F64Sub, F64Mul, F64Div, F64Min, F64Max, F64Copysign, @@ -161,8 +162,7 @@ pub enum Instruction { I64Extend8S, I64Extend16S, I64Extend32S, I64ExtendI32S, I64ExtendI32U, I64TruncF32S, I64TruncF32U, I64TruncF64S, I64TruncF64U, F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64, F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32, - // Reinterpretations (noops at runtime) - I32ReinterpretF32, I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + // Saturating Float-to-Int Conversions I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U, I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, @@ -180,10 +180,14 @@ pub enum Instruction { MemoryInit(MemAddr, DataAddr), MemoryCopy(MemAddr, MemAddr), MemoryFill(MemAddr), + MemoryFillImm(MemAddr, u8, i32), DataDrop(DataAddr), ElemDrop(ElemAddr), - // > SIMD Instructions + // > Wide Arithmetic + I64Add128, I64Sub128, I64MulWideS, I64MulWideU, + + // > SIMD V128Load(MemoryArg), V128Load8x8S(MemoryArg), V128Load8x8U(MemoryArg), V128Load16x4S(MemoryArg), V128Load16x4U(MemoryArg), @@ -259,3 +263,13 @@ pub enum Instruction { I16x8RelaxedDotI8x16I7x16S, I32x4RelaxedDotI8x16I7x16AddS } + +#[cfg(test)] +mod tests { + use super::Instruction; + + #[test] + fn instruction_layout_size_is_stable() { + assert_eq!(core::mem::size_of::(), 16); + } +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index c34b705c..29a29e5a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -2,15 +2,18 @@ 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)] +#![warn(rust_2018_idioms, unreachable_pub)] #![no_std] -#![forbid(unsafe_code)] +#![deny(unsafe_code)] //! 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}; +use alloc::{boxed::Box, sync::Arc}; +use core::{ + fmt::Debug, + ops::{Deref, Range}, +}; // Memory defaults const MEM_PAGE_SIZE: u64 = 65536; @@ -21,12 +24,12 @@ const fn max_page_count(page_size: u64) -> u64 { } // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] #[allow(clippy::single_component_path_imports, unused_imports)] use log; // noop fallback if logging is disabled. -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] #[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); @@ -62,7 +65,8 @@ pub mod archive { /// 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, Default, PartialEq)] +#[derive(Clone, Default, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TinyWasmModule { /// Optional address of the start function @@ -73,53 +77,54 @@ pub struct TinyWasmModule { /// Optimized and validated WebAssembly functions /// /// Contains data from to the `code`, `func`, and `type` sections of the original WebAssembly module. - pub funcs: Box<[WasmFunction]>, + pub funcs: ArcSlice, /// A vector of type definitions, indexed by `TypeAddr` /// /// Corresponds to the `type` section of the original WebAssembly module. - pub func_types: Box<[FuncType]>, + pub func_types: ArcSlice, /// Exported items of the WebAssembly module. /// /// Corresponds to the `export` section of the original WebAssembly module. - pub exports: Box<[Export]>, + pub exports: ArcSlice, /// Global components of the WebAssembly module. /// /// Corresponds to the `global` section of the original WebAssembly module. - pub globals: Box<[Global]>, + pub globals: ArcSlice, /// Table components of the WebAssembly module used to initialize tables. /// /// Corresponds to the `table` section of the original WebAssembly module. - pub table_types: Box<[TableType]>, + pub table_types: ArcSlice, /// Memory components of the WebAssembly module used to initialize memories. /// /// Corresponds to the `memory` section of the original WebAssembly module. - pub memory_types: Box<[MemoryType]>, + pub memory_types: ArcSlice, /// Imports of the WebAssembly module. /// /// Corresponds to the `import` section of the original WebAssembly module. - pub imports: Box<[Import]>, + pub imports: ArcSlice, /// Data segments of the WebAssembly module. /// /// Corresponds to the `data` section of the original WebAssembly module. - pub data: Box<[Data]>, + pub data: ArcSlice, /// Element segments of the WebAssembly module. /// /// Corresponds to the `elem` section of the original WebAssembly module. - pub elements: Box<[Element]>, + pub elements: ArcSlice, } /// A WebAssembly External Kind. /// /// See -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ExternalKind { /// A WebAssembly Function. @@ -152,13 +157,13 @@ pub type ConstIdx = Addr; // additional internal addresses pub type TypeAddr = Addr; pub type LocalAddr = u16; // there can't be more than 50.000 locals in a function -pub type LabelAddr = Addr; pub type ModuleInstanceAddr = Addr; /// A WebAssembly External Value. /// /// See -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum ExternVal { Func(FuncAddr), Table(TableAddr), @@ -168,7 +173,7 @@ pub enum ExternVal { impl ExternVal { #[inline] - pub fn kind(&self) -> ExternalKind { + pub const fn kind(&self) -> ExternalKind { match self { Self::Func(_) => ExternalKind::Func, Self::Table(_) => ExternalKind::Table, @@ -178,7 +183,7 @@ impl ExternVal { } #[inline] - pub fn new(kind: ExternalKind, addr: Addr) -> Self { + pub const fn new(kind: ExternalKind, addr: Addr) -> Self { match kind { ExternalKind::Func => Self::Func(addr), ExternalKind::Table => Self::Table(addr), @@ -191,79 +196,120 @@ impl ExternVal { /// The type of a WebAssembly Function. /// /// See -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct FuncType { pub params: Box<[ValType]>, pub results: Box<[ValType]>, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct ValueCounts { - pub c32: u32, - pub c64: u32, - pub c128: u32, - pub cref: u32, -} - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -pub struct ValueCountsSmall { pub c32: u16, pub c64: u16, pub c128: u16, pub cref: u16, } -impl<'a, T: IntoIterator> From for ValueCounts { - fn from(types: T) -> Self { - let mut counts = Self::default(); - for ty in types { - match ty { - ValType::I32 | ValType::F32 => counts.c32 += 1, - ValType::I64 | ValType::F64 => counts.c64 += 1, - ValType::V128 => counts.c128 += 1, - ValType::RefExtern | ValType::RefFunc => counts.cref += 1, - } - } - counts +impl ValueCounts { + #[inline] + pub fn is_empty(&self) -> bool { + self.c32 == 0 && self.c64 == 0 && self.c128 == 0 && self.cref == 0 } } -impl<'a, T: IntoIterator> From for ValueCountsSmall { - fn from(types: T) -> Self { - let mut counts = Self::default(); - for ty in types { +impl<'a> FromIterator<&'a ValType> for ValueCounts { + #[inline] + fn from_iter>(iter: I) -> Self { + iter.into_iter().fold(Self::default(), |mut counts, ty| { match ty { ValType::I32 | ValType::F32 => counts.c32 += 1, ValType::I64 | ValType::F64 => counts.c64 += 1, ValType::V128 => counts.c128 += 1, ValType::RefExtern | ValType::RefFunc => counts.cref += 1, } - } - counts + counts + }) } } -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Clone, PartialEq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunction { - pub instructions: Box<[Instruction]>, + pub instructions: ArcSlice, pub data: WasmFunctionData, pub locals: ValueCounts, - pub params: ValueCountsSmall, + pub params: ValueCounts, pub ty: FuncType, } -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, PartialEq)] +#[doc(hidden)] +// wrapper around Arc<[T]> to support serde serialization and deserialization +pub struct ArcSlice(pub Arc<[T]>); + +impl Debug for ArcSlice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.as_ref().fmt(f) + } +} + +impl From> for ArcSlice { + fn from(vec: alloc::vec::Vec) -> Self { + Self(Arc::from(vec)) + } +} + +impl Default for ArcSlice { + fn default() -> Self { + Self(Arc::from([])) + } +} + +impl Deref for ArcSlice { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl FromIterator for ArcSlice { + fn from_iter>(iter: I) -> Self { + Self(Arc::from_iter(iter)) + } +} + +#[cfg(feature = "archive")] +impl serde::Serialize for ArcSlice { + fn serialize(&self, serializer: S) -> Result { + self.0.as_ref().serialize(serializer) + } +} + +#[cfg(feature = "archive")] +impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ArcSlice { + fn deserialize>(deserializer: D) -> Result { + let vec: alloc::vec::Vec = alloc::vec::Vec::deserialize(deserializer)?; + Ok(Self(Arc::from(vec))) + } +} + +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunctionData { pub v128_constants: Box<[i128]>, + pub branch_table_targets: Box<[u32]>, } /// A WebAssembly Module Export -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Export { /// The name of the export. @@ -274,21 +320,24 @@ pub struct Export { pub index: u32, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Global { pub ty: GlobalType, pub init: ConstInstruction, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TableType { pub element_type: ValType, @@ -307,7 +356,8 @@ impl TableType { } /// Represents a memory's type. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct MemoryType { arch: MemoryArch, @@ -317,43 +367,56 @@ pub struct MemoryType { } impl MemoryType { - pub fn new(arch: MemoryArch, page_count_initial: u64, page_count_max: Option, page_size: Option) -> Self { + pub const fn new( + arch: MemoryArch, + page_count_initial: u64, + page_count_max: Option, + page_size: Option, + ) -> Self { Self { arch, page_count_initial, page_count_max, page_size } } - pub fn arch(&self) -> MemoryArch { + #[inline] + pub const fn arch(&self) -> MemoryArch { self.arch } - pub fn page_count_initial(&self) -> u64 { + #[inline] + pub const fn page_count_initial(&self) -> u64 { self.page_count_initial } - pub fn page_count_max(&self) -> u64 { - self.page_count_max.unwrap_or_else(|| max_page_count(self.page_size())) + #[inline] + pub const fn page_count_max(&self) -> u64 { + if let Some(page_count_max) = self.page_count_max { page_count_max } else { max_page_count(self.page_size()) } } - pub fn page_size(&self) -> u64 { - self.page_size.unwrap_or(MEM_PAGE_SIZE) + #[inline] + pub const fn page_size(&self) -> u64 { + if let Some(page_size) = self.page_size { page_size } else { MEM_PAGE_SIZE } } - pub fn initial_size(&self) -> u64 { + #[inline] + pub const fn initial_size(&self) -> u64 { self.page_count_initial * self.page_size() } - pub fn max_size(&self) -> u64 { + #[inline] + pub const fn max_size(&self) -> u64 { self.page_count_max() * self.page_size() } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum MemoryArch { I32, I64, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Import { pub module: Box, @@ -361,7 +424,8 @@ pub struct Import { pub kind: ImportKind, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ImportKind { Function(TypeAddr), @@ -371,7 +435,6 @@ pub enum ImportKind { } impl From<&ImportKind> for ExternalKind { - #[inline] fn from(kind: &ImportKind) -> Self { match kind { ImportKind::Function(_) => Self::Func, @@ -382,7 +445,8 @@ impl From<&ImportKind> for ExternalKind { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Data { pub data: Box<[u8]>, @@ -390,14 +454,16 @@ pub struct Data { pub kind: DataKind, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum DataKind { Active { mem: MemAddr, offset: ConstInstruction }, Passive, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Element { pub kind: ElementKind, @@ -406,7 +472,8 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ElementKind { Passive, @@ -414,7 +481,8 @@ pub enum ElementKind { Declared, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ElementItem { Func(FuncAddr), diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 22bb4eb7..8cc7f1b7 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -23,12 +23,33 @@ pub enum WasmValue { RefFunc(FuncRef), } +impl Debug for WasmValue { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::I32(i) => write!(f, "i32({i})"), + Self::I64(i) => write!(f, "i64({i})"), + Self::F32(i) => write!(f, "f32({i})"), + Self::F64(i) => write!(f, "f64({i})"), + Self::V128(i) => write!(f, "v128({i:?})"), + #[cfg(feature = "debug")] + Self::RefExtern(i) => write!(f, "ref({i:?})"), + #[cfg(feature = "debug")] + Self::RefFunc(i) => write!(f, "func({i:?})"), + #[cfg(not(feature = "debug"))] + Self::RefExtern(_) => write!(f, "ref()"), + #[cfg(not(feature = "debug"))] + Self::RefFunc(_) => write!(f, "func()"), + } + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub struct ExternRef(Option); #[derive(Clone, Copy, PartialEq, Eq)] pub struct FuncRef(Option); +#[cfg(feature = "debug")] impl Debug for ExternRef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self.0 { @@ -38,6 +59,7 @@ impl Debug for ExternRef { } } +#[cfg(feature = "debug")] impl Debug for FuncRef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self.0 { @@ -106,7 +128,7 @@ impl ExternRef { impl WasmValue { #[doc(hidden)] #[inline] - pub fn const_instr(&self) -> ConstInstruction { + pub const fn const_instr(&self) -> ConstInstruction { match self { Self::I32(i) => ConstInstruction::I32Const(*i), Self::I64(i) => ConstInstruction::I64Const(*i), @@ -114,13 +136,13 @@ impl WasmValue { Self::F64(i) => ConstInstruction::F64Const(*i), Self::V128(i) => ConstInstruction::V128Const(*i), Self::RefFunc(i) => ConstInstruction::RefFunc(i.addr()), - Self::RefExtern(_) => unimplemented!("no const_instr for RefExtern"), + Self::RefExtern(i) => ConstInstruction::RefExtern(i.addr()), } } /// Get the default value for a given type. #[inline] - pub fn default_for(ty: ValType) -> Self { + pub const fn default_for(ty: ValType) -> Self { match ty { ValType::I32 => Self::I32(0), ValType::I64 => Self::I64(0), @@ -138,19 +160,23 @@ impl WasmValue { match (self, other) { (Self::I32(a), Self::I32(b)) => a == b, (Self::I64(a), Self::I64(b)) => a == b, - (Self::V128(a), Self::V128(b)) => a == b, + (Self::V128(a), Self::V128(b)) => { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + a_bytes == b_bytes || Self::v128_nan_eq(a_bytes, b_bytes) + } (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 + true } 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 + true } else { a.to_bits() == b.to_bits() } @@ -159,8 +185,56 @@ impl WasmValue { } } + fn v128_nan_eq(a: [u8; 16], b: [u8; 16]) -> bool { + let a_f32x4: [f32; 4] = [ + f32::from_le_bytes([a[0], a[1], a[2], a[3]]), + f32::from_le_bytes([a[4], a[5], a[6], a[7]]), + f32::from_le_bytes([a[8], a[9], a[10], a[11]]), + f32::from_le_bytes([a[12], a[13], a[14], a[15]]), + ]; + let b_f32x4: [f32; 4] = [ + f32::from_le_bytes([b[0], b[1], b[2], b[3]]), + f32::from_le_bytes([b[4], b[5], b[6], b[7]]), + f32::from_le_bytes([b[8], b[9], b[10], b[11]]), + f32::from_le_bytes([b[12], b[13], b[14], b[15]]), + ]; + + let all_nan_match = a_f32x4.iter().zip(b_f32x4.iter()).all(|(x, y)| { + if x.is_nan() && y.is_nan() { + true + } else if x.is_nan() || y.is_nan() { + false + } else { + x.to_bits() == y.to_bits() + } + }); + + if all_nan_match && a_f32x4.iter().any(|x| x.is_nan()) { + return true; + } + + let a_f64x2: [f64; 2] = [ + f64::from_le_bytes([a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]]), + f64::from_le_bytes([a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]]), + ]; + let b_f64x2: [f64; 2] = [ + f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), + f64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]), + ]; + + a_f64x2.iter().zip(b_f64x2.iter()).all(|(x, y)| { + if x.is_nan() && y.is_nan() { + true + } else if x.is_nan() || y.is_nan() { + false + } else { + x.to_bits() == y.to_bits() + } + }) && a_f64x2.iter().any(|x| x.is_nan()) + } + #[doc(hidden)] - pub fn as_i32(&self) -> Option { + pub const fn as_i32(&self) -> Option { match self { Self::I32(i) => Some(*i), _ => None, @@ -168,7 +242,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_i64(&self) -> Option { + pub const fn as_i64(&self) -> Option { match self { Self::I64(i) => Some(*i), _ => None, @@ -176,7 +250,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_f32(&self) -> Option { + pub const fn as_f32(&self) -> Option { match self { Self::F32(i) => Some(*i), _ => None, @@ -184,7 +258,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_f64(&self) -> Option { + pub const fn as_f64(&self) -> Option { match self { Self::F64(i) => Some(*i), _ => None, @@ -192,7 +266,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_v128(&self) -> Option { + pub const fn as_v128(&self) -> Option { match self { Self::V128(i) => Some(*i), _ => None, @@ -200,7 +274,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_ref_extern(&self) -> Option { + pub const fn as_ref_extern(&self) -> Option { match self { Self::RefExtern(ref_extern) => Some(*ref_extern), _ => None, @@ -208,7 +282,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_ref_func(&self) -> Option { + pub const fn as_ref_func(&self) -> Option { match self { Self::RefFunc(ref_func) => Some(*ref_func), _ => None, @@ -216,27 +290,10 @@ impl WasmValue { } } -#[cold] -fn cold() {} - -impl Debug for WasmValue { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::I32(i) => write!(f, "i32({i})"), - Self::I64(i) => write!(f, "i64({i})"), - Self::F32(i) => write!(f, "f32({i})"), - Self::F64(i) => write!(f, "f64({i})"), - Self::V128(i) => write!(f, "v128({i:?})"), - Self::RefExtern(i) => write!(f, "ref({i:?})"), - Self::RefFunc(i) => write!(f, "func({i:?})"), - } - } -} - impl WasmValue { /// Get the type of a [`WasmValue`] #[inline] - pub fn val_type(&self) -> ValType { + pub const fn val_type(&self) -> ValType { match self { Self::I32(_) => ValType::I32, Self::I64(_) => ValType::I64, @@ -250,7 +307,8 @@ impl WasmValue { } /// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ValType { /// A 32-bit integer. @@ -271,13 +329,13 @@ pub enum ValType { impl ValType { #[inline] - pub fn default_value(&self) -> WasmValue { + pub const fn default_value(&self) -> WasmValue { WasmValue::default_for(*self) } #[doc(hidden)] #[inline] - pub fn is_simd(&self) -> bool { + pub const fn is_simd(&self) -> bool { matches!(self, Self::V128) } } @@ -302,7 +360,6 @@ macro_rules! impl_conversion_for_wasmvalue { if let WasmValue::$variant(i) = value { Ok(i) } else { - cold(); Err(()) } } diff --git a/examples/dump-bytecode.rs b/examples/dump-bytecode.rs new file mode 100644 index 00000000..912da41a --- /dev/null +++ b/examples/dump-bytecode.rs @@ -0,0 +1,53 @@ +use eyre::{Result, bail}; +use std::io::Read; +use std::path::Path; +use tinywasm::parser::Parser; +use tinywasm::types::{ExternalKind, ImportKind}; + +fn read_input(path: &str) -> Result> { + if path == "-" { + let mut source = String::new(); + std::io::stdin().read_to_string(&mut source)?; + return Ok(wat::parse_str(source)?); + } + + let bytes = std::fs::read(path)?; + let is_wasm = Path::new(path).extension().and_then(|s| s.to_str()) == Some("wasm"); + if is_wasm { Ok(bytes) } else { Ok(wat::parse_bytes(&bytes)?.into_owned()) } +} + +fn main() -> Result<()> { + let args = std::env::args().collect::>(); + if args.len() != 2 { + bail!("usage: cargo run --example dump-bytecode -- ") + } + + let wasm = read_input(&args[1])?; + let module = Parser::new().parse_module_bytes(&wasm)?; + + let imported_func_count = + module.imports.iter().filter(|import| matches!(import.kind, ImportKind::Function(_))).count() as u32; + + for (func_idx, func) in module.funcs.iter().enumerate() { + let global_idx = imported_func_count + func_idx as u32; + let exports = module + .exports + .iter() + .filter(|export| export.kind == ExternalKind::Func && export.index == global_idx) + .map(|export| export.name.as_ref()) + .collect::>(); + + if exports.is_empty() { + println!("func[{func_idx}] global={global_idx}"); + } else { + println!("func[{func_idx}] global={global_idx} exports={exports:?}"); + } + + for (ip, instr) in func.instructions.iter().enumerate() { + println!(" {ip:04}: {instr:?}"); + } + println!(); + } + + Ok(()) +} diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index 01f833cf..a460df83 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -1,21 +1,17 @@ use eyre::Result; use tinywasm::{Extern, FuncContext, Imports, Module, Store, types::FuncRef}; +const LHS: i32 = 5; +const RHS: i32 = 3; + fn main() -> Result<()> { - by_func_ref_passed()?; - by_func_ref_returned()?; + run_passed_funcref_example()?; + run_returned_funcref_example()?; Ok(()) } -/// Example of passing Wasm functions (as `funcref`) to an imported host function -/// and the imported host function calling them. -fn by_func_ref_passed() -> Result<()> { - // A module with: - // - Imported function "host.call_this" that accepts a callback. - // - Exported Wasm function "tell_host_to_call" that calls "host.call_this" with Wasm functions $add and $sub. - // - Wasm functions $add and $sub and an imported function $mul used as callbacks - // (just to show that imported functions can be referenced too). - // - Exported Wasm function "call_binop_by_ref", a proxy used by the host to call func-references of type (i32, i32) -> i32. +fn run_passed_funcref_example() -> Result<()> { + // Host receives funcref and calls it via an exported proxy. const WASM: &str = r#" (module (import "host" "call_this" (func $host_callback_caller (param funcref))) @@ -30,7 +26,7 @@ fn by_func_ref_passed() -> Result<()> { (type $binop (func (param i32 i32) (result i32))) (table 3 funcref) - (elem (i32.const 0) $add $sub $host_mul) ;; Function can only be referenced if added to a table. + (elem (i32.const 0) $add $sub $host_mul) (func $add (param $x i32) (param $y i32) (result i32) local.get $x local.get $y @@ -50,51 +46,39 @@ fn by_func_ref_passed() -> Result<()> { ) "#; - let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - // Import host function that takes callbacks and calls them. imports.define( "host", "call_this", - Extern::typed_func(|mut ctx: FuncContext<'_>, fn_ref: FuncRef| -> tinywasm::Result<()> { - let proxy_caller = + Extern::typed_func(|mut ctx: FuncContext<'_>, func_ref: FuncRef| -> tinywasm::Result<()> { + // Host cannot call a funcref directly, so it routes through Wasm. + let call_by_ref = ctx.module().exported_func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; - // Call the callback we got as an argument using "call_binop_by_ref". - let res = proxy_caller.call(ctx.store_mut(), (fn_ref, 5, 3))?; - println!("(funcref {fn_ref:?})(5,3) results in {res}"); - + let _result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; Ok(()) }), )?; - // Import host.mul function (one of the functions whose references are taken). imports.define( "host", "mul", - Extern::typed_func(|_, args: (i32, i32)| -> tinywasm::Result { Ok(args.0 * args.1) }), + Extern::typed_func(|_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }), )?; let instance = module.instantiate(&mut store, Some(imports))?; let caller = instance.exported_func::<(), ()>(&store, "tell_host_to_call")?; - // Call "tell_host_to_call". caller.call(&mut store, ())?; - // An interesting detail is that neither $add, $sub, nor $mul were exported, - // but with a little help from the proxy "call_binop_by_ref", references to them are callable by the host. + Ok(()) } -/// Example of returning a Wasm function as a callback to a host function -/// and the host function calling it. -fn by_func_ref_returned() -> Result<()> { - // A module with: - // - An exported function "what_should_host_call" that returns 3 `funcref`s. - // - Wasm functions $add and $sub and an imported function $mul used as callbacks - // (just to show that imported functions can be referenced too). - // - Another exported Wasm function "call_binop_by_ref", a proxy used by the host to call func-references of type (i32, i32) -> i32 +fn run_returned_funcref_example() -> Result<()> { + // Wasm returns funcref values, host executes them through the same proxy. const WASM: &str = r#" (module (import "host" "mul" (func $host_mul (param $x i32) (param $y i32) (result i32))) @@ -125,33 +109,29 @@ fn by_func_ref_returned() -> Result<()> { ) "#; - let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - // Import host.mul function (one of the possible operations). imports.define( "host", "mul", - Extern::typed_func(|_, args: (i32, i32)| -> tinywasm::Result { Ok(args.0 * args.1) }), + Extern::typed_func(|_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }), )?; let instance = module.instantiate(&mut store, Some(imports))?; - // Ask the module what to call. - let funcrefs = { - let address_getter = + let (add_ref, sub_ref, mul_ref) = { + let get_funcrefs = instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; - address_getter.call(&mut store, ())? + get_funcrefs.call(&mut store, ())? }; - let proxy_caller = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; + let call_by_ref = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; - for (idx, func_ref) in [funcrefs.0, funcrefs.1, funcrefs.2].iter().enumerate() { - // Call those `funcref`s via "call_binop_by_ref". - let res = proxy_caller.call(&mut store, (*func_ref, 5, 3))?; - println!("At idx: {idx}, funcref {func_ref:?}(5,3) results in {res}"); + for func_ref in [add_ref, sub_ref, mul_ref] { + let _result = call_by_ref.call(&mut store, (func_ref, LHS, RHS))?; } Ok(()) } diff --git a/examples/resumable.rs b/examples/resumable.rs new file mode 100644 index 00000000..6838e20f --- /dev/null +++ b/examples/resumable.rs @@ -0,0 +1,46 @@ +use eyre::Result; +use tinywasm::{ExecProgress, Module, Store}; + +const WASM: &str = r#" +(module + (func (export "count_down") (param $n i32) (result i32) + (local $cur i32) + local.get $n + local.set $cur + block + loop + local.get $cur + i32.eqz + br_if 1 + local.get $cur + i32.const 1 + i32.sub + local.set $cur + br 0 + end + end + local.get $cur)) +"#; + +fn main() -> Result<()> { + let wasm = wat::parse_str(WASM)?; + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let count_down = instance.exported_func::(&store, "count_down")?; + + let mut execution = count_down.call_resumable(&mut store, 10_000)?; + let fuel_per_round = 128; + let mut fuel_rounds = 0; + + let result = loop { + fuel_rounds += 1; + match execution.resume_with_fuel(fuel_per_round)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => {} + } + }; + + println!("completed in {fuel_rounds} rounds of {fuel_per_round} fuel, result={result}"); + Ok(()) +} diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index da238ad6..6bf8a8f7 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -48,7 +48,8 @@ path="src/argon2id.rs" [profile.wasm] opt-level=3 -lto="thin" +lto="fat" codegen-units=1 panic="abort" inherits="release" +strip=true diff --git a/examples/rust/build.sh b/examples/rust/build.sh index e1d320f8..db4cb439 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -6,8 +6,8 @@ exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" -rust_features="+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" -wasmopt_features="--enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int" +rust_features="+simd128,+relaxed-simd,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" +wasmopt_features="--enable-simd --enable-relaxed-simd --enable-tail-call --enable-extended-const --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int --duplicate-function-elimination" # ensure out dir exists mkdir -p "$dest_dir" @@ -17,7 +17,7 @@ cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profi cp "$out_dir/tinywasm_no_std.wasm" "$dest_dir/" for bin in "${bins[@]}"; do - RUSTFLAGS="-C target-feature=$rust_features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" + RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none -C target-feature=$rust_features -C panic=abort" cargo build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" --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.opt.wasm" -O3 $wasmopt_features diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index d3b3f8c2..11d76378 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.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(()) } diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index 46f17857..6a31ff0c 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -43,7 +43,7 @@ fn run() -> tinywasm::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(()) } diff --git a/examples/simple2.rs b/examples/simple2.rs new file mode 100644 index 00000000..a3124f86 --- /dev/null +++ b/examples/simple2.rs @@ -0,0 +1,21 @@ +use eyre::Result; +use tinywasm::{Module, Store}; + +const WASM: &str = r#" +(module + (func $return (param $lhs i32) (param $rhs i64) (result i32 i64) + local.get $lhs + local.get $rhs) + (export "return" (func $return))) +"#; + +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, i64), (i32, i64)>(&store, "return")?; + + assert_eq!(add.call(&mut store, (1, 2))?, (1, 2)); + Ok(()) +} diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 82d5c932..d376130a 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -131,7 +131,7 @@ fn hello() -> Result<()> { } fn host_fn() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/host_fn.wasm")?; + let module = Module::parse_file("./examples/rust/out/host_fn.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); imports.define( diff --git a/profile.sh b/profile.sh deleted file mode 100755 index 91bb9460..00000000 --- a/profile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -cargo build --example wasm-rust --profile profiling -samply record -r 10000 ./target/profiling/examples/wasm-rust $@ diff --git a/tinywasm.png b/tinywasm.png deleted file mode 100644 index 782b9d44..00000000 Binary files a/tinywasm.png and /dev/null differ