diff --git a/.cargo/config.toml b/.cargo/config.toml index e25dc35..b27192b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,7 +1,6 @@ [alias] version-dev="workspaces version --no-git-commit --force tinywasm*" -dev="run -- -l debug run" - test-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" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f93c5a4..0a75fb2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,9 +2,11 @@ name: Rust CI on: push: - branches: [main] + branches: ["**"] pull_request: - branches: [main] + branches: ["next", "main"] + schedule: + - cron: "0 0 * * 0" jobs: build-wasm: @@ -14,8 +16,14 @@ jobs: - uses: actions/checkout@v4 with: submodules: true - - name: Install Rust toolchain & Binaryen - run: rustup update && rustup target add wasm32-unknown-unknown && sudo apt-get install -y binaryen wabt + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + rustflags: "" + target: wasm32-unknown-unknown + - name: Install Binaryen and WABT + run: sudo apt-get install -y binaryen wabt - name: Build wasm run: ./examples/rust/build.sh - name: Save artifacts @@ -24,106 +32,52 @@ jobs: name: wasm path: examples/rust/out - test-std: - needs: build-wasm - name: Test with default features on stable Rust - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - - name: Install latest stable Rust toolchain - run: rustup update stable - - - name: Load wasm - uses: actions/download-artifact@v4 - with: - name: wasm - path: examples/rust/out - - - name: Build (stable) - run: cargo +stable build --workspace - - - name: Run tests (stable) - run: cargo +stable test --workspace && cargo +stable run --example wasm-rust all - - - name: Run MVP testsuite - run: cargo +stable test-wasm-1 - - - name: Run 2.0 testsuite - run: cargo +stable test-wasm-2 - - test-no-std: - needs: build-wasm - name: Test without default features on nightly Rust - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - - name: Install latest nightly Rust toolchain - run: rustup update nightly - - - name: Load wasm - uses: actions/download-artifact@v4 - with: - name: wasm - path: examples/rust/out - - - name: Build (nightly, no default features) - run: cargo +nightly build --workspace --no-default-features - - - name: Run tests (nightly, no default features) - run: cargo +nightly test --workspace --no-default-features && cargo +nightly run --example wasm-rust all - - - name: Run MVP testsuite - run: cargo +nightly test-wasm-1 - - - name: Run 2.0 testsuite - run: cargo +nightly test-wasm-2 - - test-m1: + run-tests: needs: build-wasm - name: Test on arm64 (Apple M1) - runs-on: macos-14 + strategy: + matrix: + include: + - os: ubuntu-latest + rust: stable + name: "Linux x86 (stable)" + - os: ubuntu-latest + rust: nightly + name: "Linux x86 (nightly)" + - os: ubuntu-latest + rust: stable + name: "Linux x86 (stable, no default features)" + args: "--no-default-features" + - os: ubuntu-latest + rust: nightly + name: "Linux x86 (nightly, no default features)" + args: "--no-default-features" + - os: macos-14 + rust: stable + name: "macOS arm64 (Apple M1)" + - os: ubuntu-latest + rust: stable + name: "Linux arm64" + target: aarch64-unknown-linux-gnu + - os: ubuntu-latest + rust: stable + name: "Linux armv7" + target: armv7-unknown-linux-gnueabihf + + name: Run tests on ${{ matrix.name }} + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: submodules: true - - name: Install stable Rust toolchain - run: rustup update stable - - name: Load wasm - uses: actions/download-artifact@v4 + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - name: wasm - path: examples/rust/out - - - name: Build (stable) - run: cargo +stable build - - - name: Run tests (stable) - run: cargo +stable test - - - name: Run MVP testsuite - run: cargo +stable test-wasm-1 - - - name: Run 2.0 testsuite - run: cargo +stable test-wasm-2 - - test-armv7: - needs: build-wasm - name: Test on armv7 (32-Bit Raspberry Pi) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: true + toolchain: ${{ matrix.rust }} + rustflags: "" + components: rustfmt, clippy + if: matrix.target == '' - name: Load wasm uses: actions/download-artifact@v4 @@ -131,25 +85,18 @@ jobs: name: wasm path: examples/rust/out - - name: Run all tests (for the default workspace members) - uses: houseabsolute/actions-rust-cross@v0.0.13 - with: - command: test - target: armv7-unknown-linux-gnueabihf - toolchain: nightly + - name: Run tests + run: cargo test --workspace ${{ matrix.args }} && cargo test --workspace ${{ matrix.args }} --examples + if: matrix.target == '' - - name: Run MVP testsuite - uses: houseabsolute/actions-rust-cross@v0.0.13 - with: - command: test - args: "-p tinywasm --test test-wasm-1 --release" - target: armv7-unknown-linux-gnueabihf - toolchain: nightly + - name: Run clippy + run: cargo clippy --workspace ${{ matrix.args }} + if: matrix.target == '' - - name: Run 2.0 testsuite + - name: Run tests (${{ matrix.target }}) uses: houseabsolute/actions-rust-cross@v0.0.13 with: command: test - args: "-p tinywasm --test test-wasm-2 --release" - target: armv7-unknown-linux-gnueabihf - toolchain: nightly + target: ${{ matrix.target }} + toolchain: ${{ matrix.rust }} + if: matrix.target != '' diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b18b9..8c47dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,18 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - +## [Unreleased] + +### Added + +- 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 +- New `canonicalize_nans` feature flag to enable canonicalizing NaN values in the `f32`, `f64`, and `v128` types + +### 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. + +### Fixed + +- Fixed archive **no_std** support which was broken in the previous release, and added more tests to ensure it stays working +- 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 **All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.7.0...v0.8.0 -### Changed +### Added - Full support for Multi-Memory proposal +- Improved support for WebAssembly 2.0 features + +### Changed + - Extern tables now correctly update their type after growing - Increased MSRV to 1.80.0 -- Improved support for WebAssembly 2.0 features - Simplify and optimize the interpreter loop - Use a seperate stack and locals for 32, 64 and 128 bit values and references (#21) - Updated to latest `wasmparser` version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1ca837..2302bef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,30 +1,70 @@ -# Scripts and Commands +# Contributing -> To improve the development experience, a number of custom commands and aliases have been added to the `.cargo/config.toml` file. These can be run using `cargo `. +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). -- **`cargo dev [args]`**\ - e.g. `cargo dev -f check ./examples/wasm/call.wat -a i32:0`\ - Run the development version of the tinywasm-cli. This is the main command used for developing new features.\ - See [tinywasm-cli](./crates/cli) for more information. +## 1. Clone the Repository -- **`cargo test-mvp`**\ - Run the WebAssembly MVP (1.0) test suite. Be sure to cloned this repo with `--recursive` or initialize the submodules with `git submodule update --init --recursive` +Ensure you clone this repository with the `--recursive` flag to include the submodules: -- **`cargo test-2`**\ - Run the full WebAssembly test suite (2.0) +```bash +git clone --recursive https://github.com/explodingcamera/tinywasm.git +``` -- **`cargo test-wast `**\ - Run a single WAST test file. e.g. `cargo test-wast ./examples/wast/i32.wast` +If you have already cloned the repository, you can initialize the submodules with: -- **`cargo version-dev`**\ - Bump the version to the next dev version. This should be used after a release so test results are not overwritten. Does not create a new github release. +```bash +git submodule update --init --recursive +``` -## Workspace Commands +This is required to run the WebAssembly test suite. -> These commands require the [cargo-workspaces](https://crates.io/crates/cargo-workspaces) crate to be installed. +## 2. Set up the Development Environment -- **`cargo workspaces version`**\ - Bump the version of all crates in the workspace and push changes to git. This is used for releasing new versions on github. +This project mostly uses a pretty standard Rust setup. Some common tasks: -- **`cargo workspaces publish --publish-as-is`**\ - Publish all crates in the workspace to crates.io. This should be used a new version has been released on github. After publishing, the version should be bumped to the next dev version. +```bash +# Run a specific benchmark (run without arguments to see available benchmarks) +$ cargo bench --bench {bench_name} + +# Run all tests +$ cargo test + +# Run only the WebAssembly MVP (1.0) test suite +$ cargo test-wasm-1 + +# Run only the full WebAssembly test suite (2.0) +$ cargo test-wasm-2 + +# Run a specific test (run without arguments to see available tests) +$ cargo test --test {test_name} + +# Run a single WAST test file +$ cargo test-wast {path} + +# Run a specific example (run without arguments to see available examples) +# The wasm test files required to run the `wasm-rust` examples are not +# included in the main repository. +# To build these, you will need to install binaryen and wabt +# and run `./examples/rust/build.sh`. +$ 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. + +Example usage: + +```bash +cargo install --locked samply +cargo samply --example wasm-rust -- selfhosted +``` + +# 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 + +- `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 eb1ab92..f194530 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,29 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] +version = 4 [[package]] name = "aho-corasick" @@ -42,139 +19,59 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "argh" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" dependencies = [ "argh_derive", "argh_shared", + "rust-fuzzy-search", ] [[package]] name = "argh_derive" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.76", + "syn", ] [[package]] name = "argh_shared" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" dependencies = [ "serde", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bstr" -version = "1.10.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" -dependencies = [ - "memchr", - "serde", -] +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive 0.6.12", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41502630fe304ce54cbb2f8389e017784dee2b0328147779fcbe43b9db06d35d" -dependencies = [ - "bytecheck_derive 0.7.0", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bytecheck_derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda88c587085bc07dc201ab9df871bd9baa5e07f7754b745e4d7194b43ac1eda" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bytes" -version = "1.7.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cast" @@ -217,18 +114,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstyle", "clap_lex", @@ -236,18 +133,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] -name = "cpufeatures" -version = "0.2.13" +name = "cobs" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" -dependencies = [ - "libc", -] +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" [[package]] name = "criterion" @@ -286,9 +180,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -305,41 +199,33 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] -name = "crypto-common" -version = "0.1.6" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "digest" -version = "0.10.7" +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] -name = "either" -version = "1.13.0" +name = "embedded-io" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "env_logger" @@ -356,9 +242,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "eyre" @@ -371,85 +257,52 @@ dependencies = [ ] [[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "generic-array" -version = "0.14.7" +name = "half" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ - "typenum", - "version_check", + "cfg-if", + "crunchy", ] [[package]] -name = "getrandom" -version = "0.2.15" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "globset" -version = "0.4.14" +name = "hermit-abi" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] -name = "half" -version = "2.4.1" +name = "humantime" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] -name = "hashbrown" -version = "0.12.3" +name = "include_dir" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ - "ahash 0.7.8", + "include_dir_macros", ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "include_dir_macros" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "ahash 0.8.11", + "proc-macro2", + "quote", ] -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "indenter" version = "0.3.3" @@ -458,23 +311,23 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.4.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -488,33 +341,33 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "leb128" -version = "0.2.5" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -533,21 +386,33 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "owo-colors" -version = "4.0.0" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" + +[[package]] +name = "postcard" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] [[package]] name = "pretty_env_logger" @@ -561,48 +426,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rayon" version = "1.10.0" @@ -625,9 +464,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -637,9 +476,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -648,88 +487,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck 0.6.12", -] - -[[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck 0.6.12", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rust-embed" -version = "8.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.5.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.76", - "walkdir", -] +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "rust-embed-utils" -version = "8.5.0" +name = "rust-fuzzy-search" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" -dependencies = [ - "globset", - "sha2", - "walkdir", -] +checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -740,43 +512,37 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "semver" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -784,51 +550,17 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" -version = "2.0.76" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "termcolor" version = "1.4.1" @@ -848,27 +580,13 @@ dependencies = [ "serde_json", ] -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tinywasm" -version = "0.8.0" +version = "0.9.0-alpha.0" dependencies = [ "criterion", "eyre", + "indexmap", "libm", "log", "owo-colors", @@ -879,11 +597,12 @@ dependencies = [ "tinywasm-types", "wasm-testsuite", "wast", + "wat", ] [[package]] name = "tinywasm-cli" -version = "0.8.0" +version = "0.9.0-alpha.0" dependencies = [ "argh", "eyre", @@ -895,7 +614,7 @@ dependencies = [ [[package]] name = "tinywasm-parser" -version = "0.8.0" +version = "0.9.0-alpha.0" dependencies = [ "log", "tinywasm-types", @@ -904,7 +623,7 @@ dependencies = [ [[package]] name = "tinywasm-root" -version = "0.0.0" +version = "0.9.0-alpha.0" dependencies = [ "eyre", "pretty_env_logger", @@ -914,42 +633,24 @@ dependencies = [ [[package]] name = "tinywasm-types" -version = "0.8.0" +version = "0.9.0-alpha.0" dependencies = [ - "bytecheck 0.7.0", "log", - "rkyv", + "postcard", + "serde", ] -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" - -[[package]] -name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" - -[[package]] -name = "version_check" -version = "0.9.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "walkdir" @@ -961,49 +662,45 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "wasm-encoder" -version = "0.216.0" +version = "0.229.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c23aebea22c8a75833ae08ed31ccc020835b12a41999e58c31464271b94a88" +checksum = "38ba1d491ecacb085a2552025c10a675a6fddcbd03b1fc9b36c536010ce265d2" dependencies = [ - "leb128", + "leb128fmt", + "wasmparser", ] [[package]] name = "wasm-testsuite" -version = "0.5.0" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735333508bfc70a3cc7a7ed86b3cd53e4a775cbf78511eaec8ac4926ed3a0bf7" dependencies = [ - "rust-embed", + "include_dir", + "wast", ] [[package]] name = "wasmparser" -version = "0.216.0" +version = "0.229.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" +checksum = "0cc3b1f053f5d41aa55640a1fa9b6d1b8a9e4418d118ce308d20e24ff3575a8c" dependencies = [ - "ahash 0.8.11", "bitflags", - "hashbrown 0.14.5", "indexmap", "semver", ] [[package]] name = "wast" -version = "216.0.0" +version = "229.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7eb1f2eecd913fdde0dc6c3439d0f24530a98ac6db6cb3d14d92a5328554a08" +checksum = "63fcaff613c12225696bb163f79ca38ffb40e9300eff0ff4b8aa8b2f7eadf0d9" dependencies = [ "bumpalo", - "leb128", + "leb128fmt", "memchr", "unicode-width", "wasm-encoder", @@ -1011,9 +708,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.216.0" +version = "1.229.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac0409090fb5154f95fb5ba3235675fd9e579e731524d63b6a2f653e1280c82a" +checksum = "4189bad08b70455a9e9e67dc126d2dcf91fac143a80f1046747a5dde6d4c33e0" dependencies = [ "wast", ] @@ -1024,16 +721,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", + "windows-sys", ] [[package]] @@ -1108,32 +796,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.76", -] diff --git a/Cargo.toml b/Cargo.toml index 81d9533..1f645cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,23 +4,25 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="216" -wat="1.216" +wast="229" +wat="1.229" +wasmparser={version="0.229", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" criterion={version="0.5", default-features=false, features=["cargo_bench_support", "rayon"]} [workspace.package] -version="0.8.0" -rust-version="1.80" -edition="2021" +version="0.9.0-alpha.0" +rust-version="1.85" +edition="2024" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] repository="https://github.com/explodingcamera/tinywasm" [package] name="tinywasm-root" +version.workspace=true publish=false edition.workspace=true rust-version.workspace=true @@ -51,3 +53,7 @@ lto="thin" codegen-units=1 panic="abort" inherits="release" + +[profile.samply] +inherits="release" +debug=true diff --git a/README.md b/README.md index 0cf8b66..27eb8f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- +

TinyWasm

A tiny WebAssembly Runtime written in safe Rust @@ -14,30 +14,36 @@ - **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality (< 4000 LLOC). - **Portable**: TinyWasm runs on any platform that Rust can target, including `no_std`, with minimal external dependencies. -- **Safe**: No unsafe code is used in the runtime (`rkyv` which uses unsafe code can be used for serialization, but is optional). +- **Safe**: No unsafe code is used in the runtime -## Status +## 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 Draft is mostly supported, with the exception of Fixed-Width SIMD and Memory64/Multiple Memories. See the [Supported Proposals](#supported-proposals) section for more information. +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 completly 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 / partialy supported\ +🚧 -- 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 | -| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🚧 | N/A | -| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🌑 | N/A | +| 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 | ## Usage @@ -60,24 +66,21 @@ $ tinywasm-cli --help - **`archive`**\ Enables pre-parsing of archives. This is enabled by default. -With all these features disabled, TinyWasm only depends on `core`, `alloc` ,and `libm` and can be used in `no_std` environments. -Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on wasm32 targets. +With all these features disabled, TinyWasm only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. ## Inspiration Big thanks to the authors of the following projects, which have inspired and influenced TinyWasm: -- [wasmi](https://github.com/wasmi-labs/wasmi) - an efficient and lightweight WebAssembly interpreter that also runs on `no_std` environments -- [wasm3](https://github.com/wasm3/wasm3) - a high performance WebAssembly interpreter written in C -- [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in go +- [wasmi](https://github.com/wasmi-labs/wasmi) - an efficient and lightweight WebAssembly interpreter that also runs in `no_std` environments +- [wasm3](https://github.com/wasm3/wasm3) - a high-performance WebAssembly interpreter written in C +- [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in Go - [wain](https://github.com/rhysd/wain) - a zero-dependency WebAssembly interpreter written in Rust -I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly Runtimes. +I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes. ## License Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in TinyWasm by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. - -**Note:** The GitHub repository contains a Submodule (`crates/tinywasm-parser/data`) which is licensed only under the [Apache License, Version 2.0](https://github.com/WebAssembly/spec/blob/main/test/LICENSE). This data is generated from the [WebAssembly Specification](https://github.com/WebAssembly/spec/tree/main/test) and is only used for testing purposes and not included in the final binary. diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 51a669d..d5e64a8 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,7 +15,7 @@ path="src/bin.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tinywasm={version="0.8.0-alpha.0", path="../tinywasm", features=["std", "parser"]} +tinywasm={version="0.9.0-alpha.0", path="../tinywasm", features=["std", "parser"]} argh="0.1" eyre={workspace=true} log={workspace=true} diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 96c3605..1fec2d5 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -25,6 +25,7 @@ impl FromStr for WasmArg { "i64" => val.parse::().map_err(|e| format!("invalid argument value for i64: {e:?}"))?.into(), "f32" => val.parse::().map_err(|e| format!("invalid argument value for f32: {e:?}"))?.into(), "f64" => val.parse::().map_err(|e| format!("invalid argument value for f64: {e:?}"))?.into(), + "v128" => val.parse::().map_err(|e| format!("invalid argument value for v128: {e:?}"))?.into(), t => return Err(format!("Invalid arg type: {t}")), }; diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 6efbba1..8c0fe93 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -4,7 +4,7 @@ use argh::FromArgs; use args::WasmArg; use eyre::Result; use log::{debug, info}; -use tinywasm::{types::WasmValue, Module}; +use tinywasm::{Module, types::WasmValue}; use crate::args::to_wasm_args; mod args; diff --git a/crates/cli/src/wat.rs b/crates/cli/src/wat.rs index fd00a8e..8d2998d 100644 --- a/crates/cli/src/wat.rs +++ b/crates/cli/src/wat.rs @@ -1,6 +1,6 @@ use wast::{ - parser::{self, ParseBuffer}, Wat, + parser::{self, ParseBuffer}, }; pub fn wat2wasm(wat: &str) -> Vec { diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index efb0817..7bd9cad 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -9,12 +9,11 @@ repository.workspace=true rust-version.workspace=true [dependencies] -wasmparser={version="0.216", default-features=false, features=["validate"]} +wasmparser={workspace=true, features=["validate", "features", "simd"]} log={workspace=true, optional=true} -tinywasm-types={version="0.8.0-alpha.0", path="../types", default-features=false} +tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} [features] default=["std", "logging"] logging=["log"] std=["tinywasm-types/std", "wasmparser/std"] -nightly=[] diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 10607a1..a4e3b35 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -92,7 +92,7 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result { - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))) + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); } }, }) @@ -105,11 +105,12 @@ pub(crate) fn convert_module_memories MemoryType { - MemoryType { - arch: if memory.memory64 { MemoryArch::I64 } else { MemoryArch::I32 }, - page_count_initial: memory.initial, - page_count_max: memory.maximum, - } + MemoryType::new( + if memory.memory64 { MemoryArch::I64 } else { MemoryArch::I32 }, + memory.initial, + memory.maximum, + memory.page_size_log2.map(|x| 1 << x), + ) } pub(crate) fn convert_module_tables<'a, T: IntoIterator>>>( @@ -156,7 +157,7 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result ExternalKind::Memory, wasmparser::ExternalKind::Global => ExternalKind::Global, wasmparser::ExternalKind::Tag => { - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported export kind: {:?}", export.kind))) + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported export kind: {:?}", export.kind))); } }; @@ -202,8 +203,8 @@ pub(crate) fn convert_module_code( } } - let (body, allocations) = process_operators_and_validate(validator, func, local_addr_map)?; - Ok(((body, local_counts), allocations)) + let (body, data, allocations) = process_operators_and_validate(validator, func, local_addr_map)?; + Ok(((body, data, local_counts), allocations)) } pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { @@ -250,12 +251,17 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result Ok(ConstInstruction::RefNull(convert_heaptype(*hty))), - wasmparser::Operator::RefFunc { function_index } => Ok(ConstInstruction::RefFunc(*function_index)), + wasmparser::Operator::RefNull { hty } => match convert_heaptype(*hty) { + ValType::RefFunc => Ok(ConstInstruction::RefFunc(None)), + ValType::RefExtern => Ok(ConstInstruction::RefExtern(None)), + _ => unimplemented!("Unsupported heap type: {:?}", hty), + }, + wasmparser::Operator::RefFunc { function_index } => Ok(ConstInstruction::RefFunc(Some(*function_index))), wasmparser::Operator::I32Const { value } => Ok(ConstInstruction::I32Const(*value)), wasmparser::Operator::I64Const { value } => Ok(ConstInstruction::I64Const(*value)), wasmparser::Operator::F32Const { value } => Ok(ConstInstruction::F32Const(f32::from_bits(value.bits()))), wasmparser::Operator::F64Const { value } => Ok(ConstInstruction::F64Const(f64::from_bits(value.bits()))), + wasmparser::Operator::V128Const { value } => Ok(ConstInstruction::V128Const(value.i128())), wasmparser::Operator::GlobalGet { global_index } => Ok(ConstInstruction::GlobalGet(*global_index)), op => Err(crate::ParseError::UnsupportedOperator(format!("Unsupported const instruction: {op:?}"))), } diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs index 7bd484f..9ff2f79 100644 --- a/crates/parser/src/error.rs +++ b/crates/parser/src/error.rs @@ -59,8 +59,7 @@ impl Display for ParseError { } } -#[cfg(any(feature = "std", all(not(feature = "std"), feature = "nightly")))] -impl crate::std::error::Error for ParseError {} +impl core::error::Error for ParseError {} impl From for ParseError { fn from(value: wasmparser::BinaryReaderError) -> Self { diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index dfe6b0c..aa33995 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -61,24 +61,28 @@ impl Parser { function_references: true, tail_call: true, multi_memory: true, + simd: true, + memory64: true, + custom_page_sizes: true, + extended_const: false, + wide_arithmetic: false, gc_types: true, + stack_switching: false, component_model: false, - component_model_nested_names: false, - component_model_values: false, - component_model_more_flags: false, exceptions: false, - extended_const: false, gc: false, - memory64: false, memory_control: false, relaxed_simd: false, - simd: false, threads: false, - custom_page_sizes: false, shared_everything_threads: false, - component_model_multiple_returns: 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, }; Validator::new_with_features(features.into()) } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 20ed00d..0e17ad9 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,14 +1,14 @@ use crate::log::debug; -use crate::{conversion, ParseError, Result}; +use crate::{ParseError, Result, conversion}; use alloc::string::ToString; use alloc::{boxed::Box, format, vec::Vec}; use tinywasm_types::{ - Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, ValType, - ValueCounts, ValueCountsSmall, WasmFunction, + Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, ValueCounts, + ValueCountsSmall, WasmFunction, WasmFunctionData, }; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Box<[Instruction]>, ValueCounts); +pub(crate) type Code = (Box<[Instruction]>, WasmFunctionData, ValueCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -130,7 +130,7 @@ impl ModuleReader { return Err(ParseError::DuplicateSection("Code section".into())); } self.code.reserve(count as usize); - validator.code_section_start(count, &range)?; + validator.code_section_start(&range)?; } CodeSectionEntry(function) => { debug!("Found code section entry"); @@ -168,9 +168,9 @@ impl ModuleReader { validator.end(offset)?; self.end_reached = true; } - CustomSection(reader) => { + CustomSection(_reader) => { debug!("Found custom section"); - debug!("Skipping custom section: {:?}", reader.name()); + debug!("Skipping custom section: {:?}", _reader.name()); } UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {section:?}"))), @@ -179,7 +179,6 @@ impl ModuleReader { Ok(()) } - #[inline] pub(crate) fn into_module(self) -> Result { if !self.end_reached { return Err(ParseError::EndNotReached); @@ -193,18 +192,10 @@ impl ModuleReader { .code .into_iter() .zip(self.code_type_addrs) - .map(|((instructions, locals), ty_idx)| { - let mut params = ValueCountsSmall::default(); + .map(|((instructions, 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(); - for param in &ty.params { - match param { - ValType::I32 | ValType::F32 => params.c32 += 1, - ValType::I64 | ValType::F64 => params.c64 += 1, - ValType::V128 => params.c128 += 1, - ValType::RefExtern | ValType::RefFunc => params.cref += 1, - } - } - WasmFunction { instructions, locals, params, ty } + let params = ValueCountsSmall::from(&ty.params); + WasmFunction { instructions, data, locals, params, ty } }) .collect::>() .into_boxed_slice(); diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 73cf9b4..10005c0 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -3,12 +3,15 @@ use crate::Result; use crate::conversion::{convert_heaptype, convert_valtype}; use alloc::string::ToString; use alloc::{boxed::Box, vec::Vec}; -use tinywasm_types::{Instruction, MemoryArg}; -use wasmparser::{FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, WasmModuleResources}; +use tinywasm_types::{Instruction, MemoryArg, WasmFunctionData}; +use wasmparser::{ + FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, VisitSimdOperator, WasmModuleResources, +}; struct ValidateThenVisit<'a, R: WasmModuleResources>(usize, &'a mut FunctionBuilder); + macro_rules! validate_then_visit { - ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => {$( + ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {$( fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { self.1.$visit($($($arg.clone()),*)?); self.1.validator_visitor(self.0).$visit($($($arg),*)?)?; @@ -19,14 +22,22 @@ macro_rules! validate_then_visit { impl<'a, R: WasmModuleResources> VisitOperator<'a> for ValidateThenVisit<'_, R> { type Output = Result<()>; - wasmparser::for_each_operator!(validate_then_visit); + wasmparser::for_each_visit_operator!(validate_then_visit); + + fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>> { + Some(self) + } +} + +impl VisitSimdOperator<'_> for ValidateThenVisit<'_, R> { + wasmparser::for_each_visit_simd_operator!(validate_then_visit); } pub(crate) fn process_operators_and_validate( validator: FuncValidator, body: FunctionBody<'_>, local_addr_map: Vec, -) -> Result<(Box<[Instruction]>, FuncValidatorAllocations)> { +) -> Result<(Box<[Instruction]>, 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); @@ -35,42 +46,64 @@ pub(crate) fn process_operators_and_validate( reader.visit_operator(&mut ValidateThenVisit(reader.original_position(), &mut builder))??; } - builder.validator_finish(reader.original_position())?; + reader.finish()?; if !builder.errors.is_empty() { return Err(builder.errors.remove(0)); } - Ok((builder.instructions.into_boxed_slice(), builder.validator.into_allocations())) + Ok(( + builder.instructions.into_boxed_slice(), + WasmFunctionData { v128_constants: builder.v128_constants.into_boxed_slice() }, + builder.validator.into_allocations(), + )) } -macro_rules! define_operands { - ($($name:ident, $instr:expr),*) => {$( +macro_rules! define_operand { + ($name:ident($instr:expr, $ty:ty)) => { + fn $name(&mut self, arg: $ty) -> Self::Output { + self.instructions.push($instr(arg).into()); + } + }; + + ($name:ident($instr:expr, $ty:ty, $ty2:ty)) => { + fn $name(&mut self, arg: $ty, arg2: $ty2) -> Self::Output { + self.instructions.push($instr(arg, arg2).into()); + } + }; + + ($name:ident($instr:expr)) => { fn $name(&mut self) -> Self::Output { - self.instructions.push($instr); + self.instructions.push($instr.into()); } + }; +} + +macro_rules! define_operands { + ($($name:ident($instr:ident $(,$ty:ty)*)),*) => {$( + define_operand!($name(Instruction::$instr $(,$ty)*)); )*}; } -macro_rules! define_primitive_operands { - ($($name:ident, $instr:expr, $ty:ty),*) => {$( - fn $name(&mut self, arg: $ty) -> Self::Output { - self.instructions.push($instr(arg)); +macro_rules! define_mem_operands { + ($($name:ident($instr:ident)),*) => {$( + fn $name(&mut self, memarg: wasmparser::MemArg) -> Self::Output { + self.instructions.push(Instruction::$instr(MemoryArg::new(memarg.offset, memarg.memory))); } )*}; - ($($name:ident, $instr:expr, $ty:ty, $ty2:ty),*) => {$( - fn $name(&mut self, arg: $ty, arg2: $ty2) -> Self::Output { - self.instructions.push($instr(arg, arg2)); +} + +macro_rules! define_mem_operands_simd { + ($($name:ident($instr:ident)),*) => {$( + fn $name(&mut self, memarg: wasmparser::MemArg) -> Self::Output { + self.instructions.push(Instruction::$instr(MemoryArg::new(memarg.offset, memarg.memory)).into()); } )*}; } -macro_rules! define_mem_operands { - ($($name:ident, $instr:ident),*) => {$( - fn $name(&mut self, memarg: wasmparser::MemArg) -> Self::Output { - self.instructions.push(Instruction::$instr { - offset: memarg.offset, - mem_addr: memarg.memory, - }); +macro_rules! define_mem_operands_simd_lane { + ($($name:ident($instr:ident)),*) => {$( + fn $name(&mut self, memarg: wasmparser::MemArg, lane: u8) -> Self::Output { + self.instructions.push(Instruction::$instr(MemoryArg::new(memarg.offset, memarg.memory), lane).into()); } )*}; } @@ -78,6 +111,7 @@ macro_rules! define_mem_operands { pub(crate) struct FunctionBuilder { validator: FuncValidator, instructions: Vec, + v128_constants: Vec, label_ptrs: Vec, local_addr_map: Vec, errors: Vec, @@ -87,12 +121,8 @@ impl FunctionBuilder { pub(crate) fn validator_visitor( &mut self, offset: usize, - ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> { - self.validator.visitor(offset) - } - - pub(crate) fn validator_finish(&mut self, offset: usize) -> Result<(), wasmparser::BinaryReaderError> { - self.validator.finish(offset) + ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { + self.validator.simd_visitor(offset) } } @@ -102,6 +132,7 @@ impl FunctionBuilder { validator, local_addr_map, instructions: Vec::with_capacity(instr_capacity), + v128_constants: Vec::new(), label_ptrs: Vec::with_capacity(256), errors: Vec::new(), } @@ -113,8 +144,8 @@ impl FunctionBuilder { } macro_rules! impl_visit_operator { - ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { - $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit);)* + ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { + $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit ($($ann:tt)*));)* }; (@@mvp $($rest:tt)* ) => {}; @@ -122,8 +153,10 @@ macro_rules! impl_visit_operator { (@@sign_extension $($rest:tt)* ) => {}; (@@saturating_float_to_int $($rest:tt)* ) => {}; (@@bulk_memory $($rest:tt)* ) => {}; + (@@simd $($rest:tt)* ) => {}; (@@tail_call $($rest:tt)* ) => {}; - (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident) => { + + (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { #[cold] fn $visit(&mut self $($(,$arg: $argty)*)?) { self.unsupported(stringify!($visit)) @@ -133,197 +166,31 @@ macro_rules! impl_visit_operator { impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuilder { type Output = (); - wasmparser::for_each_operator!(impl_visit_operator); + wasmparser::for_each_visit_operator!(impl_visit_operator); - define_primitive_operands! { - visit_br, Instruction::Br, u32, - visit_br_if, Instruction::BrIf, u32, - visit_global_get, Instruction::GlobalGet, u32, - visit_i32_const, Instruction::I32Const, i32, - visit_i64_const, Instruction::I64Const, i64, - visit_call, Instruction::Call, u32, - visit_memory_size, Instruction::MemorySize, u32, - visit_memory_grow, Instruction::MemoryGrow, u32 + fn simd_visitor(&mut self) -> Option<&mut dyn VisitSimdOperator<'a, Output = Self::Output>> { + Some(self) } 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, custom implementation - 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_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) } define_operands! { - visit_unreachable, Instruction::Unreachable, - visit_nop, Instruction::Nop, - visit_return, Instruction::Return, - visit_i32_eqz, Instruction::I32Eqz, - visit_i32_eq, Instruction::I32Eq, - visit_i32_ne, Instruction::I32Ne, - visit_i32_lt_s, Instruction::I32LtS, - visit_i32_lt_u, Instruction::I32LtU, - visit_i32_gt_s, Instruction::I32GtS, - visit_i32_gt_u, Instruction::I32GtU, - visit_i32_le_s, Instruction::I32LeS, - visit_i32_le_u, Instruction::I32LeU, - visit_i32_ge_s, Instruction::I32GeS, - visit_i32_ge_u, Instruction::I32GeU, - visit_i64_eqz, Instruction::I64Eqz, - visit_i64_eq, Instruction::I64Eq, - visit_i64_ne, Instruction::I64Ne, - visit_i64_lt_s, Instruction::I64LtS, - visit_i64_lt_u, Instruction::I64LtU, - visit_i64_gt_s, Instruction::I64GtS, - visit_i64_gt_u, Instruction::I64GtU, - visit_i64_le_s, Instruction::I64LeS, - visit_i64_le_u, Instruction::I64LeU, - visit_i64_ge_s, Instruction::I64GeS, - visit_i64_ge_u, Instruction::I64GeU, - visit_f32_eq, Instruction::F32Eq, - visit_f32_ne, Instruction::F32Ne, - visit_f32_lt, Instruction::F32Lt, - visit_f32_gt, Instruction::F32Gt, - visit_f32_le, Instruction::F32Le, - visit_f32_ge, Instruction::F32Ge, - visit_f64_eq, Instruction::F64Eq, - visit_f64_ne, Instruction::F64Ne, - visit_f64_lt, Instruction::F64Lt, - visit_f64_gt, Instruction::F64Gt, - visit_f64_le, Instruction::F64Le, - visit_f64_ge, Instruction::F64Ge, - visit_i32_clz, Instruction::I32Clz, - visit_i32_ctz, Instruction::I32Ctz, - visit_i32_popcnt, Instruction::I32Popcnt, - // visit_i32_add, Instruction::I32Add, custom implementation - visit_i32_sub, Instruction::I32Sub, - visit_i32_mul, Instruction::I32Mul, - visit_i32_div_s, Instruction::I32DivS, - visit_i32_div_u, Instruction::I32DivU, - visit_i32_rem_s, Instruction::I32RemS, - visit_i32_rem_u, Instruction::I32RemU, - visit_i32_and, Instruction::I32And, - visit_i32_or, Instruction::I32Or, - visit_i32_xor, Instruction::I32Xor, - visit_i32_shl, Instruction::I32Shl, - visit_i32_shr_s, Instruction::I32ShrS, - visit_i32_shr_u, Instruction::I32ShrU, - visit_i32_rotl, Instruction::I32Rotl, - visit_i32_rotr, Instruction::I32Rotr, - visit_i64_clz, Instruction::I64Clz, - visit_i64_ctz, Instruction::I64Ctz, - visit_i64_popcnt, Instruction::I64Popcnt, - visit_i64_add, Instruction::I64Add, - visit_i64_sub, Instruction::I64Sub, - visit_i64_mul, Instruction::I64Mul, - visit_i64_div_s, Instruction::I64DivS, - visit_i64_div_u, Instruction::I64DivU, - visit_i64_rem_s, Instruction::I64RemS, - visit_i64_rem_u, Instruction::I64RemU, - visit_i64_and, Instruction::I64And, - visit_i64_or, Instruction::I64Or, - visit_i64_xor, Instruction::I64Xor, - visit_i64_shl, Instruction::I64Shl, - visit_i64_shr_s, Instruction::I64ShrS, - visit_i64_shr_u, Instruction::I64ShrU, - // visit_i64_rotl, Instruction::I64Rotl, custom implementation - visit_i64_rotr, Instruction::I64Rotr, - visit_f32_abs, Instruction::F32Abs, - visit_f32_neg, Instruction::F32Neg, - visit_f32_ceil, Instruction::F32Ceil, - visit_f32_floor, Instruction::F32Floor, - visit_f32_trunc, Instruction::F32Trunc, - visit_f32_nearest, Instruction::F32Nearest, - visit_f32_sqrt, Instruction::F32Sqrt, - visit_f32_add, Instruction::F32Add, - visit_f32_sub, Instruction::F32Sub, - visit_f32_mul, Instruction::F32Mul, - visit_f32_div, Instruction::F32Div, - visit_f32_min, Instruction::F32Min, - visit_f32_max, Instruction::F32Max, - visit_f32_copysign, Instruction::F32Copysign, - visit_f64_abs, Instruction::F64Abs, - visit_f64_neg, Instruction::F64Neg, - visit_f64_ceil, Instruction::F64Ceil, - visit_f64_floor, Instruction::F64Floor, - visit_f64_trunc, Instruction::F64Trunc, - visit_f64_nearest, Instruction::F64Nearest, - visit_f64_sqrt, Instruction::F64Sqrt, - visit_f64_add, Instruction::F64Add, - visit_f64_sub, Instruction::F64Sub, - visit_f64_mul, Instruction::F64Mul, - visit_f64_div, Instruction::F64Div, - visit_f64_min, Instruction::F64Min, - visit_f64_max, Instruction::F64Max, - visit_f64_copysign, Instruction::F64Copysign, - visit_i32_wrap_i64, Instruction::I32WrapI64, - visit_i32_trunc_f32_s, Instruction::I32TruncF32S, - visit_i32_trunc_f32_u, Instruction::I32TruncF32U, - visit_i32_trunc_f64_s, Instruction::I32TruncF64S, - visit_i32_trunc_f64_u, Instruction::I32TruncF64U, - visit_i64_extend_i32_s, Instruction::I64ExtendI32S, - visit_i64_extend_i32_u, Instruction::I64ExtendI32U, - visit_i64_trunc_f32_s, Instruction::I64TruncF32S, - visit_i64_trunc_f32_u, Instruction::I64TruncF32U, - visit_i64_trunc_f64_s, Instruction::I64TruncF64S, - visit_i64_trunc_f64_u, Instruction::I64TruncF64U, - visit_f32_convert_i32_s, Instruction::F32ConvertI32S, - visit_f32_convert_i32_u, Instruction::F32ConvertI32U, - visit_f32_convert_i64_s, Instruction::F32ConvertI64S, - visit_f32_convert_i64_u, Instruction::F32ConvertI64U, - visit_f32_demote_f64, Instruction::F32DemoteF64, - visit_f64_convert_i32_s, Instruction::F64ConvertI32S, - visit_f64_convert_i32_u, Instruction::F64ConvertI32U, - visit_f64_convert_i64_s, Instruction::F64ConvertI64S, - visit_f64_convert_i64_u, Instruction::F64ConvertI64U, - visit_f64_promote_f32, Instruction::F64PromoteF32, - visit_i32_reinterpret_f32, Instruction::I32ReinterpretF32, - visit_i64_reinterpret_f64, Instruction::I64ReinterpretF64, - visit_f32_reinterpret_i32, Instruction::F32ReinterpretI32, - visit_f64_reinterpret_i64, Instruction::F64ReinterpretI64, + // 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), // sign_extension - visit_i32_extend8_s, Instruction::I32Extend8S, - visit_i32_extend16_s, Instruction::I32Extend16S, - visit_i64_extend8_s, Instruction::I64Extend8S, - visit_i64_extend16_s, Instruction::I64Extend16S, - visit_i64_extend32_s, Instruction::I64Extend32S, + 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), // Non-trapping Float-to-int Conversions - visit_i32_trunc_sat_f32_s, Instruction::I32TruncSatF32S, - visit_i32_trunc_sat_f32_u, Instruction::I32TruncSatF32U, - visit_i32_trunc_sat_f64_s, Instruction::I32TruncSatF64S, - visit_i32_trunc_sat_f64_u, Instruction::I32TruncSatF64U, - visit_i64_trunc_sat_f32_s, Instruction::I64TruncSatF32S, - visit_i64_trunc_sat_f32_u, Instruction::I64TruncSatF32U, - visit_i64_trunc_sat_f64_s, Instruction::I64TruncSatF64S, - visit_i64_trunc_sat_f64_u, Instruction::I64TruncSatF64U - } + visit_i32_trunc_sat_f32_s(I32TruncSatF32S), visit_i32_trunc_sat_f32_u(I32TruncSatF32U), visit_i32_trunc_sat_f64_s(I32TruncSatF64S), visit_i32_trunc_sat_f64_u(I32TruncSatF64U), visit_i64_trunc_sat_f32_s(I64TruncSatF32S), visit_i64_trunc_sat_f32_u(I64TruncSatF32U), visit_i64_trunc_sat_f64_s(I64TruncSatF64S), visit_i64_trunc_sat_f64_u(I64TruncSatF64U), - fn visit_return_call(&mut self, function_index: u32) -> Self::Output { - self.instructions.push(Instruction::ReturnCall(function_index)); - } + // Reference Types + 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), - fn visit_return_call_indirect(&mut self, type_index: u32, table_index: u32) -> Self::Output { - self.instructions.push(Instruction::ReturnCallIndirect(type_index, table_index)); + // 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) } fn visit_global_set(&mut self, global_index: u32) -> Self::Output { @@ -353,17 +220,13 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild _ => self.visit_unreachable(), } } + fn visit_select(&mut self) -> Self::Output { match self.validator.get_operand_type(1) { Some(Some(t)) => self.visit_typed_select(t), _ => self.visit_unreachable(), } } - fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { - let arg = MemoryArg { offset: memarg.offset, mem_addr: memarg.memory }; - let i32store = Instruction::I32Store { offset: arg.offset, mem_addr: arg.mem_addr }; - self.instructions.push(i32store); - } fn visit_local_get(&mut self, idx: u32) -> Self::Output { let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { @@ -452,14 +315,6 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } } - fn visit_i64_rotl(&mut self) -> Self::Output { - self.instructions.push(Instruction::I64Rotl); - } - - fn visit_i32_add(&mut self) -> Self::Output { - self.instructions.push(Instruction::I32Add); - } - fn visit_block(&mut self, blockty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); self.instructions.push(match blockty { @@ -573,6 +428,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild 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)); + } fn visit_f32_const(&mut self, val: wasmparser::Ieee32) -> Self::Output { self.instructions.push(Instruction::F32Const(f32::from_bits(val.bits()))); @@ -582,19 +440,6 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild self.instructions.push(Instruction::F64Const(f64::from_bits(val.bits()))); } - // Bulk Memory Operations - - define_primitive_operands! { - visit_memory_init, Instruction::MemoryInit, u32, u32, - visit_memory_copy, Instruction::MemoryCopy, u32, u32, - visit_table_init, Instruction::TableInit, u32, u32 - } - define_primitive_operands! { - visit_memory_fill, Instruction::MemoryFill, u32, - visit_data_drop, Instruction::DataDrop, u32, - visit_elem_drop, Instruction::ElemDrop, u32 - } - fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { self.instructions.push(Instruction::TableCopy { from: src_table, to: dst_table }); } @@ -618,13 +463,79 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::Ref(_) => Instruction::SelectRef, }); } +} + +macro_rules! impl_visit_simd_operator { + ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { + $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit ($($ann:tt)*));)* + }; + + (@@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)) + } + }; +} + +impl wasmparser::VisitSimdOperator<'_> for FunctionBuilder { + wasmparser::for_each_visit_simd_operator!(impl_visit_simd_operator); - define_primitive_operands! { - visit_ref_func, Instruction::RefFunc, u32, - visit_table_fill, Instruction::TableFill, u32, - visit_table_get, Instruction::TableGet, u32, - visit_table_set, Instruction::TableSet, u32, - visit_table_grow, Instruction::TableGrow, u32, - visit_table_size, Instruction::TableSize, u32 + // simd + define_mem_operands_simd! { + visit_v128_load(V128Load), visit_v128_load8x8_s(V128Load8x8S), visit_v128_load8x8_u(V128Load8x8U), visit_v128_load16x4_s(V128Load16x4S), visit_v128_load16x4_u(V128Load16x4U), visit_v128_load32x2_s(V128Load32x2S), visit_v128_load32x2_u(V128Load32x2U), visit_v128_load8_splat(V128Load8Splat), visit_v128_load16_splat(V128Load16Splat), visit_v128_load32_splat(V128Load32Splat), visit_v128_load64_splat(V128Load64Splat), visit_v128_load32_zero(V128Load32Zero), visit_v128_load64_zero(V128Load64Zero), visit_v128_store(V128Store) + } + define_mem_operands_simd_lane! { + visit_v128_load8_lane(V128Load8Lane), visit_v128_load16_lane(V128Load16Lane), visit_v128_load32_lane(V128Load32Lane), visit_v128_load64_lane(V128Load64Lane), + visit_v128_store8_lane(V128Store8Lane), visit_v128_store16_lane(V128Store16Lane), visit_v128_store32_lane(V128Store32Lane), visit_v128_store64_lane(V128Store64Lane) + } + define_operands! { + visit_v128_not(V128Not), visit_v128_and(V128And), visit_v128_andnot(V128AndNot), visit_v128_or(V128Or), visit_v128_xor(V128Xor), visit_v128_bitselect(V128Bitselect), visit_v128_any_true(V128AnyTrue), + visit_i8x16_splat(I8x16Splat), visit_i8x16_swizzle(I8x16Swizzle), visit_i8x16_eq(I8x16Eq), visit_i8x16_ne(I8x16Ne), visit_i8x16_lt_s(I8x16LtS), visit_i8x16_lt_u(I8x16LtU), visit_i8x16_gt_s(I8x16GtS), visit_i8x16_gt_u(I8x16GtU), visit_i8x16_le_s(I8x16LeS), visit_i8x16_le_u(I8x16LeU), visit_i8x16_ge_s(I8x16GeS), visit_i8x16_ge_u(I8x16GeU), + visit_i16x8_splat(I16x8Splat), visit_i16x8_eq(I16x8Eq), visit_i16x8_ne(I16x8Ne), visit_i16x8_lt_s(I16x8LtS), visit_i16x8_lt_u(I16x8LtU), visit_i16x8_gt_s(I16x8GtS), visit_i16x8_gt_u(I16x8GtU), visit_i16x8_le_s(I16x8LeS), visit_i16x8_le_u(I16x8LeU), visit_i16x8_ge_s(I16x8GeS), visit_i16x8_ge_u(I16x8GeU), + visit_i32x4_splat(I32x4Splat), visit_i32x4_eq(I32x4Eq), visit_i32x4_ne(I32x4Ne), visit_i32x4_lt_s(I32x4LtS), visit_i32x4_lt_u(I32x4LtU), visit_i32x4_gt_s(I32x4GtS), visit_i32x4_gt_u(I32x4GtU), visit_i32x4_le_s(I32x4LeS), visit_i32x4_le_u(I32x4LeU), visit_i32x4_ge_s(I32x4GeS), visit_i32x4_ge_u(I32x4GeU), + visit_i64x2_splat(I64x2Splat), visit_i64x2_eq(I64x2Eq), visit_i64x2_ne(I64x2Ne), visit_i64x2_lt_s(I64x2LtS), visit_i64x2_gt_s(I64x2GtS), visit_i64x2_le_s(I64x2LeS), visit_i64x2_ge_s(I64x2GeS), + visit_f32x4_splat(F32x4Splat), visit_f32x4_eq(F32x4Eq), visit_f32x4_ne(F32x4Ne), visit_f32x4_lt(F32x4Lt), visit_f32x4_gt(F32x4Gt), visit_f32x4_le(F32x4Le), visit_f32x4_ge(F32x4Ge), + visit_f64x2_splat(F64x2Splat), visit_f64x2_eq(F64x2Eq), visit_f64x2_ne(F64x2Ne), visit_f64x2_lt(F64x2Lt), visit_f64x2_gt(F64x2Gt), visit_f64x2_le(F64x2Le), visit_f64x2_ge(F64x2Ge), + visit_i8x16_abs(I8x16Abs), visit_i8x16_neg(I8x16Neg), visit_i8x16_all_true(I8x16AllTrue), visit_i8x16_bitmask(I8x16Bitmask), visit_i8x16_shl(I8x16Shl), visit_i8x16_shr_s(I8x16ShrS), visit_i8x16_shr_u(I8x16ShrU), visit_i8x16_add(I8x16Add), visit_i8x16_sub(I8x16Sub), visit_i8x16_min_s(I8x16MinS), visit_i8x16_min_u(I8x16MinU), visit_i8x16_max_s(I8x16MaxS), visit_i8x16_max_u(I8x16MaxU), + visit_i16x8_abs(I16x8Abs), visit_i16x8_neg(I16x8Neg), visit_i16x8_all_true(I16x8AllTrue), visit_i16x8_bitmask(I16x8Bitmask), visit_i16x8_shl(I16x8Shl), visit_i16x8_shr_s(I16x8ShrS), visit_i16x8_shr_u(I16x8ShrU), visit_i16x8_add(I16x8Add), visit_i16x8_sub(I16x8Sub), visit_i16x8_min_s(I16x8MinS), visit_i16x8_min_u(I16x8MinU), visit_i16x8_max_s(I16x8MaxS), visit_i16x8_max_u(I16x8MaxU), + visit_i32x4_abs(I32x4Abs), visit_i32x4_neg(I32x4Neg), visit_i32x4_all_true(I32x4AllTrue), visit_i32x4_bitmask(I32x4Bitmask), visit_i32x4_shl(I32x4Shl), visit_i32x4_shr_s(I32x4ShrS), visit_i32x4_shr_u(I32x4ShrU), visit_i32x4_add(I32x4Add), visit_i32x4_sub(I32x4Sub), visit_i32x4_min_s(I32x4MinS), visit_i32x4_min_u(I32x4MinU), visit_i32x4_max_s(I32x4MaxS), visit_i32x4_max_u(I32x4MaxU), + visit_i64x2_abs(I64x2Abs), visit_i64x2_neg(I64x2Neg), visit_i64x2_all_true(I64x2AllTrue), visit_i64x2_bitmask(I64x2Bitmask), visit_i64x2_shl(I64x2Shl), visit_i64x2_shr_s(I64x2ShrS), visit_i64x2_shr_u(I64x2ShrU), visit_i64x2_add(I64x2Add), visit_i64x2_sub(I64x2Sub), visit_i64x2_mul(I64x2Mul), + visit_i8x16_narrow_i16x8_s(I8x16NarrowI16x8S), visit_i8x16_narrow_i16x8_u(I8x16NarrowI16x8U), visit_i8x16_add_sat_s(I8x16AddSatS), visit_i8x16_add_sat_u(I8x16AddSatU), visit_i8x16_sub_sat_s(I8x16SubSatS), visit_i8x16_sub_sat_u(I8x16SubSatU), visit_i8x16_avgr_u(I8x16AvgrU), + visit_i16x8_narrow_i32x4_s(I16x8NarrowI32x4S), visit_i16x8_narrow_i32x4_u(I16x8NarrowI32x4U), visit_i16x8_add_sat_s(I16x8AddSatS), visit_i16x8_add_sat_u(I16x8AddSatU), visit_i16x8_sub_sat_s(I16x8SubSatS), visit_i16x8_sub_sat_u(I16x8SubSatU), visit_i16x8_avgr_u(I16x8AvgrU), + visit_i16x8_extadd_pairwise_i8x16_s(I16x8ExtAddPairwiseI8x16S), visit_i16x8_extadd_pairwise_i8x16_u(I16x8ExtAddPairwiseI8x16U), visit_i16x8_mul(I16x8Mul), + visit_i32x4_extadd_pairwise_i16x8_s(I32x4ExtAddPairwiseI16x8S), visit_i32x4_extadd_pairwise_i16x8_u(I32x4ExtAddPairwiseI16x8U), visit_i32x4_mul(I32x4Mul), + visit_i16x8_extmul_low_i8x16_s(I16x8ExtMulLowI8x16S), visit_i16x8_extmul_low_i8x16_u(I16x8ExtMulLowI8x16U), visit_i16x8_extmul_high_i8x16_s(I16x8ExtMulHighI8x16S), visit_i16x8_extmul_high_i8x16_u(I16x8ExtMulHighI8x16U), + visit_i32x4_extmul_low_i16x8_s(I32x4ExtMulLowI16x8S), visit_i32x4_extmul_low_i16x8_u(I32x4ExtMulLowI16x8U), visit_i32x4_extmul_high_i16x8_s(I32x4ExtMulHighI16x8S), visit_i32x4_extmul_high_i16x8_u(I32x4ExtMulHighI16x8U), + visit_i64x2_extmul_low_i32x4_s(I64x2ExtMulLowI32x4S), visit_i64x2_extmul_low_i32x4_u(I64x2ExtMulLowI32x4U), visit_i64x2_extmul_high_i32x4_s(I64x2ExtMulHighI32x4S), visit_i64x2_extmul_high_i32x4_u(I64x2ExtMulHighI32x4U), + visit_i16x8_extend_low_i8x16_s(I16x8ExtendLowI8x16S), visit_i16x8_extend_low_i8x16_u(I16x8ExtendLowI8x16U), visit_i16x8_extend_high_i8x16_s(I16x8ExtendHighI8x16S), visit_i16x8_extend_high_i8x16_u(I16x8ExtendHighI8x16U), + visit_i32x4_extend_low_i16x8_s(I32x4ExtendLowI16x8S), visit_i32x4_extend_low_i16x8_u(I32x4ExtendLowI16x8U), visit_i32x4_extend_high_i16x8_s(I32x4ExtendHighI16x8S), visit_i32x4_extend_high_i16x8_u(I32x4ExtendHighI16x8U), + visit_i64x2_extend_low_i32x4_s(I64x2ExtendLowI32x4S), visit_i64x2_extend_low_i32x4_u(I64x2ExtendLowI32x4U), visit_i64x2_extend_high_i32x4_s(I64x2ExtendHighI32x4S), visit_i64x2_extend_high_i32x4_u(I64x2ExtendHighI32x4U), + visit_i8x16_popcnt(I8x16Popcnt), visit_i16x8_q15mulr_sat_s(I16x8Q15MulrSatS), visit_i32x4_dot_i16x8_s(I32x4DotI16x8S), + visit_f32x4_ceil(F32x4Ceil), visit_f32x4_floor(F32x4Floor), visit_f32x4_trunc(F32x4Trunc), visit_f32x4_nearest(F32x4Nearest), visit_f32x4_abs(F32x4Abs), visit_f32x4_neg(F32x4Neg), visit_f32x4_sqrt(F32x4Sqrt), visit_f32x4_add(F32x4Add), visit_f32x4_sub(F32x4Sub), visit_f32x4_mul(F32x4Mul), visit_f32x4_div(F32x4Div), visit_f32x4_min(F32x4Min), visit_f32x4_max(F32x4Max), visit_f32x4_pmin(F32x4PMin), visit_f32x4_pmax(F32x4PMax), + visit_f64x2_ceil(F64x2Ceil), visit_f64x2_floor(F64x2Floor), visit_f64x2_trunc(F64x2Trunc), visit_f64x2_nearest(F64x2Nearest), visit_f64x2_abs(F64x2Abs), visit_f64x2_neg(F64x2Neg), visit_f64x2_sqrt(F64x2Sqrt), visit_f64x2_add(F64x2Add), visit_f64x2_sub(F64x2Sub), visit_f64x2_mul(F64x2Mul), visit_f64x2_div(F64x2Div), visit_f64x2_min(F64x2Min), visit_f64x2_max(F64x2Max), visit_f64x2_pmin(F64x2PMin), visit_f64x2_pmax(F64x2PMax), + visit_i32x4_trunc_sat_f32x4_s(I32x4TruncSatF32x4S), visit_i32x4_trunc_sat_f32x4_u(I32x4TruncSatF32x4U), + visit_f32x4_convert_i32x4_s(F32x4ConvertI32x4S), visit_f32x4_convert_i32x4_u(F32x4ConvertI32x4U), + visit_i32x4_trunc_sat_f64x2_s_zero(I32x4TruncSatF64x2SZero), visit_i32x4_trunc_sat_f64x2_u_zero(I32x4TruncSatF64x2UZero), + 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_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), + visit_i64x2_extract_lane(I64x2ExtractLane, u8), visit_i64x2_replace_lane(I64x2ReplaceLane, u8), + visit_f32x4_extract_lane(F32x4ExtractLane, u8), visit_f32x4_replace_lane(F32x4ReplaceLane, u8), + visit_f64x2_extract_lane(F64x2ExtractLane, u8), visit_f64x2_replace_lane(F64x2ReplaceLane, u8) + } + + 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)); + } + + 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()); } } diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 98f88f1..10a3099 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -15,13 +15,15 @@ path="src/lib.rs" [dependencies] log={workspace=true, optional=true} -tinywasm-parser={version="0.8.0-alpha.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.8.0-alpha.0", path="../types", default-features=false} +tinywasm-parser={version="0.9.0-alpha.0", path="../parser", default-features=false, optional=true} +tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} libm={version="0.2", default-features=false} [dev-dependencies] -wasm-testsuite={path="../wasm-testsuite"} +wasm-testsuite={version="0.5.3"} +indexmap="2.7" wast={workspace=true} +wat={workspace=true} eyre={workspace=true} pretty_env_logger={workspace=true} criterion={workspace=true} @@ -30,28 +32,50 @@ serde_json={version="1.0"} serde={version="1.0", features=["derive"]} [features] -default=["std", "parser", "logging", "archive"] +default=["std", "parser", "logging", "archive", "canonicalize_nans"] + logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] -parser=["tinywasm-parser"] + +# support for parsing WebAssembly +parser=["dep:tinywasm-parser"] + +# support for "archiving" tinywasm bytecode archive=["tinywasm-types/archive"] -simd=[] -nightly=["tinywasm-parser?/nightly"] + +# canonicalize all NaN values to a single representation +canonicalize_nans=[] + +# enable simd support (unstable / unfinished) +__simd=[] [[test]] name="test-wasm-1" harness=false -test=false [[test]] name="test-wasm-2" harness=false -test=false + +[[test]] +name="test-wasm-3" +harness=false [[test]] name="test-wasm-multi-memory" harness=false -test=false + +[[test]] +name="test-wasm-annotations" +harness=false + +[[test]] +name="test-wasm-custom-page-sizes" +harness=false + +[[test]] +name="test-wasm-tail-call" +harness=false [[test]] name="test-wasm-memory64" @@ -59,12 +83,12 @@ harness=false test=false [[test]] -name="test-wasm-annotations" +name="test-wasm-extended-const" harness=false test=false [[test]] -name="test-wasm-extended-const" +name="test-wasm-relaxed-simd" harness=false test=false diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index 0a8f033..aa8b38b 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -1,7 +1,7 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use eyre::Result; use tinywasm::{ModuleInstance, Store, types}; -use types::{archive::AlignedVec, TinyWasmModule}; +use types::TinyWasmModule; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.opt.wasm"); @@ -11,13 +11,13 @@ fn argon2id_parse() -> Result { Ok(data) } -fn argon2id_to_twasm(module: TinyWasmModule) -> Result { - let twasm = module.serialize_twasm(); +fn argon2id_to_twasm(module: &TinyWasmModule) -> Result> { + let twasm = module.serialize_twasm()?; Ok(twasm) } -fn argon2id_from_twasm(twasm: AlignedVec) -> Result { - let module = TinyWasmModule::from_twasm(&twasm)?; +fn argon2id_from_twasm(twasm: &[u8]) -> Result { + let module = TinyWasmModule::from_twasm(twasm)?; Ok(module) } @@ -31,11 +31,11 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = argon2id_parse().expect("argon2id_parse"); - let twasm = argon2id_to_twasm(module.clone()).expect("argon2id_to_twasm"); + let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); - c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(module.clone()))); - c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(twasm.clone()))); + c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); + c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); c.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone()))); } diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index 557d787..a5f1c72 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -1,7 +1,7 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use eyre::Result; use tinywasm::{ModuleInstance, Store, types}; -use types::{archive::AlignedVec, TinyWasmModule}; +use types::TinyWasmModule; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.opt.wasm"); @@ -11,14 +11,13 @@ fn fibonacci_parse() -> Result { Ok(data) } -fn fibonacci_to_twasm(module: TinyWasmModule) -> Result { - let twasm = module.serialize_twasm(); +fn fibonacci_to_twasm(module: &TinyWasmModule) -> Result> { + let twasm = module.serialize_twasm()?; Ok(twasm) } -fn fibonacci_from_twasm(twasm: AlignedVec) -> Result { - let module = TinyWasmModule::from_twasm(&twasm)?; - Ok(module) +fn fibonacci_from_twasm(twasm: &[u8]) -> Result { + Ok(TinyWasmModule::from_twasm(twasm)?) } fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> { @@ -37,11 +36,11 @@ fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> fn criterion_benchmark(c: &mut Criterion) { let module = fibonacci_parse().expect("fibonacci_parse"); - let twasm = fibonacci_to_twasm(module.clone()).expect("fibonacci_to_twasm"); + let twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm"); c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); - c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(module.clone()))); - c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(twasm.clone()))); + c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); + c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); c.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60))); c.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26))); } diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index cfc9cb8..4848a4a 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -1,7 +1,7 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use eyre::Result; use tinywasm::{Extern, FuncContext, Imports, ModuleInstance, Store, types}; -use types::{archive::AlignedVec, TinyWasmModule}; +use types::TinyWasmModule; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.opt.wasm"); @@ -11,13 +11,13 @@ fn tinywasm_parse() -> Result { Ok(data) } -fn tinywasm_to_twasm(module: TinyWasmModule) -> Result { - let twasm = module.serialize_twasm(); +fn tinywasm_to_twasm(module: &TinyWasmModule) -> Result> { + let twasm = module.serialize_twasm()?; Ok(twasm) } -fn tinywasm_from_twasm(twasm: AlignedVec) -> Result { - let module = TinyWasmModule::from_twasm(&twasm)?; +fn tinywasm_from_twasm(twasm: &[u8]) -> Result { + let module = TinyWasmModule::from_twasm(twasm)?; Ok(module) } @@ -33,11 +33,11 @@ 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.clone()).expect("tinywasm_to_twasm"); + let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(module.clone()))); - c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(twasm.clone()))); + 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()))); } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 73df9a2..9637521 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,12 +1,15 @@ use alloc::string::{String, ToString}; +use alloc::vec::Vec; use core::{fmt::Display, ops::ControlFlow}; use tinywasm_types::FuncType; +use tinywasm_types::archive::TwasmError; #[cfg(feature = "parser")] pub use tinywasm_parser::ParseError; /// Errors that can occur for `TinyWasm` operations #[derive(Debug)] +#[non_exhaustive] pub enum Error { /// A WebAssembly trap occurred Trap(Trap), @@ -20,8 +23,13 @@ pub enum Error { /// An unknown error occurred Other(String), - /// A function did not return a value - FuncDidNotReturn, + /// A host function returned an invalid value + InvalidHostFnReturn { + /// The expected type + expected: FuncType, + /// The actual value + actual: Vec, + }, /// An invalid label type was encountered InvalidLabelType, @@ -35,7 +43,10 @@ pub enum Error { #[cfg(feature = "parser")] /// A parsing error occurred - ParseError(ParseError), + Parser(ParseError), + + /// A serialization error occurred + Twasm(TwasmError), } #[derive(Debug)] @@ -163,6 +174,11 @@ impl From for Error { } } +impl From for Error { + fn from(value: TwasmError) -> Self { + Self::Twasm(value) + } +} impl From for Error { fn from(value: Trap) -> Self { Self::Trap(value) @@ -173,17 +189,20 @@ impl Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { #[cfg(feature = "parser")] - Self::ParseError(err) => write!(f, "error parsing module: {err:?}"), + Self::Parser(err) => write!(f, "error parsing module: {err:?}"), #[cfg(feature = "std")] Self::Io(err) => write!(f, "I/O error: {err}"), + Self::Twasm(err) => write!(f, "serialization error: {err}"), Self::Trap(trap) => write!(f, "trap: {trap}"), Self::Linker(err) => write!(f, "linking error: {err}"), Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {message}"), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {feature}"), - Self::FuncDidNotReturn => write!(f, "function did not return"), + Self::InvalidHostFnReturn { expected, actual } => { + write!(f, "invalid host function return: expected={expected:?}, actual={actual:?}") + } Self::InvalidStore => write!(f, "invalid store"), } } @@ -225,13 +244,12 @@ impl Display for Trap { } } -#[cfg(any(feature = "std", all(not(feature = "std"), feature = "nightly")))] -impl crate::std::error::Error for Error {} +impl core::error::Error for Error {} #[cfg(feature = "parser")] impl From for Error { fn from(value: tinywasm_parser::ParseError) -> Self { - Self::ParseError(value) + Self::Parser(value) } } diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 466dd86..2eeabca 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,8 +1,8 @@ use crate::interpreter::stack::{CallFrame, Stack}; -use crate::{log, unlikely, Function}; use crate::{Error, FuncContext, Result, Store}; +use crate::{Function, log, unlikely}; use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; -use tinywasm_types::{FuncType, ModuleInstanceAddr, ValType, WasmValue}; +use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, ValType, WasmValue}; #[derive(Debug)] /// A function handle @@ -37,9 +37,9 @@ impl FuncHandle { } // 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 !(func_ty.params.iter().zip(params).enumerate().all(|(_i, (ty, param))| { if ty != ¶m.val_type() { - log::error!("param type mismatch at index {}: expected {:?}, got {:?}", i, ty, param); + log::error!("param type mismatch at index {}: expected {:?}, got {:?}", _i, ty, param); false } else { true @@ -51,9 +51,9 @@ impl FuncHandle { let func_inst = store.get_func(self.addr); let wasm_func = match &func_inst.func { Function::Host(host_func) => { - let func = &host_func.clone().func; + let host_func = host_func.clone(); let ctx = FuncContext { store, module_addr: self.module_addr }; - return (func)(ctx, params); + return host_func.call(ctx, params); } Function::Wasm(wasm_func) => wasm_func, }; @@ -219,6 +219,18 @@ impl ToValType for 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,)+) @@ -251,11 +263,15 @@ 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); diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 3a226f4..12876c8 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use core::fmt::Debug; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; -use crate::{log, LinkingError, MemoryRef, MemoryRefMut, Result}; +use crate::{LinkingError, MemoryRef, MemoryRefMut, Result, log}; use tinywasm_types::*; /// The internal representation of a function @@ -139,7 +139,26 @@ impl Extern { ty: &tinywasm_types::FuncType, func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static, ) -> Self { - Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(func), ty: ty.clone() }))) + let _ty = ty.clone(); + let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { + let _ty = _ty.clone(); + let result = func(ctx, args)?; + + 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() }); + } + Ok(()) + })?; + + Ok(result) + }; + + Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty: ty.clone() }))) } /// Create a new typed function import @@ -192,7 +211,7 @@ impl From<&Import> for ExternName { /// # use log; /// # fn main() -> tinywasm::Result<()> { /// use tinywasm::{Imports, Extern}; -/// use tinywasm::types::{ValType, TableType, MemoryType, WasmValue}; +/// use tinywasm::types::{ValType, TableType, MemoryType, MemoryArch, WasmValue}; /// let mut imports = Imports::new(); /// /// // function args can be either a single @@ -208,7 +227,7 @@ impl From<&Import> for ExternName { /// imports /// .define("my_module", "print_i32", print_i32)? /// .define("my_module", "table", Extern::table(table_type, table_init))? -/// .define("my_module", "memory", Extern::memory(MemoryType::new_32(1, Some(2))))? +/// .define("my_module", "memory", Extern::memory(MemoryType::new(MemoryArch::I32, 1, Some(2), None)))? /// .define("my_module", "global_i32", Extern::global(WasmValue::I32(666), false))? /// .link_module("my_other_module", 0)?; /// # Ok(()) @@ -303,7 +322,7 @@ impl Imports { match (expected.size_max, actual.size_max) { (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()), (Some(expected_max), Some(actual_max)) if actual_max < expected_max => { - return Err(LinkingError::incompatible_import_type(import).into()) + return Err(LinkingError::incompatible_import_type(import).into()); } _ => {} } @@ -317,22 +336,20 @@ impl Imports { actual: &MemoryType, real_size: Option, ) -> Result<()> { - Self::compare_types(import, &expected.arch, &actual.arch)?; + Self::compare_types(import, &expected.arch(), &actual.arch())?; - if actual.page_count_initial > expected.page_count_initial - && real_size.map_or(true, |size| actual.page_count_initial > size as u64) + if actual.page_count_initial() > expected.page_count_initial() + && real_size.is_none_or(|size| actual.page_count_initial() > size as u64) { return Err(LinkingError::incompatible_import_type(import).into()); } - if expected.page_count_max.is_none() && actual.page_count_max.is_some() { + if expected.page_size() != actual.page_size() { return Err(LinkingError::incompatible_import_type(import).into()); } - if let (Some(expected_max), Some(actual_max)) = (expected.page_count_max, actual.page_count_max) { - if actual_max < expected_max { - return Err(LinkingError::incompatible_import_type(import).into()); - } + if expected.page_count_max() > actual.page_count_max() { + return Err(LinkingError::incompatible_import_type(import).into()); } Ok(()) diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index c2df82c..4bf4f5d 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -4,19 +4,28 @@ use super::no_std_floats::NoStdFloatExt; use alloc::{format, rc::Rc, string::ToString}; use core::ops::ControlFlow; + use interpreter::stack::CallFrame; use tinywasm_types::*; +#[cfg(all(feature = "std", feature = "__simd"))] +use crate::std::simd::StdFloat; +#[cfg(feature = "__simd")] +use core::simd::{cmp::*, num::*, *}; + +#[cfg(feature = "__simd")] +use core::ops::{Index, IndexMut, Shl, Shr}; + use super::num_helpers::*; use super::stack::{BlockFrame, BlockType, Stack}; use super::values::*; use crate::*; -pub(super) struct Executor<'store, 'stack> { - cf: CallFrame, - module: ModuleInstance, - store: &'store mut Store, - stack: &'stack mut Stack, +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, } impl<'store, 'stack> Executor<'store, 'stack> { @@ -26,7 +35,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { Ok(Self { cf: current_frame, module: current_module, stack, store }) } - #[inline] + #[inline(always)] pub(crate) fn run_to_completion(&mut self) -> Result<()> { loop { if let ControlFlow::Break(res) = self.exec_next() { @@ -41,6 +50,8 @@ impl<'store, 'stack> Executor<'store, 'stack> { #[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()?, @@ -55,8 +66,11 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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), + 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())), @@ -114,30 +128,30 @@ impl<'store, 'stack> Executor<'store, 'stack> { ElemDrop(elem_index) => self.exec_elem_drop(*elem_index), TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, - I32Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v)?, - I64Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v)?, - F32Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v)?, - F64Store { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v)?, - I32Store8 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v as i8)?, - I32Store16 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v as i16)?, - I64Store8 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v as i8)?, - I64Store16 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v as i16)?, - I64Store32 { mem_addr, offset } => self.exec_mem_store::(*mem_addr, *offset, |v| v as i32)?, - - I32Load { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v)?, - I64Load { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v)?, - F32Load { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v)?, - F64Load { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v)?, - I32Load8S { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i32)?, - I32Load8U { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i32)?, - I32Load16S { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i32)?, - I32Load16U { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i32)?, - I64Load8S { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i64)?, - I64Load8U { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i64)?, - I64Load16S { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i64)?, - I64Load16U { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i64)?, - I64Load32S { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i64)?, - I64Load32U { mem_addr, offset } => self.exec_mem_load::(*mem_addr, *offset, |v| v as i64)?, + 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(), |v| v as i32)?, + I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i32)?, + I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i32)?, + I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i32)?, + I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i64)?, + I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i64)?, + I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i64)?, + I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i64)?, + I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i64)?, + I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v as i64)?, 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()?, @@ -302,9 +316,364 @@ impl<'store, 'stack> Executor<'store, 'stack> { LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), - instr => { - unreachable!("unimplemented instruction: {:?}", instr); - } + #[cfg(feature = "__simd")] V128Not => self.stack.values.replace_top_same::(|v| Ok(!v)).to_cf()?, + #[cfg(feature = "__simd")] V128And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, + #[cfg(feature = "__simd")] V128AndNot => self.stack.values.calculate_same::(|a, b| Ok(a & (!b))).to_cf()?, + #[cfg(feature = "__simd")] V128Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, + #[cfg(feature = "__simd")] V128Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, + #[cfg(feature = "__simd")] V128Bitselect => self.stack.values.calculate_same_3::(|v1, v2, c| Ok((v1 & c) | (v2 & !c))).to_cf()?, + #[cfg(feature = "__simd")] V128AnyTrue => self.stack.values.replace_top::(|v| Ok((v.reduce_or() != 0) as i32)).to_cf()?, + #[cfg(feature = "__simd")] I8x16Swizzle => self.stack.values.calculate_same::(|a, s| Ok(a.swizzle_dyn(s))).to_cf()?, + #[cfg(feature = "__simd")] V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, + #[cfg(feature = "__simd")] V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, + + #[cfg(feature = "__simd")] V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + #[cfg(feature = "__simd")] V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + #[cfg(feature = "__simd")] V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + #[cfg(feature = "__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 = "__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 = "__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 = "__simd")] V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].to_le_bytes().into()), + + #[cfg(feature = "__simd")] I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + #[cfg(feature = "__simd")] I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + #[cfg(feature = "__simd")] I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + #[cfg(feature = "__simd")] I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + #[cfg(feature = "__simd")] I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + #[cfg(feature = "__simd")] I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + #[cfg(feature = "__simd")] F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + #[cfg(feature = "__simd")] F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + + #[cfg(feature = "__simd")] V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + #[cfg(feature = "__simd")] V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + #[cfg(feature = "__simd")] V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + #[cfg(feature = "__simd")] V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + + #[cfg(feature = "__simd")] I8x16Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i8))).to_cf()?, + #[cfg(feature = "__simd")] I16x8Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i16))).to_cf()?, + #[cfg(feature = "__simd")] I32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + #[cfg(feature = "__simd")] I64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + #[cfg(feature = "__simd")] F32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + #[cfg(feature = "__simd")] F64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I64x2LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I8x16LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I64x2GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I64x2LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I64x2GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I16x8GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + #[cfg(feature = "__simd")] I32x4GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + #[cfg(feature = "__simd")] I16x8Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + #[cfg(feature = "__simd")] I32x4Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + #[cfg(feature = "__simd")] I64x2Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + #[cfg(feature = "__simd")] I16x8Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + #[cfg(feature = "__simd")] I32x4Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + #[cfg(feature = "__simd")] I64x2Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + + #[cfg(feature = "__simd")] I8x16AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + #[cfg(feature = "__simd")] I16x8AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + #[cfg(feature = "__simd")] I32x4AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + #[cfg(feature = "__simd")] I64x2AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + #[cfg(feature = "__simd")] I16x8Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + #[cfg(feature = "__simd")] I32x4Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + #[cfg(feature = "__simd")] I64x2Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i8))).to_cf()?, + #[cfg(feature = "__simd")] I16x8Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i16))).to_cf()?, + #[cfg(feature = "__simd")] I32x4Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a))).to_cf()?, + #[cfg(feature = "__simd")] I64x2Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i64))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i8))).to_cf()?, + #[cfg(feature = "__simd")] I16x8ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i16))).to_cf()?, + #[cfg(feature = "__simd")] I32x4ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a))).to_cf()?, + #[cfg(feature = "__simd")] I64x2ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i64))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u8))).to_cf()?, + #[cfg(feature = "__simd")] I16x8ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u16))).to_cf()?, + #[cfg(feature = "__simd")] I32x4ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u32))).to_cf()?, + #[cfg(feature = "__simd")] I64x2ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u64))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + #[cfg(feature = "__simd")] I16x8Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + #[cfg(feature = "__simd")] I32x4Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + #[cfg(feature = "__simd")] I64x2Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + + #[cfg(feature = "__simd")] I8x16Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + #[cfg(feature = "__simd")] I16x8Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + #[cfg(feature = "__simd")] I32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + #[cfg(feature = "__simd")] I64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + + #[cfg(feature = "__simd")] I8x16MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + #[cfg(feature = "__simd")] I32x4MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + #[cfg(feature = "__simd")] I32x4MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + #[cfg(feature = "__simd")] I32x4MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + + #[cfg(feature = "__simd")] I8x16MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + #[cfg(feature = "__simd")] I32x4MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + + #[cfg(feature = "__simd")] I64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + #[cfg(feature = "__simd")] I16x8Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + #[cfg(feature = "__simd")] I32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + + #[cfg(feature = "__simd")] I8x16NarrowI16x8S => unimplemented!(), + #[cfg(feature = "__simd")] I8x16NarrowI16x8U => unimplemented!(), + #[cfg(feature = "__simd")] I16x8NarrowI32x4S => unimplemented!(), + #[cfg(feature = "__simd")] I16x8NarrowI32x4U => unimplemented!(), + + #[cfg(feature = "__simd")] I8x16AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + #[cfg(feature = "__simd")] I8x16AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + #[cfg(feature = "__simd")] I8x16SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + #[cfg(feature = "__simd")] I8x16SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + #[cfg(feature = "__simd")] I16x8SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + + #[cfg(feature = "__simd")] I16x8ExtAddPairwiseI8x16S => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtAddPairwiseI8x16U => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtAddPairwiseI16x8S => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtAddPairwiseI16x8U => unimplemented!(), + + #[cfg(feature = "__simd")] I16x8ExtMulLowI8x16S => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtMulLowI8x16U => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtMulHighI8x16S => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtMulHighI8x16U => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtMulLowI16x8S => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtMulLowI16x8U => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtMulHighI16x8S => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtMulHighI16x8U => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtMulLowI32x4S => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtMulLowI32x4U => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtMulHighI32x4S => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtMulHighI32x4U => unimplemented!(), + + #[cfg(feature = "__simd")] I16x8ExtendLowI8x16S => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtendLowI8x16U => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtendHighI8x16S => unimplemented!(), + #[cfg(feature = "__simd")] I16x8ExtendHighI8x16U => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtendLowI16x8S => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtendLowI16x8U => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtendHighI16x8S => unimplemented!(), + #[cfg(feature = "__simd")] I32x4ExtendHighI16x8U => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtendLowI32x4S => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtendLowI32x4U => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtendHighI32x4S => unimplemented!(), + #[cfg(feature = "__simd")] I64x2ExtendHighI32x4U => unimplemented!(), + + #[cfg(feature = "__simd")] I8x16Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones())).to_cf()?, + + #[cfg(feature = "__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 = "__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 = "__simd")] F32x4Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, + #[cfg(feature = "__simd")] F64x2Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, + #[cfg(feature = "__simd")] F32x4Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, + #[cfg(feature = "__simd")] F64x2Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, + #[cfg(feature = "__simd")] F32x4Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f32x4(v.sqrt()))).to_cf()?, + #[cfg(feature = "__simd")] F64x2Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f64x2(v.sqrt()))).to_cf()?, + #[cfg(feature = "__simd")] F32x4Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a + b))).to_cf()?, + #[cfg(feature = "__simd")] F64x2Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a + b))).to_cf()?, + #[cfg(feature = "__simd")] F32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a - b))).to_cf()?, + #[cfg(feature = "__simd")] F64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a - b))).to_cf()?, + #[cfg(feature = "__simd")] F32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a * b))).to_cf()?, + #[cfg(feature = "__simd")] F64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a * b))).to_cf()?, + #[cfg(feature = "__simd")] F32x4Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a / b))).to_cf()?, + #[cfg(feature = "__simd")] F64x2Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a / b))).to_cf()?, + #[cfg(feature = "__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 = "__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 = "__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 = "__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 = "__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 = "__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 = "__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 = "__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 = "__simd")] I32x4TruncSatF32x4S => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, + #[cfg(feature = "__simd")] I32x4TruncSatF32x4U => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, + #[cfg(feature = "__simd")] F32x4ConvertI32x4S => {}, + #[cfg(feature = "__simd")] F32x4ConvertI32x4U => {}, + #[cfg(feature = "__simd")] F64x2ConvertLowI32x4S => {}, + #[cfg(feature = "__simd")] F64x2ConvertLowI32x4U => {}, + #[cfg(feature = "__simd")] F32x4DemoteF64x2Zero => {}, + #[cfg(feature = "__simd")] F64x2PromoteLowF32x4 => {}, + #[cfg(feature = "__simd")] I32x4TruncSatF64x2SZero => unimplemented!(), + #[cfg(feature = "__simd")] I32x4TruncSatF64x2UZero => unimplemented!(), + + i => return ControlFlow::Break(Some(Error::UnsupportedFeature(format!("unimplemented opcode: {i:?}")))), }; self.cf.incr_instr_ptr(); @@ -316,50 +685,71 @@ impl<'store, 'stack> Executor<'store, 'stack> { ControlFlow::Break(Some(Trap::Unreachable.into())) } - fn exec_call(&mut self, wasm_func: Rc, owner: ModuleInstanceAddr) -> ControlFlow> { - let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32); - self.cf.incr_instr_ptr(); // skip the call instruction - self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; - self.module.swap_with(self.cf.module_addr(), self.store); + fn exec_call( + &mut self, + wasm_func: Rc, + owner: ModuleInstanceAddr, + ) -> ControlFlow> { + if !IS_RETURN_CALL { + let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals); + let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32); + self.cf.incr_instr_ptr(); // skip the call instruction + self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; + self.module.swap_with(self.cf.module_addr(), self.store); + } else { + let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals); + self.cf.reuse_for(wasm_func, locals, self.stack.blocks.len() as u32, owner); + self.module.swap_with(self.cf.module_addr(), self.store); + } + + ControlFlow::Continue(()) + } + 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); + self.cf.incr_instr_ptr(); ControlFlow::Continue(()) } - fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { + fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { let func_inst = self.store.get_func(self.module.resolve_func_addr(v)); - let wasm_func = match &func_inst.func { - crate::Function::Wasm(wasm_func) => wasm_func, - crate::Function::Host(host_func) => { - let func = &host_func.clone(); - let params = self.stack.values.pop_params(&host_func.ty.params); - let res = - (func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms).to_cf()?; - self.stack.values.extend_from_wasmvalues(&res); - self.cf.incr_instr_ptr(); - return ControlFlow::Continue(()); - } - }; - - self.exec_call(wasm_func.clone(), func_inst.owner) + 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_indirect(&mut self, type_addr: u32, table_addr: u32) -> ControlFlow> { + fn exec_call_indirect( + &mut self, + type_addr: u32, + table_addr: u32, + ) -> ControlFlow> { // 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; assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); - table - .get(table_idx) - .map_err(|_| Error::Trap(Trap::UndefinedElement { index: table_idx as usize })) - .to_cf()? - .addr() - .ok_or(Error::Trap(Trap::UninitializedElement { index: table_idx as usize })) - .to_cf()? + 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 func_inst = self.store.get_func(func_ref); let call_ty = self.module.func_ty(type_addr); - let wasm_func = match &func_inst.func { - crate::Function::Wasm(f) => f, + + match func_inst.func.clone() { + 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(), + )); + } + + self.exec_call::(wasm_func, func_inst.owner) + } crate::Function::Host(host_func) => { if unlikely(host_func.ty != *call_ty) { return ControlFlow::Break(Some( @@ -368,27 +758,9 @@ impl<'store, 'stack> Executor<'store, 'stack> { )); } - let host_func = host_func.clone(); - let params = self.stack.values.pop_params(&host_func.ty.params); - let res = - match (host_func.func)(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms) { - Ok(res) => res, - Err(e) => return ControlFlow::Break(Some(e)), - }; - - self.stack.values.extend_from_wasmvalues(&res); - self.cf.incr_instr_ptr(); - return ControlFlow::Continue(()); + self.exec_call_host(host_func) } - }; - - if unlikely(wasm_func.ty != *call_ty) { - return ControlFlow::Break(Some( - Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into(), - )); } - - self.exec_call(wasm_func.clone(), func_inst.owner) } fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) { @@ -514,16 +886,31 @@ impl<'store, 'stack> Executor<'store, 'stack> { fn exec_memory_size(&mut self, addr: u32) { let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)); - self.stack.values.push::(mem.page_count as i32); + + match mem.is_64bit() { + true => self.stack.values.push::(mem.page_count as i64), + false => self.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 as i32; - let pages_delta = self.stack.values.pop::(); - self.stack.values.push::(match mem.grow(pages_delta) { - Some(_) => prev_size, - None => -1, - }); + let prev_size = mem.page_count; + + let pages_delta = match mem.is_64bit() { + true => self.stack.values.pop::(), + false => self.stack.values.pop::() as i64, + }; + + 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), + }; } fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { @@ -585,10 +972,10 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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() + 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() + 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(); @@ -601,23 +988,28 @@ impl<'store, 'stack> Executor<'store, 'stack> { 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))?; - table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?)?; + table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?) } - Ok(()) } - fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( + #[cfg(feature = "__simd")] + fn exec_mem_load_lane< + LOAD: MemLoadable, + INTO: InternalValue + IndexMut, + const LOAD_SIZE: usize, + >( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, - cast: fn(LOAD) -> TARGET, + 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; let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { cold(); @@ -628,9 +1020,60 @@ impl<'store, 'stack> Executor<'store, 'stack> { }))); }; let val = mem.load_as::(addr).to_cf()?; + imm[lane as usize] = val; + self.stack.values.push(imm); + ControlFlow::Continue(()) + } + + 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)); + + let addr = match mem.is_64bit() { + true => self.stack.values.pop::() as u64, + false => self.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 val = mem.load_as::(addr).to_cf()?; self.stack.values.push(cast(val)); ControlFlow::Continue(()) } + + #[cfg(feature = "__simd")] + fn exec_mem_store_lane, U: MemStorable + 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(); + + let addr = match mem.is_64bit() { + true => self.stack.values.pop::() as u64, + false => self.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)); + } + + ControlFlow::Continue(()) + } + fn exec_mem_store, const N: usize>( &mut self, mem_addr: tinywasm_types::MemAddr, @@ -640,10 +1083,16 @@ impl<'store, 'stack> Executor<'store, 'stack> { let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); let val = self.stack.values.pop::(); let val = (cast(val)).to_mem_bytes(); - let addr = self.stack.values.pop::() as u64; + + let addr = match mem.is_64bit() { + true => self.stack.values.pop::() as u64, + false => self.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)); } + ControlFlow::Continue(()) } @@ -703,7 +1152,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); }; - table.init(dst, &items[offset as usize..(offset + size) as usize]) + table.init(dst as i64, &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)); diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs index 5b9471e..e273184 100644 --- a/crates/tinywasm/src/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -1,34 +1,25 @@ pub(super) trait NoStdFloatExt { fn round(self) -> Self; - fn abs(self) -> Self; - fn signum(self) -> Self; fn ceil(self) -> Self; fn floor(self) -> Self; fn trunc(self) -> Self; fn sqrt(self) -> Self; - fn copysign(self, other: Self) -> Self; } #[rustfmt::skip] impl NoStdFloatExt for f64 { #[inline] fn round(self) -> Self { libm::round(self) } - #[inline] fn abs(self) -> Self { libm::fabs(self) } - #[inline] fn signum(self) -> Self { libm::copysign(1.0, self) } #[inline] fn ceil(self) -> Self { libm::ceil(self) } #[inline] fn floor(self) -> Self { libm::floor(self) } #[inline] fn trunc(self) -> Self { libm::trunc(self) } #[inline] fn sqrt(self) -> Self { libm::sqrt(self) } - #[inline] fn copysign(self, other: Self) -> Self { libm::copysign(self, other) } } #[rustfmt::skip] impl NoStdFloatExt for f32 { #[inline] fn round(self) -> Self { libm::roundf(self) } - #[inline] fn abs(self) -> Self { libm::fabsf(self) } - #[inline] fn signum(self) -> Self { libm::copysignf(1.0, self) } #[inline] fn ceil(self) -> Self { libm::ceilf(self) } #[inline] fn floor(self) -> Self { libm::floorf(self) } #[inline] fn trunc(self) -> Self { libm::truncf(self) } #[inline] fn sqrt(self) -> Self { libm::sqrtf(self) } - #[inline] fn copysign(self, other: Self) -> Self { libm::copysignf(self, other) } } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 02fbc24..7e8a461 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -72,9 +72,13 @@ macro_rules! impl_wasm_float_ops { ($($t:ty)*) => ($( impl TinywasmFloatExt for $t { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest + #[inline] fn tw_nearest(self) -> Self { match self { + #[cfg(not(feature = "canonicalize_nans"))] x if x.is_nan() => x, // preserve NaN + #[cfg(feature = "canonicalize_nans")] + x if x.is_nan() => Self::NAN, // Do not preserve NaN x if x.is_infinite() || x == 0.0 => x, // preserve infinities and zeros x if (0.0..=0.5).contains(&x) => 0.0, x if (-0.5..0.0).contains(&x) => -0.0, @@ -99,7 +103,10 @@ macro_rules! impl_wasm_float_ops { Some(core::cmp::Ordering::Less) => self, Some(core::cmp::Ordering::Greater) => other, Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { self } else { other }, + #[cfg(not(feature = "canonicalize_nans"))] None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + #[cfg(feature = "canonicalize_nans")] + None => Self::NAN, // Do not preserve NaN } } @@ -111,7 +118,10 @@ macro_rules! impl_wasm_float_ops { Some(core::cmp::Ordering::Greater) => self, Some(core::cmp::Ordering::Less) => other, Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { other } else { self }, + #[cfg(not(feature = "canonicalize_nans"))] None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + #[cfg(feature = "canonicalize_nans")] + None => Self::NAN, // Do not preserve NaN } } } @@ -180,3 +190,35 @@ macro_rules! impl_checked_wrapping_rem { } impl_checked_wrapping_rem! { i32 i64 u32 u64 } + +#[cfg(all(feature = "__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 = "__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 = "__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 = "__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/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 7c2be9c..c7f02b6 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,13 +1,13 @@ use core::ops::ControlFlow; use super::BlockType; -use crate::interpreter::values::*; use crate::Trap; -use crate::{unlikely, Error}; +use crate::interpreter::values::*; +use crate::{Error, unlikely}; use alloc::boxed::Box; use alloc::{rc::Rc, vec, vec::Vec}; -use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmValue}; +use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; @@ -22,12 +22,12 @@ impl CallStack { Self { stack: vec![initial_frame] } } - #[inline(always)] + #[inline] pub(crate) fn pop(&mut self) -> Option { self.stack.pop() } - #[inline(always)] + #[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())); @@ -60,44 +60,67 @@ impl Locals { } pub(crate) fn set(&mut self, local_index: LocalAddr, value: T) { - T::local_set(self, local_index, value) + T::local_set(self, local_index, value); } } impl CallFrame { - #[inline(always)] + #[inline] pub(crate) fn instr_ptr(&self) -> usize { self.instr_ptr } - #[inline(always)] + #[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(always)] + #[inline] pub(crate) fn jump(&mut self, offset: usize) { self.instr_ptr += offset; } - #[inline(always)] + #[inline] pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { self.module_addr } - #[inline(always)] + #[inline] pub(crate) fn block_ptr(&self) -> u32 { self.block_ptr } #[inline(always)] pub(crate) fn fetch_instr(&self) -> &Instruction { - &self.func_instance.instructions[self.instr_ptr] + 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(always)] + #[inline] pub(crate) fn break_to( &mut self, break_to_relative: u32, @@ -140,7 +163,7 @@ impl CallFrame { Some(()) } - #[inline(always)] + #[inline] pub(crate) fn new( wasm_func_inst: Rc, owner: ModuleInstanceAddr, @@ -192,7 +215,7 @@ impl CallFrame { Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } } - #[inline(always)] + #[inline] pub(crate) fn instructions(&self) -> &[Instruction] { &self.func_instance.instructions } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 03c676e..481a88c 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; -use tinywasm_types::{ValType, ValueCounts, ValueCountsSmall, WasmValue}; +use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; -use crate::{interpreter::*, Result}; +use crate::{Result, interpreter::*}; use super::Locals; pub(crate) const STACK_32_SIZE: usize = 1024 * 32; @@ -48,7 +48,7 @@ impl ValueStack { #[inline] pub(crate) fn push(&mut self, value: T) { - T::stack_push(self, value) + T::stack_push(self, value); } #[inline] @@ -67,12 +67,21 @@ impl ValueStack { } #[inline] - pub(crate) fn calculate_same(&mut self, func: fn(T, T) -> Result) -> Result<()> { + pub(crate) fn calculate_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { T::stack_calculate(self, func) } #[inline] - pub(crate) fn calculate(&mut self, func: fn(T, T) -> Result) -> Result<()> { + #[allow(dead_code)] + pub(crate) fn calculate_same_3(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { + T::stack_calculate3(self, func) + } + + #[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)?); @@ -80,14 +89,29 @@ impl ValueStack { } #[inline] - pub(crate) fn replace_top(&mut self, func: fn(T) -> Result) -> Result<()> { + #[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(()) + } + + #[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(()) } #[inline] - pub(crate) fn replace_top_same(&mut self, func: fn(T) -> Result) -> Result<()> { + pub(crate) fn replace_top_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { T::replace_top(self, func) } @@ -164,23 +188,22 @@ impl ValueStack { match val_type { ValType::I32 => WasmValue::I32(self.pop()), ValType::I64 => WasmValue::I64(self.pop()), - ValType::V128 => WasmValue::V128(self.pop()), ValType::F32 => WasmValue::F32(self.pop()), ValType::F64 => WasmValue::F64(self.pop()), - ValType::RefExtern => match self.pop() { - Some(v) => WasmValue::RefExtern(v), - None => WasmValue::RefNull(ValType::RefExtern), - }, - ValType::RefFunc => match self.pop() { - Some(v) => WasmValue::RefFunc(v), - None => WasmValue::RefNull(ValType::RefFunc), - }, + ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(self.pop())), + ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(self.pop())), + + #[cfg(not(feature = "__simd"))] + ValType::V128 => WasmValue::V128(self.pop()), + + #[cfg(feature = "__simd")] + ValType::V128 => WasmValue::V128(i128::from_le_bytes(self.pop::().to_array())), } } pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { for value in values { - self.push_dyn(value.into()) + self.push_dyn(value.into()); } } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 7b363a8..779fc60 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,13 +1,17 @@ use crate::Result; -use tinywasm_types::{LocalAddr, ValType, WasmValue}; use super::stack::{Locals, ValueStack}; +use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, WasmValue}; pub(crate) type Value32 = u32; pub(crate) type Value64 = u64; -pub(crate) type Value128 = u128; pub(crate) type ValueRef = Option; +#[cfg(feature = "__simd")] +pub(crate) type Value128 = core::simd::u8x16; +#[cfg(not(feature = "__simd"))] +pub(crate) type Value128 = i128; + #[derive(Debug, Clone, Copy, PartialEq)] /// A untyped WebAssembly value pub enum TinyWasmValue { @@ -106,15 +110,14 @@ impl TinyWasmValue { 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 = "__simd")] + ValType::V128 => WasmValue::V128(i128::from_le_bytes(self.unwrap_128().to_array())), + + #[cfg(not(feature = "__simd"))] ValType::V128 => WasmValue::V128(self.unwrap_128()), - ValType::RefExtern => match self.unwrap_ref() { - Some(v) => WasmValue::RefExtern(v), - None => WasmValue::RefNull(ValType::RefExtern), - }, - ValType::RefFunc => match self.unwrap_ref() { - Some(v) => WasmValue::RefFunc(v), - None => WasmValue::RefNull(ValType::RefFunc), - }, } } } @@ -124,11 +127,16 @@ impl From<&WasmValue> for TinyWasmValue { match value { WasmValue::I32(v) => TinyWasmValue::Value32(*v as u32), WasmValue::I64(v) => TinyWasmValue::Value64(*v as u64), - WasmValue::V128(v) => TinyWasmValue::Value128(*v), WasmValue::F32(v) => TinyWasmValue::Value32(v.to_bits()), WasmValue::F64(v) => TinyWasmValue::Value64(v.to_bits()), - WasmValue::RefFunc(v) | WasmValue::RefExtern(v) => TinyWasmValue::ValueRef(Some(*v)), - WasmValue::RefNull(_) => TinyWasmValue::ValueRef(None), + WasmValue::RefExtern(v) => TinyWasmValue::ValueRef(v.addr()), + WasmValue::RefFunc(v) => TinyWasmValue::ValueRef(v.addr()), + + #[cfg(not(feature = "__simd"))] + WasmValue::V128(v) => TinyWasmValue::Value128(*v), + + #[cfg(feature = "__simd")] + WasmValue::V128(v) => TinyWasmValue::Value128(v.to_le_bytes().into()), } } } @@ -146,10 +154,14 @@ mod sealed { pub(crate) trait InternalValue: sealed::Sealed + Into { fn stack_push(stack: &mut ValueStack, value: Self); - fn replace_top(stack: &mut ValueStack, func: fn(Self) -> Result) -> Result<()> + fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> where Self: Sized; - fn stack_calculate(stack: &mut ValueStack, func: fn(Self, Self) -> Result) -> Result<()> + fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> + where + Self: Sized; + #[allow(dead_code)] + fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> where Self: Sized; @@ -169,7 +181,6 @@ macro_rules! impl_internalvalue { impl sealed::Sealed for $outer {} impl From<$outer> for TinyWasmValue { - #[inline(always)] fn from(value: $outer) -> Self { TinyWasmValue::$variant($to_internal(value)) } @@ -180,44 +191,72 @@ macro_rules! impl_internalvalue { fn stack_push(stack: &mut ValueStack, value: Self) { stack.$stack.push($to_internal(value)); } + #[inline(always)] fn stack_pop(stack: &mut ValueStack) -> Self { - ($to_outer)(stack.$stack.pop().expect("ValueStack underflow, this is a bug")) + 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 { - ($to_outer)(*stack.$stack.last().expect("ValueStack underflow, this is a bug")) + match stack.$stack.last() { + Some(v) => $to_outer(*v), + None => unreachable!("ValueStack underflow, this is a bug"), + } } #[inline(always)] - fn stack_calculate(stack: &mut ValueStack, func: fn(Self, Self) -> Result) -> Result<()> { + fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); - if let (Some(v1), Some(v2)) = (v1, v2) { - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2))?); - } else { - unreachable!("ValueStack underflow, this is a bug"); - } - Ok(()) + 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(()) } #[inline(always)] - fn replace_top(stack: &mut ValueStack, func: fn(Self) -> Result) -> Result<()> { - if let Some(v) = stack.$stack.last_mut() { - *v = $to_internal(func($to_outer(*v))?); - Ok(()) - } else { + 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(()) + } + + #[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(()) } #[inline(always)] fn local_get(locals: &Locals, index: LocalAddr) -> Self { - $to_outer(locals.$locals[index as usize]) + match locals.$locals.get(index as usize) { + Some(v) => $to_outer(*v), + None => unreachable!("Local variable out of bounds, this is a bug"), + } } + #[inline(always)] fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) { - locals.$locals[index as usize] = $to_internal(value); + 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"), + } } } )* @@ -231,6 +270,25 @@ impl_internalvalue! { Value64, stack_64, locals_64, u64, i64, |v| v as u64, |v| v as i64 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 - Value128, stack_128, locals_128, Value128, Value128, |v| v, |v| v ValueRef, stack_ref, locals_ref, ValueRef, ValueRef, |v| v, |v| v + Value128, stack_128, locals_128, Value128, Value128, |v| v, |v| v +} + +#[cfg(feature = "__simd")] +use core::simd::{num::SimdUint, *}; + +#[cfg(feature = "__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) } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 7038dd6..9cb6c0a 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -4,8 +4,8 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -#![cfg_attr(feature = "nightly", feature(portable_simd))] #![forbid(unsafe_code)] +#![cfg_attr(feature = "__simd", feature(portable_simd))] //! A tiny WebAssembly Runtime written in Rust //! @@ -26,7 +26,6 @@ //! With all these features disabled, `TinyWasm` only depends on `core`, `alloc` and `libm`. //! By disabling `std`, you can use `TinyWasm` in `no_std` environments. This requires //! a custom allocator and removes support for parsing from files and streams, but otherwise the API is the same. -//! Additionally, to have proper error types in `no_std`, you currently need a `nightly` compiler to use the unstable error trait in `core`. //! //! ## Getting Started //! The easiest way to get started is to use the [`Module::parse_bytes`] function to load a diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index f6e8e04..26cea9c 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -47,6 +47,7 @@ impl Module { /// Instantiate the module in the given store /// /// Runs the start function if it exists + /// /// If you want to run the start function yourself, use `ModuleInstance::instantiate` /// /// See diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 3765098..e32a2f3 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -16,14 +16,14 @@ pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); #[derive(Debug)] pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance); -impl<'a> MemoryRefLoad for MemoryRef<'a> { +impl MemoryRefLoad for MemoryRef<'_> { /// Load a slice of memory fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { self.0.load(offset, len) } } -impl<'a> MemoryRefLoad for MemoryRefMut<'a> { +impl MemoryRefLoad for MemoryRefMut<'_> { /// Load a slice of memory fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { self.0.load(offset, len) @@ -54,7 +54,7 @@ impl MemoryRefMut<'_> { } /// Grow the memory by the given number of pages - pub fn grow(&mut self, delta_pages: i32) -> Option { + pub fn grow(&mut self, delta_pages: i64) -> Option { self.0.grow(delta_pages) } diff --git a/crates/tinywasm/src/std.rs b/crates/tinywasm/src/std.rs index f1e97a4..90368e1 100644 --- a/crates/tinywasm/src/std.rs +++ b/crates/tinywasm/src/std.rs @@ -9,10 +9,4 @@ pub(crate) use std::*; pub(crate) mod error { #[cfg(feature = "std")] extern crate std; - - #[cfg(feature = "std")] - pub(crate) use std::error::Error; - - #[cfg(all(not(feature = "std"), feature = "nightly"))] - pub(crate) use core::error::Error; } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index b86435d..7abd7b9 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -1,12 +1,11 @@ use alloc::vec; use alloc::vec::Vec; -use tinywasm_types::{MemoryType, ModuleInstanceAddr}; +use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; -use crate::{cold, log, Error, Result}; +use crate::{Error, Result, cold, interpreter::Value128, log}; -const PAGE_SIZE: usize = 65536; -const MAX_PAGES: usize = 65536; -const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; +#[cfg(feature = "__simd")] +use core::simd::ToBytes; /// A WebAssembly Memory Instance /// @@ -21,17 +20,22 @@ pub(crate) struct MemoryInstance { impl MemoryInstance { pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { - assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); - log::debug!("initializing memory with {} pages", kind.page_count_initial); + 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; PAGE_SIZE * kind.page_count_initial as usize], - page_count: kind.page_count_initial as usize, + data: vec![0; kind.initial_size() as usize], + page_count: kind.page_count_initial() as usize, _owner: owner, } } + #[inline] + pub(crate) fn is_64bit(&self) -> bool { + matches!(self.kind.arch(), MemoryArch::I64) + } + #[inline(always)] pub(crate) fn len(&self) -> usize { self.data.len() @@ -58,7 +62,7 @@ impl MemoryInstance { } pub(crate) fn max_pages(&self) -> usize { - self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize + self.kind.page_count_max().try_into().unwrap_or(usize::MAX) } pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[u8]> { @@ -84,7 +88,7 @@ impl MemoryInstance { return Err(self.trap_oob(addr, SIZE)); } - Ok(T::from_le_bytes(match self.data[addr..end].try_into() { + Ok(T::from_mem_bytes(match self.data[addr..end].try_into() { Ok(bytes) => bytes, Err(_) => return Err(self.trap_oob(addr, SIZE)), })) @@ -128,17 +132,18 @@ impl MemoryInstance { } #[inline] - pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { + pub(crate) fn grow(&mut self, pages_delta: i64) -> Option { let current_pages = self.page_count; - let new_pages = current_pages as i64 + pages_delta as i64; - debug_assert!(new_pages <= i32::MAX as i64, "page count should never be greater than i32::MAX"); + let new_pages = current_pages as i64 + pages_delta; - if new_pages < 0 || new_pages > MAX_PAGES as i64 || new_pages as usize > self.max_pages() { + 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()); return None; } - let new_size = new_pages as usize * PAGE_SIZE; - if new_size as u64 > MAX_SIZE { + let new_size = (new_pages as u64 * self.kind.page_size()) as usize; + if new_size as u64 > self.kind.max_size() { return None; } @@ -146,7 +151,7 @@ impl MemoryInstance { 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 i32) + Some(current_pages as i64) } } @@ -159,30 +164,52 @@ pub(crate) trait MemStorable { /// A trait for types that can be loaded from memory pub(crate) trait MemLoadable: Sized + Copy { /// Load a value from memory - fn from_le_bytes(bytes: [u8; N]) -> Self; + fn from_mem_bytes(bytes: [u8; N]) -> Self; } macro_rules! impl_mem_traits { - ($($type:ty, $size:expr),*) => { + ($($ty:ty, $size:expr),*) => { $( - impl MemLoadable<$size> for $type { + impl MemLoadable<$size> for $ty { #[inline(always)] - fn from_le_bytes(bytes: [u8; $size]) -> Self { - <$type>::from_le_bytes(bytes) + fn from_mem_bytes(bytes: [u8; $size]) -> Self { + <$ty>::from_le_bytes(bytes.into()) } } - impl MemStorable<$size> for $type { + impl MemStorable<$size> for $ty { #[inline(always)] fn to_mem_bytes(self) -> [u8; $size] { - self.to_ne_bytes() + self.to_le_bytes().into() } } )* } } -impl_mem_traits!(u8, 1, i8, 1, u16, 2, i16, 2, u32, 4, i32, 4, f32, 4, u64, 8, i64, 8, f64, 8, u128, 16, i128, 16); +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 = "__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 { @@ -190,7 +217,7 @@ mod memory_instance_tests { use tinywasm_types::MemoryArch; fn create_test_memory() -> MemoryInstance { - let kind = MemoryType { arch: MemoryArch::I32, page_count_initial: 1, page_count_max: Some(2) }; + let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); let owner = ModuleInstanceAddr::default(); MemoryInstance::new(kind, owner) } @@ -242,14 +269,14 @@ mod memory_instance_tests { fn test_memory_grow() { let mut memory = create_test_memory(); let original_pages = memory.page_count; - assert_eq!(memory.grow(1), Some(original_pages as i32)); + assert_eq!(memory.grow(1), Some(original_pages as i64)); assert_eq!(memory.page_count, original_pages + 1); } #[test] fn test_memory_grow_out_of_bounds() { let mut memory = create_test_memory(); - assert!(memory.grow(MAX_PAGES as i32 + 1).is_none()); + assert!(memory.grow(memory.kind.max_size() as i64 + 1).is_none()); } #[test] @@ -258,4 +285,29 @@ mod memory_instance_tests { assert_eq!(memory.grow(1), Some(1)); assert_eq!(memory.grow(1), None); } + + #[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 data_to_store = [1, 2]; + assert!(memory.store(0, data_to_store.len(), &data_to_store).is_err()); + } + + #[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); + + assert_eq!(memory.grow(1), Some(1)); + + let data_to_store = [1, 2]; + assert!(memory.store(0, data_to_store.len(), &data_to_store).is_ok()); + + let loaded_data = memory.load(0, data_to_store.len()).unwrap(); + assert_eq!(loaded_data, &data_to_store); + } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 124b7dc..d53654e 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -4,7 +4,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; use crate::interpreter::{self, InterpreterRuntime, TinyWasmValue}; -use crate::{cold, Error, Function, ModuleInstance, Result, Trap}; +use crate::{Error, Function, ModuleInstance, Result, Trap, cold}; mod data; mod element; @@ -243,9 +243,6 @@ impl Store { let mem_count = self.data.memories.len(); let mut mem_addrs = Vec::with_capacity(mem_count); for (i, mem) in memories.into_iter().enumerate() { - if let MemoryArch::I64 = mem.arch { - return Err(Error::UnsupportedFeature("64-bit memories".to_string())); - } self.data.memories.push(MemoryInstance::new(mem, idx)); mem_addrs.push((i + mem_count) as MemAddr); } @@ -278,12 +275,13 @@ impl Store { fn elem_addr(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result> { let res = match item { - ElementItem::Func(addr) | ElementItem::Expr(ConstInstruction::RefFunc(addr)) => { + ElementItem::Func(addr) | ElementItem::Expr(ConstInstruction::RefFunc(Some(addr))) => { Some(funcs.get(*addr as usize).copied().ok_or_else(|| { Error::Other(format!("function {addr} not found. This should have been caught by the validator")) })?) } - ElementItem::Expr(ConstInstruction::RefNull(_ty)) => None, + ElementItem::Expr(ConstInstruction::RefFunc(None)) => None, + ElementItem::Expr(ConstInstruction::RefExtern(None)) => None, ElementItem::Expr(ConstInstruction::GlobalGet(addr)) => { 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")) @@ -324,7 +322,7 @@ impl Store { // this one is active, so we need to initialize it (essentially a `table.init` instruction) ElementKind::Active { offset, table } => { - let offset = self.eval_i32_const(offset)?; + let offset = self.eval_size_const(offset)?; let table_addr = table_addrs .get(table as usize) .copied() @@ -372,7 +370,7 @@ impl Store { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; - let offset = self.eval_i32_const(offset)?; + let offset = self.eval_size_const(offset)?; let Some(mem) = self.data.memories.get_mut(*mem_addr as usize) else { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; @@ -405,7 +403,7 @@ impl Store { } pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { - if let MemoryArch::I64 = mem.arch { + if let MemoryArch::I64 = mem.arch() { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } self.data.memories.push(MemoryInstance::new(mem, idx)); @@ -417,15 +415,18 @@ impl Store { Ok(self.data.funcs.len() as FuncAddr - 1) } - /// Evaluate a constant expression, only supporting i32 globals and i32.const - pub(crate) fn eval_i32_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result { - use tinywasm_types::ConstInstruction::*; - let val = match const_instr { - I32Const(i) => i, - GlobalGet(addr) => self.data.globals[addr as usize].value.get().unwrap_32() as i32, - _ => return Err(Error::Other("expected i32".to_string())), - }; - Ok(val) + /// 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 { + Ok(match const_instr { + ConstInstruction::I32Const(i) => i as i64, + ConstInstruction::I64Const(i) => i, + ConstInstruction::GlobalGet(addr) => match self.data.globals[addr as usize].value.get() { + TinyWasmValue::Value32(i) => i as i64, + TinyWasmValue::Value64(i) => i as i64, + o => return Err(Error::Other(format!("expected i32 or i64, got {o:?}"))), + }, + o => return Err(Error::Other(format!("expected i32, got {o:?}"))), + }) } /// Evaluate a constant expression @@ -441,6 +442,7 @@ impl Store { F64Const(f) => (*f).into(), I32Const(i) => (*i).into(), I64Const(i) => (*i).into(), + V128Const(i) => (*i).into(), GlobalGet(addr) => { let addr = module_global_addrs.get(*addr as usize).ok_or_else(|| { Error::Other(format!("global {addr} not found. This should have been caught by the validator")) @@ -450,10 +452,14 @@ impl Store { self.data.globals.get(*addr as usize).expect("global not found. This should be unreachable"); global.value.get() } - RefNull(t) => t.default_value().into(), - RefFunc(idx) => TinyWasmValue::ValueRef(Some(*module_func_addrs.get(*idx as usize).ok_or_else(|| { - Error::Other(format!("function {idx} not found. This should have been caught by the validator")) - })?)), + RefFunc(None) => TinyWasmValue::ValueRef(None), + RefExtern(None) => TinyWasmValue::ValueRef(None), + RefFunc(Some(idx)) => { + TinyWasmValue::ValueRef(Some(*module_func_addrs.get(*idx as usize).ok_or_else(|| { + Error::Other(format!("function {idx} not found. This should have been caught by the validator")) + })?)) + } + _ => return Err(Error::Other("unsupported const instruction".to_string())), }; Ok(val) } diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index 0e08582..f83de9b 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -30,8 +30,8 @@ impl TableInstance { let val = self.get(addr)?.addr(); Ok(match self.kind.element_type { - ValType::RefFunc => val.map_or(WasmValue::RefNull(ValType::RefFunc), WasmValue::RefFunc), - ValType::RefExtern => val.map_or(WasmValue::RefNull(ValType::RefExtern), WasmValue::RefExtern), + ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(val)), + ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(val)), _ => Err(Error::UnsupportedFeature("non-ref table".into()))?, }) } @@ -48,10 +48,11 @@ impl TableInstance { } pub(crate) fn get(&self, addr: TableAddr) -> Result<&TableElement> { - // self.elements.get(addr as usize).ok_or_else(|| Error::Trap(Trap::UndefinedElement { index: addr as usize })) - self.elements.get(addr as usize).ok_or_else(|| { - Error::Trap(Trap::TableOutOfBounds { offset: addr as usize, len: 1, max: self.elements.len() }) - }) + self.elements.get(addr as usize).ok_or(Error::Trap(Trap::TableOutOfBounds { + offset: addr as usize, + len: 1, + max: self.elements.len(), + })) } pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[TableElement]) -> Result<()> { @@ -133,9 +134,9 @@ impl TableInstance { .expect("error initializing table: function not found. This should have been caught by the validator") } - pub(crate) fn init(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { + pub(crate) fn init(&mut self, offset: i64, init: &[TableElement]) -> Result<()> { let offset = offset as usize; - let end = offset.checked_add(init.len()).ok_or_else(|| { + let end = offset.checked_add(init.len()).ok_or({ Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) })?; @@ -172,7 +173,7 @@ impl TableElement { } } - pub(crate) fn map Addr>(self, f: F) -> Self { + pub(crate) fn map(self, f: impl FnOnce(Addr) -> Addr) -> Self { match self { TableElement::Uninitialized => TableElement::Uninitialized, TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), @@ -210,7 +211,7 @@ mod tests { } match table_instance.get_wasm_val(1) { - Ok(WasmValue::RefNull(ValType::RefFunc)) => {} + Ok(WasmValue::RefFunc(f)) if f.is_null() => {} _ => panic!("get_wasm_val failed to return the correct WasmValue"), } diff --git a/crates/tinywasm/tests/generated/wasm-1.csv b/crates/tinywasm/tests/generated/wasm-1.csv index 158943a..ebb511b 100644 --- a/crates/tinywasm/tests/generated/wasm-1.csv +++ b/crates/tinywasm/tests/generated/wasm-1.csv @@ -4,4 +4,5 @@ 0.5.0,27551,335,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":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_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.6.1,27572,335,[{"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":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":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_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.7.0,27572,335,[{"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":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":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_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.8.0-alpha.0,20358,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":126,"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":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_grow.wast","passed":104,"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,20360,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":"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_grow.wast","passed":104,"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.9.0-alpha.0,19241,0,[{"name":"address.wast","passed":243,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":67,"failed":0},{"name":"block.wast","passed":171,"failed":0},{"name":"br.wast","passed":84,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":168,"failed":0},{"name":"break-drop.wast","passed":4,"failed":0},{"name":"call.wast","passed":82,"failed":0},{"name":"call_indirect.wast","passed":152,"failed":0},{"name":"comments.wast","passed":4,"failed":0},{"name":"const.wast","passed":668,"failed":0},{"name":"conversions.wast","passed":435,"failed":0},{"name":"custom.wast","passed":10,"failed":0},{"name":"data.wast","passed":45,"failed":0},{"name":"elem.wast","passed":55,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":82,"failed":0},{"name":"f32.wast","passed":2512,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2512,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":7,"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":121,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"globals.wast","passed":78,"failed":0},{"name":"i32.wast","passed":443,"failed":0},{"name":"i64.wast","passed":389,"failed":0},{"name":"if.wast","passed":151,"failed":0},{"name":"imports.wast","passed":146,"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":116,"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":81,"failed":0},{"name":"memory.wast","passed":71,"failed":0},{"name":"memory_grow.wast","passed":94,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":173,"failed":0},{"name":"names.wast","passed":483,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":111,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":5,"failed":0},{"name":"start.wast","passed":19,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"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":62,"failed":0},{"name":"unreached-invalid.wast","passed":110,"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-2.csv b/crates/tinywasm/tests/generated/wasm-2.csv index b5ba154..8e228b4 100644 --- a/crates/tinywasm/tests/generated/wasm-2.csv +++ b/crates/tinywasm/tests/generated/wasm-2.csv @@ -9,4 +9,5 @@ 0.6.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.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-alpha.0,28006,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":126,"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.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}] diff --git a/crates/tinywasm/tests/generated/wasm-3.csv b/crates/tinywasm/tests/generated/wasm-3.csv new file mode 100644 index 0000000..5d4d068 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-3.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,33714,1189,[{"name":"address.wast","passed":260,"failed":0},{"name":"address64.wast","passed":242,"failed":0},{"name":"align.wast","passed":161,"failed":0},{"name":"align64.wast","passed":156,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":93,"failed":0},{"name":"binary.wast","passed":126,"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":9},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":1},{"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":"endianness64.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_memory64.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":169,"failed":90},{"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":118,"failed":0},{"name":"load64.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":120,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory64.wast","passed":68,"failed":1},{"name":"memory_copy.wast","passed":8628,"failed":272},{"name":"memory_fill.wast","passed":184,"failed":16},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_grow64.wast","passed":49,"failed":0},{"name":"memory_init.wast","passed":439,"failed":41},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_redundancy64.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"memory_trap64.wast","passed":172,"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":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"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":111,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":47,"failed":13},{"name":"table_copy.wast","passed":1750,"failed":22},{"name":"table_copy_mixed.wast","passed":4,"failed":0},{"name":"table_fill.wast","passed":45,"failed":35},{"name":"table_get.wast","passed":16,"failed":1},{"name":"table_grow.wast","passed":58,"failed":21},{"name":"table_init.wast","passed":783,"failed":93},{"name":"table_set.wast","passed":26,"failed":2},{"name":"table_size.wast","passed":39,"failed":1},{"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":"traps.wast","passed":36,"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.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-annotations.csv b/crates/tinywasm/tests/generated/wasm-annotations.csv new file mode 100644 index 0000000..1b99033 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-annotations.csv @@ -0,0 +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}] diff --git a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv new file mode 100644 index 0000000..5ba121e --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv @@ -0,0 +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}] diff --git a/crates/tinywasm/tests/generated/wasm-extended-const.csv b/crates/tinywasm/tests/generated/wasm-extended-const.csv index 8cd9132..e20303d 100644 --- a/crates/tinywasm/tests/generated/wasm-extended-const.csv +++ b/crates/tinywasm/tests/generated/wasm-extended-const.csv @@ -1 +1,2 @@ -0.8.0-alpha.0,211,79,[{"name":"data.wast","passed":61,"failed":4},{"name":"elem.wast","passed":99,"failed":12},{"name":"global.wast","passed":51,"failed":63}] +0.8.0,211,79,[{"name":"data.wast","passed":61,"failed":4},{"name":"elem.wast","passed":99,"failed":12},{"name":"global.wast","passed":51,"failed":63}] +0.9.0-alpha.0,211,79,[{"name":"data.wast","passed":61,"failed":4},{"name":"elem.wast","passed":99,"failed":12},{"name":"global.wast","passed":51,"failed":63}] diff --git a/crates/tinywasm/tests/generated/wasm-latest.csv b/crates/tinywasm/tests/generated/wasm-latest.csv new file mode 100644 index 0000000..27a4e86 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-latest.csv @@ -0,0 +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}] diff --git a/crates/tinywasm/tests/generated/wasm-memory64.csv b/crates/tinywasm/tests/generated/wasm-memory64.csv new file mode 100644 index 0000000..20afc3b --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-memory64.csv @@ -0,0 +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,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 f0f8400..33debd6 100644 --- a/crates/tinywasm/tests/generated/wasm-multi-memory.csv +++ b/crates/tinywasm/tests/generated/wasm-multi-memory.csv @@ -1 +1,2 @@ -0.8.0-alpha.0,1710,1,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":124,"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":183,"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":149,"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","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":"traps0.wast","passed":15,"failed":0}] +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}] diff --git a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv new file mode 100644 index 0000000..a6e6228 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv @@ -0,0 +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}] diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv new file mode 100644 index 0000000..86b7780 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -0,0 +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,24355,1625,[{"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":56,"failed":226},{"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":148,"failed":53},{"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":148,"failed":53},{"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":16},{"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":97,"failed":16},{"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_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}] diff --git a/crates/tinywasm/tests/generated/wasm-tail-call.csv b/crates/tinywasm/tests/generated/wasm-tail-call.csv new file mode 100644 index 0000000..c9f0910 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-tail-call.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,119,0,[{"name":"return_call.wast","passed":44,"failed":0},{"name":"return_call_indirect.wast","passed":75,"failed":0}] diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs new file mode 100644 index 0000000..698ad61 --- /dev/null +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -0,0 +1,172 @@ +use eyre::Result; +use std::fmt::Write; +use tinywasm::{ + Extern, FuncContext, Imports, Module, Store, + types::{FuncType, ValType, WasmValue}, +}; +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 +]; +// (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 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())); + } + } + result +} + +#[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(); + + for (module, func_ty, test_args) in mod_list { + for result_to_try 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()))) + .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); + 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 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 link_res = module.clone().instantiate(&mut store, Some(imports)); + + assert_eq!(link_res.is_ok(), should_succeed); + } + } + Ok(()) +} + +#[test] +fn test_linking_invalid_typed_func() -> Result<()> { + type Existing = (i32, i32, f64); + type NonMatchingOne = f64; + type NonMatchingMul = (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}") } ), + ]; + + let mod_list = get_modules(); + for (module, _, _) in mod_list { + for typed_fn in matching_none.clone() { + 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"); + } + } + + // the valid cases are well-checked in other tests + Ok(()) +} + +fn to_name(ty: &ValType) -> &str { + match ty { + ValType::I32 => "i32", + ValType::I64 => "i64", + ValType::F32 => "f32", + ValType::F64 => "f64", + ValType::V128 => "v128", + ValType::RefFunc => "funcref", + ValType::RefExtern => "externref", + } +} + +// 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(); + let join_surround = |list: &[ValType], keyword| { + if list.is_empty() { + return "".to_string(); + } + let step = list.iter().map(|ty| format!("{} ", to_name(ty)).to_string()).collect::(); + format!("({keyword} {step})") + }; + + let results_text = join_surround(results, "result"); + let params_text = join_surround(params, "param"); + + let params_gets: String = params.iter().enumerate().fold(String::new(), |mut acc, (num, _)| { + let _ = writeln!(acc, "(local.get {num})", num = num); + acc + }); + + let result_drops = "(drop)\n".repeat(results.len()).to_string(); + let wasm_text = format!( + r#"(module + (import "host" "hfn" (func $host_fn {params_text} {results_text})) + (func (export "call_hfn") {params_text} {results_text} + {params_gets} + (call $host_fn) + ) + (func (export "call_hfn_discard") {params_text} + {params_gets} + (call $host_fn) + {result_drops} + ) + ) + "# + ); + let wasm = wat::parse_str(wasm_text).expect("failed to parse wat"); + Module::parse_bytes(&wasm).expect("failed to make module") +} diff --git a/crates/tinywasm/tests/test-wasm-1.rs b/crates/tinywasm/tests/test-wasm-1.rs index 77694c5..be235ae 100644 --- a/crates/tinywasm/tests/test-wasm-1.rs +++ b/crates/tinywasm/tests/test-wasm-1.rs @@ -1,20 +1,13 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +use eyre::Result; use testsuite::TestSuite; +use wasm_testsuite::data::{SpecVersion, spec}; fn main() -> Result<()> { - let mut test_suite = TestSuite::new(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::MVP_TESTS)?; - test_suite.save_csv("./tests/generated/wasm-1.csv", env!("CARGO_PKG_VERSION"))?; - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + let mut test_suite = TestSuite::new(); + test_suite.run_files(spec(&SpecVersion::V1))?; + test_suite.save_csv("./tests/generated/wasm-1.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() } diff --git a/crates/tinywasm/tests/test-wasm-2.rs b/crates/tinywasm/tests/test-wasm-2.rs index bd1afe6..0379ca6 100644 --- a/crates/tinywasm/tests/test-wasm-2.rs +++ b/crates/tinywasm/tests/test-wasm-2.rs @@ -1,20 +1,13 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +use eyre::Result; use testsuite::TestSuite; +use wasm_testsuite::data::{SpecVersion, spec}; fn main() -> Result<()> { - let mut test_suite = TestSuite::new(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::V2_DRAFT_1_TESTS)?; - test_suite.save_csv("./tests/generated/wasm-2.csv", env!("CARGO_PKG_VERSION"))?; - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + let mut test_suite = TestSuite::new(); + test_suite.run_files(spec(&SpecVersion::V2))?; + test_suite.save_csv("./tests/generated/wasm-2.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() } diff --git a/crates/tinywasm/tests/test-wasm-3.rs b/crates/tinywasm/tests/test-wasm-3.rs new file mode 100644 index 0000000..3e63dc5 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-3.rs @@ -0,0 +1,18 @@ +mod testsuite; +use eyre::Result; +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(); + test_suite.run_files(spec(&SpecVersion::V3))?; + test_suite.save_csv("./tests/generated/wasm-3.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-annotations.rs b/crates/tinywasm/tests/test-wasm-annotations.rs index fa40467..beac9eb 100644 --- a/crates/tinywasm/tests/test-wasm-annotations.rs +++ b/crates/tinywasm/tests/test-wasm-annotations.rs @@ -1,20 +1,14 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +use eyre::Result; use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; fn main() -> Result<()> { - let mut test_suite = TestSuite::new(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::get_proposal_tests("annotations"))?; - test_suite.save_csv("./tests/generated/wasm-annotations.csv", env!("CARGO_PKG_VERSION"))?; - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + 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-page-sizes.rs b/crates/tinywasm/tests/test-wasm-custom-page-sizes.rs new file mode 100644 index 0000000..e4aacfc --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-custom-page-sizes.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::CustomPageSizes))?; + test_suite.save_csv("./tests/generated/wasm-custom-page-sizes.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-extended-const.rs b/crates/tinywasm/tests/test-wasm-extended-const.rs index f544b35..888a49f 100644 --- a/crates/tinywasm/tests/test-wasm-extended-const.rs +++ b/crates/tinywasm/tests/test-wasm-extended-const.rs @@ -1,20 +1,13 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +use eyre::Result; use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; fn main() -> Result<()> { - let mut test_suite = TestSuite::new(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::get_proposal_tests("extended-const"))?; - test_suite.save_csv("./tests/generated/wasm-extended-const.csv", env!("CARGO_PKG_VERSION"))?; - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + let mut test_suite = TestSuite::new(); + test_suite.run_files(proposal(&Proposal::ExtendedConst))?; + test_suite.save_csv("./tests/generated/wasm-extended-const.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() } diff --git a/crates/tinywasm/tests/test-wasm-memory64.rs b/crates/tinywasm/tests/test-wasm-memory64.rs index ab23762..447bbfc 100644 --- a/crates/tinywasm/tests/test-wasm-memory64.rs +++ b/crates/tinywasm/tests/test-wasm-memory64.rs @@ -1,20 +1,13 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +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(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::get_proposal_tests("memory64"))?; + test_suite.run_files(proposal(&Proposal::Memory64))?; test_suite.save_csv("./tests/generated/wasm-memory64.csv", env!("CARGO_PKG_VERSION"))?; - - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + 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 2cee13d..30052b0 100644 --- a/crates/tinywasm/tests/test-wasm-multi-memory.rs +++ b/crates/tinywasm/tests/test-wasm-multi-memory.rs @@ -1,20 +1,14 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +use eyre::Result; use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; fn main() -> Result<()> { - let mut test_suite = TestSuite::new(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::get_proposal_tests("multi-memory"))?; - test_suite.save_csv("./tests/generated/wasm-multi-memory.csv", env!("CARGO_PKG_VERSION"))?; - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + 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-relaxed-simd.rs b/crates/tinywasm/tests/test-wasm-relaxed-simd.rs new file mode 100644 index 0000000..0851d18 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-relaxed-simd.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::RelaxedSimd))?; + test_suite.save_csv("./tests/generated/wasm-relaxed-simd.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-simd.rs b/crates/tinywasm/tests/test-wasm-simd.rs index 289bf2b..d8f15ea 100644 --- a/crates/tinywasm/tests/test-wasm-simd.rs +++ b/crates/tinywasm/tests/test-wasm-simd.rs @@ -1,20 +1,13 @@ mod testsuite; -use eyre::{eyre, Result}; -use owo_colors::OwoColorize; +use eyre::Result; use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; fn main() -> Result<()> { - let mut test_suite = TestSuite::new(); - TestSuite::set_log_level(log::LevelFilter::Off); - test_suite.run_spec_group(wasm_testsuite::SIMD_TESTS)?; - test_suite.save_csv("./tests/generated/wasm-simd.csv", env!("CARGO_PKG_VERSION"))?; - if test_suite.failed() { - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + let mut test_suite = TestSuite::new(); + test_suite.run_files(proposal(&Proposal::Simd))?; + test_suite.save_csv("./tests/generated/wasm-simd.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 new file mode 100644 index 0000000..f888f38 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-tail-call.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::TailCall))?; + 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-wast.rs b/crates/tinywasm/tests/test-wast.rs index f79681c..40b0e04 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use eyre::{bail, eyre, Result}; +use eyre::{Result, bail, eyre}; use owo_colors::OwoColorize; use testsuite::TestSuite; @@ -12,37 +12,25 @@ fn main() -> Result<()> { bail!("usage: cargo test-wast ") }; - // cwd for relative paths, absolute paths are kept as-is - let cwd = std::env::current_dir()?; - - // if current dir is crates/tinywasm, then we want to go up 2 levels - let mut wast_file = if cwd.ends_with("crates/tinywasm") { PathBuf::from("../../") } else { PathBuf::from("./") }; + let mut cwd = std::env::current_dir()?; + if cwd.ends_with("crates/tinywasm/") { + cwd.pop(); + cwd.pop(); + } - wast_file.push(&args[1]); - let wast_file = cwd.join(wast_file); + // if its a folder, run all the wast files in the folder + let arg = PathBuf::from(cwd.clone()).join(&args[1]); + println!("running tests in {:?}", arg); - test_wast(wast_file.to_str().expect("wast_file is not a valid path"))?; - Ok(()) -} + let files = if arg.is_dir() { + std::fs::read_dir(&arg)?.map(|entry| entry.map(|e| e.path())).collect::, _>>()? + } else { + vec![arg] + }; -fn test_wast(wast_file: &str) -> Result<()> { TestSuite::set_log_level(log::LevelFilter::Debug); - - let args = std::env::args().collect::>(); - println!("args: {args:?}"); - let mut test_suite = TestSuite::new(); - println!("running wast file: {wast_file}"); - - test_suite.run_paths(&[wast_file])?; - - if test_suite.failed() { - println!(); - test_suite.print_errors(); - println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) - } else { - println!("\n\npassed all tests:\n{test_suite:#?}"); - Ok(()) - } + test_suite.run_paths(&files)?; + test_suite.print_errors(); + test_suite.report_status() } diff --git a/crates/tinywasm/tests/testsuite/indexmap.rs b/crates/tinywasm/tests/testsuite/indexmap.rs deleted file mode 100644 index 0c75e4c..0000000 --- a/crates/tinywasm/tests/testsuite/indexmap.rs +++ /dev/null @@ -1,31 +0,0 @@ -/// A naive implementation of an index map for use in the test suite -pub struct IndexMap { - map: std::collections::HashMap, - keys: Vec, -} - -impl IndexMap -where - K: std::cmp::Eq + std::hash::Hash + Clone, -{ - pub fn new() -> Self { - Self { map: std::collections::HashMap::new(), keys: Vec::new() } - } - - pub fn insert(&mut self, key: K, value: V) -> Option { - if let std::collections::hash_map::Entry::Occupied(mut e) = self.map.entry(key.clone()) { - return Some(e.insert(value)); - } - - self.keys.push(key.clone()); - self.map.insert(key, value) - } - - pub fn iter(&self) -> impl Iterator { - self.keys.iter().map(move |k| (k, self.map.get(k).unwrap())) - } - - pub fn values(&self) -> impl Iterator { - self.map.values() - } -} diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index ca62d4e..c1ba46e 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -1,5 +1,6 @@ #![allow(unused)] -use eyre::Result; +use eyre::{Result, eyre}; +use indexmap::IndexMap; use owo_colors::OwoColorize; use std::io::{BufRead, Seek, SeekFrom}; use std::{ @@ -8,14 +9,11 @@ use std::{ io::BufReader, }; -mod indexmap; mod run; mod util; use serde::{Deserialize, Serialize}; -use self::indexmap::IndexMap; - #[derive(Serialize, Deserialize)] pub struct TestGroupResult { pub name: String, @@ -30,6 +28,16 @@ impl TestSuite { pretty_env_logger::formatted_builder().filter_level(level).init(); } + pub fn report_status(&self) -> Result<()> { + if self.failed() { + println!(); + Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), self))) + } else { + println!("\n\npassed all tests:\n{self:#?}"); + Ok(()) + } + } + pub fn print_errors(&self) { for (group_name, group) in &self.0 { let tests = &group.tests; @@ -50,6 +58,10 @@ impl TestSuite { Self(BTreeMap::new(), Vec::new()) } + pub fn skip(&mut self, name: &str) { + self.1.push(name.to_string()); + } + pub fn failed(&self) -> bool { self.0.values().any(|group| group.stats().1 > 0) } diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index ca788fb..d09efdb 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -1,22 +1,25 @@ use crate::testsuite::util::*; -use std::{borrow::Cow, collections::HashMap}; +use std::{borrow::Cow, collections::HashMap, fs::canonicalize, path::PathBuf}; use super::TestSuite; -use eyre::{eyre, Result}; +use eyre::{Result, eyre}; +use indexmap::IndexMap; use log::{debug, error, info}; use tinywasm::{Extern, Imports, ModuleInstance}; use tinywasm_types::{ExternVal, MemoryType, ModuleInstanceAddr, TableType, ValType, WasmValue}; -use wast::{lexer::Lexer, parser::ParseBuffer, Wast}; +use wasm_testsuite::data::TestFile; +use wasm_testsuite::wast; +use wasm_testsuite::wast::{Wast, lexer::Lexer, parser::ParseBuffer}; #[derive(Default)] -struct RegisteredModules { +struct ModuleRegistry { modules: HashMap, named_modules: HashMap, last_module: Option, } -impl RegisteredModules { +impl ModuleRegistry { fn modules(&self) -> &HashMap { &self.modules } @@ -69,11 +72,16 @@ impl RegisteredModules { } impl TestSuite { - pub fn run_paths(&mut self, tests: &[&str]) -> Result<()> { - for group in tests { - let group_wast = std::fs::read(group).expect("failed to read test wast"); - let group_wast = Cow::Owned(group_wast); - self.run_group(group, group_wast).expect("failed to run group"); + pub fn run_paths(&mut self, tests: &[PathBuf]) -> Result<()> { + for file_name in tests { + let group_wast = std::fs::read(file_name).expect("failed to read test wast"); + let file = TestFile { + contents: std::str::from_utf8(&group_wast).expect("failed to convert to utf8"), + name: canonicalize(file_name).expect("failed to canonicalize file name").to_string_lossy().to_string(), + parent: "(custom group)".into(), + }; + + self.run_file(file).expect("failed to run group"); } Ok(()) @@ -121,7 +129,11 @@ impl TestSuite { }); imports - .define("spectest", "memory", Extern::memory(MemoryType::new_32(1, Some(2))))? + .define( + "spectest", + "memory", + Extern::memory(MemoryType::new(tinywasm_types::MemoryArch::I32, 1, Some(2), None)), + )? .define("spectest", "table", table)? .define("spectest", "global_i32", Extern::global(WasmValue::I32(666), false))? .define("spectest", "global_i64", Extern::global(WasmValue::I64(666), false))? @@ -143,57 +155,51 @@ impl TestSuite { Ok(imports) } - pub fn run_spec_group>(&mut self, tests: impl IntoIterator) -> Result<()> { + pub fn run_files<'a>(&mut self, tests: impl IntoIterator>) -> Result<()> { tests.into_iter().for_each(|group| { - let group = group.as_ref(); - let group_wast = wasm_testsuite::get_test_wast(group).expect("failed to get test wast"); - if self.1.contains(&(*group).to_string()) { - info!("skipping group: {}", group); - self.test_group(&format!("{group} (skipped)"), group); + let name = group.name(); + println!("running group: {}", name); + if self.1.contains(&name.to_string()) { + info!("skipping group: {name}"); + self.test_group(&format!("{name} (skipped)"), name); return; } - self.run_group(group, group_wast).expect("failed to run group"); + self.run_file(group).expect("failed to run group"); }); Ok(()) } - pub fn run_group(&mut self, group_name: &str, group_wast: Cow<'_, [u8]>) -> Result<()> { - let file_name = group_name.split('/').last().unwrap_or(group_name); - let test_group = self.test_group(file_name, group_name); - let wast = std::str::from_utf8(&group_wast).expect("failed to convert wast to utf8"); - - let mut lexer = Lexer::new(wast); - // we need to allow confusing unicode characters since they are technically valid wasm - lexer.allow_confusing_unicode(true); - - let buf = ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); - let wast_data = wast::parser::parse::(&buf).expect("failed to parse wat"); + pub fn run_file(&mut self, file: TestFile<'_>) -> Result<()> { + let test_group = self.test_group(file.name(), file.parent()); + let wast_raw = file.raw(); + let wast = file.wast()?; + let directives = wast.directives()?; let mut store = tinywasm::Store::default(); - let mut registered_modules = RegisteredModules::default(); + let mut module_registry = ModuleRegistry::default(); - println!("running {} tests for group: {}", wast_data.directives.len(), group_name); - for (i, directive) in wast_data.directives.into_iter().enumerate() { + println!("running {} tests for group: {}", directives.len(), file.name()); + for (i, directive) in directives.into_iter().enumerate() { let span = directive.span(); use wast::WastDirective::{ AssertExhaustion, AssertInvalid, AssertMalformed, AssertReturn, AssertTrap, AssertUnlinkable, Invoke, - Register, Wat, + Module as Wat, Register, }; match directive { Register { span, name, .. } => { - let Some(last) = registered_modules.last(&store) else { + let Some(last) = module_registry.last(&store) else { test_group.add_result( &format!("Register({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("no module to register")), ); continue; }; - registered_modules.register(name.to_string(), last.id()); - test_group.add_result(&format!("Register({i})"), span.linecol_in(wast), Ok(())); + module_registry.register(name.to_string(), last.id()); + test_group.add_result(&format!("Register({i})"), span.linecol_in(wast_raw), Ok(())); } Wat(module) => { @@ -203,7 +209,7 @@ impl TestSuite { let m = parse_module_bytes(&bytes).expect("failed to parse module bytes"); let module_instance = tinywasm::Module::from(m) - .instantiate(&mut store, Some(Self::imports(registered_modules.modules()).unwrap())) + .instantiate(&mut store, Some(Self::imports(module_registry.modules()).unwrap())) .expect("failed to instantiate module"); (name, module_instance) @@ -212,15 +218,15 @@ impl TestSuite { match &result { Err(err) => debug!("failed to parse module: {:?}", err), - Ok((name, module)) => registered_modules.update_last_module(module.id(), name.clone()), + Ok((name, module)) => module_registry.update_last_module(module.id(), name.clone()), }; - test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast), result.map(|_| ())); + test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast_raw), result.map(|_| ())); } AssertMalformed { span, mut module, message } => { let Ok(module) = module.encode() else { - test_group.add_result(&format!("AssertMalformed({i})"), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertMalformed({i})"), span.linecol_in(wast_raw), Ok(())); continue; }; @@ -230,16 +236,17 @@ impl TestSuite { test_group.add_result( &format!("AssertMalformed({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), match res { Ok(_) => { - // // skip "zero byte expected" as the magic number is not checked by wasmparser - // (Don't need to error on this, doesn't matter if it's malformed) - if message == "zero byte expected" { + // - skip "zero byte expected" as the magic number is not checked by wasmparser + // (Don't need to error on this, doesn't matter if it's malformed) + // - skip "integer representation too long" as this has some false positives on older tests + if message == "zero byte expected" || message == "integer representation too long" { continue; } - Err(eyre!("expected module to be malformed")) + Err(eyre!("expected module to be malformed: {message}")) } Err(_) => Ok(()), }, @@ -248,7 +255,7 @@ impl TestSuite { AssertInvalid { span, mut module, message } => { if ["multiple memories"].contains(&message) { - test_group.add_result(&format!("AssertInvalid({i})"), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertInvalid({i})"), span.linecol_in(wast_raw), Ok(())); continue; } @@ -258,7 +265,7 @@ impl TestSuite { test_group.add_result( &format!("AssertInvalid({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), match res { Ok(_) => Err(eyre!("expected module to be invalid")), Err(_) => Ok(()), @@ -267,7 +274,7 @@ impl TestSuite { } AssertExhaustion { call, message, span } => { - let module = registered_modules.get_idx(call.module); + let module = module_registry.get_idx(call.module); let args = convert_wastargs(call.args).expect("failed to convert args"); let res = catch_unwind_silent(|| exec_fn_instance(module, &mut store, call.name, &args).map(|_| ())); @@ -275,7 +282,7 @@ impl TestSuite { let Ok(Err(tinywasm::Error::Trap(trap))) = res else { test_group.add_result( &format!("AssertExhaustion({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected trap")), ); continue; @@ -284,13 +291,13 @@ impl TestSuite { if !message.starts_with(trap.message()) { test_group.add_result( &format!("AssertExhaustion({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected trap: {}, got: {}", message, trap.message())), ); continue; } - test_group.add_result(&format!("AssertExhaustion({i})"), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertExhaustion({i})"), span.linecol_in(wast_raw), Ok(())); } AssertTrap { exec, message, span } => { @@ -300,10 +307,8 @@ impl TestSuite { let module = parse_module_bytes(&wat.encode().expect("failed to encode module")) .expect("failed to parse module"); let module = tinywasm::Module::from(module); - module.instantiate( - &mut store, - Some(Self::imports(registered_modules.modules()).unwrap()), - )?; + module + .instantiate(&mut store, Some(Self::imports(module_registry.modules()).unwrap()))?; return Ok(()); } wast::WastExecute::Get { module: _, global: _, .. } => { @@ -312,7 +317,7 @@ impl TestSuite { wast::WastExecute::Invoke(invoke) => invoke, }; - let module = registered_modules.get_idx(invoke.module); + let module = module_registry.get_idx(invoke.module); let args = convert_wastargs(invoke.args).expect("failed to convert args"); exec_fn_instance(module, &mut store, invoke.name, &args).map(|_| ()) }); @@ -320,29 +325,29 @@ impl TestSuite { match res { Err(err) => test_group.add_result( &format!("AssertTrap({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("test panicked: {:?}", try_downcast_panic(err))), ), Ok(Err(tinywasm::Error::Trap(trap))) => { if !message.starts_with(trap.message()) { test_group.add_result( &format!("AssertTrap({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected trap: {}, got: {}", message, trap.message())), ); continue; } - test_group.add_result(&format!("AssertTrap({i})"), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertTrap({i})"), span.linecol_in(wast_raw), Ok(())); } Ok(Err(err)) => test_group.add_result( &format!("AssertTrap({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected trap, {}, got: {:?}", message, err)), ), Ok(Ok(())) => test_group.add_result( &format!("AssertTrap({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected trap {}, got Ok", message)), ), } @@ -353,35 +358,38 @@ impl TestSuite { let module = parse_module_bytes(&module.encode().expect("failed to encode module")) .expect("failed to parse module"); let module = tinywasm::Module::from(module); - module.instantiate(&mut store, Some(Self::imports(registered_modules.modules()).unwrap())) + module.instantiate(&mut store, Some(Self::imports(module_registry.modules()).unwrap())) }); match res { Err(err) => test_group.add_result( &format!("AssertUnlinkable({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("test panicked: {:?}", try_downcast_panic(err))), ), Ok(Err(tinywasm::Error::Linker(err))) => { - if err.message() != message { + if err.message() != message + && (err.message() == "memory types incompatible" + && message != "incompatible import type") + { test_group.add_result( &format!("AssertUnlinkable({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected linker error: {}, got: {}", message, err.message())), ); continue; } - test_group.add_result(&format!("AssertUnlinkable({i})"), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertUnlinkable({i})"), span.linecol_in(wast_raw), Ok(())); } Ok(Err(err)) => test_group.add_result( &format!("AssertUnlinkable({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected linker error, {}, got: {:?}", message, err)), ), Ok(Ok(_)) => test_group.add_result( &format!("AssertUnlinkable({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("expected linker error {}, got Ok", message)), ), } @@ -392,7 +400,7 @@ impl TestSuite { let res: Result, _> = catch_unwind_silent(|| { let args = convert_wastargs(invoke.args)?; - let module = registered_modules.get_idx(invoke.module); + let module = module_registry.get_idx(invoke.module); exec_fn_instance(module, &mut store, invoke.name, &args).map_err(|e| { error!("failed to execute function: {:?}", e); e @@ -401,21 +409,31 @@ impl TestSuite { }); let res = res.map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))).and_then(|r| r); - test_group.add_result(&format!("Invoke({name}-{i})"), span.linecol_in(wast), res); + test_group.add_result(&format!("Invoke({name}-{i})"), span.linecol_in(wast_raw), res); } AssertReturn { span, exec, results } => { info!("AssertReturn: {:?}", exec); - let expected = convert_wastret(results.into_iter())?; + let expected = match convert_wastret(results.into_iter()) { + Err(err) => { + test_group.add_result( + &format!("AssertReturn(unsupported-{i})"), + span.linecol_in(wast_raw), + Err(eyre!("failed to convert expected results: {:?}", err)), + ); + continue; + } + Ok(expected) => expected, + }; let invoke = match match exec { wast::WastExecute::Wat(_) => Err(eyre!("wat not supported")), wast::WastExecute::Get { module: module_id, global, .. } => { - let module = registered_modules.get(module_id, &store); + let module = module_registry.get(module_id, &store); let Some(module) = module else { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("no module to get global from")), ); continue; @@ -429,7 +447,7 @@ impl TestSuite { Err(err) => { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("failed to get global: {:?}", err)), ); continue; @@ -441,7 +459,7 @@ impl TestSuite { if !module_global.eq_loose(expected) { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("global value did not match: {:?} != {:?}", module_global, expected)), ); continue; @@ -449,7 +467,7 @@ impl TestSuite { test_group.add_result( &format!("AssertReturn({global}-{i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Ok(()), ); @@ -462,7 +480,7 @@ impl TestSuite { Err(err) => { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("unsupported directive: {:?}", err)), ); continue; @@ -473,7 +491,7 @@ impl TestSuite { let res: Result, _> = catch_unwind_silent(|| { debug!("invoke: {:?}", invoke); let args = convert_wastargs(invoke.args)?; - let module = registered_modules.get_idx(invoke.module); + let module = module_registry.get_idx(invoke.module); let outcomes = exec_fn_instance(module, &mut store, invoke.name, &args).map_err(|e| { error!("failed to execute function: {:?}", e); e @@ -496,11 +514,11 @@ impl TestSuite { }); let res = res.map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))).and_then(|r| r); - test_group.add_result(&format!("AssertReturn({invoke_name}-{i})"), span.linecol_in(wast), res); + test_group.add_result(&format!("AssertReturn({invoke_name}-{i})"), span.linecol_in(wast_raw), res); } _ => test_group.add_result( &format!("Unknown({i})"), - span.linecol_in(wast), + span.linecol_in(wast_raw), Err(eyre!("unsupported directive")), ), } diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index e238f72..5e8558a 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -1,14 +1,14 @@ use std::panic::{self, AssertUnwindSafe}; -use eyre::{eyre, Result}; -use tinywasm_types::{ModuleInstanceAddr, TinyWasmModule, ValType, WasmValue}; -use wast::{core::AbstractHeapType, QuoteWat}; +use eyre::{Result, bail, eyre}; +use tinywasm_types::{ExternRef, FuncRef, ModuleInstanceAddr, TinyWasmModule, ValType, WasmValue}; +use wasm_testsuite::wast; +use wasm_testsuite::wast::{QuoteWat, core::AbstractHeapType}; pub fn try_downcast_panic(panic: Box) -> String { let info = panic.downcast_ref::().or(None).map(ToString::to_string).clone(); let info_string = panic.downcast_ref::().cloned(); let info_str = panic.downcast::<&str>().ok().map(|s| *s); - info.unwrap_or(info_str.unwrap_or(&info_string.unwrap_or("unknown panic".to_owned())).to_string()) } @@ -46,7 +46,7 @@ pub fn exec_fn( instance.exported_func_untyped(&store, name)?.call(&mut store, args) } -pub fn catch_unwind_silent R, R>(f: F) -> std::thread::Result { +pub fn catch_unwind_silent(f: impl FnOnce() -> R) -> std::thread::Result { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); let result = panic::catch_unwind(AssertUnwindSafe(f)); @@ -93,58 +93,74 @@ pub fn convert_wastret<'a>(args: impl Iterator>) -> Res fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { let wast::WastArg::Core(arg) = arg else { - return Err(eyre!("unsupported arg type: Component")); + bail!("unsupported arg type: Component"); }; - use wast::core::WastArgCore::{RefExtern, RefNull, F32, F64, I32, I64}; + use wast::core::WastArgCore::{F32, F64, I32, I64, RefExtern, RefNull, V128}; Ok(match arg { F32(f) => WasmValue::F32(f32::from_bits(f.bits)), F64(f) => WasmValue::F64(f64::from_bits(f.bits)), I32(i) => WasmValue::I32(i), I64(i) => WasmValue::I64(i), - RefExtern(v) => WasmValue::RefExtern(v), + V128(i) => WasmValue::V128(i128::from_le_bytes(i.to_le_bytes())), + RefExtern(v) => WasmValue::RefExtern(ExternRef::new(Some(v))), RefNull(t) => match t { wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func } => { - WasmValue::RefNull(ValType::RefFunc) + WasmValue::RefFunc(FuncRef::null()) } wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern } => { - WasmValue::RefNull(ValType::RefExtern) + WasmValue::RefExtern(ExternRef::null()) } - _ => return Err(eyre!("unsupported arg type: refnull: {:?}", t)), + _ => bail!("unsupported arg type: refnull: {:?}", t), }, - v => return Err(eyre!("unsupported arg type: {:?}", v)), + v => bail!("unsupported arg type: {:?}", v), }) } +fn wast_i128_to_i128(i: wast::core::V128Pattern) -> i128 { + let res: Vec = match i { + wast::core::V128Pattern::F32x4(f) => { + f.iter().flat_map(|v| nanpattern2tinywasmvalue(*v).unwrap().as_f32().unwrap().to_le_bytes()).collect() + } + wast::core::V128Pattern::F64x2(f) => { + f.iter().flat_map(|v| nanpattern2tinywasmvalue(*v).unwrap().as_f64().unwrap().to_le_bytes()).collect() + } + wast::core::V128Pattern::I16x8(f) => f.iter().flat_map(|v| v.to_le_bytes()).collect(), + wast::core::V128Pattern::I32x4(f) => f.iter().flat_map(|v| v.to_le_bytes()).collect(), + wast::core::V128Pattern::I64x2(f) => f.iter().flat_map(|v| v.to_le_bytes()).collect(), + wast::core::V128Pattern::I8x16(f) => f.iter().flat_map(|v| v.to_le_bytes()).collect(), + }; + + i128::from_le_bytes(res.try_into().unwrap()) +} + fn wastret2tinywasmvalue(ret: wast::WastRet) -> Result { let wast::WastRet::Core(ret) = ret else { - return Err(eyre!("unsupported arg type")); + bail!("unsupported arg type"); }; - use wast::core::WastRetCore::{RefExtern, RefFunc, RefNull, F32, F64, I32, I64}; + use wast::core::WastRetCore::{F32, F64, I32, I64, RefExtern, RefFunc, RefNull, V128}; Ok(match ret { F32(f) => nanpattern2tinywasmvalue(f)?, F64(f) => nanpattern2tinywasmvalue(f)?, I32(i) => WasmValue::I32(i), I64(i) => WasmValue::I64(i), + V128(i) => WasmValue::V128(wast_i128_to_i128(i)), RefNull(t) => match t { Some(wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func }) => { - WasmValue::RefNull(ValType::RefFunc) + WasmValue::RefFunc(FuncRef::null()) } Some(wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern }) => { - WasmValue::RefNull(ValType::RefExtern) + WasmValue::RefExtern(ExternRef::null()) } - _ => return Err(eyre!("unsupported arg type: refnull: {:?}", t)), - }, - RefExtern(v) => match v { - Some(v) => WasmValue::RefExtern(v), - _ => return Err(eyre!("unsupported arg type: refextern: {:?}", v)), - }, - RefFunc(v) => match v { - Some(wast::token::Index::Num(n, _)) => WasmValue::RefFunc(n), - _ => return Err(eyre!("unsupported arg type: reffunc: {:?}", v)), + _ => bail!("unsupported arg type: refnull: {:?}", t), }, - a => return Err(eyre!("unsupported arg type {:?}", a)), + RefExtern(v) => WasmValue::RefExtern(ExternRef::new(v)), + RefFunc(v) => WasmValue::RefFunc(FuncRef::new(match v { + Some(wast::token::Index::Num(n, _)) => Some(n), + _ => bail!("unsupported arg type: reffunc: {:?}", v), + })), + a => bail!("unsupported arg type {:?}", a), }) } @@ -152,6 +168,7 @@ enum Bits { U32(u32), U64(u64), } + trait FloatToken { fn bits(&self) -> Bits; fn canonical_nan() -> WasmValue; @@ -163,6 +180,7 @@ trait FloatToken { } } } + impl FloatToken for wast::token::F32 { fn bits(&self) -> Bits { Bits::U32(self.bits) @@ -176,6 +194,7 @@ impl FloatToken for wast::token::F32 { WasmValue::F32(f32::NAN) } } + impl FloatToken for wast::token::F64 { fn bits(&self) -> Bits { Bits::U64(self.bits) diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index b643964..fda8a86 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -10,11 +10,12 @@ rust-version.workspace=true [dependencies] log={workspace=true, optional=true} -rkyv={version="0.7", optional=true, default-features=false, features=["size_32", "validation"]} -bytecheck={version="0.7", optional=true} +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"] -std=["rkyv?/std"] -archive=["dep:rkyv", "dep:bytecheck"] + +std=["serde?/std"] +archive=["dep:postcard", "dep:serde"] logging=["dep:log"] diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 398b616..cbc22ad 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -1,19 +1,14 @@ use core::fmt::{Display, Formatter}; +use alloc::vec::Vec; + use crate::TinyWasmModule; -use rkyv::{ - check_archived_root, - ser::{serializers::AllocSerializer, Serializer}, - Deserialize, -}; const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; -const TWASM_VERSION: &[u8; 2] = b"01"; +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]; -pub use rkyv::AlignedVec; - fn validate_magic(wasm: &[u8]) -> Result { if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { return Err(TwasmError::InvalidMagic); @@ -33,7 +28,7 @@ pub enum TwasmError { InvalidMagic, InvalidVersion, InvalidPadding, - InvalidArchive, + InvalidArchive(postcard::Error), } impl Display for TwasmError { @@ -42,7 +37,7 @@ impl Display for TwasmError { TwasmError::InvalidMagic => write!(f, "Invalid twasm: invalid magic number"), TwasmError::InvalidVersion => write!(f, "Invalid twasm: invalid version"), TwasmError::InvalidPadding => write!(f, "Invalid twasm: invalid padding"), - TwasmError::InvalidArchive => write!(f, "Invalid twasm: invalid archive"), + TwasmError::InvalidArchive(e) => write!(f, "Invalid twasm: {}", e), } } } @@ -50,27 +45,20 @@ impl Display for TwasmError { #[cfg(feature = "std")] extern crate std; -#[cfg(feature = "std")] -impl std::error::Error for TwasmError {} +impl core::error::Error for TwasmError {} impl TinyWasmModule { /// Creates a `TinyWasmModule` from a slice of bytes. pub fn from_twasm(wasm: &[u8]) -> Result { let len = validate_magic(wasm)?; - let root = check_archived_root::(&wasm[len..]).map_err(|_e| TwasmError::InvalidArchive)?; - Ok(root.deserialize(&mut rkyv::Infallible).unwrap()) + + postcard::from_bytes(&wasm[len..]).map_err(TwasmError::InvalidArchive) } /// Serializes the `TinyWasmModule` into a vector of bytes. - /// `AlignedVec` can be deferenced as a slice of bytes and - /// implements `io::Write` when the `std` feature is enabled. - pub fn serialize_twasm(&self) -> rkyv::AlignedVec { - let mut serializer = AllocSerializer::<0>::default(); - serializer.pad(TWASM_MAGIC.len()).unwrap(); - serializer.serialize_value(self).unwrap(); - let mut out = serializer.into_serializer().into_inner(); - out[..TWASM_MAGIC.len()].copy_from_slice(&TWASM_MAGIC); - out + pub fn serialize_twasm(&self) -> Result, TwasmError> { + let buf = Vec::from(TWASM_MAGIC); + postcard::to_extend(self, buf).map_err(TwasmError::InvalidArchive) } } @@ -81,8 +69,24 @@ mod tests { #[test] fn test_serialize() { let wasm = TinyWasmModule::default(); - let twasm = wasm.serialize_twasm(); + 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(); + let mut twasm = wasm.serialize_twasm().expect("should serialize"); + twasm[0] = 0; + assert!(matches!(TinyWasmModule::from_twasm(&twasm), Err(TwasmError::InvalidMagic))); + } + + #[test] + fn test_invalid_version() { + let wasm = TinyWasmModule::default(); + let mut twasm = wasm.serialize_twasm().expect("should serialize"); + twasm[4] = 0; + assert!(matches!(TinyWasmModule::from_twasm(&twasm), Err(TwasmError::InvalidVersion))); + } } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index d77d66d..9db5ed5 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,12 +1,26 @@ use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; -use crate::{DataAddr, ElemAddr, MemAddr}; +use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. #[derive(Debug, Copy, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] -pub struct MemoryArg { - pub offset: u64, - pub mem_addr: MemAddr, +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct MemoryArg([u8; 12]); + +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) + } + + pub fn offset(&self) -> u64 { + u64::from_le_bytes(self.0[0..8].try_into().expect("invalid offset")) + } + + pub fn mem_addr(&self) -> MemAddr { + MemAddr::from_le_bytes(self.0[8..12].try_into().expect("invalid mem_addr")) + } } type BrTableDefault = u32; @@ -15,15 +29,16 @@ type EndOffset = u32; type ElseOffset = u32; #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ConstInstruction { I32Const(i32), I64Const(i64), F32Const(f32), F64Const(f64), + V128Const(i128), GlobalGet(GlobalAddr), - RefNull(ValType), - RefFunc(FuncAddr), + RefFunc(Option), + RefExtern(Option), } /// A WebAssembly Instruction @@ -37,31 +52,13 @@ pub enum ConstInstruction { /// This makes it easier to implement the label stack iteratively. /// /// See -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[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] pub enum Instruction { - // > Custom Instructions - // // LocalGet + I32Const + I32Add - // I32LocalGetConstAdd(LocalAddr, i32), - // // LocalGet + I32Const + I32Store - // I32ConstStoreLocal { local: LocalAddr, const_i32: i32, offset: u32, mem_addr: u8 }, - // // LocalGet + LocalGet + I32Store - // I32StoreLocal { local_a: LocalAddr, local_b: LocalAddr, offset: u32, mem_addr: u8 }, - // // I64Xor + I64Const + I64RotL - // // Commonly used by a few crypto libraries - // I64XorConstRotl(i64), - // // LocalTee + LocalGet - // LocalTeeGet(LocalAddr, LocalAddr), - // LocalGet2(LocalAddr, LocalAddr), - // LocalGet3(LocalAddr, LocalAddr, LocalAddr), - // LocalGetSet(LocalAddr, LocalAddr), - - // LocalGetGet32(LocalAddr, LocalAddr), LocalGetGet64(LocalAddr, LocalAddr), LocalGetGet128(LocalAddr, LocalAddr), - // LocalTeeGet32(LocalAddr, LocalAddr), LocalTeeGet64(LocalAddr, LocalAddr), LocalTeeGet128(LocalAddr, LocalAddr), - LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopy128Ref(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), + 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 // See @@ -94,63 +91,43 @@ pub enum Instruction { // > Parametric Instructions // See - Drop32, - Drop64, - Drop128, - DropRef, - - Select32, - Select64, - Select128, - SelectRef, + Drop32, Select32, + Drop64, Select64, + Drop128, Select128, + DropRef, SelectRef, // > Variable Instructions // See - LocalGet32(LocalAddr), - LocalGet64(LocalAddr), - LocalGet128(LocalAddr), - LocalGetRef(LocalAddr), - - LocalSet32(LocalAddr), - LocalSet64(LocalAddr), - LocalSet128(LocalAddr), - LocalSetRef(LocalAddr), - - LocalTee32(LocalAddr), - LocalTee64(LocalAddr), - LocalTee128(LocalAddr), - LocalTeeRef(LocalAddr), - GlobalGet(GlobalAddr), - GlobalSet32(GlobalAddr), - GlobalSet64(GlobalAddr), - GlobalSet128(GlobalAddr), - GlobalSetRef(GlobalAddr), + LocalGet32(LocalAddr), LocalSet32(LocalAddr), LocalTee32(LocalAddr), GlobalSet32(GlobalAddr), + LocalGet64(LocalAddr), LocalSet64(LocalAddr), LocalTee64(LocalAddr), GlobalSet64(GlobalAddr), + LocalGet128(LocalAddr), LocalSet128(LocalAddr), LocalTee128(LocalAddr), GlobalSet128(GlobalAddr), + LocalGetRef(LocalAddr), LocalSetRef(LocalAddr), LocalTeeRef(LocalAddr), GlobalSetRef(GlobalAddr), // > Memory Instructions - I32Load { offset: u64, mem_addr: MemAddr }, - I64Load { offset: u64, mem_addr: MemAddr }, - F32Load { offset: u64, mem_addr: MemAddr }, - F64Load { offset: u64, mem_addr: MemAddr }, - I32Load8S { offset: u64, mem_addr: MemAddr }, - I32Load8U { offset: u64, mem_addr: MemAddr }, - I32Load16S { offset: u64, mem_addr: MemAddr }, - I32Load16U { offset: u64, mem_addr: MemAddr }, - I64Load8S { offset: u64, mem_addr: MemAddr }, - I64Load8U { offset: u64, mem_addr: MemAddr }, - I64Load16S { offset: u64, mem_addr: MemAddr }, - I64Load16U { offset: u64, mem_addr: MemAddr }, - I64Load32S { offset: u64, mem_addr: MemAddr }, - I64Load32U { offset: u64, mem_addr: MemAddr }, - I32Store { offset: u64, mem_addr: MemAddr }, - I64Store { offset: u64, mem_addr: MemAddr }, - F32Store { offset: u64, mem_addr: MemAddr }, - F64Store { offset: u64, mem_addr: MemAddr }, - I32Store8 { offset: u64, mem_addr: MemAddr }, - I32Store16 { offset: u64, mem_addr: MemAddr }, - I64Store8 { offset: u64, mem_addr: MemAddr }, - I64Store16 { offset: u64, mem_addr: MemAddr }, - I64Store32 { offset: u64, mem_addr: MemAddr }, + I32Load(MemoryArg), + I64Load(MemoryArg), + F32Load(MemoryArg), + F64Load(MemoryArg), + I32Load8S(MemoryArg), + I32Load8U(MemoryArg), + I32Load16S(MemoryArg), + I32Load16U(MemoryArg), + I64Load8S(MemoryArg), + I64Load8U(MemoryArg), + I64Load16S(MemoryArg), + I64Load16U(MemoryArg), + I64Load32S(MemoryArg), + I64Load32U(MemoryArg), + I32Store(MemoryArg), + I64Store(MemoryArg), + F32Store(MemoryArg), + F64Store(MemoryArg), + I32Store8(MemoryArg), + I32Store16(MemoryArg), + I64Store8(MemoryArg), + I64Store16(MemoryArg), + I64Store32(MemoryArg), MemorySize(MemAddr), MemoryGrow(MemAddr), @@ -164,7 +141,7 @@ pub enum Instruction { RefNull(ValType), RefFunc(FuncAddr), RefIsNull, - + // > Numeric Instructions // See I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU, @@ -205,4 +182,80 @@ pub enum Instruction { MemoryFill(MemAddr), DataDrop(DataAddr), ElemDrop(ElemAddr), + + // > SIMD Instructions + V128Load(MemoryArg), + V128Load8x8S(MemoryArg), V128Load8x8U(MemoryArg), + V128Load16x4S(MemoryArg), V128Load16x4U(MemoryArg), + V128Load32x2S(MemoryArg), V128Load32x2U(MemoryArg), + + V128Load8Splat(MemoryArg), V128Load16Splat(MemoryArg), V128Load32Splat(MemoryArg), V128Load64Splat(MemoryArg), + V128Load8Lane(MemoryArg, u8), V128Load16Lane(MemoryArg, u8), V128Load32Lane(MemoryArg, u8), V128Load64Lane(MemoryArg, u8), + + V128Load32Zero(MemoryArg), V128Load64Zero(MemoryArg), + + V128Store(MemoryArg), V128Store8Lane(MemoryArg, u8), V128Store16Lane(MemoryArg, u8), V128Store32Lane(MemoryArg, u8), V128Store64Lane(MemoryArg, u8), + + I8x16Shuffle(ConstIdx), + V128Const(ConstIdx), + + I8x16ExtractLaneS(u8), I8x16ExtractLaneU(u8), I8x16ReplaceLane(u8), + I16x8ExtractLaneS(u8), I16x8ExtractLaneU(u8), I16x8ReplaceLane(u8), + I32x4ExtractLane(u8), I32x4ReplaceLane(u8), + I64x2ExtractLane(u8), I64x2ReplaceLane(u8), + F32x4ExtractLane(u8), F32x4ReplaceLane(u8), + F64x2ExtractLane(u8), F64x2ReplaceLane(u8), + + V128Not, V128And, V128AndNot, V128Or, V128Xor, V128Bitselect, V128AnyTrue, I8x16Swizzle, + I8x16Splat, I8x16Eq, I8x16Ne, I8x16LtS, I8x16LtU, I8x16GtS, I8x16GtU, I8x16LeS, I8x16LeU, I8x16GeS, I8x16GeU, + I16x8Splat, I16x8Eq, I16x8Ne, I16x8LtS, I16x8LtU, I16x8GtS, I16x8GtU, I16x8LeS, I16x8LeU, I16x8GeS, I16x8GeU, + I32x4Splat, I32x4Eq, I32x4Ne, I32x4LtS, I32x4LtU, I32x4GtS, I32x4GtU, I32x4LeS, I32x4LeU, I32x4GeS, I32x4GeU, + I64x2Splat, I64x2Eq, I64x2Ne, I64x2LtS, I64x2GtS, I64x2LeS, I64x2GeS, + F32x4Splat, F32x4Eq, F32x4Ne, F32x4Lt, F32x4Gt, F32x4Le, F32x4Ge, + F64x2Splat, F64x2Eq, F64x2Ne, F64x2Lt, F64x2Gt, F64x2Le, F64x2Ge, + + I8x16Abs, I8x16Neg, I8x16AllTrue, I8x16Bitmask, I8x16Shl, I8x16ShrS, I8x16ShrU, I8x16Add, I8x16Sub, I8x16MinS, I8x16MinU, I8x16MaxS, I8x16MaxU, + I16x8Abs, I16x8Neg, I16x8AllTrue, I16x8Bitmask, I16x8Shl, I16x8ShrS, I16x8ShrU, I16x8Add, I16x8Sub, I16x8MinS, I16x8MinU, I16x8MaxS, I16x8MaxU, + I32x4Abs, I32x4Neg, I32x4AllTrue, I32x4Bitmask, I32x4Shl, I32x4ShrS, I32x4ShrU, I32x4Add, I32x4Sub, I32x4MinS, I32x4MinU, I32x4MaxS, I32x4MaxU, + I64x2Abs, I64x2Neg, I64x2AllTrue, I64x2Bitmask, I64x2Shl, I64x2ShrS, I64x2ShrU, I64x2Add, I64x2Sub, I64x2Mul, + + I8x16NarrowI16x8S, I8x16NarrowI16x8U, I8x16AddSatS, I8x16AddSatU, I8x16SubSatS, I8x16SubSatU, I8x16AvgrU, + I16x8NarrowI32x4S, I16x8NarrowI32x4U, I16x8AddSatS, I16x8AddSatU, I16x8SubSatS, I16x8SubSatU, I16x8AvgrU, + + I16x8ExtAddPairwiseI8x16S, I16x8ExtAddPairwiseI8x16U, I16x8Mul, + I32x4ExtAddPairwiseI16x8S, I32x4ExtAddPairwiseI16x8U, I32x4Mul, + + I16x8ExtMulLowI8x16S, I16x8ExtMulLowI8x16U, I16x8ExtMulHighI8x16S, I16x8ExtMulHighI8x16U, + I32x4ExtMulLowI16x8S, I32x4ExtMulLowI16x8U, I32x4ExtMulHighI16x8S, I32x4ExtMulHighI16x8U, + I64x2ExtMulLowI32x4S, I64x2ExtMulLowI32x4U, I64x2ExtMulHighI32x4S, I64x2ExtMulHighI32x4U, + + I16x8ExtendLowI8x16S, I16x8ExtendLowI8x16U, I16x8ExtendHighI8x16S, I16x8ExtendHighI8x16U, + I32x4ExtendLowI16x8S, I32x4ExtendLowI16x8U, I32x4ExtendHighI16x8S, I32x4ExtendHighI16x8U, + I64x2ExtendLowI32x4S, I64x2ExtendLowI32x4U, I64x2ExtendHighI32x4S, I64x2ExtendHighI32x4U, + + I8x16Popcnt, I16x8Q15MulrSatS, I32x4DotI16x8S, + + F32x4Ceil, F32x4Floor, F32x4Trunc, F32x4Nearest, F32x4Abs, F32x4Neg, F32x4Sqrt, F32x4Add, F32x4Sub, F32x4Mul, F32x4Div, F32x4Min, F32x4Max, F32x4PMin, F32x4PMax, + F64x2Ceil, F64x2Floor, F64x2Trunc, F64x2Nearest, F64x2Abs, F64x2Neg, F64x2Sqrt, F64x2Add, F64x2Sub, F64x2Mul, F64x2Div, F64x2Min, F64x2Max, F64x2PMin, F64x2PMax, + I32x4TruncSatF32x4S, I32x4TruncSatF32x4U, + F32x4ConvertI32x4S, F32x4ConvertI32x4U, + I32x4TruncSatF64x2SZero, I32x4TruncSatF64x2UZero, + F64x2ConvertLowI32x4S, F64x2ConvertLowI32x4U, + F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4, + + // > Relaxed SIMD + I8x16RelaxedSwizzle, + I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, + I32x4RelaxedTruncF64x2SZero, I32x4RelaxedTruncF64x2UZero, + F32x4RelaxedMadd, F32x4RelaxedNmadd, + F64x2RelaxedMadd, F64x2RelaxedNmadd, + I8x16RelaxedLaneselect, + I16x8RelaxedLaneselect, + I32x4RelaxedLaneselect, + I64x2RelaxedLaneselect, + F32x4RelaxedMin, F32x4RelaxedMax, + F64x2RelaxedMin, F64x2RelaxedMax, + I16x8RelaxedQ15mulrS, + I16x8RelaxedDotI8x16I7x16S, + I32x4RelaxedDotI8x16I7x16AddS } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 94dc94d..470c631 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -12,6 +12,14 @@ extern crate alloc; use alloc::boxed::Box; use core::{fmt::Debug, ops::Range}; +// Memory defaults +const MEM_PAGE_SIZE: u64 = 65536; +const MAX_MEMORY_SIZE: u64 = 4294967296; + +const fn max_page_count(page_size: u64) -> u64 { + MAX_MEMORY_SIZE / page_size +} + // log for logging (optional). #[cfg(feature = "logging")] #[allow(clippy::single_component_path_imports, unused_imports)] @@ -37,13 +45,25 @@ pub use value::*; #[cfg(feature = "archive")] pub mod archive; +#[cfg(not(feature = "archive"))] +pub mod archive { + #[derive(Debug)] + pub enum TwasmError {} + impl core::fmt::Display for TwasmError { + fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Err(core::fmt::Error) + } + } + impl core::error::Error for TwasmError {} +} + /// A `TinyWasm` WebAssembly Module /// /// This is the internal representation of a WebAssembly module in `TinyWasm`. /// `TinyWasmModules` are validated before being created, so they are guaranteed to be valid (as long as they were created by `TinyWasm`). /// This means you should not trust a `TinyWasmModule` created by a third party to be valid. #[derive(Debug, Clone, Default, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TinyWasmModule { /// Optional address of the start function /// @@ -100,7 +120,7 @@ pub struct TinyWasmModule { /// /// See #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ExternalKind { /// A WebAssembly Function. Func, @@ -127,6 +147,7 @@ pub type GlobalAddr = Addr; pub type ElemAddr = Addr; pub type DataAddr = Addr; pub type ExternAddr = Addr; +pub type ConstIdx = Addr; // additional internal addresses pub type TypeAddr = Addr; @@ -171,14 +192,14 @@ impl ExternVal { /// /// See #[derive(Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[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)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct ValueCounts { pub c32: u32, pub c64: u32, @@ -187,7 +208,7 @@ pub struct ValueCounts { } #[derive(Debug, Default, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct ValueCountsSmall { pub c32: u16, pub c64: u16, @@ -195,18 +216,55 @@ pub struct ValueCountsSmall { pub cref: u16, } +impl<'a, T: IntoIterator> From for ValueCounts { + fn from(types: T) -> Self { + let mut counts = ValueCounts::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<'a, T: IntoIterator> From for ValueCountsSmall { + fn from(types: T) -> Self { + let mut counts = ValueCountsSmall::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 + } +} + #[derive(Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunction { pub instructions: Box<[Instruction]>, + pub data: WasmFunctionData, pub locals: ValueCounts, pub params: ValueCountsSmall, pub ty: FuncType, } +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] +pub struct WasmFunctionData { + pub v128_constants: Box<[i128]>, +} + /// A WebAssembly Module Export #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Export { /// The name of the export. pub name: Box, @@ -217,21 +275,21 @@ pub struct Export { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Global { pub ty: GlobalType, pub init: ConstInstruction, } #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TableType { pub element_type: ValType, pub size_initial: u32, @@ -250,28 +308,53 @@ impl TableType { /// Represents a memory's type. #[derive(Debug, Copy, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct MemoryType { - pub arch: MemoryArch, - pub page_count_initial: u64, - pub page_count_max: Option, + arch: MemoryArch, + page_count_initial: u64, + page_count_max: Option, + page_size: Option, } impl MemoryType { - pub fn new_32(page_count_initial: u64, page_count_max: Option) -> Self { - Self { arch: MemoryArch::I32, page_count_initial, page_count_max } + pub fn new(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 { + self.arch + } + + pub 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())) + } + + pub fn page_size(&self) -> u64 { + self.page_size.unwrap_or(MEM_PAGE_SIZE) + } + + pub fn initial_size(&self) -> u64 { + self.page_count_initial * self.page_size() + } + + pub fn max_size(&self) -> u64 { + self.page_count_max() * self.page_size() } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum MemoryArch { I32, I64, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Import { pub module: Box, pub name: Box, @@ -279,7 +362,7 @@ pub struct Import { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ImportKind { Function(TypeAddr), Table(TableType), @@ -300,7 +383,7 @@ impl From<&ImportKind> for ExternalKind { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Data { pub data: Box<[u8]>, pub range: Range, @@ -308,14 +391,14 @@ pub struct Data { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum DataKind { Active { mem: MemAddr, offset: ConstInstruction }, Passive, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Element { pub kind: ElementKind, pub items: Box<[ElementItem]>, @@ -324,7 +407,7 @@ pub struct Element { } #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, @@ -332,7 +415,7 @@ pub enum ElementKind { } #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ElementItem { Func(FuncAddr), Expr(ConstInstruction), diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 416f678..a9498cc 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -17,14 +17,94 @@ pub enum WasmValue { /// A 64-bit float. F64(f64), // /// A 128-bit vector - V128(u128), + V128(i128), - RefExtern(ExternAddr), - RefFunc(FuncAddr), - RefNull(ValType), + RefExtern(ExternRef), + RefFunc(FuncRef), +} + +#[derive(Clone, Copy, PartialEq)] +pub struct ExternRef(Option); + +#[derive(Clone, Copy, PartialEq)] +pub struct FuncRef(Option); + +impl Debug for ExternRef { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { + match self.0 { + Some(addr) => write!(f, "extern({:?})", addr), + None => write!(f, "extern(null)"), + } + } +} + +impl Debug for FuncRef { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { + match self.0 { + Some(addr) => write!(f, "func({:?})", addr), + None => write!(f, "func(null)"), + } + } +} + +impl FuncRef { + /// Create a new `FuncRef` from a `FuncAddr`. + /// Should only be used by the runtime. + #[doc(hidden)] + #[inline] + pub const fn new(addr: Option) -> Self { + Self(addr) + } + + /// Create a null `FuncRef`. + #[inline] + pub const fn null() -> Self { + Self(None) + } + + /// Check if the `FuncRef` is null. + #[inline] + pub const fn is_null(&self) -> bool { + self.0.is_none() + } + + /// Get the `FuncAddr` from the `FuncRef`. + #[inline] + pub const fn addr(&self) -> Option { + self.0 + } +} + +impl ExternRef { + /// Create a new `ExternRef` from an `ExternAddr`. + /// Should only be used by the runtime. + #[doc(hidden)] + #[inline] + pub const fn new(addr: Option) -> Self { + Self(addr) + } + + /// Create a null `ExternRef`. + #[inline] + pub const fn null() -> Self { + Self(None) + } + + /// Check if the `ExternRef` is null. + #[inline] + pub const fn is_null(&self) -> bool { + self.0.is_none() + } + + /// Get the `ExternAddr` from the `ExternRef`. + #[inline] + pub const fn addr(&self) -> Option { + self.0 + } } impl WasmValue { + #[doc(hidden)] #[inline] pub fn const_instr(&self) -> ConstInstruction { match self { @@ -32,10 +112,8 @@ impl WasmValue { Self::I64(i) => ConstInstruction::I64Const(*i), Self::F32(i) => ConstInstruction::F32Const(*i), Self::F64(i) => ConstInstruction::F64Const(*i), - Self::RefFunc(i) => ConstInstruction::RefFunc(*i), - Self::RefNull(ty) => ConstInstruction::RefNull(*ty), - - // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr), + Self::V128(i) => ConstInstruction::V128Const(*i), + Self::RefFunc(i) => ConstInstruction::RefFunc(i.addr()), _ => unimplemented!("no const_instr for {:?}", self), } } @@ -49,17 +127,18 @@ impl WasmValue { ValType::F32 => Self::F32(0.0), ValType::F64 => Self::F64(0.0), ValType::V128 => Self::V128(0), - ValType::RefFunc => Self::RefNull(ValType::RefFunc), - ValType::RefExtern => Self::RefNull(ValType::RefExtern), + ValType::RefFunc => Self::RefFunc(FuncRef::null()), + ValType::RefExtern => Self::RefExtern(ExternRef::null()), } } + /// Check if two values are equal, ignoring differences in NaN values. #[inline] pub fn eq_loose(&self, other: &Self) -> bool { match (self, other) { (Self::I32(a), Self::I32(b)) => a == b, (Self::I64(a), Self::I64(b)) => a == b, - (Self::RefNull(v), Self::RefNull(v2)) => v == v2, + (Self::V128(a), Self::V128(b)) => a == b, (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2, (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2, (Self::F32(a), Self::F32(b)) => { @@ -79,6 +158,62 @@ impl WasmValue { _ => false, } } + + #[doc(hidden)] + pub fn as_i32(&self) -> Option { + match self { + Self::I32(i) => Some(*i), + _ => None, + } + } + + #[doc(hidden)] + pub fn as_i64(&self) -> Option { + match self { + Self::I64(i) => Some(*i), + _ => None, + } + } + + #[doc(hidden)] + pub fn as_f32(&self) -> Option { + match self { + Self::F32(i) => Some(*i), + _ => None, + } + } + + #[doc(hidden)] + pub fn as_f64(&self) -> Option { + match self { + Self::F64(i) => Some(*i), + _ => None, + } + } + + #[doc(hidden)] + pub fn as_v128(&self) -> Option { + match self { + Self::V128(i) => Some(*i), + _ => None, + } + } + + #[doc(hidden)] + pub fn as_ref_extern(&self) -> Option { + match self { + Self::RefExtern(ref_extern) => Some(*ref_extern), + _ => None, + } + } + + #[doc(hidden)] + pub fn as_ref_func(&self) -> Option { + match self { + Self::RefFunc(ref_func) => Some(*ref_func), + _ => None, + } + } } #[cold] @@ -92,9 +227,8 @@ impl Debug for WasmValue { WasmValue::F32(i) => write!(f, "f32({i})"), WasmValue::F64(i) => write!(f, "f64({i})"), WasmValue::V128(i) => write!(f, "v128({i:?})"), - WasmValue::RefExtern(addr) => write!(f, "ref.extern({addr:?})"), - WasmValue::RefFunc(addr) => write!(f, "ref.func({addr:?})"), - WasmValue::RefNull(ty) => write!(f, "ref.null({ty:?})"), + WasmValue::RefExtern(i) => write!(f, "ref({i:?})"), + WasmValue::RefFunc(i) => write!(f, "func({i:?})"), } } } @@ -111,14 +245,13 @@ impl WasmValue { Self::V128(_) => ValType::V128, Self::RefExtern(_) => ValType::RefExtern, Self::RefFunc(_) => ValType::RefFunc, - Self::RefNull(ty) => *ty, } } } /// Type of a WebAssembly value. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ValType { /// A 32-bit integer. I32, @@ -142,6 +275,7 @@ impl ValType { WasmValue::default_for(*self) } + #[doc(hidden)] #[inline] pub fn is_simd(&self) -> bool { matches!(self, ValType::V128) @@ -177,4 +311,4 @@ macro_rules! impl_conversion_for_wasmvalue { } } -impl_conversion_for_wasmvalue! { i32 => I32, i64 => I64, f32 => F32, f64 => F64, u128 => V128 } +impl_conversion_for_wasmvalue! { i32 => I32, i64 => I64, f32 => F32, f64 => F64, i128 => V128, ExternRef => RefExtern, FuncRef => RefFunc } diff --git a/crates/wasm-testsuite/Cargo.toml b/crates/wasm-testsuite/Cargo.toml deleted file mode 100644 index 8edcb8a..0000000 --- a/crates/wasm-testsuite/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name="wasm-testsuite" -version="0.5.0" -description="Mirror of the WebAssembly core testsuite for use in testing WebAssembly implementations" -license="Apache-2.0" -readme="README.md" -edition.workspace=true -authors.workspace=true -repository.workspace=true -rust-version.workspace=true - -[lib] -path="lib.rs" - -[package.metadata.workspaces] -independent=true - -[dependencies] -rust-embed={version="8.4", features=["include-exclude"]} diff --git a/crates/wasm-testsuite/README.md b/crates/wasm-testsuite/README.md deleted file mode 100644 index 51d574c..0000000 --- a/crates/wasm-testsuite/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# `wasm-testsuite` - -This crate embeds the latest version of the [WebAssembly Test Suite](https://github.com/WebAssembly/spec/tree/main/test). It is currently mainly used for testing the `tinywasm-parser` crate. Check out the [documentation](https://docs.rs/wasm-testsuite) for more information. - -## Usage - -```rust -use wasm_testsuite::{MVP_TESTS, get_test_wast}; - -MVP_TESTS.iter().for_each(|test| { - let wast_bytes = get_test_wast(test).expect("Failed to get wast bytes"); - let wast = std::str::from_utf8(&wast_bytes).expect("failed to convert wast to utf8"); - - // Do something with the wast (e.g. parse it using the `wast` crate) -}); -``` - -## License - -This crate is licensed under the [Apache License, Version 2.0](https://github.com/WebAssembly/spec/blob/main/test/LICENSE). \ No newline at end of file diff --git a/crates/wasm-testsuite/data b/crates/wasm-testsuite/data deleted file mode 160000 index ae5a669..0000000 --- a/crates/wasm-testsuite/data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae5a66933070b705dde56c2a71bf3fbc33282864 diff --git a/crates/wasm-testsuite/lib.rs b/crates/wasm-testsuite/lib.rs deleted file mode 100644 index 1f42664..0000000 --- a/crates/wasm-testsuite/lib.rs +++ /dev/null @@ -1,79 +0,0 @@ -#![doc = include_str!("README.md")] -#![forbid(unsafe_code)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) -))] -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] - -use rust_embed::RustEmbed; -use std::borrow::Cow; - -#[derive(RustEmbed)] -#[folder = "data/"] -#[include = "*.wast"] -struct Asset; - -/// List of all supported proposals. Can be used to filter tests. -/// -/// Includes all proposals from -#[rustfmt::skip] -pub const PROPOSALS: &[&str] = &["annotations", "exception-handling", "extended-const", "function-references", "gc", "memory64", "multi-memory", "relaxed-simd", "tail-call", "threads"]; - -/// List of all tests that apply to the MVP (V1) spec -/// Note that the tests are still for the latest spec, so the latest version of Wast is used. -#[rustfmt::skip] // removed: "break-drop.wast", -pub const MVP_TESTS: &[&str] = &["address.wast", "align.wast", "binary-leb128.wast", "binary.wast", "block.wast", "br.wast", "br_if.wast", "br_table.wast", "call.wast", "call_indirect.wast", "comments.wast", "const.wast", "conversions.wast", "custom.wast", "data.wast", "elem.wast", "endianness.wast", "exports.wast", "f32.wast", "f32_bitwise.wast", "f32_cmp.wast", "f64.wast", "f64_bitwise.wast", "f64_cmp.wast", "fac.wast", "float_exprs.wast", "float_literals.wast", "float_memory.wast", "float_misc.wast", "forward.wast", "func.wast", "func_ptrs.wast", "global.wast", "i32.wast", "i64.wast", "if.wast", "imports.wast", "inline-module.wast", "int_exprs.wast", "int_literals.wast", "labels.wast", "left-to-right.wast", "linking.wast", "load.wast", "local_get.wast", "local_set.wast", "local_tee.wast", "loop.wast", "memory.wast", "memory_grow.wast", "memory_redundancy.wast", "memory_size.wast", "memory_trap.wast", "names.wast", "nop.wast", "return.wast", "select.wast", "skip-stack-guard-page.wast", "stack.wast", "start.wast", "store.wast", "switch.wast", "table.wast", "token.wast", "traps.wast", "type.wast", "unreachable.wast", "unreached-valid.wast", "unreached-invalid.wast", "unwind.wast", "utf8-custom-section-id.wast", "utf8-import-field.wast", "utf8-import-module.wast", "utf8-invalid-encoding.wast"]; - -/// List of all tests that apply to the V2 draft 1 spec. -#[rustfmt::skip] -pub const V2_DRAFT_1_TESTS: &[&str] = &["address.wast", "align.wast", "binary-leb128.wast", "binary.wast", "block.wast", "br.wast", "br_if.wast", "br_table.wast", "bulk.wast", "call.wast", "call_indirect.wast", "comments.wast", "const.wast", "conversions.wast", "custom.wast", "data.wast", "elem.wast", "endianness.wast", "exports.wast", "f32.wast", "f32_bitwise.wast", "f32_cmp.wast", "f64.wast", "f64_bitwise.wast", "f64_cmp.wast", "fac.wast", "float_exprs.wast", "float_literals.wast", "float_memory.wast", "float_misc.wast", "forward.wast", "func.wast", "func_ptrs.wast", "global.wast", "i32.wast", "i64.wast", "if.wast", "imports.wast", "inline-module.wast", "int_exprs.wast", "int_literals.wast", "labels.wast", "left-to-right.wast", "linking.wast", "load.wast", "local_get.wast", "local_set.wast", "local_tee.wast", "loop.wast", "memory.wast", "memory_copy.wast", "memory_fill.wast", "memory_grow.wast", "memory_init.wast", "memory_redundancy.wast", "memory_size.wast", "memory_trap.wast", "names.wast", "nop.wast", "obsolete-keywords.wast", "ref_func.wast", "ref_is_null.wast", "ref_null.wast", "return.wast", "select.wast", "skip-stack-guard-page.wast", "stack.wast", "start.wast", "store.wast", "switch.wast", "table-sub.wast", "table.wast", "table_copy.wast", "table_fill.wast", "table_get.wast", "table_grow.wast", "table_init.wast", "table_set.wast", "table_size.wast", "token.wast", "traps.wast", "type.wast", "unreachable.wast", "unreached-invalid.wast", "unreached-valid.wast", "unwind.wast", "utf8-custom-section-id.wast", "utf8-import-field.wast", "utf8-import-module.wast", "utf8-invalid-encoding.wast"]; - -/// List of all tests that apply to the simd proposal -#[rustfmt::skip] -pub const SIMD_TESTS: &[&str] = &["simd_address.wast", "simd_align.wast", "simd_bit_shift.wast", "simd_bitwise.wast", "simd_boolean.wast", "simd_const.wast", "simd_conversions.wast", "simd_f32x4.wast", "simd_f32x4_arith.wast", "simd_f32x4_cmp.wast", "simd_f32x4_pmin_pmax.wast", "simd_f32x4_rounding.wast", "simd_f64x2.wast", "simd_f64x2_arith.wast", "simd_f64x2_cmp.wast", "simd_f64x2_pmin_pmax.wast", "simd_f64x2_rounding.wast", "simd_i16x8_arith.wast", "simd_i16x8_arith2.wast", "simd_i16x8_cmp.wast", "simd_i16x8_extadd_pairwise_i8x16.wast", "simd_i16x8_extmul_i8x16.wast", "simd_i16x8_q15mulr_sat_s.wast", "simd_i16x8_sat_arith.wast", "simd_i32x4_arith.wast", "simd_i32x4_arith2.wast", "simd_i32x4_cmp.wast", "simd_i32x4_dot_i16x8.wast", "simd_i32x4_extadd_pairwise_i16x8.wast", "simd_i32x4_extmul_i16x8.wast", "simd_i32x4_trunc_sat_f32x4.wast", "simd_i32x4_trunc_sat_f64x2.wast", "simd_i64x2_arith.wast", "simd_i64x2_arith2.wast", "simd_i64x2_cmp.wast", "simd_i64x2_extmul_i32x4.wast", "simd_i8x16_arith.wast", "simd_i8x16_arith2.wast", "simd_i8x16_cmp.wast", "simd_i8x16_sat_arith.wast", "simd_int_to_int_extend.wast", "simd_lane.wast", "simd_linking.wast", "simd_load.wast", "simd_load16_lane.wast", "simd_load32_lane.wast", "simd_load64_lane.wast", "simd_load8_lane.wast", "simd_load_extend.wast", "simd_load_splat.wast", "simd_load_zero.wast", "simd_splat.wast", "simd_store.wast", "simd_store16_lane.wast", "simd_store32_lane.wast", "simd_store64_lane.wast", "simd_store8_lane.wast"]; - -/// List of all tests that apply to a specific proposal. -pub fn get_proposal_tests(proposal: &str) -> impl Iterator + '_ { - Asset::iter().filter_map(move |x| { - let mut parts = x.split('/'); - if parts.next() == Some("proposals") && parts.next() == Some(proposal) { - Some(format!("{}/{}", proposal, parts.next().unwrap_or_default())) - } else { - None - } - }) -} - -/// Get the WAST file as a byte slice. -pub fn get_test_wast(name: &str) -> Option> { - assert!(name.ends_with(".wast"), "Expected .wast file. Got: {name}"); - - match name.contains('/') { - true => Asset::get(&format!("proposals/{name}")).map(|x| x.data), - false => Asset::get(name).map(|x| x.data), - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashSet; - - use super::*; - - #[test] - fn test_proposals() { - let mut unique_proposals = HashSet::new(); - - // check that all proposals are present - for proposal in Asset::iter() { - if !proposal.starts_with("proposals/") { - continue; - } - - let proposal = proposal.split('/').nth(1).unwrap(); - unique_proposals.insert(proposal.to_owned()); - assert!(PROPOSALS.contains(&proposal)); - } - } -} diff --git a/examples/archive.rs b/examples/archive.rs index ea82c51..7a1021b 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -1,5 +1,5 @@ use eyre::Result; -use tinywasm::{parser::Parser, types::TinyWasmModule, Module, Store}; +use tinywasm::{Module, Store, parser::Parser, types::TinyWasmModule}; const WASM: &str = r#" (module @@ -11,12 +11,12 @@ const WASM: &str = r#" "#; fn main() -> Result<()> { - let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); let module = Parser::default().parse_module_bytes(wasm)?; - let twasm = module.serialize_twasm(); + let twasm = module.serialize_twasm()?; - // now, you could e.g. write twasm to a file called `add.twasm` - // and load it later in a different program + // Now, you could e.g. write `twasm` to a file called `add.twasm` + // and load it later in a different program. let module: Module = TinyWasmModule::from_twasm(&twasm)?.into(); let mut store = Store::default(); diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs new file mode 100644 index 0000000..01f833c --- /dev/null +++ b/examples/funcref_callbacks.rs @@ -0,0 +1,157 @@ +use eyre::Result; +use tinywasm::{Extern, FuncContext, Imports, Module, Store, types::FuncRef}; + +fn main() -> Result<()> { + by_func_ref_passed()?; + by_func_ref_returned()?; + 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. + const WASM: &str = r#" + (module + (import "host" "call_this" (func $host_callback_caller (param funcref))) + (import "host" "mul" (func $host_mul (param $x i32) (param $y i32) (result i32))) + + (func $tell_host_to_call (export "tell_host_to_call") + (call $host_callback_caller (ref.func $add)) + (call $host_callback_caller (ref.func $sub)) + (call $host_callback_caller (ref.func $host_mul)) + ) + + (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. + (func $add (param $x i32) (param $y i32) (result i32) + local.get $x + local.get $y + i32.add + ) + (func $sub (param $x i32) (param $y i32) (result i32) + local.get $x + local.get $y + i32.sub + ) + + (table $callback_register 1 funcref) + (func (export "call_binop_by_ref") (param funcref i32 i32) (result i32) + (table.set $callback_register (i32.const 0) (local.get 0)) + (call_indirect $callback_register (type $binop) (local.get 1)(local.get 2)(i32.const 0)) + ) + ) + "#; + + let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); + let 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 = + 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}"); + + 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) }), + )?; + + 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 + const WASM: &str = r#" + (module + (import "host" "mul" (func $host_mul (param $x i32) (param $y i32) (result i32))) + (type $binop (func (param i32 i32) (result i32))) + (table 3 funcref) + (elem (i32.const 0) $add $sub $host_mul) + (func $add (param $x i32) (param $y i32) (result i32) + local.get $x + local.get $y + i32.add + ) + (func $sub (param $x i32) (param $y i32) (result i32) + local.get $x + local.get $y + i32.sub + ) + (func $ref_to_funcs (export "what_should_host_call") (result funcref funcref funcref) + (ref.func $add) + (ref.func $sub) + (ref.func $host_mul) + ) + + (table $callback_register 1 funcref) + (func $call (export "call_binop_by_ref") (param funcref i32 i32) (result i32) + (table.set $callback_register (i32.const 0) (local.get 0)) + (call_indirect $callback_register (type $binop) (local.get 1)(local.get 2)(i32.const 0)) + ) + ) + "#; + + let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); + let 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) }), + )?; + + let instance = module.instantiate(&mut store, Some(imports))?; + + // Ask the module what to call. + let funcrefs = { + let address_getter = + instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; + address_getter.call(&mut store, ())? + }; + + let proxy_caller = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; + + for (idx, func_ref) in [funcrefs.0, funcrefs.1, funcrefs.2].iter().enumerate() { + // Call those `funcref`s via "call_binop_by_ref". + let res = proxy_caller.call(&mut store, (*func_ref, 5, 3))?; + println!("At idx: {idx}, funcref {func_ref:?}(5,3) results in {res}"); + } + Ok(()) +} diff --git a/examples/linking.rs b/examples/linking.rs index d215898..f94773a 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -1,6 +1,7 @@ use eyre::Result; use tinywasm::{Module, Store}; +// WebAssembly module defining and exporting an `add` function. const WASM_ADD: &str = r#" (module (func $add (param $lhs i32) (param $rhs i32) (result i32) @@ -10,6 +11,7 @@ const WASM_ADD: &str = r#" (export "add" (func $add))) "#; +// WebAssembly module importing an `add` function and using it. const WASM_IMPORT: &str = r#" (module (import "adder" "add" (func $add (param i32 i32) (result i32))) @@ -29,13 +31,20 @@ fn main() -> Result<()> { let import_module = Module::parse_bytes(&wasm_import)?; let mut store = Store::default(); + + // Instantiate the `add` module. let add_instance = add_module.instantiate(&mut store, None)?; + // Link the `adder` namespace to the `add` module's instance. let mut imports = tinywasm::Imports::new(); imports.link_module("adder", add_instance.id())?; + + // Instantiate the `import` module with the linked imports. let import_instance = import_module.instantiate(&mut store, Some(imports))?; + // Call the `main` function, which uses the imported `add` function. let main = import_instance.exported_func::<(), i32>(&store, "main")?; assert_eq!(main.call(&mut store, ())?, 3); + Ok(()) } diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index f88fde4..da238ad 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -10,13 +10,22 @@ forced-target="wasm32-unknown-unknown" edition="2021" [dependencies] -tinywasm={path="../../crates/tinywasm", features=["parser", "std", "nightly"]} +tinywasm={path="../../crates/tinywasm", default-features=false, features=["parser", "archive"]} argon2={version="0.5"} +lol_alloc="0.4.1" + +[features] +default=["std"] +std=["tinywasm/std"] [[bin]] name="hello" path="src/hello.rs" +[[bin]] +name="host_fn" +path="src/host_fn.rs" + [[bin]] name="print" path="src/print.rs" @@ -25,6 +34,10 @@ path="src/print.rs" name="tinywasm" path="src/tinywasm.rs" +[[bin]] +name="tinywasm_no_std" +path="src/tinywasm_no_std.rs" + [[bin]] name="fibonacci" path="src/fibonacci.rs" diff --git a/examples/rust/build.sh b/examples/rust/build.sh index fcfd01c..e1d320f 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -1,21 +1,26 @@ #!/usr/bin/env bash -cd "$(dirname "$0")" +cd "$(dirname "$0")" || exit -bins=("hello" "fibonacci" "print" "tinywasm" "argon2id") +bins=("host_fn" "hello" "fibonacci" "print" "tinywasm" "argon2id") exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" -features="+reference-types,+bulk-memory,+mutable-globals" +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" # ensure out dir exists mkdir -p "$dest_dir" +# build no_std +cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin tinywasm_no_std --no-default-features +cp "$out_dir/tinywasm_no_std.wasm" "$dest_dir/" + for bin in "${bins[@]}"; do - RUSTFLAGS="-C target-feature=$features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" + RUSTFLAGS="-C target-feature=$rust_features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" - wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 --enable-bulk-memory --enable-reference-types --enable-mutable-globals + wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 $wasmopt_features if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" diff --git a/examples/rust/src/argon2id.rs b/examples/rust/src/argon2id.rs index 01ea7ca..ff17d04 100644 --- a/examples/rust/src/argon2id.rs +++ b/examples/rust/src/argon2id.rs @@ -1,6 +1,6 @@ #![no_main] -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn argon2id(m_cost: i32, t_cost: i32, p_cost: i32) -> i32 { let password = b"password"; let salt = b"some random salt"; diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs index b847ad5..ec4a371 100644 --- a/examples/rust/src/fibonacci.rs +++ b/examples/rust/src/fibonacci.rs @@ -1,6 +1,6 @@ #![no_main] -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn fibonacci(n: i32) -> i32 { let mut sum = 0; let mut last = 0; @@ -13,7 +13,7 @@ pub extern "C" fn fibonacci(n: i32) -> i32 { sum } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn fibonacci_recursive(n: i32) -> i32 { if n <= 1 { return n; diff --git a/examples/rust/src/hello.rs b/examples/rust/src/hello.rs index c8e2ac3..3d9f400 100644 --- a/examples/rust/src/hello.rs +++ b/examples/rust/src/hello.rs @@ -1,23 +1,23 @@ #![no_main] #[link(wasm_import_module = "env")] -extern "C" { +unsafe extern "C" { fn print_utf8(location: i64, len: i32); } const ARG: &[u8] = &[0u8; 100]; -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn arg_ptr() -> i32 { ARG.as_ptr() as i32 } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn arg_size() -> i32 { ARG.len() as i32 } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn hello(len: i32) { let arg = core::str::from_utf8(&ARG[0..len as usize]).unwrap(); let res = format!("Hello, {}!", arg).as_bytes().to_vec(); diff --git a/examples/rust/src/host_fn.rs b/examples/rust/src/host_fn.rs new file mode 100644 index 0000000..ada74bd --- /dev/null +++ b/examples/rust/src/host_fn.rs @@ -0,0 +1,11 @@ +#![no_main] + +#[link(wasm_import_module = "env")] +unsafe extern "C" { + fn bar(left: i64, right: i32) -> i32; +} + +#[unsafe(no_mangle)] +pub fn foo() -> i32 { + unsafe { bar(1, 2) } +} diff --git a/examples/rust/src/print.rs b/examples/rust/src/print.rs index d04daa3..d2934f0 100644 --- a/examples/rust/src/print.rs +++ b/examples/rust/src/print.rs @@ -1,11 +1,11 @@ #![no_main] #[link(wasm_import_module = "env")] -extern "C" { +unsafe extern "C" { fn printi32(x: i32); } -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) { printi32(lh + rh); } diff --git a/examples/rust/src/print.wasm b/examples/rust/src/print.wasm new file mode 100644 index 0000000..e4aa125 Binary files /dev/null and b/examples/rust/src/print.wasm differ diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 08c8135..d3b3f8c 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -2,17 +2,17 @@ use tinywasm::{Extern, FuncContext}; #[link(wasm_import_module = "env")] -extern "C" { +unsafe extern "C" { fn printi32(x: i32); } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn hello() { let _ = run(); } fn run() -> tinywasm::Result<()> { - let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; + let module = tinywasm::Module::parse_stream(&include_bytes!("./print.wasm")[..])?; let mut store = tinywasm::Store::default(); let mut imports = tinywasm::Imports::new(); diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index 9f2b28c..46f1785 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -1,22 +1,38 @@ #![no_main] #![no_std] +use lol_alloc::{AssumeSingleThreaded, FreeListAllocator}; use tinywasm::{Extern, FuncContext}; +extern crate alloc; + +#[cfg(not(feature = "std"))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[global_allocator] +static ALLOCATOR: AssumeSingleThreaded = + unsafe { AssumeSingleThreaded::new(FreeListAllocator::new()) }; + #[link(wasm_import_module = "env")] -extern "C" { +unsafe extern "C" { fn printi32(x: i32); } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn hello() { let _ = run(); } fn run() -> tinywasm::Result<()> { - let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; let mut store = tinywasm::Store::default(); let mut imports = tinywasm::Imports::new(); + let res = tinywasm::parser::Parser::new().parse_module_bytes(include_bytes!("./print.wasm"))?; + let twasm = res.serialize_twasm()?; + let module = tinywasm::Module::parse_bytes(&twasm)?; + imports.define( "env", "printi32", diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 169fed5..d5a6b2a 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,6 +1,6 @@ use std::hint::black_box; -use eyre::{eyre, Result}; +use eyre::{Result, eyre}; use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; /// Examples of using WebAssembly compiled from Rust with tinywasm. @@ -14,7 +14,8 @@ use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; /// ./examples/rust/build.sh /// ``` /// -/// This requires the `wasm32-unknown-unknown` target, `binaryen` and `wabt` to be installed. +/// This requires the `wasm32-unknown-unknown` target, `binaryen`, and `wabt` to be installed: +/// /// `rustup target add wasm32-unknown-unknown`. /// /// @@ -32,6 +33,7 @@ fn main() -> Result<()> { println!("Available examples:"); println!(" hello"); println!(" printi32"); + println!(" host_fn"); println!(" fibonacci - calculate fibonacci(30)"); println!(" tinywasm - run printi32 inside of tinywasm inside of itself"); println!(" argon2id - run argon2id(1000, 2, 1)"); @@ -43,7 +45,9 @@ fn main() -> Result<()> { "printi32" => printi32()?, "fibonacci" => fibonacci()?, "tinywasm" => tinywasm()?, + "tinywasm_no_std" => tinywasm_no_std()?, "argon2id" => argon2id()?, + "host_fn" => host_fn()?, "all" => { println!("Running all examples"); println!("\nhello.wasm:"); @@ -54,8 +58,12 @@ fn main() -> Result<()> { fibonacci()?; println!("\ntinywasm.wasm:"); tinywasm()?; + println!("\ntinywasm_no_std.wasm:"); + tinywasm_no_std()?; println!("argon2id.wasm:"); argon2id()?; + println!("\nhost_fn.wasm:"); + host_fn()?; } _ => {} } @@ -64,7 +72,22 @@ fn main() -> Result<()> { } fn tinywasm() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/tinywasm.wasm")?; + let module = Module::parse_file("./examples/rust/out/tinywasm.opt.wasm")?; + let mut store = Store::default(); + + let mut imports = Imports::new(); + imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _x: i32| Ok(())))?; + let instance = module.instantiate(&mut store, Some(black_box(imports)))?; + + let hello = instance.exported_func::<(), ()>(&store, "hello")?; + hello.call(&mut store, black_box(()))?; + hello.call(&mut store, black_box(()))?; + hello.call(&mut store, black_box(()))?; + Ok(()) +} + +fn tinywasm_no_std() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/tinywasm_no_std.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -79,7 +102,7 @@ fn tinywasm() -> Result<()> { } fn hello() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/hello.wasm")?; + let module = Module::parse_file("./examples/rust/out/hello.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -107,8 +130,28 @@ fn hello() -> Result<()> { Ok(()) } +fn host_fn() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/host_fn.wasm")?; + let mut store = Store::default(); + let mut imports = Imports::new(); + imports.define( + "env", + "bar", + Extern::typed_func(|_: FuncContext<'_>, (left, right): (i64, i32)| { + assert_eq!(left, 1); + assert_eq!(right, 2); + Ok(left as i32 + right) + }), + )?; + + let instance = module.instantiate(&mut store, Some(imports))?; + let host_fn = instance.exported_func::<(), i32>(&store, "foo")?; + assert_eq!(host_fn.call(&mut store, ())?, 3); + Ok(()) +} + fn printi32() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/print.wasm")?; + let module = Module::parse_file("./examples/rust/out/print.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -151,3 +194,38 @@ fn argon2id() -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hello() { + hello().unwrap(); + } + + #[test] + fn test_printi32() { + printi32().unwrap(); + } + + #[test] + fn test_fibonacci() { + fibonacci().unwrap(); + } + + #[test] + fn test_tinywasm() { + tinywasm().unwrap(); + } + + #[test] + fn test_tinywasm_no_std() { + tinywasm_no_std().unwrap(); + } + + #[test] + fn test_argon2id() { + argon2id().unwrap(); + } +}