diff --git a/.cargo/config.toml b/.cargo/config.toml index be912c8..e25dc35 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,14 +1,7 @@ [alias] version-dev="workspaces version --no-git-commit --force tinywasm*" dev="run -- -l debug run" -test-mvp="test --package tinywasm --test test-mvp --release -- --enable " -test-2="test --package tinywasm --test test-two --release -- --enable " -test-wast="test --package tinywasm --test test-wast -- --enable " -test-wast-release="test --package tinywasm --test test-wast --release -- --enable " -generate-charts="run --package scripts --bin generate-charts --release" -benchmark="bench -p benchmarks --bench" -# # enable for linux perf -# [target.x86_64-unknown-linux-gnu] -# linker="/usr/bin/clang" -# rustflags=["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] +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 6f2da8d..f93c5a4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -50,7 +50,10 @@ jobs: run: cargo +stable test --workspace && cargo +stable run --example wasm-rust all - name: Run MVP testsuite - run: cargo +stable test-mvp + run: cargo +stable test-wasm-1 + + - name: Run 2.0 testsuite + run: cargo +stable test-wasm-2 test-no-std: needs: build-wasm @@ -77,8 +80,11 @@ jobs: - name: Run tests (nightly, no default features) run: cargo +nightly test --workspace --no-default-features && cargo +nightly run --example wasm-rust all - - name: Run MVP testsuite (nightly) - run: cargo +nightly test-mvp + - 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 @@ -99,10 +105,15 @@ jobs: - name: Build (stable) run: cargo +stable build + - name: Run tests (stable) run: cargo +stable test + - name: Run MVP testsuite - run: cargo +stable test-mvp + run: cargo +stable test-wasm-1 + + - name: Run 2.0 testsuite + run: cargo +stable test-wasm-2 test-armv7: needs: build-wasm @@ -121,16 +132,24 @@ jobs: path: examples/rust/out - name: Run all tests (for the default workspace members) - uses: houseabsolute/actions-rust-cross@v0.0.12 + 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.12 + 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-mvp --release -- --enable" + args: "-p tinywasm --test test-wasm-2 --release" target: armv7-unknown-linux-gnueabihf toolchain: nightly diff --git a/.gitignore b/.gitignore index 1ae1f15..14456f4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ perf.* flamegraph.svg /.idea /*.iml +profile.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 51f36b4..48a2e0d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,5 @@ }, "rust-analyzer.linkedProjects": [ "./Cargo.toml", - "./examples/rust/Cargo.toml" - ] + ], } \ No newline at end of file diff --git a/BENCHMARKS.md b/BENCHMARKS.md deleted file mode 100644 index 50df853..0000000 --- a/BENCHMARKS.md +++ /dev/null @@ -1,95 +0,0 @@ -# Benchmark results - -All benchmarks are run on a Ryzen 7 5800X with 32GB of RAM on Linux 6.6. -WebAssembly files are optimized using [wasm-opt](https://github.com/WebAssembly/binaryen) (with the `--O3` flag) -and the benchmark code is available in the `crates/benchmarks` folder. - -These are mainly preliminary benchmarks, and I will be rewriting the benchmarks to be more accurate and to test more features in the future. -In particular, I want to test and improve memory usage, as well as the performance of the parser. - -Take these results with a grain of salt, as they are not very accurate and are likely to change in the future. - -## WebAssembly Settings - -All WebAssembly files are compiled with the following settings: - -- `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. -- `reference-types`, `bulk-memory`, `mutable-globals` proposals are enabled. - -## Runtime Settings - -All runtimes are compiled with the following settings: - -- `opt-level` is set to 3, `lto` is set to `thin`, `codegen-units` is set to 1. -- No CPU-specific optimizations are used as AVX2 can reduce performance by more than 50% on some CPUs. -- Default runtime settings are used - -## Versions - -- `tinywasm`: `0.7.0` -- `wasmi`: `0.31.2` -- `wasmer`: `4.3.0` - -## Results - -> Results include the time it takes to parse/compile the WebAssembly file and execute the function. - -| Benchmark | Native | TinyWasm | Wasmi | Wasmer (Single Pass) | -| ------------ | -------- | ---------- | --------- | -------------------- | -| `fib` | `6.33µs` | ` 19.18µs` | `18.26µs` | ` 51.20µs` | -| `fib-rec` | `0.27ms` | ` 16.09ms` | ` 5.08ms` | ` 0.47ms` | -| `argon2id` | `0.50ms` | ` 89.52ms` | `45.31ms` | ` 4.74ms` | -| `selfhosted` | `0.05ms` | ` 7.93ms` | ` 7.54ms` | `512.45ms` | - -> Note that parsing is still pretty slow, especially for the `selfhosted` benchmark, taking up `~6ms` for TinyWasm. -> This can be improved by using the `archive` feature, which pre-parses the WebAssembly file into tinywasm's custom bytecode format. - -### Fib - -The first benchmark is a simple optimized Fibonacci function, a good way to show the overhead of calling functions and parsing the bytecode. -TinyWasm is slightly faster than Wasmi here, but that's probably because of the overhead of parsing the bytecode, as TinyWasm uses a custom bytecode to pre-process the WebAssembly bytecode. - -### Fib-Rec - -This benchmark is a recursive Fibonacci function, highlighting some issues with the current implementation of TinyWasm's Call Stack. -TinyWasm is a lot slower here, but that's because there's currently no way to reuse the same Call Frame for recursive calls, so a new Call Frame is allocated for every call. This is not a problem for most programs; the upcoming `tail-call` proposal will make this much easier to implement. - -### Argon2id - -This benchmark runs the Argon2id hashing algorithm with 2 iterations, 1KB of memory, and 1 parallel lane. -I had to decrease the memory usage from the default to 1KB because the interpreters were struggling to finish in a reasonable amount of time. -This is where `simd` instructions would be really useful, and it also highlights some of the issues with the current implementation of TinyWasm's Value Stack and Memory Instances. These spend much time on stack operations, so they might be a good place to experiment with Arena Allocation. - -### Selfhosted - -This benchmark runs TinyWasm itself in the VM, and parses and executes the `print.wasm` example from the `examples` folder. -This is a good way to show some of TinyWasm's strengths - the code is quite large at 702KB and Wasmer struggles massively with it, even with the Single Pass compiler. I think it's a decent real-world performance benchmark, but it definitely favors TinyWasm a bit. - -Wasmer also offers a pre-parsed module format, so keep in mind that this number could be a bit lower if that was used (but probably still on the same order of magnitude). This number seems so high that I'm not sure if I'm doing something wrong, so I will be looking into this in the future. - -### Conclusion - -After profiling and fixing some low-hanging fruits, I found the biggest bottleneck to be Vector operations, especially for the Value Stack, and having shared access to Memory Instances using RefCell. These are the two areas I will focus on improving in the future, trying out Arena Allocation and other data structures to improve performance. Additionally, typed FuncHandles have a significant overhead over the untyped ones, so I will also look into improving that. Still, I'm pretty happy with the results, especially considering the focus on simplicity and portability over performance. - -Something that made a much more significant difference than I expected was to give compiler hints about cold paths and to force the inlining of some functions. This made the benchmarks 30%+ faster in some cases. Many places in the codebase have comments about what optimizations have been done. - -# Running benchmarks - -Benchmarks are run using [Criterion.rs](https://github.com/bheisler/criterion.rs). To run a benchmark, use the following command: - -```sh -$ cargo benchmark -``` - -# Profiling - -To profile a benchmark, use the following command: - -```sh -$ cargo flamegraph -p benchmarks --bench -- --bench -``` - -This will generate a flamegraph in `flamegraph.svg` and a `perf.data` file. -You can use [hotspot](https://github.com/KDAB/hotspot) to analyze the `perf.data` file. -Since a lot of functions are inlined, you probably want to remove the `#[inline]` attribute from the functions you care about. -Note that this will make the benchmark considerably slower, 2-10x slower in some cases. diff --git a/CHANGELOG.md b/CHANGELOG.md index f6539a1..b9b18b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd91491..e1ca837 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,18 +7,12 @@ 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 generate-charts`**\ - Generate test result charts from the previous test runs. This is used to generate the charts in the [README](./README.md). - - **`cargo test-mvp`**\ Run the WebAssembly MVP (1.0) test suite. Be sure to cloned this repo with `--recursive` or initialize the submodules with `git submodule update --init --recursive` - **`cargo test-2`**\ Run the full WebAssembly test suite (2.0) -- **`cargo benchmark `**\ - Run a single benchmark. e.g. `cargo benchmark argon2id` - - **`cargo test-wast `**\ Run a single WAST test file. e.g. `cargo test-wast ./examples/wast/i32.wast` diff --git a/Cargo.lock b/Cargo.lock index 0703613..eb1ab92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli 0.28.1", -] - -[[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.8" @@ -49,81 +34,17 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstyle" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.3" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - -[[package]] -name = "anyhow" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "argh" @@ -144,7 +65,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.76", ] [[package]] @@ -156,80 +77,17 @@ dependencies = [ "serde", ] -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "benchmarks" -version = "0.0.0" -dependencies = [ - "argon2", - "criterion", - "tinywasm", - "wasmer", - "wasmi", - "wat", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -243,15 +101,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -263,9 +112,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -321,35 +170,11 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bytemuck" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -dependencies = [ - "serde", -] - -[[package]] -name = "bytesize" -version = "1.3.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" -dependencies = [ - "serde", -] +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cast" @@ -357,32 +182,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cc" -version = "1.0.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-targets", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -407,260 +212,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half 2.4.1", + "half", ] [[package]] name = "clap" -version = "4.4.11" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ - "anstream", "anstyle", "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.61", ] [[package]] name = "clap_lex" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" - -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors 3.5.0", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors 3.5.0", - "tracing-core", - "tracing-error", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colorchoice" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" - -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - -[[package]] -name = "core-text" -version = "19.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" -dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", - "libc", -] - -[[package]] -name = "corosensei" -version = "0.1.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" -dependencies = [ - "autocfg", - "cfg-if", - "libc", - "scopeguard", - "windows-sys 0.33.0", -] +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" -dependencies = [ - "arrayvec", - "bumpalo", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-egraph", - "cranelift-entity", - "cranelift-isle", - "gimli 0.26.2", - "log", - "regalloc2", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" -dependencies = [ - "cranelift-codegen-shared", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" - -[[package]] -name = "cranelift-egraph" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" -dependencies = [ - "cranelift-entity", - "fxhash", - "hashbrown 0.12.3", - "indexmap 1.9.3", - "log", - "smallvec", -] - -[[package]] -name = "cranelift-entity" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" - -[[package]] -name = "cranelift-frontend" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-isle" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" - -[[package]] -name = "crc32fast" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.5.1" @@ -677,7 +265,6 @@ dependencies = [ "num-traits", "once_cell", "oorandom", - "plotters", "rayon", "regex", "serde", @@ -716,20 +303,11 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -748,2513 +326,788 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.14.4" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", + "block-buffer", + "crypto-common", ] [[package]] -name = "darling" -version = "0.20.8" +name = "either" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" -dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", -] +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] -name = "darling_core" -version = "0.14.4" +name = "env_logger" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] [[package]] -name = "darling_core" -version = "0.20.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "syn 2.0.61", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "darling_macro" -version = "0.14.4" +name = "eyre" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", + "indenter", + "once_cell", ] [[package]] -name = "darling_macro" -version = "0.20.8" +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "darling_core 0.20.8", - "quote", - "syn 2.0.61", + "typenum", + "version_check", ] [[package]] -name = "dashmap" -version = "5.5.3" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "libc", + "wasi", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "globset" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", ] [[package]] -name = "derive_builder" -version = "0.12.0" +name = "half" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ - "derive_builder_macro", + "cfg-if", + "crunchy", ] [[package]] -name = "derive_builder_core" -version = "0.12.0" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", + "ahash 0.7.8", ] [[package]] -name = "derive_builder_macro" -version = "0.12.0" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "derive_builder_core", - "syn 1.0.109", + "ahash 0.8.11", ] [[package]] -name = "digest" -version = "0.10.7" +name = "hermit-abi" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] -name = "dirs-next" -version = "2.0.0" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "indexmap" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ - "libc", - "redox_users", - "winapi", + "equivalent", + "hashbrown 0.14.5", ] [[package]] -name = "dlib" -version = "0.5.2" +name = "is-terminal" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "libloading", + "hermit-abi", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "document-features" -version = "0.2.8" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "litrs", + "either", ] [[package]] -name = "downcast-rs" -version = "1.2.1" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "dwrote" -version = "0.11.0" +name = "leb128" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] -name = "dyn-clone" -version = "1.0.17" +name = "libc" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "dynasm" -version = "1.2.3" +name = "libm" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] -name = "dynasmrt" -version = "1.2.3" +name = "log" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" -dependencies = [ - "byteorder", - "dynasm", - "memmap2 0.5.10", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] -name = "either" -version = "1.11.0" +name = "memchr" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "enum-iterator" -version = "0.7.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "enum-iterator-derive", + "autocfg", ] [[package]] -name = "enum-iterator-derive" -version = "0.7.0" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "enumset" -version = "1.1.3" +name = "oorandom" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" -dependencies = [ - "enumset_derive", -] +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] -name = "enumset_derive" -version = "0.8.1" +name = "owo-colors" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" -dependencies = [ - "darling 0.20.8", - "proc-macro2", - "quote", - "syn 2.0.61", -] +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] -name = "env_logger" -version = "0.10.2" +name = "pretty_env_logger" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "humantime", - "is-terminal", + "env_logger", "log", - "regex", - "termcolor", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] [[package]] -name = "errno" -version = "0.3.9" +name = "ptr_meta" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ - "libc", - "windows-sys 0.52.0", + "ptr_meta_derive", ] [[package]] -name = "eyre" -version = "0.6.12" +name = "ptr_meta_derive" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "indenter", - "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "fallible-iterator" -version = "0.2.0" +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] [[package]] -name = "fastrand" -version = "2.1.0" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] -name = "fdeflate" -version = "0.3.4" +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "simd-adler32", + "either", + "rayon-core", ] [[package]] -name = "filetime" -version = "0.2.23" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] -name = "flate2" -version = "1.0.30" +name = "regex" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ - "crc32fast", - "miniz_oxide", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "float-ord" -version = "0.2.0" +name = "regex-automata" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] -name = "fnv" -version = "1.0.7" +name = "regex-syntax" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] -name = "font-kit" -version = "0.11.0" +name = "rend" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" 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", + "bytecheck 0.6.12", ] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "rkyv" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ - "foreign-types-shared", + "bitvec", + "bytecheck 0.6.12", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "rkyv_derive" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "form_urlencoded" -version = "1.2.1" +name = "rust-embed" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ - "percent-encoding", + "rust-embed-impl", + "rust-embed-utils", + "walkdir", ] [[package]] -name = "freetype" -version = "0.7.2" +name = "rust-embed-impl" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a440748e063798e4893ceb877151e84acef9bea9a8c6800645cf3f1b3a7806e" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ - "freetype-sys", - "libc", + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.76", + "walkdir", ] [[package]] -name = "freetype-sys" -version = "0.20.1" +name = "rust-embed-utils" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ - "cc", - "libc", - "pkg-config", + "globset", + "sha2", + "walkdir", ] [[package]] -name = "funty" -version = "2.0.0" +name = "ryu" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[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.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -dependencies = [ - "fallible-iterator", - "indexmap 1.9.3", - "stable_deref_trait", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "half" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash 0.8.11", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "image" -version = "0.24.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "jpeg-decoder", - "num-traits", - "png", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", - "serde", -] - -[[package]] -name = "indexmap-nostd" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" - -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "libc" -version = "0.2.154" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" - -[[package]] -name = "libloading" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" -dependencies = [ - "cfg-if", - "windows-targets", -] - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.5.0", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", - "simd-adler32", -] - -[[package]] -name = "more-asserts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "owo-colors" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.1", - "smallvec", - "windows-targets", -] - -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[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.3" -source = "git+https://github.com/servo/pathfinder?rev=30419d07660dc11a21e42ef4a7fa329600cff152#30419d07660dc11a21e42ef4a7fa329600cff152" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[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.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "pretty_env_logger" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" -dependencies = [ - "env_logger", - "log", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" -dependencies = [ - "bitflags 2.5.0", -] - -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "regalloc2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" -dependencies = [ - "fxhash", - "log", - "slice-group-by", - "smallvec", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "region" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" -dependencies = [ - "bitflags 1.3.2", - "libc", - "mach2", - "windows-sys 0.52.0", -] - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck 0.6.12", -] - -[[package]] -name = "rkyv" -version = "0.7.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" -dependencies = [ - "bitvec", - "bytecheck 0.6.12", - "bytes", - "hashbrown 0.12.3", - "indexmap 1.9.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rust-embed" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn 2.0.61", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" -dependencies = [ - "globset", - "sha2", - "walkdir", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[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.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", - "url", -] - -[[package]] -name = "schemars_derive" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.61", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scripts" -version = "0.0.0" -dependencies = [ - "eyre", - "plotters", -] - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "self_cell" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.201" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half 1.8.3", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.201" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.61", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.61", -] - -[[package]] -name = "serde_json" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.2.6", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared-buffer" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" -dependencies = [ - "bytes", - "memmap2 0.6.2", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tar" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "target-lexicon" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] -name = "termcolor" -version = "1.4.1" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] -name = "thiserror" -version = "1.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.61", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tinywasm" -version = "0.7.0" -dependencies = [ - "eyre", - "libm", - "log", - "owo-colors 4.0.0", - "pretty_env_logger", - "serde", - "serde_json", - "tinywasm-parser", - "tinywasm-types", - "wasm-testsuite", - "wast 207.0.0", -] - -[[package]] -name = "tinywasm-cli" -version = "0.7.0" -dependencies = [ - "argh", - "color-eyre", - "log", - "pretty_env_logger", - "tinywasm", - "wast 207.0.0", -] - -[[package]] -name = "tinywasm-parser" -version = "0.7.0" -dependencies = [ - "log", - "tinywasm-types", - "wasmparser 0.207.0", -] - -[[package]] -name = "tinywasm-root" -version = "0.0.0" -dependencies = [ - "color-eyre", - "pretty_env_logger", - "tinywasm", - "wat", -] - -[[package]] -name = "tinywasm-types" -version = "0.7.0" -dependencies = [ - "bytecheck 0.7.0", - "log", - "rkyv", -] - -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.12", -] - -[[package]] -name = "toml_datetime" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.8", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.61", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - -[[package]] -name = "ttf-parser" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "uuid" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.5.0" +name = "seahash" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "serde" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "serde_derive", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "serde_derive" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ - "bumpalo", - "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.61", - "wasm-bindgen-shared", + "syn 2.0.76", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" +name = "serde_json" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "itoa", + "memchr", + "ryu", + "serde", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" +name = "sha2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.61", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" +name = "simdutf8" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] -name = "wasm-encoder" -version = "0.32.0" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "leb128", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "wasm-encoder" -version = "0.207.0" +name = "syn" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d996306fb3aeaee0d9157adbe2f670df0236caf19f6728b221e92d0f27b3fe17" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ - "leb128", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "wasm-testsuite" -version = "0.4.0" -dependencies = [ - "rust-embed", -] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "wasmer" -version = "4.3.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6beae0c56cd5c26fe29aa613c6637bde6747a782ec3e3ed362c2dda615e701" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "bytes", - "cfg-if", - "derivative", - "indexmap 1.9.3", - "js-sys", - "more-asserts", - "rustc-demangle", - "serde", - "serde-wasm-bindgen", - "shared-buffer", - "target-lexicon", - "thiserror", - "tracing", - "wasm-bindgen", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-compiler-singlepass", - "wasmer-derive", - "wasmer-types", - "wasmer-vm", - "wat", - "winapi", + "winapi-util", ] [[package]] -name = "wasmer-compiler" -version = "4.3.0" +name = "tinytemplate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df65b299475df71947607b24528e5a34e0fc42ad84350c242e591cbf74a6bc37" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "backtrace", - "bytes", - "cfg-if", - "enum-iterator", - "enumset", - "lazy_static", - "leb128", - "memmap2 0.5.10", - "more-asserts", - "region", - "rkyv", - "self_cell", - "shared-buffer", - "smallvec", - "thiserror", - "wasmer-types", - "wasmer-vm", - "wasmparser 0.121.2", - "winapi", - "xxhash-rust", + "serde", + "serde_json", ] [[package]] -name = "wasmer-compiler-cranelift" -version = "4.3.0" +name = "tinyvec" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42867bde8e7bda9419c9b08a20eb58ed8e493fea5ba3cb920f602df826cb7795" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "gimli 0.26.2", - "more-asserts", - "rayon", - "smallvec", - "target-lexicon", - "tracing", - "wasmer-compiler", - "wasmer-types", + "tinyvec_macros", ] [[package]] -name = "wasmer-compiler-singlepass" -version = "4.3.0" +name = "tinyvec_macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ffcce77a325738b1b64e1ec7e141b62b0706ecd7cfbf70227aedc9a8c9c1bd6" -dependencies = [ - "byteorder", - "dynasm", - "dynasmrt", - "enumset", - "gimli 0.26.2", - "lazy_static", - "more-asserts", - "rayon", - "smallvec", - "wasmer-compiler", - "wasmer-types", -] +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] -name = "wasmer-config" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a0f70c177b1c5062cfe0f5308c3317751796fef9403c22a0cd7b4cacd4ccd8" +name = "tinywasm" +version = "0.8.0" dependencies = [ - "anyhow", - "bytesize", - "derive_builder", - "hex", - "indexmap 2.2.6", - "schemars", - "semver", + "criterion", + "eyre", + "libm", + "log", + "owo-colors", + "pretty_env_logger", "serde", - "serde_cbor", "serde_json", - "serde_yaml", - "thiserror", - "toml 0.8.12", - "url", + "tinywasm-parser", + "tinywasm-types", + "wasm-testsuite", + "wast", ] [[package]] -name = "wasmer-derive" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "231826965de8fe7bfba02b3b8adac3304ca8b7fea92dc6129e8330e020aa6b45" +name = "tinywasm-cli" +version = "0.8.0" dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", + "argh", + "eyre", + "log", + "pretty_env_logger", + "tinywasm", + "wast", ] [[package]] -name = "wasmer-types" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9782e1a5a28ae2c5165cdfc1aa5ce2aa89b20f745ae3f3a3974f6500849cc31a" +name = "tinywasm-parser" +version = "0.8.0" dependencies = [ - "bytecheck 0.6.12", - "enum-iterator", - "enumset", - "getrandom", - "hex", - "indexmap 1.9.3", - "more-asserts", - "rkyv", - "sha2", - "target-lexicon", - "thiserror", - "webc", - "xxhash-rust", + "log", + "tinywasm-types", + "wasmparser", ] [[package]] -name = "wasmer-vm" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f143d07733ac0832f42c7acb1b0abf22f00e38505eb605951f06af382970f80" +name = "tinywasm-root" +version = "0.0.0" dependencies = [ - "backtrace", - "cc", - "cfg-if", - "corosensei", - "crossbeam-queue", - "dashmap", - "derivative", - "enum-iterator", - "fnv", - "indexmap 1.9.3", - "lazy_static", - "libc", - "mach", - "memoffset", - "more-asserts", - "region", - "scopeguard", - "thiserror", - "wasmer-types", - "winapi", + "eyre", + "pretty_env_logger", + "tinywasm", + "wat", ] [[package]] -name = "wasmi" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" +name = "tinywasm-types" +version = "0.8.0" dependencies = [ - "smallvec", - "spin", - "wasmi_arena", - "wasmi_core", - "wasmparser-nostd", + "bytecheck 0.7.0", + "log", + "rkyv", ] [[package]] -name = "wasmi_arena" -version = "0.4.1" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "wasmi_core" -version = "0.13.0" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" -dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", -] +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "wasmparser" -version = "0.121.2" +name = "unicode-width" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" -dependencies = [ - "bitflags 2.5.0", - "indexmap 2.2.6", - "semver", -] +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] -name = "wasmparser" -version = "0.207.0" +name = "uuid" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" -dependencies = [ - "ahash 0.8.11", - "bitflags 2.5.0", - "hashbrown 0.14.5", - "indexmap 2.2.6", - "semver", -] +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" [[package]] -name = "wasmparser-nostd" -version = "0.100.2" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" -dependencies = [ - "indexmap-nostd", -] +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "wast" -version = "64.0.0" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.32.0", + "same-file", + "winapi-util", ] [[package]] -name = "wast" -version = "207.0.0" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e40be9fd494bfa501309487d2dc0b3f229be6842464ecbdc54eac2679c84c93" -dependencies = [ - "bumpalo", - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.207.0", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wat" -version = "1.0.71" +name = "wasm-encoder" +version = "0.216.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" +checksum = "04c23aebea22c8a75833ae08ed31ccc020835b12a41999e58c31464271b94a88" dependencies = [ - "wast 64.0.0", + "leb128", ] [[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +name = "wasm-testsuite" +version = "0.5.0" dependencies = [ - "js-sys", - "wasm-bindgen", + "rust-embed", ] [[package]] -name = "webc" -version = "6.0.0-alpha8" +name = "wasmparser" +version = "0.216.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf53893f8df356f1305446c1bc59c4082cb592f39ffcae0a2f10bd8ed100bb9" +checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" dependencies = [ - "anyhow", - "base64", - "bytes", - "cfg-if", - "clap", - "document-features", - "flate2", - "indexmap 1.9.3", - "libc", - "once_cell", + "ahash 0.8.11", + "bitflags", + "hashbrown 0.14.5", + "indexmap", "semver", - "serde", - "serde_cbor", - "serde_json", - "sha2", - "shared-buffer", - "tar", - "tempfile", - "thiserror", - "toml 0.7.8", - "url", - "wasmer-config", ] [[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - -[[package]] -name = "winapi" -version = "0.3.9" +name = "wast" +version = "216.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "f7eb1f2eecd913fdde0dc6c3439d0f24530a98ac6db6cb3d14d92a5328554a08" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "bumpalo", + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", ] [[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.8" +name = "wat" +version = "1.216.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "ac0409090fb5154f95fb5ba3235675fd9e579e731524d63b6a2f653e1280c82a" dependencies = [ - "windows-sys 0.52.0", + "wast", ] [[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.52.0" +name = "winapi-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-targets", + "windows-sys 0.59.0", ] [[package]] name = "windows-sys" -version = "0.33.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_msvc 0.33.0", - "windows_i686_gnu 0.33.0", - "windows_i686_msvc 0.33.0", - "windows_x86_64_gnu 0.33.0", - "windows_x86_64_msvc 0.33.0", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.52.5", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.33.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.33.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.33.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.33.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.33.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.8" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" -dependencies = [ - "memchr", -] - -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wyz" @@ -3265,51 +1118,22 @@ dependencies = [ "tap", ] -[[package]] -name = "xattr" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" -dependencies = [ - "libc", - "linux-raw-sys", - "rustix", -] - -[[package]] -name = "xxhash-rust" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" -dependencies = [ - "const-cstr", - "dlib", - "once_cell", - "pkg-config", -] - [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.76", ] diff --git a/Cargo.toml b/Cargo.toml index 58ded7c..81d9533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,19 @@ [workspace] -members=["crates/*", "benchmarks", "scripts"] +members=["crates/*"] default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" -[profile.wasm] -opt-level="s" -lto="thin" -codegen-units=1 -panic="abort" -inherits="release" +[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.7.0" +version="0.8.0" +rust-version="1.80" edition="2021" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] @@ -20,17 +22,18 @@ repository="https://github.com/explodingcamera/tinywasm" [package] name="tinywasm-root" publish=false -edition="2021" +edition.workspace=true +rust-version.workspace=true [[example]] name="wasm-rust" test=false [dev-dependencies] -color-eyre="0.6" +wat={workspace=true} +eyre={workspace=true} +pretty_env_logger={workspace=true} tinywasm={path="crates/tinywasm"} -wat={version="1"} -pretty_env_logger="0.5" [profile.bench] opt-level=3 @@ -38,6 +41,13 @@ lto="thin" codegen-units=1 debug=true -[patch.crates-io] -# https://github.com/servo/pathfinder/pull/548 & https://github.com/servo/pathfinder/issues/558 -pathfinder_simd={git="https://github.com/servo/pathfinder", rev="30419d07660dc11a21e42ef4a7fa329600cff152"} +[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 bf36393..2d897ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- +

TinyWasm

A tiny WebAssembly Runtime written in safe Rust @@ -14,41 +14,35 @@ - **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 it is optional). +- **Safe**: No unsafe code is used in the runtime (`rkyv` which uses unsafe code can be used for serialization, but is optional). ## Status -As of version `0.3.0`, TinyWasm successfully passes all the WebAssembly 1.0 tests in the [WebAssembly Test Suite](https://github.com/WebAssembly/testsuite). Work on the 2.0 tests is ongoing. This enables TinyWasm to run most WebAssembly programs, including executing TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). The results of the testsuites are available [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). - -The API is still unstable and may change at any time, so you probably don't want to use it in production _yet_. TinyWasm isn't primarily designed for high performance; it focuses more on simplicity, size, and portability. More details on its performance can be found in [BENCHMARKS.md](./BENCHMARKS.md). - -**Future Development**: The first major version will focus on improving the API and adding support for [WASI](https://wasi.dev/). While doing so, I also want to further simplify and reduce the codebase's size and improve the parser's performance. +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. ## Supported Proposals -| Proposal | Implementation Status | Version | -| -------------------------------------------------------------------------------------------------------------------------- | --------------------- | ------- | -| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | Fully implemented | 0.2.0 | -| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | Fully implemented | 0.2.0 | -| [**Sign-extension operators**](https://github.com/WebAssembly/spec/blob/master/proposals/sign-extension-ops/Overview.md) | Fully implemented | 0.2.0 | -| [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) | Fully implemented | 0.4.0 | -| [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | Partially implemented | N/A | -| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | Partially implemented | N/A | -| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | Partially implemented | N/A | +**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 -TinyWasm can be used through the `tinywasm-cli` CLI tool or as a library in your Rust project. Documentation can be found [here](https://docs.rs/tinywasm). - -### Library - -```sh -$ cargo add tinywasm -``` - -### CLI - -The CLI is mainly available for testing purposes, but can also be used to run WebAssembly programs. +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 @@ -78,7 +72,7 @@ Big thanks to the authors of the following projects, which have inspired and inf - [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in go - [wain](https://github.com/rhysd/wain) - a zero-dependency WebAssembly interpreter written in Rust -I encourage you to check these projects out if you're looking for a more mature and feature-complete WebAssembly interpreter. +I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly Runtimes. ## License diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml deleted file mode 100644 index e137a92..0000000 --- a/benchmarks/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name="benchmarks" -publish=false -edition.workspace=true - -[dependencies] -criterion={version="0.5", features=["html_reports"]} -tinywasm={path="../crates/tinywasm"} -wat={version="1"} -wasmi={version="0.31", features=["std"]} -wasmer={version="4.3", features=["cranelift", "singlepass"]} -argon2={version="0.5"} - -[[bench]] -name="selfhosted" -harness=false - -[[bench]] -name="fibonacci" -harness=false - -[[bench]] -name="argon2id" -harness=false diff --git a/benchmarks/benches/argon2id.rs b/benchmarks/benches/argon2id.rs deleted file mode 100644 index 0cf5af4..0000000 --- a/benchmarks/benches/argon2id.rs +++ /dev/null @@ -1,58 +0,0 @@ -mod util; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -fn run_tinywasm(wasm: &[u8], params: (i32, i32, i32), name: &str) { - let (mut store, instance) = util::tinywasm(wasm); - let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, name).expect("exported_func"); - argon2.call(&mut store, params).expect("call"); -} - -fn run_wasmi(wasm: &[u8], params: (i32, i32, i32), name: &str) { - let (module, mut store, linker) = util::wasmi(wasm); - let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let argon2 = instance.get_typed_func::<(i32, i32, i32), i32>(&mut store, name).expect("get_typed_func"); - argon2.call(&mut store, params).expect("call"); -} - -fn run_wasmer(wasm: &[u8], params: (i32, i32, i32), name: &str) { - use wasmer::Value; - let (mut store, instance) = util::wasmer(wasm); - let argon2 = instance.exports.get_function(name).expect("get_function"); - argon2.call(&mut store, &[Value::I32(params.0), Value::I32(params.1), Value::I32(params.2)]).expect("call"); -} - -fn run_native(params: (i32, i32, i32)) { - fn run_native(m_cost: i32, t_cost: i32, p_cost: i32) { - let password = b"password"; - let salt = b"some random salt"; - - let params = argon2::Params::new(m_cost as u32, t_cost as u32, p_cost as u32, None).unwrap(); - let argon = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); - - let mut hash = [0u8; 32]; - argon.hash_password_into(password, salt, &mut hash).unwrap(); - } - run_native(params.0, params.1, params.2) -} - -const ARGON2ID: &[u8] = include_bytes!("../../examples/rust/out/argon2id.wasm"); -fn criterion_benchmark(c: &mut Criterion) { - let params = (1000, 2, 1); - - let mut group = c.benchmark_group("argon2id"); - group.measurement_time(std::time::Duration::from_secs(7)); - group.sample_size(10); - - group.bench_function("native", |b| b.iter(|| run_native(black_box(params)))); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(ARGON2ID, black_box(params), "argon2id"))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(ARGON2ID, black_box(params), "argon2id"))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(ARGON2ID, black_box(params), "argon2id"))); -} - -criterion_group!( - name = benches; - config = Criterion::default().significance_level(0.1); - targets = criterion_benchmark -); - -criterion_main!(benches); diff --git a/benchmarks/benches/fibonacci.rs b/benchmarks/benches/fibonacci.rs deleted file mode 100644 index 15c09ac..0000000 --- a/benchmarks/benches/fibonacci.rs +++ /dev/null @@ -1,73 +0,0 @@ -mod util; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -fn run_tinywasm(wasm: &[u8], iterations: i32, name: &str) { - let (mut store, instance) = util::tinywasm(wasm); - let fib = instance.exported_func::(&store, name).expect("exported_func"); - fib.call(&mut store, iterations).expect("call"); -} - -fn run_wasmi(wasm: &[u8], iterations: i32, name: &str) { - let (module, mut store, linker) = util::wasmi(wasm); - let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let fib = instance.get_typed_func::(&mut store, name).expect("get_typed_func"); - fib.call(&mut store, iterations).expect("call"); -} - -fn run_wasmer(wasm: &[u8], iterations: i32, name: &str) { - use wasmer::*; - let compiler = wasmer::Singlepass::default(); - let mut store = Store::new(compiler); - let import_object = imports! {}; - let module = wasmer::Module::from_binary(&store, wasm).expect("wasmer::Module::from_binary"); - let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); - let fib = instance.exports.get_typed_function::(&store, name).expect("get_function"); - fib.call(&mut store, iterations).expect("call"); -} - -fn run_native(n: i32) -> i32 { - let mut sum = 0; - let mut last = 0; - let mut curr = 1; - for _i in 1..n { - sum = last + curr; - last = curr; - curr = sum; - } - sum -} - -fn run_native_recursive(n: i32) -> i32 { - if n <= 1 { - return n; - } - run_native_recursive(n - 1) + run_native_recursive(n - 2) -} - -const FIBONACCI: &[u8] = include_bytes!("../../examples/rust/out/fibonacci.wasm"); -fn criterion_benchmark(c: &mut Criterion) { - { - let mut group = c.benchmark_group("fibonacci"); - group.bench_function("native", |b| b.iter(|| run_native(black_box(60)))); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(FIBONACCI, black_box(60), "fibonacci"))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(60), "fibonacci"))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(60), "fibonacci"))); - } - - { - let mut group = c.benchmark_group("fibonacci-recursive"); - group.measurement_time(std::time::Duration::from_secs(5)); - group.bench_function("native", |b| b.iter(|| run_native_recursive(black_box(26)))); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(FIBONACCI, black_box(26), "fibonacci_recursive"))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(FIBONACCI, black_box(26), "fibonacci_recursive"))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(FIBONACCI, black_box(26), "fibonacci_recursive"))); - } -} - -criterion_group!( - name = benches; - config = Criterion::default().significance_level(0.1); - targets = criterion_benchmark -); - -criterion_main!(benches); diff --git a/benchmarks/benches/selfhosted.rs b/benchmarks/benches/selfhosted.rs deleted file mode 100644 index 4241eea..0000000 --- a/benchmarks/benches/selfhosted.rs +++ /dev/null @@ -1,77 +0,0 @@ -mod util; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -fn run_native() { - use tinywasm::*; - let module = tinywasm::Module::parse_bytes(include_bytes!("../../examples/rust/out/print.wasm")).expect("parse"); - let mut store = Store::default(); - let mut imports = Imports::default(); - imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print").expect("exported_func"); - hello.call(&mut store, (2, 3)).expect("call"); -} - -fn run_tinywasm(twasm: &[u8]) { - use tinywasm::*; - let module = Module::parse_bytes(twasm).expect("Module::parse_bytes"); - let mut store = Store::default(); - let mut imports = Imports::default(); - imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - let hello = instance.exported_func::<(), ()>(&store, "hello").expect("exported_func"); - hello.call(&mut store, ()).expect("call"); -} - -fn run_wasmi(wasm: &[u8]) { - use wasmi::*; - let engine = Engine::default(); - let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); - let mut store = Store::new(&engine, ()); - let mut linker = >::new(&engine); - linker.define("env", "printi32", Func::wrap(&mut store, |_: Caller<'_, ()>, _: i32| {})).expect("define"); - let instance = linker.instantiate(&mut store, &module).expect("instantiate").start(&mut store).expect("start"); - let hello = instance.get_typed_func::<(), ()>(&mut store, "hello").expect("get_typed_func"); - hello.call(&mut store, ()).expect("call"); -} - -fn run_wasmer(wasm: &[u8]) { - use wasmer::*; - let engine = wasmer::Engine::default(); - let mut store = Store::default(); - let import_object = imports! { - "env" => { - "printi32" => Function::new_typed(&mut store, |_: i32| {}), - }, - }; - let module = wasmer::Module::from_binary(&engine, wasm).expect("wasmer::Module::from_binary"); - let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); - let hello = instance.exports.get_function("hello").expect("get_function"); - hello.call(&mut store, &[]).expect("call"); -} - -const TINYWASM: &[u8] = include_bytes!("../../examples/rust/out/tinywasm.wasm"); -fn criterion_benchmark(c: &mut Criterion) { - { - let mut group = c.benchmark_group("selfhosted-parse"); - group.bench_function("tinywasm", |b| { - b.iter(|| tinywasm::Module::parse_bytes(black_box(TINYWASM)).expect("parse")) - }); - } - - { - let mut group = c.benchmark_group("selfhosted"); - group.bench_function("native", |b| b.iter(run_native)); - group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(TINYWASM))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM))); - } -} - -criterion_group!( - name = benches; - config = Criterion::default().sample_size(100).measurement_time(std::time::Duration::from_secs(5)).significance_level(0.1); - targets = criterion_benchmark -); - -criterion_main!(benches); diff --git a/benchmarks/benches/util/mod.rs b/benchmarks/benches/util/mod.rs deleted file mode 100644 index 2961046..0000000 --- a/benchmarks/benches/util/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![allow(dead_code)] - -pub fn tinywasm(wasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) { - use tinywasm::*; - let module = Module::parse_bytes(wasm).expect("Module::parse_bytes"); - let mut store = Store::default(); - let imports = Imports::default(); - let instance = ModuleInstance::instantiate(&mut store, module, Some(imports)).expect("instantiate"); - (store, instance) -} - -pub fn wasmi(wasm: &[u8]) -> (wasmi::Module, wasmi::Store<()>, wasmi::Linker<()>) { - use wasmi::*; - let engine = Engine::default(); - let module = wasmi::Module::new(&engine, wasm).expect("wasmi::Module::new"); - let store = Store::new(&engine, ()); - let linker = >::new(&engine); - (module, store, linker) -} - -pub fn wasmer(wasm: &[u8]) -> (wasmer::Store, wasmer::Instance) { - use wasmer::*; - let compiler = Singlepass::default(); - let mut store = Store::new(compiler); - let import_object = imports! {}; - let module = Module::new(&store, wasm).expect("wasmer::Module::new"); - let instance = Instance::new(&mut store, &module, &import_object).expect("Instance::new"); - (store, instance) -} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 4aa9cd0..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.7.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="207.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/src/args.rs b/crates/cli/src/args.rs index 3959572..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 { @@ -18,14 +18,14 @@ 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))?; + 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)), + 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 6508c5f..6efbba1 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use argh::FromArgs; use args::WasmArg; -use color_eyre::eyre::Result; +use eyre::Result; use log::{debug, info}; use tinywasm::{types::WasmValue, Module}; @@ -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,7 +78,6 @@ fn main() -> Result<()> { }; pretty_env_logger::formatted_builder().filter_level(level).init(); - let cwd = std::env::current_dir()?; match args.nested { @@ -96,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, None)?; if let Some(func) = func { let func = instance.exported_func_untyped(&store, &func)?; - let res = func.call(&mut store, &args)?; + let res = func.call(&mut store, args)?; info!("{res:?}"); } diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 206a9ed..efb0817 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -6,13 +6,15 @@ edition.workspace=true license.workspace=true authors.workspace=true repository.workspace=true +rust-version.workspace=true [dependencies] -wasmparser={version="0.207", default-features=false, features=["validate"]} -log={version="0.4", optional=true} -tinywasm-types={version="0.7.0", path="../types", default-features=false} +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=["tinywasm-types/std", "wasmparser/std"] +nightly=[] diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 001d7cc..10607a1 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -1,8 +1,8 @@ use crate::Result; -use crate::{module::Code, visit::process_operators}; +use crate::{module::Code, visit::process_operators_and_validate}; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use tinywasm_types::*; -use wasmparser::{FuncValidator, OperatorsReader, ValidatorResources}; +use wasmparser::{FuncValidator, FuncValidatorAllocations, OperatorsReader, ValidatorResources}; pub(crate) fn convert_module_elements<'a, T: IntoIterator>>>( elements: T, @@ -38,7 +38,7 @@ pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result .collect::>>()? .into_boxed_slice(); - Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(&ty), range: element.range }) + Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(ty), range: element.range }) } } } @@ -76,23 +76,23 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result ImportKind::Function(ty), wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { - element_type: convert_reftype(&ty.element_type), + 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)) + 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::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), wasmparser::TypeRef::Global(ty) => { ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) } wasmparser::TypeRef::Tag(ty) => { - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {:?}", ty))) + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))) } }, }) @@ -101,18 +101,15 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result>>( memory_types: T, ) -> Result> { - memory_types.into_iter().map(|memory| convert_module_memory(memory?)).collect::>>() + memory_types.into_iter().map(|memory| Ok(convert_module_memory(memory?))).collect::>>() } -pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> Result { - Ok(MemoryType { - arch: match memory.memory64 { - true => MemoryArch::I64, - false => MemoryArch::I32, - }, +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<'a, T: IntoIterator>>>( @@ -129,12 +126,12 @@ pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result Some( max.try_into() - .map_err(|_| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {}", max)))?, + .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 }) + Ok(TableType { element_type: convert_reftype(table.ty.element_type), size_initial, size_max }) } pub(crate) fn convert_module_globals( @@ -168,24 +165,45 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result, - validator: &mut FuncValidator, -) -> Result { + mut validator: FuncValidator, +) -> 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)); + } + + 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())), } } - let body = process_operators(Some(validator), func)?; - let locals = locals.into_boxed_slice(); - Ok((body, locals)) + 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::RecGroup) -> Result { @@ -196,6 +214,7 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result "Expected exactly one type in the type section".to_string(), )); } + 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(); @@ -203,19 +222,11 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result Ok(FuncType { params, results }) } -pub(crate) fn convert_blocktype(blocktype: wasmparser::BlockType) -> BlockArgs { - match blocktype { - wasmparser::BlockType::Empty => BlockArgs::Empty, - wasmparser::BlockType::Type(ty) => BlockArgs::Type(convert_valtype(&ty)), - wasmparser::BlockType::FuncType(ty) => BlockArgs::FuncType(ty), - } -} - -pub(crate) fn convert_reftype(reftype: &wasmparser::RefType) -> ValType { +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), + _ => unimplemented!("Unsupported reference type: {:?}, {:?}", reftype, reftype.heap_type()), } } @@ -225,15 +236,11 @@ pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> ValType { wasmparser::ValType::I64 => ValType::I64, wasmparser::ValType::F32 => ValType::F32, wasmparser::ValType::F64 => ValType::F64, - wasmparser::ValType::Ref(r) => convert_reftype(r), - wasmparser::ValType::V128 => unimplemented!("128-bit values are not supported yet"), + wasmparser::ValType::V128 => ValType::V128, + wasmparser::ValType::Ref(r) => convert_reftype(*r), } } -pub(crate) fn convert_memarg(memarg: wasmparser::MemArg) -> MemoryArg { - MemoryArg { offset: memarg.offset, mem_addr: memarg.memory } -} - 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, @@ -250,14 +257,16 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result 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))), + op => Err(crate::ParseError::UnsupportedOperator(format!("Unsupported const instruction: {op:?}"))), } } pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> ValType { match heap { - wasmparser::HeapType::Func => ValType::RefFunc, - wasmparser::HeapType::Extern => ValType::RefExtern, + 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), } } diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs index 76d806d..7bd484f 100644 --- a/crates/parser/src/error.rs +++ b/crates/parser/src/error.rs @@ -42,24 +42,24 @@ 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 { diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index dd10f4d..dfe6b0c 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -5,7 +5,6 @@ ))] #![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. extern crate alloc; @@ -15,26 +14,27 @@ 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; mod visit; -use alloc::{string::ToString, vec::Vec}; pub use error::*; use module::ModuleReader; -use tinywasm_types::WasmFunction; use wasmparser::{Validator, WasmFeaturesInflated}; pub use tinywasm_types::TinyWasmModule; @@ -49,7 +49,7 @@ impl Parser { Self {} } - fn create_validator(&self) -> Validator { + fn create_validator() -> Validator { let features = WasmFeaturesInflated { bulk_memory: true, floats: true, @@ -58,11 +58,15 @@ impl Parser { reference_types: true, sign_extension: true, saturating_float_to_int: true, + function_references: true, + tail_call: true, + multi_memory: true, - function_references: false, + 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, @@ -70,11 +74,11 @@ impl Parser { memory_control: false, relaxed_simd: false, simd: false, - tail_call: false, threads: false, - multi_memory: false, // should be working mostly custom_page_sizes: false, shared_everything_threads: false, + component_model_multiple_returns: false, + legacy_exceptions: false, }; Validator::new_with_features(features.into()) } @@ -82,7 +86,7 @@ impl Parser { /// Parse a [`TinyWasmModule`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); - let mut validator = self.create_validator(); + let mut validator = Self::create_validator(); let mut reader = ModuleReader::new(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { @@ -93,7 +97,7 @@ impl Parser { return Err(ParseError::EndNotReached); } - reader.try_into() + reader.into_module() } #[cfg(feature = "std")] @@ -112,9 +116,9 @@ impl Parser { pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { use alloc::format; - let mut validator = self.create_validator(); + 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; @@ -125,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; } @@ -133,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(); } } }; @@ -145,42 +149,6 @@ impl TryFrom for TinyWasmModule { type Error = ParseError; fn try_from(reader: ModuleReader) -> Result { - if !reader.end_reached { - return Err(ParseError::EndNotReached); - } - - let code_type_addrs = reader.code_type_addrs; - let local_function_count = reader.code.len(); - - if code_type_addrs.len() != local_function_count { - return Err(ParseError::Other("Code and code type address count mismatch".to_string())); - } - - let funcs = reader - .code - .into_iter() - .zip(code_type_addrs) - .map(|((instructions, locals), ty_idx)| WasmFunction { - instructions, - locals, - ty: reader.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(), - }) - .collect::>(); - - let globals = reader.globals; - let table_types = reader.table_types; - - Ok(TinyWasmModule { - funcs: funcs.into_boxed_slice(), - func_types: reader.func_types.into_boxed_slice(), - globals: globals.into_boxed_slice(), - table_types: table_types.into_boxed_slice(), - imports: reader.imports.into_boxed_slice(), - start_func: reader.start_func, - data: reader.data.into_boxed_slice(), - exports: reader.exports.into_boxed_slice(), - elements: reader.elements.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 1cd5ed5..20ed00d 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,10 +1,14 @@ use crate::log::debug; 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, ValType}; +use tinywasm_types::{ + Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, ValType, + ValueCounts, ValueCountsSmall, WasmFunction, +}; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Box<[Instruction]>, Box<[ValType]>); +pub(crate) type Code = (Box<[Instruction]>, ValueCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -131,9 +135,10 @@ impl ModuleReader { CodeSectionEntry(function) => { debug!("Found code section entry"); let v = validator.code_section_entry(&function)?; - let mut func_validator = v.into_validator(self.func_validator_allocations.take().unwrap_or_default()); - self.code.push(conversion::convert_module_code(function, &mut func_validator)?); - self.func_validator_allocations = Some(func_validator.into_allocations()); + 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() { @@ -163,14 +168,61 @@ impl ModuleReader { validator.end(offset)?; self.end_reached = true; } - CustomSection(_reader) => { + CustomSection(reader) => { debug!("Found custom section"); - debug!("Skipping custom section: {:?}", _reader.name()); + debug!("Skipping custom section: {:?}", reader.name()); } UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), - section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {:?}", section))), + 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/visit.rs b/crates/parser/src/visit.rs index 332380c..73cf9b4 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -1,122 +1,114 @@ -use crate::{conversion::convert_blocktype, Result}; +use crate::Result; -use crate::conversion::{convert_heaptype, convert_memarg, convert_valtype}; +use crate::conversion::{convert_heaptype, convert_valtype}; use alloc::string::ToString; -use alloc::{boxed::Box, format, vec::Vec}; -use tinywasm_types::Instruction; -use wasmparser::{FuncValidator, FunctionBody, VisitOperator, WasmModuleResources}; +use alloc::{boxed::Box, vec::Vec}; +use tinywasm_types::{Instruction, MemoryArg}; +use wasmparser::{FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, WasmModuleResources}; -struct ValidateThenVisit<'a, T, U>(T, &'a mut U); +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.0.$visit($($($arg.clone()),*)?)?; - Ok(self.1.$visit($($($arg),*)?)) - } - )* - }; + ($( @$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, T, U> VisitOperator<'a> for ValidateThenVisit<'_, T, U> -where - T: VisitOperator<'a, Output = wasmparser::Result<()>>, - U: VisitOperator<'a>, -{ - type Output = Result; +impl<'a, R: WasmModuleResources> VisitOperator<'a> for ValidateThenVisit<'_, R> { + type Output = Result<()>; wasmparser::for_each_operator!(validate_then_visit); } -pub(crate) fn process_operators( - validator: Option<&mut FuncValidator>, +pub(crate) fn process_operators_and_validate( + validator: FuncValidator, body: FunctionBody<'_>, -) -> Result> { + 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); - if let Some(validator) = validator { - while !reader.eof() { - let validate = validator.visitor(reader.original_position()); - reader.visit_operator(&mut ValidateThenVisit(validate, &mut builder))???; - } - validator.finish(reader.original_position())?; - } else { - while !reader.eof() { - reader.visit_operator(&mut builder)??; - } + 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()) + Ok((builder.instructions.into_boxed_slice(), builder.validator.into_allocations())) } macro_rules! define_operands { - ($($name:ident, $instr:expr),*) => { - $( - #[inline(always)] - fn $name(&mut self) -> Self::Output { - self.instructions.push($instr); - Ok(()) - } - )* - }; + ($($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),*) => { - $( - #[inline(always)] - fn $name(&mut self, arg: $ty) -> Self::Output { - self.instructions.push($instr(arg)); - Ok(()) - } - )* - }; - ($($name:ident, $instr:expr, $ty:ty, $ty2:ty),*) => { - $( - #[inline(always)] - fn $name(&mut self, arg: $ty, arg2: $ty) -> Self::Output { - self.instructions.push($instr(arg, arg2)); - Ok(()) - } - )* - }; + ($($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),*) => { - $( - #[inline(always)] - fn $name(&mut self, mem_arg: wasmparser::MemArg) -> Self::Output { - let arg = convert_memarg(mem_arg); - self.instructions.push(Instruction::$instr { - offset: arg.offset, - mem_addr: arg.mem_addr, - }); - Ok(()) - } - )* - }; + ($($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 { +pub(crate) struct FunctionBuilder { + validator: FuncValidator, instructions: Vec, label_ptrs: Vec, + local_addr_map: Vec, + errors: Vec, } -impl FunctionBuilder { - pub(crate) fn new(instr_capacity: usize) -> Self { - Self { instructions: Vec::with_capacity(instr_capacity / 4), label_ptrs: Vec::with_capacity(256) } +impl FunctionBuilder { + pub(crate) fn validator_visitor( + &mut self, + offset: usize, + ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> { + self.validator.visitor(offset) } - #[cold] - fn unsupported(&self, name: &str) -> Result<()> { - Err(crate::ParseError::UnsupportedOperator(format!("Unsupported instruction: {:?}", name))) + pub(crate) fn validator_finish(&mut self, offset: usize) -> Result<(), wasmparser::BinaryReaderError> { + self.validator.finish(offset) } +} - #[inline(always)] - fn visit(&mut self, op: Instruction) -> Result<()> { - self.instructions.push(op); - Ok(()) +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())); } } @@ -130,25 +122,28 @@ macro_rules! impl_visit_operator { (@@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)*)?) -> Result<()>{ + fn $visit(&mut self $($(,$arg: $argty)*)?) { self.unsupported(stringify!($visit)) } }; } -impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { - type Output = Result<()>; +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_global_set, Instruction::GlobalSet, u32, visit_i32_const, Instruction::I32Const, i32, - visit_i64_const, Instruction::I64Const, i64 + 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! { @@ -181,8 +176,6 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { visit_unreachable, Instruction::Unreachable, visit_nop, Instruction::Nop, visit_return, Instruction::Return, - visit_drop, Instruction::Drop, - visit_select, Instruction::Select(None), visit_i32_eqz, Instruction::I32Eqz, visit_i32_eq, Instruction::I32Eq, visit_i32_ne, Instruction::I32Ne, @@ -325,116 +318,183 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { visit_i64_trunc_sat_f64_u, Instruction::I64TruncSatF64U } - #[inline(always)] - fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { - let arg = convert_memarg(memarg); - let i32store = Instruction::I32Store { offset: arg.offset, mem_addr: arg.mem_addr }; + fn visit_return_call(&mut self, function_index: u32) -> Self::Output { + self.instructions.push(Instruction::ReturnCall(function_index)); + } - if self.instructions.len() < 3 || arg.mem_addr > 0xFF || arg.offset > 0xFFFF_FFFF { - return self.visit(i32store); + 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(), } + } - match self.instructions[self.instructions.len() - 2..] { - [Instruction::LocalGet(a), Instruction::I32Const(b)] => { - self.instructions.pop(); - self.instructions.pop(); - self.visit(Instruction::I32StoreLocal { - local: a, - const_i32: b, - offset: arg.offset as u32, - mem_addr: arg.mem_addr as u8, - }) - } - _ => self.visit(i32store), + 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); + } - #[inline(always)] fn visit_local_get(&mut self, idx: u32) -> Self::Output { - let Some(instruction) = self.instructions.last_mut() else { - return self.visit(Instruction::LocalGet(idx)); + 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 instruction { - Instruction::LocalGet(a) => *instruction = Instruction::LocalGet2(*a, idx), - Instruction::LocalGet2(a, b) => *instruction = Instruction::LocalGet3(*a, *b, idx), - Instruction::LocalTee(a) => *instruction = Instruction::LocalTeeGet(*a, idx), - _ => return self.visit(Instruction::LocalGet(idx)), - }; - - Ok(()) + 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(), + } } - #[inline(always)] fn visit_local_set(&mut self, idx: u32) -> Self::Output { - self.visit(Instruction::LocalSet(idx)) + 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(), + } } - #[inline(always)] fn visit_local_tee(&mut self, idx: u32) -> Self::Output { - self.visit(Instruction::LocalTee(idx)) - } + 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; + }; - #[inline(always)] - fn visit_i64_rotl(&mut self) -> Self::Output { - if self.instructions.len() < 2 { - return self.visit(Instruction::I64Rotl); + 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(), } + } - match self.instructions[self.instructions.len() - 2..] { - [Instruction::I64Xor, Instruction::I64Const(a)] => { - self.instructions.pop(); - self.instructions.pop(); - self.visit(Instruction::I64XorConstRotl(a)) - } - _ => self.visit(Instruction::I64Rotl), - } + fn visit_i64_rotl(&mut self) -> Self::Output { + self.instructions.push(Instruction::I64Rotl); } - #[inline(always)] fn visit_i32_add(&mut self) -> Self::Output { - if self.instructions.len() < 2 { - return self.visit(Instruction::I32Add); - } - - match self.instructions[self.instructions.len() - 2..] { - [Instruction::LocalGet(a), Instruction::I32Const(b)] => { - self.instructions.pop(); - self.instructions.pop(); - self.visit(Instruction::I32LocalGetConstAdd(a, b)) - } - _ => self.visit(Instruction::I32Add), - } + self.instructions.push(Instruction::I32Add); } - #[inline(always)] fn visit_block(&mut self, blockty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.visit(Instruction::Block(convert_blocktype(blockty), 0)) + 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), + }); } - #[inline(always)] fn visit_loop(&mut self, ty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.visit(Instruction::Loop(convert_blocktype(ty), 0)) + 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), + }); } - #[inline(always)] fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.visit(Instruction::If(convert_blocktype(ty).into(), 0, 0)) + 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), + }); } - #[inline(always)] fn visit_else(&mut self) -> Self::Output { self.label_ptrs.push(self.instructions.len()); - self.visit(Instruction::Else(0)) + self.instructions.push(Instruction::Else(0)); } - #[inline(always)] fn visit_end(&mut self) -> Self::Output { let Some(label_pointer) = self.label_ptrs.pop() else { - return self.visit(Instruction::Return); + return self.instructions.push(Instruction::Return); }; let current_instr_ptr = self.instructions.len(); @@ -444,19 +504,28 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { .try_into() .expect("else_instr_end_offset is too large, tinywasm does not support if blocks that large"); - #[cold] - fn error() -> crate::ParseError { - crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - ) - } - // since we're ending an else block, we need to end the if block as well - let if_label_pointer = self.label_ptrs.pop().ok_or_else(error)?; + 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 Instruction::If(_, else_offset, end_offset) = if_instruction else { - return Err(error()); + + 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) @@ -467,9 +536,17 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { .try_into() .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); } - Some(Instruction::Block(_, end_offset)) - | Some(Instruction::Loop(_, end_offset)) - | Some(Instruction::If(_, _, end_offset)) => { + 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"); @@ -479,50 +556,30 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { } }; - self.visit(Instruction::EndBlockFrame) + self.instructions.push(Instruction::EndBlockFrame); } - #[inline(always)] 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("BrTable targets are invalid, this should have been caught by the validator"); + .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)); - Ok(()) } - #[inline(always)] - fn visit_call(&mut self, idx: u32) -> Self::Output { - self.visit(Instruction::Call(idx)) + fn visit_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { + self.instructions.push(Instruction::CallIndirect(ty, table)); } - #[inline(always)] - fn visit_call_indirect(&mut self, ty: u32, table: u32, _table_byte: u8) -> Self::Output { - self.visit(Instruction::CallIndirect(ty, table)) - } - - #[inline(always)] - fn visit_memory_size(&mut self, mem: u32, mem_byte: u8) -> Self::Output { - self.visit(Instruction::MemorySize(mem, mem_byte)) - } - - #[inline(always)] - fn visit_memory_grow(&mut self, mem: u32, mem_byte: u8) -> Self::Output { - self.visit(Instruction::MemoryGrow(mem, mem_byte)) - } - - #[inline(always)] fn visit_f32_const(&mut self, val: wasmparser::Ieee32) -> Self::Output { - self.visit(Instruction::F32Const(f32::from_bits(val.bits()))) + self.instructions.push(Instruction::F32Const(f32::from_bits(val.bits()))); } - #[inline(always)] fn visit_f64_const(&mut self, val: wasmparser::Ieee64) -> Self::Output { - self.visit(Instruction::F64Const(f64::from_bits(val.bits()))) + self.instructions.push(Instruction::F64Const(f64::from_bits(val.bits()))); } // Bulk Memory Operations @@ -534,34 +591,32 @@ impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { } define_primitive_operands! { visit_memory_fill, Instruction::MemoryFill, u32, - visit_data_drop, Instruction::DataDrop, u32 - } - - #[inline(always)] - fn visit_elem_drop(&mut self, _elem_index: u32) -> Self::Output { - self.unsupported("elem_drop") + visit_data_drop, Instruction::DataDrop, u32, + visit_elem_drop, Instruction::ElemDrop, u32 } - #[inline(always)] fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { - self.visit(Instruction::TableCopy { from: src_table, to: dst_table }) + self.instructions.push(Instruction::TableCopy { from: src_table, to: dst_table }); } // Reference Types - - #[inline(always)] fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output { - self.visit(Instruction::RefNull(convert_heaptype(ty))) + self.instructions.push(Instruction::RefNull(convert_heaptype(ty))); } - #[inline(always)] fn visit_ref_is_null(&mut self) -> Self::Output { - self.visit(Instruction::RefIsNull) + self.instructions.push(Instruction::RefIsNull); } - #[inline(always)] fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { - self.visit(Instruction::Select(Some(convert_valtype(&ty)))) + 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! { diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index ca6b723..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,36 +14,78 @@ name="tinywasm" path="src/lib.rs" [dependencies] -_log={version="0.4", optional=true, package="log"} -tinywasm-parser={version="0.7.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.7.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="207.0"} +wast={workspace=true} +eyre={workspace=true} +pretty_env_logger={workspace=true} +criterion={workspace=true} owo-colors={version="4.0"} -eyre={version="0.6"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} -pretty_env_logger="0.5" [features] default=["std", "parser", "logging", "archive"] -logging=["_log", "tinywasm-parser?/logging", "tinywasm-types/logging"] +logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"] std=["tinywasm-parser?/std", "tinywasm-types/std"] parser=["tinywasm-parser"] archive=["tinywasm-types/archive"] -nightly=[] +simd=[] +nightly=["tinywasm-parser?/nightly"] [[test]] -name="test-mvp" +name="test-wasm-1" harness=false +test=false [[test]] -name="test-two" +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 85ffdf6..73df9a2 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,11 +1,11 @@ use alloc::string::{String, ToString}; -use core::fmt::Display; +use core::{fmt::Display, ops::ControlFlow}; use tinywasm_types::FuncType; #[cfg(feature = "parser")] pub use tinywasm_parser::ParseError; -/// Errors that can occur for TinyWasm operations +/// Errors that can occur for `TinyWasm` operations #[derive(Debug)] pub enum Error { /// A WebAssembly trap occurred @@ -23,15 +23,6 @@ pub enum Error { /// A function did not return a value FuncDidNotReturn, - /// The stack is empty - ValueStackUnderflow, - - /// The label stack is empty - BlockStackUnderflow, - - /// The call stack is empty - CallStackUnderflow, - /// An invalid label type was encountered InvalidLabelType, @@ -182,20 +173,17 @@ 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::Linker(err) => write!(f, "linking error: {}", err), - Self::CallStackUnderflow => write!(f, "call stack empty"), + 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::Other(message) => write!(f, "unknown error: {message}"), + Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {feature}"), Self::FuncDidNotReturn => write!(f, "function did not return"), - Self::BlockStackUnderflow => write!(f, "label stack underflow"), - Self::ValueStackUnderflow => write!(f, "value stack underflow"), Self::InvalidStore => write!(f, "invalid store"), } } @@ -217,27 +205,27 @@ impl Display for Trap { match self { Self::Unreachable => write!(f, "unreachable"), Self::MemoryOutOfBounds { offset, len, max } => { - write!(f, "out of bounds memory access: offset={}, len={}, max={}", 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={}, len={}, max={}", 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::UndefinedElement { index } => write!(f, "undefined element: index={index}"), Self::UninitializedElement { index } => { - write!(f, "uninitialized element: index={}", index) + write!(f, "uninitialized element: index={index}") } Self::IndirectCallTypeMismatch { expected, actual } => { - write!(f, "indirect call type mismatch: expected={:?}, actual={:?}", expected, actual) + write!(f, "indirect call type mismatch: expected={expected:?}, actual={actual:?}") } } } } -#[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 Error {} #[cfg(feature = "parser")] @@ -249,3 +237,16 @@ impl From for Error { /// A wrapper around [`core::result::Result`] for tinywasm operations pub type Result = crate::std::result::Result; + +pub(crate) trait Controlify { + fn to_cf(self) -> ControlFlow, T>; +} + +impl Controlify for Result { + fn to_cf(self) -> ControlFlow, T> { + match self { + Ok(value) => ControlFlow::Continue(value), + Err(err) => ControlFlow::Break(Some(err)), + } + } +} diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 95b7cc0..466dd86 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,10 +1,9 @@ -use crate::{log, runtime::RawWasmValue, unlikely, Function}; +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}; -use crate::runtime::{CallFrame, Stack}; -use crate::{Error, FuncContext, Result, Store}; - #[derive(Debug)] /// A function handle pub struct FuncHandle { @@ -49,7 +48,7 @@ impl FuncHandle { return Err(Error::Other("Type mismatch".into())); } - let func_inst = store.get_func(self.addr)?; + 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; @@ -60,8 +59,7 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let call_frame_params = params.iter().map(|v| RawWasmValue::from(*v)); - let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, call_frame_params, 0); + let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, 0); // 7. Push the frame f to the call stack // & 8. Push the values to the stack (Not needed since the call frame owns the values) @@ -72,16 +70,16 @@ impl FuncHandle { runtime.exec(store, &mut stack)?; // Once the function returns: - let result_m = func_ty.results.len(); + // 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); + // assert!(stack.values.len() >= result_m); // 2. Pop m values from the stack - let res = stack.values.last_n(result_m)?; + let res = stack.values.pop_results(&func_ty.results); // The values are returned as the results of the invocation. - Ok(res.iter().zip(func_ty.results.iter()).map(|(v, ty)| v.attach_type(*ty)).collect()) + Ok(res) } } diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index f24ed73..3a226f4 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use core::fmt::Debug; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; -use crate::{log, LinkingError, Result}; +use crate::{log, LinkingError, MemoryRef, MemoryRefMut, Result}; use tinywasm_types::*; /// The internal representation of a function @@ -72,12 +72,12 @@ impl FuncContext<'_> { } /// Get a reference to an exported memory - pub fn exported_memory(&mut self, name: &str) -> Result> { + pub fn exported_memory(&mut self, name: &str) -> Result> { self.module().exported_memory(self.store, name) } /// Get a reference to an exported memory - pub fn exported_memory_mut(&mut self, name: &str) -> Result> { + pub fn exported_memory_mut(&mut self, name: &str) -> Result> { self.module().exported_memory_mut(self.store, name) } } @@ -143,8 +143,6 @@ impl Extern { } /// Create a new typed function import - // TODO: currently, this is slower than `Extern::func` because of the type conversions. - // we should be able to optimize this and make it even faster than `Extern::func`. pub fn typed_func(func: impl Fn(FuncContext<'_>, P) -> Result + 'static) -> Self where P: FromWasmValueTuple + ValTypesFromTuple, @@ -191,7 +189,7 @@ impl From<&Import> for ExternName { /// /// ## Example /// ```rust -/// # use _log as log; +/// # use log; /// # fn main() -> tinywasm::Result<()> { /// use tinywasm::{Imports, Extern}; /// use tinywasm::types::{ValType, TableType, MemoryType, WasmValue}; @@ -348,7 +346,7 @@ impl Imports { ) -> Result { let mut imports = ResolvedImports::new(); - for import in module.data.imports.iter() { + for import in &module.0.imports { let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?; match val { @@ -368,7 +366,7 @@ impl Imports { } (Extern::Function(extern_func), ImportKind::Function(ty)) => { let import_func_type = module - .data + .0 .func_types .get(*ty as usize) .ok_or_else(|| LinkingError::incompatible_import_type(import))?; @@ -388,28 +386,27 @@ impl Imports { match (val, &import.kind) { (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { - let global = store.get_global(global_addr)?; + 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)?; - Self::compare_table_types(import, &table.borrow().kind, 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) = { - let mem = mem.borrow(); - (mem.page_count(), mem.kind) - }; + 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 func = store.get_func(func_addr); let import_func_type = module - .data + .0 .func_types .get(*ty as usize) .ok_or_else(|| LinkingError::incompatible_import_type(import))?; diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 8402302..eebf5ff 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -10,7 +10,7 @@ use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut /// /// See #[derive(Debug, Clone)] -pub struct ModuleInstance(Rc); +pub struct ModuleInstance(pub(crate) Rc); #[allow(dead_code)] #[derive(Debug)] @@ -43,7 +43,9 @@ impl ModuleInstance { #[inline] pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { - self.swap(store.get_module_instance_raw(other_addr)) + if other_addr != self.id() { + self.swap(store.get_module_instance_raw(other_addr)) + } } /// Get the module instance's address @@ -62,45 +64,39 @@ impl ModuleInstance { let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?; - let data = module.data; - addrs.funcs.extend(store.init_funcs(data.funcs.into(), idx)?); - addrs.tables.extend(store.init_tables(data.table_types.into(), idx)?); - addrs.memories.extend(store.init_memories(data.memory_types.into(), 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, data.globals.into(), &addrs.funcs, 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, &data.elements, idx)?; - let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, data.data.into(), idx)?; + 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: data.func_types, + 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: data.start_func, - imports: data.imports, - exports: data.exports, + func_start: module.0.start_func, + imports: module.0.imports, + exports: module.0.exports, }; let instance = ModuleInstance::new(instance); store.add_instance(instance.clone()); - if let Some(trap) = elem_trapped { - return Err(trap.into()); - }; - - if let Some(trap) = data_trapped { - return Err(trap.into()); - }; - - Ok(instance) + match (elem_trapped, data_trapped) { + (Some(trap), _) | (_, Some(trap)) => Err(trap.into()), + _ => Ok(instance), + } } /// Get a export by name @@ -123,7 +119,7 @@ impl ModuleInstance { #[inline] pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { - self.0.types.get(addr as usize).expect("No func type for func, this is a bug") + &self.0.types[addr as usize] } #[inline] @@ -132,37 +128,37 @@ impl ModuleInstance { } // resolve a function address to the global store address - #[inline(always)] + #[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(always)] + #[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(always)] + #[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(always)] - pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> MemAddr { + #[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(always)] + #[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(always)] + #[inline] pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { self.0.global_addrs[addr as usize] } @@ -173,14 +169,12 @@ impl ModuleInstance { return Err(Error::InvalidStore); } - let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {}", name)))?; + let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; let ExternVal::Func(func_addr) = export else { - return Err(Error::Other(format!("Export is not a function: {}", name))); + return Err(Error::Other(format!("Export is not a function: {name}"))); }; - let func_inst = store.get_func(func_addr)?; - let ty = func_inst.func.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() }) } @@ -196,7 +190,7 @@ impl ModuleInstance { /// 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 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))); }; @@ -206,7 +200,7 @@ impl ModuleInstance { /// 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 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))); }; @@ -215,15 +209,15 @@ impl ModuleInstance { } /// Get a memory by address - pub fn memory<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { - let mem = store.get_mem(self.resolve_mem_addr(addr))?; - Ok(MemoryRef { instance: mem.borrow() }) + 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(self.resolve_mem_addr(addr))?; - Ok(MemoryRefMut { instance: mem.borrow_mut() }) + let mem = store.get_mem_mut(self.resolve_mem_addr(addr)); + Ok(MemoryRefMut(mem)) } /// Get the start function of the module @@ -249,11 +243,11 @@ impl ModuleInstance { } }; - let func_addr = self.0.func_addrs.get(func_index as usize).expect("No func addr for start func, this is a bug"); - let func_inst = store.get_func(*func_addr)?; + let func_addr = self.resolve_func_addr(func_index); + let func_inst = store.get_func(func_addr); let ty = func_inst.func.ty(); - Ok(Some(FuncHandle { module_addr: self.id(), addr: *func_addr, ty: ty.clone(), name: None })) + Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone(), name: None })) } /// Invoke the start function of the module diff --git a/crates/tinywasm/src/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/runtime/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs similarity index 100% rename from crates/tinywasm/src/runtime/interpreter/no_std_floats.rs rename to crates/tinywasm/src/interpreter/no_std_floats.rs diff --git a/crates/tinywasm/src/runtime/interpreter/traits.rs b/crates/tinywasm/src/interpreter/num_helpers.rs similarity index 56% rename from crates/tinywasm/src/runtime/interpreter/traits.rs rename to crates/tinywasm/src/interpreter/num_helpers.rs index 7aeb3b7..02fbc24 100644 --- a/crates/tinywasm/src/runtime/interpreter/traits.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -1,16 +1,70 @@ -pub(crate) trait CheckedWrappingRem +pub(crate) trait TinywasmIntExt where Self: Sized, { - fn checked_wrapping_rem(self, rhs: Self) -> Option; + 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; @@ -103,13 +157,22 @@ impl_wrapping_self_sh! { i32 i64 u32 u64 } macro_rules! impl_checked_wrapping_rem { ($($t:ty)*) => ($( - impl CheckedWrappingRem for $t { + 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 checked_wrapping_rem(self, rhs: Self) -> Option { + fn wasm_checked_div(self, rhs: Self) -> Result { if rhs == 0 { - None + Err(Error::Trap(crate::Trap::DivisionByZero)) } else { - Some(self.wrapping_rem(rhs)) + self.checked_div(rhs).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow)) } } } diff --git a/crates/tinywasm/src/runtime/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs similarity index 68% rename from crates/tinywasm/src/runtime/stack/block_stack.rs rename to crates/tinywasm/src/interpreter/stack/block_stack.rs index 9a823fd..2267194 100644 --- a/crates/tinywasm/src/runtime/stack/block_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -1,14 +1,18 @@ -use crate::{cold, unlikely, Error, Result}; +use crate::unlikely; use alloc::vec::Vec; -#[derive(Debug, Clone)] +use crate::interpreter::values::{StackHeight, StackLocation}; + +#[derive(Debug)] pub(crate) struct BlockStack(Vec); -impl BlockStack { - pub(crate) fn new() -> Self { +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() @@ -33,14 +37,8 @@ impl BlockStack { } #[inline(always)] - pub(crate) fn pop(&mut self) -> Result { - match self.0.pop() { - Some(frame) => Ok(frame), - None => { - cold(); - Err(Error::BlockStackUnderflow) - } - } + 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 @@ -50,21 +48,19 @@ impl BlockStack { } } -#[derive(Debug, Clone, Copy)] +#[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: u32, // position of the stack pointer when the block was entered - pub(crate) results: u8, - pub(crate) params: u8, + pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered + pub(crate) results: StackHeight, + pub(crate) params: StackHeight, + pub(crate) ty: BlockType, } -impl BlockFrame {} - -#[derive(Debug, Copy, Clone)] -#[allow(dead_code)] +#[derive(Debug)] pub(crate) enum BlockType { Loop, If, 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/runtime/stack.rs b/crates/tinywasm/src/interpreter/stack/mod.rs similarity index 58% rename from crates/tinywasm/src/runtime/stack.rs rename to crates/tinywasm/src/interpreter/stack/mod.rs index a64b234..c526c4a 100644 --- a/crates/tinywasm/src/runtime/stack.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -2,13 +2,13 @@ mod block_stack; mod call_stack; mod value_stack; -pub(crate) use self::{call_stack::CallStack, value_stack::ValueStack}; pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; -pub(crate) use call_stack::CallFrame; +pub(crate) use call_stack::{CallFrame, CallStack, Locals}; +pub(crate) use value_stack::ValueStack; /// A WebAssembly Stack #[derive(Debug)] -pub struct Stack { +pub(crate) struct Stack { pub(crate) values: ValueStack, pub(crate) blocks: BlockStack, pub(crate) call_stack: CallStack, @@ -16,6 +16,6 @@ pub struct Stack { impl Stack { pub(crate) fn new(call_frame: CallFrame) -> Self { - Self { values: ValueStack::default(), blocks: BlockStack::new(), call_stack: CallStack::new(call_frame) } + 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 2e9fece..7038dd6 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -3,14 +3,13 @@ no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] -#![allow(unexpected_cfgs, clippy::reserve_after_initialization)] #![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. +//! `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. //! @@ -24,8 +23,8 @@ //!- **`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 +//! 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`. //! @@ -78,10 +77,11 @@ extern crate alloc; // log for logging (optional). #[cfg(feature = "logging")] #[allow(clippy::single_component_path_imports)] -use _log as log; +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)*) => {{}} ); @@ -108,8 +108,8 @@ mod reference; mod store; /// Runtime for executing WebAssembly modules. -pub mod runtime; -pub use runtime::InterpreterRuntime; +pub mod interpreter; +pub use interpreter::InterpreterRuntime; #[cfg(feature = "parser")] /// Re-export of [`tinywasm_parser`]. Requires `parser` feature. @@ -127,7 +127,7 @@ pub(crate) fn cold() {} pub(crate) fn unlikely(b: bool) -> bool { if b { - cold() + cold(); }; b } diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 5b5f0c6..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; -#[derive(Debug)] /// A WebAssembly Module /// /// See -#[derive(Clone)] -pub struct Module { - pub(crate) 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) } } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index f3acc49..3765098 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,87 +1,81 @@ -use core::cell::{Ref, RefCell, RefMut}; use core::ffi::CStr; use alloc::ffi::CString; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use crate::{GlobalInstance, MemoryInstance, Result}; -use tinywasm_types::WasmValue; +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) instance: Ref<'a, MemoryInstance>, -} +pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); /// A borrowed reference to a memory instance #[derive(Debug)] -pub struct MemoryRefMut<'a> { - pub(crate) instance: RefMut<'a, MemoryInstance>, -} +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.instance.load(offset, len) + 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.instance.load(offset, len) + self.0.load(offset, len) } } impl MemoryRef<'_> { /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, len) + 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(|x| x.to_vec()) + 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.instance.load(offset, len) + 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(|x| x.to_vec()) + 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.instance.grow(delta_pages) + self.0.grow(delta_pages) } /// Get the current size of the memory in pages pub fn page_count(&mut self) -> usize { - self.instance.page_count() + 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.instance.copy_within(src, dst, len) + 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.instance.fill(offset, len, val) + self.0.fill(offset, len, val) } /// Store a slice of memory pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> { - self.instance.store(offset, len, data) + self.0.store(offset, len, data) } } @@ -89,7 +83,7 @@ impl MemoryRefMut<'_> { pub trait MemoryRefLoad { fn load(&self, offset: usize, len: usize) -> Result<&[u8]>; fn load_vec(&self, offset: usize, len: usize) -> Result> { - self.load(offset, len).map(|x| x.to_vec()) + self.load(offset, len).map(<[u8]>::to_vec) } } @@ -130,7 +124,7 @@ pub trait MemoryStringExt: MemoryRefLoad { for i in 0..(len / 2) { let c = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]); string.push( - char::from_u32(c as u32).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?, + char::from_u32(u32::from(c)).ok_or_else(|| crate::Error::Other("Invalid UTF-16 string".to_string()))?, ); } Ok(string) @@ -139,21 +133,3 @@ pub trait MemoryStringExt: MemoryRefLoad { impl MemoryStringExt for MemoryRef<'_> {} impl MemoryStringExt for MemoryRefMut<'_> {} - -/// A reference to a global instance -#[derive(Debug)] -pub struct GlobalRef { - pub(crate) instance: RefCell, -} - -impl GlobalRef { - /// Get the value of the global - pub fn get(&self) -> WasmValue { - self.instance.borrow().get() - } - - /// Set the value of the global - pub fn set(&self, val: WasmValue) -> Result<()> { - self.instance.borrow_mut().set(val) - } -} diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs deleted file mode 100644 index 8a092c0..0000000 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! More generic macros for various instructions -//! -//! These macros are used to generate the actual instruction implementations. -//! In some basic tests this generated better assembly than using generic functions, even when inlined. -//! (Something to revisit in the future) - -// Break to a block at the given index (relative to the current frame) -// If there is no block at the given index, return or call the parent function -// -// This is a bit hard to see from the spec, but it's vaild to use breaks to return -// from a function, so we need to check if the label stack is empty -macro_rules! break_to { - ($cf:ident, $stack:ident, $module:ident, $store:ident, $break_to_relative:ident) => {{ - if $cf.break_to(*$break_to_relative, &mut $stack.values, &mut $stack.blocks).is_none() { - if $stack.call_stack.is_empty() { - return Ok(()); - } - - call!($cf, $stack, $module, $store) - } - }}; -} - -/// Load a value from memory -macro_rules! mem_load { - ($type:ty, $arg:expr, $stack:ident, $store:ident, $module:ident) => {{ - mem_load!($type, $type, $arg, $stack, $store, $module) - }}; - - ($load_type:ty, $target_type:ty, $arg:expr, $stack:ident, $store:ident, $module:ident) => {{ - #[inline(always)] - fn mem_load_inner( - store: &Store, - module: &crate::ModuleInstance, - stack: &mut crate::runtime::Stack, - mem_addr: tinywasm_types::MemAddr, - offset: u64, - ) -> Result<()> { - let mem = store.get_mem(module.resolve_mem_addr(mem_addr))?; - let addr: usize = match offset.checked_add(stack.values.pop()?.into()).map(|a| a.try_into()) { - Some(Ok(a)) => a, - _ => { - cold(); - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: offset as usize, - len: core::mem::size_of::<$load_type>(), - max: mem.borrow().max_pages(), - })); - } - }; - - const LEN: usize = core::mem::size_of::<$load_type>(); - let val = mem.borrow().load_as::(addr)?; - stack.values.push((val as $target_type).into()); - Ok(()) - } - - let (mem_addr, offset) = $arg; - mem_load_inner($store, &$module, $stack, *mem_addr, *offset)?; - }}; -} - -/// Store a value to memory -macro_rules! mem_store { - ($type:ty, $arg:expr, $stack:ident, $store:ident, $module:ident) => {{ - mem_store!($type, $type, $arg, $stack, $store, $module) - }}; - - ($store_type:ty, $target_type:ty, $arg:expr, $stack:ident, $store:ident, $module:ident) => {{ - #[inline(always)] - fn mem_store_inner( - store: &Store, - module: &crate::ModuleInstance, - stack: &mut crate::runtime::Stack, - mem_addr: tinywasm_types::MemAddr, - offset: u64, - ) -> Result<()> { - let mem = store.get_mem(module.resolve_mem_addr(mem_addr))?; - let val: $store_type = stack.values.pop()?.into(); - let val = val.to_le_bytes(); - let addr: u64 = stack.values.pop()?.into(); - mem.borrow_mut().store((offset + addr) as usize, val.len(), &val)?; - Ok(()) - } - - let (mem_addr, offset) = $arg; - mem_store_inner($store, &$module, $stack, *mem_addr, *offset)?; - }}; -} - -/// 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, https://crates.io/crates/az 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 -macro_rules! conv { - ($from:ty, $to:ty, $stack:ident) => { - $stack.values.replace_top(|v| (<$from>::from(v) as $to).into())? - }; -} - -/// 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, $stack:ident) => { - checked_conv_float!($from, $to, $to, $stack) - }; - // Conversion with an intermediate unsigned type and error checking (three types) - ($from:tt, $intermediate:tt, $to:tt, $stack:ident) => {{ - let (min, max) = float_min_max!($from, $intermediate); - let a: $from = $stack.values.pop()?.into(); - - if unlikely(a.is_nan()) { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - - if unlikely(a <= min || a >= max) { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - - $stack.values.push((a as $intermediate as $to).into()); - }}; -} - -/// Compare two values on the stack -macro_rules! comp { - ($op:tt, $to:ty, $stack:ident) => { - $stack.values.calculate(|a, b| { - ((<$to>::from(a) $op <$to>::from(b)) as i32).into() - })? - }; -} - -/// Compare a value on the stack to zero -macro_rules! comp_zero { - ($op:tt, $ty:ty, $stack:ident) => { - $stack.values.replace_top(|v| { - ((<$ty>::from(v) $op 0) as i32).into() - })? - }; -} - -/// Apply an arithmetic method to two values on the stack -macro_rules! arithmetic { - ($op:ident, $to:ty, $stack:ident) => { - $stack.values.calculate(|a, b| { - (<$to>::from(a).$op(<$to>::from(b)) as $to).into() - })? - }; - - // also allow operators such as +, - - ($op:tt, $ty:ty, $stack:ident) => { - $stack.values.calculate(|a, b| { - ((<$ty>::from(a) $op <$ty>::from(b)) as $ty).into() - })? - }; -} - -/// Apply an arithmetic method to a single value on the stack -macro_rules! arithmetic_single { - ($op:ident, $ty:ty, $stack:ident) => { - arithmetic_single!($op, $ty, $ty, $stack) - }; - - ($op:ident, $from:ty, $to:ty, $stack:ident) => { - $stack.values.replace_top(|v| (<$from>::from(v).$op() as $to).into())? - }; -} - -/// Apply an arithmetic operation to two values on the stack with error checking -macro_rules! checked_int_arithmetic { - ($op:ident, $to:ty, $stack:ident) => { - $stack.values.calculate_trap(|a, b| { - let a: $to = a.into(); - let b: $to = b.into(); - - if unlikely(b == 0) { - return Err(Error::Trap(crate::Trap::DivisionByZero)); - } - - let result = a.$op(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow))?; - Ok((result).into()) - })? - }; -} - -macro_rules! call { - ($cf:expr, $stack:expr, $module:expr, $store:expr) => {{ - let old = $cf.block_ptr; - $cf = $stack.call_stack.pop()?; - - if old > $cf.block_ptr { - $stack.blocks.truncate(old); - } - - if $cf.module_addr != $module.id() { - $module.swap_with($cf.module_addr, $store); - } - - continue; - }}; -} - -macro_rules! skip { - ($code:expr) => { - match $code { - Ok(_) => continue, - Err(e) => return Err(e), - } - }; -} - -pub(super) use arithmetic; -pub(super) use arithmetic_single; -pub(super) use break_to; -pub(super) use call; -pub(super) use checked_conv_float; -pub(super) use checked_int_arithmetic; -pub(super) use comp; -pub(super) use comp_zero; -pub(super) use conv; -pub(super) use float_min_max; -pub(super) use mem_load; -pub(super) use mem_store; -pub(super) use skip; diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs deleted file mode 100644 index d01faeb..0000000 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ /dev/null @@ -1,775 +0,0 @@ -use alloc::format; -use alloc::string::ToString; -use core::ops::{BitAnd, BitOr, BitXor, Neg}; -use tinywasm_types::{BlockArgs, ElementKind, ValType}; - -use super::{InterpreterRuntime, RawWasmValue, Stack}; -use crate::runtime::{BlockFrame, BlockType, CallFrame}; -use crate::{cold, unlikely, ModuleInstance}; -use crate::{Error, FuncContext, Result, Store, Trap}; - -mod macros; -mod traits; -use {macros::*, traits::*}; - -#[cfg(not(feature = "std"))] -mod no_std_floats; - -#[cfg(not(feature = "std"))] -#[allow(unused_imports)] -use no_std_floats::NoStdFloatExt; - -impl InterpreterRuntime { - pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack) -> Result<()> { - let mut cf = stack.call_stack.pop()?; - let mut module = store.get_module_instance_raw(cf.module_addr); - - loop { - use tinywasm_types::Instruction::*; - match cf.fetch_instr() { - Nop => cold(), - Unreachable => self.exec_unreachable()?, - Drop => stack.values.pop().map(|_| ())?, - Select(_valtype) => self.exec_select(stack)?, - - Call(v) => skip!(self.exec_call(*v, store, stack, &mut cf, &mut module)), - CallIndirect(ty, table) => { - skip!(self.exec_call_indirect(*ty, *table, store, stack, &mut cf, &mut module)) - } - If(args, el, end) => skip!(self.exec_if((*args).into(), *el, *end, stack, &mut cf, &mut module)), - Loop(args, end) => self.enter_block(stack, cf.instr_ptr, *end, BlockType::Loop, args, &module), - Block(args, end) => self.enter_block(stack, cf.instr_ptr, *end, BlockType::Block, args, &module), - - Br(v) => break_to!(cf, stack, module, store, v), - BrIf(v) => { - if i32::from(stack.values.pop()?) != 0 { - break_to!(cf, stack, module, store, v); - } - } - BrTable(default, len) => { - let start = cf.instr_ptr + 1; - let end = start + *len as usize; - if end > cf.instructions().len() { - return Err(Error::Other(format!( - "br_table out of bounds: {} >= {}", - end, - cf.instructions().len() - ))); - } - - let idx: i32 = stack.values.pop()?.into(); - match cf.instructions()[start..end].get(idx as usize) { - None => break_to!(cf, stack, module, store, default), - Some(BrLabel(to)) => break_to!(cf, stack, module, store, to), - _ => return Err(Error::Other("br_table with invalid label".to_string())), - } - } - - Return => match stack.call_stack.is_empty() { - true => return Ok(()), - false => call!(cf, stack, module, store), - }, - - // We're essentially using else as a EndBlockFrame instruction for if blocks - Else(end_offset) => self.exec_else(stack, *end_offset, &mut cf)?, - - // remove the label from the label stack - EndBlockFrame => self.exec_end_block(stack)?, - - LocalGet(local_index) => self.exec_local_get(*local_index, stack, &cf), - LocalSet(local_index) => self.exec_local_set(*local_index, stack, &mut cf)?, - LocalTee(local_index) => self.exec_local_tee(*local_index, stack, &mut cf)?, - - GlobalGet(global_index) => self.exec_global_get(*global_index, stack, store, &module)?, - GlobalSet(global_index) => self.exec_global_set(*global_index, stack, store, &module)?, - - I32Const(val) => self.exec_const(*val, stack), - I64Const(val) => self.exec_const(*val, stack), - F32Const(val) => self.exec_const(*val, stack), - F64Const(val) => self.exec_const(*val, stack), - - MemorySize(addr, byte) => self.exec_memory_size(*addr, *byte, stack, store, &module)?, - MemoryGrow(addr, byte) => self.exec_memory_grow(*addr, *byte, stack, store, &module)?, - - // Bulk memory operations - MemoryCopy(from, to) => self.exec_memory_copy(*from, *to, stack, store, &module)?, - MemoryFill(addr) => self.exec_memory_fill(*addr, stack, store, &module)?, - MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx, stack, store, &module)?, - DataDrop(data_index) => store.get_data_mut(module.resolve_data_addr(*data_index))?.drop(), - - I32Store { mem_addr, offset } => mem_store!(i32, (mem_addr, offset), stack, store, module), - I64Store { mem_addr, offset } => mem_store!(i64, (mem_addr, offset), stack, store, module), - F32Store { mem_addr, offset } => mem_store!(f32, (mem_addr, offset), stack, store, module), - F64Store { mem_addr, offset } => mem_store!(f64, (mem_addr, offset), stack, store, module), - I32Store8 { mem_addr, offset } => mem_store!(i8, i32, (mem_addr, offset), stack, store, module), - I32Store16 { mem_addr, offset } => mem_store!(i16, i32, (mem_addr, offset), stack, store, module), - I64Store8 { mem_addr, offset } => mem_store!(i8, i64, (mem_addr, offset), stack, store, module), - I64Store16 { mem_addr, offset } => mem_store!(i16, i64, (mem_addr, offset), stack, store, module), - I64Store32 { mem_addr, offset } => mem_store!(i32, i64, (mem_addr, offset), stack, store, module), - - I32Load { mem_addr, offset } => mem_load!(i32, (mem_addr, offset), stack, store, module), - I64Load { mem_addr, offset } => mem_load!(i64, (mem_addr, offset), stack, store, module), - F32Load { mem_addr, offset } => mem_load!(f32, (mem_addr, offset), stack, store, module), - F64Load { mem_addr, offset } => mem_load!(f64, (mem_addr, offset), stack, store, module), - I32Load8S { mem_addr, offset } => mem_load!(i8, i32, (mem_addr, offset), stack, store, module), - I32Load8U { mem_addr, offset } => mem_load!(u8, i32, (mem_addr, offset), stack, store, module), - I32Load16S { mem_addr, offset } => mem_load!(i16, i32, (mem_addr, offset), stack, store, module), - I32Load16U { mem_addr, offset } => mem_load!(u16, i32, (mem_addr, offset), stack, store, module), - I64Load8S { mem_addr, offset } => mem_load!(i8, i64, (mem_addr, offset), stack, store, module), - I64Load8U { mem_addr, offset } => mem_load!(u8, i64, (mem_addr, offset), stack, store, module), - I64Load16S { mem_addr, offset } => mem_load!(i16, i64, (mem_addr, offset), stack, store, module), - I64Load16U { mem_addr, offset } => mem_load!(u16, i64, (mem_addr, offset), stack, store, module), - I64Load32S { mem_addr, offset } => mem_load!(i32, i64, (mem_addr, offset), stack, store, module), - I64Load32U { mem_addr, offset } => mem_load!(u32, i64, (mem_addr, offset), stack, store, module), - - 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!(<, u32, stack), - I64LtU => comp!(<, u64, stack), - F32Lt => comp!(<, f32, stack), - F64Lt => comp!(<, f64, stack), - - I32LeS => comp!(<=, i32, stack), - I64LeS => comp!(<=, i64, stack), - I32LeU => comp!(<=, u32, stack), - I64LeU => comp!(<=, u64, stack), - F32Le => comp!(<=, f32, stack), - F64Le => comp!(<=, f64, stack), - - I32GeS => comp!(>=, i32, stack), - I64GeS => comp!(>=, i64, stack), - I32GeU => comp!(>=, u32, stack), - I64GeU => comp!(>=, u64, stack), - F32Ge => comp!(>=, f32, stack), - F64Ge => comp!(>=, f64, stack), - - I32GtS => comp!(>, i32, stack), - I64GtS => comp!(>, i64, stack), - I32GtU => comp!(>, u32, stack), - I64GtU => comp!(>, u64, stack), - F32Gt => comp!(>, f32, stack), - F64Gt => comp!(>, f64, stack), - - I64Add => arithmetic!(wrapping_add, i64, stack), - I32Add => arithmetic!(wrapping_add, i32, stack), - F32Add => arithmetic!(+, f32, stack), - F64Add => arithmetic!(+, f64, stack), - - I32Sub => arithmetic!(wrapping_sub, i32, stack), - I64Sub => arithmetic!(wrapping_sub, i64, stack), - F32Sub => arithmetic!(-, f32, stack), - F64Sub => arithmetic!(-, f64, stack), - - F32Div => arithmetic!(/, f32, stack), - F64Div => arithmetic!(/, f64, stack), - - I32Mul => arithmetic!(wrapping_mul, i32, stack), - I64Mul => arithmetic!(wrapping_mul, i64, stack), - F32Mul => arithmetic!(*, f32, stack), - F64Mul => arithmetic!(*, f64, stack), - - // these can trap - I32DivS => checked_int_arithmetic!(checked_div, i32, stack), - I64DivS => checked_int_arithmetic!(checked_div, i64, stack), - I32DivU => checked_int_arithmetic!(checked_div, u32, stack), - I64DivU => checked_int_arithmetic!(checked_div, u64, stack), - - I32RemS => checked_int_arithmetic!(checked_wrapping_rem, i32, stack), - I64RemS => checked_int_arithmetic!(checked_wrapping_rem, i64, stack), - I32RemU => checked_int_arithmetic!(checked_wrapping_rem, u32, stack), - I64RemU => checked_int_arithmetic!(checked_wrapping_rem, u64, stack), - - I32And => arithmetic!(bitand, i32, stack), - I64And => arithmetic!(bitand, i64, stack), - I32Or => arithmetic!(bitor, i32, stack), - I64Or => arithmetic!(bitor, i64, stack), - I32Xor => arithmetic!(bitxor, i32, stack), - I64Xor => arithmetic!(bitxor, i64, stack), - I32Shl => arithmetic!(wasm_shl, i32, stack), - I64Shl => arithmetic!(wasm_shl, i64, stack), - I32ShrS => arithmetic!(wasm_shr, i32, stack), - I64ShrS => arithmetic!(wasm_shr, i64, stack), - I32ShrU => arithmetic!(wasm_shr, u32, stack), - I64ShrU => arithmetic!(wasm_shr, u64, stack), - I32Rotl => arithmetic!(wasm_rotl, i32, stack), - I64Rotl => arithmetic!(wasm_rotl, i64, stack), - I32Rotr => arithmetic!(wasm_rotr, i32, stack), - I64Rotr => arithmetic!(wasm_rotr, i64, stack), - - I32Clz => arithmetic_single!(leading_zeros, i32, stack), - I64Clz => arithmetic_single!(leading_zeros, i64, stack), - I32Ctz => arithmetic_single!(trailing_zeros, i32, stack), - I64Ctz => arithmetic_single!(trailing_zeros, i64, stack), - I32Popcnt => arithmetic_single!(count_ones, i32, stack), - I64Popcnt => arithmetic_single!(count_ones, i64, stack), - - F32ConvertI32S => conv!(i32, f32, stack), - F32ConvertI64S => conv!(i64, f32, stack), - F64ConvertI32S => conv!(i32, f64, stack), - F64ConvertI64S => conv!(i64, f64, stack), - F32ConvertI32U => conv!(u32, f32, stack), - F32ConvertI64U => conv!(u64, f32, stack), - F64ConvertI32U => conv!(u32, f64, stack), - F64ConvertI64U => conv!(u64, f64, stack), - I32Extend8S => conv!(i8, i32, stack), - I32Extend16S => conv!(i16, i32, stack), - I64Extend8S => conv!(i8, i64, stack), - I64Extend16S => conv!(i16, i64, stack), - I64Extend32S => conv!(i32, i64, stack), - I64ExtendI32U => conv!(u32, i64, stack), - I64ExtendI32S => conv!(i32, i64, stack), - I32WrapI64 => conv!(i64, i32, stack), - - F32DemoteF64 => conv!(f64, f32, stack), - F64PromoteF32 => conv!(f32, f64, stack), - - F32Abs => arithmetic_single!(abs, f32, stack), - F64Abs => arithmetic_single!(abs, f64, stack), - F32Neg => arithmetic_single!(neg, f32, stack), - F64Neg => arithmetic_single!(neg, f64, stack), - F32Ceil => arithmetic_single!(ceil, f32, stack), - F64Ceil => arithmetic_single!(ceil, f64, stack), - F32Floor => arithmetic_single!(floor, f32, stack), - F64Floor => arithmetic_single!(floor, f64, stack), - F32Trunc => arithmetic_single!(trunc, f32, stack), - F64Trunc => arithmetic_single!(trunc, f64, stack), - F32Nearest => arithmetic_single!(tw_nearest, f32, stack), - F64Nearest => arithmetic_single!(tw_nearest, f64, stack), - F32Sqrt => arithmetic_single!(sqrt, f32, stack), - F64Sqrt => arithmetic_single!(sqrt, f64, stack), - F32Min => arithmetic!(tw_minimum, f32, stack), - F64Min => arithmetic!(tw_minimum, f64, stack), - F32Max => arithmetic!(tw_maximum, f32, stack), - F64Max => arithmetic!(tw_maximum, f64, stack), - F32Copysign => arithmetic!(copysign, f32, stack), - F64Copysign => arithmetic!(copysign, f64, stack), - - // no-op instructions since types are erased at runtime - I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} - - // unsigned versions of these are a bit broken atm - I32TruncF32S => checked_conv_float!(f32, i32, stack), - I32TruncF64S => checked_conv_float!(f64, i32, stack), - I32TruncF32U => checked_conv_float!(f32, u32, i32, stack), - I32TruncF64U => checked_conv_float!(f64, u32, i32, stack), - I64TruncF32S => checked_conv_float!(f32, i64, stack), - I64TruncF64S => checked_conv_float!(f64, i64, stack), - I64TruncF32U => checked_conv_float!(f32, u64, i64, stack), - I64TruncF64U => checked_conv_float!(f64, u64, i64, stack), - - TableGet(table_idx) => self.exec_table_get(*table_idx, stack, store, &module)?, - TableSet(table_idx) => self.exec_table_set(*table_idx, stack, store, &module)?, - TableSize(table_idx) => self.exec_table_size(*table_idx, stack, store, &module)?, - TableInit(table_idx, elem_idx) => self.exec_table_init(*elem_idx, *table_idx, store, &module)?, - - I32TruncSatF32S => arithmetic_single!(trunc, f32, i32, stack), - I32TruncSatF32U => arithmetic_single!(trunc, f32, u32, stack), - I32TruncSatF64S => arithmetic_single!(trunc, f64, i32, stack), - I32TruncSatF64U => arithmetic_single!(trunc, f64, u32, stack), - I64TruncSatF32S => arithmetic_single!(trunc, f32, i64, stack), - I64TruncSatF32U => arithmetic_single!(trunc, f32, u64, stack), - I64TruncSatF64S => arithmetic_single!(trunc, f64, i64, stack), - I64TruncSatF64U => arithmetic_single!(trunc, f64, u64, stack), - - // custom instructions - LocalGet2(a, b) => self.exec_local_get2(*a, *b, stack, &cf), - LocalGet3(a, b, c) => self.exec_local_get3(*a, *b, *c, stack, &cf), - LocalTeeGet(a, b) => self.exec_local_tee_get(*a, *b, stack, &mut cf), - LocalGetSet(a, b) => self.exec_local_get_set(*a, *b, &mut cf), - I64XorConstRotl(rotate_by) => self.exec_i64_xor_const_rotl(*rotate_by, stack)?, - I32LocalGetConstAdd(local, val) => self.exec_i32_local_get_const_add(*local, *val, stack, &cf), - I32StoreLocal { local, const_i32: consti32, offset, mem_addr } => { - self.exec_i32_store_local(*local, *consti32, *offset, *mem_addr, &cf, store, &module)? - } - i => { - cold(); - return Err(Error::UnsupportedFeature(format!("unimplemented instruction: {:?}", i))); - } - }; - - cf.instr_ptr += 1; - } - } - - #[inline(always)] - fn exec_end_block(&self, stack: &mut Stack) -> Result<()> { - let block = stack.blocks.pop()?; - stack.values.truncate_keep(block.stack_ptr, block.results as u32); - Ok(()) - } - - #[inline(always)] - fn exec_else(&self, stack: &mut Stack, end_offset: u32, cf: &mut CallFrame) -> Result<()> { - let block = stack.blocks.pop()?; - stack.values.truncate_keep(block.stack_ptr, block.results as u32); - cf.instr_ptr += end_offset as usize; - Ok(()) - } - - #[inline(always)] - #[cold] - fn exec_unreachable(&self) -> Result<()> { - Err(Error::Trap(Trap::Unreachable)) - } - - #[inline(always)] - fn exec_const(&self, val: impl Into, stack: &mut Stack) { - stack.values.push(val.into()); - } - - #[inline(always)] - fn exec_i32_store_local( - &self, - local: u32, - const_i32: i32, - offset: u32, - mem_addr: u8, - cf: &CallFrame, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let mem_addr = module.resolve_mem_addr(mem_addr as u32); - let mem = store.get_mem(mem_addr)?; - let val = const_i32.to_le_bytes(); - let addr: u64 = cf.get_local(local).into(); - mem.borrow_mut().store((offset as u64 + addr) as usize, val.len(), &val)?; - Ok(()) - } - - #[inline(always)] - fn exec_i32_local_get_const_add(&self, local: u32, val: i32, stack: &mut Stack, cf: &CallFrame) { - let local: i32 = cf.get_local(local).into(); - stack.values.push((local + val).into()); - } - - #[inline(always)] - fn exec_i64_xor_const_rotl(&self, rotate_by: i64, stack: &mut Stack) -> Result<()> { - let val: i64 = stack.values.pop()?.into(); - let res = stack.values.last_mut()?; - let mask: i64 = (*res).into(); - *res = (val ^ mask).rotate_left(rotate_by as u32).into(); - Ok(()) - } - - #[inline(always)] - fn exec_local_get(&self, local_index: u32, stack: &mut Stack, cf: &CallFrame) { - stack.values.push(cf.get_local(local_index)); - } - - #[inline(always)] - fn exec_local_get2(&self, a: u32, b: u32, stack: &mut Stack, cf: &CallFrame) { - stack.values.push(cf.get_local(a)); - stack.values.push(cf.get_local(b)); - } - - #[inline(always)] - fn exec_local_get3(&self, a: u32, b: u32, c: u32, stack: &mut Stack, cf: &CallFrame) { - stack.values.push(cf.get_local(a)); - stack.values.push(cf.get_local(b)); - stack.values.push(cf.get_local(c)); - } - - #[inline(always)] - fn exec_local_get_set(&self, a: u32, b: u32, cf: &mut CallFrame) { - cf.set_local(b, cf.get_local(a)) - } - - #[inline(always)] - fn exec_local_set(&self, local_index: u32, stack: &mut Stack, cf: &mut CallFrame) -> Result<()> { - cf.set_local(local_index, stack.values.pop()?); - Ok(()) - } - - #[inline(always)] - fn exec_local_tee(&self, local_index: u32, stack: &mut Stack, cf: &mut CallFrame) -> Result<()> { - cf.set_local(local_index, *stack.values.last()?); - Ok(()) - } - - #[inline(always)] - fn exec_local_tee_get(&self, a: u32, b: u32, stack: &mut Stack, cf: &mut CallFrame) { - let last = - stack.values.last().expect("localtee: stack is empty. this should have been validated by the parser"); - cf.set_local(a, *last); - stack.values.push(match a == b { - true => *last, - false => cf.get_local(b), - }); - } - - #[inline(always)] - fn exec_global_get( - &self, - global_index: u32, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let global = store.get_global_val(module.resolve_global_addr(global_index))?; - stack.values.push(global); - Ok(()) - } - - #[inline(always)] - fn exec_global_set( - &self, - global_index: u32, - stack: &mut Stack, - store: &mut Store, - module: &ModuleInstance, - ) -> Result<()> { - let idx = module.resolve_global_addr(global_index); - store.set_global_val(idx, stack.values.pop()?)?; - Ok(()) - } - - #[inline(always)] - fn exec_table_get( - &self, - table_index: u32, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let table_idx = module.resolve_table_addr(table_index); - let table = store.get_table(table_idx)?; - let idx: u32 = stack.values.pop()?.into(); - let v = table.borrow().get_wasm_val(idx)?; - stack.values.push(v.into()); - Ok(()) - } - - #[inline(always)] - fn exec_table_set( - &self, - table_index: u32, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let table_idx = module.resolve_table_addr(table_index); - let table = store.get_table(table_idx)?; - let val = stack.values.pop()?.into(); - let idx = stack.values.pop()?.into(); - table.borrow_mut().set(idx, val)?; - Ok(()) - } - - #[inline(always)] - fn exec_table_size( - &self, - table_index: u32, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let table_idx = module.resolve_table_addr(table_index); - let table = store.get_table(table_idx)?; - stack.values.push(table.borrow().size().into()); - Ok(()) - } - - #[inline(always)] - fn exec_table_init(&self, elem_index: u32, table_index: u32, store: &Store, module: &ModuleInstance) -> Result<()> { - let table_idx = module.resolve_table_addr(table_index); - let table = store.get_table(table_idx)?; - let elem = store.get_elem(module.resolve_elem_addr(elem_index))?; - - if let ElementKind::Passive = elem.kind { - return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); - } - - let Some(items) = elem.items.as_ref() else { - return Err(Trap::TableOutOfBounds { offset: 0, len: 0, max: 0 }.into()); - }; - - table.borrow_mut().init(module.func_addrs(), 0, items)?; - Ok(()) - } - - #[inline(always)] - fn exec_select(&self, stack: &mut Stack) -> Result<()> { - 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 { - *stack.values.last_mut()? = val2; - } - Ok(()) - } - - #[inline(always)] - fn exec_memory_size( - &self, - addr: u32, - byte: u8, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - if unlikely(byte != 0) { - return Err(Error::UnsupportedFeature("memory.size with byte != 0".to_string())); - } - - let mem_idx = module.resolve_mem_addr(addr); - let mem = store.get_mem(mem_idx)?; - stack.values.push((mem.borrow().page_count() as i32).into()); - Ok(()) - } - - #[inline(always)] - fn exec_memory_grow( - &self, - addr: u32, - byte: u8, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - if unlikely(byte != 0) { - return Err(Error::UnsupportedFeature("memory.grow with byte != 0".to_string())); - } - - let mut mem = store.get_mem(module.resolve_mem_addr(addr))?.borrow_mut(); - let prev_size = mem.page_count() as i32; - let pages_delta = stack.values.last_mut()?; - *pages_delta = match mem.grow(i32::from(*pages_delta)) { - Some(_) => prev_size.into(), - None => (-1).into(), - }; - - Ok(()) - } - - #[inline(always)] - fn exec_memory_copy( - &self, - from: u32, - to: u32, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let size: i32 = stack.values.pop()?.into(); - let src: i32 = stack.values.pop()?.into(); - let dst: i32 = stack.values.pop()?.into(); - - if from == to { - let mut mem_from = store.get_mem(module.resolve_mem_addr(from))?.borrow_mut(); - // 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 = store.get_mem(module.resolve_mem_addr(from))?.borrow(); - let mut mem_to = store.get_mem(module.resolve_mem_addr(to))?.borrow_mut(); - mem_to.copy_from_slice(dst as usize, mem_from.load(src as usize, size as usize)?)?; - } - Ok(()) - } - - #[inline(always)] - fn exec_memory_fill(&self, addr: u32, stack: &mut Stack, store: &Store, module: &ModuleInstance) -> Result<()> { - let size: i32 = stack.values.pop()?.into(); - let val: i32 = stack.values.pop()?.into(); - let dst: i32 = stack.values.pop()?.into(); - - let mem = store.get_mem(module.resolve_mem_addr(addr))?; - mem.borrow_mut().fill(dst as usize, size as usize, val as u8)?; - Ok(()) - } - - #[inline(always)] - fn exec_memory_init( - &self, - data_index: u32, - mem_index: u32, - stack: &mut Stack, - store: &Store, - module: &ModuleInstance, - ) -> Result<()> { - let size = i32::from(stack.values.pop()?) as usize; - let offset = i32::from(stack.values.pop()?) as usize; - let dst = i32::from(stack.values.pop()?) as usize; - - let data = match &store.get_data(module.resolve_data_addr(data_index))?.data { - Some(data) => data, - None => return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()), - }; - - if unlikely(offset + size > data.len()) { - return Err(Trap::MemoryOutOfBounds { offset, len: size, max: data.len() }.into()); - } - - let mem = store.get_mem(module.resolve_mem_addr(mem_index))?; - mem.borrow_mut().store(dst, size, &data[offset..(offset + size)])?; - Ok(()) - } - - #[inline(always)] - fn exec_call( - &self, - v: u32, - store: &mut Store, - stack: &mut Stack, - cf: &mut CallFrame, - module: &mut ModuleInstance, - ) -> Result<()> { - let func_inst = store.get_func(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 = stack.values.pop_params(&host_func.ty.params)?; - let res = (func.func)(FuncContext { store, module_addr: module.id() }, ¶ms)?; - stack.values.extend_from_typed(&res); - cf.instr_ptr += 1; - return Ok(()); - } - }; - - let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?; - let new_call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, stack.blocks.len() as u32); - - cf.instr_ptr += 1; // skip the call instruction - stack.call_stack.push(core::mem::replace(cf, new_call_frame))?; - if cf.module_addr != module.id() { - module.swap_with(cf.module_addr, store); - } - Ok(()) - } - - #[inline(always)] - fn exec_call_indirect( - &self, - type_addr: u32, - table_addr: u32, - store: &mut Store, - stack: &mut Stack, - cf: &mut CallFrame, - module: &mut ModuleInstance, - ) -> Result<()> { - let table = store.get_table(module.resolve_table_addr(table_addr))?; - let table_idx: u32 = stack.values.pop()?.into(); - - // verify that the table is of the right type, this should be validated by the parser already - let func_ref = { - let table = table.borrow(); - assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); - table.get(table_idx)?.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize })? - }; - - let func_inst = store.get_func(func_ref)?.clone(); - let call_ty = module.func_ty(type_addr); - - let wasm_func = match func_inst.func { - crate::Function::Wasm(ref f) => f, - crate::Function::Host(host_func) => { - if unlikely(host_func.ty != *call_ty) { - return Err(Trap::IndirectCallTypeMismatch { - actual: host_func.ty.clone(), - expected: call_ty.clone(), - } - .into()); - } - - let host_func = host_func.clone(); - let params = stack.values.pop_params(&host_func.ty.params)?; - let res = (host_func.func)(FuncContext { store, module_addr: module.id() }, ¶ms)?; - stack.values.extend_from_typed(&res); - - cf.instr_ptr += 1; - return Ok(()); - } - }; - - if unlikely(wasm_func.ty != *call_ty) { - return Err( - Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into() - ); - } - - let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?; - let new_call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, stack.blocks.len() as u32); - - cf.instr_ptr += 1; // skip the call instruction - stack.call_stack.push(core::mem::replace(cf, new_call_frame))?; - if cf.module_addr != module.id() { - module.swap_with(cf.module_addr, store); - } - Ok(()) - } - - #[inline(always)] - fn exec_if( - &self, - args: BlockArgs, - else_offset: u32, - end_offset: u32, - stack: &mut Stack, - cf: &mut CallFrame, - module: &mut ModuleInstance, - ) -> Result<()> { - // truthy value is on the top of the stack, so enter the then block - if i32::from(stack.values.pop()?) != 0 { - self.enter_block(stack, cf.instr_ptr, end_offset, BlockType::If, &args, module); - cf.instr_ptr += 1; - return Ok(()); - } - - // falsy value is on the top of the stack - if else_offset == 0 { - cf.instr_ptr += end_offset as usize + 1; - return Ok(()); - } - - let old = cf.instr_ptr; - cf.instr_ptr += else_offset as usize; - - self.enter_block(stack, old + else_offset as usize, end_offset - else_offset, BlockType::Else, &args, module); - - cf.instr_ptr += 1; - Ok(()) - } - - #[inline(always)] - fn enter_block( - &self, - stack: &mut super::Stack, - instr_ptr: usize, - end_instr_offset: u32, - ty: BlockType, - args: &BlockArgs, - module: &ModuleInstance, - ) { - let (params, results) = match args { - BlockArgs::Empty => (0, 0), - BlockArgs::Type(_) => (0, 1), - BlockArgs::FuncType(t) => { - let ty = module.func_ty(*t); - (ty.params.len() as u8, ty.results.len() as u8) - } - }; - - stack.blocks.push(BlockFrame { - instr_ptr, - end_instr_offset, - stack_ptr: stack.values.len() as u32 - params as u32, - results, - params, - ty, - }); - } -} diff --git a/crates/tinywasm/src/runtime/mod.rs b/crates/tinywasm/src/runtime/mod.rs deleted file mode 100644 index 8c22ce0..0000000 --- a/crates/tinywasm/src/runtime/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -mod interpreter; -mod stack; -mod value; - -use crate::Result; -pub use stack::*; -pub(crate) use value::RawWasmValue; - -#[allow(rustdoc::private_intra_doc_links)] -/// A WebAssembly runtime. -/// -/// See -pub trait Runtime { - /// Execute all call-frames on the stack until the stack is empty. - fn exec(&self, store: &mut crate::Store, stack: &mut crate::runtime::Stack) -> Result<()>; -} - -/// The main TinyWasm runtime. -/// -/// This is the default runtime used by TinyWasm. -#[derive(Debug, Default)] -pub struct InterpreterRuntime {} 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 5dc754f..0000000 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::runtime::{BlockType, RawWasmValue}; -use crate::{cold, unlikely}; -use crate::{Error, Result, Trap}; -use alloc::{boxed::Box, rc::Rc, vec::Vec}; -use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction}; - -const CALL_STACK_SIZE: usize = 1024; - -#[derive(Debug)] -pub(crate) struct CallStack { - stack: Vec, -} - -impl CallStack { - #[inline] - pub(crate) fn new(initial_frame: CallFrame) -> Self { - let mut stack = Vec::new(); - stack.reserve_exact(CALL_STACK_SIZE); - stack.push(initial_frame); - Self { stack } - } - - #[inline] - pub(crate) fn is_empty(&self) -> bool { - self.stack.is_empty() - } - - #[inline(always)] - pub(crate) fn pop(&mut self) -> Result { - match self.stack.pop() { - Some(frame) => Ok(frame), - None => { - cold(); - Err(Error::CallStackUnderflow) - } - } - } - - #[inline(always)] - pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - if unlikely(self.stack.len() >= self.stack.capacity()) { - return Err(Trap::CallStackOverflow.into()); - } - self.stack.push(call_frame); - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct CallFrame { - pub(crate) instr_ptr: usize, - pub(crate) block_ptr: u32, - pub(crate) func_instance: Rc, - pub(crate) module_addr: ModuleInstanceAddr, - pub(crate) locals: Box<[RawWasmValue]>, -} - -impl CallFrame { - #[inline(always)] - pub(crate) fn fetch_instr(&self) -> &Instruction { - match self.func_instance.instructions.get(self.instr_ptr) { - Some(instr) => instr, - None => { - cold(); - panic!("Instruction pointer out of bounds"); - } - } - } - - /// Break to a block at the given index (relative to the current frame) - /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) - pub(crate) fn break_to( - &mut self, - break_to_relative: u32, - 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.break_to(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.break_to(break_to.stack_ptr, break_to.results); - - // (the inst_ptr will be incremented by 1 before the next instruction is executed) - self.instr_ptr = break_to.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: impl ExactSizeIterator, - block_ptr: u32, - ) -> Self { - let locals = { - let total_size = wasm_func_inst.locals.len() + params.len(); - let mut locals = Vec::new(); - locals.reserve_exact(total_size); - locals.extend(params); - locals.resize_with(total_size, RawWasmValue::default); - locals.into_boxed_slice() - }; - - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, locals, block_ptr } - } - - #[inline(always)] - pub(crate) fn set_local(&mut self, local_index: LocalAddr, value: RawWasmValue) { - self.locals[local_index as usize] = value; - } - - #[inline(always)] - pub(crate) fn get_local(&self, local_index: LocalAddr) -> RawWasmValue { - self.locals[local_index as usize] - } - - #[inline(always)] - pub(crate) fn instructions(&self) -> &[Instruction] { - &self.func_instance.instructions - } -} 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 1573a5d..0000000 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::{cold, runtime::RawWasmValue, unlikely, Error, Result}; -use alloc::vec::Vec; -use tinywasm_types::{ValType, WasmValue}; - -pub(crate) const MIN_VALUE_STACK_SIZE: usize = 1024 * 128; - -#[derive(Debug)] -pub(crate) struct ValueStack { - stack: Vec, -} - -impl Default for ValueStack { - fn default() -> Self { - Self { stack: Vec::with_capacity(MIN_VALUE_STACK_SIZE) } - } -} - -impl ValueStack { - #[inline] - pub(crate) fn extend_from_typed(&mut self, values: &[WasmValue]) { - self.stack.extend(values.iter().map(|v| RawWasmValue::from(*v))); - } - - #[inline(always)] - pub(crate) fn replace_top(&mut self, func: fn(RawWasmValue) -> RawWasmValue) -> Result<()> { - let v = self.last_mut()?; - *v = func(*v); - Ok(()) - } - - #[inline(always)] - pub(crate) fn calculate(&mut self, func: fn(RawWasmValue, RawWasmValue) -> RawWasmValue) -> Result<()> { - let v2 = self.pop()?; - let v1 = self.last_mut()?; - *v1 = func(*v1, v2); - Ok(()) - } - - #[inline(always)] - pub(crate) fn calculate_trap( - &mut self, - func: fn(RawWasmValue, RawWasmValue) -> Result, - ) -> Result<()> { - let v2 = self.pop()?; - let v1 = self.last_mut()?; - *v1 = func(*v1, v2)?; - Ok(()) - } - - #[inline(always)] - pub(crate) fn len(&self) -> usize { - self.stack.len() - } - - #[inline] - pub(crate) fn truncate_keep(&mut self, n: u32, end_keep: u32) { - let total_to_keep = n + end_keep; - let len = self.stack.len() as u32; - assert!(len >= total_to_keep, "Total to keep should be less than or equal to self.top"); - - if len <= total_to_keep { - return; // No need to truncate if the current size is already less than or equal to total_to_keep - } - - let items_to_remove = len - total_to_keep; - let remove_start_index = (len - items_to_remove - end_keep) as usize; - let remove_end_index = (len - end_keep) as usize; - self.stack.drain(remove_start_index..remove_end_index); - } - - #[inline(always)] - pub(crate) fn push(&mut self, value: RawWasmValue) { - self.stack.push(value); - } - - #[inline] - pub(crate) fn last_mut(&mut self) -> Result<&mut RawWasmValue> { - match self.stack.last_mut() { - Some(v) => Ok(v), - None => { - cold(); - Err(Error::ValueStackUnderflow) - } - } - } - - #[inline] - pub(crate) fn last(&self) -> Result<&RawWasmValue> { - match self.stack.last() { - Some(v) => Ok(v), - None => { - cold(); - Err(Error::ValueStackUnderflow) - } - } - } - - #[inline(always)] - pub(crate) fn pop(&mut self) -> Result { - match self.stack.pop() { - Some(v) => Ok(v), - None => { - cold(); - Err(Error::ValueStackUnderflow) - } - } - } - - #[inline] - pub(crate) fn pop_params(&mut self, types: &[ValType]) -> Result> { - Ok(self.pop_n_rev(types.len())?.zip(types.iter()).map(|(v, ty)| v.attach_type(*ty)).collect()) - } - - #[inline] - pub(crate) fn break_to(&mut self, new_stack_size: u32, result_count: u8) { - let start = new_stack_size as usize; - let end = self.stack.len() - result_count as usize; - self.stack.drain(start..end); - } - - #[inline] - pub(crate) fn last_n(&self, n: usize) -> Result<&[RawWasmValue]> { - let len = self.stack.len(); - if unlikely(len < n) { - return Err(Error::ValueStackUnderflow); - } - Ok(&self.stack[len - n..len]) - } - - #[inline] - pub(crate) fn pop_n_rev(&mut self, n: usize) -> Result> { - if unlikely(self.stack.len() < n) { - return Err(Error::ValueStackUnderflow); - } - Ok(self.stack.drain((self.stack.len() - n)..)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[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!(i32::from(stack.pop().unwrap()), 3); - assert_eq!(stack.len(), 2); - assert_eq!(i32::from(stack.pop().unwrap()), 2); - assert_eq!(stack.len(), 1); - assert_eq!(i32::from(stack.pop().unwrap()), 1); - assert_eq!(stack.len(), 0); - } - - #[test] - fn test_truncate_keep() { - macro_rules! test_macro { - ($( $n:expr, $end_keep:expr, $expected:expr ),*) => { - $( - let mut stack = ValueStack::default(); - stack.push(1.into()); - stack.push(2.into()); - stack.push(3.into()); - stack.push(4.into()); - stack.push(5.into()); - stack.truncate_keep($n, $end_keep); - assert_eq!(stack.len(), $expected); - )* - }; - } - - test_macro! { - 0, 0, 0, - 1, 0, 1, - 0, 1, 1, - 1, 1, 2, - 2, 1, 3, - 2, 2, 4 - } - } -} diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs deleted file mode 100644 index e5769bf..0000000 --- a/crates/tinywasm/src/runtime/value.rs +++ /dev/null @@ -1,118 +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([u8; 8]); - -impl Debug for RawWasmValue { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "RawWasmValue({})", 0) - } -} - -impl RawWasmValue { - #[inline(always)] - pub fn raw_value(&self) -> [u8; 8] { - self.0 - } - - #[inline] - pub fn attach_type(self, ty: ValType) -> WasmValue { - match ty { - ValType::I32 => WasmValue::I32(self.into()), - ValType::I64 => WasmValue::I64(self.into()), - ValType::F32 => WasmValue::F32(f32::from_bits(self.into())), - ValType::F64 => WasmValue::F64(f64::from_bits(self.into())), - ValType::RefExtern => match i64::from(self) { - v if v < 0 => WasmValue::RefNull(ValType::RefExtern), - addr => WasmValue::RefExtern(addr as u32), - }, - ValType::RefFunc => match i64::from(self) { - v if v < 0 => WasmValue::RefNull(ValType::RefFunc), - addr => WasmValue::RefFunc(addr as u32), - }, - } - } -} - -impl From for RawWasmValue { - #[inline] - fn from(v: WasmValue) -> Self { - match v { - WasmValue::I32(i) => Self::from(i), - WasmValue::I64(i) => Self::from(i), - WasmValue::F32(i) => Self::from(i), - WasmValue::F64(i) => Self::from(i), - WasmValue::RefExtern(v) => Self::from(v as i64), - WasmValue::RefFunc(v) => Self::from(v as i64), - WasmValue::RefNull(_) => Self::from(-1i64), - } - } -} - -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 { - #[inline] - fn from(value: $type) -> Self { - #[allow(clippy::redundant_closure_call)] - Self(u64::to_ne_bytes($to_raw(value))) - } - } - - // Implement From for $type - impl From for $type { - #[inline] - fn from(value: RawWasmValue) -> Self { - #[allow(clippy::redundant_closure_call)] - $from_raw(value.0) - } - } - }; -} - -// This all looks like a lot of extra steps, but the compiler will optimize it all away. -impl_from_raw_wasm_value!(i32, |x| x as u64, |x: [u8; 8]| i32::from_ne_bytes(x[0..4].try_into().unwrap())); -impl_from_raw_wasm_value!(i64, |x| x as u64, |x: [u8; 8]| i64::from_ne_bytes(x[0..8].try_into().unwrap())); -impl_from_raw_wasm_value!(u8, |x| x as u64, |x: [u8; 8]| u8::from_ne_bytes(x[0..1].try_into().unwrap())); -impl_from_raw_wasm_value!(u16, |x| x as u64, |x: [u8; 8]| u16::from_ne_bytes(x[0..2].try_into().unwrap())); -impl_from_raw_wasm_value!(u32, |x| x as u64, |x: [u8; 8]| u32::from_ne_bytes(x[0..4].try_into().unwrap())); -impl_from_raw_wasm_value!(u64, |x| x, |x: [u8; 8]| u64::from_ne_bytes(x[0..8].try_into().unwrap())); -impl_from_raw_wasm_value!(i8, |x| x as u64, |x: [u8; 8]| i8::from_ne_bytes(x[0..1].try_into().unwrap())); -impl_from_raw_wasm_value!(i16, |x| x as u64, |x: [u8; 8]| i16::from_ne_bytes(x[0..2].try_into().unwrap())); -impl_from_raw_wasm_value!(f32, |x| f32::to_bits(x) as u64, |x: [u8; 8]| f32::from_ne_bytes( - x[0..4].try_into().unwrap() -)); -impl_from_raw_wasm_value!(f64, f64::to_bits, |x: [u8; 8]| f64::from_bits(u64::from_ne_bytes( - x[0..8].try_into().unwrap() -))); - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_raw_wasm_value() { - macro_rules! test_macro { - ($( $ty:ty => $val:expr ),*) => { - $( - let raw: RawWasmValue = $val.into(); - let val: $ty = raw.into(); - assert_eq!(val, $val); - )* - }; - } - - test_macro! { - i32 => 0, i64 => 0, u8 => 0, u16 => 0, u32 => 0, u64 => 0, i8 => 0, i16 => 0, f32 => 0.0, f64 => 0.0, - i32 => i32::MIN, i64 => i64::MIN, u8 => u8::MIN, u16 => u16::MIN, u32 => u32::MIN, u64 => u64::MIN, i8 => i8::MIN, i16 => i16::MIN, f32 => f32::MIN, f64 => f64::MIN, - i32 => i32::MAX, i64 => i64::MAX, u8 => u8::MAX, u16 => u16::MAX, u32 => u32::MAX, u64 => u64::MAX, i8 => i8::MAX, i16 => i16::MAX, f32 => f32::MAX, f64 => f64::MAX - } - } -} 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/element.rs b/crates/tinywasm/src/store/element.rs index 6563dff..40301a5 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -9,11 +9,15 @@ use tinywasm_types::*; pub(crate) struct ElementInstance { pub(crate) kind: ElementKind, pub(crate) items: Option>, // none is the element was dropped - _owner: ModuleInstanceAddr, // index into store.module_instances + 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/global.rs b/crates/tinywasm/src/store/global.rs index 6cc778c..b7a47d6 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -1,73 +1,19 @@ +use crate::interpreter::TinyWasmValue; use core::cell::Cell; - -use alloc::{format, string::ToString}; use tinywasm_types::*; -use crate::{runtime::RawWasmValue, unlikely, Error, Result}; - /// A WebAssembly Global Instance /// /// See #[derive(Debug)] pub(crate) struct GlobalInstance { - pub(crate) value: Cell, + 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: RawWasmValue, owner: ModuleInstanceAddr) -> Self { + pub(crate) fn new(ty: GlobalType, value: TinyWasmValue, owner: ModuleInstanceAddr) -> Self { Self { ty, value: value.into(), _owner: owner } } - - #[inline] - pub(crate) fn get(&self) -> WasmValue { - self.value.get().attach_type(self.ty.ty) - } - - pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { - if unlikely(val.val_type() != self.ty.ty) { - return Err(Error::Other(format!( - "global type mismatch: expected {:?}, got {:?}", - self.ty.ty, - val.val_type() - ))); - } - - if unlikely(!self.ty.mutable) { - return Err(Error::Other("global is immutable".to_string())); - } - - self.value.set(val.into()); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_global_instance_get_set() { - let global_type = GlobalType { ty: ValType::I32, mutable: true }; - let initial_value = RawWasmValue::from(10i32); - let owner = 0; - - let mut global_instance = GlobalInstance::new(global_type, initial_value, owner); - - // Test `get` - assert_eq!(global_instance.get(), WasmValue::I32(10), "global value should be 10"); - - // Test `set` with correct type - assert!(global_instance.set(WasmValue::I32(20)).is_ok(), "set should succeed"); - assert_eq!(global_instance.get(), WasmValue::I32(20), "global value should be 20"); - - // Test `set` with incorrect type - assert!(matches!(global_instance.set(WasmValue::F32(1.0)), Err(Error::Other(_))), "set should fail"); - - // Test `set` on immutable global - let immutable_global_type = GlobalType { ty: ValType::I32, mutable: false }; - let mut immutable_global_instance = GlobalInstance::new(immutable_global_type, initial_value, owner); - assert!(matches!(immutable_global_instance.set(WasmValue::I32(30)), Err(Error::Other(_)))); - } } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 5ef4366..b86435d 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -2,7 +2,7 @@ use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{MemoryType, ModuleInstanceAddr}; -use crate::{log, Error, Result}; +use crate::{cold, log, Error, Result}; const PAGE_SIZE: usize = 65536; const MAX_PAGES: usize = 65536; @@ -32,6 +32,11 @@ impl MemoryInstance { } } + #[inline(always)] + pub(crate) fn len(&self) -> usize { + self.data.len() + } + #[inline(never)] #[cold] fn trap_oob(&self, addr: usize, len: usize) -> Error { @@ -40,13 +45,14 @@ impl MemoryInstance { 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(()) } @@ -57,17 +63,18 @@ impl MemoryInstance { 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]) } - // this is a workaround since we can't use generic const expressions yet (https://github.com/rust-lang/rust/issues/76560) pub(crate) fn load_as>(&self, addr: usize) -> Result { let Some(end) = addr.checked_add(SIZE) else { return Err(self.trap_oob(addr, SIZE)); @@ -76,17 +83,11 @@ impl MemoryInstance { if end > self.data.len() { return Err(self.trap_oob(addr, SIZE)); } - let val = T::from_le_bytes(match self.data[addr..end].try_into() { - Ok(bytes) => bytes, - Err(_) => unreachable!("checked bounds above"), - }); - - Ok(val) - } - #[inline] - pub(crate) fn page_count(&self) -> usize { - self.page_count + 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<()> { @@ -94,8 +95,7 @@ impl MemoryInstance { if end > self.data.len() { return Err(self.trap_oob(addr, len)); } - - self.data[addr..end].fill(val); + self.data[addr..end].fill_with(|| val); Ok(()) } @@ -127,15 +127,13 @@ impl MemoryInstance { Ok(()) } + #[inline] pub(crate) fn grow(&mut self, pages_delta: i32) -> Option { - let current_pages = self.page_count(); + 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 { - return None; - } - - if new_pages as usize > self.max_pages() { + if new_pages < 0 || new_pages > MAX_PAGES as i64 || new_pages as usize > self.max_pages() { return None; } @@ -145,20 +143,26 @@ impl MemoryInstance { } // Zero initialize the new pages - self.data.resize(new_size, 0); + self.data.reserve_exact(new_size); + self.data.resize_with(new_size, Default::default); self.page_count = new_pages as usize; - debug_assert!(current_pages <= i32::MAX as usize, "page count should never be greater than i32::MAX"); 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 { +pub(crate) trait MemLoadable: Sized + Copy { /// Load a value from memory - fn from_le_bytes(bytes: [u8; T]) -> Self; + fn from_le_bytes(bytes: [u8; N]) -> Self; } -macro_rules! impl_mem_loadable_for_primitive { +macro_rules! impl_mem_traits { ($($type:ty, $size:expr),*) => { $( impl MemLoadable<$size> for $type { @@ -167,13 +171,18 @@ macro_rules! impl_mem_loadable_for_primitive { <$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_loadable_for_primitive!( - u8, 1, i8, 1, u16, 2, i16, 2, u32, 4, i32, 4, f32, 4, u64, 8, i64, 8, f64, 8, u128, 16, i128, 16 -); +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 { @@ -232,9 +241,9 @@ mod memory_instance_tests { #[test] fn test_memory_grow() { let mut memory = create_test_memory(); - let original_pages = memory.page_count(); + let original_pages = memory.page_count; assert_eq!(memory.grow(1), Some(original_pages as i32)); - assert_eq!(memory.page_count(), original_pages + 1); + assert_eq!(memory.page_count, original_pages + 1); } #[test] diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 8c6e698..124b7dc 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,10 +1,10 @@ use alloc::{boxed::Box, format, string::ToString, vec::Vec}; -use core::cell::RefCell; +use core::fmt::Debug; use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; -use crate::runtime::{self, InterpreterRuntime, RawWasmValue}; -use crate::{Error, Function, ModuleInstance, Result, Trap}; +use crate::interpreter::{self, InterpreterRuntime, TinyWasmValue}; +use crate::{cold, Error, Function, ModuleInstance, Result, Trap}; mod data; mod element; @@ -27,7 +27,6 @@ static STORE_ID: AtomicUsize = AtomicUsize::new(0); /// 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, @@ -36,6 +35,17 @@ pub struct Store { 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, @@ -57,7 +67,7 @@ impl Store { } /// Create a new store with the given runtime - pub(crate) fn runtime(&self) -> runtime::InterpreterRuntime { + pub(crate) fn runtime(&self) -> interpreter::InterpreterRuntime { match self.runtime { Runtime::Default => InterpreterRuntime::default(), } @@ -77,15 +87,15 @@ impl Default for Store { } } -#[derive(Debug, 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) tables: Vec, + pub(crate) memories: Vec, pub(crate) globals: Vec, pub(crate) elements: Vec, pub(crate) datas: Vec, @@ -108,66 +118,99 @@ impl Store { #[cold] fn not_found_error(name: &str) -> Error { - Error::Other(format!("{} not found", name)) + 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) -> Result<&FunctionInstance> { - self.data.funcs.get(addr as usize).ok_or_else(|| Self::not_found_error("function")) + 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) -> Result<&RefCell> { - self.data.memories.get(addr as usize).ok_or_else(|| Self::not_found_error("memory")) + 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) -> Result<&RefCell> { - self.data.tables.get(addr as usize).ok_or_else(|| Self::not_found_error("table")) + pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { + &self.data.tables[addr as usize] } - /// Get the data at the actual index in the store + /// Get the table at the actual index in the store #[inline] - pub(crate) fn get_data(&self, addr: DataAddr) -> Result<&DataInstance> { - self.data.datas.get(addr as usize).ok_or_else(|| Self::not_found_error("data")) + 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) -> Result<&mut DataInstance> { - self.data.datas.get_mut(addr as usize).ok_or_else(|| Self::not_found_error("data")) + 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(&self, addr: ElemAddr) -> Result<&ElementInstance> { - self.data.elements.get(addr as usize).ok_or_else(|| Self::not_found_error("element")) + 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) -> Result<&GlobalInstance> { - self.data.globals.get(addr as usize).ok_or_else(|| Self::not_found_error("global")) + 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 - #[inline] - pub fn get_global_val(&self, addr: MemAddr) -> Result { - self.data - .globals - .get(addr as usize) - .ok_or_else(|| Self::not_found_error("global")) - .map(|global| global.value.get()) + #[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 - #[inline] - pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: RawWasmValue) -> Result<()> { - let global = self.data.globals.get(addr as usize).ok_or_else(|| Self::not_found_error("global")); - global.map(|global| global.value.set(value)) + #[doc(hidden)] + pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { + self.data.globals[addr as usize].value.set(value); } } @@ -189,7 +232,7 @@ impl Store { 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(RefCell::new(TableInstance::new(table, idx))); + self.data.tables.push(TableInstance::new(table, idx)); table_addrs.push((i + table_count) as TableAddr); } Ok(table_addrs) @@ -203,7 +246,7 @@ impl Store { if let MemoryArch::I64 = mem.arch { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } - self.data.memories.push(RefCell::new(MemoryInstance::new(mem, idx))); + self.data.memories.push(MemoryInstance::new(mem, idx)); mem_addrs.push((i + mem_count) as MemAddr); } Ok(mem_addrs) @@ -237,23 +280,17 @@ impl Store { 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 {} not found. This should have been caught by the validator", addr)) + 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 {} not found. This should have been caught by the validator", addr)) + Error::Other(format!("global {addr} not found. This should have been caught by the validator")) })?; - let val: i64 = self.data.globals[addr as usize].value.get().into(); - - // check if the global is actually a null reference - match val < 0 { - true => None, - false => Some(val as u32), - } + self.data.globals[addr as usize].value.get().unwrap_ref() } - _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {:?}", item))), + _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {item:?}"))), }; Ok(res) @@ -287,14 +324,14 @@ impl Store { // this one is active, so we need to initialize it (essentially a `table.init` instruction) ElementKind::Active { offset, table } => { - let offset = self.eval_i32_const(&offset)?; + let offset = self.eval_i32_const(offset)?; let table_addr = table_addrs .get(table as usize) .copied() - .ok_or_else(|| Error::Other(format!("table {} not found for element {}", table, i)))?; + .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 {} not found for element {}", table, i))); + 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, @@ -302,7 +339,7 @@ impl Store { // 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.borrow_mut().init_raw(offset, &init) { + if let Err(Error::Trap(trap)) = table.init(offset, &init) { return Ok((elem_addrs.into_boxed_slice(), Some(trap))); } @@ -331,21 +368,16 @@ impl Store { for (i, data) in datas.into_iter().enumerate() { let data_val = match data.kind { tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { - // a. Assert: memidx == 0 - if mem_addr != 0 { - return Err(Error::UnsupportedFeature("data segments for non-zero memories".to_string())); - } - let Some(mem_addr) = mem_addrs.get(mem_addr as usize) else { - return Err(Error::Other(format!("memory {} not found for data segment {}", mem_addr, i))); + return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; - let offset = self.eval_i32_const(&offset)?; + let offset = self.eval_i32_const(offset)?; let Some(mem) = self.data.memories.get_mut(*mem_addr as usize) else { - return Err(Error::Other(format!("memory {} not found for data segment {}", mem_addr, i))); + return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; - match mem.borrow_mut().store(offset as usize, data.data.len(), &data.data) { + 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), @@ -362,13 +394,13 @@ impl Store { Ok((data_addrs.into_boxed_slice(), None)) } - pub(crate) fn add_global(&mut self, ty: GlobalType, value: RawWasmValue, idx: ModuleInstanceAddr) -> Result { + 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(RefCell::new(TableInstance::new(table, idx))); + self.data.tables.push(TableInstance::new(table, idx)); Ok(self.data.tables.len() as TableAddr - 1) } @@ -376,7 +408,7 @@ impl Store { if let MemoryArch::I64 = mem.arch { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } - self.data.memories.push(RefCell::new(MemoryInstance::new(mem, idx))); + self.data.memories.push(MemoryInstance::new(mem, idx)); Ok(self.data.memories.len() as MemAddr - 1) } @@ -386,11 +418,11 @@ impl Store { } /// Evaluate a constant expression, only supporting i32 globals and i32.const - pub(crate) fn eval_i32_const(&self, const_instr: &tinywasm_types::ConstInstruction) -> Result { + 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) => i32::from(self.data.globals[*addr as usize].value.get()), + 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) @@ -402,27 +434,40 @@ impl Store { const_instr: &tinywasm_types::ConstInstruction, module_global_addrs: &[Addr], module_func_addrs: &[FuncAddr], - ) -> Result { + ) -> Result { use tinywasm_types::ConstInstruction::*; let val = match const_instr { - F32Const(f) => RawWasmValue::from(*f), - F64Const(f) => RawWasmValue::from(*f), - I32Const(i) => RawWasmValue::from(*i), - I64Const(i) => RawWasmValue::from(*i), + 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 {} not found. This should have been caught by the validator", addr)) + 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) => RawWasmValue::from(t.default_value()), - RefFunc(idx) => RawWasmValue::from(*module_func_addrs.get(*idx as usize).ok_or_else(|| { - Error::Other(format!("function {} not found. This should have been caught by the validator", idx)) - })?), + 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 index a094a14..0e08582 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,9 +1,9 @@ -use crate::{log, unlikely}; +use crate::log; use crate::{Error, Result, Trap}; use alloc::{vec, vec::Vec}; use tinywasm_types::*; -const MAX_TABLE_SIZE: u32 = 10000000; +const MAX_TABLE_SIZE: u32 = 10_000_000; /// A WebAssembly Table Instance /// @@ -20,33 +20,102 @@ impl TableInstance { 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(WasmValue::RefFunc).unwrap_or(WasmValue::RefNull(ValType::RefFunc)), - ValType::RefExtern => val.map(WasmValue::RefExtern).unwrap_or(WasmValue::RefNull(ValType::RefExtern)), + 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::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: Addr) -> Result<()> { - self.grow_to_fit(table_idx as usize + 1) - .map(|_| self.elements[table_idx as usize] = TableElement::Initialized(value)) + 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_to_fit(&mut self, new_size: usize) -> Result<()> { - if new_size > self.elements.len() { - if unlikely(new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize) { - return Err(crate::Trap::TableOutOfBounds { offset: new_size, len: 1, max: self.elements.len() }.into()); - } + 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() })); + } - self.elements.resize(new_size, TableElement::Uninitialized); + 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(()) } @@ -64,8 +133,7 @@ impl TableInstance { .expect("error initializing table: function not found. This should have been caught by the validator") } - // Initialize the table with the given elements - pub(crate) fn init_raw(&mut self, offset: i32, init: &[TableElement]) -> Result<()> { + 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() }) @@ -79,12 +147,6 @@ impl TableInstance { log::debug!("table: {:?}", self.elements); Ok(()) } - - // Initialize the table with the given elements (resolves function references) - pub(crate) fn init(&mut self, func_addrs: &[u32], offset: i32, init: &[TableElement]) -> Result<()> { - let init = init.iter().map(|item| item.map(|addr| self.resolve_func_ref(func_addrs, addr))).collect::>(); - self.init_raw(offset, &init) - } } #[derive(Debug, Clone, Copy)] @@ -139,16 +201,22 @@ mod tests { let kind = dummy_table_type(); let mut table_instance = TableInstance::new(kind, 0); - table_instance.set(0, 0).expect("Setting table element failed"); + 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(_)) => {} - _ => assert!(false, "get_wasm_val failed to return the correct WasmValue"), + _ => 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::UndefinedElement { .. })) => {} - _ => assert!(false, "get_wasm_val failed to handle undefined element correctly"), + Err(Error::Trap(Trap::TableOutOfBounds { .. })) => {} + _ => panic!("get_wasm_val failed to handle undefined element correctly"), } } @@ -157,7 +225,7 @@ mod tests { let kind = dummy_table_type(); let mut table_instance = TableInstance::new(kind, 0); - let result = table_instance.set(0, 1); + let result = table_instance.set(0, TableElement::Initialized(1)); assert!(result.is_ok(), "Setting table element failed"); let elem = table_instance.get(0); @@ -167,26 +235,13 @@ mod tests { ); } - #[test] - fn test_table_grow_and_fit() { - let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); - - let result = table_instance.set(15, 1); - assert!(result.is_ok(), "Table grow on set failed"); - - let size = table_instance.size(); - assert!(size >= 16, "Table did not grow to expected size"); - } - #[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 func_addrs = vec![0, 1, 2, 3, 4]; - let result = table_instance.init(&func_addrs, 0, &init_elements); + let result = table_instance.init(0, &init_elements); assert!(result.is_ok(), "Initializing table with elements failed"); diff --git a/crates/tinywasm/tests/generated/README.md b/crates/tinywasm/tests/generated/README.md deleted file mode 100644 index 40acee5..0000000 --- a/crates/tinywasm/tests/generated/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# WebAssembly 1.0 Test Results (out of 20254 tests) - -![](./progress-mvp.svg) - -# WebAssembly 2.0 Test Results (out of 27883 tests) - -![](./progress-2.0.svg) diff --git a/crates/tinywasm/tests/generated/progress-2.0.svg b/crates/tinywasm/tests/generated/progress-2.0.svg deleted file mode 100644 index f5562a1..0000000 --- a/crates/tinywasm/tests/generated/progress-2.0.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - -WebAssembly 2.0 Test Suite - - -Tests Passed - - -TinyWasm Version - - - - - - - - - -0 - - - -5000 - - - -10000 - - - -15000 - - - -20000 - - - -25000 - - - - -v0.3.0 (26722) - - - -v0.4.0 (27549) - - - -v0.4.1 (27551) - - - -v0.5.0 (27551) - - - - - - - diff --git a/crates/tinywasm/tests/generated/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg deleted file mode 100644 index 3501681..0000000 --- a/crates/tinywasm/tests/generated/progress-mvp.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - -WebAssembly 1.0 Test Suite - - -Tests Passed - - -TinyWasm Version - - - - - - - - -0 - - - -5000 - - - -10000 - - - -15000 - - - -20000 - - - - -v0.0.4 (9258) - - - -v0.4.0 (20254) - - - - - - - - - - - diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/wasm-1.csv similarity index 73% rename from crates/tinywasm/tests/generated/2.0.csv rename to crates/tinywasm/tests/generated/wasm-1.csv index 11dd840..64f92d1 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/wasm-1.csv @@ -3,3 +3,5 @@ 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/mvp.csv b/crates/tinywasm/tests/generated/wasm-2.csv similarity index 81% rename from crates/tinywasm/tests/generated/mvp.csv rename to crates/tinywasm/tests/generated/wasm-2.csv index 6cf7fea..0e43692 100644 --- a/crates/tinywasm/tests/generated/mvp.csv +++ b/crates/tinywasm/tests/generated/wasm-2.csv @@ -8,3 +8,5 @@ 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/test-mvp.rs b/crates/tinywasm/tests/test-wasm-1.rs similarity index 57% rename from crates/tinywasm/tests/test-mvp.rs rename to crates/tinywasm/tests/test-wasm-1.rs index a1c2067..77694c5 100644 --- a/crates/tinywasm/tests/test-mvp.rs +++ b/crates/tinywasm/tests/test-wasm-1.rs @@ -1,30 +1,20 @@ mod testsuite; -use _log as log; use eyre::{eyre, Result}; use owo_colors::OwoColorize; use testsuite::TestSuite; fn main() -> Result<()> { - let args = std::env::args().collect::>(); - if args.len() < 2 || args[1] != "--enable" { - return Ok(()); - } - - test_mvp() -} - -fn test_mvp() -> 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/mvp.csv", env!("CARGO_PKG_VERSION"))?; + 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); + println!("\n\npassed all tests:\n{test_suite:#?}"); Ok(()) } } diff --git a/crates/tinywasm/tests/test-two.rs b/crates/tinywasm/tests/test-wasm-2.rs similarity index 58% rename from crates/tinywasm/tests/test-two.rs rename to crates/tinywasm/tests/test-wasm-2.rs index b5ed9c8..bd1afe6 100644 --- a/crates/tinywasm/tests/test-two.rs +++ b/crates/tinywasm/tests/test-wasm-2.rs @@ -1,30 +1,20 @@ mod testsuite; -use _log as log; use eyre::{eyre, Result}; use owo_colors::OwoColorize; use testsuite::TestSuite; fn main() -> Result<()> { - let args = std::env::args().collect::>(); - if args.len() < 2 || args[1] != "--enable" { - return Ok(()); - } - - test_2() -} - -fn test_2() -> 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/2.0.csv", env!("CARGO_PKG_VERSION"))?; + 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); + 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 index fa3af91..f79681c 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; -use _log as log; use eyre::{bail, eyre, Result}; use owo_colors::OwoColorize; use testsuite::TestSuite; @@ -9,11 +8,7 @@ mod testsuite; fn main() -> Result<()> { let args = std::env::args().collect::>(); - if args.len() < 2 || args[1] != "--enable" { - return Ok(()); - } - - if args.len() < 3 { + if args.len() < 2 { bail!("usage: cargo test-wast ") }; @@ -23,7 +18,7 @@ fn main() -> Result<()> { // 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[2]); + 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"))?; @@ -34,10 +29,10 @@ fn test_wast(wast_file: &str) -> Result<()> { TestSuite::set_log_level(log::LevelFilter::Debug); let args = std::env::args().collect::>(); - println!("args: {:?}", args); + println!("args: {args:?}"); let mut test_suite = TestSuite::new(); - println!("running wast file: {}", wast_file); + println!("running wast file: {wast_file}"); test_suite.run_paths(&[wast_file])?; @@ -47,7 +42,7 @@ fn test_wast(wast_file: &str) -> Result<()> { println!(); Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), test_suite,))) } else { - println!("\n\npassed all tests:\n{:#?}", test_suite); + 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 index 3e751c4..0c75e4c 100644 --- a/crates/tinywasm/tests/testsuite/indexmap.rs +++ b/crates/tinywasm/tests/testsuite/indexmap.rs @@ -21,31 +21,11 @@ where self.map.insert(key, value) } - pub fn get(&self, key: &K) -> Option<&V> { - self.map.get(key) - } - - pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { - self.map.get_mut(key) - } - pub fn iter(&self) -> impl Iterator { self.keys.iter().map(move |k| (k, self.map.get(k).unwrap())) } - pub fn len(&self) -> usize { - self.map.len() - } - - pub fn keys(&self) -> impl Iterator { - self.keys.iter() - } - pub fn values(&self) -> impl Iterator { self.map.values() } - - pub fn values_mut(&mut self) -> impl Iterator { - self.map.values_mut() - } } diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index 6223982..ca62d4e 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -1,6 +1,4 @@ -#![allow(dead_code)] // rust analyzer doesn't recognize that code is used by tests without harness - -use _log as log; +#![allow(unused)] use eyre::Result; use owo_colors::OwoColorize; use std::io::{BufRead, Seek, SeekFrom}; @@ -25,17 +23,9 @@ pub struct TestGroupResult { pub failed: usize, } -fn format_linecol(linecol: (usize, usize)) -> String { - format!("{}:{}", linecol.0 + 1, linecol.1 + 1) -} - pub struct TestSuite(BTreeMap, Vec); impl TestSuite { - pub fn skip(&mut self, groups: &[&str]) { - self.1.extend(groups.iter().map(|s| s.to_string())); - } - pub fn set_log_level(level: log::LevelFilter) { pretty_env_logger::formatted_builder().filter_level(level).init(); } @@ -92,7 +82,7 @@ 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; @@ -101,7 +91,7 @@ impl TestSuite { } 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(()) @@ -111,10 +101,10 @@ impl TestSuite { fn link(name: &str, file: &str, line: Option) -> String { let (path, name) = match line { None => (file.to_string(), name.to_owned()), - Some(line) => (format!("{}:{}:0", file, line), (format!("{}:{}", name, line))), + Some(line) => (format!("{file}:{line}:0"), (format!("{name}:{line}"))), }; - format!("\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\", path, name) + format!("\x1b]8;;file://{path}\x1b\\{name}\x1b]8;;\x1b\\") } impl Debug for TestSuite { diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index 1de633f..ca788fb 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -1,9 +1,7 @@ -/// Here be dragons (this file is in need of a big refactor) use crate::testsuite::util::*; use std::{borrow::Cow, collections::HashMap}; use super::TestSuite; -use _log as log; use eyre::{eyre, Result}; use log::{debug, error, info}; use tinywasm::{Extern, Imports, ModuleInstance}; @@ -72,11 +70,11 @@ impl RegisteredModules { impl TestSuite { pub fn run_paths(&mut self, tests: &[&str]) -> Result<()> { - tests.iter().for_each(|group| { + 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"); - }); + } Ok(()) } @@ -87,7 +85,7 @@ impl TestSuite { let table = Extern::table(TableType::new(ValType::RefFunc, 10, Some(20)), WasmValue::default_for(ValType::RefFunc)); - let print = Extern::typed_func(|_ctx: tinywasm::FuncContext, _: ()| { + let print = Extern::typed_func(|_ctx: tinywasm::FuncContext, (): ()| { log::debug!("print"); Ok(()) }); @@ -145,12 +143,13 @@ impl TestSuite { Ok(imports) } - pub fn run_spec_group(&mut self, tests: &[&str]) -> Result<()> { - tests.iter().for_each(|group| { + 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()) { + if self.1.contains(&(*group).to_string()) { info!("skipping group: {}", group); - self.test_group(&format!("{} (skipped)", group), group); + self.test_group(&format!("{group} (skipped)"), group); return; } @@ -178,20 +177,23 @@ impl TestSuite { 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::*; + 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!("Register({})", i), + &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(())); + test_group.add_result(&format!("Register({i})"), span.linecol_in(wast), Ok(())); } Wat(module) => { @@ -213,12 +215,12 @@ impl TestSuite { 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(|_| ())); + test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast), result.map(|_| ())); } - AssertMalformed { span, mut module, message: _ } => { + AssertMalformed { span, mut module, message } => { let Ok(module) = module.encode() else { - test_group.add_result(&format!("AssertMalformed({})", i), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertMalformed({i})"), span.linecol_in(wast), Ok(())); continue; }; @@ -227,22 +229,35 @@ impl TestSuite { .and_then(|res| res); test_group.add_result( - &format!("AssertMalformed({})", i), + &format!("AssertMalformed({i})"), span.linecol_in(wast), match res { - Ok(_) => Err(eyre!("expected module to be malformed")), + 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: _ } => { + AssertInvalid { span, mut module, message } => { + if ["multiple memories"].contains(&message) { + test_group.add_result(&format!("AssertInvalid({i})"), span.linecol_in(wast), Ok(())); + continue; + } + 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), + &format!("AssertInvalid({i})"), span.linecol_in(wast), match res { Ok(_) => Err(eyre!("expected module to be invalid")), @@ -259,23 +274,23 @@ impl TestSuite { let Ok(Err(tinywasm::Error::Trap(trap))) = res else { test_group.add_result( - &format!("AssertExhaustion({})", i), + &format!("AssertExhaustion({i})"), span.linecol_in(wast), Err(eyre!("expected trap")), ); continue; }; - if trap.message() != message { + if !message.starts_with(trap.message()) { test_group.add_result( - &format!("AssertExhaustion({})", i), + &format!("AssertExhaustion({i})"), span.linecol_in(wast), Err(eyre!("expected trap: {}, got: {}", message, trap.message())), ); continue; } - test_group.add_result(&format!("AssertExhaustion({})", i), span.linecol_in(wast), Ok(())); + test_group.add_result(&format!("AssertExhaustion({i})"), span.linecol_in(wast), Ok(())); } AssertTrap { exec, message, span } => { @@ -304,29 +319,29 @@ impl TestSuite { match res { Err(err) => test_group.add_result( - &format!("AssertTrap({})", i), + &format!("AssertTrap({i})"), span.linecol_in(wast), Err(eyre!("test panicked: {:?}", try_downcast_panic(err))), ), Ok(Err(tinywasm::Error::Trap(trap))) => { - if trap.message() != message { + if !message.starts_with(trap.message()) { test_group.add_result( - &format!("AssertTrap({})", i), + &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(())) + test_group.add_result(&format!("AssertTrap({i})"), span.linecol_in(wast), Ok(())); } Ok(Err(err)) => test_group.add_result( - &format!("AssertTrap({})", i), + &format!("AssertTrap({i})"), span.linecol_in(wast), Err(eyre!("expected trap, {}, got: {:?}", message, err)), ), Ok(Ok(())) => test_group.add_result( - &format!("AssertTrap({})", i), + &format!("AssertTrap({i})"), span.linecol_in(wast), Err(eyre!("expected trap {}, got Ok", message)), ), @@ -343,29 +358,29 @@ impl TestSuite { match res { Err(err) => test_group.add_result( - &format!("AssertUnlinkable({})", i), + &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), + &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(())) + test_group.add_result(&format!("AssertUnlinkable({i})"), span.linecol_in(wast), Ok(())); } Ok(Err(err)) => test_group.add_result( - &format!("AssertUnlinkable({})", i), + &format!("AssertUnlinkable({i})"), span.linecol_in(wast), Err(eyre!("expected linker error, {}, got: {:?}", message, err)), ), Ok(Ok(_)) => test_group.add_result( - &format!("AssertUnlinkable({})", i), + &format!("AssertUnlinkable({i})"), span.linecol_in(wast), Err(eyre!("expected linker error {}, got Ok", message)), ), @@ -386,7 +401,7 @@ impl TestSuite { }); let res = res.map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))).and_then(|r| r); - test_group.add_result(&format!("Invoke({}-{})", name, i), span.linecol_in(wast), res); + test_group.add_result(&format!("Invoke({name}-{i})"), span.linecol_in(wast), res); } AssertReturn { span, exec, results } => { @@ -399,7 +414,7 @@ impl TestSuite { let module = registered_modules.get(module_id, &store); let Some(module) = module else { test_group.add_result( - &format!("AssertReturn(unsupported-{})", i), + &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast), Err(eyre!("no module to get global from")), ); @@ -407,15 +422,13 @@ impl TestSuite { }; let module_global = match match module.export_addr(global) { - Some(ExternVal::Global(addr)) => { - store.get_global_val(addr).map_err(|_| eyre!("failed to get 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), + &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast), Err(eyre!("failed to get global: {:?}", err)), ); @@ -427,7 +440,7 @@ impl TestSuite { if !module_global.eq_loose(expected) { test_group.add_result( - &format!("AssertReturn(unsupported-{})", i), + &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast), Err(eyre!("global value did not match: {:?} != {:?}", module_global, expected)), ); @@ -435,7 +448,7 @@ impl TestSuite { } test_group.add_result( - &format!("AssertReturn({}-{})", global, i), + &format!("AssertReturn({global}-{i})"), span.linecol_in(wast), Ok(()), ); @@ -448,7 +461,7 @@ impl TestSuite { Ok(invoke) => invoke, Err(err) => { test_group.add_result( - &format!("AssertReturn(unsupported-{})", i), + &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast), Err(eyre!("unsupported directive: {:?}", err)), ); @@ -483,10 +496,10 @@ impl TestSuite { }); let res = res.map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))).and_then(|r| r); - test_group.add_result(&format!("AssertReturn({}-{})", invoke_name, i), span.linecol_in(wast), res); + test_group.add_result(&format!("AssertReturn({invoke_name}-{i})"), span.linecol_in(wast), res); } _ => test_group.add_result( - &format!("Unknown({})", i), + &format!("Unknown({i})"), span.linecol_in(wast), Err(eyre!("unsupported directive")), ), diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 7b3ff1e..29f028b 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -2,10 +2,11 @@ use std::panic::{self, AssertUnwindSafe}; use eyre::{eyre, Result}; use tinywasm_types::{ModuleInstanceAddr, TinyWasmModule, ValType, WasmValue}; -use wast::QuoteWat; +use wast::{core::AbstractHeapType, QuoteWat}; pub fn try_downcast_panic(panic: Box) -> String { - let info = panic.downcast_ref::().or(None).map(|p| p.to_string()).clone(); + #[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); @@ -96,7 +97,7 @@ fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result WasmValue::F32(f32::from_bits(f.bits)), F64(f) => WasmValue::F64(f64::from_bits(f.bits)), @@ -104,28 +105,36 @@ fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result WasmValue::I64(i), RefExtern(v) => WasmValue::RefExtern(v), RefNull(t) => match t { - wast::core::HeapType::Func => WasmValue::RefNull(ValType::RefFunc), - wast::core::HeapType::Extern => WasmValue::RefNull(ValType::RefExtern), + 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)), }) } -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 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), RefNull(t) => match t { - Some(wast::core::HeapType::Func) => WasmValue::RefNull(ValType::RefFunc), - Some(wast::core::HeapType::Extern) => WasmValue::RefNull(ValType::RefExtern), + 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 { @@ -186,7 +195,7 @@ fn nanpattern2tinywasmvalue(arg: wast::core::NanPattern) -> Result T::canonical_nan(), ArithmeticNan => T::arithmetic_nan(), diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 33271d5..b643964 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -6,9 +6,10 @@ edition.workspace=true license.workspace=true authors.workspace=true repository.workspace=true +rust-version.workspace=true [dependencies] -log={version="0.4", optional=true} +log={workspace=true, optional=true} rkyv={version="0.7", optional=true, default-features=false, features=["size_32", "validation"]} bytecheck={version="0.7", optional=true} diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 52146e7..398b616 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -54,20 +54,16 @@ extern crate std; impl std::error::Error for TwasmError {} impl TinyWasmModule { - /// Creates a TinyWasmModule from a slice of bytes. + /// 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| { - crate::log::error!("Invalid archive: {}", _e); - TwasmError::InvalidArchive - })?; - + 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. + /// 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(); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 6290bb4..d77d66d 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,50 +1,6 @@ use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; use crate::{DataAddr, ElemAddr, MemAddr}; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] -pub enum BlockArgs { - Empty, - Type(ValType), - FuncType(u32), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] -/// A packed representation of BlockArgs -/// This is needed to keep the size of the Instruction enum small. -/// Sadly, using #[repr(u8)] on BlockArgs itself is not possible because of the FuncType variant. -pub struct BlockArgsPacked([u8; 5]); // Modifying this directly can cause runtime errors, but no UB - -impl From for BlockArgsPacked { - fn from(args: BlockArgs) -> Self { - let mut packed = [0; 5]; - match args { - BlockArgs::Empty => packed[0] = 0, - BlockArgs::Type(t) => { - packed[0] = 1; - packed[1] = t.to_byte(); - } - BlockArgs::FuncType(t) => { - packed[0] = 2; - packed[1..].copy_from_slice(&t.to_le_bytes()); - } - } - Self(packed) - } -} - -impl From for BlockArgs { - fn from(packed: BlockArgsPacked) -> Self { - match packed.0[0] { - 0 => BlockArgs::Empty, - 1 => BlockArgs::Type(ValType::from_byte(packed.0[1]).unwrap()), - 2 => BlockArgs::FuncType(u32::from_le_bytes(packed.0[1..].try_into().unwrap())), - _ => unreachable!(), - } - } -} - /// Represents a memory immediate in a WebAssembly memory instruction. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] @@ -85,54 +41,91 @@ pub enum ConstInstruction { #[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] -#[non_exhaustive] pub enum Instruction { // > Custom Instructions - BrLabel(LabelAddr), - // LocalGet + I32Const + I32Add - // One of the most common patterns in the Rust compiler output - I32LocalGetConstAdd(LocalAddr, i32), - // LocalGet + I32Const + I32Store => I32LocalGetConstStore + I32Const - // Also common, helps us skip the stack entirely. - // Has to be followed by an I32Const instruction - I32StoreLocal { local: LocalAddr, const_i32: i32, 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), + // // 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 // See Unreachable, Nop, - Block(BlockArgs, EndOffset), - Loop(BlockArgs, EndOffset), - If(BlockArgsPacked, ElseOffset, EndOffset), // If else offset is 0 if there is no else block + + Block(EndOffset), + BlockWithType(ValType, EndOffset), + BlockWithFuncType(TypeAddr, EndOffset), + + Loop(EndOffset), + LoopWithType(ValType, EndOffset), + LoopWithFuncType(TypeAddr, EndOffset), + + If(ElseOffset, EndOffset), + IfWithType(ValType, ElseOffset, EndOffset), + IfWithFuncType(TypeAddr, ElseOffset, EndOffset), + Else(EndOffset), EndBlockFrame, Br(LabelAddr), BrIf(LabelAddr), BrTable(BrTableDefault, BrTableLen), // has to be followed by multiple BrLabel instructions + BrLabel(LabelAddr), Return, Call(FuncAddr), CallIndirect(TypeAddr, TableAddr), - + ReturnCall(FuncAddr), + ReturnCallIndirect(TypeAddr, TableAddr), + // > Parametric Instructions // See - Drop, - Select(Option), + Drop32, + Drop64, + Drop128, + DropRef, + + 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), + GlobalSet32(GlobalAddr), + GlobalSet64(GlobalAddr), + GlobalSet128(GlobalAddr), + GlobalSetRef(GlobalAddr), // > Memory Instructions I32Load { offset: u64, mem_addr: MemAddr }, @@ -158,8 +151,8 @@ pub enum Instruction { I64Store8 { offset: u64, mem_addr: MemAddr }, I64Store16 { offset: u64, mem_addr: MemAddr }, I64Store32 { offset: u64, mem_addr: MemAddr }, - MemorySize(MemAddr, u8), - MemoryGrow(MemAddr, u8), + MemorySize(MemAddr), + MemoryGrow(MemAddr), // > Constants I32Const(i32), @@ -198,7 +191,7 @@ pub enum Instruction { I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, // > Table Instructions - TableInit(TableAddr, ElemAddr), + TableInit(ElemAddr, TableAddr), TableGet(TableAddr), TableSet(TableAddr), TableCopy { from: TableAddr, to: TableAddr }, @@ -211,45 +204,5 @@ pub enum Instruction { MemoryCopy(MemAddr, MemAddr), MemoryFill(MemAddr), DataDrop(DataAddr), -} - -#[cfg(test)] -mod test_blockargs_packed { - use super::*; - - #[test] - fn test_empty() { - let packed: BlockArgsPacked = BlockArgs::Empty.into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Empty); - } - - #[test] - fn test_val_type_i32() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::I32).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::I32)); - } - - #[test] - fn test_val_type_i64() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::I64).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::I64)); - } - - #[test] - fn test_val_type_f32() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::F32).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::F32)); - } - - #[test] - fn test_val_type_f64() { - let packed: BlockArgsPacked = BlockArgs::Type(ValType::F64).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::Type(ValType::F64)); - } - - #[test] - fn test_func_type() { - let packed: BlockArgsPacked = BlockArgs::FuncType(0x12345678).into(); - assert_eq!(BlockArgs::from(packed), BlockArgs::FuncType(0x12345678)); - } + ElemDrop(ElemAddr), } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 5e38bfc..94dc94d 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -14,14 +14,19 @@ use core::{fmt::Debug, ops::Range}; // 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"))] -#[macro_use] +#[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; @@ -32,11 +37,11 @@ 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. +/// 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 { @@ -125,7 +130,7 @@ 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; @@ -172,11 +177,30 @@ pub struct FuncType { pub results: Box<[ValType]>, } -#[derive(Debug, Clone, PartialEq)] +#[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, +} + +#[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: Box<[ValType]>, + pub locals: ValueCounts, + pub params: ValueCountsSmall, pub ty: FuncType, } @@ -307,7 +331,7 @@ pub enum ElementKind { Declared, } -#[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 ElementItem { Func(FuncAddr), diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 28ca6e2..416f678 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -17,7 +17,8 @@ pub enum WasmValue { /// A 64-bit float. F64(f64), // /// A 128-bit vector - // V128(u128), + V128(u128), + RefExtern(ExternAddr), RefFunc(FuncAddr), RefNull(ValType), @@ -31,7 +32,6 @@ impl WasmValue { Self::I64(i) => ConstInstruction::I64Const(*i), Self::F32(i) => ConstInstruction::F32Const(*i), Self::F64(i) => ConstInstruction::F64Const(*i), - Self::RefFunc(i) => ConstInstruction::RefFunc(*i), Self::RefNull(ty) => ConstInstruction::RefNull(*ty), @@ -48,7 +48,7 @@ impl WasmValue { ValType::I64 => Self::I64(0), ValType::F32 => Self::F32(0.0), ValType::F64 => Self::F64(0.0), - // ValType::V128 => Self::V128(0), + ValType::V128 => Self::V128(0), ValType::RefFunc => Self::RefNull(ValType::RefFunc), ValType::RefExtern => Self::RefNull(ValType::RefExtern), } @@ -87,14 +87,14 @@ 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.half({:?})", i), - WasmValue::RefExtern(addr) => write!(f, "ref.extern({:?})", addr), - WasmValue::RefFunc(addr) => write!(f, "ref.func({:?})", addr), - WasmValue::RefNull(ty) => write!(f, "ref.null({:?})", ty), + WasmValue::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:?})"), } } } @@ -108,7 +108,7 @@ impl WasmValue { Self::I64(_) => ValType::I64, Self::F32(_) => ValType::F32, Self::F64(_) => ValType::F64, - // Self::V128(_) => ValType::V128, + Self::V128(_) => ValType::V128, Self::RefExtern(_) => ValType::RefExtern, Self::RefFunc(_) => ValType::RefFunc, Self::RefNull(ty) => *ty, @@ -129,7 +129,7 @@ pub enum ValType { /// A 64-bit float. F64, /// A 128-bit vector - // V128, + V128, /// A reference to a function. RefFunc, /// A reference to an external value. @@ -142,27 +142,9 @@ impl ValType { WasmValue::default_for(*self) } - pub(crate) fn to_byte(self) -> u8 { - match self { - ValType::I32 => 0x7F, - ValType::I64 => 0x7E, - ValType::F32 => 0x7D, - ValType::F64 => 0x7C, - ValType::RefFunc => 0x70, - ValType::RefExtern => 0x6F, - } - } - - pub(crate) fn from_byte(byte: u8) -> Option { - match byte { - 0x7F => Some(ValType::I32), - 0x7E => Some(ValType::I64), - 0x7D => Some(ValType::F32), - 0x7C => Some(ValType::F64), - 0x70 => Some(ValType::RefFunc), - 0x6F => Some(ValType::RefExtern), - _ => None, - } + #[inline] + pub fn is_simd(&self) -> bool { + matches!(self, ValType::V128) } } @@ -195,9 +177,4 @@ macro_rules! impl_conversion_for_wasmvalue { } } -impl_conversion_for_wasmvalue! { - i32 => I32, - i64 => I64, - f32 => F32, - f64 => F64 -} +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 5a59ecd..8edcb8a 100644 --- a/crates/wasm-testsuite/Cargo.toml +++ b/crates/wasm-testsuite/Cargo.toml @@ -1,12 +1,13 @@ [package] name="wasm-testsuite" -version="0.4.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.3", features=["include-exclude"]} +rust-embed={version="8.4", features=["include-exclude"]} diff --git a/crates/wasm-testsuite/data b/crates/wasm-testsuite/data index 9df2c8a..ae5a669 160000 --- a/crates/wasm-testsuite/data +++ b/crates/wasm-testsuite/data @@ -1 +1 @@ -Subproject commit 9df2c8a23c4d2f889c2c1a62e5fb9b744579efc5 +Subproject commit ae5a66933070b705dde56c2a71bf3fbc33282864 diff --git a/crates/wasm-testsuite/lib.rs b/crates/wasm-testsuite/lib.rs index e3352fb..1f42664 100644 --- a/crates/wasm-testsuite/lib.rs +++ b/crates/wasm-testsuite/lib.rs @@ -18,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","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"]; +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 index 4dd714a..ea82c51 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use tinywasm::{parser::Parser, types::TinyWasmModule, Module, Store}; const WASM: &str = r#" diff --git a/examples/linking.rs b/examples/linking.rs index f278266..d215898 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use tinywasm::{Module, Store}; const WASM_ADD: &str = r#" diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 9c1f77d..fcfd01c 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -15,7 +15,7 @@ for bin in "${bins[@]}"; do RUSTFLAGS="-C target-feature=$features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" - wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wasm" -O3 --enable-bulk-memory --enable-reference-types --enable-mutable-globals + 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" diff --git a/examples/rust/src/fibonacci.rs b/examples/rust/src/fibonacci.rs index 8de496d..b847ad5 100644 --- a/examples/rust/src/fibonacci.rs +++ b/examples/rust/src/fibonacci.rs @@ -1,5 +1,4 @@ #![no_main] -#![allow(non_snake_case)] #[no_mangle] pub extern "C" fn fibonacci(n: i32) -> i32 { diff --git a/examples/simple.rs b/examples/simple.rs index 6f79c0f..e0842dd 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,4 +1,4 @@ -use color_eyre::eyre::Result; +use eyre::Result; use tinywasm::{Module, Store}; const WASM: &str = r#" diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 921c4a3..169fed5 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -1,4 +1,6 @@ -use color_eyre::eyre::{eyre, Result}; +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. @@ -14,8 +16,8 @@ use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; /// /// This requires the `wasm32-unknown-unknown` target, `binaryen` and `wabt` to be installed. /// `rustup target add wasm32-unknown-unknown`. -/// https://github.com/WebAssembly/wabt -/// https://github.com/WebAssembly/binaryen +/// +/// /// fn main() -> Result<()> { pretty_env_logger::init(); @@ -32,6 +34,7 @@ fn main() -> Result<()> { 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(()); } @@ -40,6 +43,7 @@ fn main() -> Result<()> { "printi32" => printi32()?, "fibonacci" => fibonacci()?, "tinywasm" => tinywasm()?, + "argon2id" => argon2id()?, "all" => { println!("Running all examples"); println!("\nhello.wasm:"); @@ -50,6 +54,8 @@ fn main() -> Result<()> { fibonacci()?; println!("\ntinywasm.wasm:"); tinywasm()?; + println!("argon2id.wasm:"); + argon2id()?; } _ => {} } @@ -62,19 +68,13 @@ fn tinywasm() -> Result<()> { 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))?; + 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, ())?; - + hello.call(&mut store, black_box(()))?; + hello.call(&mut store, black_box(()))?; + hello.call(&mut store, black_box(()))?; Ok(()) } @@ -91,7 +91,7 @@ fn hello() -> Result<()> { let ptr = args.0 as usize; let len = args.1 as usize; let string = mem.load_string(ptr, len)?; - println!("{}", string); + println!("{string}"); Ok(()) }), )?; @@ -116,7 +116,7 @@ fn printi32() -> Result<()> { "env", "printi32", Extern::typed_func(|_: FuncContext<'_>, x: i32| { - println!("{}", x); + println!("{x}"); Ok(()) }), )?; @@ -129,14 +129,25 @@ fn printi32() -> Result<()> { } fn fibonacci() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/fibonacci.wasm")?; + 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")?; - let n = 30; + let fibonacci = instance.exported_func::(&store, "fibonacci_recursive")?; + let n = 26; let result = fibonacci.call(&mut store, n)?; - println!("fibonacci({}) = {}", n, result); + 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/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/rust-toolchain.toml b/rust-toolchain.toml index a84b93a..c046a09 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel="nightly-2024-04-15" +channel="nightly" diff --git a/scripts/Cargo.toml b/scripts/Cargo.toml deleted file mode 100644 index 5217729..0000000 --- a/scripts/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name="scripts" -publish=false -edition.workspace=true - -[dependencies] -plotters={version="0.3"} -eyre={version="0.6"} diff --git a/scripts/src/bin/generate-charts/main.rs b/scripts/src/bin/generate-charts/main.rs deleted file mode 100644 index 1206e5f..0000000 --- a/scripts/src/bin/generate-charts/main.rs +++ /dev/null @@ -1,35 +0,0 @@ -mod progress; -use std::{path::PathBuf, str::FromStr}; - -use eyre::Result; - -fn main() -> Result<()> { - generate_charts() -} - -fn generate_charts() -> Result<()> { - let results_dir = PathBuf::from_str("./crates/tinywasm/tests/generated")?; - - // check if the folder exists - if !results_dir.exists() { - return Err(eyre::eyre!( - "This script should be run from the root of the project, and the test results should be generated first." - )); - } - - progress::create_progress_chart( - "WebAssembly 1.0 Test Suite", - &results_dir.join("mvp.csv"), - &results_dir.join("progress-mvp.svg"), - )?; - println!("created progress chart: {}", results_dir.join("progress-mvp.svg").display()); - - progress::create_progress_chart( - "WebAssembly 2.0 Test Suite", - &results_dir.join("2.0.csv"), - &results_dir.join("progress-2.0.svg"), - )?; - println!("created progress chart: {}", results_dir.join("progress-2.0.svg").display()); - - Ok(()) -} diff --git a/scripts/src/bin/generate-charts/progress.rs b/scripts/src/bin/generate-charts/progress.rs deleted file mode 100644 index 1ecc09c..0000000 --- a/scripts/src/bin/generate-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(name: &str, 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(name, (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(()) -}