diff --git a/.cargo/config.toml b/.cargo/config.toml index f9691ee..e25dc35 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,5 +2,6 @@ version-dev="workspaces version --no-git-commit --force tinywasm*" dev="run -- -l debug run" -test-mvp="test --package tinywasm --test mvp -- test_mvp --exact --nocapture --ignored" -generate-charts="test --package tinywasm --test mvp -- generate_charts --ignored" +test-wasm-1="test --package tinywasm --test test-wasm-1 --release" +test-wasm-2="test --package tinywasm --test test-wasm-2 --release" +test-wast="test --package tinywasm --test test-wast" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c4ae296..f93c5a4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -7,37 +7,149 @@ on: branches: [main] jobs: + build-wasm: + name: Build wasm + runs-on: ubuntu-latest + steps: + - 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: Build wasm + run: ./examples/rust/build.sh + - name: Save artifacts + uses: actions/upload-artifact@v4 + with: + 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 stable Rust toolchain - run: | - rustup toolchain install stable - rustup default stable + - 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 --verbose + run: cargo +stable build --workspace - name: Run tests (stable) - run: cargo +stable test --verbose + 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 - - name: Install nightly Rust toolchain - run: | - rustup toolchain install nightly - rustup default nightly + 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 --verbose --no-default-features + run: cargo +nightly build --workspace --no-default-features - name: Run tests (nightly, no default features) - run: cargo +nightly test --verbose --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: + needs: build-wasm + name: Test on arm64 (Apple M1) + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install 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 + + - 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 + + - name: Load wasm + uses: actions/download-artifact@v4 + with: + 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 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 2.0 testsuite + 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 diff --git a/.gitignore b/.gitignore index 0d6dc9e..14456f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ /target notes.md -examples/tinywasm.wat \ No newline at end of file +examples/rust/out/* +examples/rust/target +examples/rust/Cargo.lock +examples/wast/* +perf.* +flamegraph.svg +/.idea +/*.iml +profile.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..48a2e0d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "search.exclude": { + "**/wasm-testsuite/data": true + }, + "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + ], +} \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..7aaa99c --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,27 @@ +# TinyWasm's Architecture + +TinyWasm follows the general Runtime Structure described in the [WebAssembly Specification](https://webassembly.github.io/spec/core/exec/runtime.html). +Some key differences are: + +- **Type Storage**: Types are inferred from usage context rather than stored explicitly, with all values held as `u64`. +- **Stack Design**: Implements a specific stack for values, labels, and frames to simplify the implementation and enable optimizations. +- **Bytecode Format**: Adopts a custom bytecode format to reduce memory usage and improve performance by allowing direct execution without the need for decoding. +- **Global State Access**: Allows cross-module access to the `Store`'s global state, optimizing imports and exports access. Access requires a module instance reference, maintaining implicit ownership through a reference count. +- **Non-thread-safe Store**: Designed for efficiency in single-threaded applications. +- **JIT Compilation Support**: Prepares for JIT compiler integration with function instances designed to accommodate `WasmFunction`, `HostFunction`, or future `JitFunction`. +- **`no_std` Environment Support**: Offers compatibility with `no_std` environments by allowing disabling of `std` feature +- **Call Frame Execution**: Executes call frames in a single loop rather than recursively, using a single stack for all frames, facilitating easier pause, resume, and step-through. + +## Bytecode Format + +To improve performance and reduce code size, instructions are encoded as enum variants instead of opcodes. +This allows preprocessing the bytecode into a more memory aligned format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. See this [blog post](https://wasmer.io/posts/improving-with-zero-copy-deserialization) by Wasmer +for more details which inspired this design. + +Some instructions are split into multiple variants to reduce the size of the enum (e.g. `br_table` and `br_label`). +Additionally, label instructions contain offsets relative to the current instruction to make branching faster and easier to implement. +Also, `End` instructions are split into `End` and `EndBlock`. Others are also combined, especially in cases where the stack can be skipped. + +See [instructions.rs](./crates/types/src/instructions.rs) for the full list of instructions. + +This is a area that can still be improved. While being able to load pre-processes bytecode directly into memory is nice, in-place decoding could achieve similar speeds, see [A fast in-place interpreter for WebAssembly](https://arxiv.org/abs/2205.01183). diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b9b18b9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,135 @@ +# Changelog + +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). + + + +## [0.8.0] - 2024-08-29 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.7.0...v0.8.0 + +### Changed + +- Full support for Multi-Memory proposal +- 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 +- Removed benchmarks comparing TinyWasm to other WebAssembly runtimes to reduce build dependencies +- Memory and Data Instances are no longer reference counted + +## [0.7.0] - 2024-05-15 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.6.0...v0.7.0 + +### Changed + +- Remove all unsafe code +- Refactor interpreter loop +- Optimize Call-frames +- Remove unnecessary reference counter data from store + +## [0.6.1] - 2024-05-10 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.6.0...v0.6.1 + +### Changed + +- Switched back to the original `wasmparser` crate, which recently added support for `no_std` +- Performance improvements +- Updated dependencies + +## [0.6.0] - 2024-03-27 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.5.0...v0.6.0 + +### Added + +- `Imports` and `Module` are now cloneable (#9) + +### Changed + +- Improved documentation and added more tests +- Tests can now be run on more targets (#11) +- Nightly version has been updated to fix broken builds in some cases (#12) +- Add `aarch64-apple-darwin` and `armv7-unknown-linux-gnueabihf` targets to CI (#12) + +### Removed + +- Removed the `EndFunc` instruction, as it was already covered by the `Return` instruction\ + This also fixes a weird bug that only occurred on certain nightly versions of Rust + +## [0.5.0] - 2024-03-01 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.4.0...v0.5.0 + +### Added + +- Added this `CHANGELOG.md` file to the project +- Added merged instructions for improved performance and reduced bytecode size + +### Changed + +- Now using a custom `wasmparser` fork +- Switched to a visitor pattern for parsing WebAssembly modules +- Reduced the overhead of control flow instructions +- Reduced the size of bytecode instructions +- Fixed issues on the latest nightly Rust compiler +- Simplified a lot of the internal macros + +### Removed + +- Removed duplicate internal code + +## [0.4.0] - 2024-02-04 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.3.0...v0.4.0 + +### Added + +- Added benchmarks for comparison with other WebAssembly runtimes +- Added support for pre-processing WebAssembly modules into tinywasm bytecode +- Improved examples and documentation +- Implemented the bulk memory operations proposal + +### Changed + +- Overall performance improvements + +## [0.3.0] - 2024-01-26 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.2.0...v0.3.0 + +- Better trap handling +- Implement linker +- Element instantiation +- Table Operations +- FuncRefs +- Typesafe host functions +- Host function context +- Spec compliance improvements +- Wasm 2.0 testsuite +- Usage examples +- End-to-end tests +- Lots of bug fixes +- Full `no_std` support + +## [0.3.0] - 2024-01-11 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.1.0...v0.2.0 + +- Support for `br_table` +- Memory trapping improvements +- Implicit function label scopes +- else Instructions +- All Memory instructions +- Imports +- Basic linking +- Globals +- Fix function addr resolution +- Reference Instructions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fa8ac8..e1ca837 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,20 @@ -# Common Commands +# Scripts and Commands -> To improve the development experience, a number of aliases have been added to the `.cargo/config.toml` file. These can be run using `cargo `. +> 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 `. -- **`cargo dev`**\ +- **`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. - -- **`cargo generate-charts`**\ - Generate test result charts + 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. - **`cargo test-mvp`**\ - Run the WebAssembly MVP (1.0) test suite + 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` + +- **`cargo test-2`**\ + Run the full WebAssembly test suite (2.0) + +- **`cargo test-wast `**\ + Run a single WAST test file. e.g. `cargo test-wast ./examples/wast/i32.wast` - **`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. @@ -22,5 +26,5 @@ - **`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. -- **`cargo workspaces publish --from-git`**\ +- **`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. diff --git a/Cargo.lock b/Cargo.lock index 710ebe2..eb1ab92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,54 +3,48 @@ version = 3 [[package]] -name = "addr2line" -version = "0.21.0" +name = "ahash" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "gimli", + "getrandom", + "once_cell", + "version_check", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "ahash" -version = "0.7.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] -name = "android-tzdata" -version = "0.1.1" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "anstyle" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "argh" @@ -71,7 +65,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.76", ] [[package]] @@ -85,36 +79,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -139,9 +112,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -149,26 +122,37 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +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 = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive 0.6.12", + "ptr_meta", + "simdutf8", +] [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "41502630fe304ce54cbb2f8389e017784dee2b0328147779fcbe43b9db06d35d" dependencies = [ - "bytecheck_derive", + "bytecheck_derive 0.7.0", "ptr_meta", "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", @@ -176,31 +160,27 @@ dependencies = [ ] [[package]] -name = "bytemuck" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" - -[[package]] -name = "byteorder" -version = "1.5.0" +name = "bytecheck_derive" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "eda88c587085bc07dc201ab9df871bd9baa5e07f7754b745e4d7194b43ac1eda" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "bytes" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] -name = "cc" -version = "1.0.83" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" @@ -209,122 +189,131 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.31" +name = "ciborium" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets 0.48.5", + "ciborium-io", + "ciborium-ll", + "serde", ] [[package]] -name = "cmake" -version = "0.1.50" +name = "ciborium-io" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ - "cc", + "ciborium-io", + "half", ] [[package]] -name = "color-eyre" -version = "0.6.2" +name = "clap" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ - "backtrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", + "clap_builder", ] [[package]] -name = "color_quant" -version = "1.1.0" +name = "clap_builder" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstyle", + "clap_lex", +] [[package]] -name = "const-cstr" -version = "0.3.0" +name = "clap_lex" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] -name = "core-foundation" -version = "0.9.4" +name = "cpufeatures" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ - "core-foundation-sys", "libc", ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" +name = "criterion" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] [[package]] -name = "core-graphics" -version = "0.22.3" +name = "criterion-plot" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", + "cast", + "itertools", ] [[package]] -name = "core-graphics-types" -version = "0.1.3" +name = "crossbeam-deque" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "core-text" -version = "19.2.0" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", - "libc", + "crossbeam-utils", ] [[package]] -name = "cpufeatures" -version = "0.2.11" +name = "crossbeam-utils" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" -dependencies = [ - "libc", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] -name = "crc32fast" -version = "1.3.2" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" @@ -347,52 +336,16 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "either" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "dwrote" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -402,111 +355,21 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] -[[package]] -name = "fdeflate" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "flate2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-ord" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" - -[[package]] -name = "font-kit" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "core-foundation", - "core-graphics", - "core-text", - "dirs-next", - "dwrote", - "float-ord", - "freetype", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "freetype" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" -dependencies = [ - "freetype-sys", - "libc", -] - -[[package]] -name = "freetype-sys" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" -dependencies = [ - "cmake", - "libc", - "pkg-config", -] - [[package]] name = "funty" version = "2.0.0" @@ -525,31 +388,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] -[[package]] -name = "gif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - [[package]] name = "globset" version = "0.4.14" @@ -564,63 +411,44 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "half" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "ahash", + "cfg-if", + "crunchy", ] [[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - -[[package]] -name = "humantime" -version = "2.1.0" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] -name = "iana-time-zone" -version = "0.1.58" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "ahash 0.8.11", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "hermit-abi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] -name = "image" -version = "0.24.7" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "jpeg-decoder", - "num-rational", - "num-traits", - "png", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indenter" @@ -629,48 +457,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] -name = "indexmap-nostd" -version = "0.4.0" +name = "indexmap" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", - "rustix", - "windows-sys 0.48.0", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "itoa" -version = "1.0.10" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "jpeg-decoder" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" - -[[package]] -name = "js-sys" -version = "0.3.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "wasm-bindgen", + "either", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "leb128" @@ -680,98 +500,37 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" - -[[package]] -name = "libloading" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.1", - "libc", - "redox_syscall", -] +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "linux-raw-sys" -version = "0.4.12" +name = "libm" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", - "simd-adler32", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -779,94 +538,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "pathfinder_geometry" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" -dependencies = [ - "log", - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_simd" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444332826c70dc47be74a7c6a5fc44e23a7905ad6858d4162b658320455ef93" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "pkg-config" -version = "0.3.27" +name = "oorandom" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] -name = "plotters" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" -dependencies = [ - "chrono", - "font-kit", - "image", - "lazy_static", - "num-traits", - "pathfinder_geometry", - "plotters-backend", - "plotters-bitmap", - "plotters-svg", - "ttf-parser", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" - -[[package]] -name = "plotters-bitmap" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cebbe1f70205299abc69e8b295035bb52a6a70ee35474ad10011f0a4efb8543" -dependencies = [ - "gif", - "image", - "plotters-backend", -] - -[[package]] -name = "plotters-svg" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "png" -version = "0.17.10" +name = "owo-colors" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] name = "pretty_env_logger" @@ -880,9 +561,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -909,9 +590,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -923,30 +604,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "bitflags 1.3.2", + "either", + "rayon-core", ] [[package]] -name = "redox_users" -version = "0.4.4" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -956,9 +637,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -967,29 +648,29 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rend" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ - "bytecheck", + "bytecheck 0.6.12", ] [[package]] name = "rkyv" -version = "0.7.43" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", - "bytecheck", + "bytecheck 0.6.12", "bytes", - "hashbrown", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -1000,9 +681,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.43" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", @@ -1011,9 +692,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.1.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810294a8a4a0853d4118e3b94bb079905f2107c7fe979d8f0faae98765eb6378" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -1022,61 +703,33 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.1.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc144a1273124a67b8c1d7cd19f5695d1878b31569c0512f6086f0f4676604e" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.41", + "syn 2.0.76", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.1.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ccd4875431253d6bb54b804bcff4369cbde9bae33defde25fdf6c2ef91d40" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ "globset", "sha2", "walkdir", ] -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1095,37 +748,38 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.41", + "syn 2.0.76", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1141,12 +795,6 @@ dependencies = [ "digest", ] -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - [[package]] name = "simdutf8" version = "0.1.4" @@ -1166,9 +814,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.41" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -1183,38 +831,28 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] -name = "thiserror" -version = "1.0.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.51" +name = "tinytemplate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.41", + "serde", + "serde_json", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1227,12 +865,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tinywasm" -version = "0.0.5" +version = "0.8.0" dependencies = [ + "criterion", "eyre", + "libm", "log", "owo-colors", - "plotters", + "pretty_env_logger", "serde", "serde_json", "tinywasm-parser", @@ -1243,10 +883,10 @@ dependencies = [ [[package]] name = "tinywasm-cli" -version = "0.0.5" +version = "0.8.0" dependencies = [ "argh", - "color-eyre", + "eyre", "log", "pretty_env_logger", "tinywasm", @@ -1255,27 +895,32 @@ dependencies = [ [[package]] name = "tinywasm-parser" -version = "0.0.5" +version = "0.8.0" dependencies = [ "log", "tinywasm-types", - "wasmparser-nostd", + "wasmparser", +] + +[[package]] +name = "tinywasm-root" +version = "0.0.0" +dependencies = [ + "eyre", + "pretty_env_logger", + "tinywasm", + "wat", ] [[package]] name = "tinywasm-types" -version = "0.0.5" +version = "0.8.0" dependencies = [ + "bytecheck 0.7.0", "log", "rkyv", ] -[[package]] -name = "ttf-parser" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" - [[package]] name = "typenum" version = "1.17.0" @@ -1290,27 +935,27 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "uuid" -version = "1.6.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1322,91 +967,42 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.41", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.41", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" - [[package]] name = "wasm-encoder" -version = "0.38.1" +version = "0.216.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" +checksum = "04c23aebea22c8a75833ae08ed31ccc020835b12a41999e58c31464271b94a88" dependencies = [ "leb128", ] [[package]] name = "wasm-testsuite" -version = "0.2.0" +version = "0.5.0" dependencies = [ "rust-embed", ] [[package]] -name = "wasmparser-nostd" -version = "0.100.1" +name = "wasmparser" +version = "0.216.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" +checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" dependencies = [ - "indexmap-nostd", + "ahash 0.8.11", + "bitflags", + "hashbrown 0.14.5", + "indexmap", + "semver", ] [[package]] name = "wast" -version = "69.0.1" +version = "216.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ee37317321afde358e4d7593745942c48d6d17e0e6e943704de9bbee121e7a" +checksum = "f7eb1f2eecd913fdde0dc6c3439d0f24530a98ac6db6cb3d14d92a5328554a08" dependencies = [ + "bumpalo", "leb128", "memchr", "unicode-width", @@ -1414,68 +1010,21 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "winapi" -version = "0.3.9" +name = "wat" +version = "1.216.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "ac0409090fb5154f95fb5ba3235675fd9e579e731524d63b6a2f653e1280c82a" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "wast", ] -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-targets 0.48.5", + "windows-sys 0.59.0", ] [[package]] @@ -1484,149 +1033,107 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_gnu" -version = "0.52.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" - -[[package]] -name = "wio" -version = "0.2.2" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "winapi", + "tap", ] [[package]] -name = "wyz" -version = "0.5.1" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "tap", + "zerocopy-derive", ] [[package]] -name = "yeslogic-fontconfig-sys" -version = "3.2.0" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", + "proc-macro2", + "quote", + "syn 2.0.76", ] diff --git a/Cargo.toml b/Cargo.toml index 067fca2..81d9533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,53 @@ [workspace] members=["crates/*"] -default-members=["crates/cli"] +default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" +[workspace.dependencies] +wast="216" +wat="1.216" +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.0.5" +version="0.8.0" +rust-version="1.80" edition="2021" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] repository="https://github.com/explodingcamera/tinywasm" + +[package] +name="tinywasm-root" +publish=false +edition.workspace=true +rust-version.workspace=true + +[[example]] +name="wasm-rust" +test=false + +[dev-dependencies] +wat={workspace=true} +eyre={workspace=true} +pretty_env_logger={workspace=true} +tinywasm={path="crates/tinywasm"} + +[profile.bench] +opt-level=3 +lto="thin" +codegen-units=1 +debug=true + +[profile.profiling] +inherits="release" +debug=true + +[profile.wasm] +opt-level=3 +lto="thin" +codegen-units=1 +panic="abort" +inherits="release" diff --git a/README.md b/README.md index e272fff..2d897ee 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,55 @@
- +

TinyWasm

- A tiny WebAssembly Runtime written in Rust + A tiny WebAssembly Runtime written in safe Rust

[![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg)](./LICENSE-APACHE) -# 🚧 Status +## Why TinyWasm? -> [!WARNING] -> This project is still in development and is not ready for use. +- **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). -I'm currently working on supporting the WebAssembly MVP (1.0) specification. You can see the current status in the graph below. The goal is to support all the features of the MVP specification and then move on to the next version. +## 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. -## Features +## Supported Proposals + +**Legend**\ +🌑 -- not available\ +🚧 -- in development / partialy 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 | + +## Usage + +See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information on how to use TinyWasm. +For testing purposes, you can also use the `tinywasm-cli` tool: + +```sh +$ cargo install tinywasm-cli +$ tinywasm-cli --help +``` + +## Feature Flags - **`std`**\ Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. @@ -31,22 +57,27 @@ I'm currently working on supporting the WebAssembly MVP (1.0) specification. You Enables logging using the `log` crate. This is enabled by default. - **`parser`**\ Enables the `tinywasm-parser` crate. This is enabled by default. +- **`archive`**\ + Enables pre-parsing of archives. This is enabled by default. + +With all these features disabled, TinyWasm only depends on `core`, `alloc` ,and `libm` and can be used in `no_std` environments. +Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on wasm32 targets. + +## Inspiration + +Big thanks to the authors of the following projects, which have inspired and influenced TinyWasm: -# 🎯 Goals +- [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 +- [wain](https://github.com/rhysd/wain) - a zero-dependency WebAssembly interpreter written in Rust -- Interpreted Runtime (no JIT) -- Self-hosted (can run itself compiled to WebAssembly) -- No unsafe code -- Works on `no_std` (with `alloc` the feature and nightly compiler) -- Fully support WebAssembly MVP (1.0) -- Low Memory Usage (less than 10kb) -- Fast Startup Time -- Preemptive multitasking support +I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly Runtimes. -# 📄 License +## 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 is because the data is generated from the [WebAssembly Specification](https://github.com/WebAssembly/spec/tree/main/test) and is only used for testing purposes and is not included in the final binary. +**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 866f8cb..51a669d 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace=true license.workspace=true authors.workspace=true repository.workspace=true +rust-version.workspace=true [[bin]] name="tinywasm-cli" @@ -14,12 +15,12 @@ path="src/bin.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tinywasm={version="0.0.5-alpha.0", path="../tinywasm", features=["std", "parser"]} +tinywasm={version="0.8.0-alpha.0", path="../tinywasm", features=["std", "parser"]} argh="0.1" -color-eyre={version="0.6", default-features=false} -log="0.4" -pretty_env_logger="0.5" -wast={version="69.0", optional=true} +eyre={workspace=true} +log={workspace=true} +pretty_env_logger={workspace=true} +wast={workspace=true, optional=true} [features] default=["wat"] diff --git a/crates/cli/README.md b/crates/cli/README.md new file mode 100644 index 0000000..70b2cff --- /dev/null +++ b/crates/cli/README.md @@ -0,0 +1,11 @@ +# `tinywasm-cli` + +The `tinywasm-cli` crate contains the command line interface for the `tinywasm` project. See [`tinywasm`](https://crates.io/crates/tinywasm) for more information. +It is recommended to use the library directly instead of the CLI. + +## Usage + +```bash +$ cargo install tinywasm-cli +$ tinywasm-cli --help +``` diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 379ecf8..96c3605 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -5,7 +5,7 @@ use tinywasm::types::WasmValue; pub struct WasmArg(WasmValue); pub fn to_wasm_args(args: Vec) -> Vec { - args.into_iter().map(|a| a.into()).collect() + args.into_iter().map(Into::into).collect() } impl From for WasmValue { @@ -17,30 +17,15 @@ impl From for WasmValue { impl FromStr for WasmArg { type Err = String; fn from_str(s: &str) -> std::prelude::v1::Result { - let [ty, val]: [&str; 2] = s - .split(':') - .collect::>() - .try_into() - .map_err(|e| format!("invalid arguments: {:?}", e))?; + let [ty, val]: [&str; 2] = + s.split(':').collect::>().try_into().map_err(|e| format!("invalid arguments: {e:?}"))?; let arg: WasmValue = match ty { - "i32" => val - .parse::() - .map_err(|e| format!("invalid argument value for i32: {e:?}"))? - .into(), - "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(), - t => return Err(format!("Invalid arg type: {}", t)), + "i32" => val.parse::().map_err(|e| format!("invalid argument value for i32: {e:?}"))?.into(), + "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(), + t => return Err(format!("Invalid arg type: {t}")), }; Ok(WasmArg(arg)) diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 2712751..6efbba1 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -2,9 +2,9 @@ use std::str::FromStr; use argh::FromArgs; use args::WasmArg; -use color_eyre::eyre::Result; +use eyre::Result; use log::{debug, info}; -use tinywasm::{self, types::WasmValue, Module}; +use tinywasm::{types::WasmValue, Module}; use crate::args::to_wasm_args; mod args; @@ -14,7 +14,7 @@ mod util; mod wat; #[derive(FromArgs)] -/// TinyWasm CLI +/// `TinyWasm` CLI struct TinyWasmCli { #[argh(subcommand)] nested: TinyWasmSubcommand, @@ -40,7 +40,7 @@ impl FromStr for Engine { fn from_str(s: &str) -> Result { match s { "main" => Ok(Self::Main), - _ => Err(format!("unknown engine: {}", s)), + _ => Err(format!("unknown engine: {s}")), } } } @@ -67,8 +67,6 @@ struct Run { } fn main() -> Result<()> { - color_eyre::install()?; - let args: TinyWasmCli = argh::from_env(); let level = match args.log_level.as_str() { "trace" => log::LevelFilter::Trace, @@ -80,16 +78,10 @@ fn main() -> Result<()> { }; pretty_env_logger::formatted_builder().filter_level(level).init(); - let cwd = std::env::current_dir()?; match args.nested { - TinyWasmSubcommand::Run(Run { - wasm_file, - engine, - args, - func, - }) => { + TinyWasmSubcommand::Run(Run { wasm_file, engine, args, func }) => { debug!("args: {:?}", args); let path = cwd.join(wasm_file.clone()); @@ -101,24 +93,24 @@ fn main() -> Result<()> { tinywasm::Module::parse_bytes(&wasm)? } #[cfg(not(feature = "wat"))] - true => return Err(color_eyre::eyre::eyre!("wat support is not enabled in this build")), + true => return Err(eyre::eyre!("wat support is not enabled in this build")), false => tinywasm::Module::parse_file(path)?, }; match engine { - Engine::Main => run(module, func, to_wasm_args(args)), + Engine::Main => run(module, func, &to_wasm_args(args)), } } } } -fn run(module: Module, func: Option, args: Vec) -> Result<()> { +fn run(module: Module, func: Option, args: &[WasmValue]) -> Result<()> { let mut store = tinywasm::Store::default(); - let instance = module.instantiate(&mut store)?; + let instance = module.instantiate(&mut store, None)?; if let Some(func) = func { - let func = instance.get_func(&store, &func)?; - let res = func.call(&mut store, &args)?; + let func = instance.exported_func_untyped(&store, &func)?; + let res = func.call(&mut store, args)?; info!("{res:?}"); } diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index bbdf4ac..efb0817 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -6,15 +6,15 @@ edition.workspace=true license.workspace=true authors.workspace=true repository.workspace=true +rust-version.workspace=true [dependencies] -# fork of wasmparser with no_std support, see https://github.com/bytecodealliance/wasmtime/issues/3495 -# TODO: create dependency free parser -wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} -log={version="0.4", optional=true} -tinywasm-types={version="0.0.5-alpha.0", path="../types"} +wasmparser={version="0.216", default-features=false, features=["validate"]} +log={workspace=true, optional=true} +tinywasm-types={version="0.8.0-alpha.0", path="../types", default-features=false} [features] default=["std", "logging"] logging=["log"] -std=[] +std=["tinywasm-types/std", "wasmparser/std"] +nightly=[] diff --git a/crates/parser/README.md b/crates/parser/README.md index 6cf2234..563cd6b 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -1,7 +1,7 @@ # `tinywasm-parser` -This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. It is based on -[`wasmparser_nostd`](https://crates.io/crates/wasmparser_nostd) and used by [`tinywasm`](https://crates.io/crates/tinywasm). +This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. +It uses [my fork](https://crates.io/crates/tinywasm-wasmparser) of the [`wasmparser`](https://crates.io/crates/wasmparser) crate that has been modified to be compatible with `no_std` environments. ## Features @@ -11,11 +11,11 @@ This crate provides a parser that can parse WebAssembly modules into a TinyWasm ## Usage ```rust -use tinywasm_parser::{Parser, TinyWasmModule}; +use tinywasm_parser::Parser; let bytes = include_bytes!("./file.wasm"); let parser = Parser::new(); -let module: TinyWasmModule = parser.parse_module_bytes(bytes).unwrap(); -let mudule: TinyWasmModule = parser.parse_module_file("path/to/file.wasm").unwrap(); -let module: TinyWasmModule = parser.parse_module_stream(&mut stream).unwrap(); +let module = parser.parse_module_bytes(bytes).unwrap(); +let mudule = parser.parse_module_file("path/to/file.wasm").unwrap(); +let module = parser.parse_module_stream(&mut stream).unwrap(); ``` diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index fb48321..10607a1 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -1,474 +1,272 @@ +use crate::Result; +use crate::{module::Code, visit::process_operators_and_validate}; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; -use log::info; -use tinywasm_types::{ - BlockArgs, ConstInstruction, Export, ExternalKind, FuncType, Global, Instruction, MemArg, MemoryArch, MemoryType, - TableType, ValType, -}; -use wasmparser::{FuncValidator, ValidatorResources}; +use tinywasm_types::*; +use wasmparser::{FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources}; -use crate::{module::CodeSection, Result}; +pub(crate) fn convert_module_elements<'a, T: IntoIterator>>>( + elements: T, +) -> Result> { + elements.into_iter().map(|element| convert_module_element(element?)).collect::>>() +} + +pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result { + let kind = match element.kind { + wasmparser::ElementKind::Active { table_index, offset_expr } => tinywasm_types::ElementKind::Active { + table: table_index.unwrap_or(0), + offset: process_const_operators(offset_expr.get_operators_reader())?, + }, + wasmparser::ElementKind::Passive => tinywasm_types::ElementKind::Passive, + wasmparser::ElementKind::Declared => tinywasm_types::ElementKind::Declared, + }; + + match element.items { + wasmparser::ElementItems::Functions(funcs) => { + let items = funcs + .into_iter() + .map(|func| Ok(ElementItem::Func(func?))) + .collect::>>()? + .into_boxed_slice(); + + Ok(tinywasm_types::Element { kind, items, ty: ValType::RefFunc, range: element.range }) + } + + wasmparser::ElementItems::Expressions(ty, exprs) => { + let items = exprs + .into_iter() + .map(|expr| Ok(ElementItem::Expr(process_const_operators(expr?.get_operators_reader())?))) + .collect::>>()? + .into_boxed_slice(); + + Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(ty), range: element.range }) + } + } +} + +pub(crate) fn convert_module_data_sections<'a, T: IntoIterator>>>( + data_sections: T, +) -> Result> { + data_sections.into_iter().map(|data| convert_module_data(data?)).collect::>>() +} + +pub(crate) fn convert_module_data(data: wasmparser::Data<'_>) -> Result { + Ok(tinywasm_types::Data { + data: data.data.to_vec().into_boxed_slice(), + range: data.range, + kind: match data.kind { + wasmparser::DataKind::Active { memory_index, offset_expr } => { + let offset = process_const_operators(offset_expr.get_operators_reader())?; + tinywasm_types::DataKind::Active { mem: memory_index, offset } + } + wasmparser::DataKind::Passive => tinywasm_types::DataKind::Passive, + }, + }) +} + +pub(crate) fn convert_module_imports<'a, T: IntoIterator>>>( + imports: T, +) -> Result> { + imports.into_iter().map(|import| convert_module_import(import?)).collect::>>() +} + +pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result { + Ok(Import { + module: import.module.to_string().into_boxed_str(), + name: import.name.to_string().into_boxed_str(), + kind: match import.ty { + wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), + wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { + element_type: convert_reftype(ty.element_type), + size_initial: ty.initial.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial)) + })?, + size_max: match ty.maximum { + Some(max) => Some(max.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")) + })?), + None => None, + }, + }), + wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), + wasmparser::TypeRef::Global(ty) => { + ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) + } + wasmparser::TypeRef::Tag(ty) => { + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))) + } + }, + }) +} pub(crate) fn convert_module_memories>>( memory_types: T, ) -> Result> { - let memory_type = memory_types - .into_iter() - .map(|memory| { - let memory = memory?; - Ok(MemoryType { - arch: match memory.memory64 { - true => MemoryArch::I64, - false => MemoryArch::I32, - }, - page_count_initial: memory.initial, - page_count_max: memory.maximum, - }) - }) - .collect::>>()?; + memory_types.into_iter().map(|memory| Ok(convert_module_memory(memory?))).collect::>>() +} - Ok(memory_type) +pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> MemoryType { + MemoryType { + arch: if memory.memory64 { MemoryArch::I64 } else { MemoryArch::I32 }, + page_count_initial: memory.initial, + page_count_max: memory.maximum, + } } -pub(crate) fn convert_module_tables>>( +pub(crate) fn convert_module_tables<'a, T: IntoIterator>>>( table_types: T, ) -> Result> { - let table_type = table_types - .into_iter() - .map(|table| { - let table = table?; - let ty = convert_valtype(&table.element_type); - Ok(TableType { - element_type: ty, - size_initial: table.initial, - size_max: table.maximum, - }) - }) - .collect::>>()?; + table_types.into_iter().map(|table| convert_module_table(table?)).collect::>>() +} - Ok(table_type) +pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result { + let size_initial = table.ty.initial.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", table.ty.initial)) + })?; + + let size_max = match table.ty.maximum { + Some(max) => Some( + max.try_into() + .map_err(|_| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")))?, + ), + None => None, + }; + + Ok(TableType { element_type: convert_reftype(table.ty.element_type), size_initial, size_max }) } -pub(crate) fn convert_module_globals<'a, T: IntoIterator>>>( - globals: T, +pub(crate) fn convert_module_globals( + globals: wasmparser::SectionLimited<'_, wasmparser::Global<'_>>, ) -> Result> { let globals = globals .into_iter() .map(|global| { let global = global?; let ty = convert_valtype(&global.ty.content_type); - - let ops = global - .init_expr - .get_operators_reader() - .into_iter() - .collect::>>()?; - - // In practice, the len can never be something other than 2, - // but we'll keep this here since it's part of the spec - // Invalid modules will be rejected by the validator anyway (there are also tests for this in the testsuite) - assert!(ops.len() >= 2); - assert!(matches!(ops[ops.len() - 1], wasmparser::Operator::End)); - - Ok(Global { - ty, - init: process_const_operator(ops[ops.len() - 2].clone())?, - mutable: global.ty.mutable, - }) + let ops = global.init_expr.get_operators_reader(); + Ok(Global { init: process_const_operators(ops)?, ty: GlobalType { mutable: global.ty.mutable, ty } }) }) .collect::>>()?; Ok(globals) } -pub(crate) fn convert_module_export(export: wasmparser::Export) -> Result { +pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result { let kind = match export.kind { wasmparser::ExternalKind::Func => ExternalKind::Func, wasmparser::ExternalKind::Table => ExternalKind::Table, wasmparser::ExternalKind::Memory => 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))) } }; - Ok(Export { - index: export.index, - name: Box::from(export.name), - kind, - }) + Ok(Export { index: export.index, name: Box::from(export.name), kind }) } pub(crate) fn convert_module_code( - func: wasmparser::FunctionBody, + func: wasmparser::FunctionBody<'_>, mut validator: FuncValidator, -) -> Result { +) -> Result<(Code, FuncValidatorAllocations)> { let locals_reader = func.get_locals_reader()?; let count = locals_reader.get_count(); let pos = locals_reader.original_position(); - let mut locals = Vec::with_capacity(count as usize); + + // maps a local's address to the index in the type's locals array + let mut local_addr_map = Vec::with_capacity(count as usize); + let mut local_counts = ValueCounts::default(); for (i, local) in locals_reader.into_iter().enumerate() { let local = local?; validator.define_locals(pos + i, local.0, local.1)?; - for _ in 0..local.0 { - locals.push(convert_valtype(&local.1)); - } } - let body_reader = func.get_operators_reader()?; - let body = process_operators(body_reader.original_position(), body_reader.into_iter(), validator)?; + for i in 0..validator.len_locals() { + match validator.get_local_type(i) { + Some(wasmparser::ValType::I32 | wasmparser::ValType::F32) => { + local_addr_map.push(local_counts.c32); + local_counts.c32 += 1; + } + Some(wasmparser::ValType::I64 | wasmparser::ValType::F64) => { + local_addr_map.push(local_counts.c64); + local_counts.c64 += 1; + } + Some(wasmparser::ValType::V128) => { + local_addr_map.push(local_counts.c128); + local_counts.c128 += 1; + } + Some(wasmparser::ValType::Ref(_)) => { + local_addr_map.push(local_counts.cref); + local_counts.cref += 1; + } + None => return Err(crate::ParseError::UnsupportedOperator("Unknown local type".to_string())), + } + } - Ok(CodeSection { - locals: locals.into_boxed_slice(), - body, - }) + let (body, allocations) = process_operators_and_validate(validator, func, local_addr_map)?; + Ok(((body, local_counts), allocations)) } -pub(crate) fn convert_module_type(ty: wasmparser::Type) -> Result { - let wasmparser::Type::Func(ty) = ty; - let params = ty - .params() - .iter() - .map(|p| Ok(convert_valtype(p))) - .collect::>>()? - .into_boxed_slice(); - - let results = ty - .results() - .iter() - .map(|p| Ok(convert_valtype(p))) - .collect::>>()? - .into_boxed_slice(); +pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { + let mut types = ty.types(); - Ok(FuncType { params, results }) -} + if types.len() != 1 { + return Err(crate::ParseError::UnsupportedOperator( + "Expected exactly one type in the type section".to_string(), + )); + } -pub(crate) fn convert_blocktype(blocktype: wasmparser::BlockType) -> BlockArgs { - use wasmparser::BlockType::*; - match blocktype { - Empty => BlockArgs::Empty, + let ty = types.next().unwrap().unwrap_func(); + let params = ty.params().iter().map(convert_valtype).collect::>().into_boxed_slice(); + let results = ty.results().iter().map(convert_valtype).collect::>().into_boxed_slice(); - // We should maybe have all this in a single variant for our custom bytecode + Ok(FuncType { params, results }) +} - // TODO: maybe solve this differently so we can support 128-bit values - // without having to increase the size of the WasmValue enum - Type(ty) => BlockArgs::Type(convert_valtype(&ty)), - FuncType(ty) => BlockArgs::FuncType(ty), +pub(crate) fn convert_reftype(reftype: wasmparser::RefType) -> ValType { + match reftype { + _ if reftype.is_func_ref() => ValType::RefFunc, + _ if reftype.is_extern_ref() => ValType::RefExtern, + _ => unimplemented!("Unsupported reference type: {:?}, {:?}", reftype, reftype.heap_type()), } } pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> ValType { - use wasmparser::ValType::*; match valtype { - I32 => ValType::I32, - I64 => ValType::I64, - F32 => ValType::F32, - F64 => ValType::F64, - V128 => ValType::V128, - FuncRef => ValType::FuncRef, - ExternRef => ValType::ExternRef, + wasmparser::ValType::I32 => ValType::I32, + wasmparser::ValType::I64 => ValType::I64, + wasmparser::ValType::F32 => ValType::F32, + wasmparser::ValType::F64 => ValType::F64, + wasmparser::ValType::V128 => ValType::V128, + wasmparser::ValType::Ref(r) => convert_reftype(*r), } } -pub(crate) fn convert_memarg(memarg: wasmparser::MemArg) -> MemArg { - MemArg { - offset: memarg.offset, - align: memarg.align, +pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result { + let ops = ops.into_iter().collect::>>()?; + // In practice, the len can never be something other than 2, + // but we'll keep this here since it's part of the spec + // Invalid modules will be rejected by the validator anyway (there are also tests for this in the testsuite) + assert!(ops.len() >= 2); + assert!(matches!(ops[ops.len() - 1], wasmparser::Operator::End)); + + match &ops[ops.len() - 2] { + wasmparser::Operator::RefNull { hty } => Ok(ConstInstruction::RefNull(convert_heaptype(*hty))), + wasmparser::Operator::RefFunc { function_index } => Ok(ConstInstruction::RefFunc(*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::GlobalGet { global_index } => Ok(ConstInstruction::GlobalGet(*global_index)), + op => Err(crate::ParseError::UnsupportedOperator(format!("Unsupported const instruction: {op:?}"))), } } -pub fn process_const_operator(op: wasmparser::Operator) -> Result { - match op { - 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()))), // TODO: check if this is correct - wasmparser::Operator::F64Const { value } => Ok(ConstInstruction::F64Const(f64::from_bits(value.bits()))), // TODO: check if this is correct - wasmparser::Operator::GlobalGet { global_index } => Ok(ConstInstruction::GlobalGet(global_index)), - op => Err(crate::ParseError::UnsupportedOperator(format!( - "Unsupported instruction: {:?}", - op - ))), - } -} - -pub fn process_operators<'a>( - mut offset: usize, - ops: impl Iterator, wasmparser::BinaryReaderError>>, - mut validator: FuncValidator, -) -> Result> { - let mut instructions = Vec::new(); - let mut labels_ptrs = Vec::new(); // indexes into the instructions array - - for op in ops { - info!("op: {:?}", op); - - let op = op?; - validator.op(offset, &op)?; - offset += 1; - - use wasmparser::Operator::*; - let res = match op { - BrTable { targets } => { - let def = targets.default(); - let targets = targets - .targets() - .collect::, wasmparser::BinaryReaderError>>()?; - instructions.push(Instruction::BrTable(def, targets.len())); - instructions.extend(targets.into_iter().map(Instruction::BrLabel)); - continue; - } - Unreachable => Instruction::Unreachable, - Nop => Instruction::Nop, - Block { blockty } => { - labels_ptrs.push(instructions.len()); - Instruction::Block(convert_blocktype(blockty), 0) - } - Loop { blockty } => { - labels_ptrs.push(instructions.len()); - Instruction::Loop(convert_blocktype(blockty), 0) - } - If { blockty } => { - labels_ptrs.push(instructions.len()); - Instruction::If(convert_blocktype(blockty), None, 0) - } - Else => { - labels_ptrs.push(instructions.len()); - Instruction::Else(0) - } - End => { - if let Some(label_pointer) = labels_ptrs.pop() { - info!("ending block: {:?}", instructions[label_pointer]); - - let current_instr_ptr = instructions.len(); - - // last_label_pointer is Some if we're ending a block - match instructions[label_pointer] { - Instruction::Else(ref mut else_instr_end_offset) => { - *else_instr_end_offset = current_instr_ptr - label_pointer; - - // since we're ending an else block, we need to end the if block as well - let if_label_pointer = labels_ptrs.pop().ok_or(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - ))?; - - let if_instruction = &mut instructions[if_label_pointer]; - let Instruction::If(_, ref mut else_offset, ref mut end_offset) = if_instruction else { - return Err(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - )); - }; - - *else_offset = Some(label_pointer - if_label_pointer); - *end_offset = current_instr_ptr - if_label_pointer; - } - Instruction::Block(_, ref mut end_offset) - | Instruction::Loop(_, ref mut end_offset) - | Instruction::If(_, _, ref mut end_offset) => { - *end_offset = current_instr_ptr - label_pointer; - } - _ => { - return Err(crate::ParseError::UnsupportedOperator( - "Expected to end a block, but the last label was not a block".to_string(), - )) - } - } - - Instruction::EndBlockFrame - } else { - // last_label_pointer is None if we're ending the function - Instruction::EndFunc - } - } - - Br { relative_depth } => Instruction::Br(relative_depth), - BrIf { relative_depth } => Instruction::BrIf(relative_depth), - Return => Instruction::Return, - Call { function_index } => Instruction::Call(function_index), - CallIndirect { - type_index, - table_index, - .. - } => Instruction::CallIndirect(type_index, table_index), - Drop => Instruction::Drop, - Select => Instruction::Select, - LocalGet { local_index } => Instruction::LocalGet(local_index), - LocalSet { local_index } => Instruction::LocalSet(local_index), - LocalTee { local_index } => Instruction::LocalTee(local_index), - GlobalGet { global_index } => Instruction::GlobalGet(global_index), - GlobalSet { global_index } => Instruction::GlobalSet(global_index), - MemorySize { .. } => Instruction::MemorySize, - MemoryGrow { .. } => Instruction::MemoryGrow, - I32Const { value } => Instruction::I32Const(value), - I64Const { value } => Instruction::I64Const(value), - F32Const { value } => Instruction::F32Const(f32::from_bits(value.bits())), // TODO: check if this is correct - F64Const { value } => Instruction::F64Const(f64::from_bits(value.bits())), // TODO: check if this is correct - I32Load { memarg } => Instruction::I32Load(convert_memarg(memarg)), - I64Load { memarg } => Instruction::I64Load(convert_memarg(memarg)), - F32Load { memarg } => Instruction::F32Load(convert_memarg(memarg)), - F64Load { memarg } => Instruction::F64Load(convert_memarg(memarg)), - I32Load8S { memarg } => Instruction::I32Load8S(convert_memarg(memarg)), - I32Load8U { memarg } => Instruction::I32Load8U(convert_memarg(memarg)), - I32Load16S { memarg } => Instruction::I32Load16S(convert_memarg(memarg)), - I32Load16U { memarg } => Instruction::I32Load16U(convert_memarg(memarg)), - I64Load8S { memarg } => Instruction::I64Load8S(convert_memarg(memarg)), - I64Load8U { memarg } => Instruction::I64Load8U(convert_memarg(memarg)), - I64Load16S { memarg } => Instruction::I64Load16S(convert_memarg(memarg)), - I64Load16U { memarg } => Instruction::I64Load16U(convert_memarg(memarg)), - I64Load32S { memarg } => Instruction::I64Load32S(convert_memarg(memarg)), - I64Load32U { memarg } => Instruction::I64Load32U(convert_memarg(memarg)), - I32Store { memarg } => Instruction::I32Store(convert_memarg(memarg)), - I64Store { memarg } => Instruction::I64Store(convert_memarg(memarg)), - F32Store { memarg } => Instruction::F32Store(convert_memarg(memarg)), - F64Store { memarg } => Instruction::F64Store(convert_memarg(memarg)), - I32Store8 { memarg } => Instruction::I32Store8(convert_memarg(memarg)), - I32Store16 { memarg } => Instruction::I32Store16(convert_memarg(memarg)), - I64Store8 { memarg } => Instruction::I64Store8(convert_memarg(memarg)), - I64Store16 { memarg } => Instruction::I64Store16(convert_memarg(memarg)), - I64Store32 { memarg } => Instruction::I64Store32(convert_memarg(memarg)), - I32Eqz => Instruction::I32Eqz, - I32Eq => Instruction::I32Eq, - I32Ne => Instruction::I32Ne, - I32LtS => Instruction::I32LtS, - I32LtU => Instruction::I32LtU, - I32GtS => Instruction::I32GtS, - I32GtU => Instruction::I32GtU, - I32LeS => Instruction::I32LeS, - I32LeU => Instruction::I32LeU, - I32GeS => Instruction::I32GeS, - I32GeU => Instruction::I32GeU, - I64Eqz => Instruction::I64Eqz, - I64Eq => Instruction::I64Eq, - I64Ne => Instruction::I64Ne, - I64LtS => Instruction::I64LtS, - I64LtU => Instruction::I64LtU, - I64GtS => Instruction::I64GtS, - I64GtU => Instruction::I64GtU, - I64LeS => Instruction::I64LeS, - I64LeU => Instruction::I64LeU, - I64GeS => Instruction::I64GeS, - I64GeU => Instruction::I64GeU, - F32Eq => Instruction::F32Eq, - F32Ne => Instruction::F32Ne, - F32Lt => Instruction::F32Lt, - F32Gt => Instruction::F32Gt, - F32Le => Instruction::F32Le, - F32Ge => Instruction::F32Ge, - F64Eq => Instruction::F64Eq, - F64Ne => Instruction::F64Ne, - F64Lt => Instruction::F64Lt, - F64Gt => Instruction::F64Gt, - F64Le => Instruction::F64Le, - F64Ge => Instruction::F64Ge, - I32Clz => Instruction::I32Clz, - I32Ctz => Instruction::I32Ctz, - I32Popcnt => Instruction::I32Popcnt, - I32Add => Instruction::I32Add, - I32Sub => Instruction::I32Sub, - I32Mul => Instruction::I32Mul, - I32DivS => Instruction::I32DivS, - I32DivU => Instruction::I32DivU, - I32RemS => Instruction::I32RemS, - I32RemU => Instruction::I32RemU, - I32And => Instruction::I32And, - I32Or => Instruction::I32Or, - I32Xor => Instruction::I32Xor, - I32Shl => Instruction::I32Shl, - I32ShrS => Instruction::I32ShrS, - I32ShrU => Instruction::I32ShrU, - I32Rotl => Instruction::I32Rotl, - I32Rotr => Instruction::I32Rotr, - I64Clz => Instruction::I64Clz, - I64Ctz => Instruction::I64Ctz, - I64Popcnt => Instruction::I64Popcnt, - I64Add => Instruction::I64Add, - I64Sub => Instruction::I64Sub, - I64Mul => Instruction::I64Mul, - I64DivS => Instruction::I64DivS, - I64DivU => Instruction::I64DivU, - I64RemS => Instruction::I64RemS, - I64RemU => Instruction::I64RemU, - I64And => Instruction::I64And, - I64Or => Instruction::I64Or, - I64Xor => Instruction::I64Xor, - I64Shl => Instruction::I64Shl, - I64ShrS => Instruction::I64ShrS, - I64ShrU => Instruction::I64ShrU, - I64Rotl => Instruction::I64Rotl, - I64Rotr => Instruction::I64Rotr, - F32Abs => Instruction::F32Abs, - F32Neg => Instruction::F32Neg, - F32Ceil => Instruction::F32Ceil, - F32Floor => Instruction::F32Floor, - F32Trunc => Instruction::F32Trunc, - F32Nearest => Instruction::F32Nearest, - F32Sqrt => Instruction::F32Sqrt, - F32Add => Instruction::F32Add, - F32Sub => Instruction::F32Sub, - F32Mul => Instruction::F32Mul, - F32Div => Instruction::F32Div, - F32Min => Instruction::F32Min, - F32Max => Instruction::F32Max, - F32Copysign => Instruction::F32Copysign, - F64Abs => Instruction::F64Abs, - F64Neg => Instruction::F64Neg, - F64Ceil => Instruction::F64Ceil, - F64Floor => Instruction::F64Floor, - F64Trunc => Instruction::F64Trunc, - F64Nearest => Instruction::F64Nearest, - F64Sqrt => Instruction::F64Sqrt, - F64Add => Instruction::F64Add, - F64Sub => Instruction::F64Sub, - F64Mul => Instruction::F64Mul, - F64Div => Instruction::F64Div, - F64Min => Instruction::F64Min, - F64Max => Instruction::F64Max, - F64Copysign => Instruction::F64Copysign, - I32WrapI64 => Instruction::I32WrapI64, - I32TruncF32S => Instruction::I32TruncF32S, - I32TruncF32U => Instruction::I32TruncF32U, - I32TruncF64S => Instruction::I32TruncF64S, - I32TruncF64U => Instruction::I32TruncF64U, - I64ExtendI32S => Instruction::I64ExtendI32S, - I64ExtendI32U => Instruction::I64ExtendI32U, - I64TruncF32S => Instruction::I64TruncF32S, - I64TruncF32U => Instruction::I64TruncF32U, - I64TruncF64S => Instruction::I64TruncF64S, - I64TruncF64U => Instruction::I64TruncF64U, - F32ConvertI32S => Instruction::F32ConvertI32S, - F32ConvertI32U => Instruction::F32ConvertI32U, - F32ConvertI64S => Instruction::F32ConvertI64S, - F32ConvertI64U => Instruction::F32ConvertI64U, - F32DemoteF64 => Instruction::F32DemoteF64, - F64ConvertI32S => Instruction::F64ConvertI32S, - F64ConvertI32U => Instruction::F64ConvertI32U, - F64ConvertI64S => Instruction::F64ConvertI64S, - F64ConvertI64U => Instruction::F64ConvertI64U, - F64PromoteF32 => Instruction::F64PromoteF32, - I32ReinterpretF32 => Instruction::I32ReinterpretF32, - I64ReinterpretF64 => Instruction::I64ReinterpretF64, - F32ReinterpretI32 => Instruction::F32ReinterpretI32, - F64ReinterpretI64 => Instruction::F64ReinterpretI64, - op => { - return Err(crate::ParseError::UnsupportedOperator(format!( - "Unsupported instruction: {:?}", - op - ))) - } - }; - - instructions.push(res); - } - - if !labels_ptrs.is_empty() { - panic!( - "last_label_pointer should be None after processing all instructions: {:?}", - labels_ptrs - ); +pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> ValType { + match heap { + wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Func } => ValType::RefFunc, + wasmparser::HeapType::Abstract { shared: false, ty: wasmparser::AbstractHeapType::Extern } => { + ValType::RefExtern + } + _ => unimplemented!("Unsupported heap type: {:?}", heap), } - - validator.finish(offset)?; - - Ok(instructions.into_boxed_slice()) } diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs index acacfa0..7bd484f 100644 --- a/crates/parser/src/error.rs +++ b/crates/parser/src/error.rs @@ -4,16 +4,37 @@ use alloc::string::{String, ToString}; use wasmparser::Encoding; #[derive(Debug)] +/// Errors that can occur when parsing a WebAssembly module pub enum ParseError { + /// An invalid type was encountered InvalidType, + /// An unsupported section was encountered UnsupportedSection(String), + /// A duplicate section was encountered DuplicateSection(String), + /// An empty section was encountered EmptySection(String), + /// An unsupported operator was encountered UnsupportedOperator(String), - ParseError { message: String, offset: usize }, + /// An error occurred while parsing the module + ParseError { + /// The error message + message: String, + /// The offset in the module where the error occurred + offset: usize, + }, + /// An invalid encoding was encountered InvalidEncoding(Encoding), - InvalidLocalCount { expected: u32, actual: u32 }, + /// An invalid local count was encountered + InvalidLocalCount { + /// The expected local count + expected: u32, + /// The actual local count + actual: u32, + }, + /// The end of the module was not reached EndNotReached, + /// An unknown error occurred Other(String), } @@ -21,33 +42,30 @@ impl Display for ParseError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::InvalidType => write!(f, "invalid type"), - Self::UnsupportedSection(section) => write!(f, "unsupported section: {}", section), - Self::DuplicateSection(section) => write!(f, "duplicate section: {}", section), - Self::EmptySection(section) => write!(f, "empty section: {}", section), - Self::UnsupportedOperator(operator) => write!(f, "unsupported operator: {}", operator), + Self::UnsupportedSection(section) => write!(f, "unsupported section: {section}"), + Self::DuplicateSection(section) => write!(f, "duplicate section: {section}"), + Self::EmptySection(section) => write!(f, "empty section: {section}"), + Self::UnsupportedOperator(operator) => write!(f, "unsupported operator: {operator}"), Self::ParseError { message, offset } => { - write!(f, "error parsing module: {} at offset {}", message, offset) + write!(f, "error parsing module: {message} at offset {offset}") } - Self::InvalidEncoding(encoding) => write!(f, "invalid encoding: {:?}", encoding), + Self::InvalidEncoding(encoding) => write!(f, "invalid encoding: {encoding:?}"), Self::InvalidLocalCount { expected, actual } => { - write!(f, "invalid local count: expected {}, actual {}", expected, actual) + write!(f, "invalid local count: expected {expected}, actual {actual}") } Self::EndNotReached => write!(f, "end of module not reached"), - Self::Other(message) => write!(f, "unknown error: {}", message), + Self::Other(message) => write!(f, "unknown error: {message}"), } } } -#[cfg(any(feature = "std", all(not(feature = "std"), nightly)))] +#[cfg(any(feature = "std", all(not(feature = "std"), feature = "nightly")))] impl crate::std::error::Error for ParseError {} impl From for ParseError { fn from(value: wasmparser::BinaryReaderError) -> Self { - Self::ParseError { - message: value.message().to_string(), - offset: value.offset(), - } + Self::ParseError { message: value.message().to_string(), offset: value.offset() } } } -pub type Result = core::result::Result; +pub(crate) type Result = core::result::Result; diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 36deb7f..dfe6b0c 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -1,43 +1,92 @@ #![no_std] +#![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)] #![forbid(unsafe_code)] -#![cfg_attr(not(feature = "std"), feature(error_in_core))] +//! See [`tinywasm`](https://docs.rs/tinywasm) for documentation. -mod std; extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + // log for logging (optional). #[cfg(feature = "logging")] -#[allow(clippy::single_component_path_imports)] +#[allow(clippy::single_component_path_imports, unused_imports)] use log; +// noop fallback if logging is disabled. #[cfg(not(feature = "logging"))] -mod log { +#[allow(unused_imports, unused_macros)] +pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); + macro_rules! info ( ($($tt:tt)*) => {{}} ); + macro_rules! error ( ($($tt:tt)*) => {{}} ); pub(crate) use debug; + pub(crate) use error; + pub(crate) use info; } mod conversion; mod error; mod module; -use alloc::vec::Vec; +mod visit; pub use error::*; use module::ModuleReader; -use tinywasm_types::Function; -use wasmparser::Validator; +use wasmparser::{Validator, WasmFeaturesInflated}; pub use tinywasm_types::TinyWasmModule; -#[derive(Default)] +/// A WebAssembly parser +#[derive(Default, Debug)] pub struct Parser {} impl Parser { + /// Create a new parser instance pub fn new() -> Self { Self {} } + fn create_validator() -> Validator { + let features = WasmFeaturesInflated { + bulk_memory: true, + floats: true, + multi_value: true, + mutable_global: true, + reference_types: true, + sign_extension: true, + saturating_float_to_int: true, + function_references: true, + tail_call: true, + multi_memory: true, + + gc_types: true, + 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, + }; + Validator::new_with_features(features.into()) + } + + /// Parse a [`TinyWasmModule`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); - let mut validator = Validator::new(); + let mut validator = Self::create_validator(); let mut reader = ModuleReader::new(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { @@ -48,10 +97,11 @@ impl Parser { return Err(ParseError::EndNotReached); } - reader.try_into() + reader.into_module() } #[cfg(feature = "std")] + /// Parse a [`TinyWasmModule`] from a file. Requires `std` feature. pub fn parse_module_file(&self, path: impl AsRef + Clone) -> Result { use alloc::format; let f = crate::std::fs::File::open(path.clone()) @@ -62,12 +112,13 @@ impl Parser { } #[cfg(feature = "std")] + /// Parse a [`TinyWasmModule`] from a stream. Requires `std` feature. pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { use alloc::format; - let mut validator = Validator::new(); + let mut validator = Self::create_validator(); let mut reader = ModuleReader::new(); - let mut buffer = Vec::new(); + let mut buffer = alloc::vec::Vec::new(); let mut parser = wasmparser::Parser::new(0); let mut eof = false; @@ -78,7 +129,7 @@ impl Parser { buffer.extend((0..hint).map(|_| 0u8)); let read_bytes = stream .read(&mut buffer[len..]) - .map_err(|e| ParseError::Other(format!("Error reading from stream: {}", e)))?; + .map_err(|e| ParseError::Other(format!("Error reading from stream: {e}")))?; buffer.truncate(len + read_bytes); eof = read_bytes == 0; } @@ -86,7 +137,7 @@ impl Parser { reader.process_payload(payload, &mut validator)?; buffer.drain(..consumed); if eof || reader.end_reached { - return reader.try_into(); + return reader.into_module(); } } }; @@ -98,34 +149,6 @@ impl TryFrom for TinyWasmModule { type Error = ParseError; fn try_from(reader: ModuleReader) -> Result { - if !reader.end_reached { - return Err(ParseError::EndNotReached); - } - - let func_types = reader.func_addrs; - let funcs = reader - .code - .into_iter() - .zip(func_types) - .map(|(f, ty)| Function { - instructions: f.body, - locals: f.locals, - ty, - }) - .collect::>(); - - let globals = reader.globals; - let table_types = reader.table_types; - - Ok(TinyWasmModule { - version: reader.version, - start_func: reader.start_func, - func_types: reader.func_types.into_boxed_slice(), - funcs: funcs.into_boxed_slice(), - exports: reader.exports.into_boxed_slice(), - globals: globals.into_boxed_slice(), - table_types: table_types.into_boxed_slice(), - memory_types: reader.memory_types.into_boxed_slice(), - }) + reader.into_module() } } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index ea6aaad..20ed00d 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,60 +1,40 @@ use crate::log::debug; -use alloc::{boxed::Box, format, vec::Vec}; -use core::fmt::Debug; -use tinywasm_types::{Export, FuncType, Global, Instruction, MemoryType, TableType, ValType}; -use wasmparser::{Payload, Validator}; - use crate::{conversion, ParseError, Result}; +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, +}; +use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -#[derive(Debug, Clone, PartialEq)] -pub struct CodeSection { - pub locals: Box<[ValType]>, - pub body: Box<[Instruction]>, -} +pub(crate) type Code = (Box<[Instruction]>, ValueCounts); #[derive(Default)] -pub struct ModuleReader { - pub version: Option, - pub start_func: Option, - - pub func_types: Vec, - pub func_addrs: Vec, - pub exports: Vec, - pub code: Vec, - pub globals: Vec, - pub table_types: Vec, - pub memory_types: Vec, - - // pub element_section: Option>, - // pub data_section: Option>, - // pub import_section: Option>, - pub end_reached: bool, -} +pub(crate) struct ModuleReader { + func_validator_allocations: Option, -impl Debug for ModuleReader { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_struct("ModuleReader") - .field("version", &self.version) - .field("func_types", &self.func_types) - .field("func_addrs", &self.func_addrs) - .field("code", &self.code) - .field("exports", &self.exports) - .field("globals", &self.globals) - .field("table_types", &self.table_types) - .field("memory_types", &self.memory_types) - // .field("element_section", &self.element_section) - // .field("data_section", &self.data_section) - // .field("import_section", &self.import_section) - .finish() - } + pub(crate) version: Option, + pub(crate) start_func: Option, + pub(crate) func_types: Vec, + pub(crate) code_type_addrs: Vec, + pub(crate) exports: Vec, + pub(crate) code: Vec, + pub(crate) globals: Vec, + pub(crate) table_types: Vec, + pub(crate) memory_types: Vec, + pub(crate) imports: Vec, + pub(crate) data: Vec, + pub(crate) elements: Vec, + pub(crate) end_reached: bool, } impl ModuleReader { - pub fn new() -> ModuleReader { + pub(crate) fn new() -> ModuleReader { Self::default() } - pub fn process_payload(&mut self, payload: Payload, validator: &mut Validator) -> Result<()> { + pub(crate) fn process_payload(&mut self, payload: Payload<'_>, validator: &mut Validator) -> Result<()> { use wasmparser::Payload::*; match payload { @@ -67,11 +47,19 @@ impl ModuleReader { } } StartSection { func, range } => { + if self.start_func.is_some() { + return Err(ParseError::DuplicateSection("Start section".into())); + } + debug!("Found start section"); validator.start_section(func, &range)?; self.start_func = Some(func); } TypeSection(reader) => { + if !self.func_types.is_empty() { + return Err(ParseError::DuplicateSection("Type section".into())); + } + debug!("Found type section"); validator.type_section(&reader)?; self.func_types = reader @@ -79,68 +67,97 @@ impl ModuleReader { .map(|t| conversion::convert_module_type(t?)) .collect::>>()?; } - FunctionSection(reader) => { - debug!("Found function section"); - validator.function_section(&reader)?; - self.func_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; - } + GlobalSection(reader) => { + if !self.globals.is_empty() { + return Err(ParseError::DuplicateSection("Global section".into())); + } + debug!("Found global section"); validator.global_section(&reader)?; self.globals = conversion::convert_module_globals(reader)?; } TableSection(reader) => { + if !self.table_types.is_empty() { + return Err(ParseError::DuplicateSection("Table section".into())); + } debug!("Found table section"); validator.table_section(&reader)?; self.table_types = conversion::convert_module_tables(reader)?; } MemorySection(reader) => { + if !self.memory_types.is_empty() { + return Err(ParseError::DuplicateSection("Memory section".into())); + } + debug!("Found memory section"); validator.memory_section(&reader)?; self.memory_types = conversion::convert_module_memories(reader)?; } - ElementSection(_reader) => { - return Err(ParseError::UnsupportedSection("Element section".into())); - // debug!("Found element section"); - // validator.element_section(&reader)?; - // self.element_section = Some(reader); + ElementSection(reader) => { + debug!("Found element section"); + validator.element_section(&reader)?; + self.elements = conversion::convert_module_elements(reader)?; + } + DataSection(reader) => { + if !self.data.is_empty() { + return Err(ParseError::DuplicateSection("Data section".into())); + } + + debug!("Found data section"); + validator.data_section(&reader)?; + self.data = conversion::convert_module_data_sections(reader)?; + } + DataCountSection { count, range } => { + debug!("Found data count section"); + if !self.data.is_empty() { + return Err(ParseError::DuplicateSection("Data count section".into())); + } + validator.data_count_section(count, &range)?; } - DataSection(_reader) => { - return Err(ParseError::UnsupportedSection("Data section".into())); - // debug!("Found data section"); - // validator.data_section(&reader)?; - // self.data_section = Some(reader); + FunctionSection(reader) => { + if !self.code_type_addrs.is_empty() { + return Err(ParseError::DuplicateSection("Function section".into())); + } + + debug!("Found function section"); + validator.function_section(&reader)?; + self.code_type_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; } CodeSectionStart { count, range, .. } => { debug!("Found code section ({} functions)", count); if !self.code.is_empty() { return Err(ParseError::DuplicateSection("Code section".into())); } - + self.code.reserve(count as usize); validator.code_section_start(count, &range)?; } CodeSectionEntry(function) => { debug!("Found code section entry"); let v = validator.code_section_entry(&function)?; - let func_validator = v.into_validator(Default::default()); - - self.code - .push(conversion::convert_module_code(function, func_validator)?); - } - ImportSection(_reader) => { - return Err(ParseError::UnsupportedSection("Import section".into())); + let func_validator = v.into_validator(self.func_validator_allocations.take().unwrap_or_default()); + let (code, allocations) = conversion::convert_module_code(function, func_validator)?; + self.code.push(code); + self.func_validator_allocations = Some(allocations); + } + ImportSection(reader) => { + if !self.imports.is_empty() { + return Err(ParseError::DuplicateSection("Import section".into())); + } - // debug!("Found import section"); - // validator.import_section(&reader)?; - // self.import_section = Some(reader); + debug!("Found import section"); + validator.import_section(&reader)?; + self.imports = conversion::convert_module_imports(reader)?; } ExportSection(reader) => { + if !self.exports.is_empty() { + return Err(ParseError::DuplicateSection("Export section".into())); + } + debug!("Found export section"); validator.export_section(&reader)?; - self.exports = reader - .into_iter() - .map(|e| conversion::convert_module_export(e?)) - .collect::>>()?; + self.exports = + reader.into_iter().map(|e| conversion::convert_module_export(e?)).collect::>>()?; } End(offset) => { debug!("Reached end of module"); @@ -155,19 +172,57 @@ impl ModuleReader { debug!("Found custom section"); debug!("Skipping custom section: {:?}", reader.name()); } - // TagSection(tag) => { - // debug!("Found tag section"); - // validator.tag_section(&tag)?; - // } UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), - section => { - return Err(ParseError::UnsupportedSection(format!( - "Unsupported section: {:?}", - section - ))) - } + section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {section:?}"))), }; Ok(()) } + + #[inline] + pub(crate) fn into_module(self) -> Result { + if !self.end_reached { + return Err(ParseError::EndNotReached); + } + + if self.code_type_addrs.len() != self.code.len() { + return Err(ParseError::Other("Code and code type address count mismatch".to_string())); + } + + let funcs = self + .code + .into_iter() + .zip(self.code_type_addrs) + .map(|((instructions, locals), ty_idx)| { + let mut params = ValueCountsSmall::default(); + 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 } + }) + .collect::>() + .into_boxed_slice(); + + let globals = self.globals; + let table_types = self.table_types; + + Ok(TinyWasmModule { + funcs, + func_types: self.func_types.into_boxed_slice(), + globals: globals.into_boxed_slice(), + table_types: table_types.into_boxed_slice(), + imports: self.imports.into_boxed_slice(), + start_func: self.start_func, + data: self.data.into_boxed_slice(), + exports: self.exports.into_boxed_slice(), + elements: self.elements.into_boxed_slice(), + memory_types: self.memory_types.into_boxed_slice(), + }) + } } diff --git a/crates/parser/src/std.rs b/crates/parser/src/std.rs deleted file mode 100644 index 67152be..0000000 --- a/crates/parser/src/std.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(feature = "std")] -extern crate std; - -#[cfg(feature = "std")] -pub use std::*; diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs new file mode 100644 index 0000000..73cf9b4 --- /dev/null +++ b/crates/parser/src/visit.rs @@ -0,0 +1,630 @@ +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}; + +struct ValidateThenVisit<'a, R: WasmModuleResources>(usize, &'a mut FunctionBuilder); +macro_rules! validate_then_visit { + ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => {$( + fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { + self.1.$visit($($($arg.clone()),*)?); + self.1.validator_visitor(self.0).$visit($($($arg),*)?)?; + Ok(()) + } + )*}; +} + +impl<'a, R: WasmModuleResources> VisitOperator<'a> for ValidateThenVisit<'_, R> { + type Output = Result<()>; + wasmparser::for_each_operator!(validate_then_visit); +} + +pub(crate) fn process_operators_and_validate( + validator: FuncValidator, + body: FunctionBody<'_>, + local_addr_map: Vec, +) -> Result<(Box<[Instruction]>, 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); + + while !reader.eof() { + reader.visit_operator(&mut ValidateThenVisit(reader.original_position(), &mut builder))??; + } + + builder.validator_finish(reader.original_position())?; + if !builder.errors.is_empty() { + return Err(builder.errors.remove(0)); + } + + Ok((builder.instructions.into_boxed_slice(), builder.validator.into_allocations())) +} + +macro_rules! define_operands { + ($($name:ident, $instr:expr),*) => {$( + fn $name(&mut self) -> Self::Output { + self.instructions.push($instr); + } + )*}; +} + +macro_rules! define_primitive_operands { + ($($name:ident, $instr:expr, $ty:ty),*) => {$( + fn $name(&mut self, arg: $ty) -> Self::Output { + self.instructions.push($instr(arg)); + } + )*}; + ($($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 { + ($($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, + }); + } + )*}; +} + +pub(crate) struct FunctionBuilder { + validator: FuncValidator, + instructions: Vec, + label_ptrs: Vec, + local_addr_map: Vec, + errors: Vec, +} + +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 FunctionBuilder { + pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { + Self { + validator, + local_addr_map, + instructions: Vec::with_capacity(instr_capacity), + label_ptrs: Vec::with_capacity(256), + errors: Vec::new(), + } + } + + fn unsupported(&mut self, name: &str) { + self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); + } +} + +macro_rules! impl_visit_operator { + ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { + $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit);)* + }; + + (@@mvp $($rest:tt)* ) => {}; + (@@reference_types $($rest:tt)* ) => {}; + (@@sign_extension $($rest:tt)* ) => {}; + (@@saturating_float_to_int $($rest:tt)* ) => {}; + (@@bulk_memory $($rest:tt)* ) => {}; + (@@tail_call $($rest:tt)* ) => {}; + (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident) => { + #[cold] + fn $visit(&mut self $($(,$arg: $argty)*)?) { + self.unsupported(stringify!($visit)) + } + }; +} + +impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuilder { + type Output = (); + wasmparser::for_each_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 + } + + 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 + } + + 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, + + // 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, + + // 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 + } + + fn visit_return_call(&mut self, function_index: u32) -> Self::Output { + self.instructions.push(Instruction::ReturnCall(function_index)); + } + + fn visit_return_call_indirect(&mut self, type_index: u32, table_index: u32) -> Self::Output { + self.instructions.push(Instruction::ReturnCallIndirect(type_index, table_index)); + } + + fn visit_global_set(&mut self, global_index: u32) -> Self::Output { + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match t { + wasmparser::ValType::I32 => Instruction::GlobalSet32(global_index), + wasmparser::ValType::F32 => Instruction::GlobalSet32(global_index), + wasmparser::ValType::I64 => Instruction::GlobalSet64(global_index), + wasmparser::ValType::F64 => Instruction::GlobalSet64(global_index), + wasmparser::ValType::V128 => Instruction::GlobalSet128(global_index), + wasmparser::ValType::Ref(_) => Instruction::GlobalSetRef(global_index), + }), + _ => self.visit_unreachable(), + } + } + + fn visit_drop(&mut self) -> Self::Output { + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match t { + wasmparser::ValType::I32 => Instruction::Drop32, + wasmparser::ValType::F32 => Instruction::Drop32, + wasmparser::ValType::I64 => Instruction::Drop64, + wasmparser::ValType::F64 => Instruction::Drop64, + wasmparser::ValType::V128 => Instruction::Drop128, + wasmparser::ValType::Ref(_) => Instruction::DropRef, + }), + _ => self.visit_unreachable(), + } + } + 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 { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); + return; + }; + + match self.validator.get_local_type(idx) { + Some(t) => self.instructions.push(match t { + wasmparser::ValType::I32 => Instruction::LocalGet32(resolved_idx), + wasmparser::ValType::F32 => Instruction::LocalGet32(resolved_idx), + wasmparser::ValType::I64 => Instruction::LocalGet64(resolved_idx), + wasmparser::ValType::F64 => Instruction::LocalGet64(resolved_idx), + wasmparser::ValType::V128 => Instruction::LocalGet128(resolved_idx), + wasmparser::ValType::Ref(_) => Instruction::LocalGetRef(resolved_idx), + }), + _ => self.visit_unreachable(), + } + } + + fn visit_local_set(&mut self, idx: u32) -> Self::Output { + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); + return; + }; + + if let Some( + Instruction::LocalGet32(from) + | Instruction::LocalGet64(from) + | Instruction::LocalGet128(from) + | Instruction::LocalGetRef(from), + ) = self.instructions.last() + { + let from = *from; + self.instructions.pop(); + // validation will ensure that the last instruction is the correct local.get + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match t { + wasmparser::ValType::I32 => Instruction::LocalCopy32(from, resolved_idx), + wasmparser::ValType::F32 => Instruction::LocalCopy32(from, resolved_idx), + wasmparser::ValType::I64 => Instruction::LocalCopy64(from, resolved_idx), + wasmparser::ValType::F64 => Instruction::LocalCopy64(from, resolved_idx), + wasmparser::ValType::V128 => Instruction::LocalCopy128(from, resolved_idx), + wasmparser::ValType::Ref(_) => Instruction::LocalCopyRef(from, resolved_idx), + }), + _ => self.visit_unreachable(), + } + return; + } + + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match t { + wasmparser::ValType::I32 => Instruction::LocalSet32(resolved_idx), + wasmparser::ValType::F32 => Instruction::LocalSet32(resolved_idx), + wasmparser::ValType::I64 => Instruction::LocalSet64(resolved_idx), + wasmparser::ValType::F64 => Instruction::LocalSet64(resolved_idx), + wasmparser::ValType::V128 => Instruction::LocalSet128(resolved_idx), + wasmparser::ValType::Ref(_) => Instruction::LocalSetRef(resolved_idx), + }), + _ => self.visit_unreachable(), + } + } + + fn visit_local_tee(&mut self, idx: u32) -> Self::Output { + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); + return; + }; + + match self.validator.get_operand_type(0) { + Some(Some(t)) => self.instructions.push(match t { + wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), + wasmparser::ValType::F32 => Instruction::LocalTee32(resolved_idx), + wasmparser::ValType::I64 => Instruction::LocalTee64(resolved_idx), + wasmparser::ValType::F64 => Instruction::LocalTee64(resolved_idx), + wasmparser::ValType::V128 => Instruction::LocalTee128(resolved_idx), + wasmparser::ValType::Ref(_) => Instruction::LocalTeeRef(resolved_idx), + }), + _ => self.visit_unreachable(), + } + } + + fn visit_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 { + wasmparser::BlockType::Empty => Instruction::Block(0), + wasmparser::BlockType::FuncType(idx) => Instruction::BlockWithFuncType(idx, 0), + wasmparser::BlockType::Type(ty) => Instruction::BlockWithType(convert_valtype(&ty), 0), + }); + } + + fn visit_loop(&mut self, ty: wasmparser::BlockType) -> Self::Output { + self.label_ptrs.push(self.instructions.len()); + self.instructions.push(match ty { + wasmparser::BlockType::Empty => Instruction::Loop(0), + wasmparser::BlockType::FuncType(idx) => Instruction::LoopWithFuncType(idx, 0), + wasmparser::BlockType::Type(ty) => Instruction::LoopWithType(convert_valtype(&ty), 0), + }); + } + + fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { + self.label_ptrs.push(self.instructions.len()); + self.instructions.push(match ty { + wasmparser::BlockType::Empty => Instruction::If(0, 0), + wasmparser::BlockType::FuncType(idx) => Instruction::IfWithFuncType(idx, 0, 0), + wasmparser::BlockType::Type(ty) => Instruction::IfWithType(convert_valtype(&ty), 0, 0), + }); + } + + fn visit_else(&mut self) -> Self::Output { + self.label_ptrs.push(self.instructions.len()); + self.instructions.push(Instruction::Else(0)); + } + + fn visit_end(&mut self) -> Self::Output { + let Some(label_pointer) = self.label_ptrs.pop() else { + return self.instructions.push(Instruction::Return); + }; + + let current_instr_ptr = self.instructions.len(); + match self.instructions.get_mut(label_pointer) { + Some(Instruction::Else(else_instr_end_offset)) => { + *else_instr_end_offset = (current_instr_ptr - label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support if blocks that large"); + + // since we're ending an else block, we need to end the if block as well + let Some(if_label_pointer) = self.label_ptrs.pop() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Expected to end an if block, but there was no if block to end".to_string(), + )); + + return; + }; + + let if_instruction = &mut self.instructions[if_label_pointer]; + + let (else_offset, end_offset) = match if_instruction { + Instruction::If(else_offset, end_offset) + | Instruction::IfWithFuncType(_, else_offset, end_offset) + | Instruction::IfWithType(_, else_offset, end_offset) => (else_offset, end_offset), + _ => { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Expected to end an if block, but the last label was not an if".to_string(), + )); + + return; + } + }; + + *else_offset = (label_pointer - if_label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + + *end_offset = (current_instr_ptr - if_label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + } + Some( + Instruction::Block(end_offset) + | Instruction::BlockWithType(_, end_offset) + | Instruction::BlockWithFuncType(_, end_offset) + | Instruction::Loop(end_offset) + | Instruction::LoopWithFuncType(_, end_offset) + | Instruction::LoopWithType(_, end_offset) + | Instruction::If(_, end_offset) + | Instruction::IfWithFuncType(_, _, end_offset) + | Instruction::IfWithType(_, _, end_offset), + ) => { + *end_offset = (current_instr_ptr - label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + } + _ => { + unreachable!("Expected to end a block, but the last label was not a block") + } + }; + + self.instructions.push(Instruction::EndBlockFrame); + } + + fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { + let def = targets.default(); + let instrs = targets + .targets() + .map(|t| t.map(Instruction::BrLabel)) + .collect::, wasmparser::BinaryReaderError>>() + .expect("visit_br_table: BrTable targets are invalid, this should have been caught by the validator"); + + self.instructions.extend(([Instruction::BrTable(def, instrs.len() as u32)].into_iter()).chain(instrs)); + } + + fn visit_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { + self.instructions.push(Instruction::CallIndirect(ty, table)); + } + + fn visit_f32_const(&mut self, val: wasmparser::Ieee32) -> Self::Output { + self.instructions.push(Instruction::F32Const(f32::from_bits(val.bits()))); + } + + fn visit_f64_const(&mut self, val: wasmparser::Ieee64) -> Self::Output { + 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 }); + } + + // Reference Types + fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output { + self.instructions.push(Instruction::RefNull(convert_heaptype(ty))); + } + + fn visit_ref_is_null(&mut self) -> Self::Output { + self.instructions.push(Instruction::RefIsNull); + } + + fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { + self.instructions.push(match ty { + wasmparser::ValType::I32 => Instruction::Select32, + wasmparser::ValType::F32 => Instruction::Select32, + wasmparser::ValType::I64 => Instruction::Select64, + wasmparser::ValType::F64 => Instruction::Select64, + wasmparser::ValType::V128 => Instruction::Select128, + wasmparser::ValType::Ref(_) => Instruction::SelectRef, + }); + } + + 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 + } +} diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 50e6480..98f88f1 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace=true license.workspace=true authors.workspace=true repository.workspace=true +rust-version.workspace=true readme="../../README.md" [lib] @@ -13,21 +14,78 @@ name="tinywasm" path="src/lib.rs" [dependencies] -log={version="0.4", optional=true} -tinywasm-parser={version="0.0.5-alpha.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.0.5-alpha.0", path="../types", default-features=false} +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} +libm={version="0.2", default-features=false} [dev-dependencies] wasm-testsuite={path="../wasm-testsuite"} -wast={version="69.0"} -owo-colors={version="3.5"} -eyre={version="0.6"} +wast={workspace=true} +eyre={workspace=true} +pretty_env_logger={workspace=true} +criterion={workspace=true} +owo-colors={version="4.0"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} -plotters={version="0.3"} [features] -default=["std", "parser", "logging"] -logging=["log", "tinywasm-types/logging", "tinywasm-parser?/logging"] +default=["std", "parser", "logging", "archive"] +logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] +archive=["tinywasm-types/archive"] +simd=[] +nightly=["tinywasm-parser?/nightly"] + +[[test]] +name="test-wasm-1" +harness=false +test=false + +[[test]] +name="test-wasm-2" +harness=false +test=false + +[[test]] +name="test-wasm-multi-memory" +harness=false +test=false + +[[test]] +name="test-wasm-memory64" +harness=false +test=false + +[[test]] +name="test-wasm-annotations" +harness=false +test=false + +[[test]] +name="test-wasm-extended-const" +harness=false +test=false + +[[test]] +name="test-wasm-simd" +harness=false +test=false + +[[test]] +name="test-wast" +harness=false +test=false + +[[bench]] +name="argon2id" +harness=false + +[[bench]] +name="fibonacci" +harness=false + +[[bench]] +name="tinywasm" +harness=false diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs new file mode 100644 index 0000000..0a8f033 --- /dev/null +++ b/crates/tinywasm/benches/argon2id.rs @@ -0,0 +1,43 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use eyre::Result; +use tinywasm::{ModuleInstance, Store, types}; +use types::{archive::AlignedVec, TinyWasmModule}; + +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.opt.wasm"); + +fn argon2id_parse() -> Result { + let parser = tinywasm_parser::Parser::new(); + let data = parser.parse_module_bytes(WASM)?; + Ok(data) +} + +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)?; + Ok(module) +} + +fn argon2id_run(module: TinyWasmModule) -> Result<()> { + let mut store = Store::default(); + let instance = ModuleInstance::instantiate(&mut store, module.into(), None)?; + let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, "argon2id")?; + argon2.call(&mut store, (1000, 2, 1))?; + Ok(()) +} + +fn criterion_benchmark(c: &mut Criterion) { + let module = argon2id_parse().expect("argon2id_parse"); + let twasm = argon2id_to_twasm(module.clone()).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", |b| b.iter(|| argon2id_run(module.clone()))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs new file mode 100644 index 0000000..557d787 --- /dev/null +++ b/crates/tinywasm/benches/fibonacci.rs @@ -0,0 +1,50 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use eyre::Result; +use tinywasm::{ModuleInstance, Store, types}; +use types::{archive::AlignedVec, TinyWasmModule}; + +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.opt.wasm"); + +fn fibonacci_parse() -> Result { + let parser = tinywasm_parser::Parser::new(); + let data = parser.parse_module_bytes(WASM)?; + Ok(data) +} + +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_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> { + let mut store = Store::default(); + let instance = ModuleInstance::instantiate(&mut store, module.into(), None)?; + let argon2 = instance.exported_func::( + &store, + match recursive { + true => "fibonacci_recursive", + false => "fibonacci", + }, + )?; + argon2.call(&mut store, n)?; + Ok(()) +} + +fn criterion_benchmark(c: &mut Criterion) { + let module = fibonacci_parse().expect("fibonacci_parse"); + let twasm = fibonacci_to_twasm(module.clone()).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_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))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs new file mode 100644 index 0000000..cfc9cb8 --- /dev/null +++ b/crates/tinywasm/benches/tinywasm.rs @@ -0,0 +1,45 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use eyre::Result; +use tinywasm::{Extern, FuncContext, Imports, ModuleInstance, Store, types}; +use types::{archive::AlignedVec, TinyWasmModule}; + +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.opt.wasm"); + +fn tinywasm_parse() -> Result { + let parser = tinywasm_parser::Parser::new(); + let data = parser.parse_module_bytes(WASM)?; + Ok(data) +} + +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)?; + Ok(module) +} + +fn tinywasm_run(module: TinyWasmModule) -> Result<()> { + let mut store = Store::default(); + let mut imports = Imports::default(); + imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); + let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports)).expect("instantiate"); + let hello = instance.exported_func::<(), ()>(&store, "hello").expect("exported_func"); + hello.call(&mut store, ()).expect("call"); + Ok(()) +} + +fn criterion_benchmark(c: &mut Criterion) { + let module = tinywasm_parse().expect("tinywasm_parse"); + let twasm = tinywasm_to_twasm(module.clone()).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", |b| b.iter(|| tinywasm_run(module.clone()))); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 8776365..73df9a2 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,8 +1,72 @@ -use alloc::string::String; -use core::fmt::Display; +use alloc::string::{String, ToString}; +use core::{fmt::Display, ops::ControlFlow}; +use tinywasm_types::FuncType; #[cfg(feature = "parser")] -use tinywasm_parser::ParseError; +pub use tinywasm_parser::ParseError; + +/// Errors that can occur for `TinyWasm` operations +#[derive(Debug)] +pub enum Error { + /// A WebAssembly trap occurred + Trap(Trap), + + /// A linking error occurred + Linker(LinkingError), + + /// A WebAssembly feature is not supported + UnsupportedFeature(String), + + /// An unknown error occurred + Other(String), + + /// A function did not return a value + FuncDidNotReturn, + + /// An invalid label type was encountered + InvalidLabelType, + + /// The store is not the one that the module instance was instantiated in + InvalidStore, + + #[cfg(feature = "std")] + /// An I/O error occurred + Io(crate::std::io::Error), + + #[cfg(feature = "parser")] + /// A parsing error occurred + ParseError(ParseError), +} + +#[derive(Debug)] +/// Errors that can occur when linking a WebAssembly module +pub enum LinkingError { + /// An unknown import was encountered + UnknownImport { + /// The module name + module: String, + /// The import name + name: String, + }, + + /// A mismatched import type was encountered + IncompatibleImportType { + /// The module name + module: String, + /// The import name + name: String, + }, +} + +impl LinkingError { + pub(crate) fn incompatible_import_type(import: &tinywasm_types::Import) -> Self { + Self::IncompatibleImportType { module: import.module.to_string(), name: import.name.to_string() } + } + + pub(crate) fn unknown_import(import: &tinywasm_types::Import) -> Self { + Self::UnknownImport { module: import.module.to_string(), name: import.name.to_string() } + } +} #[derive(Debug)] /// A WebAssembly trap @@ -13,71 +77,155 @@ pub enum Trap { Unreachable, /// An out-of-bounds memory access occurred - MemoryOutOfBounds, + MemoryOutOfBounds { + /// The offset of the access + offset: usize, + /// The size of the access + len: usize, + /// The maximum size of the memory + max: usize, + }, + + /// An out-of-bounds table access occurred + TableOutOfBounds { + /// The offset of the access + offset: usize, + /// The size of the access + len: usize, + /// The maximum size of the memory + max: usize, + }, /// A division by zero occurred DivisionByZero, -} - -#[derive(Debug)] -/// A tinywasm error -pub enum Error { - #[cfg(feature = "parser")] - /// A parsing error occurred - ParseError(ParseError), - - #[cfg(feature = "std")] - /// An I/O error occurred - Io(crate::std::io::Error), - - /// A WebAssembly feature is not supported - UnsupportedFeature(String), - - /// An unknown error occurred - Other(String), - - /// A WebAssembly trap occurred - Trap(Trap), - /// A function did not return a value - FuncDidNotReturn, + /// Invalid Integer Conversion + InvalidConversionToInt, + + /// Integer Overflow + IntegerOverflow, + + /// Call stack overflow + CallStackOverflow, + + /// An undefined element was encountered + UndefinedElement { + /// The element index + index: usize, + }, + + /// An uninitialized element was encountered + UninitializedElement { + /// The element index + index: usize, + }, + + /// Indirect call type mismatch + IndirectCallTypeMismatch { + /// The expected type + expected: FuncType, + /// The actual type + actual: FuncType, + }, +} - /// The stack is empty - StackUnderflow, +impl Trap { + /// Get the message of the trap + pub fn message(&self) -> &'static str { + match self { + Self::Unreachable => "unreachable", + Self::MemoryOutOfBounds { .. } => "out of bounds memory access", + Self::TableOutOfBounds { .. } => "out of bounds table access", + Self::DivisionByZero => "integer divide by zero", + Self::InvalidConversionToInt => "invalid conversion to integer", + Self::IntegerOverflow => "integer overflow", + Self::CallStackOverflow => "call stack exhausted", + Self::UndefinedElement { .. } => "undefined element", + Self::UninitializedElement { .. } => "uninitialized element", + Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch", + } + } +} - /// The label stack is empty - LabelStackUnderflow, +impl LinkingError { + /// Get the message of the linking error + pub fn message(&self) -> &'static str { + match self { + Self::UnknownImport { .. } => "unknown import", + Self::IncompatibleImportType { .. } => "incompatible import type", + } + } +} - /// The call stack is empty - CallStackEmpty, +impl From for Error { + fn from(value: LinkingError) -> Self { + Self::Linker(value) + } +} - /// The store is not the one that the module instance was instantiated in - InvalidStore, +impl From for Error { + fn from(value: Trap) -> Self { + Self::Trap(value) + } } 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::ParseError(err) => write!(f, "error parsing module: {err:?}"), #[cfg(feature = "std")] - Self::Io(err) => write!(f, "I/O error: {}", err), + Self::Io(err) => write!(f, "I/O error: {err}"), - Self::Trap(trap) => write!(f, "trap: {:?}", trap), - - Self::Other(message) => write!(f, "unknown error: {}", message), - Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {}", feature), + 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::LabelStackUnderflow => write!(f, "label stack underflow"), - Self::StackUnderflow => write!(f, "stack underflow"), - Self::CallStackEmpty => write!(f, "call stack empty"), Self::InvalidStore => write!(f, "invalid store"), } } } -#[cfg(any(feature = "std", all(not(feature = "std"), nightly)))] +impl Display for LinkingError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::UnknownImport { module, name } => write!(f, "unknown import: {}.{}", module, name), + Self::IncompatibleImportType { module, name } => { + write!(f, "incompatible import type: {}.{}", module, name) + } + } + } +} + +impl Display for Trap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Unreachable => write!(f, "unreachable"), + Self::MemoryOutOfBounds { offset, len, max } => { + write!(f, "out of bounds memory access: offset={offset}, len={len}, max={max}") + } + Self::TableOutOfBounds { offset, len, max } => { + write!(f, "out of bounds table access: offset={offset}, len={len}, max={max}") + } + Self::DivisionByZero => write!(f, "integer divide by zero"), + Self::InvalidConversionToInt => write!(f, "invalid conversion to integer"), + Self::IntegerOverflow => write!(f, "integer overflow"), + Self::CallStackOverflow => write!(f, "call stack exhausted"), + Self::UndefinedElement { index } => write!(f, "undefined element: index={index}"), + Self::UninitializedElement { index } => { + write!(f, "uninitialized element: index={index}") + } + Self::IndirectCallTypeMismatch { expected, actual } => { + write!(f, "indirect call type mismatch: expected={expected:?}, actual={actual:?}") + } + } + } +} + +#[cfg(any(feature = "std", all(not(feature = "std"), feature = "nightly")))] impl crate::std::error::Error for Error {} #[cfg(feature = "parser")] @@ -87,5 +235,18 @@ impl From for Error { } } -/// A specialized [`Result`] type for tinywasm operations +/// A wrapper around [`core::result::Result`] for tinywasm operations pub type Result = crate::std::result::Result; + +pub(crate) trait Controlify { + fn to_cf(self) -> ControlFlow, T>; +} + +impl Controlify for Result { + fn to_cf(self) -> ControlFlow, T> { + match self { + Ok(value) => ControlFlow::Continue(value), + Err(err) => ControlFlow::Break(Some(err)), + } + } +} diff --git a/crates/tinywasm/src/export.rs b/crates/tinywasm/src/export.rs deleted file mode 100644 index c6ae6d3..0000000 --- a/crates/tinywasm/src/export.rs +++ /dev/null @@ -1,18 +0,0 @@ -use alloc::{boxed::Box, format}; -use tinywasm_types::{Export, ExternalKind}; - -use crate::{Error, Result}; - -#[derive(Debug)] -/// Exports of a module instance -pub struct ExportInstance(pub(crate) Box<[Export]>); - -impl ExportInstance { - /// Get an export by name - pub fn get(&self, name: &str, ty: ExternalKind) -> Result<&Export> { - self.0 - .iter() - .find(|e| e.name == name.into() && e.kind == ty) - .ok_or(Error::Other(format!("export {} not found", name))) - } -} diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 58890f6..466dd86 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,17 +1,14 @@ -use alloc::{format, string::String, string::ToString, vec, vec::Vec}; -use log::{debug, info}; -use tinywasm_types::{FuncAddr, FuncType, WasmValue}; - -use crate::{ - runtime::{CallFrame, Stack}, - Error, ModuleInstance, Result, Store, -}; +use crate::interpreter::stack::{CallFrame, Stack}; +use crate::{log, unlikely, Function}; +use crate::{Error, FuncContext, Result, Store}; +use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; +use tinywasm_types::{FuncType, ModuleInstanceAddr, ValType, WasmValue}; #[derive(Debug)] /// A function handle pub struct FuncHandle { - pub(crate) module: ModuleInstance, - pub(crate) addr: FuncAddr, + pub(crate) module_addr: ModuleInstanceAddr, + pub(crate) addr: u32, pub(crate) ty: FuncType, /// The name of the function, if it has one @@ -19,22 +16,19 @@ pub struct FuncHandle { } impl FuncHandle { - /// Call a function + /// Call a function (Invocation) /// /// See + #[inline] pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { - let mut stack = Stack::default(); - - // 1. Assert: funcs[func_addr] exists - // 2. let func_inst be the functiuon instance funcs[func_addr] - let func_inst = store.get_func(self.addr as usize)?; + // Comments are ordered by the steps in the spec + // In this implementation, some steps are combined and ordered differently for performance reasons // 3. Let func_ty be the function type let func_ty = &self.ty; // 4. If the length of the provided argument values is different from the number of expected arguments, then fail - if func_ty.params.len() != params.len() { - info!("func_ty.params: {:?}", func_ty.params); + if unlikely(func_ty.params.len() != params.len()) { return Err(Error::Other(format!( "param count mismatch: expected {}, got {}", func_ty.params.len(), @@ -43,43 +37,55 @@ impl FuncHandle { } // 5. For each value type and the corresponding value, check if types match - for (i, (ty, param)) in func_ty.params.iter().zip(params).enumerate() { + if !(func_ty.params.iter().zip(params).enumerate().all(|(i, (ty, param))| { if ty != ¶m.val_type() { - return Err(Error::Other(format!( - "param type mismatch at index {}: expected {:?}, got {:?}", - i, ty, param - ))); + log::error!("param type mismatch at index {}: expected {:?}, got {:?}", i, ty, param); + false + } else { + true } + })) { + return Err(Error::Other("Type mismatch".into())); } + let 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 ctx = FuncContext { store, module_addr: self.module_addr }; + return (func)(ctx, params); + } + Function::Wasm(wasm_func) => wasm_func, + }; + // 6. Let f be the dummy frame - debug!("locals: {:?}", func_inst.locals()); - let call_frame = CallFrame::new(self.addr as usize, params, func_inst.locals().to_vec()); + let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, 0); // 7. Push the frame f to the call stack - stack.call_stack.push(call_frame); - - // 8. Push the values to the stack (Not needed since the call frame owns the values) + // & 8. Push the values to the stack (Not needed since the call frame owns the values) + let mut stack = Stack::new(call_frame); // 9. Invoke the function instance let runtime = store.runtime(); - runtime.exec(store, &mut stack, self.module.clone())?; + runtime.exec(store, &mut stack)?; // Once the function returns: - let result_m = func_ty.results.len(); - let res = stack.values.pop_n(result_m)?; - - Ok(res - .iter() - .zip(func_ty.results.iter()) - .map(|(v, ty)| v.attach_type(*ty)) - .collect()) + // let result_m = func_ty.results.len(); + + // 1. Assert: m values are on the top of the stack (Ensured by validation) + // assert!(stack.values.len() >= result_m); + + // 2. Pop m values from the stack + let res = stack.values.pop_results(&func_ty.results); + + // The values are returned as the results of the invocation. + Ok(res) } } #[derive(Debug)] /// A typed function handle -pub struct TypedFuncHandle { +pub struct FuncHandleTyped { /// The underlying function handle pub func: FuncHandle, pub(crate) marker: core::marker::PhantomData<(P, R)>, @@ -90,12 +96,12 @@ pub trait IntoWasmValueTuple { } pub trait FromWasmValueTuple { - fn from_wasm_value_tuple(values: Vec) -> Result + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result where Self: Sized; } -impl TypedFuncHandle { +impl FuncHandleTyped { /// Call a typed function pub fn call(&self, store: &mut Store, params: P) -> Result { // Convert params into Vec @@ -105,9 +111,10 @@ impl TypedFuncHandle { let result = self.func.call(store, &wasm_values)?; // Convert the Vec back to R - R::from_wasm_value_tuple(result) + R::from_wasm_value_tuple(&result) } } + macro_rules! impl_into_wasm_value_tuple { ($($T:ident),*) => { impl<$($T),*> IntoWasmValueTuple for ($($T,)*) @@ -115,6 +122,7 @@ macro_rules! impl_into_wasm_value_tuple { $($T: Into),* { #[allow(non_snake_case)] + #[inline] fn into_wasm_value_tuple(self) -> Vec { let ($($T,)*) = self; vec![$($T.into(),)*] @@ -123,15 +131,16 @@ macro_rules! impl_into_wasm_value_tuple { } } -impl_into_wasm_value_tuple!(); -impl_into_wasm_value_tuple!(T1); -impl_into_wasm_value_tuple!(T1, T2); -impl_into_wasm_value_tuple!(T1, T2, T3); -impl_into_wasm_value_tuple!(T1, T2, T3, T4); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); +macro_rules! impl_into_wasm_value_tuple_single { + ($T:ident) => { + impl IntoWasmValueTuple for $T { + #[inline] + fn into_wasm_value_tuple(self) -> Vec { + vec![self.into()] + } + } + }; +} macro_rules! impl_from_wasm_value_tuple { ($($T:ident),*) => { @@ -139,16 +148,19 @@ macro_rules! impl_from_wasm_value_tuple { where $($T: TryFrom),* { - fn from_wasm_value_tuple(values: Vec) -> Result { + #[inline] + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { #[allow(unused_variables, unused_mut)] - let mut iter = values.into_iter(); + let mut iter = values.iter(); + Ok(( $( $T::try_from( - iter.next() + *iter.next() .ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))? ) - .map_err(|_| Error::Other("Could not convert WasmValue to expected type".to_string()))?, + .map_err(|e| Error::Other(format!("FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", e, + )))?, )* )) } @@ -156,6 +168,102 @@ macro_rules! impl_from_wasm_value_tuple { } } +macro_rules! impl_from_wasm_value_tuple_single { + ($T:ident) => { + impl FromWasmValueTuple for $T { + #[inline] + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { + #[allow(unused_variables, unused_mut)] + let mut iter = values.iter(); + $T::try_from(*iter.next().ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?) + .map_err(|e| { + Error::Other(format!( + "FromWasmValueTupleSingle: Could not convert WasmValue to expected type: {:?}", + e + )) + }) + } + } + }; +} + +pub trait ValTypesFromTuple { + fn val_types() -> Box<[ValType]>; +} + +pub trait ToValType { + fn to_val_type() -> ValType; +} + +impl ToValType for i32 { + fn to_val_type() -> ValType { + ValType::I32 + } +} + +impl ToValType for i64 { + fn to_val_type() -> ValType { + ValType::I64 + } +} + +impl ToValType for f32 { + fn to_val_type() -> ValType { + ValType::F32 + } +} + +impl ToValType for f64 { + fn to_val_type() -> ValType { + ValType::F64 + } +} + +macro_rules! impl_val_types_from_tuple { + ($($t:ident),+) => { + impl<$($t),+> ValTypesFromTuple for ($($t,)+) + where + $($t: ToValType,)+ + { + #[inline] + fn val_types() -> Box<[ValType]> { + Box::new([$($t::to_val_type(),)+]) + } + } + }; +} + +impl ValTypesFromTuple for () { + #[inline] + fn val_types() -> Box<[ValType]> { + Box::new([]) + } +} + +impl ValTypesFromTuple for T { + #[inline] + fn val_types() -> Box<[ValType]> { + Box::new([T::to_val_type()]) + } +} + +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_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_val_types_from_tuple!(T1); +impl_val_types_from_tuple!(T1, T2); +impl_val_types_from_tuple!(T1, T2, T3); +impl_val_types_from_tuple!(T1, T2, T3, T4); +impl_val_types_from_tuple!(T1, T2, T3, T4, T5); +impl_val_types_from_tuple!(T1, T2, T3, T4, T5, T6); + impl_from_wasm_value_tuple!(); impl_from_wasm_value_tuple!(T1); impl_from_wasm_value_tuple!(T1, T2); @@ -163,5 +271,11 @@ impl_from_wasm_value_tuple!(T1, T2, T3); impl_from_wasm_value_tuple!(T1, T2, T3, T4); impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5); impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); + +impl_into_wasm_value_tuple!(); +impl_into_wasm_value_tuple!(T1); +impl_into_wasm_value_tuple!(T1, T2); +impl_into_wasm_value_tuple!(T1, T2, T3); +impl_into_wasm_value_tuple!(T1, T2, T3, T4); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs new file mode 100644 index 0000000..3a226f4 --- /dev/null +++ b/crates/tinywasm/src/imports.rs @@ -0,0 +1,425 @@ +use alloc::boxed::Box; +use alloc::collections::BTreeMap; +use alloc::rc::Rc; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::fmt::Debug; + +use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; +use crate::{log, LinkingError, MemoryRef, MemoryRefMut, Result}; +use tinywasm_types::*; + +/// The internal representation of a function +#[derive(Debug, Clone)] +pub enum Function { + /// A host function + Host(Rc), + + /// A pointer to a WebAssembly function + Wasm(Rc), +} + +impl Function { + pub(crate) fn ty(&self) -> &FuncType { + match self { + Self::Host(f) => &f.ty, + Self::Wasm(f) => &f.ty, + } + } +} + +/// A host function +pub struct HostFunction { + pub(crate) ty: tinywasm_types::FuncType, + pub(crate) func: HostFuncInner, +} + +impl HostFunction { + /// Get the function's type + pub fn ty(&self) -> &tinywasm_types::FuncType { + &self.ty + } + + /// Call the function + pub fn call(&self, ctx: FuncContext<'_>, args: &[WasmValue]) -> Result> { + (self.func)(ctx, args) + } +} + +pub(crate) type HostFuncInner = Box, &[WasmValue]) -> Result>>; + +/// The context of a host-function call +#[derive(Debug)] +pub struct FuncContext<'a> { + pub(crate) store: &'a mut crate::Store, + pub(crate) module_addr: ModuleInstanceAddr, +} + +impl FuncContext<'_> { + /// Get a reference to the store + pub fn store(&self) -> &crate::Store { + self.store + } + + /// Get a mutable reference to the store + pub fn store_mut(&mut self) -> &mut crate::Store { + self.store + } + + /// Get a reference to the module instance + pub fn module(&self) -> crate::ModuleInstance { + self.store.get_module_instance_raw(self.module_addr) + } + + /// Get a reference to an exported memory + pub fn exported_memory(&mut self, name: &str) -> Result> { + self.module().exported_memory(self.store, name) + } + + /// Get a reference to an exported memory + pub fn exported_memory_mut(&mut self, name: &str) -> Result> { + self.module().exported_memory_mut(self.store, name) + } +} + +impl Debug for HostFunction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("HostFunction").field("ty", &self.ty).field("func", &"...").finish() + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +/// An external value +pub enum Extern { + /// A global value + Global { + /// The type of the global value. + ty: GlobalType, + /// The actual value of the global, encapsulated in `WasmValue`. + val: WasmValue, + }, + + /// A table + Table { + /// Defines the type of the table, including its element type and limits. + ty: TableType, + /// The initial value of the table. + init: WasmValue, + }, + + /// A memory + Memory { + /// Defines the type of the memory, including its limits and the type of its pages. + ty: MemoryType, + }, + + /// A function + Function(Function), +} + +impl Extern { + /// Create a new global import + pub fn global(val: WasmValue, mutable: bool) -> Self { + Self::Global { ty: GlobalType { ty: val.val_type(), mutable }, val } + } + + /// Create a new table import + pub fn table(ty: TableType, init: WasmValue) -> Self { + Self::Table { ty, init } + } + + /// Create a new memory import + pub fn memory(ty: MemoryType) -> Self { + Self::Memory { ty } + } + + /// Create a new function import + pub fn func( + 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() }))) + } + + /// Create a new typed function import + pub fn typed_func(func: impl Fn(FuncContext<'_>, P) -> Result + 'static) -> Self + where + P: FromWasmValueTuple + ValTypesFromTuple, + R: IntoWasmValueTuple + ValTypesFromTuple + Debug, + { + let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { + let args = P::from_wasm_value_tuple(args)?; + let result = func(ctx, args)?; + Ok(result.into_wasm_value_tuple().to_vec()) + }; + + let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; + Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty }))) + } + + /// Get the kind of the external value + pub fn kind(&self) -> ExternalKind { + match self { + Self::Global { .. } => ExternalKind::Global, + Self::Table { .. } => ExternalKind::Table, + Self::Memory { .. } => ExternalKind::Memory, + Self::Function { .. } => ExternalKind::Func, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +/// Name of an import +pub struct ExternName { + module: String, + name: String, +} + +impl From<&Import> for ExternName { + fn from(import: &Import) -> Self { + Self { module: import.module.to_string(), name: import.name.to_string() } + } +} + +#[derive(Debug, Default)] +/// Imports for a module instance +/// +/// This is used to link a module instance to its imports +/// +/// ## Example +/// ```rust +/// # use log; +/// # fn main() -> tinywasm::Result<()> { +/// use tinywasm::{Imports, Extern}; +/// use tinywasm::types::{ValType, TableType, MemoryType, WasmValue}; +/// let mut imports = Imports::new(); +/// +/// // function args can be either a single +/// // value that implements `TryFrom` or a tuple of them +/// let print_i32 = Extern::typed_func(|_ctx: tinywasm::FuncContext<'_>, arg: i32| { +/// log::debug!("print_i32: {}", arg); +/// Ok(()) +/// }); +/// +/// let table_type = TableType::new(ValType::RefFunc, 10, Some(20)); +/// let table_init = WasmValue::default_for(ValType::RefFunc); +/// +/// 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", "global_i32", Extern::global(WasmValue::I32(666), false))? +/// .link_module("my_other_module", 0)?; +/// # Ok(()) +/// # } +/// ``` +/// +/// Note that module instance addresses for [`Imports::link_module`] can be obtained from [`crate::ModuleInstance::id`]. +/// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`]. +#[derive(Clone)] +pub struct Imports { + values: BTreeMap, + modules: BTreeMap, +} + +pub(crate) enum ResolvedExtern { + Store(S), // already in the store + Extern(V), // needs to be added to the store, provided value +} + +pub(crate) struct ResolvedImports { + pub(crate) globals: Vec, + pub(crate) tables: Vec, + pub(crate) memories: Vec, + pub(crate) funcs: Vec, +} + +impl ResolvedImports { + pub(crate) fn new() -> Self { + Self { globals: Vec::new(), tables: Vec::new(), memories: Vec::new(), funcs: Vec::new() } + } +} + +impl Imports { + /// Create a new empty import set + pub fn new() -> Self { + Imports { values: BTreeMap::new(), modules: BTreeMap::new() } + } + + /// Merge two import sets + pub fn merge(mut self, other: Self) -> Self { + self.values.extend(other.values); + self.modules.extend(other.modules); + self + } + + /// Link a module + /// + /// This will automatically link all imported values on instantiation + pub fn link_module(&mut self, name: &str, addr: ModuleInstanceAddr) -> Result<&mut Self> { + self.modules.insert(name.to_string(), addr); + Ok(self) + } + + /// Define an import + pub fn define(&mut self, module: &str, name: &str, value: Extern) -> Result<&mut Self> { + self.values.insert(ExternName { module: module.to_string(), name: name.to_string() }, value); + Ok(self) + } + + pub(crate) fn take( + &mut self, + store: &mut crate::Store, + import: &Import, + ) -> Option> { + let name = ExternName::from(import); + if let Some(v) = self.values.get(&name) { + return Some(ResolvedExtern::Extern(v.clone())); + } + if let Some(addr) = self.modules.get(&name.module) { + let instance = store.get_module_instance(*addr)?; + return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?)); + } + + None + } + + fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { + if expected != actual { + log::error!("failed to link import {}, expected {:?}, got {:?}", import.name, expected, actual); + return Err(LinkingError::incompatible_import_type(import).into()); + } + Ok(()) + } + + fn compare_table_types(import: &Import, expected: &TableType, actual: &TableType) -> Result<()> { + Self::compare_types(import, &actual.element_type, &expected.element_type)?; + + if actual.size_initial > expected.size_initial { + return Err(LinkingError::incompatible_import_type(import).into()); + } + + match (expected.size_max, actual.size_max) { + (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()), + (Some(expected_max), Some(actual_max)) if actual_max < expected_max => { + return Err(LinkingError::incompatible_import_type(import).into()) + } + _ => {} + } + + Ok(()) + } + + fn compare_memory_types( + import: &Import, + expected: &MemoryType, + actual: &MemoryType, + real_size: Option, + ) -> Result<()> { + 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) + { + return Err(LinkingError::incompatible_import_type(import).into()); + } + + if expected.page_count_max.is_none() && actual.page_count_max.is_some() { + return Err(LinkingError::incompatible_import_type(import).into()); + } + + if let (Some(expected_max), Some(actual_max)) = (expected.page_count_max, actual.page_count_max) { + if actual_max < expected_max { + return Err(LinkingError::incompatible_import_type(import).into()); + } + } + + Ok(()) + } + + pub(crate) fn link( + mut self, + store: &mut crate::Store, + module: &crate::Module, + idx: ModuleInstanceAddr, + ) -> Result { + let mut imports = ResolvedImports::new(); + + for import in &module.0.imports { + let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?; + + match val { + // A link to something that needs to be added to the store + ResolvedExtern::Extern(ex) => match (ex, &import.kind) { + (Extern::Global { ty, val }, ImportKind::Global(import_ty)) => { + Self::compare_types(import, &ty, import_ty)?; + imports.globals.push(store.add_global(ty, val.into(), idx)?); + } + (Extern::Table { ty, .. }, ImportKind::Table(import_ty)) => { + Self::compare_table_types(import, &ty, import_ty)?; + imports.tables.push(store.add_table(ty, idx)?); + } + (Extern::Memory { ty }, ImportKind::Memory(import_ty)) => { + Self::compare_memory_types(import, &ty, import_ty, None)?; + imports.memories.push(store.add_mem(ty, idx)?); + } + (Extern::Function(extern_func), ImportKind::Function(ty)) => { + let import_func_type = module + .0 + .func_types + .get(*ty as usize) + .ok_or_else(|| LinkingError::incompatible_import_type(import))?; + + Self::compare_types(import, extern_func.ty(), import_func_type)?; + imports.funcs.push(store.add_func(extern_func, idx)?); + } + _ => return Err(LinkingError::incompatible_import_type(import).into()), + }, + + // A link to something already in the store + ResolvedExtern::Store(val) => { + // check if the kind matches + if val.kind() != (&import.kind).into() { + return Err(LinkingError::incompatible_import_type(import).into()); + } + + match (val, &import.kind) { + (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { + let global = store.get_global(global_addr); + Self::compare_types(import, &global.ty, ty)?; + imports.globals.push(global_addr); + } + (ExternVal::Table(table_addr), ImportKind::Table(ty)) => { + let table = store.get_table(table_addr); + let mut kind = table.kind.clone(); + kind.size_initial = table.size() as u32; + Self::compare_table_types(import, &kind, ty)?; + imports.tables.push(table_addr); + } + (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => { + let mem = store.get_mem(memory_addr); + let (size, kind) = { (mem.page_count, mem.kind) }; + Self::compare_memory_types(import, &kind, ty, Some(size))?; + imports.memories.push(memory_addr); + } + (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { + let func = store.get_func(func_addr); + let import_func_type = module + .0 + .func_types + .get(*ty as usize) + .ok_or_else(|| LinkingError::incompatible_import_type(import))?; + + Self::compare_types(import, func.func.ty(), import_func_type)?; + imports.funcs.push(func_addr); + } + _ => return Err(LinkingError::incompatible_import_type(import).into()), + } + } + } + } + + Ok(imports) + } +} diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 5e87e74..eebf5ff 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,103 +1,232 @@ -use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec}; -use tinywasm_types::{Export, ExternalKind, FuncAddr, FuncType, ModuleInstanceAddr}; +use alloc::{boxed::Box, format, rc::Rc, string::ToString}; +use tinywasm_types::*; -use crate::{ - func::{FromWasmValueTuple, IntoWasmValueTuple}, - Error, ExportInstance, FuncHandle, Result, Store, TypedFuncHandle, -}; +use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; +use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store}; -/// A WebAssembly Module Instance +/// An instanciated WebAssembly module /// -/// Addrs are indices into the store's data structures. +/// Backed by an Rc, so cloning is cheap /// /// See #[derive(Debug, Clone)] -pub struct ModuleInstance(Arc); +pub struct ModuleInstance(pub(crate) Rc); +#[allow(dead_code)] #[derive(Debug)] -struct ModuleInstanceInner { +pub(crate) struct ModuleInstanceInner { + pub(crate) failed_to_instantiate: bool, + pub(crate) store_id: usize, - pub(crate) _idx: ModuleInstanceAddr, - pub(crate) func_start: Option, + pub(crate) idx: ModuleInstanceAddr, + pub(crate) types: Box<[FuncType]>, - pub(crate) exports: ExportInstance, - - pub(crate) func_addrs: Vec, - // pub table_addrs: Vec, - // pub mem_addrs: Vec, - // pub global_addrs: Vec, - // pub elem_addrs: Vec, - // pub data_addrs: Vec, + + pub(crate) func_addrs: Box<[FuncAddr]>, + pub(crate) table_addrs: Box<[TableAddr]>, + pub(crate) mem_addrs: Box<[MemAddr]>, + pub(crate) global_addrs: Box<[GlobalAddr]>, + pub(crate) elem_addrs: Box<[ElemAddr]>, + pub(crate) data_addrs: Box<[DataAddr]>, + + pub(crate) func_start: Option, + pub(crate) imports: Box<[Import]>, + pub(crate) exports: Box<[Export]>, } impl ModuleInstance { - /// Get the module's exports - pub fn exports(&self) -> &ExportInstance { - &self.0.exports - } - - pub(crate) fn new( - types: Box<[FuncType]>, - func_start: Option, - exports: Box<[Export]>, - func_addrs: Vec, - idx: ModuleInstanceAddr, - store_id: usize, - ) -> Self { - Self(Arc::new(ModuleInstanceInner { - store_id, - _idx: idx, - types, - func_start, - func_addrs, - exports: ExportInstance(exports), - })) + // drop the module instance reference and swap it with another one + #[inline] + pub(crate) fn swap(&mut self, other: Self) { + self.0 = other.0; + } + + #[inline] + pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { + if other_addr != self.id() { + self.swap(store.get_module_instance_raw(other_addr)) + } + } + + /// Get the module instance's address + #[inline] + pub fn id(&self) -> ModuleInstanceAddr { + self.0.idx + } + + /// Instantiate the module in the given store + /// + /// See + pub fn instantiate(store: &mut Store, module: Module, imports: Option) -> Result { + // This doesn't completely follow the steps in the spec, but the end result is the same + // Constant expressions are evaluated directly where they are used, so we + // don't need to create a auxiliary frame etc. + + let idx = store.next_module_instance_idx(); + let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?; + + addrs.funcs.extend(store.init_funcs(module.0.funcs.into(), idx)?); + addrs.tables.extend(store.init_tables(module.0.table_types.into(), idx)?); + addrs.memories.extend(store.init_memories(module.0.memory_types.into(), idx)?); + + let global_addrs = store.init_globals(addrs.globals, module.0.globals.into(), &addrs.funcs, idx)?; + let (elem_addrs, elem_trapped) = + store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?; + let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, module.0.data.into(), idx)?; + + let instance = ModuleInstanceInner { + failed_to_instantiate: elem_trapped.is_some() || data_trapped.is_some(), + store_id: store.id(), + idx, + types: module.0.func_types, + func_addrs: addrs.funcs.into_boxed_slice(), + table_addrs: addrs.tables.into_boxed_slice(), + mem_addrs: addrs.memories.into_boxed_slice(), + global_addrs: global_addrs.into_boxed_slice(), + elem_addrs, + data_addrs, + func_start: module.0.start_func, + imports: module.0.imports, + exports: module.0.exports, + }; + + let instance = ModuleInstance::new(instance); + store.add_instance(instance.clone()); + + match (elem_trapped, data_trapped) { + (Some(trap), _) | (_, Some(trap)) => Err(trap.into()), + _ => Ok(instance), + } + } + + /// Get a export by name + pub fn export_addr(&self, name: &str) -> Option { + let exports = self.0.exports.iter().find(|e| e.name == name.into())?; + let addr = match exports.kind { + ExternalKind::Func => self.0.func_addrs.get(exports.index as usize)?, + ExternalKind::Table => self.0.table_addrs.get(exports.index as usize)?, + ExternalKind::Memory => self.0.mem_addrs.get(exports.index as usize)?, + ExternalKind::Global => self.0.global_addrs.get(exports.index as usize)?, + }; + + Some(ExternVal::new(exports.kind, *addr)) + } + + #[inline] + pub(crate) fn new(inner: ModuleInstanceInner) -> Self { + Self(Rc::new(inner)) } + #[inline] pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { &self.0.types[addr as usize] } + #[inline] + pub(crate) fn func_addrs(&self) -> &[FuncAddr] { + &self.0.func_addrs + } + + // resolve a function address to the global store address + #[inline] + pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { + self.0.func_addrs[addr as usize] + } + + // resolve a table address to the global store address + #[inline] + pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { + self.0.table_addrs[addr as usize] + } + + // resolve a memory address to the global store address + #[inline] + pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { + self.0.mem_addrs[addr as usize] + } + + // resolve a data address to the global store address + #[inline] + pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { + self.0.data_addrs[addr as usize] + } + + // resolve a memory address to the global store address + #[inline] + pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { + self.0.elem_addrs[addr as usize] + } + + // resolve a global address to the global store address + #[inline] + pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { + self.0.global_addrs[addr as usize] + } + /// Get an exported function by name - pub fn get_func(&self, store: &Store, name: &str) -> Result { + pub fn exported_func_untyped(&self, store: &Store, name: &str) -> Result { if self.0.store_id != store.id() { return Err(Error::InvalidStore); } - let export = self.0.exports.get(name, ExternalKind::Func)?; - let func_addr = self.0.func_addrs[export.index as usize]; - let func = store.get_func(func_addr as usize)?; - let ty = self.0.types[func.ty_addr() as usize].clone(); + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + let ExternVal::Func(func_addr) = export else { + return Err(Error::Other(format!("Export is not a function: {name}"))); + }; - Ok(FuncHandle { - addr: export.index, - module: self.clone(), - name: Some(name.to_string()), - ty, - }) + let ty = store.get_func(func_addr).func.ty(); + Ok(FuncHandle { addr: func_addr, module_addr: self.id(), name: Some(name.to_string()), ty: ty.clone() }) } /// Get a typed exported function by name - pub fn get_typed_func(&self, store: &Store, name: &str) -> Result> + pub fn exported_func(&self, store: &Store, name: &str) -> Result> where P: IntoWasmValueTuple, R: FromWasmValueTuple, { - let func = self.get_func(store, name)?; - Ok(TypedFuncHandle { - func, - marker: core::marker::PhantomData, - }) + let func = self.exported_func_untyped(store, name)?; + Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) + } + + /// Get an exported memory by name + pub fn exported_memory<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + let ExternVal::Memory(mem_addr) = export else { + return Err(Error::Other(format!("Export is not a memory: {}", name))); + }; + + self.memory(store, mem_addr) + } + + /// Get an exported memory by name + pub fn exported_memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + let ExternVal::Memory(mem_addr) = export else { + return Err(Error::Other(format!("Export is not a memory: {}", name))); + }; + + self.memory_mut(store, mem_addr) + } + + /// Get a memory by address + pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result> { + let mem = store.get_mem(self.resolve_mem_addr(addr)); + Ok(MemoryRef(mem)) + } + + /// Get a memory by address (mutable) + pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { + let mem = store.get_mem_mut(self.resolve_mem_addr(addr)); + Ok(MemoryRefMut(mem)) } /// Get the start function of the module /// /// Returns None if the module has no start function /// If no start function is specified, also checks for a _start function in the exports - /// (which is not part of the spec, but used by llvm) /// /// See - pub fn get_start_func(&mut self, store: &Store) -> Result> { + pub fn start_func(&self, store: &Store) -> Result> { if self.0.store_id != store.id() { return Err(Error::InvalidStore); } @@ -106,24 +235,19 @@ impl ModuleInstance { Some(func_index) => func_index, None => { // alternatively, check for a _start function in the exports - let Ok(start) = self.0.exports.get("_start", ExternalKind::Func) else { + let Some(ExternVal::Func(func_addr)) = self.export_addr("_start") else { return Ok(None); }; - start.index + func_addr } }; - let func_addr = self.0.func_addrs[func_index as usize]; - let func = store.get_func(func_addr as usize)?; - let ty = self.0.types[func.ty_addr() as usize].clone(); + let func_addr = self.resolve_func_addr(func_index); + let func_inst = store.get_func(func_addr); + let ty = func_inst.func.ty(); - Ok(Some(FuncHandle { - module: self.clone(), - addr: func_addr, - ty, - name: None, - })) + Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone(), name: None })) } /// Invoke the start function of the module @@ -131,8 +255,8 @@ impl ModuleInstance { /// Returns None if the module has no start function /// /// See - pub fn start(&mut self, store: &mut Store) -> Result> { - let Some(func) = self.get_start_func(store)? else { + pub fn start(&self, store: &mut Store) -> Result> { + let Some(func) = self.start_func(store)? else { return Ok(None); }; diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs new file mode 100644 index 0000000..c2df82c --- /dev/null +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -0,0 +1,748 @@ +#[cfg(not(feature = "std"))] +#[allow(unused_imports)] +use super::no_std_floats::NoStdFloatExt; + +use alloc::{format, rc::Rc, string::ToString}; +use core::ops::ControlFlow; +use interpreter::stack::CallFrame; +use tinywasm_types::*; + +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, +} + +impl<'store, 'stack> Executor<'store, 'stack> { + pub(crate) fn new(store: &'store mut Store, stack: &'stack mut Stack) -> Result { + let current_frame = stack.call_stack.pop().expect("no call frame, this is a bug"); + let current_module = store.get_module_instance_raw(current_frame.module_addr()); + Ok(Self { cf: current_frame, module: current_module, stack, store }) + } + + #[inline] + pub(crate) fn run_to_completion(&mut self) -> Result<()> { + loop { + if let ControlFlow::Break(res) = self.exec_next() { + return match res { + Some(e) => Err(e), + None => Ok(()), + }; + } + } + } + + #[inline(always)] + fn exec_next(&mut self) -> ControlFlow> { + use tinywasm_types::Instruction::*; + match self.cf.fetch_instr() { + Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} + Unreachable => self.exec_unreachable()?, + + Drop32 => self.stack.values.drop::(), + Drop64 => self.stack.values.drop::(), + Drop128 => self.stack.values.drop::(), + DropRef => self.stack.values.drop::(), + + Select32 => self.stack.values.select::(), + Select64 => self.stack.values.select::(), + Select128 => self.stack.values.select::(), + SelectRef => self.stack.values.select::(), + + Call(v) => return self.exec_call_direct(*v), + CallIndirect(ty, table) => return self.exec_call_indirect(*ty, *table), + + If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())), + IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())), + IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)), + Else(end_offset) => self.exec_else(*end_offset), + Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())), + LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())), + LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)), + Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())), + BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())), + BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)), + Br(v) => return self.exec_br(*v), + BrIf(v) => return self.exec_br_if(*v), + BrTable(default, len) => return self.exec_brtable(*default, *len), + Return => return self.exec_return(), + EndBlockFrame => self.exec_end_block(), + + LocalGet32(local_index) => self.exec_local_get::(*local_index), + LocalGet64(local_index) => self.exec_local_get::(*local_index), + LocalGet128(local_index) => self.exec_local_get::(*local_index), + LocalGetRef(local_index) => self.exec_local_get::(*local_index), + + LocalSet32(local_index) => self.exec_local_set::(*local_index), + LocalSet64(local_index) => self.exec_local_set::(*local_index), + LocalSet128(local_index) => self.exec_local_set::(*local_index), + LocalSetRef(local_index) => self.exec_local_set::(*local_index), + + LocalTee32(local_index) => self.exec_local_tee::(*local_index), + LocalTee64(local_index) => self.exec_local_tee::(*local_index), + LocalTee128(local_index) => self.exec_local_tee::(*local_index), + LocalTeeRef(local_index) => self.exec_local_tee::(*local_index), + + GlobalGet(global_index) => self.exec_global_get(*global_index), + GlobalSet32(global_index) => self.exec_global_set::(*global_index), + GlobalSet64(global_index) => self.exec_global_set::(*global_index), + GlobalSet128(global_index) => self.exec_global_set::(*global_index), + GlobalSetRef(global_index) => self.exec_global_set::(*global_index), + + I32Const(val) => self.exec_const(*val), + I64Const(val) => self.exec_const(*val), + F32Const(val) => self.exec_const(*val), + F64Const(val) => self.exec_const(*val), + RefFunc(func_idx) => self.exec_const::(Some(*func_idx)), + RefNull(_) => self.exec_const::(None), + RefIsNull => self.exec_ref_is_null(), + + MemorySize(addr) => self.exec_memory_size(*addr), + MemoryGrow(addr) => self.exec_memory_grow(*addr), + + // Bulk memory operations + MemoryCopy(from, to) => self.exec_memory_copy(*from, *to).to_cf()?, + MemoryFill(addr) => self.exec_memory_fill(*addr).to_cf()?, + MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx).to_cf()?, + DataDrop(data_index) => self.exec_data_drop(*data_index), + ElemDrop(elem_index) => self.exec_elem_drop(*elem_index), + TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, + + I32Store { 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)?, + + I64Eqz => self.stack.values.replace_top::(|v| Ok(i32::from(v == 0))).to_cf()?, + I32Eqz => self.stack.values.replace_top_same::(|v| Ok(i32::from(v == 0))).to_cf()?, + I32Eq => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a == b))).to_cf()?, + I64Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, + F32Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, + F64Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, + + I32Ne => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a != b))).to_cf()?, + I64Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, + F32Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, + F64Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, + + I32LtS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a < b))).to_cf()?, + I64LtS => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, + I32LtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, + I64LtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, + F32Lt => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, + F64Lt => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, + + I32LeS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a <= b))).to_cf()?, + I64LeS => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, + I32LeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, + I64LeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, + F32Le => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, + F64Le => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, + + I32GeS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a >= b))).to_cf()?, + I64GeS => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, + I32GeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, + I64GeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, + F32Ge => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, + F64Ge => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, + + I32GtS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a > b))).to_cf()?, + I64GtS => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, + I32GtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, + I64GtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, + F32Gt => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, + F64Gt => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, + + I32Add => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_add(b))).to_cf()?, + I64Add => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_add(b))).to_cf()?, + F32Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + F64Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + + I32Sub => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_sub(b))).to_cf()?, + I64Sub => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_sub(b))).to_cf()?, + F32Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + F64Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + + F32Div => self.stack.values.calculate_same::(|a, b| Ok(a / b)).to_cf()?, + F64Div => self.stack.values.calculate_same::(|a, b| Ok(a / b)).to_cf()?, + + I32Mul => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_mul(b))).to_cf()?, + I64Mul => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_mul(b))).to_cf()?, + F32Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + F64Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + + I32DivS => self.stack.values.calculate_same::(|a, b| a.wasm_checked_div(b)).to_cf()?, + I64DivS => self.stack.values.calculate_same::(|a, b| a.wasm_checked_div(b)).to_cf()?, + I32DivU => self.stack.values.calculate_same::(|a, b| a.checked_div(b).ok_or_else(trap_0)).to_cf()?, + I64DivU => self.stack.values.calculate_same::(|a, b| a.checked_div(b).ok_or_else(trap_0)).to_cf()?, + I32RemS => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, + I64RemS => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, + I32RemU => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, + I64RemU => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, + + I32And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, + I64And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, + I32Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, + I64Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, + I32Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, + I64Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, + I32Shl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shl(b))).to_cf()?, + I64Shl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shl(b))).to_cf()?, + I32ShrS => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, + I64ShrS => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, + I32ShrU => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, + I64ShrU => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, + I32Rotl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotl(b))).to_cf()?, + I64Rotl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotl(b))).to_cf()?, + I32Rotr => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotr(b))).to_cf()?, + I64Rotr => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotr(b))).to_cf()?, + + I32Clz => self.stack.values.replace_top_same::(|v| Ok(v.leading_zeros() as i32)).to_cf()?, + I64Clz => self.stack.values.replace_top_same::(|v| Ok(v.leading_zeros() as i64)).to_cf()?, + I32Ctz => self.stack.values.replace_top_same::(|v| Ok(v.trailing_zeros() as i32)).to_cf()?, + I64Ctz => self.stack.values.replace_top_same::(|v| Ok(v.trailing_zeros() as i64)).to_cf()?, + I32Popcnt => self.stack.values.replace_top_same::(|v| Ok(v.count_ones() as i32)).to_cf()?, + I64Popcnt => self.stack.values.replace_top_same::(|v| Ok(v.count_ones() as i64)).to_cf()?, + + F32ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, + F32ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, + F64ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, + F64ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, + F32ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, + F32ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, + F64ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, + F64ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, + + I32Extend8S => self.stack.values.replace_top_same::(|v| Ok((v as i8) as i32)).to_cf()?, + I32Extend16S => self.stack.values.replace_top_same::(|v| Ok((v as i16) as i32)).to_cf()?, + I64Extend8S => self.stack.values.replace_top_same::(|v| Ok((v as i8) as i64)).to_cf()?, + I64Extend16S => self.stack.values.replace_top_same::(|v| Ok((v as i16) as i64)).to_cf()?, + I64Extend32S => self.stack.values.replace_top_same::(|v| Ok((v as i32) as i64)).to_cf()?, + I64ExtendI32U => self.stack.values.replace_top::(|v| Ok(v as i64)).to_cf()?, + I64ExtendI32S => self.stack.values.replace_top::(|v| Ok(v as i64)).to_cf()?, + I32WrapI64 => self.stack.values.replace_top::(|v| Ok(v as i32)).to_cf()?, + + F32DemoteF64 => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, + F64PromoteF32 => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, + + F32Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, + F64Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, + F32Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, + F64Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, + F32Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, + F64Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, + F32Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, + F64Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, + F32Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, + F64Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, + F32Nearest => self.stack.values.replace_top_same::(|v| Ok(v.tw_nearest())).to_cf()?, + F64Nearest => self.stack.values.replace_top_same::(|v| Ok(v.tw_nearest())).to_cf()?, + F32Sqrt => self.stack.values.replace_top_same::(|v| Ok(v.sqrt())).to_cf()?, + F64Sqrt => self.stack.values.replace_top_same::(|v| Ok(v.sqrt())).to_cf()?, + F32Min => self.stack.values.calculate_same::(|a, b| Ok(a.tw_minimum(b))).to_cf()?, + F64Min => self.stack.values.calculate_same::(|a, b| Ok(a.tw_minimum(b))).to_cf()?, + F32Max => self.stack.values.calculate_same::(|a, b| Ok(a.tw_maximum(b))).to_cf()?, + F64Max => self.stack.values.calculate_same::(|a, b| Ok(a.tw_maximum(b))).to_cf()?, + F32Copysign => self.stack.values.calculate_same::(|a, b| Ok(a.copysign(b))).to_cf()?, + F64Copysign => self.stack.values.calculate_same::(|a, b| Ok(a.copysign(b))).to_cf()?, + + I32TruncF32S => checked_conv_float!(f32, i32, self), + I32TruncF64S => checked_conv_float!(f64, i32, self), + I32TruncF32U => checked_conv_float!(f32, u32, i32, self), + I32TruncF64U => checked_conv_float!(f64, u32, i32, self), + I64TruncF32S => checked_conv_float!(f32, i64, self), + I64TruncF64S => checked_conv_float!(f64, i64, self), + I64TruncF32U => checked_conv_float!(f32, u64, i64, self), + I64TruncF64U => checked_conv_float!(f64, u64, i64, self), + + TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, + TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, + TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, + TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, + TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, + TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, + + I32TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32)).to_cf()?, + I32TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32)).to_cf()?, + I32TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32)).to_cf()?, + I32TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32)).to_cf()?, + I64TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64)).to_cf()?, + I64TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64)).to_cf()?, + I64TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64)).to_cf()?, + I64TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64)).to_cf()?, + + LocalCopy32(from, to) => self.exec_local_copy::(*from, *to), + LocalCopy64(from, to) => self.exec_local_copy::(*from, *to), + LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), + LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), + + instr => { + unreachable!("unimplemented instruction: {:?}", instr); + } + }; + + self.cf.incr_instr_ptr(); + ControlFlow::Continue(()) + } + + #[cold] + fn exec_unreachable(&self) -> ControlFlow> { + 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); + ControlFlow::Continue(()) + } + 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) + } + 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 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, + crate::Function::Host(host_func) => { + if unlikely(host_func.ty != *call_ty) { + return ControlFlow::Break(Some( + Trap::IndirectCallTypeMismatch { actual: host_func.ty.clone(), expected: call_ty.clone() } + .into(), + )); + } + + 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(()); + } + }; + + 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)) { + // truthy value is on the top of the stack, so enter the then block + if self.stack.values.pop::() != 0 { + self.enter_block(end_offset, BlockType::If, (params, results)); + return; + } + + // falsy value is on the top of the stack + if else_offset == 0 { + self.cf.jump(end_offset as usize); + return; + } + + self.cf.jump(else_offset as usize); + self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)); + } + fn exec_else(&mut self, end_offset: u32) { + self.exec_end_block(); + self.cf.jump(end_offset as usize); + } + fn resolve_functype(&self, idx: u32) -> (StackHeight, StackHeight) { + let ty = self.module.func_ty(idx); + ((&*ty.params).into(), (&*ty.results).into()) + } + fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { + self.stack.blocks.push(BlockFrame { + instr_ptr: self.cf.instr_ptr(), + end_instr_offset, + stack_ptr: self.stack.values.height(), + results, + params, + ty, + }); + } + fn exec_br(&mut self, to: u32) -> ControlFlow> { + if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { + return self.exec_return(); + } + + self.cf.incr_instr_ptr(); + ControlFlow::Continue(()) + } + fn exec_br_if(&mut self, to: u32) -> ControlFlow> { + if self.stack.values.pop::() != 0 + && self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() + { + return self.exec_return(); + } + self.cf.incr_instr_ptr(); + ControlFlow::Continue(()) + } + fn exec_brtable(&mut self, default: u32, len: u32) -> ControlFlow> { + let start = self.cf.instr_ptr() + 1; + let end = start + len as usize; + if end > self.cf.instructions().len() { + return ControlFlow::Break(Some(Error::Other(format!( + "br_table out of bounds: {} >= {}", + end, + self.cf.instructions().len() + )))); + } + + let idx = self.stack.values.pop::(); + let to = match self.cf.instructions()[start..end].get(idx as usize) { + None => default, + Some(Instruction::BrLabel(to)) => *to, + _ => return ControlFlow::Break(Some(Error::Other("br_table out of bounds".to_string()))), + }; + + if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { + return self.exec_return(); + } + + self.cf.incr_instr_ptr(); + ControlFlow::Continue(()) + } + fn exec_return(&mut self) -> ControlFlow> { + let old = self.cf.block_ptr(); + match self.stack.call_stack.pop() { + None => return ControlFlow::Break(None), + Some(cf) => self.cf = cf, + } + + if old > self.cf.block_ptr() { + self.stack.blocks.truncate(old); + } + + self.module.swap_with(self.cf.module_addr(), self.store); + ControlFlow::Continue(()) + } + fn exec_end_block(&mut self) { + let block = self.stack.blocks.pop(); + self.stack.values.truncate_keep(block.stack_ptr, block.results); + } + fn exec_local_get(&mut self, local_index: u16) { + let v = self.cf.locals.get::(local_index); + self.stack.values.push(v); + } + fn exec_local_set(&mut self, local_index: u16) { + let v = self.stack.values.pop::(); + self.cf.locals.set(local_index, v); + } + fn exec_local_tee(&mut self, local_index: u16) { + let v = self.stack.values.peek::(); + self.cf.locals.set(local_index, v); + } + + fn exec_global_get(&mut self, global_index: u32) { + self.stack.values.push_dyn(self.store.get_global_val(self.module.resolve_global_addr(global_index))); + } + fn exec_global_set(&mut self, global_index: u32) { + self.store.set_global_val(self.module.resolve_global_addr(global_index), self.stack.values.pop::().into()); + } + fn exec_const(&mut self, val: T) { + self.stack.values.push(val); + } + fn exec_ref_is_null(&mut self) { + let is_null = self.stack.values.pop::().is_none() as i32; + self.stack.values.push::(is_null); + } + + fn exec_memory_size(&mut self, addr: u32) { + let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)); + 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, + }); + } + + fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { + let size: i32 = self.stack.values.pop(); + let src: i32 = self.stack.values.pop(); + let dst: i32 = self.stack.values.pop(); + + if from == to { + let mem_from = self.store.get_mem_mut(self.module.resolve_mem_addr(from)); + // copy within the same memory + mem_from.copy_within(dst as usize, src as usize, size as usize)?; + } else { + // copy between two memories + let (mem_from, mem_to) = + self.store.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?; + + mem_from.copy_from_slice(dst as usize, mem_to.load(src as usize, size as usize)?)?; + } + Ok(()) + } + fn exec_memory_fill(&mut self, addr: u32) -> Result<()> { + let size: i32 = self.stack.values.pop(); + let val: i32 = self.stack.values.pop(); + let dst: i32 = self.stack.values.pop(); + + let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(addr)); + mem.fill(dst as usize, size as usize, val as u8) + } + fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { + let size: i32 = self.stack.values.pop(); + let offset: i32 = self.stack.values.pop(); + let dst: i32 = self.stack.values.pop(); + + let data = self + .store + .data + .datas + .get(self.module.resolve_data_addr(data_index) as usize) + .ok_or_else(|| Error::Other("data not found".to_string()))?; + + let mem = self + .store + .data + .memories + .get_mut(self.module.resolve_mem_addr(mem_index) as usize) + .ok_or_else(|| Error::Other("memory not found".to_string()))?; + + let data_len = data.data.as_ref().map_or(0, |d| d.len()); + + if unlikely(((size + offset) as usize > data_len) || ((dst + size) as usize > mem.len())) { + return Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len }.into()); + } + + if size == 0 { + return Ok(()); + } + + let Some(data) = &data.data else { return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()) }; + mem.store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)]) + } + fn exec_data_drop(&mut self, data_index: u32) { + self.store.get_data_mut(self.module.resolve_data_addr(data_index)).drop() + } + fn exec_elem_drop(&mut self, elem_index: u32) { + self.store.get_elem_mut(self.module.resolve_elem_addr(elem_index)).drop() + } + fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { + let size: i32 = self.stack.values.pop(); + let src: i32 = self.stack.values.pop(); + let dst: i32 = self.stack.values.pop(); + + if from == to { + // copy within the same memory + self.store.get_table_mut(self.module.resolve_table_addr(from)).copy_within( + dst as usize, + src as usize, + size as usize, + )?; + } else { + // copy between two memories + let (table_from, table_to) = + self.store.get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?; + 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>( + &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 val = self.stack.values.pop::() as u64; + let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { + cold(); + return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { + offset: val as usize, + len: LOAD_SIZE, + max: 0, + }))); + }; + let val = mem.load_as::(addr).to_cf()?; + self.stack.values.push(cast(val)); + ControlFlow::Continue(()) + } + fn exec_mem_store, const N: usize>( + &mut self, + mem_addr: tinywasm_types::MemAddr, + offset: u64, + cast: fn(T) -> U, + ) -> ControlFlow> { + let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); + let val = self.stack.values.pop::(); + let val = (cast(val)).to_mem_bytes(); + let addr = self.stack.values.pop::() as u64; + if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { + return ControlFlow::Break(Some(e)); + } + ControlFlow::Continue(()) + } + + fn exec_table_get(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)); + let idx: i32 = self.stack.values.pop::(); + let v = table.get_wasm_val(idx as u32)?; + self.stack.values.push_dyn(v.into()); + Ok(()) + } + fn exec_table_set(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + let val = self.stack.values.pop::(); + let idx = self.stack.values.pop::() as u32; + table.set(idx, val.into()) + } + fn exec_table_size(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table(self.module.resolve_table_addr(table_index)); + self.stack.values.push_dyn(table.size().into()); + Ok(()) + } + fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { + let size: i32 = self.stack.values.pop(); // n + let offset: i32 = self.stack.values.pop(); // s + let dst: i32 = self.stack.values.pop(); // d + + let elem = self + .store + .data + .elements + .get(self.module.resolve_elem_addr(elem_index) as usize) + .ok_or_else(|| Error::Other("element not found".to_string()))?; + + let table = self + .store + .data + .tables + .get_mut(self.module.resolve_table_addr(table_index) as usize) + .ok_or_else(|| Error::Other("table not found".to_string()))?; + + let elem_len = elem.items.as_ref().map_or(0, alloc::vec::Vec::len); + let table_len = table.size(); + + if unlikely(size < 0 || ((size + offset) as usize > elem_len) || ((dst + size) > table_len)) { + return Err(Trap::TableOutOfBounds { offset: offset as usize, len: size as usize, max: elem_len }.into()); + } + + if size == 0 { + return Ok(()); + } + + if let ElementKind::Active { .. } = elem.kind { + return Err(Error::Other("table.init with active element".to_string())); + } + + let Some(items) = elem.items.as_ref() else { + return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); + }; + + table.init(dst, &items[offset as usize..(offset + size) as usize]) + } + fn exec_table_grow(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + let sz = table.size(); + + let n = self.stack.values.pop::(); + let val = self.stack.values.pop::(); + + match table.grow(n, val.into()) { + Ok(_) => self.stack.values.push(sz), + Err(_) => self.stack.values.push(-1_i32), + } + + Ok(()) + } + fn exec_table_fill(&mut self, table_index: u32) -> Result<()> { + let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + + let n = self.stack.values.pop::(); + let val = self.stack.values.pop::(); + let i = self.stack.values.pop::(); + + if unlikely(i + n > table.size()) { + return Err(Error::Trap(Trap::TableOutOfBounds { + offset: i as usize, + len: n as usize, + max: table.size() as usize, + })); + } + + if n == 0 { + return Ok(()); + } + + table.fill(self.module.func_addrs(), i as usize, n as usize, val.into()) + } + + fn exec_local_copy(&mut self, from: u16, to: u16) { + let v = self.cf.locals.get::(from); + self.cf.locals.set(to, v); + } +} diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs new file mode 100644 index 0000000..0b7df2f --- /dev/null +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -0,0 +1,22 @@ +pub(crate) mod executor; +pub(crate) mod num_helpers; +pub(crate) mod stack; +mod values; + +#[cfg(not(feature = "std"))] +mod no_std_floats; + +use crate::{Result, Store}; +pub use values::*; + +/// The main `TinyWasm` runtime. +/// +/// This is the default runtime used by `TinyWasm`. +#[derive(Debug, Default)] +pub struct InterpreterRuntime {} + +impl InterpreterRuntime { + pub(crate) fn exec(&self, store: &mut Store, stack: &mut stack::Stack) -> Result<()> { + executor::Executor::new(store, stack)?.run_to_completion() + } +} diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs new file mode 100644 index 0000000..5b9471e --- /dev/null +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -0,0 +1,34 @@ +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 new file mode 100644 index 0000000..02fbc24 --- /dev/null +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -0,0 +1,182 @@ +pub(crate) trait TinywasmIntExt +where + Self: Sized, +{ + fn checked_wrapping_rem(self, rhs: Self) -> Result; + fn wasm_checked_div(self, rhs: Self) -> Result; +} + +/// Doing the actual conversion from float to int is a bit tricky, because +/// we need to check for overflow. This macro generates the min/max values +/// for a specific conversion, which are then used in the actual conversion. +/// Rust sadly doesn't have wrapping casts for floats yet, maybe never. +/// Alternatively, could be used for this but +/// it's not worth the dependency. +#[rustfmt::skip] +macro_rules! float_min_max { + (f32, i32) => {(-2147483904.0_f32, 2147483648.0_f32)}; + (f64, i32) => {(-2147483649.0_f64, 2147483648.0_f64)}; + (f32, u32) => {(-1.0_f32, 4294967296.0_f32)}; // 2^32 + (f64, u32) => {(-1.0_f64, 4294967296.0_f64)}; // 2^32 + (f32, i64) => {(-9223373136366403584.0_f32, 9223372036854775808.0_f32)}; // 2^63 + 2^40 | 2^63 + (f64, i64) => {(-9223372036854777856.0_f64, 9223372036854775808.0_f64)}; // 2^63 + 2^40 | 2^63 + (f32, u64) => {(-1.0_f32, 18446744073709551616.0_f32)}; // 2^64 + (f64, u64) => {(-1.0_f64, 18446744073709551616.0_f64)}; // 2^64 + // other conversions are not allowed + ($from:ty, $to:ty) => {compile_error!("invalid float conversion")}; +} + +/// Convert a value on the stack with error checking +macro_rules! checked_conv_float { + // Direct conversion with error checking (two types) + ($from:tt, $to:tt, $self:expr) => { + checked_conv_float!($from, $to, $to, $self) + }; + // Conversion with an intermediate unsigned type and error checking (three types) + ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { + $self + .stack + .values + .replace_top::<$from, $to>(|v| { + let (min, max) = float_min_max!($from, $intermediate); + if unlikely(v.is_nan()) { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + if unlikely(v <= min || v >= max) { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + Ok((v as $intermediate as $to).into()) + }) + .to_cf()? + }; +} + +pub(crate) use checked_conv_float; +pub(crate) use float_min_max; + +pub(super) fn trap_0() -> Error { + Error::Trap(crate::Trap::DivisionByZero) +} +pub(crate) trait TinywasmFloatExt { + fn tw_minimum(self, other: Self) -> Self; + fn tw_maximum(self, other: Self) -> Self; + fn tw_nearest(self) -> Self; +} + +use crate::{Error, Result}; + +#[cfg(not(feature = "std"))] +use super::no_std_floats::NoStdFloatExt; + +macro_rules! impl_wasm_float_ops { + ($($t:ty)*) => ($( + impl TinywasmFloatExt for $t { + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest + fn tw_nearest(self) -> Self { + match self { + x if x.is_nan() => x, // 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, + x => { + // Handle normal and halfway cases + let rounded = x.round(); + let diff = (x - rounded).abs(); + if diff != 0.5 || rounded % 2.0 == 0.0 { + return rounded + } + + rounded - x.signum() // Make even + } + } + } + + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmin + // Based on f32::minimum (which is not yet stable) + #[inline] + fn tw_minimum(self, other: Self) -> Self { + match self.partial_cmp(&other) { + 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 }, + None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + } + } + + // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax + // Based on f32::maximum (which is not yet stable) + #[inline] + fn tw_maximum(self, other: Self) -> Self { + match self.partial_cmp(&other) { + 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 }, + None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. + } + } + } + )*) +} + +impl_wasm_float_ops! { f32 f64 } + +pub(crate) trait WasmIntOps { + fn wasm_shl(self, rhs: Self) -> Self; + fn wasm_shr(self, rhs: Self) -> Self; + fn wasm_rotl(self, rhs: Self) -> Self; + fn wasm_rotr(self, rhs: Self) -> Self; +} + +macro_rules! impl_wrapping_self_sh { + ($($t:ty)*) => ($( + impl WasmIntOps for $t { + #[inline] + fn wasm_shl(self, rhs: Self) -> Self { + self.wrapping_shl(rhs as u32) + } + + #[inline] + fn wasm_shr(self, rhs: Self) -> Self { + self.wrapping_shr(rhs as u32) + } + + #[inline] + fn wasm_rotl(self, rhs: Self) -> Self { + self.rotate_left(rhs as u32) + } + + #[inline] + fn wasm_rotr(self, rhs: Self) -> Self { + self.rotate_right(rhs as u32) + } + } + )*) +} + +impl_wrapping_self_sh! { i32 i64 u32 u64 } + +macro_rules! impl_checked_wrapping_rem { + ($($t:ty)*) => ($( + impl TinywasmIntExt for $t { + #[inline] + fn checked_wrapping_rem(self, rhs: Self) -> Result { + if rhs == 0 { + Err(Error::Trap(crate::Trap::DivisionByZero)) + } else { + Ok(self.wrapping_rem(rhs)) + } + } + + #[inline] + fn wasm_checked_div(self, rhs: Self) -> Result { + if rhs == 0 { + Err(Error::Trap(crate::Trap::DivisionByZero)) + } else { + self.checked_div(rhs).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) + } + } + } + )*) +} + +impl_checked_wrapping_rem! { i32 i64 u32 u64 } diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs new file mode 100644 index 0000000..2267194 --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -0,0 +1,69 @@ +use crate::unlikely; +use alloc::vec::Vec; + +use crate::interpreter::values::{StackHeight, StackLocation}; + +#[derive(Debug)] +pub(crate) struct BlockStack(Vec); + +impl Default for BlockStack { + fn default() -> Self { + Self(Vec::with_capacity(128)) + } +} + +impl BlockStack { + #[inline(always)] + pub(crate) fn len(&self) -> usize { + self.0.len() + } + + #[inline(always)] + pub(crate) fn push(&mut self, block: BlockFrame) { + self.0.push(block); + } + + #[inline] + /// get the label at the given index, where 0 is the top of the stack + pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { + let len = (self.0.len() as u32) - offset; + + // the vast majority of wasm functions don't use break to return + if unlikely(index >= len) { + return None; + } + + Some(&self.0[self.0.len() - index as usize - 1]) + } + + #[inline(always)] + pub(crate) fn pop(&mut self) -> BlockFrame { + self.0.pop().expect("block stack underflow, this is a bug") + } + + /// keep the top `len` blocks and discard the rest + #[inline(always)] + pub(crate) fn truncate(&mut self, len: u32) { + self.0.truncate(len as usize); + } +} + +#[derive(Debug)] +pub(crate) struct BlockFrame { + pub(crate) instr_ptr: usize, // position of the instruction pointer when the block was entered + pub(crate) end_instr_offset: u32, // position of the end instruction of the block + + pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered + pub(crate) results: StackHeight, + pub(crate) params: StackHeight, + + pub(crate) ty: BlockType, +} + +#[derive(Debug)] +pub(crate) enum BlockType { + Loop, + If, + Else, + Block, +} diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs new file mode 100644 index 0000000..7c2be9c --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -0,0 +1,199 @@ +use core::ops::ControlFlow; + +use super::BlockType; +use crate::interpreter::values::*; +use crate::Trap; +use crate::{unlikely, Error}; + +use alloc::boxed::Box; +use alloc::{rc::Rc, vec, vec::Vec}; +use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmValue}; + +pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; + +#[derive(Debug)] +pub(crate) struct CallStack { + stack: Vec, +} + +impl CallStack { + #[inline] + pub(crate) fn new(initial_frame: CallFrame) -> Self { + Self { stack: vec![initial_frame] } + } + + #[inline(always)] + pub(crate) fn pop(&mut self) -> Option { + self.stack.pop() + } + + #[inline(always)] + 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())); + } + self.stack.push(call_frame); + ControlFlow::Continue(()) + } +} + +#[derive(Debug)] +pub(crate) struct CallFrame { + instr_ptr: usize, + func_instance: Rc, + block_ptr: u32, + module_addr: ModuleInstanceAddr, + pub(crate) locals: Locals, +} + +#[derive(Debug)] +pub(crate) struct Locals { + pub(crate) locals_32: Box<[Value32]>, + pub(crate) locals_64: Box<[Value64]>, + pub(crate) locals_128: Box<[Value128]>, + pub(crate) locals_ref: Box<[ValueRef]>, +} + +impl Locals { + pub(crate) fn get(&self, local_index: LocalAddr) -> T { + T::local_get(self, local_index) + } + + pub(crate) fn set(&mut self, local_index: LocalAddr, value: T) { + T::local_set(self, local_index, value) + } +} + +impl CallFrame { + #[inline(always)] + pub(crate) fn instr_ptr(&self) -> usize { + self.instr_ptr + } + + #[inline(always)] + pub(crate) fn incr_instr_ptr(&mut self) { + self.instr_ptr += 1; + } + + #[inline(always)] + pub(crate) fn jump(&mut self, offset: usize) { + self.instr_ptr += offset; + } + + #[inline(always)] + pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { + self.module_addr + } + + #[inline(always)] + 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] + } + + /// 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)] + pub(crate) fn break_to( + &mut self, + break_to_relative: u32, + values: &mut super::ValueStack, + blocks: &mut super::BlockStack, + ) -> Option<()> { + let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?; + + // instr_ptr points to the label instruction, but the next step + // will increment it by 1 since we're changing the "current" instr_ptr + match break_to.ty { + BlockType::Loop => { + // this is a loop, so we want to jump back to the start of the loop + self.instr_ptr = break_to.instr_ptr; + + // We also want to push the params to the stack + values.truncate_keep(break_to.stack_ptr, break_to.params); + + // check if we're breaking to the loop + if break_to_relative != 0 { + // we also want to trim the label stack to the loop (but not including the loop) + blocks.truncate(blocks.len() as u32 - break_to_relative); + return Some(()); + } + } + + BlockType::Block | BlockType::If | BlockType::Else => { + // this is a block, so we want to jump to the next instruction after the block ends + // We also want to push the block's results to the stack + values.truncate_keep(break_to.stack_ptr, break_to.results); + + // (the inst_ptr will be incremented by 1 before the next instruction is executed) + self.instr_ptr = break_to.instr_ptr + break_to.end_instr_offset as usize; + + // we also want to trim the label stack, including the block + blocks.truncate(blocks.len() as u32 - (break_to_relative + 1)); + } + } + + Some(()) + } + + #[inline(always)] + pub(crate) fn new( + wasm_func_inst: Rc, + owner: ModuleInstanceAddr, + params: &[WasmValue], + block_ptr: u32, + ) -> Self { + let locals = { + let mut locals_32 = Vec::new(); + locals_32.reserve_exact(wasm_func_inst.locals.c32 as usize); + let mut locals_64 = Vec::new(); + locals_64.reserve_exact(wasm_func_inst.locals.c64 as usize); + let mut locals_128 = Vec::new(); + locals_128.reserve_exact(wasm_func_inst.locals.c128 as usize); + let mut locals_ref = Vec::new(); + locals_ref.reserve_exact(wasm_func_inst.locals.cref as usize); + + for p in params { + match p.into() { + TinyWasmValue::Value32(v) => locals_32.push(v), + TinyWasmValue::Value64(v) => locals_64.push(v), + TinyWasmValue::Value128(v) => locals_128.push(v), + TinyWasmValue::ValueRef(v) => locals_ref.push(v), + } + } + + locals_32.resize_with(wasm_func_inst.locals.c32 as usize, Default::default); + locals_64.resize_with(wasm_func_inst.locals.c64 as usize, Default::default); + locals_128.resize_with(wasm_func_inst.locals.c128 as usize, Default::default); + locals_ref.resize_with(wasm_func_inst.locals.cref as usize, Default::default); + + Locals { + locals_32: locals_32.into_boxed_slice(), + locals_64: locals_64.into_boxed_slice(), + locals_128: locals_128.into_boxed_slice(), + locals_ref: locals_ref.into_boxed_slice(), + } + }; + + Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + } + + #[inline] + pub(crate) fn new_raw( + wasm_func_inst: Rc, + owner: ModuleInstanceAddr, + locals: Locals, + block_ptr: u32, + ) -> Self { + Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + } + + #[inline(always)] + pub(crate) fn instructions(&self) -> &[Instruction] { + &self.func_instance.instructions + } +} diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs new file mode 100644 index 0000000..c526c4a --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -0,0 +1,21 @@ +mod block_stack; +mod call_stack; +mod value_stack; + +pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; +pub(crate) use call_stack::{CallFrame, CallStack, Locals}; +pub(crate) use value_stack::ValueStack; + +/// A WebAssembly Stack +#[derive(Debug)] +pub(crate) struct Stack { + pub(crate) values: ValueStack, + pub(crate) blocks: BlockStack, + pub(crate) call_stack: CallStack, +} + +impl Stack { + pub(crate) fn new(call_frame: CallFrame) -> Self { + Self { values: ValueStack::new(), blocks: BlockStack::default(), call_stack: CallStack::new(call_frame) } + } +} diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs new file mode 100644 index 0000000..03c676e --- /dev/null +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -0,0 +1,186 @@ +use alloc::vec::Vec; +use tinywasm_types::{ValType, ValueCounts, ValueCountsSmall, WasmValue}; + +use crate::{interpreter::*, Result}; + +use super::Locals; +pub(crate) const STACK_32_SIZE: usize = 1024 * 32; +pub(crate) const STACK_64_SIZE: usize = 1024 * 16; +pub(crate) const STACK_128_SIZE: usize = 1024 * 8; +pub(crate) const STACK_REF_SIZE: usize = 1024; + +#[derive(Debug)] +pub(crate) struct ValueStack { + pub(crate) stack_32: Vec, + pub(crate) stack_64: Vec, + pub(crate) stack_128: Vec, + pub(crate) stack_ref: Vec, +} + +impl ValueStack { + pub(crate) fn new() -> Self { + Self { + stack_32: Vec::with_capacity(STACK_32_SIZE), + stack_64: Vec::with_capacity(STACK_64_SIZE), + stack_128: Vec::with_capacity(STACK_128_SIZE), + stack_ref: Vec::with_capacity(STACK_REF_SIZE), + } + } + + pub(crate) fn height(&self) -> StackLocation { + StackLocation { + s32: self.stack_32.len() as u32, + s64: self.stack_64.len() as u32, + s128: self.stack_128.len() as u32, + sref: self.stack_ref.len() as u32, + } + } + + #[inline] + pub(crate) fn peek(&self) -> T { + T::stack_peek(self) + } + + #[inline] + pub(crate) fn pop(&mut self) -> T { + T::stack_pop(self) + } + + #[inline] + pub(crate) fn push(&mut self, value: T) { + T::stack_push(self, value) + } + + #[inline] + pub(crate) fn drop(&mut self) { + T::stack_pop(self); + } + + #[inline] + pub(crate) fn select(&mut self) { + let cond: i32 = self.pop(); + let val2: T = self.pop(); + if cond == 0 { + self.drop::(); + self.push(val2); + } + } + + #[inline] + pub(crate) fn calculate_same(&mut self, func: fn(T, T) -> Result) -> Result<()> { + T::stack_calculate(self, func) + } + + #[inline] + pub(crate) fn calculate(&mut self, func: fn(T, T) -> Result) -> Result<()> { + let v2 = T::stack_pop(self); + let v1 = T::stack_pop(self); + U::stack_push(self, func(v1, v2)?); + Ok(()) + } + + #[inline] + pub(crate) fn replace_top(&mut self, func: fn(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<()> { + T::replace_top(self, func) + } + + pub(crate) fn pop_params(&mut self, val_types: &[ValType]) -> Vec { + val_types.iter().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>() + } + + pub(crate) fn pop_results(&mut self, val_types: &[ValType]) -> Vec { + let mut results = val_types.iter().rev().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>(); + results.reverse(); + results + } + + #[inline] + pub(crate) fn pop_locals(&mut self, pc: ValueCountsSmall, lc: ValueCounts) -> Locals { + Locals { + locals_32: { + let mut locals_32 = { alloc::vec![Value32::default(); lc.c32 as usize].into_boxed_slice() }; + locals_32[0..pc.c32 as usize] + .copy_from_slice(&self.stack_32[(self.stack_32.len() - pc.c32 as usize)..]); + self.stack_32.truncate(self.stack_32.len() - pc.c32 as usize); + locals_32 + }, + locals_64: { + let mut locals_64 = { alloc::vec![Value64::default(); lc.c64 as usize].into_boxed_slice() }; + locals_64[0..pc.c64 as usize] + .copy_from_slice(&self.stack_64[(self.stack_64.len() - pc.c64 as usize)..]); + self.stack_64.truncate(self.stack_64.len() - pc.c64 as usize); + locals_64 + }, + locals_128: { + let mut locals_128 = { alloc::vec![Value128::default(); lc.c128 as usize].into_boxed_slice() }; + locals_128[0..pc.c128 as usize] + .copy_from_slice(&self.stack_128[(self.stack_128.len() - pc.c128 as usize)..]); + self.stack_128.truncate(self.stack_128.len() - pc.c128 as usize); + locals_128 + }, + locals_ref: { + let mut locals_ref = { alloc::vec![ValueRef::default(); lc.cref as usize].into_boxed_slice() }; + locals_ref[0..pc.cref as usize] + .copy_from_slice(&self.stack_ref[(self.stack_ref.len() - pc.cref as usize)..]); + self.stack_ref.truncate(self.stack_ref.len() - pc.cref as usize); + locals_ref + }, + } + } + + pub(crate) fn truncate_keep(&mut self, to: StackLocation, keep: StackHeight) { + #[inline(always)] + fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { + let len = data.len() as u32; + if len <= n { + return; // No need to truncate if the current size is already less than or equal to total_to_keep + } + data.drain((n as usize)..(len - end_keep) as usize); + } + + truncate_keep(&mut self.stack_32, to.s32, u32::from(keep.s32)); + truncate_keep(&mut self.stack_64, to.s64, u32::from(keep.s64)); + truncate_keep(&mut self.stack_128, to.s128, u32::from(keep.s128)); + truncate_keep(&mut self.stack_ref, to.sref, u32::from(keep.sref)); + } + + pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) { + match value { + TinyWasmValue::Value32(v) => self.stack_32.push(v), + TinyWasmValue::Value64(v) => self.stack_64.push(v), + TinyWasmValue::Value128(v) => self.stack_128.push(v), + TinyWasmValue::ValueRef(v) => self.stack_ref.push(v), + } + } + + pub(crate) fn pop_wasmvalue(&mut self, val_type: ValType) -> WasmValue { + 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), + }, + } + } + + pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { + for value in values { + self.push_dyn(value.into()) + } + } +} diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs new file mode 100644 index 0000000..7b363a8 --- /dev/null +++ b/crates/tinywasm/src/interpreter/values.rs @@ -0,0 +1,236 @@ +use crate::Result; +use tinywasm_types::{LocalAddr, ValType, WasmValue}; + +use super::stack::{Locals, ValueStack}; + +pub(crate) type Value32 = u32; +pub(crate) type Value64 = u64; +pub(crate) type Value128 = u128; +pub(crate) type ValueRef = Option; + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A untyped WebAssembly value +pub enum TinyWasmValue { + /// A 32-bit value + Value32(Value32), + /// A 64-bit value + Value64(Value64), + /// A 128-bit value + Value128(Value128), + /// A reference value + ValueRef(ValueRef), +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct StackLocation { + pub(crate) s32: u32, + pub(crate) s64: u32, + pub(crate) s128: u32, + pub(crate) sref: u32, +} + +#[derive(Debug, Clone, Copy, Default)] +pub(crate) struct StackHeight { + pub(crate) s32: u16, + pub(crate) s64: u16, + pub(crate) s128: u16, + pub(crate) sref: u16, +} + +impl From for StackHeight { + fn from(value: ValType) -> Self { + match value { + ValType::I32 | ValType::F32 => Self { s32: 1, ..Default::default() }, + ValType::I64 | ValType::F64 => Self { s64: 1, ..Default::default() }, + ValType::V128 => Self { s128: 1, ..Default::default() }, + ValType::RefExtern | ValType::RefFunc => Self { sref: 1, ..Default::default() }, + } + } +} + +impl From<&[ValType]> for StackHeight { + fn from(value: &[ValType]) -> Self { + let mut s32 = 0; + let mut s64 = 0; + let mut s128 = 0; + let mut sref = 0; + for val_type in value { + match val_type { + ValType::I32 | ValType::F32 => s32 += 1, + ValType::I64 | ValType::F64 => s64 += 1, + ValType::V128 => s128 += 1, + ValType::RefExtern | ValType::RefFunc => sref += 1, + } + } + Self { s32, s64, s128, sref } + } +} + +impl TinyWasmValue { + /// Asserts that the value is a 32-bit value and returns it (panics if the value is the wrong size) + pub fn unwrap_32(&self) -> Value32 { + match self { + TinyWasmValue::Value32(v) => *v, + _ => unreachable!("Expected Value32"), + } + } + + /// Asserts that the value is a 64-bit value and returns it (panics if the value is the wrong size) + pub fn unwrap_64(&self) -> Value64 { + match self { + TinyWasmValue::Value64(v) => *v, + _ => unreachable!("Expected Value64"), + } + } + + /// Asserts that the value is a 128-bit value and returns it (panics if the value is the wrong size) + pub fn unwrap_128(&self) -> Value128 { + match self { + TinyWasmValue::Value128(v) => *v, + _ => unreachable!("Expected Value128"), + } + } + + /// Asserts that the value is a reference value and returns it (panics if the value is the wrong size) + pub fn unwrap_ref(&self) -> ValueRef { + match self { + TinyWasmValue::ValueRef(v) => *v, + _ => unreachable!("Expected ValueRef"), + } + } + + /// Attaches a type to the value (panics if the size of the value is not the same as the type) + pub fn attach_type(&self, ty: ValType) -> WasmValue { + match ty { + ValType::I32 => WasmValue::I32(self.unwrap_32() as i32), + ValType::I64 => WasmValue::I64(self.unwrap_64() as i64), + ValType::F32 => WasmValue::F32(f32::from_bits(self.unwrap_32())), + ValType::F64 => WasmValue::F64(f64::from_bits(self.unwrap_64())), + ValType::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), + }, + } + } +} + +impl From<&WasmValue> for TinyWasmValue { + fn from(value: &WasmValue) -> Self { + 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), + } + } +} + +impl From for TinyWasmValue { + fn from(value: WasmValue) -> Self { + TinyWasmValue::from(&value) + } +} + +mod sealed { + #[allow(unreachable_pub)] + pub trait Sealed {} +} + +pub(crate) trait InternalValue: sealed::Sealed + Into { + fn stack_push(stack: &mut ValueStack, value: Self); + fn replace_top(stack: &mut ValueStack, func: fn(Self) -> Result) -> Result<()> + where + Self: Sized; + fn stack_calculate(stack: &mut ValueStack, func: fn(Self, Self) -> Result) -> Result<()> + where + Self: Sized; + + fn stack_pop(stack: &mut ValueStack) -> Self + where + Self: Sized; + fn stack_peek(stack: &ValueStack) -> Self + where + Self: Sized; + fn local_get(locals: &Locals, index: LocalAddr) -> Self; + fn local_set(locals: &mut Locals, index: LocalAddr, value: Self); +} + +macro_rules! impl_internalvalue { + ($( $variant:ident, $stack:ident, $locals:ident, $internal:ty, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { + $( + impl sealed::Sealed for $outer {} + + impl From<$outer> for TinyWasmValue { + #[inline(always)] + fn from(value: $outer) -> Self { + TinyWasmValue::$variant($to_internal(value)) + } + } + + impl InternalValue for $outer { + #[inline(always)] + fn stack_push(stack: &mut ValueStack, value: Self) { + stack.$stack.push($to_internal(value)); + } + #[inline(always)] + fn stack_pop(stack: &mut ValueStack) -> Self { + ($to_outer)(stack.$stack.pop().expect("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")) + } + + #[inline(always)] + fn stack_calculate(stack: &mut ValueStack, func: fn(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(()) + } + + #[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 { + unreachable!("ValueStack underflow, this is a bug"); + } + } + + #[inline(always)] + fn local_get(locals: &Locals, index: LocalAddr) -> Self { + $to_outer(locals.$locals[index as usize]) + } + #[inline(always)] + fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) { + locals.$locals[index as usize] = $to_internal(value); + } + } + )* + }; +} + +impl_internalvalue! { + Value32, stack_32, locals_32, u32, u32, |v| v, |v| v + Value64, stack_64, locals_64, u64, u64, |v| v, |v| v + Value32, stack_32, locals_32, u32, i32, |v| v as u32, |v: u32| v as i32 + 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 +} diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index ec48acf..7038dd6 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -1,28 +1,39 @@ #![no_std] -#![forbid(unsafe_code)] #![doc(test( no_crate_inject, - attr( - deny(warnings, rust_2018_idioms), - allow(dead_code, unused_assignments, unused_variables) - ) + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -#![cfg_attr(nightly, feature(error_in_core))] +#![cfg_attr(feature = "nightly", feature(portable_simd))] +#![forbid(unsafe_code)] //! A tiny WebAssembly Runtime written in Rust //! -//! TinyWasm provides a minimal WebAssembly runtime for executing WebAssembly modules. -//! It currently supports a subset of the WebAssembly MVP specification and is intended -//! to be useful for embedded systems and other environments where a full-featured -//! runtime is not required. +//! `TinyWasm` provides a minimal WebAssembly runtime for executing WebAssembly modules. +//! It currently supports all features of the WebAssembly MVP specification and is +//! designed to be easy to use and integrate in other projects. //! -//! ## Getting Started +//! ## Features +//!- **`std`**\ +//! Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. +//!- **`logging`**\ +//! Enables logging using the `log` crate. This is enabled by default. +//!- **`parser`**\ +//! Enables the `tinywasm-parser` crate. This is enabled by default. +//!- **`archive`**\ +//! Enables pre-parsing of archives. This is enabled by default. //! +//! With all these features disabled, `TinyWasm` only depends on `core`, `alloc` and `libm`. +//! 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 //! WebAssembly module from bytes. This will parse the module and validate it, returning //! a [`Module`] that can be used to instantiate the module. //! +//! //! ```rust //! use tinywasm::{Store, Module}; //! @@ -38,29 +49,27 @@ //! // This will allocate the module and its globals into the store //! // and execute the module's start function. //! // Every ModuleInstance has its own ID space for functions, globals, etc. -//! let instance = module.instantiate(&mut store)?; +//! let instance = module.instantiate(&mut store, None)?; //! //! // Get a typed handle to the exported "add" function //! // Alternatively, you can use `instance.get_func` to get an untyped handle -//! // that takes and returns WasmValue types -//! let func = instance.get_typed_func::<(i32, i32), (i32,)>(&mut store, "add")?; +//! // that takes and returns [`WasmValue`]s +//! let func = instance.exported_func::<(i32, i32), i32>(&mut store, "add")?; //! let res = func.call(&mut store, (1, 2))?; //! -//! assert_eq!(res, (3,)); +//! assert_eq!(res, 3); //! # Ok::<(), tinywasm::Error>(()) //! ``` //! -//! ## Features -//! - `std` (default): Enables the use of `std` and `std::io` for parsing from files and streams. -//! - `logging` (default): Enables logging via the `log` crate. -//! - `parser` (default): Enables the `tinywasm_parser` crate for parsing WebAssembly modules. +//! For more examples, see the [`examples`](https://github.com/explodingcamera/tinywasm/tree/main/examples) directory. +//! +//! ## Imports //! -//! ## No-std support -//! TinyWasm supports `no_std` environments by disabling the `std` feature and registering -//! a custom allocator. This removes support for parsing from files and streams, -//! but otherwise the API is the same. +//! To provide imports to a module, you can use the [`Imports`] struct. +//! This struct allows you to register custom functions, globals, memories, tables, +//! and other modules to be linked into the module when it is instantiated. //! -//! Additionally, if you want proper error types, you must use a `nightly` compiler. +//! See the [`Imports`] documentation for more information. mod std; extern crate alloc; @@ -70,32 +79,37 @@ extern crate alloc; #[allow(clippy::single_component_path_imports)] use log; +// noop fallback if logging is disabled. #[cfg(not(feature = "logging"))] +#[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); + macro_rules! info ( ($($tt:tt)*) => {{}} ); + macro_rules! error ( ($($tt:tt)*) => {{}} ); pub(crate) use debug; + pub(crate) use error; + pub(crate) use info; } mod error; pub use error::*; - -mod store; -pub use store::*; - -mod module; -pub use module::Module; - -mod instance; +pub use func::{FuncHandle, FuncHandleTyped}; +pub use imports::*; pub use instance::ModuleInstance; - -mod export; -pub use export::ExportInstance; +pub use module::Module; +pub use reference::*; +pub use store::*; mod func; -pub use func::{FuncHandle, TypedFuncHandle}; +mod imports; +mod instance; +mod module; +mod reference; +mod store; -mod runtime; -pub use runtime::*; +/// Runtime for executing WebAssembly modules. +pub mod interpreter; +pub use interpreter::InterpreterRuntime; #[cfg(feature = "parser")] /// Re-export of [`tinywasm_parser`]. Requires `parser` feature. @@ -107,3 +121,13 @@ pub mod parser { pub mod types { pub use tinywasm_types::*; } + +#[cold] +pub(crate) fn cold() {} + +pub(crate) fn unlikely(b: bool) -> bool { + if b { + cold(); + }; + b +} diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 9580f9a..f6e8e04 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -1,24 +1,21 @@ +use crate::{Imports, ModuleInstance, Result, Store}; use tinywasm_types::TinyWasmModule; -use crate::{ModuleInstance, Result, Store}; - -#[derive(Debug)] /// A WebAssembly Module /// /// See -pub struct Module { - data: TinyWasmModule, -} +#[derive(Debug, Clone)] +pub struct Module(pub(crate) TinyWasmModule); impl From<&TinyWasmModule> for Module { fn from(data: &TinyWasmModule) -> Self { - Self { data: data.clone() } + Self(data.clone()) } } impl From for Module { fn from(data: TinyWasmModule) -> Self { - Self { data } + Self(data) } } @@ -50,28 +47,12 @@ 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::new` + /// If you want to run the start function yourself, use `ModuleInstance::instantiate` /// /// See - pub fn instantiate( - self, - store: &mut Store, - // imports: Option<()>, - ) -> Result { - let idx = store.next_module_instance_idx(); - - let func_addrs = store.add_funcs(self.data.funcs.into(), idx); - let instance = ModuleInstance::new( - self.data.func_types, - self.data.start_func, - self.data.exports, - func_addrs, - idx, - store.id(), - ); - - store.add_instance(instance.clone())?; - // let _ = instance.start(store)?; + pub fn instantiate(self, store: &mut Store, imports: Option) -> Result { + let instance = ModuleInstance::instantiate(store, self, imports)?; + let _ = instance.start(store)?; Ok(instance) } } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs new file mode 100644 index 0000000..3765098 --- /dev/null +++ b/crates/tinywasm/src/reference.rs @@ -0,0 +1,135 @@ +use core::ffi::CStr; + +use alloc::ffi::CString; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +use crate::{MemoryInstance, Result}; + +// This module essentially contains the public APIs to interact with the data stored in the store + +/// A reference to a memory instance +#[derive(Debug)] +pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); + +/// A borrowed reference to a memory instance +#[derive(Debug)] +pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance); + +impl<'a> MemoryRefLoad for MemoryRef<'a> { + /// 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> { + /// Load a slice of memory + fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.0.load(offset, len) + } +} + +impl MemoryRef<'_> { + /// Load a slice of memory + pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.0.load(offset, len) + } + + /// Load a slice of memory as a vector + pub fn load_vec(&self, offset: usize, len: usize) -> Result> { + self.load(offset, len).map(<[u8]>::to_vec) + } +} + +impl MemoryRefMut<'_> { + /// Load a slice of memory + pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { + self.0.load(offset, len) + } + + /// Load a slice of memory as a vector + pub fn load_vec(&self, offset: usize, len: usize) -> Result> { + self.load(offset, len).map(<[u8]>::to_vec) + } + + /// Grow the memory by the given number of pages + pub fn grow(&mut self, delta_pages: i32) -> Option { + self.0.grow(delta_pages) + } + + /// Get the current size of the memory in pages + pub fn page_count(&mut self) -> usize { + self.0.page_count + } + + /// Copy a slice of memory to another place in memory + pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> { + self.0.copy_within(src, dst, len) + } + + /// Fill a slice of memory with a value + pub fn fill(&mut self, offset: usize, len: usize, val: u8) -> Result<()> { + self.0.fill(offset, len, val) + } + + /// Store a slice of memory + pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> { + self.0.store(offset, len, data) + } +} + +#[doc(hidden)] +pub trait MemoryRefLoad { + fn load(&self, offset: usize, len: usize) -> Result<&[u8]>; + fn load_vec(&self, offset: usize, len: usize) -> Result> { + self.load(offset, len).map(<[u8]>::to_vec) + } +} + +/// Convenience methods for loading strings from memory +pub trait MemoryStringExt: MemoryRefLoad { + /// Load a C-style string from memory + fn load_cstr(&self, offset: usize, len: usize) -> Result<&CStr> { + let bytes = self.load(offset, len)?; + CStr::from_bytes_with_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) + } + + /// Load a C-style string from memory, stopping at the first nul byte + fn load_cstr_until_nul(&self, offset: usize, max_len: usize) -> Result<&CStr> { + let bytes = self.load(offset, max_len)?; + CStr::from_bytes_until_nul(bytes).map_err(|_| crate::Error::Other("Invalid C-style string".to_string())) + } + + /// Load a UTF-8 string from memory + fn load_string(&self, offset: usize, len: usize) -> Result { + let bytes = self.load(offset, len)?; + String::from_utf8(bytes.to_vec()).map_err(|_| crate::Error::Other("Invalid UTF-8 string".to_string())) + } + + /// Load a C-style string from memory + fn load_cstring(&self, offset: usize, len: usize) -> Result { + Ok(CString::from(self.load_cstr(offset, len)?)) + } + + /// Load a C-style string from memory, stopping at the first nul byte + fn load_cstring_until_nul(&self, offset: usize, max_len: usize) -> Result { + Ok(CString::from(self.load_cstr_until_nul(offset, max_len)?)) + } + + /// Load a JavaScript-style utf-16 string from memory + fn load_js_string(&self, offset: usize, len: usize) -> Result { + let bytes = self.load(offset, len)?; + let mut string = String::new(); + for i in 0..(len / 2) { + let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]); + string.push( + char::from_u32(u32::from(c)).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?, + ); + } + Ok(string) + } +} + +impl MemoryStringExt for MemoryRef<'_> {} +impl MemoryStringExt for MemoryRefMut<'_> {} diff --git a/crates/tinywasm/src/runtime/executor/macros.rs b/crates/tinywasm/src/runtime/executor/macros.rs deleted file mode 100644 index 5a9a570..0000000 --- a/crates/tinywasm/src/runtime/executor/macros.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! More generic macros for various instructions -//! -//! These macros are used to generate the actual instruction implementations. - -/// Convert the top value on the stack to a specific type -macro_rules! conv_1 { - ($from:ty, $to:ty, $stack:ident) => {{ - let a: $from = $stack.values.pop()?.into(); - $stack.values.push((a as $to).into()); - }}; -} - -/// Convert the unsigned value on the top of the stack to a specific type -macro_rules! conv_2 { - ($ty:ty, $uty:ty, $to:ty, $stack:ident) => {{ - let a: $ty = $stack.values.pop()?.into(); - $stack.values.push((a as $uty as $to).into()); - }}; -} - -/// Compare two values on the stack -macro_rules! comp { - ($op:tt, $ty:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - $stack.values.push(((a $op b) as i32).into()); - }}; -} - -/// Compare two values on the stack (cast to ty2 before comparison) -macro_rules! comp_cast { - ($op:tt, $ty:ty, $ty2:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - - // Cast to unsigned type before comparison - let a_unsigned: $ty2 = a as $ty2; - let b_unsigned: $ty2 = b as $ty2; - $stack.values.push(((a_unsigned $op b_unsigned) as i32).into()); - }}; -} - -/// Compare a value on the stack to zero -macro_rules! comp_zero { - ($op:tt, $ty:ty, $stack:ident) => {{ - let a: $ty = $stack.values.pop()?.into(); - $stack.values.push(((a $op 0) as i32).into()); - }}; -} - -/// Apply an arithmetic operation to two values on the stack -macro_rules! arithmetic { - ($op:tt, $ty:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - $stack.values.push((a $op b).into()); - }}; -} - -/// Apply an arithmetic operation to two values on the stack -macro_rules! checked_arithmetic { - ($op:ident, $ty:ty, $stack:ident, $trap:expr) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - let result = a.$op(b).ok_or_else(|| Error::Trap($trap))?; - $stack.values.push(result.into()); - }}; -} - -/// Apply an arithmetic operation to two values on the stack (cast to ty2 before operation) -macro_rules! checked_arithmetic_cast { - ($op:ident, $ty:ty, $ty2:ty, $stack:ident, $trap:expr) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - - // Cast to unsigned type before operation - let a_unsigned: $ty2 = a as $ty2; - let b_unsigned: $ty2 = b as $ty2; - - let result = a_unsigned.$op(b_unsigned).ok_or_else(|| Error::Trap($trap))?; - $stack.values.push((result as $ty).into()); - }}; -} - -pub(super) use arithmetic; -pub(super) use checked_arithmetic; -pub(super) use checked_arithmetic_cast; -pub(super) use comp; -pub(super) use comp_cast; -pub(super) use comp_zero; -pub(super) use conv_1; -pub(super) use conv_2; diff --git a/crates/tinywasm/src/runtime/executor/mod.rs b/crates/tinywasm/src/runtime/executor/mod.rs deleted file mode 100644 index 93ebbd2..0000000 --- a/crates/tinywasm/src/runtime/executor/mod.rs +++ /dev/null @@ -1,332 +0,0 @@ -use super::{DefaultRuntime, Stack}; -use crate::{ - get_label_args, - log::debug, - runtime::{BlockType, LabelFrame}, - CallFrame, Error, ModuleInstance, Result, Store, -}; -use alloc::vec::Vec; -use log::info; -use tinywasm_types::Instruction; - -mod macros; -use macros::*; - -impl DefaultRuntime { - pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack, module: ModuleInstance) -> Result<()> { - // The current call frame, gets updated inside of exec_one - let mut cf = stack.call_stack.pop()?; - - // The function to execute, gets updated from ExecResult::Call - let mut func = store.get_func(cf.func_ptr)?.clone(); - let mut instrs = func.instructions(); - - // TODO: we might be able to index into the instructions directly - // since the instruction pointer should always be in bounds - while let Some(instr) = instrs.get(cf.instr_ptr) { - match exec_one(&mut cf, instr, instrs, stack, store, &module)? { - // Continue execution at the new top of the call stack - ExecResult::Call => { - func = store.get_func(cf.func_ptr)?.clone(); - instrs = func.instructions(); - continue; - } - - // return from the function - ExecResult::Return => return Ok(()), - - // continue to the next instruction and increment the instruction pointer - ExecResult::Ok => { - cf.instr_ptr += 1; - } - - // trap the program - ExecResult::Trap(trap) => { - cf.instr_ptr += 1; - // push the call frame back onto the stack so that it can be resumed - // if the trap can be handled - stack.call_stack.push(cf); - return Err(Error::Trap(trap)); - } - } - } - - debug!("end of exec"); - debug!("stack: {:?}", stack.values); - debug!("insts: {:?}", instrs); - debug!("instr_ptr: {}", cf.instr_ptr); - Err(Error::FuncDidNotReturn) - } -} - -enum ExecResult { - Ok, - Return, - Call, - Trap(crate::Trap), -} - -/// Run a single step of the interpreter -/// A seperate function is used so later, we can more easily implement -/// a step-by-step debugger (using generators once they're stable?) -#[inline] -fn exec_one( - cf: &mut CallFrame, - instr: &Instruction, - instrs: &[Instruction], - stack: &mut Stack, - store: &mut Store, - module: &ModuleInstance, -) -> Result { - info!("ptr: {} instr: {:?}", cf.instr_ptr, instr); - - use tinywasm_types::Instruction::*; - match instr { - Nop => { /* do nothing */ } - Unreachable => return Ok(ExecResult::Trap(crate::Trap::Unreachable)), // we don't need to include the call frame here because it's already on the stack - Drop => stack.values.pop().map(|_| ())?, - Select => { - let cond: i32 = stack.values.pop()?.into(); - let val2 = stack.values.pop()?; - - // if cond != 0, we already have the right value on the stack - if cond == 0 { - let _ = stack.values.pop()?; - stack.values.push(val2); - } - } - Call(v) => { - debug!("start call"); - // prepare the call frame - let func = store.get_func(*v as usize)?; - let func_ty = module.func_ty(*v); - - debug!("params: {:?}", func_ty.params); - debug!("stack: {:?}", stack.values); - let params = stack.values.pop_n(func_ty.params.len())?; - let call_frame = CallFrame::new_raw(*v as usize, ¶ms, func.locals().to_vec()); - - // push the call frame - cf.instr_ptr += 1; // skip the call instruction - stack.call_stack.push(cf.clone()); - stack.call_stack.push(call_frame); - - // call the function - *cf = stack.call_stack.pop()?; - debug!("calling: {:?}", func); - return Ok(ExecResult::Call); - } - - Return => todo!("called function returned"), - - If(args, else_offset, end_offset) => { - let end_instr_ptr = cf.instr_ptr + *end_offset; - - info!( - "if it's true, we'll jump to the next instruction (@{})", - cf.instr_ptr + 1 - ); - - if let Some(else_offset) = else_offset { - info!( - "else: {:?} (@{})", - instrs[cf.instr_ptr + else_offset], - cf.instr_ptr + else_offset - ); - }; - - info!("end: {:?} (@{})", instrs[end_instr_ptr], end_instr_ptr); - - if stack.values.pop_t::()? != 0 { - cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), // - params, - args: get_label_args(*args, module)?, - ty: BlockType::If, - }, - &mut stack.values, - ) - } - } - - Loop(args, end_offset) => { - // let params = stack.values.pop_block_params(*args, &module)?; - cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), // - params, - args: get_label_args(*args, module)?, - ty: BlockType::Loop, - }, - &mut stack.values, - ); - } - - Block(args, end_offset) => { - // let params = stack.values.pop_block_params(*args, &module)?; - cf.enter_label( - LabelFrame { - instr_ptr: cf.instr_ptr, - end_instr_ptr: cf.instr_ptr + *end_offset, - stack_ptr: stack.values.len(), //- params, - args: get_label_args(*args, module)?, - ty: BlockType::Block, - }, - &mut stack.values, - ); - } - - BrTable(_default, len) => { - let instr = instrs[cf.instr_ptr + 1..cf.instr_ptr + 1 + *len] - .iter() - .map(|i| match i { - BrLabel(l) => Ok(*l), - _ => panic!("Expected BrLabel, this should have been validated by the parser"), - }) - .collect::>>()?; - - if instr.len() != *len { - panic!("Expected {} BrLabel instructions, got {}", len, instr.len()); - } - - todo!() - } - - Br(v) => cf.break_to(*v, &mut stack.values)?, - BrIf(v) => { - if stack.values.pop_t::()? > 0 { - cf.break_to(*v, &mut stack.values)? - }; - } - - EndFunc => { - if cf.labels.len() > 0 { - panic!("endfunc: block frames not empty, this should have been validated by the parser"); - } - - match stack.call_stack.is_empty() { - true => return Ok(ExecResult::Return), - false => { - *cf = stack.call_stack.pop()?; - return Ok(ExecResult::Call); - } - } - } - - EndBlockFrame => { - let blocks = &mut cf.labels; - - // remove the label from the label stack - let Some(block) = blocks.pop() else { - panic!("end: no label to end, this should have been validated by the parser"); - }; - - let res_count = block.args.results; - info!("we want to keep {} values on the stack", res_count); - info!("current block stack ptr: {}", block.stack_ptr); - info!("stack: {:?}", stack.values); - - // trim the lable's stack from the stack - stack.values.truncate_keep(block.stack_ptr, res_count) - } - - LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)), - LocalSet(local_index) => cf.set_local(*local_index as usize, stack.values.pop()?), - LocalTee(local_index) => cf.set_local(*local_index as usize, *stack.values.last()?), - - I32Const(val) => stack.values.push((*val).into()), - I64Const(val) => stack.values.push((*val).into()), - F32Const(val) => stack.values.push((*val).into()), - F64Const(val) => stack.values.push((*val).into()), - - I64Eqz => comp_zero!(==, i64, stack), - I32Eqz => comp_zero!(==, i32, stack), - - I32Eq => comp!(==, i32, stack), - I64Eq => comp!(==, i64, stack), - F32Eq => comp!(==, f32, stack), - F64Eq => comp!(==, f64, stack), - - I32Ne => comp!(!=, i32, stack), - I64Ne => comp!(!=, i64, stack), - F32Ne => comp!(!=, f32, stack), - F64Ne => comp!(!=, f64, stack), - - I32LtS => comp!(<, i32, stack), - I64LtS => comp!(<, i64, stack), - I32LtU => comp_cast!(<, i32, u32, stack), - I64LtU => comp_cast!(<, i64, u64, stack), - F32Lt => comp!(<, f32, stack), - F64Lt => comp!(<, f64, stack), - - I32LeS => comp!(<=, i32, stack), - I64LeS => comp!(<=, i64, stack), - I32LeU => comp_cast!(<=, i32, u32, stack), - I64LeU => comp_cast!(<=, i64, u64, stack), - F32Le => comp!(<=, f32, stack), - F64Le => comp!(<=, f64, stack), - - I32GeS => comp!(>=, i32, stack), - I64GeS => comp!(>=, i64, stack), - I32GeU => comp_cast!(>=, i32, u32, stack), - I64GeU => comp_cast!(>=, i64, u64, stack), - F32Ge => comp!(>=, f32, stack), - F64Ge => comp!(>=, f64, stack), - - I32GtS => comp!(>, i32, stack), - I64GtS => comp!(>, i64, stack), - I32GtU => comp_cast!(>, i32, u32, stack), - I64GtU => comp_cast!(>, i64, u64, stack), - F32Gt => comp!(>, f32, stack), - F64Gt => comp!(>, f64, stack), - - I64Add => arithmetic!(+, i64, stack), - I32Add => arithmetic!(+, i32, stack), - F32Add => arithmetic!(+, f32, stack), - F64Add => arithmetic!(+, f64, stack), - - I32Sub => arithmetic!(-, i32, stack), - I64Sub => arithmetic!(-, i64, stack), - F32Sub => arithmetic!(-, f32, stack), - F64Sub => arithmetic!(-, f64, stack), - - F32Div => arithmetic!(/, f32, stack), - F64Div => arithmetic!(/, f64, stack), - - I32Mul => arithmetic!(*, i32, stack), - I64Mul => arithmetic!(*, i64, stack), - F32Mul => arithmetic!(*, f32, stack), - F64Mul => arithmetic!(*, f64, stack), - - // these can trap - I32DivS => checked_arithmetic!(checked_div, i32, stack, crate::Trap::DivisionByZero), - I64DivS => checked_arithmetic!(checked_div, i64, stack, crate::Trap::DivisionByZero), - I32DivU => checked_arithmetic_cast!(checked_div, i32, u32, stack, crate::Trap::DivisionByZero), - I64DivU => checked_arithmetic_cast!(checked_div, i64, u64, stack, crate::Trap::DivisionByZero), - - F32ConvertI32S => conv_1!(i32, f32, stack), - F32ConvertI64S => conv_1!(i64, f32, stack), - F64ConvertI32S => conv_1!(i32, f64, stack), - F64ConvertI64S => conv_1!(i64, f64, stack), - F32ConvertI32U => conv_2!(i32, u32, f32, stack), - F32ConvertI64U => conv_2!(i64, u64, f32, stack), - F64ConvertI32U => conv_2!(i32, u32, f64, stack), - F64ConvertI64U => conv_2!(i64, u64, f64, stack), - I64ExtendI32U => conv_2!(i32, u32, i64, stack), - I64ExtendI32S => conv_1!(i32, i64, stack), - I32WrapI64 => conv_1!(i64, i32, stack), - - // no-op instructions since types are erased at runtime - I32ReinterpretF32 => {} - I64ReinterpretF64 => {} - F32ReinterpretI32 => {} - F64ReinterpretI64 => {} - - i => todo!("{:?}", i), - }; - - Ok(ExecResult::Ok) -} diff --git a/crates/tinywasm/src/runtime/mod.rs b/crates/tinywasm/src/runtime/mod.rs deleted file mode 100644 index 610810f..0000000 --- a/crates/tinywasm/src/runtime/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod executor; -mod stack; -mod value; - -pub use stack::*; -pub(crate) use value::RawWasmValue; - -#[allow(rustdoc::private_intra_doc_links)] -/// A WebAssembly Runtime. -/// -/// Generic over `CheckTypes` to enable type checking at runtime. -/// This is useful for debugging, but should be disabled if you know -/// that the module is valid. -/// -/// See -/// -/// Execution is implemented in the [`crate::runtime::executor`] module -#[derive(Debug, Default)] -pub struct DefaultRuntime {} diff --git a/crates/tinywasm/src/runtime/stack.rs b/crates/tinywasm/src/runtime/stack.rs deleted file mode 100644 index 0912640..0000000 --- a/crates/tinywasm/src/runtime/stack.rs +++ /dev/null @@ -1,18 +0,0 @@ -mod blocks; -mod call_stack; -mod value_stack; - -use self::{call_stack::CallStack, value_stack::ValueStack}; -pub(crate) use blocks::{get_label_args, BlockType, LabelFrame}; -pub(crate) use call_stack::CallFrame; - -/// A WebAssembly Stack -#[derive(Debug, Default)] -pub struct Stack { - // keeping this typed for now to make it easier to debug - // TODO: Maybe split into Vec and Vec for better memory usage? - pub(crate) values: ValueStack, - - /// The call stack - pub(crate) call_stack: CallStack, -} diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs deleted file mode 100644 index 05c9043..0000000 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ /dev/null @@ -1,82 +0,0 @@ -use alloc::vec::Vec; -use log::info; -use tinywasm_types::BlockArgs; - -use crate::{ModuleInstance, Result}; - -#[derive(Debug, Default, Clone)] -pub(crate) struct Labels(Vec); - -impl Labels { - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - #[inline] - pub(crate) fn push(&mut self, block: LabelFrame) { - self.0.push(block); - } - - #[inline] - pub(crate) fn top(&self) -> Option<&LabelFrame> { - self.0.last() - } - - #[inline] - /// get the block at the given index, where 0 is the top of the stack - pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { - info!("get block: {}", index); - info!("blocks: {:?}", self.0); - self.0.get(self.0.len() - index - 1) - } - - #[inline] - pub(crate) fn pop(&mut self) -> Option { - self.0.pop() - } - - /// keep the top `len` blocks and discard the rest - #[inline] - pub(crate) fn truncate(&mut self, len: usize) { - self.0.truncate(len); - } -} - -#[derive(Debug, Clone)] -pub(crate) struct LabelFrame { - // position of the instruction pointer when the block was entered - pub(crate) instr_ptr: usize, - // position of the end instruction of the block - pub(crate) end_instr_ptr: usize, - - // position of the stack pointer when the block was entered - pub(crate) stack_ptr: usize, - pub(crate) args: LabelArgs, - pub(crate) ty: BlockType, -} - -#[derive(Debug, Copy, Clone)] -#[allow(dead_code)] -pub(crate) enum BlockType { - Loop, - If, - Else, - Block, -} - -#[derive(Debug, Clone)] -pub(crate) struct LabelArgs { - pub(crate) params: usize, - pub(crate) results: usize, -} - -pub(crate) fn get_label_args(args: BlockArgs, module: &ModuleInstance) -> Result { - Ok(match args { - BlockArgs::Empty => LabelArgs { params: 0, results: 0 }, - BlockArgs::Type(_) => LabelArgs { params: 0, results: 1 }, - BlockArgs::FuncType(t) => LabelArgs { - params: module.func_ty(t).params.len(), - results: module.func_ty(t).results.len(), - }, - }) -} diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs deleted file mode 100644 index bf45753..0000000 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::{runtime::RawWasmValue, BlockType, Error, LabelFrame, Result}; -use alloc::{boxed::Box, vec::Vec}; -use tinywasm_types::{ValType, WasmValue}; - -use super::blocks::Labels; - -// minimum call stack size -const CALL_STACK_SIZE: usize = 1024; -const CALL_STACK_MAX_SIZE: usize = 1024 * 1024; - -#[derive(Debug)] -pub(crate) struct CallStack { - stack: Vec, - top: usize, -} - -impl Default for CallStack { - fn default() -> Self { - Self { - stack: Vec::with_capacity(CALL_STACK_SIZE), - top: 0, - } - } -} - -impl CallStack { - pub(crate) fn is_empty(&self) -> bool { - self.top == 0 - } - - pub(crate) fn pop(&mut self) -> Result { - assert!(self.top <= self.stack.len()); - if self.top == 0 { - return Err(Error::CallStackEmpty); - } - - self.top -= 1; - Ok(self.stack.pop().unwrap()) - } - - #[inline] - pub(crate) fn _top(&self) -> Result<&CallFrame> { - assert!(self.top <= self.stack.len()); - if self.top == 0 { - return Err(Error::CallStackEmpty); - } - Ok(&self.stack[self.top - 1]) - } - - #[inline] - pub(crate) fn _top_mut(&mut self) -> Result<&mut CallFrame> { - assert!(self.top <= self.stack.len()); - if self.top == 0 { - return Err(Error::CallStackEmpty); - } - Ok(&mut self.stack[self.top - 1]) - } - - #[inline] - pub(crate) fn push(&mut self, call_frame: CallFrame) { - assert!(self.top <= self.stack.len()); - assert!(self.stack.len() <= CALL_STACK_MAX_SIZE); - - self.top += 1; - self.stack.push(call_frame); - } -} - -#[derive(Debug, Clone)] -pub(crate) struct CallFrame { - // having real pointers here would be nice :( but we can't really do that in safe rust - pub(crate) instr_ptr: usize, - pub(crate) func_ptr: usize, - - pub(crate) labels: Labels, - pub(crate) locals: Box<[RawWasmValue]>, - pub(crate) local_count: usize, -} - -impl CallFrame { - #[inline] - /// Push a new label to the label stack and ensure the stack has the correct values - pub(crate) fn enter_label(&mut self, label_frame: LabelFrame, stack: &mut super::ValueStack) { - if label_frame.args.params > 0 { - stack.extend_from_within((label_frame.stack_ptr - label_frame.args.params)..label_frame.stack_ptr); - } - - self.labels.push(label_frame); - } - - /// Break to a block at the given index (relative to the current frame) - #[inline] - pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Result<()> { - let current_label = self.labels.top().ok_or(Error::LabelStackUnderflow)?; - let break_to = self - .labels - .get_relative_to_top(break_to_relative as usize) - .ok_or(Error::LabelStackUnderflow)?; - - // trim the lable's stack from the stack - value_stack.truncate_keep(break_to.stack_ptr, break_to.args.results); - - // instr_ptr points to the label instruction, but the next step - // will increment it by 1 since we're changing the "current" instr_ptr - match break_to.ty { - BlockType::Loop => { - // this is a loop, so we want to jump back to the start of the loop - self.instr_ptr = break_to.instr_ptr; - - // we also want to trim the label stack to the loop (but not including the loop) - self.labels.truncate(self.labels.len() - break_to_relative as usize); - } - BlockType::Block => { - // this is a block, so we want to jump to the next instruction after the block ends (the inst_ptr will be incremented by 1 before the next instruction is executed) - self.instr_ptr = break_to.end_instr_ptr; - - // we also want to trim the label stack, including the block - self.labels - .truncate(self.labels.len() - (break_to_relative as usize + 1)); - } - _ => unimplemented!("break to block type: {:?}", current_label.ty), - } - - // self.instr_ptr = block_frame.instr_ptr; - // value_stack.trim(block_frame.stack_ptr); - - // // // Adjusting how to trim the blocks stack based on the block type - // // let trim_index = match block_frame.block { - // // // if we are breaking to a loop, we want to jump back to the start of the loop - // // BlockFrameInner::Loop => block_index as usize - 1, - // // // if we are breaking to any other block, we want to jump to the end of the block - // // // TODO: check if this is correct - // // BlockFrameInner::If | BlockFrameInner::Else | BlockFrameInner::Block => block_index as usize - 1, - // // }; - - // self.block_frames.trim(block_index as usize); - Ok(()) - } - - pub(crate) fn new_raw(func_ptr: usize, params: &[RawWasmValue], local_types: Vec) -> Self { - let mut locals = Vec::with_capacity(local_types.len() + params.len()); - locals.extend(params.iter().cloned()); - locals.extend(local_types.iter().map(|_| RawWasmValue::default())); - - Self { - instr_ptr: 0, - func_ptr, - local_count: locals.len(), - locals: locals.into_boxed_slice(), - labels: Labels::default(), - } - } - - pub(crate) fn new(func_ptr: usize, params: &[WasmValue], local_types: Vec) -> Self { - CallFrame::new_raw( - func_ptr, - ¶ms.iter().map(|v| RawWasmValue::from(*v)).collect::>(), - local_types, - ) - } - - #[inline] - pub(crate) fn set_local(&mut self, local_index: usize, value: RawWasmValue) { - if local_index >= self.local_count { - panic!("Invalid local index"); - } - - self.locals[local_index] = value; - } - - #[inline] - pub(crate) fn get_local(&self, local_index: usize) -> RawWasmValue { - if local_index >= self.local_count { - panic!("Invalid local index"); - } - - self.locals[local_index] - } -} diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs deleted file mode 100644 index 694eb29..0000000 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ /dev/null @@ -1,171 +0,0 @@ -use core::ops::Range; - -use crate::{runtime::RawWasmValue, Error, Result}; -use alloc::vec::Vec; -use log::info; - -// minimum stack size -pub(crate) const STACK_SIZE: usize = 1024; - -#[derive(Debug)] -pub(crate) struct ValueStack { - stack: Vec, - - // TODO: don't pop the stack, just keep track of the top for better performance - top: usize, -} - -impl Default for ValueStack { - fn default() -> Self { - Self { - stack: Vec::with_capacity(STACK_SIZE), - top: 0, - } - } -} - -impl ValueStack { - #[cfg(test)] - pub(crate) fn data(&self) -> &[RawWasmValue] { - &self.stack - } - - #[inline] - pub(crate) fn extend_from_within(&mut self, range: Range) { - self.top += range.len(); - self.stack.extend_from_within(range); - } - - #[inline] - pub(crate) fn len(&self) -> usize { - assert!(self.top <= self.stack.len()); - self.top - } - - #[inline] - pub(crate) fn _truncate(&mut self, n: usize) { - assert!(self.top <= self.stack.len()); - self.top -= n; - self.stack.truncate(self.top); - } - - #[inline] - // example: [1, 2, 3] n=1, end_keep=1 => [1, 3] - // example: [1] n=1, end_keep=1 => [1] - pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { - if n == end_keep || n == 0 { - return; - } - - assert!(self.top <= self.stack.len()); - info!("removing from {} to {}", self.top - n, self.top - end_keep); - self.stack.drain(self.top - n..self.top - end_keep); - self.top -= n - end_keep; - } - - #[inline] - pub(crate) fn _extend(&mut self, values: impl IntoIterator + ExactSizeIterator) { - self.top += values.len(); - self.stack.extend(values); - } - - #[inline] - pub(crate) fn push(&mut self, value: RawWasmValue) { - self.top += 1; - self.stack.push(value); - } - - #[inline] - pub(crate) fn last(&self) -> Result<&RawWasmValue> { - self.stack.last().ok_or(Error::StackUnderflow) - } - - #[inline] - pub(crate) fn pop_t>(&mut self) -> Result { - self.top -= 1; - Ok(self.stack.pop().ok_or(Error::StackUnderflow)?.into()) - } - - #[inline] - pub(crate) fn pop(&mut self) -> Result { - self.top -= 1; - self.stack.pop().ok_or(Error::StackUnderflow) - } - - #[inline] - pub(crate) fn pop_n(&mut self, n: usize) -> Result> { - if self.top < n { - return Err(Error::StackUnderflow); - } - self.top -= n; - let res = self.stack.drain(self.top..).rev().collect::>(); - Ok(res) - } - - #[inline] - pub(crate) fn pop_n_const(&mut self) -> Result<[RawWasmValue; N]> { - if self.top < N { - return Err(Error::StackUnderflow); - } - self.top -= N; - let mut res = [RawWasmValue::default(); N]; - for i in res.iter_mut().rev() { - *i = self.stack.pop().ok_or(Error::InvalidStore)?; - } - - Ok(res) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::std::panic; - - fn crate_stack + Copy>(data: &[T]) -> ValueStack { - let mut stack = ValueStack::default(); - stack._extend(data.iter().map(|v| (*v).into())); - stack - } - - fn assert_truncate_keep + Copy>(data: &[T], n: usize, end_keep: usize, expected: &[T]) { - let mut stack = crate_stack(data); - stack.truncate_keep(n, end_keep); - assert_eq!( - stack.data(), - expected.iter().map(|v| (*v).into()).collect::>().as_slice() - ); - } - - fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> crate::std::thread::Result { - let prev_hook = panic::take_hook(); - panic::set_hook(alloc::boxed::Box::new(|_| {})); - let result = panic::catch_unwind(f); - panic::set_hook(prev_hook); - result - } - - #[test] - fn test_truncate_keep() { - assert_truncate_keep(&[1, 2, 3], 1, 1, &[1, 2, 3]); - assert_truncate_keep(&[1], 1, 1, &[1]); - assert_truncate_keep(&[1, 2, 3], 2, 1, &[1, 3]); - assert_truncate_keep::(&[], 0, 0, &[]); - catch_unwind_silent(|| assert_truncate_keep(&[1, 2, 3], 4, 1, &[1, 3])).expect_err("should panic"); - } - - #[test] - fn test_value_stack() { - let mut stack = ValueStack::default(); - stack.push(1.into()); - stack.push(2.into()); - stack.push(3.into()); - assert_eq!(stack.len(), 3); - assert_eq!(stack.pop_t::().unwrap(), 3); - assert_eq!(stack.len(), 2); - assert_eq!(stack.pop_t::().unwrap(), 2); - assert_eq!(stack.len(), 1); - assert_eq!(stack.pop_t::().unwrap(), 1); - assert_eq!(stack.len(), 0); - } -} diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs deleted file mode 100644 index 16fb42c..0000000 --- a/crates/tinywasm/src/runtime/value.rs +++ /dev/null @@ -1,67 +0,0 @@ -use core::fmt::Debug; - -use tinywasm_types::{ValType, WasmValue}; - -/// A raw wasm value. -/// -/// This is the internal representation of all wasm values -/// -/// See [`WasmValue`] for the public representation. -#[derive(Clone, Copy, Default, PartialEq, Eq)] -pub struct RawWasmValue(u64); - -impl Debug for RawWasmValue { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "RawWasmValue({})", self.0 as i64) // cast to i64 so at least negative numbers for i32 and i64 are printed correctly - } -} - -impl RawWasmValue { - pub fn attach_type(self, ty: ValType) -> WasmValue { - match ty { - ValType::I32 => WasmValue::I32(self.0 as i32), - ValType::I64 => WasmValue::I64(self.0 as i64), - ValType::F32 => WasmValue::F32(f32::from_bits(self.0 as u32)), - ValType::F64 => WasmValue::F64(f64::from_bits(self.0)), - ValType::ExternRef => todo!(), - ValType::FuncRef => todo!(), - ValType::V128 => todo!(), - } - } -} - -impl From for RawWasmValue { - fn from(v: WasmValue) -> Self { - match v { - WasmValue::I32(i) => Self(i as u64), - WasmValue::I64(i) => Self(i as u64), - WasmValue::F32(i) => Self(i.to_bits() as u64), - WasmValue::F64(i) => Self(i.to_bits()), - } - } -} - -macro_rules! impl_from_raw_wasm_value { - ($type:ty, $to_raw:expr, $from_raw:expr) => { - // Implement From<$type> for RawWasmValue - impl From<$type> for RawWasmValue { - fn from(value: $type) -> Self { - #[allow(clippy::redundant_closure_call)] // the comiler will figure it out :) - Self($to_raw(value)) - } - } - - // Implement From for $type - impl From for $type { - fn from(value: RawWasmValue) -> Self { - #[allow(clippy::redundant_closure_call)] // the comiler will figure it out :) - $from_raw(value.0) - } - } - }; -} - -impl_from_raw_wasm_value!(i32, |x| x as u64, |x| x as i32); -impl_from_raw_wasm_value!(i64, |x| x as u64, |x| x as i64); -impl_from_raw_wasm_value!(f32, |x| f32::to_bits(x) as u64, |x| f32::from_bits(x as u32)); -impl_from_raw_wasm_value!(f64, f64::to_bits, f64::from_bits); diff --git a/crates/tinywasm/src/std.rs b/crates/tinywasm/src/std.rs index b77675b..f1e97a4 100644 --- a/crates/tinywasm/src/std.rs +++ b/crates/tinywasm/src/std.rs @@ -13,6 +13,6 @@ pub(crate) mod error { #[cfg(feature = "std")] pub(crate) use std::error::Error; - #[cfg(all(not(feature = "std"), nightly))] + #[cfg(all(not(feature = "std"), feature = "nightly"))] pub(crate) use core::error::Error; } diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs deleted file mode 100644 index 1a9d968..0000000 --- a/crates/tinywasm/src/store.rs +++ /dev/null @@ -1,145 +0,0 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; - -use alloc::{format, rc::Rc, vec::Vec}; -use tinywasm_types::{FuncAddr, Function, Instruction, ModuleInstanceAddr, TypeAddr, ValType}; - -use crate::{ - runtime::{self, DefaultRuntime}, - Error, ModuleInstance, Result, -}; - -// global store id counter -static STORE_ID: AtomicUsize = AtomicUsize::new(0); - -/// Global state that can be manipulated by WebAssembly programs -/// -/// Data should only be addressable by the module that owns it -/// -/// Note that the state doesn't do any garbage collection - so it will grow -/// indefinitely if you keep adding modules to it. When calling temporary -/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler) -/// -/// See -#[derive(Debug)] -pub struct Store { - id: usize, - module_instances: Vec, - module_instance_count: usize, - - pub(crate) data: StoreData, - pub(crate) runtime: Runtime, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum Runtime { - Default, -} - -impl Store { - /// Create a new store - pub fn new() -> Self { - Self::default() - } - - /// Create a new store with the given runtime - pub(crate) fn runtime(&self) -> runtime::DefaultRuntime { - match self.runtime { - Runtime::Default => DefaultRuntime::default(), - } - } -} - -impl PartialEq for Store { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Default for Store { - fn default() -> Self { - let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - - Self { - id, - module_instances: Vec::new(), - module_instance_count: 0, - data: StoreData::default(), - runtime: Runtime::Default, - } - } -} - -#[derive(Debug)] -/// A WebAssembly Function Instance -/// -/// See -pub struct FunctionInstance { - pub(crate) func: Function, - pub(crate) _module_instance: ModuleInstanceAddr, // index into store.module_instances -} - -impl FunctionInstance { - pub(crate) fn _module_instance_addr(&self) -> ModuleInstanceAddr { - self._module_instance - } - - pub(crate) fn locals(&self) -> &[ValType] { - &self.func.locals - } - - pub(crate) fn instructions(&self) -> &[Instruction] { - &self.func.instructions - } - - pub(crate) fn ty_addr(&self) -> TypeAddr { - self.func.ty - } -} - -#[derive(Debug, Default)] -/// Global state that can be manipulated by WebAssembly programs -pub(crate) struct StoreData { - pub(crate) funcs: Vec>, - // pub tables: Vec, - // pub mems: Vec, - // pub globals: Vec, - // pub elems: Vec, - // pub datas: Vec, -} - -impl Store { - /// Get the store's ID (unique per process) - pub fn id(&self) -> usize { - self.id - } - - pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { - self.module_instance_count as ModuleInstanceAddr - } - - /// Initialize the store with global state from the given module - pub(crate) fn add_instance(&mut self, instance: ModuleInstance) -> Result<()> { - self.module_instances.push(instance); - self.module_instance_count += 1; - Ok(()) - } - - pub(crate) fn add_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Vec { - let mut func_addrs = Vec::with_capacity(funcs.len()); - for func in funcs.into_iter() { - self.data.funcs.push(Rc::new(FunctionInstance { - func, - _module_instance: idx, - })); - func_addrs.push(idx as FuncAddr); - } - func_addrs - } - - pub(crate) fn get_func(&self, addr: usize) -> Result<&Rc> { - self.data - .funcs - .get(addr) - .ok_or_else(|| Error::Other(format!("function {} not found", addr))) - } -} diff --git a/crates/tinywasm/src/store/data.rs b/crates/tinywasm/src/store/data.rs new file mode 100644 index 0000000..935e761 --- /dev/null +++ b/crates/tinywasm/src/store/data.rs @@ -0,0 +1,21 @@ +use alloc::vec::Vec; +use tinywasm_types::*; + +/// A WebAssembly Data Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct DataInstance { + pub(crate) data: Option>, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl DataInstance { + pub(crate) fn new(data: Option>, owner: ModuleInstanceAddr) -> Self { + Self { data, _owner: owner } + } + + pub(crate) fn drop(&mut self) { + self.data.is_some().then(|| self.data.take()); + } +} diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs new file mode 100644 index 0000000..40301a5 --- /dev/null +++ b/crates/tinywasm/src/store/element.rs @@ -0,0 +1,23 @@ +use crate::TableElement; +use alloc::vec::Vec; +use tinywasm_types::*; + +/// A WebAssembly Element Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct ElementInstance { + pub(crate) kind: ElementKind, + pub(crate) items: Option>, // none is the element was dropped + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl ElementInstance { + pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option>) -> Self { + Self { kind, _owner: owner, items } + } + + pub(crate) fn drop(&mut self) { + self.items.is_some().then(|| self.items.take()); + } +} diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs new file mode 100644 index 0000000..ef370c2 --- /dev/null +++ b/crates/tinywasm/src/store/function.rs @@ -0,0 +1,18 @@ +use crate::Function; +use alloc::rc::Rc; +use tinywasm_types::*; + +#[derive(Debug, Clone)] +/// A WebAssembly Function Instance +/// +/// See +pub(crate) struct FunctionInstance { + pub(crate) func: Function, + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions +} + +impl FunctionInstance { + pub(crate) fn new_wasm(func: WasmFunction, owner: ModuleInstanceAddr) -> Self { + Self { func: Function::Wasm(Rc::new(func)), owner } + } +} diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs new file mode 100644 index 0000000..b7a47d6 --- /dev/null +++ b/crates/tinywasm/src/store/global.rs @@ -0,0 +1,19 @@ +use crate::interpreter::TinyWasmValue; +use core::cell::Cell; +use tinywasm_types::*; + +/// A WebAssembly Global Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct GlobalInstance { + pub(crate) value: Cell, + pub(crate) ty: GlobalType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl GlobalInstance { + pub(crate) fn new(ty: GlobalType, value: TinyWasmValue, owner: ModuleInstanceAddr) -> Self { + Self { ty, value: value.into(), _owner: owner } + } +} diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs new file mode 100644 index 0000000..b86435d --- /dev/null +++ b/crates/tinywasm/src/store/memory.rs @@ -0,0 +1,261 @@ +use alloc::vec; +use alloc::vec::Vec; +use tinywasm_types::{MemoryType, ModuleInstanceAddr}; + +use crate::{cold, log, Error, Result}; + +const PAGE_SIZE: usize = 65536; +const MAX_PAGES: usize = 65536; +const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; + +/// A WebAssembly Memory Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct MemoryInstance { + pub(crate) kind: MemoryType, + pub(crate) data: Vec, + pub(crate) page_count: usize, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl MemoryInstance { + pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { + assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); + log::debug!("initializing memory with {} pages", kind.page_count_initial); + + Self { + kind, + data: vec![0; PAGE_SIZE * kind.page_count_initial as usize], + page_count: kind.page_count_initial as usize, + _owner: owner, + } + } + + #[inline(always)] + pub(crate) fn len(&self) -> usize { + self.data.len() + } + + #[inline(never)] + #[cold] + fn trap_oob(&self, addr: usize, len: usize) -> Error { + Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }) + } + + pub(crate) fn store(&mut self, addr: usize, len: usize, data: &[u8]) -> Result<()> { + let Some(end) = addr.checked_add(len) else { + cold(); + return Err(self.trap_oob(addr, data.len())); + }; + + if end > self.data.len() || end < addr { + cold(); + return Err(self.trap_oob(addr, data.len())); + } + self.data[addr..end].copy_from_slice(data); + Ok(()) + } + + pub(crate) fn max_pages(&self) -> usize { + self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize + } + + pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[u8]> { + let Some(end) = addr.checked_add(len) else { + cold(); + return Err(self.trap_oob(addr, len)); + }; + + if end > self.data.len() || end < addr { + cold(); + return Err(self.trap_oob(addr, len)); + } + + Ok(&self.data[addr..end]) + } + + pub(crate) fn load_as>(&self, addr: usize) -> Result { + let Some(end) = addr.checked_add(SIZE) else { + return Err(self.trap_oob(addr, SIZE)); + }; + + if end > self.data.len() { + return Err(self.trap_oob(addr, SIZE)); + } + + Ok(T::from_le_bytes(match self.data[addr..end].try_into() { + Ok(bytes) => bytes, + Err(_) => return Err(self.trap_oob(addr, SIZE)), + })) + } + + pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { + let end = addr.checked_add(len).ok_or_else(|| self.trap_oob(addr, len))?; + if end > self.data.len() { + return Err(self.trap_oob(addr, len)); + } + self.data[addr..end].fill_with(|| val); + Ok(()) + } + + pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[u8]) -> Result<()> { + let end = dst.checked_add(src.len()).ok_or_else(|| self.trap_oob(dst, src.len()))?; + if end > self.data.len() { + return Err(self.trap_oob(dst, src.len())); + } + + self.data[dst..end].copy_from_slice(src); + Ok(()) + } + + pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { + // Calculate the end of the source slice + let src_end = src.checked_add(len).ok_or_else(|| self.trap_oob(src, len))?; + if src_end > self.data.len() { + return Err(self.trap_oob(src, len)); + } + + // Calculate the end of the destination slice + let dst_end = dst.checked_add(len).ok_or_else(|| self.trap_oob(dst, len))?; + if dst_end > self.data.len() { + return Err(self.trap_oob(dst, len)); + } + + // Perform the copy + self.data.copy_within(src..src_end, dst); + Ok(()) + } + + #[inline] + pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { + let current_pages = self.page_count; + let new_pages = current_pages as i64 + pages_delta as i64; + debug_assert!(new_pages <= i32::MAX as i64, "page count should never be greater than i32::MAX"); + + if new_pages < 0 || new_pages > MAX_PAGES as i64 || new_pages as usize > self.max_pages() { + return None; + } + + let new_size = new_pages as usize * PAGE_SIZE; + if new_size as u64 > MAX_SIZE { + return None; + } + + // Zero initialize the new pages + self.data.reserve_exact(new_size); + self.data.resize_with(new_size, Default::default); + self.page_count = new_pages as usize; + Some(current_pages as i32) + } +} + +/// A trait for types that can be stored in memory +pub(crate) trait MemStorable { + /// Store a value in memory + fn to_mem_bytes(self) -> [u8; N]; +} + +/// A trait for types that can be loaded from memory +pub(crate) trait MemLoadable: Sized + Copy { + /// Load a value from memory + fn from_le_bytes(bytes: [u8; N]) -> Self; +} + +macro_rules! impl_mem_traits { + ($($type:ty, $size:expr),*) => { + $( + impl MemLoadable<$size> for $type { + #[inline(always)] + fn from_le_bytes(bytes: [u8; $size]) -> Self { + <$type>::from_le_bytes(bytes) + } + } + + impl MemStorable<$size> for $type { + #[inline(always)] + fn to_mem_bytes(self) -> [u8; $size] { + self.to_ne_bytes() + } + } + )* + } +} + +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); + +#[cfg(test)] +mod memory_instance_tests { + use super::*; + 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 owner = ModuleInstanceAddr::default(); + MemoryInstance::new(kind, owner) + } + + #[test] + fn test_memory_store_and_load() { + let mut memory = create_test_memory(); + let data_to_store = [1, 2, 3, 4]; + assert!(memory.store(0, 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); + } + + #[test] + fn test_memory_store_out_of_bounds() { + let mut memory = create_test_memory(); + let data_to_store = [1, 2, 3, 4]; + assert!(memory.store(memory.data.len(), data_to_store.len(), &data_to_store).is_err()); + } + + #[test] + fn test_memory_fill() { + let mut memory = create_test_memory(); + assert!(memory.fill(0, 10, 42).is_ok()); + assert_eq!(&memory.data[0..10], &[42; 10]); + } + + #[test] + fn test_memory_fill_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.fill(memory.data.len(), 10, 42).is_err()); + } + + #[test] + fn test_memory_copy_within() { + let mut memory = create_test_memory(); + memory.fill(0, 10, 1).unwrap(); + assert!(memory.copy_within(10, 0, 10).is_ok()); + assert_eq!(&memory.data[10..20], &[1; 10]); + } + + #[test] + fn test_memory_copy_within_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.copy_within(memory.data.len(), 0, 10).is_err()); + } + + #[test] + fn test_memory_grow() { + let mut memory = create_test_memory(); + let original_pages = memory.page_count; + assert_eq!(memory.grow(1), Some(original_pages as i32)); + assert_eq!(memory.page_count, original_pages + 1); + } + + #[test] + fn test_memory_grow_out_of_bounds() { + let mut memory = create_test_memory(); + assert!(memory.grow(MAX_PAGES as i32 + 1).is_none()); + } + + #[test] + fn test_memory_grow_max_pages() { + let mut memory = create_test_memory(); + assert_eq!(memory.grow(1), Some(1)); + assert_eq!(memory.grow(1), None); + } +} diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs new file mode 100644 index 0000000..124b7dc --- /dev/null +++ b/crates/tinywasm/src/store/mod.rs @@ -0,0 +1,473 @@ +use alloc::{boxed::Box, format, string::ToString, vec::Vec}; +use core::fmt::Debug; +use core::sync::atomic::{AtomicUsize, Ordering}; +use tinywasm_types::*; + +use crate::interpreter::{self, InterpreterRuntime, TinyWasmValue}; +use crate::{cold, Error, Function, ModuleInstance, Result, Trap}; + +mod data; +mod element; +mod function; +mod global; +mod memory; +mod table; + +pub(crate) use {data::*, element::*, function::*, global::*, memory::*, table::*}; + +// global store id counter +static STORE_ID: AtomicUsize = AtomicUsize::new(0); + +/// Global state that can be manipulated by WebAssembly programs +/// +/// Data should only be addressable by the module that owns it +/// +/// Note that the state doesn't do any garbage collection - so it will grow +/// indefinitely if you keep adding modules to it. When calling temporary +/// functions, you should create a new store and then drop it when you're done (e.g. in a request handler) +/// +/// See +pub struct Store { + id: usize, + module_instances: Vec, + + pub(crate) data: StoreData, + pub(crate) runtime: Runtime, +} + +impl Debug for Store { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Store") + .field("id", &self.id) + .field("module_instances", &self.module_instances) + .field("data", &"...") + .field("runtime", &self.runtime) + .finish() + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum Runtime { + Default, +} + +impl Store { + /// Create a new store + pub fn new() -> Self { + Self::default() + } + + /// Get a module instance by the internal id + pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { + self.module_instances.get(addr as usize) + } + + pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> ModuleInstance { + self.module_instances[addr as usize].clone() + } + + /// Create a new store with the given runtime + pub(crate) fn runtime(&self) -> interpreter::InterpreterRuntime { + match self.runtime { + Runtime::Default => InterpreterRuntime::default(), + } + } +} + +impl PartialEq for Store { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Default for Store { + fn default() -> Self { + let id = STORE_ID.fetch_add(1, Ordering::Relaxed); + Self { id, module_instances: Vec::new(), data: StoreData::default(), runtime: Runtime::Default } + } +} + +#[derive(Default)] +/// Global state that can be manipulated by WebAssembly programs +/// +/// Data should only be addressable by the module that owns it +/// See +pub(crate) struct StoreData { + pub(crate) funcs: Vec, + pub(crate) tables: Vec, + pub(crate) memories: Vec, + pub(crate) globals: Vec, + pub(crate) elements: Vec, + pub(crate) datas: Vec, +} + +impl Store { + /// Get the store's ID (unique per process) + pub fn id(&self) -> usize { + self.id + } + + pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { + self.module_instances.len() as ModuleInstanceAddr + } + + pub(crate) fn add_instance(&mut self, instance: ModuleInstance) { + assert!(instance.id() == self.module_instances.len() as ModuleInstanceAddr); + self.module_instances.push(instance); + } + + #[cold] + fn not_found_error(name: &str) -> Error { + Error::Other(format!("{name} not found")) + } + + /// Get the function at the actual index in the store + #[inline] + pub(crate) fn get_func(&self, addr: FuncAddr) -> &FunctionInstance { + &self.data.funcs[addr as usize] + } + + /// Get the memory at the actual index in the store + #[inline] + pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { + &self.data.memories[addr as usize] + } + + /// Get the memory at the actual index in the store + #[inline(always)] + pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { + &mut self.data.memories[addr as usize] + } + + /// Get the memory at the actual index in the store + #[inline(always)] + pub(crate) fn get_mems_mut( + &mut self, + addr: MemAddr, + addr2: MemAddr, + ) -> Result<(&mut MemoryInstance, &mut MemoryInstance)> { + match get_pair_mut(&mut self.data.memories, addr as usize, addr2 as usize) { + Some(mems) => Ok(mems), + None => { + cold(); + Err(Self::not_found_error("memory")) + } + } + } + + /// Get the table at the actual index in the store + #[inline] + pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { + &self.data.tables[addr as usize] + } + + /// Get the table at the actual index in the store + #[inline] + pub(crate) fn get_table_mut(&mut self, addr: TableAddr) -> &mut TableInstance { + &mut self.data.tables[addr as usize] + } + + /// Get two mutable tables at the actual index in the store + #[inline] + pub(crate) fn get_tables_mut( + &mut self, + addr: TableAddr, + addr2: TableAddr, + ) -> Result<(&mut TableInstance, &mut TableInstance)> { + match get_pair_mut(&mut self.data.tables, addr as usize, addr2 as usize) { + Some(tables) => Ok(tables), + None => { + cold(); + Err(Self::not_found_error("table")) + } + } + } + + /// Get the data at the actual index in the store + #[inline] + pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { + &mut self.data.datas[addr as usize] + } + + /// Get the element at the actual index in the store + #[inline] + pub(crate) fn get_elem_mut(&mut self, addr: ElemAddr) -> &mut ElementInstance { + &mut self.data.elements[addr as usize] + } + + /// Get the global at the actual index in the store + #[inline] + pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { + &self.data.globals[addr as usize] + } + + /// Get the global at the actual index in the store + #[doc(hidden)] + pub fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { + self.data.globals[addr as usize].value.get() + } + + /// Set the global at the actual index in the store + #[doc(hidden)] + pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { + self.data.globals[addr as usize].value.set(value); + } +} + +// Linking related functions +impl Store { + /// Add functions to the store, returning their addresses in the store + pub(crate) fn init_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Result> { + let func_count = self.data.funcs.len(); + let mut func_addrs = Vec::with_capacity(func_count); + for (i, func) in funcs.into_iter().enumerate() { + self.data.funcs.push(FunctionInstance::new_wasm(func, idx)); + func_addrs.push((i + func_count) as FuncAddr); + } + Ok(func_addrs) + } + + /// Add tables to the store, returning their addresses in the store + pub(crate) fn init_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Result> { + let table_count = self.data.tables.len(); + let mut table_addrs = Vec::with_capacity(table_count); + for (i, table) in tables.into_iter().enumerate() { + self.data.tables.push(TableInstance::new(table, idx)); + table_addrs.push((i + table_count) as TableAddr); + } + Ok(table_addrs) + } + + /// Add memories to the store, returning their addresses in the store + pub(crate) fn init_memories(&mut self, memories: Vec, idx: ModuleInstanceAddr) -> Result> { + let mem_count = self.data.memories.len(); + 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); + } + Ok(mem_addrs) + } + + /// Add globals to the store, returning their addresses in the store + pub(crate) fn init_globals( + &mut self, + mut imported_globals: Vec, + new_globals: Vec, + func_addrs: &[FuncAddr], + idx: ModuleInstanceAddr, + ) -> Result> { + let global_count = self.data.globals.len(); + imported_globals.reserve_exact(new_globals.len()); + let mut global_addrs = imported_globals; + + for (i, global) in new_globals.iter().enumerate() { + self.data.globals.push(GlobalInstance::new( + global.ty, + self.eval_const(&global.init, &global_addrs, func_addrs)?, + idx, + )); + global_addrs.push((i + global_count) as Addr); + } + + Ok(global_addrs) + } + + fn elem_addr(&self, item: &ElementItem, globals: &[Addr], funcs: &[FuncAddr]) -> Result> { + let res = match item { + ElementItem::Func(addr) | ElementItem::Expr(ConstInstruction::RefFunc(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::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")) + })?; + self.data.globals[addr as usize].value.get().unwrap_ref() + } + _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {item:?}"))), + }; + + Ok(res) + } + + /// Add elements to the store, returning their addresses in the store + /// Should be called after the tables have been added + pub(crate) fn init_elements( + &mut self, + table_addrs: &[TableAddr], + func_addrs: &[FuncAddr], + global_addrs: &[Addr], + elements: &[Element], + idx: ModuleInstanceAddr, + ) -> Result<(Box<[Addr]>, Option)> { + let elem_count = self.data.elements.len(); + let mut elem_addrs = Vec::with_capacity(elem_count); + for (i, element) in elements.iter().enumerate() { + let init = element + .items + .iter() + .map(|item| Ok(TableElement::from(self.elem_addr(item, global_addrs, func_addrs)?))) + .collect::>>()?; + + let items = match element.kind { + // doesn't need to be initialized, can be initialized lazily using the `table.init` instruction + ElementKind::Passive => Some(init), + + // this one is not available to the runtime but needs to be initialized to declare references + ElementKind::Declared => None, // a. Execute the instruction elm.drop i + + // 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 table_addr = table_addrs + .get(table as usize) + .copied() + .ok_or_else(|| Error::Other(format!("table {table} not found for element {i}")))?; + + let Some(table) = self.data.tables.get_mut(table_addr as usize) else { + return Err(Error::Other(format!("table {table} not found for element {i}"))); + }; + + // In wasm 2.0, it's possible to call a function that hasn't been instantiated yet, + // when using a partially initialized active element segments. + // This isn't mentioned in the spec, but the "unofficial" testsuite has a test for it: + // https://github.com/WebAssembly/testsuite/blob/5a1a590603d81f40ef471abba70a90a9ae5f4627/linking.wast#L264-L276 + // I have NO IDEA why this is allowed, but it is. + if let Err(Error::Trap(trap)) = table.init(offset, &init) { + return Ok((elem_addrs.into_boxed_slice(), Some(trap))); + } + + // f. Execute the instruction elm.drop i + None + } + }; + + self.data.elements.push(ElementInstance::new(element.kind, idx, items)); + elem_addrs.push((i + elem_count) as Addr); + } + + // this should be optimized out by the compiler + Ok((elem_addrs.into_boxed_slice(), None)) + } + + /// Add data to the store, returning their addresses in the store + pub(crate) fn init_datas( + &mut self, + mem_addrs: &[MemAddr], + datas: Vec, + idx: ModuleInstanceAddr, + ) -> Result<(Box<[Addr]>, Option)> { + let data_count = self.data.datas.len(); + let mut data_addrs = Vec::with_capacity(data_count); + for (i, data) in datas.into_iter().enumerate() { + let data_val = match data.kind { + tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { + let Some(mem_addr) = mem_addrs.get(mem_addr as usize) else { + return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); + }; + + let offset = self.eval_i32_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}"))); + }; + + match mem.store(offset as usize, data.data.len(), &data.data) { + Ok(()) => None, + Err(Error::Trap(trap)) => return Ok((data_addrs.into_boxed_slice(), Some(trap))), + Err(e) => return Err(e), + } + } + tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), + }; + + self.data.datas.push(DataInstance::new(data_val, idx)); + data_addrs.push((i + data_count) as Addr); + } + + // this should be optimized out by the compiler + Ok((data_addrs.into_boxed_slice(), None)) + } + + pub(crate) fn add_global(&mut self, ty: GlobalType, value: TinyWasmValue, idx: ModuleInstanceAddr) -> Result { + self.data.globals.push(GlobalInstance::new(ty, value, idx)); + Ok(self.data.globals.len() as Addr - 1) + } + + pub(crate) fn add_table(&mut self, table: TableType, idx: ModuleInstanceAddr) -> Result { + self.data.tables.push(TableInstance::new(table, idx)); + Ok(self.data.tables.len() as TableAddr - 1) + } + + pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { + if let MemoryArch::I64 = mem.arch { + return Err(Error::UnsupportedFeature("64-bit memories".to_string())); + } + self.data.memories.push(MemoryInstance::new(mem, idx)); + Ok(self.data.memories.len() as MemAddr - 1) + } + + pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { + self.data.funcs.push(FunctionInstance { func, owner: idx }); + Ok(self.data.funcs.len() as FuncAddr - 1) + } + + /// 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 + pub(crate) fn eval_const( + &self, + const_instr: &tinywasm_types::ConstInstruction, + module_global_addrs: &[Addr], + module_func_addrs: &[FuncAddr], + ) -> Result { + use tinywasm_types::ConstInstruction::*; + let val = match const_instr { + F32Const(f) => (*f).into(), + F64Const(f) => (*f).into(), + I32Const(i) => (*i).into(), + I64Const(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")) + })?; + + let global = + 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")) + })?)), + }; + Ok(val) + } +} + +// remove this when the `get_many_mut` function is stabilized +fn get_pair_mut(slice: &mut [T], i: usize, j: usize) -> Option<(&mut T, &mut T)> { + let (first, second) = (core::cmp::min(i, j), core::cmp::max(i, j)); + if i == j || second >= slice.len() { + return None; + } + let (_, tmp) = slice.split_at_mut(first); + let (x, rest) = tmp.split_at_mut(1); + let (_, y) = rest.split_at_mut(second - first - 1); + let pair = if i < j { (&mut x[0], &mut y[0]) } else { (&mut y[0], &mut x[0]) }; + Some(pair) +} diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs new file mode 100644 index 0000000..0e08582 --- /dev/null +++ b/crates/tinywasm/src/store/table.rs @@ -0,0 +1,257 @@ +use crate::log; +use crate::{Error, Result, Trap}; +use alloc::{vec, vec::Vec}; +use tinywasm_types::*; + +const MAX_TABLE_SIZE: u32 = 10_000_000; + +/// A WebAssembly Table Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct TableInstance { + pub(crate) elements: Vec, + pub(crate) kind: TableType, + pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl TableInstance { + pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { + Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } + } + + #[inline(never)] + #[cold] + fn trap_oob(&self, addr: usize, len: usize) -> Error { + Error::Trap(crate::Trap::TableOutOfBounds { offset: addr, len, max: self.elements.len() }) + } + + pub(crate) fn get_wasm_val(&self, addr: TableAddr) -> Result { + 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), + _ => Err(Error::UnsupportedFeature("non-ref table".into()))?, + }) + } + + pub(crate) fn fill(&mut self, func_addrs: &[u32], addr: usize, len: usize, val: TableElement) -> Result<()> { + let val = val.map(|addr| self.resolve_func_ref(func_addrs, addr)); + let end = addr.checked_add(len).ok_or_else(|| self.trap_oob(addr, len))?; + if end > self.elements.len() { + return Err(self.trap_oob(addr, len)); + } + + self.elements[addr..end].fill(val); + Ok(()) + } + + 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() }) + }) + } + + pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[TableElement]) -> Result<()> { + let end = dst.checked_add(src.len()).ok_or_else(|| self.trap_oob(dst, src.len()))?; + + if end > self.elements.len() { + return Err(self.trap_oob(dst, src.len())); + } + + self.elements[dst..end].copy_from_slice(src); + Ok(()) + } + + pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[TableElement]> { + let Some(end) = addr.checked_add(len) else { + return Err(self.trap_oob(addr, len)); + }; + + if end > self.elements.len() || end < addr { + return Err(self.trap_oob(addr, len)); + } + + Ok(&self.elements[addr..end]) + } + + pub(crate) fn copy_within(&mut self, dst: usize, src: usize, len: usize) -> Result<()> { + // Calculate the end of the source slice + let src_end = src.checked_add(len).ok_or_else(|| self.trap_oob(src, len))?; + if src_end > self.elements.len() { + return Err(self.trap_oob(src, len)); + } + + // Calculate the end of the destination slice + let dst_end = dst.checked_add(len).ok_or_else(|| self.trap_oob(dst, len))?; + if dst_end > self.elements.len() { + return Err(self.trap_oob(dst, len)); + } + + // Perform the copy + self.elements.copy_within(src..src_end, dst); + Ok(()) + } + + pub(crate) fn set(&mut self, table_idx: TableAddr, value: TableElement) -> Result<()> { + if table_idx as usize >= self.elements.len() { + return Err(self.trap_oob(table_idx as usize, 1)); + } + + self.elements[table_idx as usize] = value; + Ok(()) + } + + pub(crate) fn grow(&mut self, n: i32, init: TableElement) -> Result<()> { + if n < 0 { + return Err(Error::Trap(crate::Trap::TableOutOfBounds { offset: 0, len: 1, max: self.elements.len() })); + } + + let len = n as usize + self.elements.len(); + let max = self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize; + if len > max { + return Err(Error::Trap(crate::Trap::TableOutOfBounds { offset: len, len: 1, max: self.elements.len() })); + } + + self.elements.resize(len, init); + Ok(()) + } + + pub(crate) fn size(&self) -> i32 { + self.elements.len() as i32 + } + + fn resolve_func_ref(&self, func_addrs: &[u32], addr: Addr) -> Addr { + if self.kind.element_type != ValType::RefFunc { + return addr; + } + + *func_addrs + .get(addr as usize) + .expect("error initializing table: function not found. This should have been caught by the validator") + } + + pub(crate) fn init(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { + let offset = offset as usize; + let end = offset.checked_add(init.len()).ok_or_else(|| { + Error::Trap(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }) + })?; + + if end > self.elements.len() || end < offset { + return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); + } + + self.elements[offset..end].copy_from_slice(init); + log::debug!("table: {:?}", self.elements); + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum TableElement { + Uninitialized, + Initialized(TableAddr), +} + +impl From> for TableElement { + fn from(addr: Option) -> Self { + match addr { + None => TableElement::Uninitialized, + Some(addr) => TableElement::Initialized(addr), + } + } +} + +impl TableElement { + pub(crate) fn addr(&self) -> Option { + match self { + TableElement::Uninitialized => None, + TableElement::Initialized(addr) => Some(*addr), + } + } + + pub(crate) fn map Addr>(self, f: F) -> Self { + match self { + TableElement::Uninitialized => TableElement::Uninitialized, + TableElement::Initialized(addr) => TableElement::Initialized(f(addr)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Helper to create a dummy TableType + fn dummy_table_type() -> TableType { + TableType { element_type: ValType::RefFunc, size_initial: 10, size_max: Some(20) } + } + + #[test] + fn test_table_instance_creation() { + let kind = dummy_table_type(); + let table_instance = TableInstance::new(kind.clone(), 0); + assert_eq!(table_instance.size(), kind.size_initial as i32, "Table instance creation failed: size mismatch"); + } + + #[test] + fn test_get_wasm_val() { + let kind = dummy_table_type(); + let mut table_instance = TableInstance::new(kind, 0); + + table_instance.set(0, TableElement::Initialized(0)).expect("Setting table element failed"); + table_instance.set(1, TableElement::Uninitialized).expect("Setting table element failed"); + + match table_instance.get_wasm_val(0) { + Ok(WasmValue::RefFunc(_)) => {} + _ => panic!("get_wasm_val failed to return the correct WasmValue"), + } + + match table_instance.get_wasm_val(1) { + Ok(WasmValue::RefNull(ValType::RefFunc)) => {} + _ => panic!("get_wasm_val failed to return the correct WasmValue"), + } + + match table_instance.get_wasm_val(999) { + Err(Error::Trap(Trap::TableOutOfBounds { .. })) => {} + _ => panic!("get_wasm_val failed to handle undefined element correctly"), + } + } + + #[test] + fn test_set_and_get() { + let kind = dummy_table_type(); + let mut table_instance = TableInstance::new(kind, 0); + + let result = table_instance.set(0, TableElement::Initialized(1)); + assert!(result.is_ok(), "Setting table element failed"); + + let elem = table_instance.get(0); + assert!( + elem.is_ok() && matches!(elem.unwrap(), &TableElement::Initialized(1)), + "Getting table element failed or returned incorrect value" + ); + } + + #[test] + fn test_table_init() { + let kind = dummy_table_type(); + let mut table_instance = TableInstance::new(kind, 0); + + let init_elements = vec![TableElement::Initialized(0); 5]; + let result = table_instance.init(0, &init_elements); + + assert!(result.is_ok(), "Initializing table with elements failed"); + + for i in 0..5 { + let elem = table_instance.get(i); + assert!( + elem.is_ok() && matches!(elem.unwrap(), &TableElement::Initialized(_)), + "Element not initialized correctly at index {}", + i + ); + } + } +} diff --git a/crates/tinywasm/tests/charts/mod.rs b/crates/tinywasm/tests/charts/mod.rs deleted file mode 100644 index fba287b..0000000 --- a/crates/tinywasm/tests/charts/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod progress; -pub use progress::create_progress_chart; diff --git a/crates/tinywasm/tests/charts/progress.rs b/crates/tinywasm/tests/charts/progress.rs deleted file mode 100644 index 0bc66a6..0000000 --- a/crates/tinywasm/tests/charts/progress.rs +++ /dev/null @@ -1,77 +0,0 @@ -use eyre::Result; -use plotters::prelude::*; -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; - -const FONT: &str = "Victor Mono"; - -pub fn create_progress_chart(csv_path: &Path, output_path: &Path) -> Result<()> { - let file = File::open(csv_path)?; - let reader = io::BufReader::new(file); - - let mut max_tests = 0; - let mut data: Vec = Vec::new(); - let mut versions: Vec = Vec::new(); - - for line in reader.lines() { - let line = line?; - let parts: Vec<&str> = line.split(',').collect(); - - if parts.len() > 3 { - let version = format!("v{}", parts[0]); - let passed: u32 = parts[1].parse()?; - let failed: u32 = parts[2].parse()?; - let total = failed + passed; - - if total > max_tests { - max_tests = total; - } - - versions.push(version); - data.push(passed); - } - } - - let root_area = SVGBackend::new(output_path, (1000, 400)).into_drawing_area(); - root_area.fill(&WHITE)?; - - let mut chart = ChartBuilder::on(&root_area) - .x_label_area_size(45) - .y_label_area_size(70) - .margin(10) - .margin_top(20) - .caption("MVP TESTSUITE", (FONT, 30.0, FontStyle::Bold)) - .build_cartesian_2d((0..(versions.len() - 1) as u32).into_segmented(), 0..max_tests)?; - - chart - .configure_mesh() - .light_line_style(TRANSPARENT) - .bold_line_style(BLACK.mix(0.3)) - .max_light_lines(10) - .disable_x_mesh() - .y_desc("Tests Passed") - .y_label_style((FONT, 15)) - .x_desc("TinyWasm Version") - .x_labels((versions.len()).min(4)) - .x_label_style((FONT, 15)) - .x_label_formatter(&|x| { - let SegmentValue::CenterOf(value) = x else { - return "".to_string(); - }; - let v = versions.get(*value as usize).unwrap_or(&"".to_string()).to_string(); - format!("{} ({})", v, data[*value as usize]) - }) - .axis_desc_style((FONT, 15, FontStyle::Bold)) - .draw()?; - - chart.draw_series( - Histogram::vertical(&chart) - .style(BLUE.mix(0.5).filled()) - .data(data.iter().enumerate().map(|(x, y)| (x as u32, *y))), - )?; - - root_area.present()?; - - Ok(()) -} diff --git a/crates/tinywasm/tests/generated/wasm-1.csv b/crates/tinywasm/tests/generated/wasm-1.csv new file mode 100644 index 0000000..64f92d1 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-1.csv @@ -0,0 +1,7 @@ +0.3.0,26722,1161,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":1},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":171,"failed":12},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":594,"failed":186},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0,27549,334,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.1,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.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,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}] diff --git a/crates/tinywasm/tests/generated/wasm-2.csv b/crates/tinywasm/tests/generated/wasm-2.csv new file mode 100644 index 0000000..0e43692 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-2.csv @@ -0,0 +1,12 @@ +0.0.4,9258,10909,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] +0.0.5,11135,9093,[{"name":"address.wast","passed":1,"failed":259},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":78,"failed":13},{"name":"binary.wast","passed":107,"failed":5},{"name":"block.wast","passed":170,"failed":53},{"name":"br.wast","passed":20,"failed":77},{"name":"br_if.wast","passed":29,"failed":89},{"name":"br_table.wast","passed":24,"failed":150},{"name":"call.wast","passed":18,"failed":73},{"name":"call_indirect.wast","passed":34,"failed":136},{"name":"comments.wast","passed":5,"failed":3},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":25,"failed":594},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":22,"failed":39},{"name":"elem.wast","passed":27,"failed":72},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":90,"failed":6},{"name":"f32.wast","passed":1018,"failed":1496},{"name":"f32_bitwise.wast","passed":4,"failed":360},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":1018,"failed":1496},{"name":"f64_bitwise.wast","passed":4,"failed":360},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":1,"failed":7},{"name":"float_exprs.wast","passed":275,"failed":625},{"name":"float_literals.wast","passed":112,"failed":51},{"name":"float_memory.wast","passed":0,"failed":90},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":81,"failed":91},{"name":"func_ptrs.wast","passed":7,"failed":29},{"name":"global.wast","passed":50,"failed":60},{"name":"i32.wast","passed":85,"failed":375},{"name":"i64.wast","passed":31,"failed":385},{"name":"if.wast","passed":116,"failed":125},{"name":"imports.wast","passed":23,"failed":160},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":25,"failed":26},{"name":"labels.wast","passed":13,"failed":16},{"name":"left-to-right.wast","passed":0,"failed":96},{"name":"linking.wast","passed":5,"failed":127},{"name":"load.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":18,"failed":18},{"name":"local_set.wast","passed":38,"failed":15},{"name":"local_tee.wast","passed":41,"failed":56},{"name":"loop.wast","passed":42,"failed":78},{"name":"memory.wast","passed":30,"failed":49},{"name":"memory_grow.wast","passed":11,"failed":85},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":1,"failed":181},{"name":"names.wast","passed":484,"failed":2},{"name":"nop.wast","passed":4,"failed":84},{"name":"return.wast","passed":20,"failed":64},{"name":"select.wast","passed":28,"failed":120},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":4,"failed":16},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":39,"failed":19},{"name":"traps.wast","passed":4,"failed":32},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":0,"failed":64},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":9,"failed":41},{"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.1.0,17630,2598,[{"name":"address.wast","passed":5,"failed":255},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":110,"failed":2},{"name":"block.wast","passed":193,"failed":30},{"name":"br.wast","passed":84,"failed":13},{"name":"br_if.wast","passed":90,"failed":28},{"name":"br_table.wast","passed":25,"failed":149},{"name":"call.wast","passed":29,"failed":62},{"name":"call_indirect.wast","passed":36,"failed":134},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":371,"failed":248},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":50,"failed":49},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":92,"failed":4},{"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":2,"failed":6},{"name":"float_exprs.wast","passed":761,"failed":139},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":6,"failed":84},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":124,"failed":48},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":51,"failed":59},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":120,"failed":121},{"name":"imports.wast","passed":74,"failed":109},{"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":14,"failed":15},{"name":"left-to-right.wast","passed":1,"failed":95},{"name":"linking.wast","passed":21,"failed":111},{"name":"load.wast","passed":60,"failed":37},{"name":"local_get.wast","passed":32,"failed":4},{"name":"local_set.wast","passed":50,"failed":3},{"name":"local_tee.wast","passed":68,"failed":29},{"name":"loop.wast","passed":93,"failed":27},{"name":"memory.wast","passed":34,"failed":45},{"name":"memory_grow.wast","passed":12,"failed":84},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":2,"failed":180},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":46,"failed":42},{"name":"return.wast","passed":73,"failed":11},{"name":"select.wast","passed":86,"failed":62},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":9,"failed":11},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":22,"failed":14},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":50,"failed":14},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":35,"failed":15},{"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.2.0,19344,884,[{"name":"address.wast","passed":181,"failed":79},{"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":220,"failed":3},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":171,"failed":3},{"name":"call.wast","passed":73,"failed":18},{"name":"call_indirect.wast","passed":50,"failed":120},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":439,"failed":180},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":56,"failed":43},{"name":"endianness.wast","passed":29,"failed":40},{"name":"exports.wast","passed":92,"failed":4},{"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":6,"failed":2},{"name":"float_exprs.wast","passed":890,"failed":10},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":78,"failed":12},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":168,"failed":4},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":103,"failed":7},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":231,"failed":10},{"name":"imports.wast","passed":80,"failed":103},{"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":26,"failed":3},{"name":"left-to-right.wast","passed":92,"failed":4},{"name":"linking.wast","passed":29,"failed":103},{"name":"load.wast","passed":93,"failed":4},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":93,"failed":4},{"name":"loop.wast","passed":116,"failed":4},{"name":"memory.wast","passed":78,"failed":1},{"name":"memory_grow.wast","passed":91,"failed":5},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":35,"failed":7},{"name":"memory_trap.wast","passed":180,"failed":2},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":78,"failed":10},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":114,"failed":34},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":11,"failed":9},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"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":"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.3.0,20254,0,[{"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":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_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.4.0,20254,0,[{"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":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_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.4.1,20257,0,[{"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":"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_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.5.0,20272,0,[{"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":"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.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,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}] diff --git a/crates/tinywasm/tests/generated/wasm-extended-const.csv b/crates/tinywasm/tests/generated/wasm-extended-const.csv new file mode 100644 index 0000000..1c80050 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-extended-const.csv @@ -0,0 +1 @@ +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}] diff --git a/crates/tinywasm/tests/generated/wasm-multi-memory.csv b/crates/tinywasm/tests/generated/wasm-multi-memory.csv new file mode 100644 index 0000000..acfe7f3 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-multi-memory.csv @@ -0,0 +1 @@ +0.8.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}] diff --git a/crates/tinywasm/tests/mvp.csv b/crates/tinywasm/tests/mvp.csv deleted file mode 100644 index 79028b6..0000000 --- a/crates/tinywasm/tests/mvp.csv +++ /dev/null @@ -1,3 +0,0 @@ -0.0.3,9258,7567,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] -0.0.4,9258,10909,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"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":0,"failed":176}] -0.0.5-alpha.0,11135,9093,[{"name":"address.wast","passed":1,"failed":259},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":78,"failed":13},{"name":"binary.wast","passed":107,"failed":5},{"name":"block.wast","passed":170,"failed":53},{"name":"br.wast","passed":20,"failed":77},{"name":"br_if.wast","passed":29,"failed":89},{"name":"br_table.wast","passed":24,"failed":150},{"name":"call.wast","passed":18,"failed":73},{"name":"call_indirect.wast","passed":34,"failed":136},{"name":"comments.wast","passed":5,"failed":3},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":25,"failed":594},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":22,"failed":39},{"name":"elem.wast","passed":27,"failed":72},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":90,"failed":6},{"name":"f32.wast","passed":1018,"failed":1496},{"name":"f32_bitwise.wast","passed":4,"failed":360},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":1018,"failed":1496},{"name":"f64_bitwise.wast","passed":4,"failed":360},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":1,"failed":7},{"name":"float_exprs.wast","passed":275,"failed":625},{"name":"float_literals.wast","passed":112,"failed":51},{"name":"float_memory.wast","passed":0,"failed":90},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":81,"failed":91},{"name":"func_ptrs.wast","passed":7,"failed":29},{"name":"global.wast","passed":50,"failed":60},{"name":"i32.wast","passed":85,"failed":375},{"name":"i64.wast","passed":31,"failed":385},{"name":"if.wast","passed":116,"failed":125},{"name":"imports.wast","passed":23,"failed":160},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":25,"failed":26},{"name":"labels.wast","passed":13,"failed":16},{"name":"left-to-right.wast","passed":0,"failed":96},{"name":"linking.wast","passed":5,"failed":127},{"name":"load.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":18,"failed":18},{"name":"local_set.wast","passed":38,"failed":15},{"name":"local_tee.wast","passed":41,"failed":56},{"name":"loop.wast","passed":42,"failed":78},{"name":"memory.wast","passed":30,"failed":49},{"name":"memory_grow.wast","passed":11,"failed":85},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":1,"failed":181},{"name":"names.wast","passed":484,"failed":2},{"name":"nop.wast","passed":4,"failed":84},{"name":"return.wast","passed":20,"failed":64},{"name":"select.wast","passed":28,"failed":120},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":4,"failed":16},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":39,"failed":19},{"name":"traps.wast","passed":4,"failed":32},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":0,"failed":64},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":9,"failed":41},{"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/mvp.rs b/crates/tinywasm/tests/mvp.rs deleted file mode 100644 index e02436f..0000000 --- a/crates/tinywasm/tests/mvp.rs +++ /dev/null @@ -1,40 +0,0 @@ -mod charts; -mod testsuite; - -use eyre::{eyre, Result}; -use testsuite::TestSuite; - -#[test] -#[ignore] -fn generate_charts() -> Result<()> { - // Create a line chart - charts::create_progress_chart( - std::path::Path::new("./tests/mvp.csv"), - std::path::Path::new("./tests/progress-mvp.svg"), - )?; - - // // Create a bar chart - // charts::create_bar_chart( - // std::path::Path::new("./tests/mvp.csv"), - // std::path::Path::new("./tests/mvp_bar_chart.png"), - // )?; - - Ok(()) -} - -#[test] -#[ignore] -fn test_mvp() -> Result<()> { - let mut test_suite = TestSuite::new(); - - test_suite.run(wasm_testsuite::MVP_TESTS)?; - test_suite.save_csv("./tests/mvp.csv", env!("CARGO_PKG_VERSION"))?; - - if test_suite.failed() { - eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); - Err(eyre!("failed one or more tests")) - } else { - println!("\n\npassed all tests:\n{:#?}", test_suite); - Ok(()) - } -} diff --git a/crates/tinywasm/tests/progress-mvp.svg b/crates/tinywasm/tests/progress-mvp.svg deleted file mode 100644 index c8a9bc1..0000000 --- a/crates/tinywasm/tests/progress-mvp.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - -MVP TESTSUITE - - -Tests Passed - - -TinyWasm Version - - - - - - - - -0 - - - -5000 - - - -10000 - - - -15000 - - - -20000 - - - - -v0.0.3 (9258) - - - -v0.0.4 (9258) - - - -v0.0.5-alpha.0 (11046) - - - - - - diff --git a/crates/tinywasm/tests/test-wasm-1.rs b/crates/tinywasm/tests/test-wasm-1.rs new file mode 100644 index 0000000..77694c5 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-1.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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(()) + } +} diff --git a/crates/tinywasm/tests/test-wasm-2.rs b/crates/tinywasm/tests/test-wasm-2.rs new file mode 100644 index 0000000..bd1afe6 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-2.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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(()) + } +} diff --git a/crates/tinywasm/tests/test-wasm-annotations.rs b/crates/tinywasm/tests/test-wasm-annotations.rs new file mode 100644 index 0000000..fa40467 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-annotations.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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(()) + } +} diff --git a/crates/tinywasm/tests/test-wasm-extended-const.rs b/crates/tinywasm/tests/test-wasm-extended-const.rs new file mode 100644 index 0000000..f544b35 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-extended-const.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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(()) + } +} diff --git a/crates/tinywasm/tests/test-wasm-memory64.rs b/crates/tinywasm/tests/test-wasm-memory64.rs new file mode 100644 index 0000000..ab23762 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-memory64.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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("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(()) + } +} diff --git a/crates/tinywasm/tests/test-wasm-multi-memory.rs b/crates/tinywasm/tests/test-wasm-multi-memory.rs new file mode 100644 index 0000000..2cee13d --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-multi-memory.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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(()) + } +} diff --git a/crates/tinywasm/tests/test-wasm-simd.rs b/crates/tinywasm/tests/test-wasm-simd.rs new file mode 100644 index 0000000..289bf2b --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-simd.rs @@ -0,0 +1,20 @@ +mod testsuite; +use eyre::{eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +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(()) + } +} diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs new file mode 100644 index 0000000..f79681c --- /dev/null +++ b/crates/tinywasm/tests/test-wast.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; + +use eyre::{bail, eyre, Result}; +use owo_colors::OwoColorize; +use testsuite::TestSuite; + +mod testsuite; + +fn main() -> Result<()> { + let args = std::env::args().collect::>(); + if args.len() < 2 { + 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("./") }; + + wast_file.push(&args[1]); + let wast_file = cwd.join(wast_file); + + test_wast(wast_file.to_str().expect("wast_file is not a valid path"))?; + Ok(()) +} + +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(()) + } +} diff --git a/crates/tinywasm/tests/testsuite/indexmap.rs b/crates/tinywasm/tests/testsuite/indexmap.rs new file mode 100644 index 0000000..0c75e4c --- /dev/null +++ b/crates/tinywasm/tests/testsuite/indexmap.rs @@ -0,0 +1,31 @@ +/// 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 d5cde5f..ca62d4e 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -1,4 +1,6 @@ +#![allow(unused)] use eyre::Result; +use owo_colors::OwoColorize; use std::io::{BufRead, Seek, SeekFrom}; use std::{ collections::BTreeMap, @@ -6,11 +8,14 @@ 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, @@ -18,19 +23,39 @@ pub struct TestGroupResult { pub failed: usize, } -pub struct TestSuite(BTreeMap); +pub struct TestSuite(BTreeMap, Vec); impl TestSuite { + pub fn set_log_level(level: log::LevelFilter) { + pretty_env_logger::formatted_builder().filter_level(level).init(); + } + + pub fn print_errors(&self) { + for (group_name, group) in &self.0 { + let tests = &group.tests; + for (test_name, test) in tests.iter() { + if let Err(e) = &test.result { + eprintln!( + "{} {} failed: {:?}", + link(group_name, &group.file, Some(test.linecol.0 + 1)).bold().underline(), + test_name.bold(), + e.to_string().bright_red() + ); + } + } + } + } + pub fn new() -> Self { - Self(BTreeMap::new()) + Self(BTreeMap::new(), Vec::new()) } pub fn failed(&self) -> bool { self.0.values().any(|group| group.stats().1 > 0) } - fn test_group(&mut self, name: &str) -> &mut TestGroup { - self.0.entry(name.to_string()).or_insert_with(TestGroup::new) + fn test_group(&mut self, name: &str, file: &str) -> &mut TestGroup { + self.0.entry(name.to_string()).or_insert_with(|| TestGroup::new(file)) } // create or add to a test result file @@ -57,29 +82,33 @@ impl TestSuite { let mut failed = 0; let mut groups = Vec::new(); - for (name, group) in self.0.iter() { + for (name, group) in &self.0 { let (group_passed, group_failed) = group.stats(); passed += group_passed; failed += group_failed; - groups.push(TestGroupResult { - name: name.to_string(), - passed: group_passed, - failed: group_failed, - }); + groups.push(TestGroupResult { name: name.to_string(), passed: group_passed, failed: group_failed }); } let groups = serde_json::to_string(&groups)?; - let line = format!("{},{},{},{}\n", version, passed, failed, groups); + let line = format!("{version},{passed},{failed},{groups}\n"); file.write_all(line.as_bytes()).expect("failed to write to csv file"); Ok(()) } } +fn link(name: &str, file: &str, line: Option) -> String { + let (path, name) = match line { + None => (file.to_string(), name.to_owned()), + Some(line) => (format!("{file}:{line}:0"), (format!("{name}:{line}"))), + }; + + format!("\x1b]8;;file://{path}\x1b\\{name}\x1b]8;;\x1b\\") +} + impl Debug for TestSuite { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - use owo_colors::OwoColorize; let mut total_passed = 0; let mut total_failed = 0; @@ -88,23 +117,12 @@ impl Debug for TestSuite { total_passed += group_passed; total_failed += group_failed; - writeln!(f, "{}", group_name.bold().underline())?; + writeln!(f, "{}", link(group_name, &group.file, None).bold().underline())?; writeln!(f, " Tests Passed: {}", group_passed.to_string().green())?; - writeln!(f, " Tests Failed: {}", group_failed.to_string().red())?; - - // for (test_name, test) in &group.tests { - // write!(f, " {}: ", test_name.bold())?; - // match &test.result { - // Ok(()) => { - // writeln!(f, "{}", "Passed".green())?; - // } - // Err(e) => { - // writeln!(f, "{}", "Failed".red())?; - // // writeln!(f, "Error: {:?}", e)?; - // } - // } - // writeln!(f, " Span: {:?}", test.span)?; - // } + + if group_failed != 0 { + writeln!(f, " Tests Failed: {}", group_failed.to_string().red())?; + } } writeln!(f, "\n{}", "Total Test Summary:".bold().underline())?; @@ -116,12 +134,13 @@ impl Debug for TestSuite { } struct TestGroup { - tests: BTreeMap, + tests: IndexMap, + file: String, } impl TestGroup { - fn new() -> Self { - Self { tests: BTreeMap::new() } + fn new(file: &str) -> Self { + Self { tests: IndexMap::new(), file: file.to_string() } } fn stats(&self) -> (usize, usize) { @@ -138,12 +157,13 @@ impl TestGroup { (passed_count, failed_count) } - fn add_result(&mut self, name: &str, span: wast::token::Span, result: Result<()>) { - self.tests.insert(name.to_string(), TestCase { result, _span: span }); + fn add_result(&mut self, name: &str, linecol: (usize, usize), result: Result<()>) { + self.tests.insert(name.to_string(), TestCase { result, linecol }); } } +#[derive(Debug)] struct TestCase { result: Result<()>, - _span: wast::token::Span, + linecol: (usize, usize), } diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index ec4715b..ca788fb 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -1,164 +1,510 @@ use crate::testsuite::util::*; +use std::{borrow::Cow, collections::HashMap}; use super::TestSuite; use eyre::{eyre, Result}; -use tinywasm_types::TinyWasmModule; +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}; +#[derive(Default)] +struct RegisteredModules { + modules: HashMap, + + named_modules: HashMap, + last_module: Option, +} + +impl RegisteredModules { + fn modules(&self) -> &HashMap { + &self.modules + } + + fn update_last_module(&mut self, addr: ModuleInstanceAddr, name: Option) { + self.last_module = Some(addr); + if let Some(name) = name { + self.named_modules.insert(name, addr); + } + } + fn register(&mut self, name: String, addr: ModuleInstanceAddr) { + log::debug!("registering module: {}", name); + self.modules.insert(name.clone(), addr); + + self.last_module = Some(addr); + self.named_modules.insert(name, addr); + } + + fn get_idx(&self, module_id: Option>) -> Option<&ModuleInstanceAddr> { + match module_id { + Some(module) => { + log::debug!("getting module: {}", module.name()); + + if let Some(addr) = self.modules.get(module.name()) { + return Some(addr); + } + + if let Some(addr) = self.named_modules.get(module.name()) { + return Some(addr); + } + + None + } + None => self.last_module.as_ref(), + } + } + + fn get<'a>( + &self, + module_id: Option>, + store: &'a tinywasm::Store, + ) -> Option<&'a ModuleInstance> { + let addr = self.get_idx(module_id)?; + store.get_module_instance(*addr) + } + + fn last<'a>(&self, store: &'a tinywasm::Store) -> Option<&'a ModuleInstance> { + store.get_module_instance(*self.last_module.as_ref()?) + } +} + impl TestSuite { - pub fn run(&mut self, tests: &[&str]) -> Result<()> { - tests.iter().for_each(|group| { - let test_group = self.test_group(group); - - let wast = wasm_testsuite::get_test_wast(group).expect("failed to get test wast"); - let wast = std::str::from_utf8(&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"); - - let mut last_module: Option = None; - for (i, directive) in wast_data.directives.into_iter().enumerate() { - let span = directive.span(); - use wast::WastDirective::*; - let name = format!("{}-{}", group, i); - - match directive { - // TODO: needs to support more binary sections - Wat(mut module) => { - let result = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); - - match &result { - Err(_) => last_module = None, - Ok(m) => last_module = Some(m.clone()), - } + 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"); + } - test_group.add_result(&format!("{}-parse", name), span, result.map(|_| ())); - } + Ok(()) + } - AssertMalformed { - span, - mut module, - message: _, - } => { - let Ok(module) = module.encode() else { - test_group.add_result(&format!("{}-malformed", name), span, Ok(())); - continue; - }; + fn imports(modules: &HashMap) -> Result { + let mut imports = Imports::new(); - let res = catch_unwind_silent(|| parse_module_bytes(&module)) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); + let table = + Extern::table(TableType::new(ValType::RefFunc, 10, Some(20)), WasmValue::default_for(ValType::RefFunc)); + let print = Extern::typed_func(|_ctx: tinywasm::FuncContext, (): ()| { + log::debug!("print"); + Ok(()) + }); + + let print_i32 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: i32| { + log::debug!("print_i32: {}", arg); + Ok(()) + }); + + let print_i64 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: i64| { + log::debug!("print_i64: {}", arg); + Ok(()) + }); + + let print_f32 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: f32| { + log::debug!("print_f32: {}", arg); + Ok(()) + }); + + let print_f64 = Extern::typed_func(|_ctx: tinywasm::FuncContext, arg: f64| { + log::debug!("print_f64: {}", arg); + Ok(()) + }); + + let print_i32_f32 = Extern::typed_func(|_ctx: tinywasm::FuncContext, args: (i32, f32)| { + log::debug!("print_i32_f32: {}, {}", args.0, args.1); + Ok(()) + }); + + let print_f64_f64 = Extern::typed_func(|_ctx: tinywasm::FuncContext, args: (f64, f64)| { + log::debug!("print_f64_f64: {}, {}", args.0, args.1); + Ok(()) + }); + + imports + .define("spectest", "memory", Extern::memory(MemoryType::new_32(1, Some(2))))? + .define("spectest", "table", table)? + .define("spectest", "global_i32", Extern::global(WasmValue::I32(666), false))? + .define("spectest", "global_i64", Extern::global(WasmValue::I64(666), false))? + .define("spectest", "global_f32", Extern::global(WasmValue::F32(666.6), false))? + .define("spectest", "global_f64", Extern::global(WasmValue::F64(666.6), false))? + .define("spectest", "print", print)? + .define("spectest", "print_i32", print_i32)? + .define("spectest", "print_i64", print_i64)? + .define("spectest", "print_f32", print_f32)? + .define("spectest", "print_f64", print_f64)? + .define("spectest", "print_i32_f32", print_i32_f32)? + .define("spectest", "print_f64_f64", print_f64_f64)?; + + for (name, addr) in modules { + log::debug!("registering module: {}", name); + imports.link_module(name, *addr)?; + } + + Ok(imports) + } + + pub fn run_spec_group>(&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); + return; + } + + self.run_group(group, group_wast).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"); + + let mut store = tinywasm::Store::default(); + let mut registered_modules = RegisteredModules::default(); + + println!("running {} tests for group: {}", wast_data.directives.len(), group_name); + for (i, directive) in wast_data.directives.into_iter().enumerate() { + let span = directive.span(); + use wast::WastDirective::{ + AssertExhaustion, AssertInvalid, AssertMalformed, AssertReturn, AssertTrap, AssertUnlinkable, Invoke, + Register, Wat, + }; + + match directive { + Register { span, name, .. } => { + let Some(last) = registered_modules.last(&store) else { test_group.add_result( - &format!("{}-malformed", name), - span, - match res { - Ok(_) => Err(eyre!("expected module to be malformed")), - Err(_) => Ok(()), - }, + &format!("Register({i})"), + span.linecol_in(wast), + 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(())); + } + + Wat(module) => { + debug!("got wat module"); + let result = catch_unwind_silent(|| { + let (name, bytes) = encode_quote_wat(module); + 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())) + .expect("failed to instantiate module"); + + (name, module_instance) + }) + .map_err(|e| eyre!("failed to parse wat module: {:?}", try_downcast_panic(e))); + + match &result { + Err(err) => debug!("failed to parse module: {:?}", err), + Ok((name, module)) => registered_modules.update_last_module(module.id(), name.clone()), + }; + + test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast), 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(())); + continue; + }; + + let res = catch_unwind_silent(|| parse_module_bytes(&module)) + .map_err(|e| eyre!("failed to parse module (expected): {:?}", try_downcast_panic(e))) + .and_then(|res| res); + + test_group.add_result( + &format!("AssertMalformed({i})"), + span.linecol_in(wast), + 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" { + continue; + } + + Err(eyre!("expected module to be malformed")) + } + Err(_) => Ok(()), + }, + ); + } + + AssertInvalid { span, mut module, message } => { + if ["multiple memories"].contains(&message) { + test_group.add_result(&format!("AssertInvalid({i})"), span.linecol_in(wast), Ok(())); + continue; } - AssertInvalid { - span, - mut module, - message: _, - } => { - let res = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); + let res = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) + .map_err(|e| eyre!("failed to parse module (invalid): {:?}", try_downcast_panic(e))) + .and_then(|res| res); + + test_group.add_result( + &format!("AssertInvalid({i})"), + span.linecol_in(wast), + match res { + Ok(_) => Err(eyre!("expected module to be invalid")), + Err(_) => Ok(()), + }, + ); + } + AssertExhaustion { call, message, span } => { + let module = registered_modules.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(|_| ())); + + let Ok(Err(tinywasm::Error::Trap(trap))) = res else { + test_group.add_result( + &format!("AssertExhaustion({i})"), + span.linecol_in(wast), + Err(eyre!("expected trap")), + ); + continue; + }; + + if !message.starts_with(trap.message()) { test_group.add_result( - &format!("{}-invalid", name), - span, - match res { - Ok(_) => Err(eyre!("expected module to be invalid")), - Err(_) => Ok(()), - }, + &format!("AssertExhaustion({i})"), + span.linecol_in(wast), + Err(eyre!("expected trap: {}, got: {}", message, trap.message())), ); + continue; } - AssertTrap { exec, message: _, span } => { - let res: Result, _> = catch_unwind_silent(|| { - let (module, name) = match exec { - wast::WastExecute::Wat(_wat) => unimplemented!("wat"), - wast::WastExecute::Get { module: _, global: _ } => unimplemented!("get"), - wast::WastExecute::Invoke(invoke) => (last_module.as_ref(), invoke.name), - }; - exec_fn(module, name, &[]).map(|_| ()) - }); + test_group.add_result(&format!("AssertExhaustion({i})"), span.linecol_in(wast), Ok(())); + } - match res { - Err(err) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("test panicked: {:?}", err)), - ), - Ok(Err(tinywasm::Error::Trap(_))) => { - test_group.add_result(&format!("{}-trap", name), span, Ok(())) + AssertTrap { exec, message, span } => { + let res: Result, _> = catch_unwind_silent(|| { + let invoke = match exec { + wast::WastExecute::Wat(mut wat) => { + 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()), + )?; + return Ok(()); } - Ok(Err(err)) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("expected trap, got error: {:?}", err)), - ), - Ok(Ok(())) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("expected trap, got ok")), - ), + wast::WastExecute::Get { module: _, global: _, .. } => { + panic!("get not supported"); + } + wast::WastExecute::Invoke(invoke) => invoke, + }; + + let module = registered_modules.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(|_| ()) + }); + + match res { + Err(err) => test_group.add_result( + &format!("AssertTrap({i})"), + span.linecol_in(wast), + 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), + Err(eyre!("expected trap: {}, got: {}", message, trap.message())), + ); + continue; + } + + test_group.add_result(&format!("AssertTrap({i})"), span.linecol_in(wast), Ok(())); + } + Ok(Err(err)) => test_group.add_result( + &format!("AssertTrap({i})"), + span.linecol_in(wast), + Err(eyre!("expected trap, {}, got: {:?}", message, err)), + ), + Ok(Ok(())) => test_group.add_result( + &format!("AssertTrap({i})"), + span.linecol_in(wast), + Err(eyre!("expected trap {}, got Ok", message)), + ), + } + } + + AssertUnlinkable { mut module, span, message } => { + let res = catch_unwind_silent(|| { + 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())) + }); + + match res { + Err(err) => test_group.add_result( + &format!("AssertUnlinkable({i})"), + span.linecol_in(wast), + Err(eyre!("test panicked: {:?}", try_downcast_panic(err))), + ), + Ok(Err(tinywasm::Error::Linker(err))) => { + if err.message() != message { + test_group.add_result( + &format!("AssertUnlinkable({i})"), + span.linecol_in(wast), + Err(eyre!("expected linker error: {}, got: {}", message, err.message())), + ); + continue; + } + + test_group.add_result(&format!("AssertUnlinkable({i})"), span.linecol_in(wast), Ok(())); } + Ok(Err(err)) => test_group.add_result( + &format!("AssertUnlinkable({i})"), + span.linecol_in(wast), + Err(eyre!("expected linker error, {}, got: {:?}", message, err)), + ), + Ok(Ok(_)) => test_group.add_result( + &format!("AssertUnlinkable({i})"), + span.linecol_in(wast), + Err(eyre!("expected linker error {}, got Ok", message)), + ), } + } + + Invoke(invoke) => { + let name = invoke.name; + + let res: Result, _> = catch_unwind_silent(|| { + let args = convert_wastargs(invoke.args)?; + let module = registered_modules.get_idx(invoke.module); + exec_fn_instance(module, &mut store, invoke.name, &args).map_err(|e| { + error!("failed to execute function: {:?}", e); + e + })?; + Ok(()) + }); + + 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); + } + + AssertReturn { span, exec, results } => { + info!("AssertReturn: {:?}", exec); + let expected = convert_wastret(results.into_iter())?; + + 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 Some(module) = module else { + test_group.add_result( + &format!("AssertReturn(unsupported-{i})"), + span.linecol_in(wast), + Err(eyre!("no module to get global from")), + ); + continue; + }; - AssertReturn { span, exec, results } => { - let res: Result, _> = catch_unwind_silent(|| { - let invoke = match exec { - wast::WastExecute::Wat(_) => unimplemented!("wat"), - wast::WastExecute::Get { module: _, global: _ } => { - return Err(eyre!("get not supported")) + let module_global = match match module.export_addr(global) { + Some(ExternVal::Global(addr)) => Ok(store.get_global_val(addr)), + _ => Err(eyre!("no module to get global from")), + } { + Ok(module_global) => module_global, + Err(err) => { + test_group.add_result( + &format!("AssertReturn(unsupported-{i})"), + span.linecol_in(wast), + Err(eyre!("failed to get global: {:?}", err)), + ); + continue; } - wast::WastExecute::Invoke(invoke) => invoke, }; + let expected = expected.first().expect("expected global value"); + let module_global = module_global.attach_type(expected.val_type()); - let args = invoke - .args - .into_iter() - .map(wastarg2tinywasmvalue) - .collect::>>()?; + if !module_global.eq_loose(expected) { + test_group.add_result( + &format!("AssertReturn(unsupported-{i})"), + span.linecol_in(wast), + Err(eyre!("global value did not match: {:?} != {:?}", module_global, expected)), + ); + continue; + } - let outcomes = exec_fn(last_module.as_ref(), invoke.name, &args)?; - let expected = results - .into_iter() - .map(wastret2tinywasmvalue) - .collect::>>()?; + test_group.add_result( + &format!("AssertReturn({global}-{i})"), + span.linecol_in(wast), + Ok(()), + ); - if outcomes.len() != expected.len() { - return Err(eyre!("expected {} results, got {}", expected.len(), outcomes.len())); - } - outcomes - .iter() - .zip(expected) - .enumerate() - .try_for_each(|(i, (outcome, exp))| { - (outcome == &exp) - .then_some(()) - .ok_or_else(|| eyre!("result {} did not match: {:?} != {:?}", i, outcome, exp)) - }) - }); - - let res = res.map_err(|e| eyre!("test panicked: {:?}", e)).and_then(|r| r); - test_group.add_result(&format!("{}-return", name), span, res); - } - _ => test_group.add_result(&format!("{}-unknown", name), span, Err(eyre!("unsupported directive"))), + continue; + // check if module_global matches the expected results + } + wast::WastExecute::Invoke(invoke) => Ok(invoke), + } { + Ok(invoke) => invoke, + Err(err) => { + test_group.add_result( + &format!("AssertReturn(unsupported-{i})"), + span.linecol_in(wast), + Err(eyre!("unsupported directive: {:?}", err)), + ); + continue; + } + }; + + let invoke_name = invoke.name; + let res: Result, _> = catch_unwind_silent(|| { + debug!("invoke: {:?}", invoke); + let args = convert_wastargs(invoke.args)?; + let module = registered_modules.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 + })?; + + if outcomes.len() != expected.len() { + return Err(eyre!( + "span: {:?} expected {} results, got {}", + span, + expected.len(), + outcomes.len() + )); + } + + outcomes.iter().zip(expected).enumerate().try_for_each(|(i, (outcome, exp))| { + (outcome.eq_loose(&exp)) + .then_some(()) + .ok_or_else(|| eyre!(" result {} did not match: {:?} != {:?}", i, outcome, exp)) + }) + }); + + 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!("Unknown({i})"), + span.linecol_in(wast), + Err(eyre!("unsupported directive")), + ), } - }); + } Ok(()) } diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index ab2ffa3..29f028b 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -1,12 +1,41 @@ -use std::panic; +use std::panic::{self, AssertUnwindSafe}; use eyre::{eyre, Result}; -use tinywasm_types::TinyWasmModule; +use tinywasm_types::{ModuleInstanceAddr, TinyWasmModule, ValType, WasmValue}; +use wast::{core::AbstractHeapType, QuoteWat}; + +pub fn try_downcast_panic(panic: Box) -> String { + #[allow(deprecated)] // new name is not available on stable + 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()) +} + +pub fn exec_fn_instance( + instance: Option<&ModuleInstanceAddr>, + store: &mut tinywasm::Store, + name: &str, + args: &[tinywasm_types::WasmValue], +) -> Result, tinywasm::Error> { + let Some(instance) = instance else { + return Err(tinywasm::Error::Other("no instance found".to_string())); + }; + + let Some(instance) = store.get_module_instance(*instance) else { + return Err(tinywasm::Error::Other("no instance found".to_string())); + }; + + let func = instance.exported_func_untyped(store, name)?; + func.call(store, args) +} pub fn exec_fn( module: Option<&TinyWasmModule>, name: &str, args: &[tinywasm_types::WasmValue], + imports: Option, ) -> Result, tinywasm::Error> { let Some(module) = module else { return Err(tinywasm::Error::Other("no module found".to_string())); @@ -14,52 +43,109 @@ pub fn exec_fn( let mut store = tinywasm::Store::new(); let module = tinywasm::Module::from(module); - let instance = module.instantiate(&mut store)?; - instance.get_func(&store, name)?.call(&mut store, args) + let instance = module.instantiate(&mut store, imports)?; + instance.exported_func_untyped(&store, name)?.call(&mut store, args) } -pub fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> std::thread::Result { +pub fn catch_unwind_silent R, R>(f: F) -> std::thread::Result { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); - let result = panic::catch_unwind(f); + let result = panic::catch_unwind(AssertUnwindSafe(f)); panic::set_hook(prev_hook); result } +pub fn encode_quote_wat(module: QuoteWat) -> (Option, Vec) { + match module { + QuoteWat::QuoteModule(_, quoted_wat) => { + let wat = quoted_wat + .iter() + .map(|(_, s)| std::str::from_utf8(s).expect("failed to convert wast to utf8")) + .collect::>() + .join("\n"); + + let lexer = wast::lexer::Lexer::new(&wat); + let buf = wast::parser::ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); + let mut wat_data = wast::parser::parse::(&buf).expect("failed to parse wat"); + (None, wat_data.encode().expect("failed to encode module")) + } + QuoteWat::Wat(mut wat) => { + let wast::Wat::Module(ref module) = wat else { + unimplemented!("Not supported"); + }; + (module.id.map(|id| id.name().to_string()), wat.encode().expect("failed to encode module")) + } + _ => unimplemented!("Not supported"), + } +} + pub fn parse_module_bytes(bytes: &[u8]) -> Result { let parser = tinywasm_parser::Parser::new(); Ok(parser.parse_module_bytes(bytes)?) } -pub fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { +pub fn convert_wastargs(args: Vec) -> Result> { + args.into_iter().map(|a| wastarg2tinywasmvalue(a)).collect() +} + +pub fn convert_wastret<'a>(args: impl Iterator>) -> Result> { + args.map(|a| wastret2tinywasmvalue(a)).collect() +} + +fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { let wast::WastArg::Core(arg) = arg else { - return Err(eyre!("unsupported arg type")); + return Err(eyre!("unsupported arg type: Component")); }; - use tinywasm_types::WasmValue; - use wast::core::WastArgCore::*; + use wast::core::WastArgCore::{RefExtern, RefNull, F32, F64, I32, I64}; 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), - _ => return Err(eyre!("unsupported arg type")), + RefExtern(v) => WasmValue::RefExtern(v), + RefNull(t) => match t { + wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func } => { + WasmValue::RefNull(ValType::RefFunc) + } + wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern } => { + WasmValue::RefNull(ValType::RefExtern) + } + _ => return Err(eyre!("unsupported arg type: refnull: {:?}", t)), + }, + v => return Err(eyre!("unsupported arg type: {:?}", v)), }) } -pub fn wastret2tinywasmvalue(arg: wast::WastRet) -> Result { - let wast::WastRet::Core(arg) = arg else { +fn wastret2tinywasmvalue(ret: wast::WastRet) -> Result { + let wast::WastRet::Core(ret) = ret else { return Err(eyre!("unsupported arg type")); }; - use tinywasm_types::WasmValue; - use wast::core::WastRetCore::*; - Ok(match arg { + use wast::core::WastRetCore::{RefExtern, RefFunc, RefNull, F32, F64, I32, I64}; + Ok(match ret { F32(f) => nanpattern2tinywasmvalue(f)?, F64(f) => nanpattern2tinywasmvalue(f)?, I32(i) => WasmValue::I32(i), I64(i) => WasmValue::I64(i), - _ => return Err(eyre!("unsupported arg type")), + RefNull(t) => match t { + Some(wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func }) => { + WasmValue::RefNull(ValType::RefFunc) + } + Some(wast::core::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern }) => { + WasmValue::RefNull(ValType::RefExtern) + } + _ => 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)), + }, + a => return Err(eyre!("unsupported arg type {:?}", a)), }) } @@ -69,29 +155,50 @@ enum Bits { } trait FloatToken { fn bits(&self) -> Bits; + fn canonical_nan() -> WasmValue; + fn arithmetic_nan() -> WasmValue; + fn value(&self) -> WasmValue { + match self.bits() { + Bits::U32(v) => WasmValue::F32(f32::from_bits(v)), + Bits::U64(v) => WasmValue::F64(f64::from_bits(v)), + } + } } -impl FloatToken for wast::token::Float32 { +impl FloatToken for wast::token::F32 { fn bits(&self) -> Bits { Bits::U32(self.bits) } + + fn canonical_nan() -> WasmValue { + WasmValue::F32(f32::NAN) + } + + fn arithmetic_nan() -> WasmValue { + WasmValue::F32(f32::NAN) + } } -impl FloatToken for wast::token::Float64 { +impl FloatToken for wast::token::F64 { fn bits(&self) -> Bits { Bits::U64(self.bits) } + + fn canonical_nan() -> WasmValue { + WasmValue::F64(f64::NAN) + } + + fn arithmetic_nan() -> WasmValue { + WasmValue::F64(f64::NAN) + } } fn nanpattern2tinywasmvalue(arg: wast::core::NanPattern) -> Result where T: FloatToken, { - use wast::core::NanPattern::*; + use wast::core::NanPattern::{ArithmeticNan, CanonicalNan, Value}; Ok(match arg { - CanonicalNan => tinywasm_types::WasmValue::F32(f32::NAN), - ArithmeticNan => tinywasm_types::WasmValue::F32(f32::NAN), - Value(v) => match v.bits() { - Bits::U32(v) => tinywasm_types::WasmValue::F32(f32::from_bits(v)), - Bits::U64(v) => tinywasm_types::WasmValue::F64(f64::from_bits(v)), - }, + CanonicalNan => T::canonical_nan(), + ArithmeticNan => T::arithmetic_nan(), + Value(v) => v.value(), }) } diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index bc6769a..b643964 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -6,13 +6,15 @@ edition.workspace=true license.workspace=true authors.workspace=true repository.workspace=true +rust-version.workspace=true [dependencies] -log={version="0.4", optional=true} -rkyv={version="0.7", optional=true, default-features=false, features=["size_32"]} +log={workspace=true, optional=true} +rkyv={version="0.7", optional=true, default-features=false, features=["size_32", "validation"]} +bytecheck={version="0.7", optional=true} [features] -default=["std", "logging"] -std=["rkyv/std"] -serialize=["dep:rkyv", "dep:log"] +default=["std", "logging", "archive"] +std=["rkyv?/std"] +archive=["dep:rkyv", "dep:bytecheck"] logging=["dep:log"] diff --git a/crates/types/README.md b/crates/types/README.md index f2d048b..5a4431e 100644 --- a/crates/types/README.md +++ b/crates/types/README.md @@ -1,3 +1,3 @@ -# `tinywasm_types` +# `tinywasm-types` -This crate contains the types used by the [`tinywasm`](https://crates.io/crates/tinywasm) crate. It is also used by the [`tinywasm_parser`](https://crates.io/crates/tinywasm_parser) crate to parse WebAssembly binaries. +This crate contains the types used by the [`tinywasm`](https://crates.io/crates/tinywasm) crate. It is also used by the [`tinywasm-parser`](https://crates.io/crates/tinywasm-parser) crate to parse WebAssembly binaries. diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs new file mode 100644 index 0000000..398b616 --- /dev/null +++ b/crates/types/src/archive.rs @@ -0,0 +1,88 @@ +use core::fmt::{Display, Formatter}; + +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"; +#[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); + } + if &wasm[TWASM_MAGIC_PREFIX.len()..TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()] != TWASM_VERSION { + return Err(TwasmError::InvalidVersion); + } + if wasm[TWASM_MAGIC_PREFIX.len() + TWASM_VERSION.len()..TWASM_MAGIC.len()] != [0; 10] { + return Err(TwasmError::InvalidPadding); + } + + Ok(TWASM_MAGIC.len()) +} + +#[derive(Debug)] +pub enum TwasmError { + InvalidMagic, + InvalidVersion, + InvalidPadding, + InvalidArchive, +} + +impl Display for TwasmError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + TwasmError::InvalidMagic => write!(f, "Invalid twasm: invalid magic number"), + TwasmError::InvalidVersion => write!(f, "Invalid twasm: invalid version"), + TwasmError::InvalidPadding => write!(f, "Invalid twasm: invalid padding"), + TwasmError::InvalidArchive => write!(f, "Invalid twasm: invalid archive"), + } + } +} + +#[cfg(feature = "std")] +extern crate std; + +#[cfg(feature = "std")] +impl std::error::Error for TwasmError {} + +impl TinyWasmModule { + /// Creates a `TinyWasmModule` from a slice of bytes. + pub fn from_twasm(wasm: &[u8]) -> Result { + let len = validate_magic(wasm)?; + let root = check_archived_root::(&wasm[len..]).map_err(|_e| TwasmError::InvalidArchive)?; + Ok(root.deserialize(&mut rkyv::Infallible).unwrap()) + } + + /// Serializes the `TinyWasmModule` into a vector of bytes. + /// `AlignedVec` can be deferenced as a slice of bytes and + /// implements `io::Write` when the `std` feature is enabled. + pub fn serialize_twasm(&self) -> rkyv::AlignedVec { + let mut serializer = AllocSerializer::<0>::default(); + serializer.pad(TWASM_MAGIC.len()).unwrap(); + serializer.serialize_value(self).unwrap(); + let mut out = serializer.into_serializer().into_inner(); + out[..TWASM_MAGIC.len()].copy_from_slice(&TWASM_MAGIC); + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize() { + let wasm = TinyWasmModule::default(); + let twasm = wasm.serialize_twasm(); + let wasm2 = TinyWasmModule::from_twasm(&twasm).unwrap(); + assert_eq!(wasm, wasm2); + } +} diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 3f7b2ef..d77d66d 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,31 +1,29 @@ use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum BlockArgs { - Empty, - Type(ValType), - FuncType(u32), -} +use crate::{DataAddr, ElemAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct MemArg { - pub align: u8, +#[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, } type BrTableDefault = u32; -type BrTableLen = usize; -type EndOffset = usize; -type ElseOffset = usize; +type BrTableLen = u32; +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))] pub enum ConstInstruction { I32Const(i32), I64Const(i64), F32Const(f32), F64Const(f64), GlobalGet(GlobalAddr), + RefNull(ValType), + RefFunc(FuncAddr), } /// A WebAssembly Instruction @@ -34,203 +32,177 @@ pub enum ConstInstruction { /// Wasm Bytecode can map to multiple of these instructions. /// /// # Differences to the spec -/// * `br_table` stores the jump lables in the following `br_label` instructions to keep this enum small. -/// * Lables/Blocks: we store the label end offset in the instruction itself and -/// have seperate EndBlockFrame and EndFunc instructions to mark the end of a block or function. -/// This makes it easier to implement the label stack (we call it BlockFrameStack) iteratively. +/// * `br_table` stores the jump labels in the following `br_label` instructions to keep this enum small. +/// * Lables/Blocks: we store the label end offset in the instruction itself and use `EndBlockFrame` to mark the end of a block. +/// This makes it easier to implement the label stack iteratively. /// /// See -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +// should be kept as small as possible (16 bytes max) +#[rustfmt::skip] pub enum Instruction { - // Custom Instructions - BrLabel(LabelAddr), + // > 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), - // Control Instructions + // > Control Instructions // See Unreachable, Nop, - Block(BlockArgs, EndOffset), - Loop(BlockArgs, EndOffset), - If(BlockArgs, Option, EndOffset), + + Block(EndOffset), + BlockWithType(ValType, EndOffset), + BlockWithFuncType(TypeAddr, EndOffset), + + Loop(EndOffset), + LoopWithType(ValType, EndOffset), + LoopWithFuncType(TypeAddr, EndOffset), + + If(ElseOffset, EndOffset), + IfWithType(ValType, ElseOffset, EndOffset), + IfWithFuncType(TypeAddr, ElseOffset, EndOffset), + Else(EndOffset), EndBlockFrame, - EndFunc, Br(LabelAddr), BrIf(LabelAddr), BrTable(BrTableDefault, BrTableLen), // has to be followed by multiple BrLabel instructions + BrLabel(LabelAddr), Return, Call(FuncAddr), CallIndirect(TypeAddr, TableAddr), - - // Parametric Instructions + ReturnCall(FuncAddr), + ReturnCallIndirect(TypeAddr, TableAddr), + + // > Parametric Instructions // See - Drop, - Select, + Drop32, + Drop64, + Drop128, + DropRef, - // Variable Instructions + Select32, + Select64, + Select128, + SelectRef, + + // > Variable Instructions // See - LocalGet(LocalAddr), - LocalSet(LocalAddr), - LocalTee(LocalAddr), + 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), - GlobalSet(GlobalAddr), - - // Memory Instructions - I32Load(MemArg), - I64Load(MemArg), - F32Load(MemArg), - F64Load(MemArg), - I32Load8S(MemArg), - I32Load8U(MemArg), - I32Load16S(MemArg), - I32Load16U(MemArg), - I64Load8S(MemArg), - I64Load8U(MemArg), - I64Load16S(MemArg), - I64Load16U(MemArg), - I64Load32S(MemArg), - I64Load32U(MemArg), - I32Store(MemArg), - I64Store(MemArg), - F32Store(MemArg), - F64Store(MemArg), - I32Store8(MemArg), - I32Store16(MemArg), - I64Store8(MemArg), - I64Store16(MemArg), - I64Store32(MemArg), - MemorySize, - MemoryGrow, - - // Constants + GlobalSet32(GlobalAddr), + GlobalSet64(GlobalAddr), + GlobalSet128(GlobalAddr), + 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 }, + MemorySize(MemAddr), + MemoryGrow(MemAddr), + + // > Constants I32Const(i32), I64Const(i64), F32Const(f32), F64Const(f64), - // Numeric Instructions + // > Reference Types + RefNull(ValType), + RefFunc(FuncAddr), + RefIsNull, + + // > Numeric Instructions // See - I32Eqz, - I32Eq, - I32Ne, - I32LtS, - I32LtU, - I32GtS, - I32GtU, - I32LeS, - I32LeU, - I32GeS, - I32GeU, - I64Eqz, - I64Eq, - I64Ne, - I64LtS, - I64LtU, - I64GtS, - I64GtU, - I64LeS, - I64LeU, - I64GeS, - I64GeU, - F32Eq, - F32Ne, - F32Lt, - F32Gt, - F32Le, - F32Ge, - F64Eq, - F64Ne, - F64Lt, - F64Gt, - F64Le, - F64Ge, - I32Clz, - I32Ctz, - I32Popcnt, - I32Add, - I32Sub, - I32Mul, - I32DivS, - I32DivU, - I32RemS, - I32RemU, - I32And, - I32Or, - I32Xor, - I32Shl, - I32ShrS, - I32ShrU, - I32Rotl, - I32Rotr, - I64Clz, - I64Ctz, - I64Popcnt, - I64Add, - I64Sub, - I64Mul, - I64DivS, - I64DivU, - I64RemS, - I64RemU, - I64And, - I64Or, - I64Xor, - I64Shl, - I64ShrS, - I64ShrU, - I64Rotl, - I64Rotr, - F32Abs, - F32Neg, - F32Ceil, - F32Floor, - F32Trunc, - F32Nearest, - F32Sqrt, - F32Add, - F32Sub, - F32Mul, - F32Div, - F32Min, - F32Max, - F32Copysign, - F64Abs, - F64Neg, - F64Ceil, - F64Floor, - F64Trunc, - F64Nearest, - F64Sqrt, - F64Add, - F64Sub, - F64Mul, - F64Div, - F64Min, - F64Max, - F64Copysign, - I32WrapI64, - I32TruncF32S, - I32TruncF32U, - I32TruncF64S, - I32TruncF64U, - I64ExtendI32S, - I64ExtendI32U, - I64TruncF32S, - I64TruncF32U, - I64TruncF64S, - I64TruncF64U, - F32ConvertI32S, - F32ConvertI32U, - F32ConvertI64S, - F32ConvertI64U, - F32DemoteF64, - F64ConvertI32S, - F64ConvertI32U, - F64ConvertI64S, - F64ConvertI64U, - F64PromoteF32, - I32ReinterpretF32, - I64ReinterpretF64, - F32ReinterpretI32, - F64ReinterpretI64, + I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU, + I64Eqz, I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU, + // Comparisons + F32Eq, F32Ne, F32Lt, F32Gt, F32Le, F32Ge, + F64Eq, F64Ne, F64Lt, F64Gt, F64Le, F64Ge, + I32Clz, I32Ctz, I32Popcnt, I32Add, I32Sub, I32Mul, I32DivS, I32DivU, I32RemS, I32RemU, + I64Clz, I64Ctz, I64Popcnt, I64Add, I64Sub, I64Mul, I64DivS, I64DivU, I64RemS, I64RemU, + // Bitwise + I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr, + I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr, + // Floating Point + F32Abs, F32Neg, F32Ceil, F32Floor, F32Trunc, F32Nearest, F32Sqrt, F32Add, F32Sub, F32Mul, F32Div, F32Min, F32Max, F32Copysign, + F64Abs, F64Neg, F64Ceil, F64Floor, F64Trunc, F64Nearest, F64Sqrt, F64Add, F64Sub, F64Mul, F64Div, F64Min, F64Max, F64Copysign, + I32WrapI64, I32TruncF32S, I32TruncF32U, I32TruncF64S, I32TruncF64U, I32Extend8S, I32Extend16S, + I64Extend8S, I64Extend16S, I64Extend32S, I64ExtendI32S, I64ExtendI32U, I64TruncF32S, I64TruncF32U, I64TruncF64S, I64TruncF64U, + F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64, + F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32, + // Reinterpretations (noops at runtime) + I32ReinterpretF32, I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + // Saturating Float-to-Int Conversions + I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U, + I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, + + // > Table Instructions + TableInit(ElemAddr, TableAddr), + TableGet(TableAddr), + TableSet(TableAddr), + TableCopy { from: TableAddr, to: TableAddr }, + TableGrow(TableAddr), + TableSize(TableAddr), + TableFill(TableAddr), + + // > Bulk Memory Instructions + MemoryInit(MemAddr, DataAddr), + MemoryCopy(MemAddr, MemAddr), + MemoryFill(MemAddr), + DataDrop(DataAddr), + ElemDrop(ElemAddr), } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 387a878..94dc94d 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -1,226 +1,106 @@ -#![no_std] -#![forbid(unsafe_code)] #![doc(test( no_crate_inject, - attr( - deny(warnings, rust_2018_idioms), - allow(dead_code, unused_assignments, unused_variables) - ) + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +#![no_std] +#![forbid(unsafe_code)] //! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser). extern crate alloc; +use alloc::boxed::Box; +use core::{fmt::Debug, ops::Range}; // log for logging (optional). -// #[cfg(feature = "logging")] -// #[allow(unused_imports)] -// use log; - -// #[cfg(not(feature = "logging"))] -// #[macro_use] -// pub(crate) mod log { -// // macro_rules! debug ( ($($tt:tt)*) => {{}} ); -// // pub(crate) use debug; -// } +#[cfg(feature = "logging")] +#[allow(clippy::single_component_path_imports, unused_imports)] +use log; + +// noop fallback if logging is disabled. +#[cfg(not(feature = "logging"))] +#[allow(unused_imports, unused_macros)] +pub(crate) mod log { + macro_rules! debug ( ($($tt:tt)*) => {{}} ); + macro_rules! info ( ($($tt:tt)*) => {{}} ); + macro_rules! error ( ($($tt:tt)*) => {{}} ); + pub(crate) use debug; + pub(crate) use error; + pub(crate) use info; +} mod instructions; -use core::fmt::Debug; - -use alloc::boxed::Box; +mod value; pub use instructions::*; +pub use value::*; + +#[cfg(feature = "archive")] +pub mod archive; -/// A TinyWasm WebAssembly Module +/// 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)] +/// 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))] pub struct TinyWasmModule { - /// The version of the WebAssembly module. - pub version: Option, - - /// The start function of the WebAssembly module. + /// Optional address of the start function + /// + /// Corresponds to the `start` section of the original WebAssembly module. pub start_func: Option, - /// The functions of the WebAssembly module. - pub funcs: Box<[Function]>, + /// Optimized and validated WebAssembly functions + /// + /// Contains data from to the `code`, `func`, and `type` sections of the original WebAssembly module. + pub funcs: Box<[WasmFunction]>, - /// The types of the WebAssembly module. + /// A vector of type definitions, indexed by `TypeAddr` + /// + /// Corresponds to the `type` section of the original WebAssembly module. pub func_types: Box<[FuncType]>, - /// The exports of the WebAssembly module. + /// Exported items of the WebAssembly module. + /// + /// Corresponds to the `export` section of the original WebAssembly module. pub exports: Box<[Export]>, - /// The tables of the WebAssembly module. + /// Global components of the WebAssembly module. + /// + /// Corresponds to the `global` section of the original WebAssembly module. pub globals: Box<[Global]>, - /// The tables of the WebAssembly module. + /// Table components of the WebAssembly module used to initialize tables. + /// + /// Corresponds to the `table` section of the original WebAssembly module. pub table_types: Box<[TableType]>, - /// The memories of the WebAssembly module. + /// Memory components of the WebAssembly module used to initialize memories. + /// + /// Corresponds to the `memory` section of the original WebAssembly module. pub memory_types: Box<[MemoryType]>, - // pub elements: Option>, - // pub imports: Option>, - // pub data_segments: Option>, -} - -/// A WebAssembly value. -/// -/// See -#[derive(Clone, PartialEq, Copy)] -pub enum WasmValue { - // Num types - /// A 32-bit integer. - I32(i32), - /// A 64-bit integer. - I64(i64), - /// A 32-bit float. - F32(f32), - /// A 64-bit float. - F64(f64), - // Vec types - // V128(i128), -} - -impl WasmValue { - /// Get the default value for a given type. - pub fn default_for(ty: ValType) -> Self { - match ty { - ValType::I32 => Self::I32(0), - ValType::I64 => Self::I64(0), - ValType::F32 => Self::F32(0.0), - ValType::F64 => Self::F64(0.0), - ValType::V128 => unimplemented!("V128 is not yet supported"), - ValType::FuncRef => unimplemented!("FuncRef is not yet supported"), - ValType::ExternRef => unimplemented!("ExternRef is not yet supported"), - } - } -} - -impl From for WasmValue { - fn from(i: i32) -> Self { - Self::I32(i) - } -} - -impl From for WasmValue { - fn from(i: i64) -> Self { - Self::I64(i) - } -} - -impl From for WasmValue { - fn from(i: f32) -> Self { - Self::F32(i) - } -} - -impl From for WasmValue { - fn from(i: f64) -> Self { - Self::F64(i) - } -} -// impl From for WasmValue { -// fn from(i: i128) -> Self { -// Self::V128(i) -// } -// } + /// Imports of the WebAssembly module. + /// + /// Corresponds to the `import` section of the original WebAssembly module. + pub imports: Box<[Import]>, -impl TryFrom for i32 { - type Error = (); + /// Data segments of the WebAssembly module. + /// + /// Corresponds to the `data` section of the original WebAssembly module. + pub data: Box<[Data]>, - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::I32(i) => Ok(i), - _ => Err(()), - } - } -} - -impl TryFrom for i64 { - type Error = (); - - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::I64(i) => Ok(i), - _ => Err(()), - } - } -} - -impl TryFrom for f32 { - type Error = (); - - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::F32(i) => Ok(i), - _ => Err(()), - } - } -} - -impl TryFrom for f64 { - type Error = (); - - fn try_from(value: WasmValue) -> Result { - match value { - WasmValue::F64(i) => Ok(i), - _ => Err(()), - } - } -} - -impl Debug for WasmValue { - fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { - match self { - WasmValue::I32(i) => write!(f, "i32({})", i), - WasmValue::I64(i) => write!(f, "i64({})", i), - WasmValue::F32(i) => write!(f, "f32({})", i), - WasmValue::F64(i) => write!(f, "f64({})", i), - // WasmValue::V128(i) => write!(f, "v128({})", i), - } - } -} - -impl WasmValue { - /// Get the type of a [`WasmValue`] - pub fn val_type(&self) -> ValType { - match self { - Self::I32(_) => ValType::I32, - Self::I64(_) => ValType::I64, - Self::F32(_) => ValType::F32, - Self::F64(_) => ValType::F64, - // Self::V128(_) => ValType::V128, - } - } -} - -/// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ValType { - /// A 32-bit integer. - I32, - /// A 64-bit integer. - I64, - /// A 32-bit float. - F32, - /// A 64-bit float. - F64, - /// A 128-bit vector. - V128, - /// A reference to a function. - FuncRef, - /// A reference to an external value. - ExternRef, + /// Element segments of the WebAssembly module. + /// + /// Corresponds to the `elem` section of the original WebAssembly module. + pub elements: Box<[Element]>, } /// A WebAssembly External Kind. /// /// See -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ExternalKind { /// A WebAssembly Function. Func, @@ -238,59 +118,95 @@ pub enum ExternalKind { /// /// See pub type Addr = u32; + +// aliases for clarity pub type FuncAddr = Addr; pub type TableAddr = Addr; pub type MemAddr = Addr; pub type GlobalAddr = Addr; -pub type ElmAddr = Addr; +pub type ElemAddr = Addr; pub type DataAddr = Addr; pub type ExternAddr = Addr; + // additional internal addresses pub type TypeAddr = Addr; -pub type LocalAddr = Addr; +pub type LocalAddr = u16; // there can't be more than 50.000 locals in a function pub type LabelAddr = Addr; pub type ModuleInstanceAddr = Addr; /// A WebAssembly External Value. /// /// See -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ExternVal { Func(FuncAddr), Table(TableAddr), - Mem(MemAddr), + Memory(MemAddr), Global(GlobalAddr), } +impl ExternVal { + #[inline] + pub fn kind(&self) -> ExternalKind { + match self { + Self::Func(_) => ExternalKind::Func, + Self::Table(_) => ExternalKind::Table, + Self::Memory(_) => ExternalKind::Memory, + Self::Global(_) => ExternalKind::Global, + } + } + + #[inline] + pub fn new(kind: ExternalKind, addr: Addr) -> Self { + match kind { + ExternalKind::Func => Self::Func(addr), + ExternalKind::Table => Self::Table(addr), + ExternalKind::Memory => Self::Memory(addr), + ExternalKind::Global => Self::Global(addr), + } + } +} + /// The type of a WebAssembly Function. /// /// See -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct FuncType { pub params: Box<[ValType]>, pub results: Box<[ValType]>, } -impl FuncType { - /// Get the number of parameters of a function type. - pub fn empty() -> Self { - Self { - params: Box::new([]), - results: Box::new([]), - } - } +#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct ValueCounts { + pub c32: u32, + pub c64: u32, + pub c128: u32, + pub cref: u32, } -/// A WebAssembly Function -#[derive(Debug, Clone)] -pub struct Function { - pub ty: TypeAddr, - pub locals: Box<[ValType]>, +#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct ValueCountsSmall { + pub c32: u16, + pub c64: u16, + pub c128: u16, + pub cref: u16, +} + +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct WasmFunction { pub instructions: Box<[Instruction]>, + pub locals: ValueCounts, + pub params: ValueCountsSmall, + pub ty: FuncType, } /// A WebAssembly Module Export -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Export { /// The name of the export. pub name: Box, @@ -300,32 +216,124 @@ pub struct Export { pub index: u32, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Global { + pub ty: GlobalType, + pub init: ConstInstruction, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct GlobalType { pub mutable: bool, pub ty: ValType, - pub init: ConstInstruction, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct TableType { pub element_type: ValType, pub size_initial: u32, pub size_max: Option, } -#[derive(Debug, Clone)] +impl TableType { + pub fn empty() -> Self { + Self { element_type: ValType::RefFunc, size_initial: 0, size_max: None } + } + + pub fn new(element_type: ValType, size_initial: u32, size_max: Option) -> Self { + Self { element_type, size_initial, size_max } + } +} /// Represents a memory's type. -#[derive(Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct MemoryType { pub arch: MemoryArch, pub page_count_initial: u64, pub page_count_max: 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 } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum MemoryArch { I32, I64, } + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct Import { + pub module: Box, + pub name: Box, + pub kind: ImportKind, +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub enum ImportKind { + Function(TypeAddr), + Table(TableType), + Memory(MemoryType), + Global(GlobalType), +} + +impl From<&ImportKind> for ExternalKind { + #[inline] + fn from(kind: &ImportKind) -> Self { + match kind { + ImportKind::Function(_) => Self::Func, + ImportKind::Table(_) => Self::Table, + ImportKind::Memory(_) => Self::Memory, + ImportKind::Global(_) => Self::Global, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub struct Data { + pub data: Box<[u8]>, + pub range: Range, + pub kind: DataKind, +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +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))] +pub struct Element { + pub kind: ElementKind, + pub items: Box<[ElementItem]>, + pub range: Range, + pub ty: ValType, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub enum ElementKind { + Passive, + Active { table: TableAddr, offset: ConstInstruction }, + Declared, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +pub enum ElementItem { + Func(FuncAddr), + Expr(ConstInstruction), +} diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs new file mode 100644 index 0000000..416f678 --- /dev/null +++ b/crates/types/src/value.rs @@ -0,0 +1,180 @@ +use core::fmt::Debug; + +use crate::{ConstInstruction, ExternAddr, FuncAddr}; + +/// A WebAssembly value. +/// +/// See +#[derive(Clone, Copy, PartialEq)] +pub enum WasmValue { + // Num types + /// A 32-bit integer. + I32(i32), + /// A 64-bit integer. + I64(i64), + /// A 32-bit float. + F32(f32), + /// A 64-bit float. + F64(f64), + // /// A 128-bit vector + V128(u128), + + RefExtern(ExternAddr), + RefFunc(FuncAddr), + RefNull(ValType), +} + +impl WasmValue { + #[inline] + pub fn const_instr(&self) -> ConstInstruction { + match self { + Self::I32(i) => ConstInstruction::I32Const(*i), + Self::I64(i) => ConstInstruction::I64Const(*i), + Self::F32(i) => ConstInstruction::F32Const(*i), + Self::F64(i) => ConstInstruction::F64Const(*i), + Self::RefFunc(i) => ConstInstruction::RefFunc(*i), + Self::RefNull(ty) => ConstInstruction::RefNull(*ty), + + // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr), + _ => unimplemented!("no const_instr for {:?}", self), + } + } + + /// Get the default value for a given type. + #[inline] + pub fn default_for(ty: ValType) -> Self { + match ty { + ValType::I32 => Self::I32(0), + ValType::I64 => Self::I64(0), + ValType::F32 => Self::F32(0.0), + ValType::F64 => Self::F64(0.0), + ValType::V128 => Self::V128(0), + ValType::RefFunc => Self::RefNull(ValType::RefFunc), + ValType::RefExtern => Self::RefNull(ValType::RefExtern), + } + } + + #[inline] + pub fn eq_loose(&self, other: &Self) -> bool { + match (self, other) { + (Self::I32(a), Self::I32(b)) => a == b, + (Self::I64(a), Self::I64(b)) => a == b, + (Self::RefNull(v), Self::RefNull(v2)) => v == v2, + (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2, + (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2, + (Self::F32(a), Self::F32(b)) => { + if a.is_nan() && b.is_nan() { + true // Both are NaN, treat them as equal + } else { + a.to_bits() == b.to_bits() + } + } + (Self::F64(a), Self::F64(b)) => { + if a.is_nan() && b.is_nan() { + true // Both are NaN, treat them as equal + } else { + a.to_bits() == b.to_bits() + } + } + _ => false, + } + } +} + +#[cold] +fn cold() {} + +impl Debug for WasmValue { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { + match self { + WasmValue::I32(i) => write!(f, "i32({i})"), + WasmValue::I64(i) => write!(f, "i64({i})"), + WasmValue::F32(i) => write!(f, "f32({i})"), + WasmValue::F64(i) => write!(f, "f64({i})"), + WasmValue::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:?})"), + } + } +} + +impl WasmValue { + /// Get the type of a [`WasmValue`] + #[inline] + pub fn val_type(&self) -> ValType { + match self { + Self::I32(_) => ValType::I32, + Self::I64(_) => ValType::I64, + Self::F32(_) => ValType::F32, + Self::F64(_) => ValType::F64, + Self::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))] +pub enum ValType { + /// A 32-bit integer. + I32, + /// A 64-bit integer. + I64, + /// A 32-bit float. + F32, + /// A 64-bit float. + F64, + /// A 128-bit vector + V128, + /// A reference to a function. + RefFunc, + /// A reference to an external value. + RefExtern, +} + +impl ValType { + #[inline] + pub fn default_value(&self) -> WasmValue { + WasmValue::default_for(*self) + } + + #[inline] + pub fn is_simd(&self) -> bool { + matches!(self, ValType::V128) + } +} + +macro_rules! impl_conversion_for_wasmvalue { + ($($t:ty => $variant:ident),*) => { + $( + // Implementing From<$t> for WasmValue + impl From<$t> for WasmValue { + #[inline] + fn from(i: $t) -> Self { + Self::$variant(i) + } + } + + // Implementing TryFrom for $t + impl TryFrom for $t { + type Error = (); + + #[inline] + fn try_from(value: WasmValue) -> Result { + if let WasmValue::$variant(i) = value { + Ok(i) + } else { + cold(); + Err(()) + } + } + } + )* + } +} + +impl_conversion_for_wasmvalue! { i32 => I32, i64 => I64, f32 => F32, f64 => F64, u128 => V128 } diff --git a/crates/wasm-testsuite/Cargo.toml b/crates/wasm-testsuite/Cargo.toml index 3e6d647..8edcb8a 100644 --- a/crates/wasm-testsuite/Cargo.toml +++ b/crates/wasm-testsuite/Cargo.toml @@ -1,12 +1,13 @@ [package] name="wasm-testsuite" -version="0.2.0" +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" @@ -15,4 +16,4 @@ path="lib.rs" independent=true [dependencies] -rust-embed={version="8.1.0", features=["include-exclude"]} +rust-embed={version="8.4", features=["include-exclude"]} diff --git a/crates/wasm-testsuite/data b/crates/wasm-testsuite/data index dc27dad..ae5a669 160000 --- a/crates/wasm-testsuite/data +++ b/crates/wasm-testsuite/data @@ -1 +1 @@ -Subproject commit dc27dad3e34e466bdbfea32fe3c73f5e31f88560 +Subproject commit ae5a66933070b705dde56c2a71bf3fbc33282864 diff --git a/crates/wasm-testsuite/lib.rs b/crates/wasm-testsuite/lib.rs index 9e38bfc..1f42664 100644 --- a/crates/wasm-testsuite/lib.rs +++ b/crates/wasm-testsuite/lib.rs @@ -2,10 +2,7 @@ #![forbid(unsafe_code)] #![doc(test( no_crate_inject, - attr( - deny(warnings, rust_2018_idioms), - allow(dead_code, unused_assignments, unused_variables) - ) + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] @@ -21,56 +18,39 @@ struct Asset; /// /// Includes all proposals from #[rustfmt::skip] -pub const PROPOSALS: &[&str] = &["annotations", "exception-handling", "memory64", "function-references", "multi-memory", "relaxed-simd", "tail-call", "threads", "extended-const", "gc"]; +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. +/// 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","token.wast","traps.wast","type.wast","unreachable.wast","unreached-invalid.wast","unwind.wast","utf8-custom-section-id.wast","utf8-import-field.wast","utf8-import-module.wast","utf8-invalid-encoding.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","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"]; +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"]; -/// Get all test file names and their contents. -pub fn get_tests_wast(include_proposals: &[String]) -> impl Iterator)> { - get_tests(include_proposals) - .filter_map(|name| Some((name.clone(), get_test_wast(&name)?))) - .map(|(name, data)| (name, Cow::Owned(data.to_vec()))) -} - -/// Get all test file names. -pub fn get_tests(include_proposals: &[String]) -> impl Iterator { - let include_proposals = include_proposals.to_vec(); +/// 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('/'); - match parts.next() { - Some("proposals") => { - let proposal = parts.next(); - let test_name = parts.next().unwrap_or_default(); - - if proposal.map_or(false, |p| include_proposals.contains(&p.to_string())) { - let full_path = format!("{}/{}", proposal.unwrap_or_default(), test_name); - Some(full_path) - } else { - None - } - } - Some(test_name) => Some(test_name.to_owned()), - None => None, + 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> { - if !name.ends_with(".wast") { - panic!("Expected .wast file. Got: {}", name); - } + assert!(name.ends_with(".wast"), "Expected .wast file. Got: {name}"); match name.contains('/') { - true => Asset::get(&format!("proposals/{}", name)).map(|x| x.data), + true => Asset::get(&format!("proposals/{name}")).map(|x| x.data), false => Asset::get(name).map(|x| x.data), } } diff --git a/examples/archive.rs b/examples/archive.rs new file mode 100644 index 0000000..ea82c51 --- /dev/null +++ b/examples/archive.rs @@ -0,0 +1,29 @@ +use eyre::Result; +use tinywasm::{parser::Parser, types::TinyWasmModule, Module, Store}; + +const WASM: &str = r#" +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + local.get $lhs + local.get $rhs + i32.add) + (export "add" (func $add))) +"#; + +fn main() -> Result<()> { + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let module = Parser::default().parse_module_bytes(wasm)?; + let twasm = module.serialize_twasm(); + + // now, you could e.g. write twasm to a file called `add.twasm` + // and load it later in a different program + + let module: Module = TinyWasmModule::from_twasm(&twasm)?.into(); + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + + assert_eq!(add.call(&mut store, (1, 2))?, 3); + + Ok(()) +} diff --git a/examples/linking.rs b/examples/linking.rs new file mode 100644 index 0000000..d215898 --- /dev/null +++ b/examples/linking.rs @@ -0,0 +1,41 @@ +use eyre::Result; +use tinywasm::{Module, Store}; + +const WASM_ADD: &str = r#" +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + local.get $lhs + local.get $rhs + i32.add) + (export "add" (func $add))) +"#; + +const WASM_IMPORT: &str = r#" +(module + (import "adder" "add" (func $add (param i32 i32) (result i32))) + (func $main (result i32) + i32.const 1 + i32.const 2 + call $add) + (export "main" (func $main)) +) +"#; + +fn main() -> Result<()> { + let wasm_add = wat::parse_str(WASM_ADD).expect("failed to parse wat"); + let wasm_import = wat::parse_str(WASM_IMPORT).expect("failed to parse wat"); + + let add_module = Module::parse_bytes(&wasm_add)?; + let import_module = Module::parse_bytes(&wasm_import)?; + + let mut store = Store::default(); + let add_instance = add_module.instantiate(&mut store, None)?; + + let mut imports = tinywasm::Imports::new(); + imports.link_module("adder", add_instance.id())?; + let import_instance = import_module.instantiate(&mut store, Some(imports))?; + + let main = import_instance.exported_func::<(), i32>(&store, "main")?; + assert_eq!(main.call(&mut store, ())?, 3); + Ok(()) +} diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml new file mode 100644 index 0000000..f88fde4 --- /dev/null +++ b/examples/rust/Cargo.toml @@ -0,0 +1,41 @@ +cargo-features=["per-package-target"] + +# treat this as an independent package +[workspace] + +[package] +publish=false +name="rust-wasm-examples" +forced-target="wasm32-unknown-unknown" +edition="2021" + +[dependencies] +tinywasm={path="../../crates/tinywasm", features=["parser", "std", "nightly"]} +argon2={version="0.5"} + +[[bin]] +name="hello" +path="src/hello.rs" + +[[bin]] +name="print" +path="src/print.rs" + +[[bin]] +name="tinywasm" +path="src/tinywasm.rs" + +[[bin]] +name="fibonacci" +path="src/fibonacci.rs" + +[[bin]] +name="argon2id" +path="src/argon2id.rs" + +[profile.wasm] +opt-level=3 +lto="thin" +codegen-units=1 +panic="abort" +inherits="release" diff --git a/examples/rust/README.md b/examples/rust/README.md new file mode 100644 index 0000000..c24054c --- /dev/null +++ b/examples/rust/README.md @@ -0,0 +1,6 @@ +# WebAssembly Rust Examples + +This is a seperate crate that generates WebAssembly from Rust code. +It is used by the `wasm-rust` example. + +Requires the `wasm32-unknown-unknown` target to be installed. diff --git a/examples/rust/analyze.py b/examples/rust/analyze.py new file mode 100644 index 0000000..a813c1c --- /dev/null +++ b/examples/rust/analyze.py @@ -0,0 +1,35 @@ +import re +import sys +from collections import Counter + +# Check if a file path was provided +if len(sys.argv) < 3: + print("Usage: python script.py sequence_length path/to/yourfile.wat") + sys.exit(1) + +# The first command line argument is the file path +seq_len = int(sys.argv[1]) +file_path = sys.argv[2] + +# Regex to match WASM operators, adjust as necessary +operator_pattern = re.compile(r"\b[a-z0-9_]+\.[a-z0-9_]+\b") + +# Read the file +with open(file_path, "r") as file: + content = file.read() + +# Find all operators +operators = operator_pattern.findall(content) + +# Generate sequences of three consecutive operators +sequences = [" ".join(operators[i : i + seq_len]) for i in range(len(operators) - 2)] + +# Count occurrences of each sequence +sequence_counts = Counter(sequences) + +# Sort sequences by their count, this time in ascending order for reverse display +sorted_sequences = sorted(sequence_counts.items(), key=lambda x: x[1]) + +# Print the sequences, now from least common to most common +for sequence, count in sorted_sequences: + print(f"{sequence}: {count}") diff --git a/examples/rust/build.sh b/examples/rust/build.sh new file mode 100755 index 0000000..fcfd01c --- /dev/null +++ b/examples/rust/build.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" + +bins=("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" + +# ensure out dir exists +mkdir -p "$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" + + 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 + + if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then + wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" + fi +done diff --git a/examples/rust/rust-toolchain.toml b/examples/rust/rust-toolchain.toml new file mode 100644 index 0000000..21a0cab --- /dev/null +++ b/examples/rust/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel="nightly" +targets=["wasm32-unknown-unknown"] diff --git a/examples/rust/src/argon2id.rs b/examples/rust/src/argon2id.rs new file mode 100644 index 0000000..01ea7ca --- /dev/null +++ b/examples/rust/src/argon2id.rs @@ -0,0 +1,14 @@ +#![no_main] + +#[no_mangle] +pub extern "C" fn argon2id(m_cost: i32, t_cost: i32, p_cost: i32) -> i32 { + let password = b"password"; + let salt = b"some random salt"; + + let params = argon2::Params::new(m_cost as u32, t_cost as u32, p_cost as u32, None).unwrap(); + let argon = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); + + let mut hash = [0u8; 32]; + argon.hash_password_into(password, salt, &mut hash).unwrap(); + hash[0] as i32 +} diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs new file mode 100644 index 0000000..b847ad5 --- /dev/null +++ b/examples/rust/src/fibonacci.rs @@ -0,0 +1,22 @@ +#![no_main] + +#[no_mangle] +pub extern "C" fn fibonacci(n: i32) -> i32 { + let mut sum = 0; + let mut last = 0; + let mut curr = 1; + for _i in 1..n { + sum = last + curr; + last = curr; + curr = sum; + } + sum +} + +#[no_mangle] +pub extern "C" fn fibonacci_recursive(n: i32) -> i32 { + if n <= 1 { + return n; + } + fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2) +} diff --git a/examples/rust/src/hello.rs b/examples/rust/src/hello.rs new file mode 100644 index 0000000..c8e2ac3 --- /dev/null +++ b/examples/rust/src/hello.rs @@ -0,0 +1,28 @@ +#![no_main] + +#[link(wasm_import_module = "env")] +extern "C" { + fn print_utf8(location: i64, len: i32); +} + +const ARG: &[u8] = &[0u8; 100]; + +#[no_mangle] +pub unsafe extern "C" fn arg_ptr() -> i32 { + ARG.as_ptr() as i32 +} + +#[no_mangle] +pub unsafe extern "C" fn arg_size() -> i32 { + ARG.len() as i32 +} + +#[no_mangle] +pub unsafe extern "C" fn hello(len: i32) { + let arg = core::str::from_utf8(&ARG[0..len as usize]).unwrap(); + let res = format!("Hello, {}!", arg).as_bytes().to_vec(); + + let len = res.len() as i32; + let ptr = res.leak().as_ptr() as i64; + print_utf8(ptr, len); +} diff --git a/examples/rust/src/print.rs b/examples/rust/src/print.rs new file mode 100644 index 0000000..d04daa3 --- /dev/null +++ b/examples/rust/src/print.rs @@ -0,0 +1,11 @@ +#![no_main] + +#[link(wasm_import_module = "env")] +extern "C" { + fn printi32(x: i32); +} + +#[no_mangle] +pub unsafe extern "C" fn add_and_print(lh: i32, rh: i32) { + printi32(lh + rh); +} diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs new file mode 100644 index 0000000..08c8135 --- /dev/null +++ b/examples/rust/src/tinywasm.rs @@ -0,0 +1,32 @@ +#![no_main] +use tinywasm::{Extern, FuncContext}; + +#[link(wasm_import_module = "env")] +extern "C" { + fn printi32(x: i32); +} + +#[no_mangle] +pub extern "C" fn hello() { + let _ = run(); +} + +fn run() -> tinywasm::Result<()> { + let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; + let mut store = tinywasm::Store::default(); + let mut imports = tinywasm::Imports::new(); + + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, v: i32| { + unsafe { printi32(v) } + Ok(()) + }), + )?; + let instance = module.instantiate(&mut store, Some(imports))?; + + let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; + Ok(()) +} diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs new file mode 100644 index 0000000..9f2b28c --- /dev/null +++ b/examples/rust/src/tinywasm_no_std.rs @@ -0,0 +1,33 @@ +#![no_main] +#![no_std] +use tinywasm::{Extern, FuncContext}; + +#[link(wasm_import_module = "env")] +extern "C" { + fn printi32(x: i32); +} + +#[no_mangle] +pub extern "C" fn hello() { + let _ = run(); +} + +fn run() -> tinywasm::Result<()> { + let module = tinywasm::Module::parse_bytes(include_bytes!("../out/print.wasm"))?; + let mut store = tinywasm::Store::default(); + let mut imports = tinywasm::Imports::new(); + + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, v: i32| { + unsafe { printi32(v) } + Ok(()) + }), + )?; + let instance = module.instantiate(&mut store, Some(imports))?; + + let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; + Ok(()) +} diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..e0842dd --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,22 @@ +use eyre::Result; +use tinywasm::{Module, Store}; + +const WASM: &str = r#" +(module + (func $add (param $lhs i32) (param $rhs i32) (result i32) + local.get $lhs + local.get $rhs + i32.add) + (export "add" (func $add))) +"#; + +fn main() -> Result<()> { + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + + assert_eq!(add.call(&mut store, (1, 2))?, 3); + Ok(()) +} diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs new file mode 100644 index 0000000..169fed5 --- /dev/null +++ b/examples/wasm-rust.rs @@ -0,0 +1,153 @@ +use std::hint::black_box; + +use eyre::{eyre, Result}; +use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; + +/// Examples of using WebAssembly compiled from Rust with tinywasm. +/// +/// These examples are meant to be run with `cargo run --example wasm-rust `. +/// For example, `cargo run --example wasm-rust hello`. +/// +/// To run these, you first need to compile the Rust examples to WebAssembly: +/// +/// ```sh +/// ./examples/rust/build.sh +/// ``` +/// +/// This requires the `wasm32-unknown-unknown` target, `binaryen` and `wabt` to be installed. +/// `rustup target add wasm32-unknown-unknown`. +/// +/// +/// +fn main() -> Result<()> { + pretty_env_logger::init(); + + if !std::path::Path::new("./examples/rust/out/").exists() { + return Err(eyre!("No WebAssembly files found. See examples/wasm-rust.rs for instructions.")); + } + + let args = std::env::args().collect::>(); + if args.len() < 2 { + println!("Usage: cargo run --example wasm-rust "); + println!("Available examples:"); + println!(" hello"); + println!(" printi32"); + println!(" fibonacci - calculate fibonacci(30)"); + println!(" tinywasm - run printi32 inside of tinywasm inside of itself"); + println!(" argon2id - run argon2id(1000, 2, 1)"); + return Ok(()); + } + + match args[1].as_str() { + "hello" => hello()?, + "printi32" => printi32()?, + "fibonacci" => fibonacci()?, + "tinywasm" => tinywasm()?, + "argon2id" => argon2id()?, + "all" => { + println!("Running all examples"); + println!("\nhello.wasm:"); + hello()?; + println!("\nprinti32.wasm:"); + printi32()?; + println!("\nfibonacci.wasm:"); + fibonacci()?; + println!("\ntinywasm.wasm:"); + tinywasm()?; + println!("argon2id.wasm:"); + argon2id()?; + } + _ => {} + } + + Ok(()) +} + +fn tinywasm() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/tinywasm.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 hello() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/hello.wasm")?; + let mut store = Store::default(); + + let mut imports = Imports::new(); + imports.define( + "env", + "print_utf8", + Extern::typed_func(|mut ctx: FuncContext<'_>, args: (i64, i32)| { + let mem = ctx.exported_memory("memory")?; + let ptr = args.0 as usize; + let len = args.1 as usize; + let string = mem.load_string(ptr, len)?; + println!("{string}"); + Ok(()) + }), + )?; + + let instance = module.instantiate(&mut store, Some(imports))?; + let arg_ptr = instance.exported_func::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; + let arg = b"world"; + + instance.exported_memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; + let hello = instance.exported_func::(&store, "hello")?; + hello.call(&mut store, arg.len() as i32)?; + + Ok(()) +} + +fn printi32() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/print.wasm")?; + let mut store = Store::default(); + + let mut imports = Imports::new(); + imports.define( + "env", + "printi32", + Extern::typed_func(|_: FuncContext<'_>, x: i32| { + println!("{x}"); + Ok(()) + }), + )?; + + let instance = module.instantiate(&mut store, Some(imports))?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + add_and_print.call(&mut store, (1, 2))?; + + Ok(()) +} + +fn fibonacci() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/fibonacci.opt.wasm")?; + let mut store = Store::default(); + + let instance = module.instantiate(&mut store, None)?; + let fibonacci = instance.exported_func::(&store, "fibonacci_recursive")?; + let n = 26; + let result = fibonacci.call(&mut store, n)?; + println!("fibonacci({n}) = {result}"); + + Ok(()) +} + +fn argon2id() -> Result<()> { + let module = Module::parse_file("./examples/rust/out/argon2id.opt.wasm")?; + let mut store = Store::default(); + + let instance = module.instantiate(&mut store, None)?; + let argon2id = instance.exported_func::<(i32, i32, i32), i32>(&store, "argon2id")?; + argon2id.call(&mut store, (1000, 2, 1))?; + + Ok(()) +} diff --git a/examples/wasm/add.wat b/examples/wasm/add.wat deleted file mode 100644 index 4976689..0000000 --- a/examples/wasm/add.wat +++ /dev/null @@ -1,16 +0,0 @@ -(module - (func $add (export "add") (param $a i32) (param $b i32) (result i32) - local.get $a - local.get $b - i32.add) - - (func $sub (export "sub") (param $a i32) (param $b i32) (result i32) - local.get $a - local.get $b - i32.sub) - - (func $add_64 (export "add_64") (param $a i64) (param $b i64) (result i64) - local.get $a - local.get $b - i64.add) -) diff --git a/examples/wasm/call.wat b/examples/wasm/call.wat deleted file mode 100644 index d515e78..0000000 --- a/examples/wasm/call.wat +++ /dev/null @@ -1,42 +0,0 @@ -(module - (func (export "check") (param i32) (result i32) - i64.const 0 ;; Set 0 to the stack - local.get 0 - i32.const 10 - i32.lt_s ;; Check if input is less than 10 - if (param i64) (result i32) ;; If so, - i32.const 1 ;; Set 1 to the stack - return ;; And return immediately - else ;; Otherwise, - i32.const 0 ;; Set 0 to the stack - return ;; And return immediately - end) ;; End of the if/else block - - (func (export "simple_block") (result i32) - (block (result i32) - (i32.const 0) - (i32.const 1) - (i32.add) - ) - ) - - (func (export "checkloop") (result i32) - (block (result i32) - (i32.const 0) - (loop (param i32) - (block (br 2 (i32.const 18))) - (br 0 (i32.const 20)) - ) - (i32.const 19) - ) - ) - - - (func (export "param") (result i32) - (i32.const 1) - (loop (param i32) (result i32) - (i32.const 2) - (i32.add) - ) - ) -) \ No newline at end of file diff --git a/examples/wasm/global.wat b/examples/wasm/global.wat deleted file mode 100644 index 22bde45..0000000 --- a/examples/wasm/global.wat +++ /dev/null @@ -1 +0,0 @@ -(module (global i32 (i32.const 0))) \ No newline at end of file diff --git a/examples/wasm/helloworld.wasm b/examples/wasm/helloworld.wasm deleted file mode 100644 index a5c95d0..0000000 Binary files a/examples/wasm/helloworld.wasm and /dev/null differ diff --git a/examples/wasm/helloworld.wat b/examples/wasm/helloworld.wat deleted file mode 100644 index b74c98f..0000000 --- a/examples/wasm/helloworld.wat +++ /dev/null @@ -1,15 +0,0 @@ -(module - ;; Imports from JavaScript namespace - (import "console" "log" (func $log (param i32 i32))) ;; Import log function - (import "js" "mem" (memory 1)) ;; Import 1 page of memory (54kb) - - ;; Data section of our module - (data (i32.const 0) "Hello World from WebAssembly!") - - ;; Function declaration: Exported as helloWorld(), no arguments - (func (export "helloWorld") - i32.const 0 ;; pass offset 0 to log - i32.const 29 ;; pass length 29 to log (strlen of sample text) - call $log - ) -) \ No newline at end of file diff --git a/examples/wasm/loop.wat b/examples/wasm/loop.wat deleted file mode 100644 index 0dcd191..0000000 --- a/examples/wasm/loop.wat +++ /dev/null @@ -1,84 +0,0 @@ -(module - (func $loop_test (export "loop_test") (result i32) - (local i32) ;; Local 0: Counter - - ;; Initialize the counter - (local.set 0 (i32.const 0)) - - ;; Loop starts here - (loop $my_loop - ;; Increment the counter - (local.set 0 (i32.add (local.get 0) (i32.const 1))) - - ;; Exit condition: break out of the loop if counter >= 10 - (br_if $my_loop (i32.lt_s (local.get 0) (i32.const 10))) - ) - - ;; Return the counter value - (local.get 0) - ) - - (func $loop_test3 (export "loop_test3") (result i32) - (local i32) ;; Local 0: Counter - - ;; Initialize the counter - (local.set 0 (i32.const 0)) - - ;; Loop starts here - (block $exit_loop ;; Label for exiting the loop - (loop $my_loop - ;; Increment the counter - (local.set 0 (i32.add (local.get 0) (i32.const 1))) - - ;; Prepare an index for br_table - ;; Here, we use the counter, but you could modify this - ;; For simplicity, 0 will continue the loop, any other value will exit - (local.get 0) - (i32.const 10) - (i32.lt_s) - (br_table $my_loop $exit_loop) - ) - ) - - ;; Return the counter value - (local.get 0) - ) - - (func $calculate (export "loop_test2") (result i32) - (local i32) ;; Local 0: Counter for the outer loop - (local i32) ;; Local 1: Counter for the inner loop - (local i32) ;; Local 2: Result variable - - ;; Initialize variables - (local.set 0 (i32.const 0)) ;; Initialize outer loop counter - (local.set 1 (i32.const 0)) ;; Initialize inner loop counter - (local.set 2 (i32.const 0)) ;; Initialize result variable - - (block $outer ;; Outer loop label - (loop $outer_loop - (local.set 1 (i32.const 5)) ;; Reset inner loop counter for each iteration of the outer loop - - (block $inner ;; Inner loop label - (loop $inner_loop - (br_if $inner (i32.eqz (local.get 1))) ;; Break to $inner if inner loop counter is zero - - ;; Computation: Adding product of counters to the result - (local.set 2 (i32.add (local.get 2) (i32.mul (local.get 0) (local.get 1)))) - - ;; Decrement inner loop counter - (local.set 1 (i32.sub (local.get 1) (i32.const 1))) - ) - ) - - ;; Increment outer loop counter - (local.set 0 (i32.add (local.get 0) (i32.const 1))) - - ;; Break condition for outer loop: break if outer loop counter >= 5 - (br_if $outer (i32.ge_s (local.get 0) (i32.const 5))) - ) - ) - - ;; Return the result - (local.get 2) - ) -) \ No newline at end of file diff --git a/profile.sh b/profile.sh new file mode 100755 index 0000000..91bb946 --- /dev/null +++ b/profile.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +cargo build --example wasm-rust --profile profiling +samply record -r 10000 ./target/profiling/examples/wasm-rust $@ diff --git a/rustfmt.toml b/rustfmt.toml index 94ac875..589b2d2 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ max_width=120 +use_small_heuristics="Max"