diff --git a/.cargo/config.toml b/.cargo/config.toml index 807bc56..bfe56dd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,3 +6,4 @@ 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="test --package tinywasm --test generate-charts -- --enable " +benchmark="bench -p benchmarks --bench" diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 78f05d7..2cded9d 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -2,7 +2,7 @@ All benchmarks are run on a Ryzen 7 5800X with 32GB of RAM, running Linux 6.6. WebAssembly files are optimized using [wasm-opt](https://github.com/WebAssembly/binaryen), -and the benchmark code is available in the `benches` folder. +and the benchmark code is available in the `crates/benchmarks` folder. These are mainly preliminary benchmarks, and I will be adding more in the future that are also looking into memory usage and other metrics. @@ -22,18 +22,21 @@ All runtimes are compiled with the following settings: ## Versions -- `tinywasm`: `0.4.0` -- `wasmi`: `0.31.0` -- `wasmer`: `4.2.0` +- `tinywasm`: `0.4.1` +- `wasmi`: `0.31.2` +- `wasmer`: `4.2.5` ## Results -| Benchmark | Native | TinyWasm | Wasmi | Wasmer (Single Pass) | -| ------------ | ------ | -------- | -------- | -------------------- | -| `fib` | 6ns | 44.76µs | 48.96µs | 52µs | -| `fib-rec` | 284ns | 25.565ms | 5.11ms | 0.50ms | -| `argon2id` | 0.52ms | 110.08ms | 44.408ms | 4.76ms | -| `selfhosted` | 45µs | 2.18ms | 4.25ms | 258.87ms | +| Benchmark | Native | TinyWasm\* | Wasmi | Wasmer (Single Pass) | +| ------------ | -------- | ---------- | --------- | -------------------- | +| `fib` | \*\* | ` 43.60µs` | `48.27µs` | ` 44.99µs` | +| `fib-rec` | `0.27ms` | ` 21.13ms` | ` 4.63ms` | ` 0.47ms` | +| `argon2id` | `0.53ms` | ` 99.16ms` | `45.00ms` | ` 4.59ms` | +| `selfhosted` | `0.05ms` | ` 1.84ms` | ` 6.51ms` | `446.48ms` | + +_\* converting WASM to TinyWasm bytecode is not included. I takes ~5.7ms to convert `tinywasm.wasm` to TinyWasm bytecode._ +_\*\* essentially instant as it gets computed at compile time._ ### Fib @@ -49,7 +52,7 @@ TinyWasm is a lot slower here, but that's because there's currently no way to re 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 especially 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. +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 a lot of time on `Vec` operations, so they might be a good place to start experimenting with Arena Allocation. ### Selfhosted @@ -62,12 +65,14 @@ Wasmer also offers a pre-parsed module format, so keep in mind that this number 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 be focusing 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 be looking into improving that as well. Still, I'm quite happy with the results, especially considering the use of standard Rust data structures. +Something that made a much bigger difference than I expected was to give compiler hints about cold paths, and to force inlining of some functions. This made the benchmarks 30%+ faster in some cases. A lot of 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 bench --bench +$ cargo benchmark ``` # Profiling @@ -75,7 +80,7 @@ $ cargo bench --bench To profile a benchmark, use the following command: ```sh -$ cargo flamegraph --bench -- --bench +$ cargo flamegraph -p benchmarks --bench -- --bench ``` This will generate a flamegraph in `flamegraph.svg` and a `perf.data` file. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f33ee83 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,76 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.4.0...v0.5.0 + +### Added + +- Added this `CHANGELOG.md` file to the project +- Added merged instructions for improved performance and reduced bytecode size + +### Changed + +- Now using a custom `wasmparser` fork +- Switched to a visitor pattern for parsing WebAssembly modules +- Reduced the overhead of control flow instructions +- Reduced the size of bytecode instructions +- Fixed issues on the latest nightly Rust compiler +- Simpliefied a lot of the internal macros + +### Removed + +- Removed duplicate internal code + +## [0.4.0] - 2024-02-04 [(commits)](https://github.com/explodingcamera/tinywasm/compare/v0.3.0...v0.4.0) + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.3.0...v0.4.0 + +### Added + +- Added benchmarks for comparison with other WebAssembly runtimes +- Added support for pre-processing WebAssembly modules into tinywasm bytecode +- Improved examples and documentation +- Implemented the bulk memory operations proposal + +### Changed + +- Overall performance improvements + +## [0.3.0] - 2024-01-26 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.2.0...v0.3.0 + +- Better trap handling +- Implement linker +- Element instantiation +- Table Operations +- FuncRefs +- Typesafe host functions +- Host function context +- Spec compliance improvements +- Wasm 2.0 testsuite +- Usage examples +- End-to-end tests +- Lots of bug fixes +- Full `no_std` support + +## [0.3.0] - 2024-01-11 + +**All Commits**: https://github.com/explodingcamera/tinywasm/compare/v0.1.0...v0.2.0 + +- Support for br_table +- Memory trapping improvments +- Implicit function lable scopes +- else Instructions +- All Memory instructions +- Imports +- Basic linking +- Globals +- Fix function addr resolution +- Reference Instructions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c00a34..bd91491 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,12 @@ - **`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 e147db6..902dd10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,15 +19,28 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", "version_check", ] +[[package]] +name = "ahash" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +dependencies = [ + "cfg-if", + "const-random", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -60,9 +73,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "argh" @@ -83,7 +96,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -140,6 +153,18 @@ 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" @@ -184,9 +209,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -194,17 +219,17 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ - "bytecheck_derive 0.6.11", + "bytecheck_derive 0.6.12", "ptr_meta", "simdutf8", ] @@ -222,9 +247,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", @@ -244,9 +269,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.1" +version = "1.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" [[package]] name = "byteorder" @@ -268,12 +293,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" [[package]] name = "cfg-if" @@ -283,16 +305,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -324,18 +346,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstyle", "clap_lex", @@ -343,9 +365,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "color-eyre" @@ -386,6 +408,26 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" +[[package]] +name = "const-random" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -514,7 +556,7 @@ dependencies = [ "cranelift-entity", "fxhash", "hashbrown 0.12.3", - "indexmap", + "indexmap 1.9.3", "log", "smallvec", ] @@ -545,9 +587,9 @@ checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -640,9 +682,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.4" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da01daa5f6d41c91358398e8db4dde38e292378da1f28300b59ef4732b879454" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -650,26 +692,26 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.4" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44f6238b948a3c6c3073cdf53bb0c2d5e024ee27e0f35bfe9d556a12395808a" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] name = "darling_macro" -version = "0.20.4" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2d88bd93979b1feb760a6b5c531ac5ba06bd63e74894c377af02faee07b9cd" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -783,9 +825,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "enum-iterator" @@ -825,7 +867,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -842,20 +884,16 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -1021,7 +1059,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 1.9.3", "stable_deref_trait", ] @@ -1046,9 +1084,9 @@ dependencies = [ [[package]] name = "half" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" dependencies = [ "cfg-if", "crunchy", @@ -1060,7 +1098,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -1068,12 +1106,15 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.9", +] [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" [[package]] name = "humantime" @@ -1083,9 +1124,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1122,9 +1163,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.8" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", @@ -1150,6 +1191,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + [[package]] name = "indexmap-nostd" version = "0.4.0" @@ -1158,12 +1209,12 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix", + "libc", "windows-sys 0.52.0", ] @@ -1190,9 +1241,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1211,9 +1262,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" @@ -1242,12 +1293,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - [[package]] name = "lock_api" version = "0.4.11" @@ -1308,9 +1353,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", "simd-adler32", @@ -1324,9 +1369,9 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -1407,8 +1452,7 @@ dependencies = [ [[package]] name = "pathfinder_simd" version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0444332826c70dc47be74a7c6a5fc44e23a7905ad6858d4162b658320455ef93" +source = "git+https://github.com/servo/pathfinder?rev=e4fcda0d5259d0acf902aee6de7d2501f2bd6629#e4fcda0d5259d0acf902aee6de7d2501f2bd6629" dependencies = [ "rustc_version", ] @@ -1427,9 +1471,9 @@ checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" @@ -1479,9 +1523,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.11" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6c3c3e617595665b8ea2ff95a86066be38fb121ff920a9c0eb282abcd1da5a" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -1669,24 +1713,24 @@ dependencies = [ [[package]] name = "rend" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ - "bytecheck 0.6.11", + "bytecheck 0.6.12", ] [[package]] name = "rkyv" -version = "0.7.43" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" dependencies = [ "bitvec", - "bytecheck 0.6.11", + "bytecheck 0.6.12", "bytes", "hashbrown 0.12.3", - "indexmap", + "indexmap 1.9.3", "ptr_meta", "rend", "rkyv_derive", @@ -1697,9 +1741,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.43" +version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" dependencies = [ "proc-macro2", "quote", @@ -1708,9 +1752,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -1719,22 +1763,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.48", + "syn 2.0.51", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.2.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" dependencies = [ "globset", "sha2", @@ -1756,24 +1800,11 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" -dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -1804,15 +1835,15 @@ checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -1830,20 +1861,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -1935,9 +1966,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", @@ -1952,9 +1983,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.13" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "termcolor" @@ -1967,34 +1998,43 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2022,7 +2062,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tinywasm" -version = "0.4.0" +version = "0.5.0" dependencies = [ "eyre", "libm", @@ -2035,52 +2075,62 @@ dependencies = [ "tinywasm-parser", "tinywasm-types", "wasm-testsuite", - "wast 70.0.2", + "wast 200.0.0", ] [[package]] name = "tinywasm-cli" -version = "0.4.0" +version = "0.5.0" dependencies = [ "argh", "color-eyre", "log", "pretty_env_logger", "tinywasm", - "wast 70.0.2", + "wast 200.0.0", ] [[package]] name = "tinywasm-parser" -version = "0.4.0" +version = "0.5.0" dependencies = [ "log", "tinywasm-types", - "wasmparser-nostd", + "tinywasm-wasmparser", ] [[package]] name = "tinywasm-root" version = "0.0.0" dependencies = [ - "argon2", "color-eyre", - "criterion", + "pretty_env_logger", "tinywasm", - "wasmer", - "wasmi", "wat", ] [[package]] name = "tinywasm-types" -version = "0.4.0" +version = "0.5.0" dependencies = [ "bytecheck 0.7.0", "log", "rkyv", ] +[[package]] +name = "tinywasm-wasmparser" +version = "0.200.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "365709f885b90cad71c71120414f99255e74d9d03f232618d9d1c6eb5db0ea99" +dependencies = [ + "ahash 0.8.9", + "bitflags 2.4.2", + "hashbrown 0.14.3", + "indexmap 2.2.3", + "semver", +] + [[package]] name = "tracing" version = "0.1.40" @@ -2100,7 +2150,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -2160,9 +2210,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -2220,9 +2270,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2230,24 +2280,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2255,22 +2305,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "wasm-encoder" @@ -2283,16 +2333,16 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.41.0" +version = "0.200.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e09bca7d6388637d27fb5edbeab11f56bfabcef8743c55ae34370e1e5030a071" +checksum = "b9e3fb0c8fbddd78aa6095b850dfeedbc7506cf5f81e633f69cf8f2333ab84b9" dependencies = [ "leb128", ] [[package]] name = "wasm-testsuite" -version = "0.2.1" +version = "0.2.2" dependencies = [ "rust-embed", ] @@ -2306,7 +2356,7 @@ dependencies = [ "bytes", "cfg-if", "derivative", - "indexmap", + "indexmap 1.9.3", "js-sys", "more-asserts", "rustc-demangle", @@ -2409,10 +2459,10 @@ version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0caf1c87937b52aba8e9f920a278e1beda282f7439612c0b48f51a58e7a87bab" dependencies = [ - "bytecheck 0.6.11", + "bytecheck 0.6.12", "enum-iterator", "enumset", - "indexmap", + "indexmap 1.9.3", "more-asserts", "rkyv", "target-lexicon", @@ -2434,7 +2484,7 @@ dependencies = [ "derivative", "enum-iterator", "fnv", - "indexmap", + "indexmap 1.9.3", "lazy_static", "libc", "mach", @@ -2484,7 +2534,7 @@ version = "0.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a" dependencies = [ - "indexmap", + "indexmap 1.9.3", "url", ] @@ -2511,15 +2561,15 @@ dependencies = [ [[package]] name = "wast" -version = "70.0.2" +version = "200.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d5061300042ff5065123dae1e27d00c03f567d34a2937c8472255148a216dc" +checksum = "d1810d14e6b03ebb8fb05eef4009ad5749c989b65197d83bce7de7172ed91366" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.41.0", + "wasm-encoder 0.200.0", ] [[package]] @@ -2533,9 +2583,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -2584,7 +2634,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -2615,7 +2665,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -2635,17 +2685,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -2656,9 +2706,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -2674,9 +2724,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -2692,9 +2742,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -2710,9 +2760,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -2728,9 +2778,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -2740,9 +2790,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -2758,9 +2808,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "wio" @@ -2791,3 +2841,23 @@ dependencies = [ "once_cell", "pkg-config", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.51", +] diff --git a/Cargo.toml b/Cargo.toml index 49b73a5..41b7af0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members=["crates/*"] +default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [profile.wasm] @@ -10,7 +11,7 @@ panic="abort" inherits="release" [workspace.package] -version="0.4.0" +version="0.5.0" edition="2021" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] @@ -25,18 +26,11 @@ edition="2021" name="wasm-rust" test=false -[[bench]] -name="selfhosted" -harness=false - -[[bench]] -name="fibonacci" -harness=false - - -[[bench]] -name="argon2id" -harness=false +[dev-dependencies] +color-eyre="0.6" +tinywasm={path="crates/tinywasm", features=["unsafe"]} +wat={version="1.0"} +pretty_env_logger="0.5" [profile.bench] opt-level=3 @@ -44,12 +38,6 @@ lto="thin" codegen-units=1 debug=true -[dev-dependencies] -color-eyre="0.6" -criterion={version="0.5", features=["html_reports"]} - -tinywasm={path="crates/tinywasm", features=["unsafe"]} -wat={version="1.0"} -wasmi={version="0.31", features=["std"]} -wasmer={version="4.2", features=["cranelift", "singlepass"]} -argon2={version="0.5"} +[patch.crates-io] +# https://github.com/servo/pathfinder/pull/548 +pathfinder_simd={git="https://github.com/servo/pathfinder", rev="e4fcda0d5259d0acf902aee6de7d2501f2bd6629"} diff --git a/README.md b/README.md index da4d738..c2abbf3 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ ## Why TinyWasm? -- **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality. -- **Portable**: TinyWasm runs on any platform that LLVM supports, including WebAssembly itself, with minimal external dependencies. +- **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality (< 6000 lines of code). +- **Portable**: TinyWasm runs on any platform that Rust can target, including WebAssembly itself, with minimal external dependencies. - **Lightweight**: TinyWasm is easy to integrate and has a low call overhead, making it suitable for scripting and embedding. ## 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 achievement ensures that TinyWasm can run most WebAssembly programs, including versions of TinyWasm itself compiled to WebAssembly (see [examples/wasm-rust.rs](./examples/wasm-rust.rs)). The results of the testsuite are available [here](https://github.com/explodingcamera/tinywasm/tree/main/crates/tinywasm/tests/generated). +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 versions of 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 don't use it in production _yet_. Note that TinyWasm isn't primarily designed for high performance; its focus lies more on simplicity, size, and portability. More details on its performance aspects can be found in [BENCHMARKS.md](./BENCHMARKS.md). +The API is still unstable and may change at any time, so you probably don't want to use it in production _yet_. Note that TinyWasm isn't primarily designed for high performance; its focus lies more on simplicity, size, and portability. More details on its performance aspects can be found in [BENCHMARKS.md](./BENCHMARKS.md). ## Supported Proposals diff --git a/crates/benchmarks/Cargo.toml b/crates/benchmarks/Cargo.toml new file mode 100644 index 0000000..b9225c1 --- /dev/null +++ b/crates/benchmarks/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name="benchmarks" +publish=false +edition.workspace=true + +[dependencies] +criterion={version="0.5", features=["html_reports"]} +tinywasm={path="../../crates/tinywasm", features=["unsafe"]} +wat={version="1.0"} +wasmi={version="0.31", features=["std"]} +wasmer={version="4.2", 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/benches/argon2id.rs b/crates/benchmarks/benches/argon2id.rs similarity index 88% rename from benches/argon2id.rs rename to crates/benchmarks/benches/argon2id.rs index 4504812..7c1ffc5 100644 --- a/benches/argon2id.rs +++ b/crates/benchmarks/benches/argon2id.rs @@ -36,7 +36,7 @@ fn run_native(params: (i32, i32, i32)) { run_native(params.0, params.1, params.2) } -const ARGON2ID: &[u8] = include_bytes!("../examples/rust/out/argon2id.wasm"); +const ARGON2ID: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.wasm"); fn criterion_benchmark(c: &mut Criterion) { let twasm = wasm_to_twasm(ARGON2ID); let params = (1000, 2, 1); @@ -47,8 +47,8 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_function("native", |b| b.iter(|| run_native(black_box(params)))); group.bench_function("tinywasm", |b| b.iter(|| run_tinywasm(&twasm, 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"))); + 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!( diff --git a/benches/fibonacci.rs b/crates/benchmarks/benches/fibonacci.rs similarity index 77% rename from benches/fibonacci.rs rename to crates/benchmarks/benches/fibonacci.rs index 1d1134a..8a4dab2 100644 --- a/benches/fibonacci.rs +++ b/crates/benchmarks/benches/fibonacci.rs @@ -17,12 +17,12 @@ fn run_wasmi(wasm: &[u8], iterations: i32, name: &str) { fn run_wasmer(wasm: &[u8], iterations: i32, name: &str) { use wasmer::*; - let engine: Engine = wasmer::Singlepass::default().into(); - let mut store = Store::default(); + let compiler = wasmer::Singlepass::default(); + let mut store = Store::new(compiler); let import_object = imports! {}; - let module = wasmer::Module::from_binary(&engine, &wasm).expect("wasmer::Module::from_binary"); + 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::(&mut store, name).expect("get_function"); + let fib = instance.exports.get_typed_function::(&store, name).expect("get_function"); fib.call(&mut store, iterations).expect("call"); } @@ -45,7 +45,7 @@ fn run_native_recursive(n: i32) -> i32 { run_native_recursive(n - 1) + run_native_recursive(n - 2) } -const FIBONACCI: &[u8] = include_bytes!("../examples/rust/out/fibonacci.wasm"); +const FIBONACCI: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); fn criterion_benchmark(c: &mut Criterion) { let twasm = wasm_to_twasm(FIBONACCI); @@ -53,8 +53,8 @@ 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(&twasm, 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"))); + 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"))); } { @@ -62,8 +62,8 @@ fn criterion_benchmark(c: &mut Criterion) { 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(&twasm, 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"))); + 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"))); } } diff --git a/benches/selfhosted.rs b/crates/benchmarks/benches/selfhosted.rs similarity index 76% rename from benches/selfhosted.rs rename to crates/benchmarks/benches/selfhosted.rs index 57146e7..94dfdce 100644 --- a/benches/selfhosted.rs +++ b/crates/benchmarks/benches/selfhosted.rs @@ -1,11 +1,10 @@ mod util; -use criterion::{criterion_group, criterion_main, Criterion}; - use crate::util::twasm_to_module; +use criterion::{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 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"); @@ -46,21 +45,27 @@ fn run_wasmer(wasm: &[u8]) { "printi32" => Function::new_typed(&mut store, |_: i32| {}), }, }; - let module = wasmer::Module::from_binary(&engine, &wasm).expect("wasmer::Module::from_binary"); + 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"); +const TINYWASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); fn criterion_benchmark(c: &mut Criterion) { - let twasm = util::wasm_to_twasm(TINYWASM); + { + let mut group = c.benchmark_group("selfhosted-parse"); + group.bench_function("tinywasm", |b| b.iter(|| util::parse_wasm(TINYWASM))); + } - 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(&twasm))); - group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); - group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM))); + { + let twasm = util::wasm_to_twasm(TINYWASM); + 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(&twasm))); + group.bench_function("wasmi", |b| b.iter(|| run_wasmi(TINYWASM))); + group.bench_function("wasmer", |b| b.iter(|| run_wasmer(TINYWASM))); + } } criterion_group!( diff --git a/benches/util/mod.rs b/crates/benchmarks/benches/util/mod.rs similarity index 86% rename from benches/util/mod.rs rename to crates/benchmarks/benches/util/mod.rs index 03e2075..0df2a52 100644 --- a/benches/util/mod.rs +++ b/crates/benchmarks/benches/util/mod.rs @@ -2,6 +2,11 @@ use tinywasm::{self, parser::Parser, types::TinyWasmModule}; +pub fn parse_wasm(wasm: &[u8]) -> TinyWasmModule { + let parser = Parser::new(); + parser.parse_module_bytes(wasm).expect("parse_module_bytes") +} + pub fn wasm_to_twasm(wasm: &[u8]) -> Vec { let parser = Parser::new(); let res = parser.parse_module_bytes(wasm).expect("parse_module_bytes"); @@ -10,7 +15,7 @@ pub fn wasm_to_twasm(wasm: &[u8]) -> Vec { #[inline] pub fn twasm_to_module(twasm: &[u8]) -> tinywasm::Module { - unsafe { TinyWasmModule::from_twasm_unchecked(&twasm) }.into() + unsafe { TinyWasmModule::from_twasm_unchecked(twasm) }.into() } pub fn tinywasm(twasm: &[u8]) -> (tinywasm::Store, tinywasm::ModuleInstance) { diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 11914ef..f6105c8 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,12 +14,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.4.0", path="../tinywasm", features=["std", "parser"]} +tinywasm={version="0.5.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="70.0", optional=true} +wast={version="200.0", optional=true} [features] default=["wat"] diff --git a/crates/cli/README.md b/crates/cli/README.md index 1f7a1bb..70b2cff 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -1,6 +1,7 @@ # `tinywasm-cli` The `tinywasm-cli` crate contains the command line interface for the `tinywasm` project. See [`tinywasm`](https://crates.io/crates/tinywasm) for more information. +It is recommended to use the library directly instead of the CLI. ## Usage diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 0fee8e1..b21c0ac 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -9,11 +9,12 @@ repository.workspace=true [dependencies] # fork of wasmparser with no_std support, see https://github.com/bytecodealliance/wasmtime/issues/3495 -wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} +wasmparser={version="0.200.3", package="tinywasm-wasmparser", default-features=false} log={version="0.4", optional=true} -tinywasm-types={version="0.4.0", path="../types", default-features=false} +tinywasm-types={version="0.5.0", path="../types", default-features=false} [features] default=["std", "logging"] logging=["log"] std=["tinywasm-types/std"] + diff --git a/crates/parser/README.md b/crates/parser/README.md index 8ac7a30..563cd6b 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -1,7 +1,7 @@ # `tinywasm-parser` -This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. It is based on -[`wasmparser_nostd`](https://crates.io/crates/wasmparser_nostd) and used by [`tinywasm`](https://crates.io/crates/tinywasm). +This crate provides a parser that can parse WebAssembly modules into a TinyWasm module. +It uses [my fork](https://crates.io/crates/tinywasm-wasmparser) of the [`wasmparser`](https://crates.io/crates/wasmparser) crate that has been modified to be compatible with `no_std` environments. ## Features diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 7c10d62..c13d08f 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -1,10 +1,9 @@ -use crate::log; +use crate::Result; +use crate::{module::Code, visit::process_operators}; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use tinywasm_types::*; use wasmparser::{FuncValidator, OperatorsReader, ValidatorResources}; -use crate::{module::CodeSection, Result}; - pub(crate) fn convert_module_elements<'a, T: IntoIterator>>>( elements: T, ) -> Result> { @@ -15,26 +14,34 @@ pub(crate) fn convert_module_elements<'a, T: IntoIterator) -> Result { let kind = match element.kind { wasmparser::ElementKind::Active { table_index, offset_expr } => tinywasm_types::ElementKind::Active { - table: table_index, + table: table_index.unwrap_or(0), offset: process_const_operators(offset_expr.get_operators_reader())?, }, wasmparser::ElementKind::Passive => tinywasm_types::ElementKind::Passive, wasmparser::ElementKind::Declared => tinywasm_types::ElementKind::Declared, }; - let items = match element.items { + match element.items { wasmparser::ElementItems::Functions(funcs) => { - funcs.into_iter().map(|func| Ok(ElementItem::Func(func?))).collect::>>()?.into_boxed_slice() + let items = funcs + .into_iter() + .map(|func| Ok(ElementItem::Func(func?))) + .collect::>>()? + .into_boxed_slice(); + + Ok(tinywasm_types::Element { kind, items, ty: ValType::RefFunc, range: element.range }) } - wasmparser::ElementItems::Expressions(exprs) => exprs - .into_iter() - .map(|expr| Ok(ElementItem::Expr(process_const_operators(expr?.get_operators_reader())?))) - .collect::>>()? - .into_boxed_slice(), - }; + wasmparser::ElementItems::Expressions(ty, exprs) => { + let items = exprs + .into_iter() + .map(|expr| Ok(ElementItem::Expr(process_const_operators(expr?.get_operators_reader())?))) + .collect::>>()? + .into_boxed_slice(); - Ok(tinywasm_types::Element { kind, items, ty: convert_valtype(&element.ty), range: element.range }) + Ok(tinywasm_types::Element { kind, items, ty: convert_reftype(&ty), range: element.range }) + } + } } pub(crate) fn convert_module_data_sections<'a, T: IntoIterator>>>( @@ -71,7 +78,11 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result ImportKind::Function(ty), - wasmparser::TypeRef::Table(ty) => ImportKind::Table(convert_module_table(ty)?), + wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { + element_type: convert_reftype(&ty.element_type), + size_initial: ty.initial, + size_max: ty.maximum, + }), 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) }) @@ -103,16 +114,16 @@ pub(crate) fn convert_module_memory(memory: wasmparser::MemoryType) -> Result>>( +pub(crate) fn convert_module_tables<'a, T: IntoIterator>>>( table_types: T, ) -> Result> { let table_type = table_types.into_iter().map(|table| convert_module_table(table?)).collect::>>()?; Ok(table_type) } -pub(crate) fn convert_module_table(table: wasmparser::TableType) -> Result { - let ty = convert_valtype(&table.element_type); - Ok(TableType { element_type: ty, size_initial: table.initial, size_max: table.maximum }) +pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result { + let ty = convert_reftype(&table.ty.element_type); + Ok(TableType { element_type: ty, size_initial: table.ty.initial, size_max: table.ty.maximum }) } pub(crate) fn convert_module_globals<'a, T: IntoIterator>>>( @@ -131,7 +142,7 @@ pub(crate) fn convert_module_globals<'a, T: IntoIterator Result { +pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result { let kind = match export.kind { wasmparser::ExternalKind::Func => ExternalKind::Func, wasmparser::ExternalKind::Table => ExternalKind::Table, @@ -146,14 +157,14 @@ pub(crate) fn convert_module_export(export: wasmparser::Export) -> Result, mut validator: FuncValidator, -) -> Result { +) -> Result { 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); + let mut locals = Vec::with_capacity(count as usize); for (i, local) in locals_reader.into_iter().enumerate() { let local = local?; validator.define_locals(pos + i, local.0, local.1)?; @@ -162,14 +173,20 @@ pub(crate) fn convert_module_code( } } - let body_reader = func.get_operators_reader()?; - let body = process_operators(body_reader.original_position(), body_reader.into_iter(), validator)?; - - Ok(CodeSection { locals: locals.into_boxed_slice(), body }) + let body = process_operators(Some(&mut validator), &func)?; + let locals = locals.into_boxed_slice(); + Ok((body, locals)) } -pub(crate) fn convert_module_type(ty: wasmparser::Type) -> Result { - let wasmparser::Type::Func(ty) = ty; +pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { + let mut types = ty.types(); + + if types.len() != 1 { + return Err(crate::ParseError::UnsupportedOperator( + "Expected exactly one type in the type section".to_string(), + )); + } + let ty = types.next().unwrap().unwrap_func(); let params = ty.params().iter().map(|p| Ok(convert_valtype(p))).collect::>>()?.into_boxed_slice(); @@ -188,6 +205,14 @@ pub(crate) fn convert_blocktype(blocktype: wasmparser::BlockType) -> BlockArgs { } } +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), + } +} + pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> ValType { use wasmparser::ValType::*; match valtype { @@ -195,30 +220,28 @@ pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> ValType { I64 => ValType::I64, F32 => ValType::F32, F64 => ValType::F64, + Ref(r) => convert_reftype(r), V128 => unimplemented!("128-bit values are not supported yet"), - FuncRef => ValType::RefFunc, - ExternRef => ValType::RefExtern, } } pub(crate) fn convert_memarg(memarg: wasmparser::MemArg) -> MemoryArg { - MemoryArg { offset: memarg.offset, align: memarg.align, align_max: memarg.max_align, mem_addr: memarg.memory } + MemoryArg { offset: memarg.offset, mem_addr: memarg.memory } } -pub(crate) fn process_const_operators(ops: OperatorsReader) -> Result { +pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result { let ops = ops.into_iter().collect::>>()?; // In practice, the len can never be something other than 2, // but we'll keep this here since it's part of the spec // Invalid modules will be rejected by the validator anyway (there are also tests for this in the testsuite) assert!(ops.len() >= 2); assert!(matches!(ops[ops.len() - 1], wasmparser::Operator::End)); - process_const_operator(ops[ops.len() - 2].clone()) } -pub fn process_const_operator(op: wasmparser::Operator) -> Result { +pub(crate) fn process_const_operator(op: wasmparser::Operator<'_>) -> Result { match op { - wasmparser::Operator::RefNull { ty } => Ok(ConstInstruction::RefNull(convert_valtype(&ty))), + wasmparser::Operator::RefNull { hty } => Ok(ConstInstruction::RefNull(convert_heaptype(hty))), wasmparser::Operator::RefFunc { function_index } => Ok(ConstInstruction::RefFunc(function_index)), wasmparser::Operator::I32Const { value } => Ok(ConstInstruction::I32Const(value)), wasmparser::Operator::I64Const { value } => Ok(ConstInstruction::I64Const(value)), @@ -229,301 +252,10 @@ pub fn process_const_operator(op: wasmparser::Operator) -> Result( - mut offset: usize, - ops: impl Iterator, wasmparser::BinaryReaderError>>, - mut validator: FuncValidator, -) -> Result> { - let mut instructions = Vec::new(); - let mut labels_ptrs = Vec::new(); // indexes into the instructions array - - for op in ops { - log::debug!("op: {:?}", op); - - let op = op?; - validator.op(offset, &op)?; - offset += 1; - - use wasmparser::Operator::*; - let res = match op { - BrTable { targets } => { - let def = targets.default(); - let targets = targets.targets().collect::, wasmparser::BinaryReaderError>>()?; - instructions.push(Instruction::BrTable(def, targets.len())); - instructions.extend(targets.into_iter().map(Instruction::BrLabel)); - continue; - } - Unreachable => Instruction::Unreachable, - Nop => Instruction::Nop, - Block { blockty } => { - labels_ptrs.push(instructions.len()); - Instruction::Block(convert_blocktype(blockty), 0) - } - Loop { blockty } => { - labels_ptrs.push(instructions.len()); - Instruction::Loop(convert_blocktype(blockty), 0) - } - If { blockty } => { - labels_ptrs.push(instructions.len()); - Instruction::If(convert_blocktype(blockty), None, 0) - } - Else => { - labels_ptrs.push(instructions.len()); - Instruction::Else(0) - } - End => { - if let Some(label_pointer) = labels_ptrs.pop() { - log::debug!("ending block: {:?}", instructions[label_pointer]); - - let current_instr_ptr = instructions.len(); - - // last_label_pointer is Some if we're ending a block - match instructions[label_pointer] { - Instruction::Else(ref mut else_instr_end_offset) => { - *else_instr_end_offset = current_instr_ptr - label_pointer; - - // since we're ending an else block, we need to end the if block as well - let if_label_pointer = labels_ptrs.pop().ok_or(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - ))?; - - let if_instruction = &mut instructions[if_label_pointer]; - let Instruction::If(_, ref mut else_offset, ref mut end_offset) = if_instruction else { - return Err(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - )); - }; - - *else_offset = Some(label_pointer - if_label_pointer); - *end_offset = current_instr_ptr - if_label_pointer; - } - Instruction::Block(_, ref mut end_offset) - | Instruction::Loop(_, ref mut end_offset) - | Instruction::If(_, _, ref mut end_offset) => { - *end_offset = current_instr_ptr - label_pointer; - } - _ => { - return Err(crate::ParseError::UnsupportedOperator( - "Expected to end a block, but the last label was not a block".to_string(), - )) - } - } - - Instruction::EndBlockFrame - } else { - // last_label_pointer is None if we're ending the function - Instruction::EndFunc - } - } - - Br { relative_depth } => Instruction::Br(relative_depth), - BrIf { relative_depth } => Instruction::BrIf(relative_depth), - Return => Instruction::Return, - Call { function_index } => Instruction::Call(function_index), - CallIndirect { type_index, table_index, .. } => Instruction::CallIndirect(type_index, table_index), - Drop => Instruction::Drop, - Select => Instruction::Select(None), - TypedSelect { ty } => Instruction::Select(Some(convert_valtype(&ty))), - LocalGet { local_index } => Instruction::LocalGet(local_index), - LocalSet { local_index } => Instruction::LocalSet(local_index), - LocalTee { local_index } => Instruction::LocalTee(local_index), - GlobalGet { global_index } => Instruction::GlobalGet(global_index), - GlobalSet { global_index } => Instruction::GlobalSet(global_index), - MemorySize { mem, mem_byte } => Instruction::MemorySize(mem, mem_byte), - MemoryGrow { mem, mem_byte } => Instruction::MemoryGrow(mem, mem_byte), - - MemoryCopy { dst_mem, src_mem } => Instruction::MemoryCopy(src_mem, dst_mem), - MemoryFill { mem } => Instruction::MemoryFill(mem), - MemoryInit { data_index, mem } => Instruction::MemoryInit(data_index, mem), - DataDrop { data_index } => Instruction::DataDrop(data_index), - - I32Const { value } => Instruction::I32Const(value), - I64Const { value } => Instruction::I64Const(value), - F32Const { value } => Instruction::F32Const(f32::from_bits(value.bits())), - F64Const { value } => Instruction::F64Const(f64::from_bits(value.bits())), - RefNull { ty } => Instruction::RefNull(convert_valtype(&ty)), - RefIsNull => Instruction::RefIsNull, - RefFunc { function_index } => Instruction::RefFunc(function_index), - I32Load { memarg } => Instruction::I32Load(convert_memarg(memarg)), - I64Load { memarg } => Instruction::I64Load(convert_memarg(memarg)), - F32Load { memarg } => Instruction::F32Load(convert_memarg(memarg)), - F64Load { memarg } => Instruction::F64Load(convert_memarg(memarg)), - I32Load8S { memarg } => Instruction::I32Load8S(convert_memarg(memarg)), - I32Load8U { memarg } => Instruction::I32Load8U(convert_memarg(memarg)), - I32Load16S { memarg } => Instruction::I32Load16S(convert_memarg(memarg)), - I32Load16U { memarg } => Instruction::I32Load16U(convert_memarg(memarg)), - I64Load8S { memarg } => Instruction::I64Load8S(convert_memarg(memarg)), - I64Load8U { memarg } => Instruction::I64Load8U(convert_memarg(memarg)), - I64Load16S { memarg } => Instruction::I64Load16S(convert_memarg(memarg)), - I64Load16U { memarg } => Instruction::I64Load16U(convert_memarg(memarg)), - I64Load32S { memarg } => Instruction::I64Load32S(convert_memarg(memarg)), - I64Load32U { memarg } => Instruction::I64Load32U(convert_memarg(memarg)), - I32Store { memarg } => Instruction::I32Store(convert_memarg(memarg)), - I64Store { memarg } => Instruction::I64Store(convert_memarg(memarg)), - F32Store { memarg } => Instruction::F32Store(convert_memarg(memarg)), - F64Store { memarg } => Instruction::F64Store(convert_memarg(memarg)), - I32Store8 { memarg } => Instruction::I32Store8(convert_memarg(memarg)), - I32Store16 { memarg } => Instruction::I32Store16(convert_memarg(memarg)), - I64Store8 { memarg } => Instruction::I64Store8(convert_memarg(memarg)), - I64Store16 { memarg } => Instruction::I64Store16(convert_memarg(memarg)), - I64Store32 { memarg } => Instruction::I64Store32(convert_memarg(memarg)), - I32Eqz => Instruction::I32Eqz, - I32Eq => Instruction::I32Eq, - I32Ne => Instruction::I32Ne, - I32LtS => Instruction::I32LtS, - I32LtU => Instruction::I32LtU, - I32GtS => Instruction::I32GtS, - I32GtU => Instruction::I32GtU, - I32LeS => Instruction::I32LeS, - I32LeU => Instruction::I32LeU, - I32GeS => Instruction::I32GeS, - I32GeU => Instruction::I32GeU, - I64Eqz => Instruction::I64Eqz, - I64Eq => Instruction::I64Eq, - I64Ne => Instruction::I64Ne, - I64LtS => Instruction::I64LtS, - I64LtU => Instruction::I64LtU, - I64GtS => Instruction::I64GtS, - I64GtU => Instruction::I64GtU, - I64LeS => Instruction::I64LeS, - I64LeU => Instruction::I64LeU, - I64GeS => Instruction::I64GeS, - I64GeU => Instruction::I64GeU, - F32Eq => Instruction::F32Eq, - F32Ne => Instruction::F32Ne, - F32Lt => Instruction::F32Lt, - F32Gt => Instruction::F32Gt, - F32Le => Instruction::F32Le, - F32Ge => Instruction::F32Ge, - F64Eq => Instruction::F64Eq, - F64Ne => Instruction::F64Ne, - F64Lt => Instruction::F64Lt, - F64Gt => Instruction::F64Gt, - F64Le => Instruction::F64Le, - F64Ge => Instruction::F64Ge, - I32Clz => Instruction::I32Clz, - I32Ctz => Instruction::I32Ctz, - I32Popcnt => Instruction::I32Popcnt, - I32Add => Instruction::I32Add, - I32Sub => Instruction::I32Sub, - I32Mul => Instruction::I32Mul, - I32DivS => Instruction::I32DivS, - I32DivU => Instruction::I32DivU, - I32RemS => Instruction::I32RemS, - I32RemU => Instruction::I32RemU, - I32And => Instruction::I32And, - I32Or => Instruction::I32Or, - I32Xor => Instruction::I32Xor, - I32Shl => Instruction::I32Shl, - I32ShrS => Instruction::I32ShrS, - I32ShrU => Instruction::I32ShrU, - I32Rotl => Instruction::I32Rotl, - I32Rotr => Instruction::I32Rotr, - I64Clz => Instruction::I64Clz, - I64Ctz => Instruction::I64Ctz, - I64Popcnt => Instruction::I64Popcnt, - I64Add => Instruction::I64Add, - I64Sub => Instruction::I64Sub, - I64Mul => Instruction::I64Mul, - I64DivS => Instruction::I64DivS, - I64DivU => Instruction::I64DivU, - I64RemS => Instruction::I64RemS, - I64RemU => Instruction::I64RemU, - I64And => Instruction::I64And, - I64Or => Instruction::I64Or, - I64Xor => Instruction::I64Xor, - I64Shl => Instruction::I64Shl, - I64ShrS => Instruction::I64ShrS, - I64ShrU => Instruction::I64ShrU, - I64Rotl => Instruction::I64Rotl, - I64Rotr => Instruction::I64Rotr, - F32Abs => Instruction::F32Abs, - F32Neg => Instruction::F32Neg, - F32Ceil => Instruction::F32Ceil, - F32Floor => Instruction::F32Floor, - F32Trunc => Instruction::F32Trunc, - F32Nearest => Instruction::F32Nearest, - F32Sqrt => Instruction::F32Sqrt, - F32Add => Instruction::F32Add, - F32Sub => Instruction::F32Sub, - F32Mul => Instruction::F32Mul, - F32Div => Instruction::F32Div, - F32Min => Instruction::F32Min, - F32Max => Instruction::F32Max, - F32Copysign => Instruction::F32Copysign, - F64Abs => Instruction::F64Abs, - F64Neg => Instruction::F64Neg, - F64Ceil => Instruction::F64Ceil, - F64Floor => Instruction::F64Floor, - F64Trunc => Instruction::F64Trunc, - F64Nearest => Instruction::F64Nearest, - F64Sqrt => Instruction::F64Sqrt, - F64Add => Instruction::F64Add, - F64Sub => Instruction::F64Sub, - F64Mul => Instruction::F64Mul, - F64Div => Instruction::F64Div, - F64Min => Instruction::F64Min, - F64Max => Instruction::F64Max, - F64Copysign => Instruction::F64Copysign, - I32WrapI64 => Instruction::I32WrapI64, - I32TruncF32S => Instruction::I32TruncF32S, - I32TruncF32U => Instruction::I32TruncF32U, - I32TruncF64S => Instruction::I32TruncF64S, - I32TruncF64U => Instruction::I32TruncF64U, - I64Extend8S => Instruction::I64Extend8S, - I64Extend16S => Instruction::I64Extend16S, - I64Extend32S => Instruction::I64Extend32S, - I64ExtendI32S => Instruction::I64ExtendI32S, - I64ExtendI32U => Instruction::I64ExtendI32U, - I32Extend8S => Instruction::I32Extend8S, - I32Extend16S => Instruction::I32Extend16S, - I64TruncF32S => Instruction::I64TruncF32S, - I64TruncF32U => Instruction::I64TruncF32U, - I64TruncF64S => Instruction::I64TruncF64S, - I64TruncF64U => Instruction::I64TruncF64U, - F32ConvertI32S => Instruction::F32ConvertI32S, - F32ConvertI32U => Instruction::F32ConvertI32U, - F32ConvertI64S => Instruction::F32ConvertI64S, - F32ConvertI64U => Instruction::F32ConvertI64U, - F32DemoteF64 => Instruction::F32DemoteF64, - F64ConvertI32S => Instruction::F64ConvertI32S, - F64ConvertI32U => Instruction::F64ConvertI32U, - F64ConvertI64S => Instruction::F64ConvertI64S, - F64ConvertI64U => Instruction::F64ConvertI64U, - F64PromoteF32 => Instruction::F64PromoteF32, - I32ReinterpretF32 => Instruction::I32ReinterpretF32, - I64ReinterpretF64 => Instruction::I64ReinterpretF64, - F32ReinterpretI32 => Instruction::F32ReinterpretI32, - F64ReinterpretI64 => Instruction::F64ReinterpretI64, - I32TruncSatF32S => Instruction::I32TruncSatF32S, - I32TruncSatF32U => Instruction::I32TruncSatF32U, - I32TruncSatF64S => Instruction::I32TruncSatF64S, - I32TruncSatF64U => Instruction::I32TruncSatF64U, - I64TruncSatF32S => Instruction::I64TruncSatF32S, - I64TruncSatF32U => Instruction::I64TruncSatF32U, - I64TruncSatF64S => Instruction::I64TruncSatF64S, - I64TruncSatF64U => Instruction::I64TruncSatF64U, - TableGet { table } => Instruction::TableGet(table), - TableSet { table } => Instruction::TableSet(table), - TableInit { table, elem_index } => Instruction::TableInit(table, elem_index), - TableCopy { src_table, dst_table } => Instruction::TableCopy { from: src_table, to: dst_table }, - TableGrow { table } => Instruction::TableGrow(table), - TableSize { table } => Instruction::TableSize(table), - TableFill { table } => Instruction::TableFill(table), - op => { - log::error!("Unsupported instruction: {:?}", op); - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported instruction: {:?}", op))); - } - }; - - instructions.push(res); +pub(crate) fn convert_heaptype(heap: wasmparser::HeapType) -> ValType { + match heap { + wasmparser::HeapType::Func => ValType::RefFunc, + wasmparser::HeapType::Extern => ValType::RefExtern, + _ => unimplemented!("Unsupported heap type: {:?}", heap), } - - if !labels_ptrs.is_empty() { - panic!("last_label_pointer should be None after processing all instructions: {:?}", labels_ptrs); - } - - validator.finish(offset)?; - - Ok(instructions.into_boxed_slice()) } diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs index 35bad28..76d806d 100644 --- a/crates/parser/src/error.rs +++ b/crates/parser/src/error.rs @@ -6,15 +6,35 @@ use wasmparser::Encoding; #[derive(Debug)] /// Errors that can occur when parsing a WebAssembly module pub enum ParseError { + /// An invalid type was encountered InvalidType, + /// An unsupported section was encountered UnsupportedSection(String), + /// A duplicate section was encountered DuplicateSection(String), + /// An empty section was encountered EmptySection(String), + /// An unsupported operator was encountered UnsupportedOperator(String), - ParseError { message: String, offset: usize }, + /// An error occurred while parsing the module + ParseError { + /// The error message + message: String, + /// The offset in the module where the error occurred + offset: usize, + }, + /// An invalid encoding was encountered InvalidEncoding(Encoding), - InvalidLocalCount { expected: u32, actual: u32 }, + /// An invalid local count was encountered + InvalidLocalCount { + /// The expected local count + expected: u32, + /// The actual local count + actual: u32, + }, + /// The end of the module was not reached EndNotReached, + /// An unknown error occurred Other(String), } @@ -48,4 +68,4 @@ impl From for ParseError { } } -pub type Result = core::result::Result; +pub(crate) type Result = core::result::Result; diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index c608232..7beb5f8 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -1,6 +1,12 @@ #![no_std] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) +))] +#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] #![forbid(unsafe_code)] #![cfg_attr(not(feature = "std"), feature(error_in_core))] +//! See [`tinywasm`](https://docs.rs/tinywasm) for documentation. mod std; extern crate alloc; @@ -22,25 +28,57 @@ mod log { mod conversion; mod error; mod module; +mod visit; use alloc::{string::ToString, vec::Vec}; pub use error::*; use module::ModuleReader; use tinywasm_types::{TypedWasmFunction, WasmFunction}; -use wasmparser::Validator; +use wasmparser::{Validator, WasmFeatures}; pub use tinywasm_types::TinyWasmModule; -#[derive(Default)] +/// A WebAssembly parser +#[derive(Default, Debug)] pub struct Parser {} impl Parser { + /// Create a new parser instance pub fn new() -> Self { Self {} } + fn create_validator(&self) -> Validator { + let features = WasmFeatures { + bulk_memory: true, + floats: true, + multi_value: true, + mutable_global: true, + reference_types: true, + sign_extension: true, + saturating_float_to_int: true, + + function_references: false, + component_model: false, + component_model_nested_names: false, + component_model_values: false, + exceptions: false, + extended_const: false, + gc: false, + memory64: false, + memory_control: false, + relaxed_simd: false, + simd: false, + tail_call: false, + threads: false, + multi_memory: false, // should be working mostly + }; + Validator::new_with_features(features) + } + + /// Parse a [`TinyWasmModule`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); - let mut validator = Validator::new(); + let mut validator = self.create_validator(); let mut reader = ModuleReader::new(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { @@ -55,6 +93,7 @@ impl Parser { } #[cfg(feature = "std")] + /// Parse a [`TinyWasmModule`] from a file. Requires `std` feature. pub fn parse_module_file(&self, path: impl AsRef + Clone) -> Result { use alloc::format; let f = crate::std::fs::File::open(path.clone()) @@ -65,10 +104,11 @@ impl Parser { } #[cfg(feature = "std")] + /// Parse a [`TinyWasmModule`] from a stream. Requires `std` feature. pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { use alloc::format; - let mut validator = Validator::new(); + let mut validator = self.create_validator(); let mut reader = ModuleReader::new(); let mut buffer = Vec::new(); let mut parser = wasmparser::Parser::new(0); @@ -116,11 +156,11 @@ impl TryFrom for TinyWasmModule { .code .into_iter() .zip(code_type_addrs) - .map(|(f, ty_idx)| TypedWasmFunction { + .map(|((instructions, locals), ty_idx)| TypedWasmFunction { type_addr: ty_idx, wasm_function: WasmFunction { - instructions: f.body, - locals: f.locals, + instructions, + locals, ty: reader.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(), }, }) diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index f5c01ac..8414c17 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,63 +1,34 @@ use crate::log::debug; use crate::{conversion, ParseError, Result}; use alloc::{boxed::Box, format, vec::Vec}; -use core::fmt::Debug; use tinywasm_types::{Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, ValType}; use wasmparser::{Payload, Validator}; -#[derive(Debug, Clone)] -pub struct CodeSection { - pub locals: Box<[ValType]>, - pub body: Box<[Instruction]>, -} +pub(crate) type Code = (Box<[Instruction]>, Box<[ValType]>); #[derive(Default)] -pub struct ModuleReader { - pub version: Option, - pub start_func: Option, - - pub func_types: Vec, - - // map from local function index to type index - pub code_type_addrs: Vec, - - pub exports: Vec, - pub code: Vec, - pub globals: Vec, - pub table_types: Vec, - pub memory_types: Vec, - pub imports: Vec, - pub data: Vec, - pub elements: Vec, - - // pub element_section: Option>, - pub end_reached: bool, -} - -impl Debug for ModuleReader { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_struct("ModuleReader") - .field("version", &self.version) - .field("func_types", &self.func_types) - .field("func_addrs", &self.code_type_addrs) - .field("code", &self.code) - .field("exports", &self.exports) - .field("globals", &self.globals) - .field("table_types", &self.table_types) - .field("memory_types", &self.memory_types) - .field("import_section", &self.imports) - // .field("element_section", &self.element_section) - // .field("data_section", &self.data_section) - .finish() - } +pub(crate) struct ModuleReader { + pub(crate) version: Option, + pub(crate) start_func: Option, + pub(crate) func_types: Vec, + pub(crate) code_type_addrs: Vec, + pub(crate) exports: Vec, + pub(crate) code: Vec, + pub(crate) globals: Vec, + pub(crate) table_types: Vec, + pub(crate) memory_types: Vec, + pub(crate) imports: Vec, + pub(crate) data: Vec, + pub(crate) elements: Vec, + pub(crate) end_reached: bool, } impl ModuleReader { - pub fn new() -> ModuleReader { + pub(crate) fn new() -> ModuleReader { Self::default() } - pub fn process_payload(&mut self, payload: Payload, validator: &mut Validator) -> Result<()> { + pub(crate) fn process_payload(&mut self, payload: Payload<'_>, validator: &mut Validator) -> Result<()> { use wasmparser::Payload::*; match payload { @@ -90,15 +61,7 @@ impl ModuleReader { .map(|t| conversion::convert_module_type(t?)) .collect::>>()?; } - FunctionSection(reader) => { - if !self.code_type_addrs.is_empty() { - return Err(ParseError::DuplicateSection("Function section".into())); - } - debug!("Found function section"); - validator.function_section(&reader)?; - self.code_type_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; - } GlobalSection(reader) => { if !self.globals.is_empty() { return Err(ParseError::DuplicateSection("Global section".into())); @@ -146,11 +109,21 @@ impl ModuleReader { } validator.data_count_section(count, &range)?; } + FunctionSection(reader) => { + if !self.code_type_addrs.is_empty() { + return Err(ParseError::DuplicateSection("Function section".into())); + } + + debug!("Found function section"); + validator.function_section(&reader)?; + self.code_type_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; + } CodeSectionStart { count, range, .. } => { debug!("Found code section ({} functions)", count); if !self.code.is_empty() { return Err(ParseError::DuplicateSection("Code section".into())); } + self.code.reserve(count as usize); validator.code_section_start(count, &range)?; } CodeSectionEntry(function) => { @@ -191,10 +164,6 @@ impl ModuleReader { debug!("Found custom section"); debug!("Skipping custom section: {:?}", _reader.name()); } - // TagSection(tag) => { - // debug!("Found tag section"); - // validator.tag_section(&tag)?; - // } UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {:?}", section))), }; diff --git a/crates/parser/src/std.rs b/crates/parser/src/std.rs index 67152be..16a7058 100644 --- a/crates/parser/src/std.rs +++ b/crates/parser/src/std.rs @@ -2,4 +2,4 @@ extern crate std; #[cfg(feature = "std")] -pub use std::*; +pub(crate) use std::*; diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs new file mode 100644 index 0000000..cc9f0e2 --- /dev/null +++ b/crates/parser/src/visit.rs @@ -0,0 +1,528 @@ +use crate::{conversion::convert_blocktype, Result}; + +use crate::conversion::{convert_heaptype, convert_memarg, convert_valtype}; +use alloc::string::ToString; +use alloc::{boxed::Box, format, vec::Vec}; +use tinywasm_types::{BlockArgsPacked, Instruction}; +use wasmparser::{FuncValidator, FunctionBody, VisitOperator, WasmModuleResources}; + +struct ValidateThenVisit<'a, T, U>(T, &'a mut U); +macro_rules! validate_then_visit { + ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { + $( + #[inline] + fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { + self.0.$visit($($($arg.clone()),*)?)?; + Ok(self.1.$visit($($($arg),*)?)) + } + )* + }; +} + +impl<'a, T, U> VisitOperator<'a> for ValidateThenVisit<'_, T, U> +where + T: VisitOperator<'a, Output = wasmparser::Result<()>>, + U: VisitOperator<'a>, +{ + type Output = Result; + wasmparser::for_each_operator!(validate_then_visit); +} + +pub(crate) fn process_operators( + validator: Option<&mut FuncValidator>, + body: &FunctionBody<'_>, +) -> Result> { + 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)??; + } + } + + Ok(builder.instructions.into_boxed_slice()) +} + +macro_rules! define_operands { + ($($name:ident, $instr:expr),*) => { + $( + fn $name(&mut self) -> Self::Output { + self.instructions.push($instr); + Ok(()) + } + )* + }; +} + +macro_rules! define_primitive_operands { + ($($name:ident, $instr:expr, $ty:ty),*) => { + $( + fn $name(&mut self, arg: $ty) -> Self::Output { + self.instructions.push($instr(arg)); + Ok(()) + } + )* + }; + ($($name:ident, $instr:expr, $ty:ty, $ty2:ty),*) => { + $( + fn $name(&mut self, arg: $ty, arg2: $ty) -> Self::Output { + self.instructions.push($instr(arg, arg2)); + Ok(()) + } + )* + }; +} + +macro_rules! define_mem_operands { + ($($name:ident, $instr:ident),*) => { + $( + 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(()) + } + )* + }; +} + +pub(crate) struct FunctionBuilder { + instructions: Vec, + label_ptrs: Vec, +} + +impl FunctionBuilder { + pub(crate) fn new(instr_capacity: usize) -> Self { + Self { instructions: Vec::with_capacity(instr_capacity), label_ptrs: Vec::with_capacity(128) } + } + + #[cold] + fn unsupported(&self, name: &str) -> Result<()> { + Err(crate::ParseError::UnsupportedOperator(format!("Unsupported instruction: {:?}", name))) + } + + #[inline] + fn visit(&mut self, op: Instruction) -> Result<()> { + self.instructions.push(op); + Ok(()) + } +} + +impl<'a> wasmparser::VisitOperator<'a> for FunctionBuilder { + type Output = Result<()>; + + fn visit_default(&mut self, op: &str) -> Self::Output { + self.unsupported(op) + } + + 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 + } + + define_mem_operands! { + visit_i32_load, I32Load, + visit_i64_load, I64Load, + visit_f32_load, F32Load, + visit_f64_load, F64Load, + visit_i32_load8_s, I32Load8S, + visit_i32_load8_u, I32Load8U, + visit_i32_load16_s, I32Load16S, + visit_i32_load16_u, I32Load16U, + visit_i64_load8_s, I64Load8S, + visit_i64_load8_u, I64Load8U, + visit_i64_load16_s, I64Load16S, + visit_i64_load16_u, I64Load16U, + visit_i64_load32_s, I64Load32S, + visit_i64_load32_u, I64Load32U, + visit_i32_store, I32Store, + visit_i64_store, I64Store, + visit_f32_store, F32Store, + visit_f64_store, F64Store, + visit_i32_store8, I32Store8, + visit_i32_store16, I32Store16, + visit_i64_store8, I64Store8, + visit_i64_store16, I64Store16, + visit_i64_store32, I64Store32 + } + + define_operands! { + 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, + visit_i32_lt_s, Instruction::I32LtS, + visit_i32_lt_u, Instruction::I32LtU, + visit_i32_gt_s, Instruction::I32GtS, + visit_i32_gt_u, Instruction::I32GtU, + visit_i32_le_s, Instruction::I32LeS, + visit_i32_le_u, Instruction::I32LeU, + visit_i32_ge_s, Instruction::I32GeS, + visit_i32_ge_u, Instruction::I32GeU, + visit_i64_eqz, Instruction::I64Eqz, + visit_i64_eq, Instruction::I64Eq, + visit_i64_ne, Instruction::I64Ne, + visit_i64_lt_s, Instruction::I64LtS, + visit_i64_lt_u, Instruction::I64LtU, + visit_i64_gt_s, Instruction::I64GtS, + visit_i64_gt_u, Instruction::I64GtU, + visit_i64_le_s, Instruction::I64LeS, + visit_i64_le_u, Instruction::I64LeU, + visit_i64_ge_s, Instruction::I64GeS, + visit_i64_ge_u, Instruction::I64GeU, + visit_f32_eq, Instruction::F32Eq, + visit_f32_ne, Instruction::F32Ne, + visit_f32_lt, Instruction::F32Lt, + visit_f32_gt, Instruction::F32Gt, + visit_f32_le, Instruction::F32Le, + visit_f32_ge, Instruction::F32Ge, + visit_f64_eq, Instruction::F64Eq, + visit_f64_ne, Instruction::F64Ne, + visit_f64_lt, Instruction::F64Lt, + visit_f64_gt, Instruction::F64Gt, + visit_f64_le, Instruction::F64Le, + visit_f64_ge, Instruction::F64Ge, + visit_i32_clz, Instruction::I32Clz, + visit_i32_ctz, Instruction::I32Ctz, + visit_i32_popcnt, Instruction::I32Popcnt, + // visit_i32_add, Instruction::I32Add, custom implementation + visit_i32_sub, Instruction::I32Sub, + visit_i32_mul, Instruction::I32Mul, + visit_i32_div_s, Instruction::I32DivS, + visit_i32_div_u, Instruction::I32DivU, + visit_i32_rem_s, Instruction::I32RemS, + visit_i32_rem_u, Instruction::I32RemU, + visit_i32_and, Instruction::I32And, + visit_i32_or, Instruction::I32Or, + visit_i32_xor, Instruction::I32Xor, + visit_i32_shl, Instruction::I32Shl, + visit_i32_shr_s, Instruction::I32ShrS, + visit_i32_shr_u, Instruction::I32ShrU, + visit_i32_rotl, Instruction::I32Rotl, + visit_i32_rotr, Instruction::I32Rotr, + visit_i64_clz, Instruction::I64Clz, + visit_i64_ctz, Instruction::I64Ctz, + visit_i64_popcnt, Instruction::I64Popcnt, + visit_i64_add, Instruction::I64Add, + visit_i64_sub, Instruction::I64Sub, + visit_i64_mul, Instruction::I64Mul, + visit_i64_div_s, Instruction::I64DivS, + visit_i64_div_u, Instruction::I64DivU, + visit_i64_rem_s, Instruction::I64RemS, + visit_i64_rem_u, Instruction::I64RemU, + visit_i64_and, Instruction::I64And, + visit_i64_or, Instruction::I64Or, + visit_i64_xor, Instruction::I64Xor, + visit_i64_shl, Instruction::I64Shl, + visit_i64_shr_s, Instruction::I64ShrS, + visit_i64_shr_u, Instruction::I64ShrU, + // visit_i64_rotl, Instruction::I64Rotl, custom implementation + visit_i64_rotr, Instruction::I64Rotr, + visit_f32_abs, Instruction::F32Abs, + visit_f32_neg, Instruction::F32Neg, + visit_f32_ceil, Instruction::F32Ceil, + visit_f32_floor, Instruction::F32Floor, + visit_f32_trunc, Instruction::F32Trunc, + visit_f32_nearest, Instruction::F32Nearest, + visit_f32_sqrt, Instruction::F32Sqrt, + visit_f32_add, Instruction::F32Add, + visit_f32_sub, Instruction::F32Sub, + visit_f32_mul, Instruction::F32Mul, + visit_f32_div, Instruction::F32Div, + visit_f32_min, Instruction::F32Min, + visit_f32_max, Instruction::F32Max, + visit_f32_copysign, Instruction::F32Copysign, + visit_f64_abs, Instruction::F64Abs, + visit_f64_neg, Instruction::F64Neg, + visit_f64_ceil, Instruction::F64Ceil, + visit_f64_floor, Instruction::F64Floor, + visit_f64_trunc, Instruction::F64Trunc, + visit_f64_nearest, Instruction::F64Nearest, + visit_f64_sqrt, Instruction::F64Sqrt, + visit_f64_add, Instruction::F64Add, + visit_f64_sub, Instruction::F64Sub, + visit_f64_mul, Instruction::F64Mul, + visit_f64_div, Instruction::F64Div, + visit_f64_min, Instruction::F64Min, + visit_f64_max, Instruction::F64Max, + visit_f64_copysign, Instruction::F64Copysign, + visit_i32_wrap_i64, Instruction::I32WrapI64, + visit_i32_trunc_f32_s, Instruction::I32TruncF32S, + visit_i32_trunc_f32_u, Instruction::I32TruncF32U, + visit_i32_trunc_f64_s, Instruction::I32TruncF64S, + visit_i32_trunc_f64_u, Instruction::I32TruncF64U, + visit_i64_extend_i32_s, Instruction::I64ExtendI32S, + visit_i64_extend_i32_u, Instruction::I64ExtendI32U, + visit_i64_trunc_f32_s, Instruction::I64TruncF32S, + visit_i64_trunc_f32_u, Instruction::I64TruncF32U, + visit_i64_trunc_f64_s, Instruction::I64TruncF64S, + visit_i64_trunc_f64_u, Instruction::I64TruncF64U, + visit_f32_convert_i32_s, Instruction::F32ConvertI32S, + visit_f32_convert_i32_u, Instruction::F32ConvertI32U, + visit_f32_convert_i64_s, Instruction::F32ConvertI64S, + visit_f32_convert_i64_u, Instruction::F32ConvertI64U, + visit_f32_demote_f64, Instruction::F32DemoteF64, + visit_f64_convert_i32_s, Instruction::F64ConvertI32S, + visit_f64_convert_i32_u, Instruction::F64ConvertI32U, + visit_f64_convert_i64_s, Instruction::F64ConvertI64S, + visit_f64_convert_i64_u, Instruction::F64ConvertI64U, + visit_f64_promote_f32, Instruction::F64PromoteF32, + visit_i32_reinterpret_f32, Instruction::I32ReinterpretF32, + visit_i64_reinterpret_f64, Instruction::I64ReinterpretF64, + visit_f32_reinterpret_i32, Instruction::F32ReinterpretI32, + visit_f64_reinterpret_i64, Instruction::F64ReinterpretI64, + + // sign_extension + visit_i32_extend8_s, Instruction::I32Extend8S, + visit_i32_extend16_s, Instruction::I32Extend16S, + visit_i64_extend8_s, Instruction::I64Extend8S, + visit_i64_extend16_s, Instruction::I64Extend16S, + visit_i64_extend32_s, Instruction::I64Extend32S, + + // Non-trapping Float-to-int Conversions + visit_i32_trunc_sat_f32_s, Instruction::I32TruncSatF32S, + visit_i32_trunc_sat_f32_u, Instruction::I32TruncSatF32U, + visit_i32_trunc_sat_f64_s, Instruction::I32TruncSatF64S, + visit_i32_trunc_sat_f64_u, Instruction::I32TruncSatF64U, + visit_i64_trunc_sat_f32_s, Instruction::I64TruncSatF32S, + visit_i64_trunc_sat_f32_u, Instruction::I64TruncSatF32U, + visit_i64_trunc_sat_f64_s, Instruction::I64TruncSatF64S, + visit_i64_trunc_sat_f64_u, Instruction::I64TruncSatF64U + } + + fn visit_local_get(&mut self, idx: u32) -> Self::Output { + if let Some(instruction) = self.instructions.last_mut() { + 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(()) + } else { + self.visit(Instruction::LocalGet(idx)) + } + } + + fn visit_local_set(&mut self, idx: u32) -> Self::Output { + self.visit(Instruction::LocalSet(idx)) + // if let Some(instruction) = self.instructions.last_mut() { + // match instruction { + // // Needs more testing, seems to make performance worse + // // Instruction::LocalGet(a) => *instruction = Instruction::LocalGetSet(*a, idx), + // _ => return self.visit(Instruction::LocalSet(idx)), + // }; + // // Ok(()) + // } else { + // self.visit(Instruction::LocalSet(idx)) + // } + } + + fn visit_local_tee(&mut self, idx: u32) -> Self::Output { + self.visit(Instruction::LocalTee(idx)) + } + + fn visit_i64_rotl(&mut self) -> Self::Output { + if self.instructions.len() < 2 { + return self.visit(Instruction::I64Rotl); + } + + 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_i32_add(&mut self) -> Self::Output { + self.visit(Instruction::I32Add) + // 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), + // } + } + + 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)) + } + + 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)) + } + + fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { + self.label_ptrs.push(self.instructions.len()); + self.visit(Instruction::If(BlockArgsPacked::new(convert_blocktype(ty)), 0, 0)) + } + + fn visit_else(&mut self) -> Self::Output { + self.label_ptrs.push(self.instructions.len()); + self.visit(Instruction::Else(0)) + } + + #[inline] + fn visit_end(&mut self) -> Self::Output { + let Some(label_pointer) = self.label_ptrs.pop() else { + return self.visit(Instruction::EndFunc); + }; + + let current_instr_ptr = self.instructions.len(); + + match self.instructions[label_pointer] { + Instruction::Else(ref mut else_instr_end_offset) => { + *else_instr_end_offset = (current_instr_ptr - label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support if blocks that large"); + + #[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 if_instruction = &mut self.instructions[if_label_pointer]; + let Instruction::If(_, ref mut else_offset, ref mut end_offset) = if_instruction else { + return Err(error()); + }; + + *else_offset = (label_pointer - if_label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + + *end_offset = (current_instr_ptr - if_label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + } + Instruction::Block(_, ref mut end_offset) + | Instruction::Loop(_, ref mut end_offset) + | Instruction::If(_, _, ref mut end_offset) => { + *end_offset = (current_instr_ptr - label_pointer) + .try_into() + .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + } + _ => { + return Err(crate::ParseError::UnsupportedOperator( + "Expected to end a block, but the last label was not a block".to_string(), + )) + } + }; + + self.visit(Instruction::EndBlockFrame) + } + + fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { + let def = targets.default(); + let instrs = targets + .targets() + .map(|t| t.map(Instruction::BrLabel)) + .collect::, wasmparser::BinaryReaderError>>() + .expect("BrTable targets are invalid, this should have been caught by the validator"); + + self.instructions + .extend(IntoIterator::into_iter([Instruction::BrTable(def, instrs.len() as u32)]).chain(instrs)); + + Ok(()) + } + + fn visit_call(&mut self, idx: u32) -> Self::Output { + self.visit(Instruction::Call(idx)) + } + + fn visit_call_indirect(&mut self, ty: u32, table: u32, _table_byte: u8) -> Self::Output { + self.visit(Instruction::CallIndirect(ty, table)) + } + + fn visit_memory_size(&mut self, mem: u32, mem_byte: u8) -> Self::Output { + self.visit(Instruction::MemorySize(mem, mem_byte)) + } + + fn visit_memory_grow(&mut self, mem: u32, mem_byte: u8) -> Self::Output { + self.visit(Instruction::MemoryGrow(mem, mem_byte)) + } + + fn visit_f32_const(&mut self, val: wasmparser::Ieee32) -> Self::Output { + self.visit(Instruction::F32Const(f32::from_bits(val.bits()))) + } + + fn visit_f64_const(&mut self, val: wasmparser::Ieee64) -> Self::Output { + self.visit(Instruction::F64Const(f64::from_bits(val.bits()))) + } + + // Bulk Memory Operations + + define_primitive_operands! { + visit_memory_init, Instruction::MemoryInit, u32, u32, + visit_memory_copy, Instruction::MemoryCopy, u32, u32, + visit_table_init, Instruction::TableInit, u32, u32 + } + define_primitive_operands! { + visit_memory_fill, Instruction::MemoryFill, u32, + visit_data_drop, Instruction::DataDrop, u32 + } + + fn visit_elem_drop(&mut self, _elem_index: u32) -> Self::Output { + self.unsupported("elem_drop") + } + + fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { + self.visit(Instruction::TableCopy { from: src_table, to: dst_table }) + } + + // Reference Types + + fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output { + self.visit(Instruction::RefNull(convert_heaptype(ty))) + } + + fn visit_ref_is_null(&mut self) -> Self::Output { + self.visit(Instruction::RefIsNull) + } + + fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { + self.visit(Instruction::Select(Some(convert_valtype(&ty)))) + } + + define_primitive_operands! { + visit_ref_func, Instruction::RefFunc, u32, + visit_table_fill, Instruction::TableFill, u32, + visit_table_get, Instruction::TableGet, u32, + visit_table_set, Instruction::TableSet, u32, + visit_table_grow, Instruction::TableGrow, u32, + visit_table_size, Instruction::TableSize, u32 + } +} diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index ad58b9e..406a82d 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -14,13 +14,13 @@ path="src/lib.rs" [dependencies] log={version="0.4", optional=true} -tinywasm-parser={version="0.4.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.4.0", path="../types", default-features=false} +tinywasm-parser={version="0.5.0", path="../parser", default-features=false, optional=true} +tinywasm-types={version="0.5.0", path="../types", default-features=false} libm={version="0.2", default-features=false} [dev-dependencies] wasm-testsuite={path="../wasm-testsuite"} -wast={version="70.0"} +wast={version="200.0"} owo-colors={version="4.0"} eyre={version="0.6"} serde_json={version="1.0"} diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 4f68eaa..85ffdf6 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -8,14 +8,6 @@ pub use tinywasm_parser::ParseError; /// Errors that can occur for TinyWasm operations #[derive(Debug)] pub enum Error { - #[cfg(feature = "std")] - /// An I/O error occurred - Io(crate::std::io::Error), - - #[cfg(feature = "parser")] - /// A parsing error occurred - ParseError(ParseError), - /// A WebAssembly trap occurred Trap(Trap), @@ -32,19 +24,27 @@ pub enum Error { FuncDidNotReturn, /// The stack is empty - StackUnderflow, + ValueStackUnderflow, /// The label stack is empty - LabelStackUnderflow, + BlockStackUnderflow, + + /// The call stack is empty + CallStackUnderflow, /// An invalid label type was encountered InvalidLabelType, - /// The call stack is empty - CallStackEmpty, - /// The store is not the one that the module instance was instantiated in InvalidStore, + + #[cfg(feature = "std")] + /// An I/O error occurred + Io(crate::std::io::Error), + + #[cfg(feature = "parser")] + /// A parsing error occurred + ParseError(ParseError), } #[derive(Debug)] @@ -57,6 +57,7 @@ pub enum LinkingError { /// The import name name: String, }, + /// A mismatched import type was encountered IncompatibleImportType { /// The module name @@ -188,13 +189,13 @@ impl Display for Error { Self::Trap(trap) => write!(f, "trap: {}", trap), Self::Linker(err) => write!(f, "linking error: {}", err), - Self::CallStackEmpty => write!(f, "call stack empty"), + Self::CallStackUnderflow => write!(f, "call stack empty"), Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {}", message), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {}", feature), Self::FuncDidNotReturn => write!(f, "function did not return"), - Self::LabelStackUnderflow => write!(f, "label stack underflow"), - Self::StackUnderflow => write!(f, "stack underflow"), + Self::BlockStackUnderflow => write!(f, "label stack underflow"), + Self::ValueStackUnderflow => write!(f, "value stack underflow"), Self::InvalidStore => write!(f, "invalid store"), } } diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 2088494..d7f7ca1 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -2,10 +2,8 @@ use crate::{log, runtime::RawWasmValue, unlikely, Function}; use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; use tinywasm_types::{FuncType, ModuleInstanceAddr, ValType, WasmValue}; -use crate::{ - runtime::{CallFrame, Stack}, - Error, FuncContext, Result, Store, -}; +use crate::runtime::{CallFrame, Stack}; +use crate::{Error, FuncContext, Result, Store}; #[derive(Debug)] /// A function handle @@ -24,6 +22,9 @@ impl FuncHandle { /// See #[inline] pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { + // Comments are ordered by the steps in the spec + // In this implementation, some steps are combined and ordered differently for performance reasons + // 3. Let func_ty be the function type let func_ty = &self.ty; @@ -37,7 +38,7 @@ impl FuncHandle { } // 5. For each value type and the corresponding value, check if types match - if !unlikely(func_ty.params.iter().zip(params).enumerate().all(|(i, (ty, param))| { + if !(func_ty.params.iter().zip(params).enumerate().all(|(i, (ty, param))| { if ty != ¶m.val_type() { log::error!("param type mismatch at index {}: expected {:?}, got {:?}", i, ty, param); false @@ -59,8 +60,8 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let call_frame = - CallFrame::new(wasm_func.clone(), func_inst.owner, params.iter().map(|v| RawWasmValue::from(*v))); + 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); // 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) @@ -115,6 +116,7 @@ impl FuncHandleTyped { R::from_wasm_value_tuple(&result) } } + macro_rules! impl_into_wasm_value_tuple { ($($T:ident),*) => { impl<$($T),*> IntoWasmValueTuple for ($($T,)*) @@ -142,21 +144,6 @@ macro_rules! impl_into_wasm_value_tuple_single { }; } -impl_into_wasm_value_tuple_single!(i32); -impl_into_wasm_value_tuple_single!(i64); -impl_into_wasm_value_tuple_single!(f32); -impl_into_wasm_value_tuple_single!(f64); - -impl_into_wasm_value_tuple!(); -impl_into_wasm_value_tuple!(T1); -impl_into_wasm_value_tuple!(T1, T2); -impl_into_wasm_value_tuple!(T1, T2, T3); -impl_into_wasm_value_tuple!(T1, T2, T3, T4); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); - macro_rules! impl_from_wasm_value_tuple { ($($T:ident),*) => { impl<$($T),*> FromWasmValueTuple for ($($T,)*) @@ -202,21 +189,6 @@ macro_rules! impl_from_wasm_value_tuple_single { }; } -impl_from_wasm_value_tuple_single!(i32); -impl_from_wasm_value_tuple_single!(i64); -impl_from_wasm_value_tuple_single!(f32); -impl_from_wasm_value_tuple_single!(f64); - -impl_from_wasm_value_tuple!(); -impl_from_wasm_value_tuple!(T1); -impl_from_wasm_value_tuple!(T1, T2); -impl_from_wasm_value_tuple!(T1, T2, T3); -impl_from_wasm_value_tuple!(T1, T2, T3, T4); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6, T7, T8); - pub trait ValTypesFromTuple { fn val_types() -> Box<[ValType]>; } @@ -270,19 +242,42 @@ impl ValTypesFromTuple for () { } } -impl ValTypesFromTuple for T1 -where - T1: ToValType, -{ +impl ValTypesFromTuple for T { #[inline] fn val_types() -> Box<[ValType]> { - Box::new([T1::to_val_type()]) + Box::new([T::to_val_type()]) } } +impl_from_wasm_value_tuple_single!(i32); +impl_from_wasm_value_tuple_single!(i64); +impl_from_wasm_value_tuple_single!(f32); +impl_from_wasm_value_tuple_single!(f64); + +impl_into_wasm_value_tuple_single!(i32); +impl_into_wasm_value_tuple_single!(i64); +impl_into_wasm_value_tuple_single!(f32); +impl_into_wasm_value_tuple_single!(f64); + impl_val_types_from_tuple!(T1); impl_val_types_from_tuple!(T1, T2); impl_val_types_from_tuple!(T1, T2, T3); impl_val_types_from_tuple!(T1, T2, T3, T4); impl_val_types_from_tuple!(T1, T2, T3, T4, T5); impl_val_types_from_tuple!(T1, T2, T3, T4, T5, T6); + +impl_from_wasm_value_tuple!(); +impl_from_wasm_value_tuple!(T1); +impl_from_wasm_value_tuple!(T1, T2); +impl_from_wasm_value_tuple!(T1, T2, T3); +impl_from_wasm_value_tuple!(T1, T2, T3, T4); +impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5); +impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); + +impl_into_wasm_value_tuple!(); +impl_into_wasm_value_tuple!(T1); +impl_into_wasm_value_tuple!(T1, T2); +impl_into_wasm_value_tuple!(T1, T2, T3); +impl_into_wasm_value_tuple!(T1, T2, T3, T4); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); +impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index e273838..1c5ca98 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -1,18 +1,12 @@ -#![allow(dead_code)] - +use alloc::boxed::Box; +use alloc::collections::BTreeMap; +use alloc::rc::Rc; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use core::fmt::Debug; -use crate::{ - func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}, - log, LinkingError, Result, -}; -use alloc::{ - boxed::Box, - collections::BTreeMap, - rc::Rc, - string::{String, ToString}, - vec::Vec, -}; +use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; +use crate::{log, LinkingError, Result}; use tinywasm_types::*; /// The internal representation of a function @@ -163,11 +157,11 @@ impl Extern { }; let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; - Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty }))) } - pub(crate) fn kind(&self) -> ExternalKind { + /// Get the kind of the external value + pub fn kind(&self) -> ExternalKind { match self { Self::Global { .. } => ExternalKind::Global, Self::Table { .. } => ExternalKind::Table, diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index cb12f1b..3fc4fe0 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,10 +1,8 @@ use alloc::{boxed::Box, format, rc::Rc, string::ToString}; use tinywasm_types::*; -use crate::{ - func::{FromWasmValueTuple, IntoWasmValueTuple}, - log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store, -}; +use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; +use crate::{log, Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store}; /// An instanciated WebAssembly module /// @@ -38,15 +36,18 @@ pub(crate) struct ModuleInstanceInner { impl ModuleInstance { // drop the module instance reference and swap it with another one + #[inline] pub(crate) fn swap(&mut self, other: Self) { self.0 = other.0; } + #[inline] pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { self.swap(store.get_module_instance_raw(other_addr)) } /// Get the module instance's address + #[inline] pub fn id(&self) -> ModuleInstanceAddr { self.0.idx } @@ -60,7 +61,7 @@ impl ModuleInstance { // don't need to create a auxiliary frame etc. let idx = store.next_module_instance_idx(); - log::error!("Instantiating module at index {}", idx); + log::info!("Instantiating module at index {}", idx); let imports = imports.unwrap_or_default(); let mut addrs = imports.link(store, &module, idx)?; @@ -120,44 +121,53 @@ impl ModuleInstance { Some(ExternVal::new(kind, *addr)) } - pub(crate) fn func_addrs(&self) -> &[FuncAddr] { - &self.0.func_addrs - } - + #[inline] pub(crate) fn new(inner: ModuleInstanceInner) -> Self { Self(Rc::new(inner)) } + #[inline] pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { self.0.types.get(addr as usize).expect("No func type for func, this is a bug") } + #[inline] + pub(crate) fn func_addrs(&self) -> &[FuncAddr] { + &self.0.func_addrs + } + // resolve a function address to the global store address + #[inline] pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { *self.0.func_addrs.get(addr as usize).expect("No func addr for func, this is a bug") } // resolve a table address to the global store address + #[inline] pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { *self.0.table_addrs.get(addr as usize).expect("No table addr for table, this is a bug") } // resolve a memory address to the global store address + #[inline] pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { *self.0.mem_addrs.get(addr as usize).expect("No mem addr for mem, this is a bug") } // resolve a data address to the global store address + #[inline] pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> MemAddr { *self.0.data_addrs.get(addr as usize).expect("No data addr for data, this is a bug") } // resolve a memory address to the global store address + #[inline] pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { *self.0.elem_addrs.get(addr as usize).expect("No elem addr for elem, this is a bug") } // resolve a global address to the global store address + #[inline] pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { self.0.global_addrs[addr as usize] } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 79b111c..583a68d 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -10,20 +10,25 @@ //! A tiny WebAssembly Runtime written in Rust //! //! TinyWasm provides a minimal WebAssembly runtime for executing WebAssembly modules. -//! It currently supports a subset of the WebAssembly MVP specification and is intended -//! to be useful for embedded systems and other environments where a full-featured -//! runtime is not required. +//! It currently supports all features of the WebAssembly MVP specification and is +//! designed to be easy to use and integrate in other projects. //! //! ## Features -//! - `std` (default): Enables the use of `std` and `std::io` for parsing from files and streams. -//! - `logging` (default): Enables logging via the `log` crate. -//! - `parser` (default): Enables the `tinywasm_parser` crate for parsing WebAssembly modules. +//!- **`std`**\ +//! Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. +//!- **`logging`**\ +//! Enables logging using the `log` crate. This is enabled by default. +//!- **`parser`**\ +//! Enables the `tinywasm-parser` crate. This is enabled by default. +//!- **`archive`**\ +//! Enables pre-parsing of archives. This is enabled by default. +//!- **`unsafe`**\ +//! Uses `unsafe` code to improve performance, particularly in Memory access //! -//! ## No-std support -//! TinyWasm supports `no_std` environments by disabling the `std` feature and registering -//! a custom allocator. This removes support for parsing from files and streams, -//! but otherwise the API is the same. -//! Additionally, to have proper error types, you currently need a `nightly` compiler to have the error trait in core. +//! With all these features disabled, TinyWasm only depends on `core`, `alloc` and `libm`. +//! By disabling `std`, you can use TinyWasm in `no_std` environments. This requires +//! a custom allocator and removes support for parsing from files and streams, but otherwise the API is the same. +//! Additionally, to have proper error types in `no_std`, you currently need a `nightly` compiler to use the unstable error trait in `core`. //! //! ## Getting Started //! The easiest way to get started is to use the [`Module::parse_bytes`] function to load a @@ -81,34 +86,29 @@ use log; pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); macro_rules! info ( ($($tt:tt)*) => {{}} ); - macro_rules! trace ( ($($tt:tt)*) => {{}} ); macro_rules! error ( ($($tt:tt)*) => {{}} ); pub(crate) use debug; pub(crate) use error; pub(crate) use info; - pub(crate) use trace; } mod error; -pub use error::*; - -mod store; -pub use store::*; - -mod module; -pub use module::Module; - -mod instance; -pub use instance::ModuleInstance; - -mod reference; -pub use reference::*; +pub use { + error::*, + func::{FuncHandle, FuncHandleTyped}, + imports::*, + instance::ModuleInstance, + module::Module, + reference::*, + store::*, +}; mod func; -pub use func::{FuncHandle, FuncHandleTyped}; - mod imports; -pub use imports::*; +mod instance; +mod module; +mod reference; +mod store; /// Runtime for executing WebAssembly modules. pub mod runtime; @@ -130,7 +130,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 5a2c4f7..08e76da 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -1,6 +1,5 @@ -use tinywasm_types::TinyWasmModule; - use crate::{Imports, ModuleInstance, Result, Store}; +use tinywasm_types::TinyWasmModule; #[derive(Debug)] /// A WebAssembly Module diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index a34e30b..6713a42 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,15 +1,12 @@ -use core::{ - cell::{Ref, RefCell, RefMut}, - ffi::CStr, -}; +use core::cell::{Ref, RefCell, RefMut}; +use core::ffi::CStr; + +use alloc::ffi::CString; +use alloc::rc::Rc; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; use crate::{GlobalInstance, MemoryInstance, Result}; -use alloc::{ - ffi::CString, - rc::Rc, - string::{String, ToString}, - vec::Vec, -}; use tinywasm_types::WasmValue; // This module essentially contains the public APIs to interact with the data stored in the store @@ -29,21 +26,21 @@ pub struct MemoryRefMut<'a> { impl<'a> MemoryRefLoad for MemoryRef<'a> { /// Load a slice of memory fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, 0, len) + self.instance.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, 0, len) + self.instance.load(offset, len) } } impl MemoryRef<'_> { /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, 0, len) + self.instance.load(offset, len) } /// Load a slice of memory as a vector @@ -55,7 +52,7 @@ impl MemoryRef<'_> { impl MemoryRefMut<'_> { /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { - self.instance.load(offset, 0, len) + self.instance.load(offset, len) } /// Load a slice of memory as a vector @@ -85,7 +82,7 @@ impl MemoryRefMut<'_> { /// Store a slice of memory pub fn store(&mut self, offset: usize, len: usize, data: &[u8]) -> Result<()> { - self.instance.store(offset, 0, data, len) + self.instance.store(offset, len, data) } } diff --git a/crates/tinywasm/src/runtime/interpreter/macros.rs b/crates/tinywasm/src/runtime/interpreter/macros.rs index a769b12..30f34fc 100644 --- a/crates/tinywasm/src/runtime/interpreter/macros.rs +++ b/crates/tinywasm/src/runtime/interpreter/macros.rs @@ -4,62 +4,73 @@ //! 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, $break_to_relative:ident) => {{ + if $cf.break_to(*$break_to_relative, &mut $stack.values, &mut $stack.blocks).is_none() { + match $stack.call_stack.is_empty() { + true => return Ok(ExecResult::Return), + false => return Ok(ExecResult::Call), + } + } + }}; +} + /// Load a value from memory macro_rules! mem_load { - ($type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ + ($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:ident, $stack:ident, $store:ident, $module:ident) => {{ - // TODO: there could be a lot of performance improvements here - let mem_idx = $module.resolve_mem_addr($arg.mem_addr); + ($load_type:ty, $target_type:ty, $arg:expr, $stack:ident, $store:ident, $module:ident) => {{ + let (mem_addr, offset) = $arg; + + let mem_idx = $module.resolve_mem_addr(*mem_addr); let mem = $store.get_mem(mem_idx as usize)?; let mem_ref = mem.borrow_mut(); - let addr = $stack.values.pop()?.raw_value(); - let addr = $arg.offset.checked_add(addr).ok_or_else(|| { + let addr: u64 = $stack.values.pop()?.into(); + let addr = offset.checked_add(addr).ok_or_else(|| { + cold(); Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: $arg.offset as usize, + offset: *offset as usize, len: core::mem::size_of::<$load_type>(), max: mem_ref.max_pages(), }) })?; let addr: usize = addr.try_into().ok().ok_or_else(|| { + cold(); Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: $arg.offset as usize, + offset: *offset as usize, len: core::mem::size_of::<$load_type>(), max: mem_ref.max_pages(), }) })?; const LEN: usize = core::mem::size_of::<$load_type>(); - let val = mem_ref.load_as::(addr, $arg.align as usize)?; - // let loaded_value = mem_ref.load_as::<$load_type>(addr, $arg.align as usize)?; + let val = mem_ref.load_as::(addr)?; $stack.values.push((val as $target_type).into()); }}; } /// Store a value to memory macro_rules! mem_store { - ($type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ - log::debug!("mem_store!({}, {:?})", stringify!($type), $arg); - + ($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:ident, $stack:ident, $store:ident, $module:ident) => {{ - // likewise, there could be a lot of performance improvements here - let mem_idx = $module.resolve_mem_addr($arg.mem_addr); - let mem = $store.get_mem(mem_idx as usize)?; - - let val = $stack.values.pop_t::<$store_type>()?; - let addr = $stack.values.pop()?.raw_value(); - - let val = val as $store_type; + ($store_type:ty, $target_type:ty, $arg:expr, $stack:ident, $store:ident, $module:ident) => {{ + let (mem_addr, offset) = $arg; + let mem = $store.get_mem($module.resolve_mem_addr(*mem_addr) as usize)?; + let val: $store_type = $stack.values.pop()?.into(); let val = val.to_le_bytes(); - - mem.borrow_mut().store(($arg.offset + addr) as usize, $arg.align as usize, &val, val.len())?; + let addr: u64 = $stack.values.pop()?.into(); + mem.borrow_mut().store((*offset + addr) as usize, val.len(), &val)?; }}; } @@ -69,46 +80,27 @@ macro_rules! mem_store { /// 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 - }; + (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"); - }; + ($from:ty, $to:ty) => {compile_error!("invalid float conversion")}; } /// Convert a value on the stack macro_rules! conv { - ($from:ty, $intermediate:ty, $to:ty, $stack:ident) => {{ - let a: $from = $stack.values.pop()?.into(); - $stack.values.push((a as $intermediate as $to).into()); - }}; ($from:ty, $to:ty, $stack:ident) => {{ - let a: $from = $stack.values.pop()?.into(); - $stack.values.push((a as $to).into()); + $stack.values.replace_top(|v| { + let a: $from = v.into(); + (a as $to).into() + }); }}; } @@ -123,11 +115,11 @@ macro_rules! checked_conv_float { let (min, max) = float_min_max!($from, $intermediate); let a: $from = $stack.values.pop()?.into(); - if a.is_nan() { + if unlikely(a.is_nan()) { return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); } - if a <= min || a >= max { + if unlikely(a <= min || a >= max) { return Err(Error::Trap(crate::Trap::IntegerOverflow)); } @@ -137,13 +129,9 @@ macro_rules! checked_conv_float { /// Compare two values on the stack macro_rules! comp { - ($op:tt, $ty:ty, $stack:ident) => {{ - comp!($op, $ty, $ty, $stack) - }}; - - ($op:tt, $intermediate:ty, $to:ty, $stack:ident) => {{ - let b = $stack.values.pop_t::<$intermediate>()? as $to; - let a = $stack.values.pop_t::<$intermediate>()? as $to; + ($op:tt, $to:ty, $stack:ident) => {{ + let b: $to = $stack.values.pop()?.into(); + let a: $to = $stack.values.pop()?.into(); $stack.values.push(((a $op b) as i32).into()); }}; } @@ -151,70 +139,58 @@ macro_rules! comp { /// Compare a value on the stack to zero macro_rules! comp_zero { ($op:tt, $ty:ty, $stack:ident) => {{ - let a = $stack.values.pop_t::<$ty>()?; + let a: $ty = $stack.values.pop()?.into(); $stack.values.push(((a $op 0) as i32).into()); }}; } /// Apply an arithmetic method to two values on the stack macro_rules! arithmetic { - ($op:ident, $ty:ty, $stack:ident) => {{ - arithmetic!($op, $ty, $ty, $stack) + ($op:ident, $to:ty, $stack:ident) => {{ + let b: $to = $stack.values.pop()?.into(); + let a: $to = $stack.values.pop()?.into(); + $stack.values.push((a.$op(b) as $to).into()); }}; // also allow operators such as +, - ($op:tt, $ty:ty, $stack:ident) => {{ - let b: $ty = $stack.values.pop_t()?; - let a: $ty = $stack.values.pop_t()?; + let b: $ty = $stack.values.pop()?.into(); + let a: $ty = $stack.values.pop()?.into(); $stack.values.push((a $op b).into()); }}; - - ($op:ident, $intermediate:ty, $to:ty, $stack:ident) => {{ - let b = $stack.values.pop_t::<$to>()? as $intermediate; - let a = $stack.values.pop_t::<$to>()? as $intermediate; - let result = a.$op(b); - $stack.values.push((result as $to).into()); - }}; } /// Apply an arithmetic method to a single value on the stack macro_rules! arithmetic_single { ($op:ident, $ty:ty, $stack:ident) => {{ let a: $ty = $stack.values.pop()?.into(); - let result = a.$op(); - $stack.values.push((result as $ty).into()); + $stack.values.push((a.$op() as $ty).into()); }}; ($op:ident, $from:ty, $to:ty, $stack:ident) => {{ let a: $from = $stack.values.pop()?.into(); - let result = a.$op(); - $stack.values.push((result as $to).into()); + $stack.values.push((a.$op() as $to).into()); }}; } /// Apply an arithmetic operation to two values on the stack with error checking macro_rules! checked_int_arithmetic { - // Direct conversion with error checking (two types) - ($from:tt, $to:tt, $stack:ident) => {{ - checked_int_arithmetic!($from, $to, $to, $stack) - }}; - - ($op:ident, $from:ty, $to:ty, $stack:ident) => {{ - let b = $stack.values.pop_t::<$from>()? as $to; - let a = $stack.values.pop_t::<$from>()? as $to; + ($op:ident, $to:ty, $stack:ident) => {{ + let b: $to = $stack.values.pop()?.into(); + let a: $to = $stack.values.pop()?.into(); - if b == 0 { + if unlikely(b == 0) { return Err(Error::Trap(crate::Trap::DivisionByZero)); } let result = a.$op(b).ok_or_else(|| Error::Trap(crate::Trap::IntegerOverflow))?; - // Cast back to original type if different - $stack.values.push((result as $from).into()); + $stack.values.push((result).into()); }}; } pub(super) use arithmetic; pub(super) use arithmetic_single; +pub(super) use break_to; pub(super) use checked_conv_float; pub(super) use checked_int_arithmetic; pub(super) use comp; diff --git a/crates/tinywasm/src/runtime/interpreter/mod.rs b/crates/tinywasm/src/runtime/interpreter/mod.rs index 79abbf3..007acaa 100644 --- a/crates/tinywasm/src/runtime/interpreter/mod.rs +++ b/crates/tinywasm/src/runtime/interpreter/mod.rs @@ -1,26 +1,23 @@ -use super::{InterpreterRuntime, Stack}; -use crate::{cold, log, unlikely}; -use crate::{ - runtime::{BlockType, CallFrame, LabelFrame}, - Error, FuncContext, ModuleInstance, Result, Store, Trap, -}; use alloc::format; use alloc::{string::ToString, vec::Vec}; use core::ops::{BitAnd, BitOr, BitXor, Neg}; use tinywasm_types::{ElementKind, ValType}; +use super::{InterpreterRuntime, Stack}; +use crate::runtime::{BlockFrame, BlockType, CallFrame}; +use crate::{cold, log, unlikely}; +use crate::{Error, FuncContext, ModuleInstance, 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::FExt; - -mod macros; -mod traits; - -use macros::*; -use traits::*; +use no_std_floats::NoStdFloatExt; impl InterpreterRuntime { // #[inline(always)] // a small 2-3% performance improvement in some cases @@ -32,28 +29,36 @@ impl InterpreterRuntime { let mut current_module = store.get_module_instance_raw(cf.func_instance.1); loop { - match exec_one(&mut cf, stack, store, ¤t_module)? { + match exec_one(&mut cf, stack, store, ¤t_module) { // Continue execution at the new top of the call stack - ExecResult::Call => { + Ok(ExecResult::Call) => { + let old = cf.block_ptr; cf = stack.call_stack.pop()?; + + if old > cf.block_ptr { + stack.blocks.truncate(old); + } + + // keeping the pointer seperate from the call frame is about 2% faster + // than storing it in the call frame if cf.func_instance.1 != current_module.id() { current_module.swap_with(cf.func_instance.1, store); } } // return from the function - ExecResult::Return => return Ok(()), + Ok(ExecResult::Return) => return Ok(()), // continue to the next instruction and increment the instruction pointer - ExecResult::Ok => cf.instr_ptr += 1, + Ok(ExecResult::Ok) => cf.instr_ptr += 1, // trap the program - ExecResult::Trap(trap) => { + Err(error) => { cf.instr_ptr += 1; // push the call frame back onto the stack so that it can be resumed // if the trap can be handled stack.call_stack.push(cf)?; - return Err(Error::Trap(trap)); + return Err(error); } } } @@ -64,44 +69,31 @@ enum ExecResult { Ok, Return, Call, - Trap(crate::Trap), -} - -// 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, $break_to_relative:ident) => {{ - if $cf.break_to(*$break_to_relative, &mut $stack.values).is_none() { - if $stack.call_stack.is_empty() { - return Ok(ExecResult::Return); - } else { - return Ok(ExecResult::Call); - } - } - }}; } /// Run a single step of the interpreter /// A seperate function is used so later, we can more easily implement /// a step-by-step debugger (using generators once they're stable?) -#[inline(always)] // this improves performance by more than 20% in some cases +// we want this be always part of the loop, rust just doesn't inline it as its too big +// this can be a 30%+ performance difference in some cases +#[inline(always)] fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &ModuleInstance) -> Result { let instrs = &cf.func_instance.0.instructions; + if unlikely(cf.instr_ptr >= instrs.len() || instrs.is_empty()) { - cold(); log::error!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instrs.len()); return Err(Error::Other(format!("instr_ptr out of bounds: {} >= {}", cf.instr_ptr, instrs.len()))); } + // A match statement is probably the fastest way to do this without + // unreasonable complexity. This *should* be optimized to a jump table. + // See https://pliniker.github.io/post/dispatchers/ use tinywasm_types::Instruction::*; - match &instrs[cf.instr_ptr] { + match cf.current_instruction() { Nop => { /* do nothing */ } Unreachable => { cold(); - return Ok(ExecResult::Trap(crate::Trap::Unreachable)); + return Err(crate::Trap::Unreachable.into()); } // we don't need to include the call frame here because it's already on the stack Drop => stack.values.pop().map(|_| ())?, @@ -136,7 +128,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M }; let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?; - let call_frame = CallFrame::new(wasm_func, func_inst.owner, params); + let call_frame = CallFrame::new(wasm_func, func_inst.owner, params, stack.blocks.len()); // push the call frame cf.instr_ptr += 1; // skip the call instruction @@ -152,14 +144,10 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M let table_idx = stack.values.pop_t::()?; // verify that the table is of the right type, this should be validated by the parser already - assert!(table.borrow().kind.element_type == ValType::RefFunc, "table is not of type funcref"); - let func_ref = { - table - .borrow() - .get(table_idx as usize)? - .addr() - .ok_or(Trap::UninitializedElement { index: table_idx as usize })? + let table = table.borrow(); + assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); + table.get(table_idx as usize)?.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize })? }; let func_inst = store.get_func(func_ref as usize)?.clone(); @@ -193,7 +181,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M } let params = stack.values.pop_n_rev(wasm_func.ty.params.len())?; - let call_frame = CallFrame::new(wasm_func, func_inst.owner, params); + let call_frame = CallFrame::new(wasm_func, func_inst.owner, params, stack.blocks.len()); // push the call frame cf.instr_ptr += 1; // skip the call instruction @@ -207,67 +195,70 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M If(args, else_offset, end_offset) => { // truthy value is on the top of the stack, so enter the then block if stack.values.pop_t::()? != 0 { - cf.enter_label( - LabelFrame::new( + cf.enter_block( + BlockFrame::new( cf.instr_ptr, - cf.instr_ptr + *end_offset, - stack.values.len(), // - params, + cf.instr_ptr + *end_offset as usize, + stack.values.len(), BlockType::If, - args, + &args.unpack(), module, ), &mut stack.values, + &mut stack.blocks, ); return Ok(ExecResult::Ok); } // falsy value is on the top of the stack - if let Some(else_offset) = else_offset { - let label = LabelFrame::new( - cf.instr_ptr + *else_offset, - cf.instr_ptr + *end_offset, - stack.values.len(), // - params, + if *else_offset != 0 { + let label = BlockFrame::new( + cf.instr_ptr + *else_offset as usize, + cf.instr_ptr + *end_offset as usize, + stack.values.len(), BlockType::Else, - args, + &args.unpack(), module, ); - cf.instr_ptr += *else_offset; - cf.enter_label(label, &mut stack.values); + cf.instr_ptr += *else_offset as usize; + cf.enter_block(label, &mut stack.values, &mut stack.blocks); } else { - cf.instr_ptr += *end_offset; + cf.instr_ptr += *end_offset as usize; } } Loop(args, end_offset) => { - cf.enter_label( - LabelFrame::new( + cf.enter_block( + BlockFrame::new( cf.instr_ptr, - cf.instr_ptr + *end_offset, - stack.values.len(), // - params, + cf.instr_ptr + *end_offset as usize, + stack.values.len(), BlockType::Loop, args, module, ), &mut stack.values, + &mut stack.blocks, ); } Block(args, end_offset) => { - cf.enter_label( - LabelFrame::new( + cf.enter_block( + BlockFrame::new( cf.instr_ptr, - cf.instr_ptr + *end_offset, - stack.values.len(), // - params, + cf.instr_ptr + *end_offset as usize, + stack.values.len(), BlockType::Block, args, module, ), &mut stack.values, + &mut stack.blocks, ); } BrTable(default, len) => { - let instr = instrs[cf.instr_ptr + 1..cf.instr_ptr + 1 + *len] + let instr = cf.instructions()[cf.instr_ptr + 1..cf.instr_ptr + 1 + *len as usize] .iter() .map(|i| match i { BrLabel(l) => Ok(*l), @@ -278,7 +269,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M }) .collect::>>()?; - if unlikely(instr.len() != *len) { + if unlikely(instr.len() != *len as usize) { panic!( "Expected {} BrLabel instructions, got {}, this should have been validated by the parser", len, @@ -304,10 +295,10 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M }, EndFunc => { - assert!( - cf.labels.len() == 0, - "endfunc: block frames not empty, this should have been validated by the parser" - ); + if stack.blocks.len() != cf.block_ptr { + cold(); + panic!("endfunc: block frames not empty, this should have been validated by the parser"); + } match stack.call_stack.is_empty() { true => return Ok(ExecResult::Return), @@ -317,28 +308,35 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M // We're essentially using else as a EndBlockFrame instruction for if blocks Else(end_offset) => { - let Some(block) = cf.labels.pop() else { + let Some(block) = stack.blocks.pop() else { cold(); panic!("else: no label to end, this should have been validated by the parser"); }; let res_count = block.results; stack.values.truncate_keep(block.stack_ptr, res_count); - cf.instr_ptr += *end_offset; + cf.instr_ptr += *end_offset as usize; } EndBlockFrame => { // remove the label from the label stack - let Some(block) = cf.labels.pop() else { + let Some(block) = stack.blocks.pop() else { cold(); - panic!("end: no label to end, this should have been validated by the parser"); + panic!("end blockframe: no label to end, this should have been validated by the parser"); }; - stack.values.truncate_keep(block.stack_ptr, block.results) + + stack.values.truncate_keep(block.stack_ptr, block.results); } LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)), LocalSet(local_index) => cf.set_local(*local_index as usize, stack.values.pop()?), - LocalTee(local_index) => cf.set_local(*local_index as usize, *stack.values.last()?), + LocalTee(local_index) => { + let local = stack.values.last(); + cf.set_local( + *local_index as usize, + *local.expect("localtee: stack is empty. this should have been validated by the parser"), + ); + } GlobalGet(global_index) => { let idx = module.resolve_global_addr(*global_index); @@ -357,8 +355,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M F64Const(val) => stack.values.push((*val).into()), MemorySize(addr, byte) => { - if *byte != 0 { - cold(); + if unlikely(*byte != 0) { return Err(Error::UnsupportedFeature("memory.size with byte != 0".to_string())); } @@ -368,8 +365,7 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M } MemoryGrow(addr, byte) => { - if *byte != 0 { - cold(); + if unlikely(*byte != 0) { return Err(Error::UnsupportedFeature("memory.grow with byte != 0".to_string())); } @@ -390,9 +386,9 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M // Bulk memory operations MemoryCopy(from, to) => { - let size = stack.values.pop_t::()?; - let src = stack.values.pop_t::()?; - let dst = stack.values.pop_t::()?; + let size: i32 = stack.values.pop()?.into(); + let src: i32 = stack.values.pop()?.into(); + let dst: i32 = stack.values.pop()?.into(); let mem = store.get_mem(module.resolve_mem_addr(*from) as usize)?; let mut mem = mem.borrow_mut(); @@ -404,14 +400,14 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M // copy between two memories let mem2 = store.get_mem(module.resolve_mem_addr(*to) as usize)?; let mut mem2 = mem2.borrow_mut(); - mem2.copy_from_slice(dst as usize, mem.load(src as usize, 0, size as usize)?)?; + mem2.copy_from_slice(dst as usize, mem.load(src as usize, size as usize)?)?; } } MemoryFill(addr) => { - let size = stack.values.pop_t::()?; - let val = stack.values.pop_t::()?; - let dst = stack.values.pop_t::()?; + 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) as usize)?; let mut mem = mem.borrow_mut(); @@ -423,26 +419,20 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M let offset = stack.values.pop_t::()? as usize; let dst = stack.values.pop_t::()? as usize; - let data_idx = module.resolve_data_addr(*data_index); - let Some(ref data) = store.get_data(data_idx as usize)?.data else { - cold(); - return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()); + let data = match &store.get_data(module.resolve_data_addr(*data_index) as usize)?.data { + Some(data) => data, + None => return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()), }; - let mem_idx = module.resolve_mem_addr(*mem_index); - let mem = store.get_mem(mem_idx as usize)?; - - let data_len = data.len(); - if offset + size > data_len { - cold(); - return Err(Trap::MemoryOutOfBounds { offset, len: size, max: data_len }.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) as usize)?; let mut mem = mem.borrow_mut(); - let data = &data[offset..(offset + size)]; // mem.store checks bounds - mem.store(dst, 0, data, size)?; + mem.store(dst, size, &data[offset..(offset + size)])?; } DataDrop(data_index) => { @@ -451,30 +441,30 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M data.drop(); } - I32Store(arg) => mem_store!(i32, arg, stack, store, module), - I64Store(arg) => mem_store!(i64, arg, stack, store, module), - F32Store(arg) => mem_store!(f32, arg, stack, store, module), - F64Store(arg) => mem_store!(f64, arg, stack, store, module), - I32Store8(arg) => mem_store!(i8, i32, arg, stack, store, module), - I32Store16(arg) => mem_store!(i16, i32, arg, stack, store, module), - I64Store8(arg) => mem_store!(i8, i64, arg, stack, store, module), - I64Store16(arg) => mem_store!(i16, i64, arg, stack, store, module), - I64Store32(arg) => mem_store!(i32, i64, arg, stack, store, module), - - I32Load(arg) => mem_load!(i32, arg, stack, store, module), - I64Load(arg) => mem_load!(i64, arg, stack, store, module), - F32Load(arg) => mem_load!(f32, arg, stack, store, module), - F64Load(arg) => mem_load!(f64, arg, stack, store, module), - I32Load8S(arg) => mem_load!(i8, i32, arg, stack, store, module), - I32Load8U(arg) => mem_load!(u8, i32, arg, stack, store, module), - I32Load16S(arg) => mem_load!(i16, i32, arg, stack, store, module), - I32Load16U(arg) => mem_load!(u16, i32, arg, stack, store, module), - I64Load8S(arg) => mem_load!(i8, i64, arg, stack, store, module), - I64Load8U(arg) => mem_load!(u8, i64, arg, stack, store, module), - I64Load16S(arg) => mem_load!(i16, i64, arg, stack, store, module), - I64Load16U(arg) => mem_load!(u16, i64, arg, stack, store, module), - I64Load32S(arg) => mem_load!(i32, i64, arg, stack, store, module), - I64Load32U(arg) => mem_load!(u32, i64, arg, stack, store, module), + 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), @@ -491,29 +481,29 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M I32LtS => comp!(<, i32, stack), I64LtS => comp!(<, i64, stack), - I32LtU => comp!(<, i32, u32, stack), - I64LtU => comp!(<, i64, u64, 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!(<=, i32, u32, stack), - I64LeU => comp!(<=, i64, u64, 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!(>=, i32, u32, stack), - I64GeU => comp!(>=, i64, u64, 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!(>, i32, u32, stack), - I64GtU => comp!(>, i64, u64, stack), + I32GtU => comp!(>, u32, stack), + I64GtU => comp!(>, u64, stack), F32Gt => comp!(>, f32, stack), F64Gt => comp!(>, f64, stack), @@ -538,13 +528,13 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M // 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, i32, u32, stack), - I64DivU => checked_int_arithmetic!(checked_div, i64, u64, 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, i32, u32, stack), - I64RemU => checked_int_arithmetic!(checked_wrapping_rem, i64, u64, 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), @@ -556,8 +546,8 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M I64Shl => arithmetic!(wasm_shl, i64, stack), I32ShrS => arithmetic!(wasm_shr, i32, stack), I64ShrS => arithmetic!(wasm_shr, i64, stack), - I32ShrU => arithmetic!(wasm_shr, u32, i32, stack), - I64ShrU => arithmetic!(wasm_shr, u64, 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), @@ -574,16 +564,16 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M F32ConvertI64S => conv!(i64, f32, stack), F64ConvertI32S => conv!(i32, f64, stack), F64ConvertI64S => conv!(i64, f64, stack), - F32ConvertI32U => conv!(i32, u32, f32, stack), - F32ConvertI64U => conv!(i64, u64, f32, stack), - F64ConvertI32U => conv!(i32, u32, f64, stack), - F64ConvertI64U => conv!(i64, u64, f64, stack), - I32Extend8S => conv!(i32, i8, i32, stack), - I32Extend16S => conv!(i32, i16, i32, stack), - I64Extend8S => conv!(i64, i8, i64, stack), - I64Extend16S => conv!(i64, i16, i64, stack), - I64Extend32S => conv!(i64, i32, i64, stack), - I64ExtendI32U => conv!(i32, u32, i64, 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), @@ -600,22 +590,19 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M F64Floor => arithmetic_single!(floor, f64, stack), F32Trunc => arithmetic_single!(trunc, f32, stack), F64Trunc => arithmetic_single!(trunc, f64, stack), - F32Nearest => arithmetic_single!(wasm_nearest, f32, stack), - F64Nearest => arithmetic_single!(wasm_nearest, 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!(wasm_min, f32, stack), - F64Min => arithmetic!(wasm_min, f64, stack), - F32Max => arithmetic!(wasm_max, f32, stack), - F64Max => arithmetic!(wasm_max, 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 => {} + I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} // unsigned versions of these are a bit broken atm I32TruncF32S => checked_conv_float!(f32, i32, stack), @@ -676,6 +663,48 @@ fn exec_one(cf: &mut CallFrame, stack: &mut Stack, store: &mut Store, module: &M I64TruncSatF64S => arithmetic_single!(trunc, f64, i64, stack), I64TruncSatF64U => arithmetic_single!(trunc, f64, u64, stack), + // custom instructions + LocalGet2(a, b) => { + stack.values.extend_from_slice(&[cf.get_local(*a as usize), cf.get_local(*b as usize)]); + } + LocalGet3(a, b, c) => { + stack.values.extend_from_slice(&[ + cf.get_local(*a as usize), + cf.get_local(*b as usize), + cf.get_local(*c as usize), + ]); + } + // LocalGet4(a, b, c, d) => { + // stack.values.extend_from_slice(&[ + // cf.get_local(*a as usize), + // cf.get_local(*b as usize), + // cf.get_local(*c as usize), + // cf.get_local(*d as usize), + // ]); + // } + LocalTeeGet(a, b) => { + #[inline] + fn local_tee_get(cf: &mut CallFrame, stack: &mut Stack, a: u32, b: u32) -> Result<()> { + let last = *stack + .values + .last() + .expect("localtee: stack is empty. this should have been validated by the parser"); + cf.set_local(a as usize, last); + stack.values.push(cf.get_local(b as usize)); + Ok(()) + } + local_tee_get(cf, stack, *a, *b)?; + } + LocalGetSet(a, b) => { + let a = cf.get_local(*a as usize); + cf.set_local(*b as usize, a); + } + I64XorConstRotl(rotate_by) => { + let val = stack.values.pop_t::()?; + let mask = stack.values.pop_t::()?; + let res = val ^ mask; + stack.values.push(res.rotate_left(*rotate_by as u32).into()); + } i => { cold(); log::error!("unimplemented instruction: {:?}", i); diff --git a/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs b/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs index 5620249..5b9471e 100644 --- a/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/runtime/interpreter/no_std_floats.rs @@ -1,4 +1,4 @@ -pub(super) trait FExt { +pub(super) trait NoStdFloatExt { fn round(self) -> Self; fn abs(self) -> Self; fn signum(self) -> Self; @@ -9,85 +9,26 @@ pub(super) trait FExt { fn copysign(self, other: Self) -> Self; } -impl FExt for f64 { - #[inline] - fn round(self) -> Self { - libm::round(self) - } - - #[inline] - fn abs(self) -> Self { - libm::fabs(self) - } - - #[inline] - fn signum(self) -> Self { - libm::copysign(1.0, self) - } - - #[inline] - fn ceil(self) -> Self { - libm::ceil(self) - } - - #[inline] - fn floor(self) -> Self { - libm::floor(self) - } - - #[inline] - fn trunc(self) -> Self { - libm::trunc(self) - } - - #[inline] - fn sqrt(self) -> Self { - libm::sqrt(self) - } - - #[inline] - fn copysign(self, other: Self) -> Self { - libm::copysign(self, other) - } +#[rustfmt::skip] +impl NoStdFloatExt for f64 { + #[inline] fn round(self) -> Self { libm::round(self) } + #[inline] fn abs(self) -> Self { libm::fabs(self) } + #[inline] fn signum(self) -> Self { libm::copysign(1.0, self) } + #[inline] fn ceil(self) -> Self { libm::ceil(self) } + #[inline] fn floor(self) -> Self { libm::floor(self) } + #[inline] fn trunc(self) -> Self { libm::trunc(self) } + #[inline] fn sqrt(self) -> Self { libm::sqrt(self) } + #[inline] fn copysign(self, other: Self) -> Self { libm::copysign(self, other) } } -impl FExt for f32 { - #[inline] - fn round(self) -> Self { - libm::roundf(self) - } - - #[inline] - fn abs(self) -> Self { - libm::fabsf(self) - } - - #[inline] - fn signum(self) -> Self { - libm::copysignf(1.0, self) - } - - #[inline] - fn ceil(self) -> Self { - libm::ceilf(self) - } - - #[inline] - fn floor(self) -> Self { - libm::floorf(self) - } - - #[inline] - fn trunc(self) -> Self { - libm::truncf(self) - } - - #[inline] - fn sqrt(self) -> Self { - libm::sqrtf(self) - } - #[inline] - fn copysign(self, other: Self) -> Self { - libm::copysignf(self, other) - } +#[rustfmt::skip] +impl NoStdFloatExt for f32 { + #[inline] fn round(self) -> Self { libm::roundf(self) } + #[inline] fn abs(self) -> Self { libm::fabsf(self) } + #[inline] fn signum(self) -> Self { libm::copysignf(1.0, self) } + #[inline] fn ceil(self) -> Self { libm::ceilf(self) } + #[inline] fn floor(self) -> Self { libm::floorf(self) } + #[inline] fn trunc(self) -> Self { libm::truncf(self) } + #[inline] fn sqrt(self) -> Self { libm::sqrtf(self) } + #[inline] fn copysign(self, other: Self) -> Self { libm::copysignf(self, other) } } diff --git a/crates/tinywasm/src/runtime/interpreter/traits.rs b/crates/tinywasm/src/runtime/interpreter/traits.rs index 06a97e3..7aeb3b7 100644 --- a/crates/tinywasm/src/runtime/interpreter/traits.rs +++ b/crates/tinywasm/src/runtime/interpreter/traits.rs @@ -5,42 +5,34 @@ where fn checked_wrapping_rem(self, rhs: Self) -> Option; } -pub(crate) trait WasmFloatOps { - fn wasm_min(self, other: Self) -> Self; - fn wasm_max(self, other: Self) -> Self; - fn wasm_nearest(self) -> Self; +pub(crate) trait TinywasmFloatExt { + fn tw_minimum(self, other: Self) -> Self; + fn tw_maximum(self, other: Self) -> Self; + fn tw_nearest(self) -> Self; } #[cfg(not(feature = "std"))] -use super::no_std_floats::FExt; +use super::no_std_floats::NoStdFloatExt; macro_rules! impl_wasm_float_ops { ($($t:ty)*) => ($( - impl WasmFloatOps for $t { + impl TinywasmFloatExt for $t { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest - fn wasm_nearest(self) -> Self { + fn tw_nearest(self) -> Self { match self { x if x.is_nan() => x, // preserve NaN x if x.is_infinite() || x == 0.0 => x, // preserve infinities and zeros x if (0.0..=0.5).contains(&x) => 0.0, x if (-0.5..0.0).contains(&x) => -0.0, - // x => x.round(), x => { // Handle normal and halfway cases let rounded = x.round(); let diff = (x - rounded).abs(); - - if diff == 0.5 { - // Halfway case: round to even - if rounded % 2.0 == 0.0 { - rounded // Already even - } else { - rounded - x.signum() // Make even - } - } else { - // Normal case - rounded + if diff != 0.5 || rounded % 2.0 == 0.0 { + return rounded } + + rounded - x.signum() // Make even } } } @@ -48,32 +40,24 @@ macro_rules! impl_wasm_float_ops { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmin // Based on f32::minimum (which is not yet stable) #[inline] - fn wasm_min(self, other: Self) -> Self { - if self < other { - self - } else if other < self { - other - } else if self == other { - if self.is_sign_negative() && other.is_sign_positive() { self } else { other } - } else { - // At least one input is NaN. Use `+` to perform NaN propagation and quieting. - self + other + fn tw_minimum(self, other: Self) -> Self { + match self.partial_cmp(&other) { + Some(core::cmp::Ordering::Less) => self, + Some(core::cmp::Ordering::Greater) => other, + Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { self } else { other }, + None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. } } // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax // Based on f32::maximum (which is not yet stable) #[inline] - fn wasm_max(self, other: Self) -> Self { - if self > other { - self - } else if other > self { - other - } else if self == other { - if self.is_sign_negative() && other.is_sign_positive() { other } else { self } - } else { - // At least one input is NaN. Use `+` to perform NaN propagation and quieting. - self + other + fn tw_maximum(self, other: Self) -> Self { + match self.partial_cmp(&other) { + Some(core::cmp::Ordering::Greater) => self, + Some(core::cmp::Ordering::Less) => other, + Some(core::cmp::Ordering::Equal) => if self.is_sign_negative() && other.is_sign_positive() { other } else { self }, + None => self + other, // At least one input is NaN. Use `+` to perform NaN propagation and quieting. } } } diff --git a/crates/tinywasm/src/runtime/mod.rs b/crates/tinywasm/src/runtime/mod.rs index 3b9a57c..8c22ce0 100644 --- a/crates/tinywasm/src/runtime/mod.rs +++ b/crates/tinywasm/src/runtime/mod.rs @@ -2,11 +2,10 @@ mod interpreter; mod stack; mod value; +use crate::Result; pub use stack::*; pub(crate) use value::RawWasmValue; -use crate::Result; - #[allow(rustdoc::private_intra_doc_links)] /// A WebAssembly runtime. /// diff --git a/crates/tinywasm/src/runtime/stack.rs b/crates/tinywasm/src/runtime/stack.rs index 31c41f0..3db3c4b 100644 --- a/crates/tinywasm/src/runtime/stack.rs +++ b/crates/tinywasm/src/runtime/stack.rs @@ -1,20 +1,21 @@ -mod blocks; +mod block_stack; mod call_stack; mod value_stack; use self::{call_stack::CallStack, value_stack::ValueStack}; -pub(crate) use blocks::{BlockType, LabelFrame}; +pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; pub(crate) use call_stack::CallFrame; /// A WebAssembly Stack #[derive(Debug)] pub struct Stack { pub(crate) values: ValueStack, + pub(crate) blocks: BlockStack, pub(crate) call_stack: CallStack, } impl Stack { pub(crate) fn new(call_frame: CallFrame) -> Self { - Self { values: ValueStack::default(), call_stack: CallStack::new(call_frame) } + Self { values: ValueStack::default(), blocks: BlockStack::default(), call_stack: CallStack::new(call_frame) } } } diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/block_stack.rs similarity index 72% rename from crates/tinywasm/src/runtime/stack/blocks.rs rename to crates/tinywasm/src/runtime/stack/block_stack.rs index f302e59..cf7a0d4 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/block_stack.rs @@ -1,31 +1,28 @@ +use crate::{unlikely, ModuleInstance}; use alloc::vec::Vec; use tinywasm_types::BlockArgs; -use crate::{unlikely, ModuleInstance}; - -#[derive(Debug, Clone)] -pub(crate) struct Labels(Vec); // TODO: maybe Box<[LabelFrame]> by analyzing the lable count when parsing the module? - -impl Labels { - pub(crate) fn new() -> Self { - // this is somehow a lot faster than Vec::with_capacity(128) or even using Default::default() in the benchmarks - Self(Vec::new()) - } +#[derive(Debug, Clone, Default)] +pub(crate) struct BlockStack(Vec); // TODO: maybe Box<[LabelFrame]> by analyzing the lable count when parsing the module? +impl BlockStack { + #[inline] pub(crate) fn len(&self) -> usize { self.0.len() } #[inline] - pub(crate) fn push(&mut self, label: LabelFrame) { - self.0.push(label); + pub(crate) fn push(&mut self, block: BlockFrame) { + self.0.push(block); } #[inline] /// get the label at the given index, where 0 is the top of the stack - pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { + pub(crate) fn get_relative_to(&self, index: usize, offset: usize) -> Option<&BlockFrame> { + let len = self.0.len() - offset; + // the vast majority of wasm functions don't use break to return - if unlikely(index >= self.0.len()) { + if unlikely(index >= len) { return None; } @@ -33,7 +30,7 @@ impl Labels { } #[inline] - pub(crate) fn pop(&mut self) -> Option { + pub(crate) fn pop(&mut self) -> Option { self.0.pop() } @@ -45,7 +42,7 @@ impl Labels { } #[derive(Debug, Clone)] -pub(crate) struct LabelFrame { +pub(crate) struct BlockFrame { // position of the instruction pointer when the block was entered pub(crate) instr_ptr: usize, // position of the end instruction of the block @@ -58,7 +55,7 @@ pub(crate) struct LabelFrame { pub(crate) ty: BlockType, } -impl LabelFrame { +impl BlockFrame { #[inline] pub(crate) fn new( instr_ptr: usize, diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index dcbfcac..12270d1 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -1,14 +1,12 @@ -use crate::unlikely; -use crate::{ - runtime::{BlockType, RawWasmValue}, - Error, Result, Trap, -}; use alloc::{boxed::Box, rc::Rc, vec::Vec}; -use tinywasm_types::{ModuleInstanceAddr, WasmFunction}; +use tinywasm_types::{Instruction, ModuleInstanceAddr, WasmFunction}; + +use crate::runtime::{BlockType, RawWasmValue}; +use crate::unlikely; +use crate::{Error, Result, Trap}; -use super::{blocks::Labels, LabelFrame}; +use super::BlockFrame; -// minimum call stack size const CALL_STACK_SIZE: usize = 128; const CALL_STACK_MAX_SIZE: usize = 1024; @@ -34,7 +32,7 @@ impl CallStack { pub(crate) fn pop(&mut self) -> Result { match self.stack.pop() { Some(frame) => Ok(frame), - None => Err(Error::CallStackEmpty), + None => Err(Error::CallStackUnderflow), } } @@ -51,26 +49,35 @@ impl CallStack { #[derive(Debug, Clone)] pub(crate) struct CallFrame { pub(crate) instr_ptr: usize, - // pub(crate) module: ModuleInstanceAddr, + pub(crate) block_ptr: usize, pub(crate) func_instance: (Rc, ModuleInstanceAddr), - pub(crate) labels: Labels, pub(crate) locals: Box<[RawWasmValue]>, } impl CallFrame { /// Push a new label to the label stack and ensure the stack has the correct values - pub(crate) fn enter_label(&mut self, label_frame: LabelFrame, stack: &mut super::ValueStack) { - if label_frame.params > 0 { - stack.extend_from_within((label_frame.stack_ptr - label_frame.params)..label_frame.stack_ptr); + pub(crate) fn enter_block( + &mut self, + block_frame: BlockFrame, + values: &mut super::ValueStack, + blocks: &mut super::BlockStack, + ) { + if block_frame.params > 0 { + values.extend_from_within((block_frame.stack_ptr - block_frame.params)..block_frame.stack_ptr); } - self.labels.push(label_frame); + blocks.push(block_frame); } /// 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, value_stack: &mut super::ValueStack) -> Option<()> { - let break_to = self.labels.get_relative_to_top(break_to_relative as usize)?; + 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 as usize, 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 @@ -80,12 +87,12 @@ impl CallFrame { self.instr_ptr = break_to.instr_ptr; // We also want to push the params to the stack - value_stack.break_to(break_to.stack_ptr, break_to.params); + 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) - self.labels.truncate(self.labels.len() - break_to_relative as usize); + blocks.truncate(blocks.len() - break_to_relative as usize); return Some(()); } } @@ -93,13 +100,13 @@ impl CallFrame { 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 - value_stack.break_to(break_to.stack_ptr, break_to.results); + 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.end_instr_ptr; // we also want to trim the label stack, including the block - self.labels.truncate(self.labels.len() - (break_to_relative as usize + 1)); + blocks.truncate(blocks.len() - (break_to_relative as usize + 1)); } } @@ -112,6 +119,7 @@ impl CallFrame { wasm_func_inst: Rc, owner: ModuleInstanceAddr, params: impl Iterator + ExactSizeIterator, + block_ptr: usize, ) -> Self { let locals = { let local_types = &wasm_func_inst.locals; @@ -122,7 +130,7 @@ impl CallFrame { locals.into_boxed_slice() }; - Self { instr_ptr: 0, func_instance: (wasm_func_inst, owner), locals, labels: Labels::new() } + Self { instr_ptr: 0, func_instance: (wasm_func_inst, owner), locals, block_ptr } } #[inline] @@ -134,4 +142,14 @@ impl CallFrame { pub(crate) fn get_local(&self, local_index: usize) -> RawWasmValue { self.locals[local_index] } + + #[inline(always)] + pub(crate) fn instructions(&self) -> &[Instruction] { + &self.func_instance.0.instructions + } + + #[inline(always)] + pub(crate) fn current_instruction(&self) -> &Instruction { + &self.func_instance.0.instructions[self.instr_ptr] + } } diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index 9b8f82d..5903228 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -5,7 +5,6 @@ use alloc::vec::Vec; use tinywasm_types::{ValType, WasmValue}; pub(crate) const MIN_VALUE_STACK_SIZE: usize = 1024; -// pub(crate) const MAX_VALUE_STACK_SIZE: usize = 1024 * 1024; #[derive(Debug)] pub(crate) struct ValueStack { @@ -26,11 +25,22 @@ impl ValueStack { #[inline] pub(crate) fn extend_from_typed(&mut self, values: &[WasmValue]) { - if values.is_empty() { + self.stack.extend(values.iter().map(|v| RawWasmValue::from(*v))); + } + + #[inline] + pub(crate) fn extend_from_slice(&mut self, values: &[RawWasmValue]) { + self.stack.extend_from_slice(values); + } + + #[inline] + pub(crate) fn replace_top(&mut self, func: impl FnOnce(RawWasmValue) -> RawWasmValue) { + let len = self.stack.len(); + if unlikely(len == 0) { return; } - - self.stack.extend(values.iter().map(|v| RawWasmValue::from(*v))); + let top = self.stack[len - 1]; + self.stack[len - 1] = func(top); } #[inline] @@ -53,20 +63,14 @@ impl ValueStack { self.stack.drain(remove_start_index..remove_end_index); } - #[inline] + #[inline(always)] pub(crate) fn push(&mut self, value: RawWasmValue) { self.stack.push(value); } #[inline] - pub(crate) fn last(&self) -> Result<&RawWasmValue> { - match self.stack.last() { - Some(v) => Ok(v), - None => { - cold(); - Err(Error::StackUnderflow) - } - } + pub(crate) fn last(&self) -> Option<&RawWasmValue> { + self.stack.last() } #[inline] @@ -75,7 +79,7 @@ impl ValueStack { Some(v) => Ok(v.into()), None => { cold(); - Err(Error::StackUnderflow) + Err(Error::ValueStackUnderflow) } } } @@ -86,7 +90,7 @@ impl ValueStack { Some(v) => Ok(v), None => { cold(); - Err(Error::StackUnderflow) + Err(Error::ValueStackUnderflow) } } } @@ -106,7 +110,7 @@ impl ValueStack { pub(crate) fn last_n(&self, n: usize) -> Result<&[RawWasmValue]> { let len = self.stack.len(); if unlikely(len < n) { - return Err(Error::StackUnderflow); + return Err(Error::ValueStackUnderflow); } Ok(&self.stack[len - n..len]) } @@ -115,7 +119,7 @@ impl ValueStack { pub(crate) fn pop_n_rev(&mut self, n: usize) -> Result> { let len = self.stack.len(); if unlikely(len < n) { - return Err(Error::StackUnderflow); + return Err(Error::ValueStackUnderflow); } let res = self.stack.drain((len - n)..); Ok(res) diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs index 5341361..4835eab 100644 --- a/crates/tinywasm/src/runtime/value.rs +++ b/crates/tinywasm/src/runtime/value.rs @@ -1,5 +1,4 @@ use core::fmt::Debug; - use tinywasm_types::{ValType, WasmValue}; /// A raw wasm value. @@ -8,39 +7,43 @@ use tinywasm_types::{ValType, WasmValue}; /// /// See [`WasmValue`] for the public representation. #[derive(Clone, Copy, Default, PartialEq, Eq)] -#[repr(transparent)] -pub struct RawWasmValue(u64); +// pub struct RawWasmValue([u8; 16]); +pub struct RawWasmValue([u8; 8]); impl Debug for RawWasmValue { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "RawWasmValue({})", self.0 as i64) // cast to i64 so at least negative numbers for i32 and i64 are printed correctly + write!(f, "RawWasmValue({})", 0) } } impl RawWasmValue { #[inline(always)] - pub fn raw_value(&self) -> u64 { + 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.0 as i32), - ValType::I64 => WasmValue::I64(self.0 as i64), - ValType::F32 => WasmValue::F32(f32::from_bits(self.0 as u32)), - ValType::F64 => WasmValue::F64(f64::from_bits(self.0)), + ValType::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::V128 => WasmValue::V128(self.into()), ValType::RefExtern => { - if self.0 == -1i64 as u64 { + let val: i64 = self.into(); + if val < 0 { WasmValue::RefNull(ValType::RefExtern) } else { - WasmValue::RefExtern(self.0 as u32) + WasmValue::RefExtern(val as u32) } } ValType::RefFunc => { - if self.0 == -1i64 as u64 { + let val: i64 = self.into(); + if val < 0 { WasmValue::RefNull(ValType::RefFunc) } else { - WasmValue::RefFunc(self.0 as u32) + WasmValue::RefFunc(val as u32) } } } @@ -48,15 +51,17 @@ impl RawWasmValue { } impl From for RawWasmValue { + #[inline] fn from(v: WasmValue) -> Self { match v { - WasmValue::I32(i) => Self(i as u64), - WasmValue::I64(i) => Self(i as u64), - WasmValue::F32(i) => Self(i.to_bits() as u64), - WasmValue::F64(i) => Self(i.to_bits()), - WasmValue::RefExtern(v) => Self(v as i64 as u64), - WasmValue::RefFunc(v) => Self(v as i64 as u64), - WasmValue::RefNull(_) => Self(-1i64 as u64), + 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::V128(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), } } } @@ -65,29 +70,43 @@ 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)] // the comiler will figure it out :) - Self($to_raw(value)) + #[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)] // the comiler will figure it out :) + #[allow(clippy::redundant_closure_call)] $from_raw(value.0) } } }; } -impl_from_raw_wasm_value!(i32, |x| x as u64, |x| x as i32); -impl_from_raw_wasm_value!(i64, |x| x as u64, |x| x as i64); -impl_from_raw_wasm_value!(f32, |x| f32::to_bits(x) as u64, |x| f32::from_bits(x as u32)); -impl_from_raw_wasm_value!(f64, f64::to_bits, f64::from_bits); +type RawValue = u64; +type RawValueRep = [u8; 8]; + +// This all looks like a lot of extra steps, but the compiler will optimize it all away. +// The `u128` is used to make the conversion easier to write. +impl_from_raw_wasm_value!(i32, |x| x as RawValue, |x: RawValueRep| i32::from_ne_bytes(x[0..4].try_into().unwrap())); +impl_from_raw_wasm_value!(i64, |x| x as RawValue, |x: RawValueRep| i64::from_ne_bytes(x[0..8].try_into().unwrap())); +impl_from_raw_wasm_value!(f32, |x| f32::to_bits(x) as RawValue, |x: RawValueRep| f32::from_bits(u32::from_ne_bytes( + x[0..4].try_into().unwrap() +))); +impl_from_raw_wasm_value!(f64, |x| f64::to_bits(x) as RawValue, |x: RawValueRep| f64::from_bits(u64::from_ne_bytes( + x[0..8].try_into().unwrap() +))); + +impl_from_raw_wasm_value!(u8, |x| x as RawValue, |x: RawValueRep| u8::from_ne_bytes(x[0..1].try_into().unwrap())); +impl_from_raw_wasm_value!(u16, |x| x as RawValue, |x: RawValueRep| u16::from_ne_bytes(x[0..2].try_into().unwrap())); +impl_from_raw_wasm_value!(u32, |x| x as RawValue, |x: RawValueRep| u32::from_ne_bytes(x[0..4].try_into().unwrap())); +impl_from_raw_wasm_value!(u64, |x| x as RawValue, |x: RawValueRep| u64::from_ne_bytes(x[0..8].try_into().unwrap())); +// impl_from_raw_wasm_value!(u128, |x| x, |x: RawValueRep| RawValue::from_ne_bytes(x)); -// convenience impls (not actually part of the spec) -impl_from_raw_wasm_value!(i8, |x| x as u64, |x| x as i8); -impl_from_raw_wasm_value!(i16, |x| x as u64, |x| x as i16); -impl_from_raw_wasm_value!(u32, |x| x as u64, |x| x as u32); -impl_from_raw_wasm_value!(u64, |x| x, |x| x); +impl_from_raw_wasm_value!(i8, |x| x as RawValue, |x: RawValueRep| i8::from_ne_bytes(x[0..1].try_into().unwrap())); +impl_from_raw_wasm_value!(i16, |x| x as RawValue, |x: RawValueRep| i16::from_ne_bytes(x[0..2].try_into().unwrap())); diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index 7508d00..ef370c2 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -1,4 +1,5 @@ use crate::Function; +use alloc::rc::Rc; use tinywasm_types::*; #[derive(Debug, Clone)] @@ -9,3 +10,9 @@ pub(crate) struct FunctionInstance { pub(crate) func: Function, pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions } + +impl FunctionInstance { + pub(crate) fn new_wasm(func: WasmFunction, owner: ModuleInstanceAddr) -> Self { + Self { func: Function::Wasm(Rc::new(func)), owner } + } +} diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index 298a31e..1bcbad7 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -1,7 +1,7 @@ use alloc::{format, string::ToString}; use tinywasm_types::*; -use crate::{runtime::RawWasmValue, Error, Result}; +use crate::{runtime::RawWasmValue, unlikely, Error, Result}; /// A WebAssembly Global Instance /// @@ -18,12 +18,13 @@ impl GlobalInstance { Self { ty, value, _owner: owner } } + #[inline] pub(crate) fn get(&self) -> WasmValue { self.value.attach_type(self.ty.ty) } pub(crate) fn set(&mut self, val: WasmValue) -> Result<()> { - if val.val_type() != self.ty.ty { + if unlikely(val.val_type() != self.ty.ty) { return Err(Error::Other(format!( "global type mismatch: expected {:?}, got {:?}", self.ty.ty, @@ -31,7 +32,7 @@ impl GlobalInstance { ))); } - if !self.ty.mutable { + if unlikely(!self.ty.mutable) { return Err(Error::Other("global is immutable".to_string())); } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 9b527d3..ea820da 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -2,11 +2,11 @@ use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{MemoryType, ModuleInstanceAddr}; -use crate::{cold, unlikely, Error, Result}; +use crate::{log, Error, Result}; -pub(crate) const PAGE_SIZE: usize = 65536; -pub(crate) const MAX_PAGES: usize = 65536; -pub(crate) const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; +const PAGE_SIZE: usize = 65536; +const MAX_PAGES: usize = 65536; +const MAX_SIZE: u64 = PAGE_SIZE as u64 * MAX_PAGES as u64; /// A WebAssembly Memory Instance /// @@ -32,22 +32,18 @@ impl MemoryInstance { } } - pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8], len: usize) -> Result<()> { + #[cold] + fn trap_oob(&self, addr: usize, len: usize) -> Error { + Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }) + } + + pub(crate) fn store(&mut self, addr: usize, len: usize, data: &[u8]) -> Result<()> { let Some(end) = addr.checked_add(len) else { - cold(); - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: addr, - len: data.len(), - max: self.data.len(), - })); + return Err(self.trap_oob(addr, data.len())); }; - if unlikely(end > self.data.len() || end < addr) { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: addr, - len: data.len(), - max: self.data.len(), - })); + if end > self.data.len() || end < addr { + return Err(self.trap_oob(addr, data.len())); } // WebAssembly doesn't require alignment for stores @@ -71,52 +67,48 @@ impl MemoryInstance { self.kind.page_count_max.unwrap_or(MAX_PAGES as u64) as usize } - pub(crate) fn load(&self, addr: usize, _align: usize, len: usize) -> Result<&[u8]> { + pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[u8]> { let Some(end) = addr.checked_add(len) else { - cold(); - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + return Err(self.trap_oob(addr, len)); }; - if unlikely(end > self.data.len() || end < addr) { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + if end > self.data.len() || end < addr { + 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, _align: usize) -> Result { + pub(crate) fn load_as>(&self, addr: usize) -> Result { let Some(end) = addr.checked_add(SIZE) else { - cold(); - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: SIZE, max: self.max_pages() })); + return Err(self.trap_oob(addr, SIZE)); }; - if unlikely(end > self.data.len()) { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len: SIZE, max: self.data.len() })); + if end > self.data.len() { + return Err(self.trap_oob(addr, SIZE)); } + #[cfg(not(feature = "unsafe"))] + let val = T::from_le_bytes(self.data[addr..end].try_into().expect("slice size mismatch")); + #[cfg(feature = "unsafe")] - // WebAssembly doesn't require alignment for loads // SAFETY: we checked that `end` is in bounds above. All types that implement `Into` are valid // to load from unaligned addresses. let val = unsafe { core::ptr::read_unaligned(self.data[addr..end].as_ptr() as *const T) }; - #[cfg(not(feature = "unsafe"))] - let val = T::from_le_bytes(self.data[addr..end].try_into().expect("slice size mismatch")); - Ok(val) } + #[inline] pub(crate) fn page_count(&self) -> usize { self.page_count } pub(crate) fn fill(&mut self, addr: usize, len: usize, val: u8) -> Result<()> { - let end = addr - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }))?; - if unlikely(end > self.data.len()) { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() })); + let end = addr.checked_add(len).ok_or_else(|| self.trap_oob(addr, len))?; + if end > self.data.len() { + return Err(self.trap_oob(addr, len)); } self.data[addr..end].fill(val); @@ -124,15 +116,9 @@ impl MemoryInstance { } pub(crate) fn copy_from_slice(&mut self, dst: usize, src: &[u8]) -> Result<()> { - let end = dst.checked_add(src.len()).ok_or_else(|| { - Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len: src.len(), max: self.data.len() }) - })?; - if unlikely(end > self.data.len()) { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { - offset: dst, - len: src.len(), - max: self.data.len(), - })); + let end = dst.checked_add(src.len()).ok_or_else(|| self.trap_oob(dst, src.len()))?; + if end > self.data.len() { + return Err(self.trap_oob(dst, src.len())); } self.data[dst..end].copy_from_slice(src); @@ -141,19 +127,15 @@ impl MemoryInstance { 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(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() }))?; + let src_end = src.checked_add(len).ok_or_else(|| self.trap_oob(src, len))?; if src_end > self.data.len() { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: src, len, max: self.data.len() })); + return Err(self.trap_oob(src, len)); } // Calculate the end of the destination slice - let dst_end = dst - .checked_add(len) - .ok_or_else(|| Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() }))?; + let dst_end = dst.checked_add(len).ok_or_else(|| self.trap_oob(dst, len))?; if dst_end > self.data.len() { - return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { offset: dst, len, max: self.data.len() })); + return Err(self.trap_oob(dst, len)); } // Perform the copy @@ -170,7 +152,6 @@ impl MemoryInstance { } if new_pages as usize > self.max_pages() { - log::info!("memory size out of bounds: {}", new_pages); return None; } @@ -182,12 +163,8 @@ impl MemoryInstance { // Zero initialize the new pages self.data.resize(new_size, 0); self.page_count = new_pages as usize; - - log::debug!("memory was {} pages", current_pages); - log::debug!("memory grown by {} pages", pages_delta); - log::debug!("memory grown to {} pages", self.page_count); - - Some(current_pages.try_into().expect("memory size out of bounds, this should have been caught earlier")) + debug_assert!(current_pages <= i32::MAX as usize, "page count should never be greater than i32::MAX"); + Some(current_pages as i32) } } @@ -200,20 +177,25 @@ impl MemoryInstance { /// UB for loading things things like packed structs pub(crate) unsafe trait MemLoadable: Sized + Copy { /// Load a value from memory + #[allow(unused)] fn from_le_bytes(bytes: [u8; T]) -> Self; /// Load a value from memory + #[allow(unused)] fn from_be_bytes(bytes: [u8; T]) -> Self; } macro_rules! impl_mem_loadable_for_primitive { ($($type:ty, $size:expr),*) => { $( + #[allow(unused)] #[allow(unsafe_code)] unsafe impl MemLoadable<$size> for $type { + #[inline] fn from_le_bytes(bytes: [u8; $size]) -> Self { <$type>::from_le_bytes(bytes) } + #[inline] fn from_be_bytes(bytes: [u8; $size]) -> Self { <$type>::from_be_bytes(bytes) } @@ -241,8 +223,8 @@ mod memory_instance_tests { fn test_memory_store_and_load() { let mut memory = create_test_memory(); let data_to_store = [1, 2, 3, 4]; - assert!(memory.store(0, 0, &data_to_store, data_to_store.len()).is_ok()); - let loaded_data = memory.load(0, 0, data_to_store.len()).unwrap(); + assert!(memory.store(0, data_to_store.len(), &data_to_store).is_ok()); + let loaded_data = memory.load(0, data_to_store.len()).unwrap(); assert_eq!(loaded_data, &data_to_store); } @@ -250,7 +232,7 @@ mod memory_instance_tests { fn test_memory_store_out_of_bounds() { let mut memory = create_test_memory(); let data_to_store = [1, 2, 3, 4]; - assert!(memory.store(memory.data.len(), 0, &data_to_store, data_to_store.len()).is_err()); + assert!(memory.store(memory.data.len(), data_to_store.len(), &data_to_store).is_err()); } #[test] diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index c8c5d8a..f4e0df9 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,15 +1,10 @@ -use crate::log; use alloc::{boxed::Box, format, rc::Rc, string::ToString, vec::Vec}; -use core::{ - cell::RefCell, - sync::atomic::{AtomicUsize, Ordering}, -}; +use core::cell::RefCell; +use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; -use crate::{ - runtime::{self, InterpreterRuntime, RawWasmValue}, - Error, Function, ModuleInstance, Result, Trap, -}; +use crate::runtime::{self, InterpreterRuntime, RawWasmValue}; +use crate::{Error, Function, ModuleInstance, Result, Trap}; mod data; mod element; @@ -17,6 +12,7 @@ mod function; mod global; mod memory; mod table; + pub(crate) use {data::*, element::*, function::*, global::*, memory::*, table::*}; // global store id counter @@ -120,6 +116,69 @@ impl Store { Ok(()) } + #[cold] + fn not_found_error(name: &str) -> Error { + Error::Other(format!("{} not found", name)) + } + + /// Get the function at the actual index in the store + #[inline] + pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { + self.data.funcs.get(addr).ok_or_else(|| Self::not_found_error("function")) + } + + /// Get the memory at the actual index in the store + #[inline] + pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { + self.data.memories.get(addr).ok_or_else(|| Self::not_found_error("memory")) + } + + /// Get the table at the actual index in the store + #[inline] + pub(crate) fn get_table(&self, addr: usize) -> Result<&Rc>> { + self.data.tables.get(addr).ok_or_else(|| Self::not_found_error("table")) + } + + /// Get the data at the actual index in the store + #[inline] + pub(crate) fn get_data(&self, addr: usize) -> Result<&DataInstance> { + self.data.datas.get(addr).ok_or_else(|| Self::not_found_error("data")) + } + + /// Get the data at the actual index in the store + #[inline] + pub(crate) fn get_data_mut(&mut self, addr: usize) -> Result<&mut DataInstance> { + self.data.datas.get_mut(addr).ok_or_else(|| Self::not_found_error("data")) + } + + /// Get the element at the actual index in the store + #[inline] + pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { + self.data.elements.get(addr).ok_or_else(|| Self::not_found_error("element")) + } + + /// Get the global at the actual index in the store + #[inline] + pub(crate) fn get_global(&self, addr: usize) -> Result<&Rc>> { + self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")) + } + + /// Get the global at the actual index in the store + #[inline] + pub fn get_global_val(&self, addr: usize) -> Result { + self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")).map(|global| global.borrow().value) + } + + /// Set the global at the actual index in the store + #[inline] + pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { + let global = self.data.globals.get(addr).ok_or_else(|| Self::not_found_error("global")); + global.map(|global| global.borrow_mut().value = value) + } +} + +// Linking related functions +impl Store { /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs( &mut self, @@ -128,12 +187,10 @@ impl Store { ) -> Result> { let func_count = self.data.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); - for (i, func) in funcs.into_iter().enumerate() { - self.data.funcs.push(FunctionInstance { func: Function::Wasm(Rc::new(func.wasm_function)), owner: idx }); + self.data.funcs.push(FunctionInstance::new_wasm(func.wasm_function, idx)); func_addrs.push((i + func_count) as FuncAddr); } - Ok(func_addrs) } @@ -156,9 +213,7 @@ impl Store { if let MemoryArch::I64 = mem.arch { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } - log::info!("adding memory: {:?}", mem); self.data.memories.push(Rc::new(RefCell::new(MemoryInstance::new(mem, idx)))); - mem_addrs.push((i + mem_count) as MemAddr); } Ok(mem_addrs) @@ -204,10 +259,9 @@ impl Store { let val = i64::from(global.borrow().value); // check if the global is actually a null reference - if val < 0 { - None - } else { - Some(val as u32) + match val < 0 { + true => None, + false => Some(val as u32), } } _ => return Err(Error::UnsupportedFeature(format!("const expression other than ref: {:?}", item))), @@ -235,17 +289,12 @@ impl Store { .map(|item| Ok(TableElement::from(self.elem_addr(item, global_addrs, func_addrs)?))) .collect::>>()?; - log::error!("element kind: {:?}", element.kind); - let items = match element.kind { // doesn't need to be initialized, can be initialized lazily using the `table.init` instruction ElementKind::Passive => Some(init), // this one is not available to the runtime but needs to be initialized to declare references - ElementKind::Declared => { - // a. Execute the instruction elm.drop i - None - } + ElementKind::Declared => None, // a. Execute the instruction elm.drop i // this one is active, so we need to initialize it (essentially a `table.init` instruction) ElementKind::Active { offset, table } => { @@ -255,17 +304,17 @@ impl Store { .copied() .ok_or_else(|| Error::Other(format!("table {} not found for element {}", table, i)))?; - if let Some(table) = self.data.tables.get_mut(table_addr as usize) { - // In wasm 2.0, it's possible to call a function that hasn't been instantiated yet, - // when using a partially initialized active element segments. - // This isn't mentioned in the spec, but the "unofficial" testsuite has a test for it: - // https://github.com/WebAssembly/testsuite/blob/5a1a590603d81f40ef471abba70a90a9ae5f4627/linking.wast#L264-L276 - // I have NO IDEA why this is allowed, but it is. - if let Err(Error::Trap(trap)) = table.borrow_mut().init_raw(offset, &init) { - return Ok((elem_addrs.into_boxed_slice(), Some(trap))); - } - } else { + 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))); + }; + + // In wasm 2.0, it's possible to call a function that hasn't been instantiated yet, + // when using a partially initialized active element segments. + // This isn't mentioned in the spec, but the "unofficial" testsuite has a test for it: + // https://github.com/WebAssembly/testsuite/blob/5a1a590603d81f40ef471abba70a90a9ae5f4627/linking.wast#L264-L276 + // I have NO IDEA why this is allowed, but it is. + if let Err(Error::Trap(trap)) = table.borrow_mut().init_raw(offset, &init) { + return Ok((elem_addrs.into_boxed_slice(), Some(trap))); } // f. Execute the instruction elm.drop i @@ -291,36 +340,30 @@ impl Store { let data_count = self.data.datas.len(); let mut data_addrs = Vec::with_capacity(data_count); for (i, data) in datas.into_iter().enumerate() { - let data_val = - match data.kind { - tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { - // a. Assert: memidx == 0 - if mem_addr != 0 { - return Err(Error::UnsupportedFeature("data segments for non-zero memories".to_string())); - } - - let mem_addr = mem_addrs.get(mem_addr as usize).copied().ok_or_else(|| { - Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)) - })?; - - let offset = self.eval_i32_const(&offset)?; - - let mem = self.data.memories.get_mut(mem_addr as usize).ok_or_else(|| { - Error::Other(format!("memory {} not found for data segment {}", mem_addr, i)) - })?; - - // See comment for active element sections in the function above why we need to do this here - if let Err(Error::Trap(trap)) = - mem.borrow_mut().store(offset as usize, 0, &data.data, data.data.len()) - { - return Ok((data_addrs.into_boxed_slice(), Some(trap))); - } - - // drop the data - None + 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())); } - tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), - }; + + 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))); + }; + + 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))); + }; + + match mem.borrow_mut().store(offset as usize, data.data.len(), &data.data) { + Ok(()) => None, + Err(Error::Trap(trap)) => return Ok((data_addrs.into_boxed_slice(), Some(trap))), + Err(e) => return Err(e), + } + } + tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), + }; self.data.datas.push(DataInstance::new(data_val, idx)); data_addrs.push((i + data_count) as Addr); @@ -359,10 +402,8 @@ impl Store { let val = match const_instr { I32Const(i) => *i, GlobalGet(addr) => { - let addr = *addr as usize; - let global = self.data.globals[addr].clone(); - let val = global.borrow().value; - i32::from(val) + let global = self.data.globals[*addr as usize].borrow(); + i32::from(global.value) } _ => return Err(Error::Other("expected i32".to_string())), }; @@ -383,72 +424,20 @@ impl Store { I32Const(i) => RawWasmValue::from(*i), I64Const(i) => RawWasmValue::from(*i), GlobalGet(addr) => { - let addr = module_global_addrs.get(*addr as usize).copied().ok_or_else(|| { + 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)) })?; let global = - self.data.globals.get(addr as usize).expect("global not found. This should be unreachable"); + self.data.globals.get(*addr as usize).expect("global not found. This should be unreachable"); global.borrow().value } RefNull(t) => RawWasmValue::from(t.default_value()), - RefFunc(idx) => RawWasmValue::from(module_func_addrs.get(*idx as usize).copied().ok_or_else(|| { + 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)) })?), }; Ok(val) } - - /// Get the function at the actual index in the store - pub(crate) fn get_func(&self, addr: usize) -> Result<&FunctionInstance> { - self.data.funcs.get(addr).ok_or_else(|| Error::Other(format!("function {} not found", addr))) - } - - /// Get the memory at the actual index in the store - pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { - self.data.memories.get(addr).ok_or_else(|| Error::Other(format!("memory {} not found", addr))) - } - - /// Get the table at the actual index in the store - pub(crate) fn get_table(&self, addr: usize) -> Result<&Rc>> { - self.data.tables.get(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) - } - - /// Get the data at the actual index in the store - pub(crate) fn get_data(&self, addr: usize) -> Result<&DataInstance> { - self.data.datas.get(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) - } - - /// Get the data at the actual index in the store - pub(crate) fn get_data_mut(&mut self, addr: usize) -> Result<&mut DataInstance> { - self.data.datas.get_mut(addr).ok_or_else(|| Error::Other(format!("table {} not found", addr))) - } - - /// Get the element at the actual index in the store - pub(crate) fn get_elem(&self, addr: usize) -> Result<&ElementInstance> { - self.data.elements.get(addr).ok_or_else(|| Error::Other(format!("element {} not found", addr))) - } - - /// Get the global at the actual index in the store - pub(crate) fn get_global(&self, addr: usize) -> Result<&Rc>> { - self.data.globals.get(addr).ok_or_else(|| Error::Other(format!("global {} not found", addr))) - } - - /// Get the global at the actual index in the store - pub fn get_global_val(&self, addr: usize) -> Result { - self.data - .globals - .get(addr) - .ok_or_else(|| Error::Other(format!("global {} not found", addr))) - .map(|global| global.borrow().value) - } - - pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { - self.data - .globals - .get(addr) - .ok_or_else(|| Error::Other(format!("global {} not found", addr))) - .map(|global| global.borrow_mut().value = value) - } } diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index ea520b8..d9dd9ad 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,4 +1,4 @@ -use crate::log; +use crate::{log, unlikely}; use crate::{Error, Result, Trap}; use alloc::{vec, vec::Vec}; use tinywasm_types::*; @@ -40,7 +40,7 @@ impl TableInstance { pub(crate) fn grow_to_fit(&mut self, new_size: usize) -> Result<()> { if new_size > self.elements.len() { - if new_size > self.kind.size_max.unwrap_or(MAX_TABLE_SIZE) as usize { + 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()); } @@ -82,7 +82,6 @@ impl TableInstance { // 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) } } diff --git a/crates/tinywasm/tests/generated/2.0.csv b/crates/tinywasm/tests/generated/2.0.csv index fbf18ae..40ef2ae 100644 --- a/crates/tinywasm/tests/generated/2.0.csv +++ b/crates/tinywasm/tests/generated/2.0.csv @@ -1,2 +1,3 @@ 0.3.0,26722,1161,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":8,"failed":109},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":1},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":171,"failed":12},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":3928,"failed":522},{"name":"memory_fill.wast","passed":64,"failed":36},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":177,"failed":63},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":594,"failed":186},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.4.0-alpha.0,27549,334,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0,27549,334,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":720,"failed":60},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.1,27551,335,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":75,"failed":42},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":9,"failed":8},{"name":"ref_is_null.wast","passed":4,"failed":12},{"name":"ref_null.wast","passed":1,"failed":2},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1613,"failed":115},{"name":"table_fill.wast","passed":23,"failed":22},{"name":"table_get.wast","passed":10,"failed":6},{"name":"table_grow.wast","passed":21,"failed":29},{"name":"table_init.wast","passed":719,"failed":61},{"name":"table_set.wast","passed":19,"failed":7},{"name":"table_size.wast","passed":8,"failed":31},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/tinywasm/tests/generated/mvp.csv b/crates/tinywasm/tests/generated/mvp.csv index 23c080d..2e151d5 100644 --- a/crates/tinywasm/tests/generated/mvp.csv +++ b/crates/tinywasm/tests/generated/mvp.csv @@ -3,3 +3,5 @@ 0.1.0,17630,2598,[{"name":"address.wast","passed":5,"failed":255},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":110,"failed":2},{"name":"block.wast","passed":193,"failed":30},{"name":"br.wast","passed":84,"failed":13},{"name":"br_if.wast","passed":90,"failed":28},{"name":"br_table.wast","passed":25,"failed":149},{"name":"call.wast","passed":29,"failed":62},{"name":"call_indirect.wast","passed":36,"failed":134},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":371,"failed":248},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":50,"failed":49},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":92,"failed":4},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":2,"failed":6},{"name":"float_exprs.wast","passed":761,"failed":139},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":6,"failed":84},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":124,"failed":48},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":51,"failed":59},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":120,"failed":121},{"name":"imports.wast","passed":74,"failed":109},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":14,"failed":15},{"name":"left-to-right.wast","passed":1,"failed":95},{"name":"linking.wast","passed":21,"failed":111},{"name":"load.wast","passed":60,"failed":37},{"name":"local_get.wast","passed":32,"failed":4},{"name":"local_set.wast","passed":50,"failed":3},{"name":"local_tee.wast","passed":68,"failed":29},{"name":"loop.wast","passed":93,"failed":27},{"name":"memory.wast","passed":34,"failed":45},{"name":"memory_grow.wast","passed":12,"failed":84},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":2,"failed":180},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":46,"failed":42},{"name":"return.wast","passed":73,"failed":11},{"name":"select.wast","passed":86,"failed":62},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":9,"failed":11},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":22,"failed":14},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":50,"failed":14},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":35,"failed":15},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.2.0,19344,884,[{"name":"address.wast","passed":181,"failed":79},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":220,"failed":3},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":171,"failed":3},{"name":"call.wast","passed":73,"failed":18},{"name":"call_indirect.wast","passed":50,"failed":120},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":439,"failed":180},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":56,"failed":43},{"name":"endianness.wast","passed":29,"failed":40},{"name":"exports.wast","passed":92,"failed":4},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":6,"failed":2},{"name":"float_exprs.wast","passed":890,"failed":10},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":78,"failed":12},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":168,"failed":4},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":103,"failed":7},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":231,"failed":10},{"name":"imports.wast","passed":80,"failed":103},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":26,"failed":3},{"name":"left-to-right.wast","passed":92,"failed":4},{"name":"linking.wast","passed":29,"failed":103},{"name":"load.wast","passed":93,"failed":4},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":93,"failed":4},{"name":"loop.wast","passed":116,"failed":4},{"name":"memory.wast","passed":78,"failed":1},{"name":"memory_grow.wast","passed":91,"failed":5},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":35,"failed":7},{"name":"memory_trap.wast","passed":180,"failed":2},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":78,"failed":10},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":114,"failed":34},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":11,"failed":9},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.3.0,20254,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.0,20254,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.4.1,20257,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":99,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/tinywasm/tests/generated/progress-2.0.svg b/crates/tinywasm/tests/generated/progress-2.0.svg index c869fcb..6424367 100644 --- a/crates/tinywasm/tests/generated/progress-2.0.svg +++ b/crates/tinywasm/tests/generated/progress-2.0.svg @@ -10,45 +10,50 @@ Tests Passed TinyWasm Version - - - - - + + + + + 0 - + 5000 - - + + 10000 - - + + 15000 - - + + 20000 - - + + 25000 - + - + v0.3.0 (26722) - - -v0.4.0-alpha.0 (27464) + + +v0.4.0 (27549) - - - + + +v0.4.1 (27552) + + + + + diff --git a/crates/tinywasm/tests/generated/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg index b8a3e35..2a26dd5 100644 --- a/crates/tinywasm/tests/generated/progress-mvp.svg +++ b/crates/tinywasm/tests/generated/progress-mvp.svg @@ -36,29 +36,27 @@ TinyWasm Version - + v0.0.4 (9258) - - -v0.0.5 (11135) - - - + + v0.1.0 (17630) - - -v0.2.0 (19344) - - - + + v0.3.0 (20254) - - - - - - + + +v0.4.1 (20257) + + + + + + + + + diff --git a/crates/tinywasm/tests/testsuite/indexmap.rs b/crates/tinywasm/tests/testsuite/indexmap.rs index 1642ce2..3e751c4 100644 --- a/crates/tinywasm/tests/testsuite/indexmap.rs +++ b/crates/tinywasm/tests/testsuite/indexmap.rs @@ -1,3 +1,4 @@ +/// A naive implementation of an index map for use in the test suite pub struct IndexMap { map: std::collections::HashMap, keys: Vec, diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index 2019c04..35f0607 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -128,23 +128,10 @@ impl Debug for TestSuite { writeln!(f, "{}", link(group_name, &group.file, None).bold().underline())?; writeln!(f, " Tests Passed: {}", group_passed.to_string().green())?; + if group_failed != 0 { writeln!(f, " Tests Failed: {}", group_failed.to_string().red())?; } - - // for (test_name, test) in &group.tests { - // write!(f, " {}: ", test_name.bold())?; - // match &test.result { - // Ok(()) => { - // writeln!(f, "{}", "Passed".green())?; - // } - // Err(e) => { - // writeln!(f, "{}", "Failed".red())?; - // // writeln!(f, "Error: {:?}", e)?; - // } - // } - // writeln!(f, " Span: {:?}", test.span)?; - // } } writeln!(f, "\n{}", "Total Test Summary:".bold().underline())?; diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index 125a9d6..84efc3a 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -1,3 +1,4 @@ +/// Here be dragons (this file is in need of a big refactor) use crate::testsuite::util::*; use std::{borrow::Cow, collections::HashMap}; @@ -6,7 +7,7 @@ use eyre::{eyre, Result}; use log::{debug, error, info}; use tinywasm::{Extern, Imports, ModuleInstance}; use tinywasm_types::{ExternVal, MemoryType, ModuleInstanceAddr, TableType, ValType, WasmValue}; -use wast::{lexer::Lexer, parser::ParseBuffer, QuoteWat, Wast}; +use wast::{lexer::Lexer, parser::ParseBuffer, Wast}; #[derive(Default)] struct RegisteredModules { @@ -125,8 +126,8 @@ impl TestSuite { .define("spectest", "table", table)? .define("spectest", "global_i32", Extern::global(WasmValue::I32(666), false))? .define("spectest", "global_i64", Extern::global(WasmValue::I64(666), false))? - .define("spectest", "global_f32", Extern::global(WasmValue::F32(666.0), false))? - .define("spectest", "global_f64", Extern::global(WasmValue::F64(666.0), false))? + .define("spectest", "global_f32", Extern::global(WasmValue::F32(666.6), false))? + .define("spectest", "global_f64", Extern::global(WasmValue::F64(666.6), false))? .define("spectest", "print", print)? .define("spectest", "print_i32", print_i32)? .define("spectest", "print_i64", print_i64)? @@ -195,31 +196,7 @@ impl TestSuite { Wat(module) => { debug!("got wat module"); let result = catch_unwind_silent(|| { - let (name, bytes) = match module { - QuoteWat::QuoteModule(_, quoted_wat) => { - let wat = quoted_wat - .iter() - .map(|(_, s)| std::str::from_utf8(s).expect("failed to convert wast to utf8")) - .collect::>() - .join("\n"); - - let lexer = Lexer::new(&wat); - let buf = ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); - let mut wat_data = wast::parser::parse::(&buf).expect("failed to parse wat"); - (None, wat_data.encode().expect("failed to encode module")) - } - QuoteWat::Wat(mut wat) => { - let wast::Wat::Module(ref module) = wat else { - unimplemented!("Not supported"); - }; - ( - module.id.map(|id| id.name().to_string()), - wat.encode().expect("failed to encode module"), - ) - } - _ => unimplemented!("Not supported"), - }; - + let (name, bytes) = encode_quote_wat(module); let m = parse_module_bytes(&bytes).expect("failed to parse module bytes"); let module_instance = tinywasm::Module::from(m) @@ -488,10 +465,6 @@ impl TestSuite { e })?; - debug!("outcomes: {:?}", outcomes); - - debug!("expected: {:?}", expected); - if outcomes.len() != expected.len() { return Err(eyre!( "span: {:?} expected {} results, got {}", @@ -501,8 +474,6 @@ impl TestSuite { )); } - log::debug!("outcomes: {:?}", outcomes); - outcomes.iter().zip(expected).enumerate().try_for_each(|(i, (outcome, exp))| { (outcome.eq_loose(&exp)) .then_some(()) @@ -511,7 +482,6 @@ 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( diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 1b91e1e..a45c59f 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -2,6 +2,7 @@ use std::panic::{self, AssertUnwindSafe}; use eyre::{eyre, Result}; use tinywasm_types::{ModuleInstanceAddr, TinyWasmModule, ValType, WasmValue}; +use wast::QuoteWat; pub fn try_downcast_panic(panic: Box) -> String { let info = panic.downcast_ref::().or(None).map(|p| p.to_string()).clone(); @@ -53,6 +54,30 @@ pub fn catch_unwind_silent R, R>(f: F) -> std::thread::Result result } +pub fn encode_quote_wat(module: QuoteWat) -> (Option, Vec) { + match module { + QuoteWat::QuoteModule(_, quoted_wat) => { + let wat = quoted_wat + .iter() + .map(|(_, s)| std::str::from_utf8(s).expect("failed to convert wast to utf8")) + .collect::>() + .join("\n"); + + let lexer = wast::lexer::Lexer::new(&wat); + let buf = wast::parser::ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); + let mut wat_data = wast::parser::parse::(&buf).expect("failed to parse wat"); + (None, wat_data.encode().expect("failed to encode module")) + } + QuoteWat::Wat(mut wat) => { + let wast::Wat::Module(ref module) = wat else { + unimplemented!("Not supported"); + }; + (module.id.map(|id| id.name().to_string()), wat.encode().expect("failed to encode module")) + } + _ => unimplemented!("Not supported"), + } +} + pub fn parse_module_bytes(bytes: &[u8]) -> Result { let parser = tinywasm_parser::Parser::new(); Ok(parser.parse_module_bytes(bytes)?) diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index bbd2206..273d6ed 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -7,10 +7,8 @@ use rkyv::{ Deserialize, }; -// 16 bytes const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; const TWASM_VERSION: &[u8; 2] = b"01"; - #[rustfmt::skip] const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TWASM_MAGIC_PREFIX[2], TWASM_MAGIC_PREFIX[3], TWASM_VERSION[0], TWASM_VERSION[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 0e2eafe..9923116 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,35 +1,61 @@ -use crate::{DataAddr, ElemAddr, MemAddr}; - 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))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[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 BlockArgsPacked { + pub fn new(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) + } + pub fn unpack(&self) -> BlockArgs { + match self.0[0] { + 0 => BlockArgs::Empty, + 1 => BlockArgs::Type(ValType::from_byte(self.0[1]).unwrap()), + 2 => BlockArgs::FuncType(u32::from_le_bytes(self.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))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct MemoryArg { - pub mem_addr: MemAddr, - pub align: u8, - pub align_max: u8, pub offset: u64, + pub mem_addr: MemAddr, } type BrTableDefault = u32; -type BrTableLen = usize; -type EndOffset = usize; -type ElseOffset = usize; +type BrTableLen = u32; +type EndOffset = u32; +type ElseOffset = u32; #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ConstInstruction { I32Const(i32), I64Const(i64), @@ -52,20 +78,47 @@ pub enum ConstInstruction { /// This makes it easier to implement the label stack iteratively. /// /// See -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] +// should be kept as small as possible (16 bytes max) pub enum Instruction { // Custom Instructions BrLabel(LabelAddr), + // Not implemented yet + // LocalGet + I32Const + I32Add + // One of the most common patterns in the Rust compiler output + // I32LocalGetConstAdd(LocalAddr, i32), + + // Not implemented yet + // LocalGet + I32Const + I32Store => I32LocalGetConstStore + I32Const + // Also common, helps us skip the stack entirely. + // Has to be followed by an I32Const instruction + // I32StoreLocal { local: LocalAddr, offset: i32, mem_addr: MemAddr }, + + // 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), + + // Not implemented yet + // I32AddConst(i32), + // I32SubConst(i32), + // I64AddConst(i64), + // I64SubConst(i64), + // Control Instructions // See Unreachable, Nop, Block(BlockArgs, EndOffset), Loop(BlockArgs, EndOffset), - If(BlockArgs, Option, EndOffset), + If(BlockArgsPacked, ElseOffset, EndOffset), // If else offset is 0 if there is no else block Else(EndOffset), EndBlockFrame, EndFunc, @@ -90,29 +143,29 @@ pub enum Instruction { GlobalSet(GlobalAddr), // Memory Instructions - I32Load(MemoryArg), - I64Load(MemoryArg), - F32Load(MemoryArg), - F64Load(MemoryArg), - I32Load8S(MemoryArg), - I32Load8U(MemoryArg), - I32Load16S(MemoryArg), - I32Load16U(MemoryArg), - I64Load8S(MemoryArg), - I64Load8U(MemoryArg), - I64Load16S(MemoryArg), - I64Load16U(MemoryArg), - I64Load32S(MemoryArg), - I64Load32U(MemoryArg), - I32Store(MemoryArg), - I64Store(MemoryArg), - F32Store(MemoryArg), - F64Store(MemoryArg), - I32Store8(MemoryArg), - I32Store16(MemoryArg), - I64Store8(MemoryArg), - I64Store16(MemoryArg), - I64Store32(MemoryArg), + I32Load { offset: u64, mem_addr: MemAddr }, + I64Load { offset: u64, mem_addr: MemAddr }, + F32Load { offset: u64, mem_addr: MemAddr }, + F64Load { offset: u64, mem_addr: MemAddr }, + I32Load8S { offset: u64, mem_addr: MemAddr }, + I32Load8U { offset: u64, mem_addr: MemAddr }, + I32Load16S { offset: u64, mem_addr: MemAddr }, + I32Load16U { offset: u64, mem_addr: MemAddr }, + I64Load8S { offset: u64, mem_addr: MemAddr }, + I64Load8U { offset: u64, mem_addr: MemAddr }, + I64Load16S { offset: u64, mem_addr: MemAddr }, + I64Load16U { offset: u64, mem_addr: MemAddr }, + I64Load32S { offset: u64, mem_addr: MemAddr }, + I64Load32U { offset: u64, mem_addr: MemAddr }, + I32Store { offset: u64, mem_addr: MemAddr }, + I64Store { offset: u64, mem_addr: MemAddr }, + F32Store { offset: u64, mem_addr: MemAddr }, + F64Store { offset: u64, mem_addr: MemAddr }, + I32Store8 { offset: u64, mem_addr: MemAddr }, + I32Store16 { offset: u64, mem_addr: MemAddr }, + I64Store8 { offset: u64, mem_addr: MemAddr }, + I64Store16 { offset: u64, mem_addr: MemAddr }, + I64Store32 { offset: u64, mem_addr: MemAddr }, MemorySize(MemAddr, u8), MemoryGrow(MemAddr, u8), @@ -281,3 +334,51 @@ pub enum Instruction { MemoryFill(MemAddr), DataDrop(DataAddr), } + +#[cfg(test)] +mod test_blockargs_packed { + use super::*; + + #[test] + fn test_empty() { + let args = BlockArgs::Empty; + let packed = BlockArgsPacked::new(args); + assert_eq!(packed.unpack(), BlockArgs::Empty); + } + + #[test] + fn test_val_type_i32() { + let args = BlockArgs::Type(ValType::I32); + let packed = BlockArgsPacked::new(args); + assert_eq!(packed.unpack(), BlockArgs::Type(ValType::I32)); + } + + #[test] + fn test_val_type_i64() { + let args = BlockArgs::Type(ValType::I64); + let packed = BlockArgsPacked::new(args); + assert_eq!(packed.unpack(), BlockArgs::Type(ValType::I64)); + } + + #[test] + fn test_val_type_f32() { + let args = BlockArgs::Type(ValType::F32); + let packed = BlockArgsPacked::new(args); + assert_eq!(packed.unpack(), BlockArgs::Type(ValType::F32)); + } + + #[test] + fn test_val_type_f64() { + let args = BlockArgs::Type(ValType::F64); + let packed = BlockArgsPacked::new(args); + assert_eq!(packed.unpack(), BlockArgs::Type(ValType::F64)); + } + + #[test] + fn test_func_type() { + let func_type = 123; // Use an arbitrary u32 value + let args = BlockArgs::FuncType(func_type); + let packed = BlockArgsPacked::new(args); + assert_eq!(packed.unpack(), BlockArgs::FuncType(func_type)); + } +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 205ec5a..d2da7d7 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -39,16 +39,17 @@ pub mod archive; /// 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))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct TinyWasmModule { /// The version of the WebAssembly module. pub version: Option, + /// The start function of the WebAssembly module. pub start_func: Option, /// The functions of the WebAssembly module. pub funcs: Box<[TypedWasmFunction]>, + /// The types of the WebAssembly module. pub func_types: Box<[FuncType]>, @@ -78,8 +79,7 @@ pub struct TinyWasmModule { /// /// See #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ExternalKind { /// A WebAssembly Function. Func, @@ -97,6 +97,8 @@ pub enum ExternalKind { /// /// See pub type Addr = u32; + +// aliases for clarity pub type FuncAddr = Addr; pub type TableAddr = Addr; pub type MemAddr = Addr; @@ -104,6 +106,7 @@ pub type GlobalAddr = Addr; pub type ElemAddr = Addr; pub type DataAddr = Addr; pub type ExternAddr = Addr; + // additional internal addresses pub type TypeAddr = Addr; pub type LocalAddr = Addr; @@ -146,25 +149,15 @@ impl ExternVal { /// The type of a WebAssembly Function. /// /// See -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[derive(Debug, Clone, PartialEq, Default)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct FuncType { pub params: Box<[ValType]>, pub results: Box<[ValType]>, } -impl FuncType { - /// Get the number of parameters of a function type. - #[inline] - pub fn empty() -> Self { - Self { params: Box::new([]), results: Box::new([]) } - } -} - #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[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]>, @@ -172,8 +165,7 @@ pub struct WasmFunction { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct TypedWasmFunction { pub type_addr: u32, pub wasm_function: WasmFunction, @@ -181,8 +173,7 @@ pub struct TypedWasmFunction { /// A WebAssembly Module Export #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Export { /// The name of the export. pub name: Box, @@ -193,24 +184,21 @@ pub struct Export { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Global { pub ty: GlobalType, pub init: ConstInstruction, } #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct TableType { pub element_type: ValType, pub size_initial: u32, @@ -227,12 +215,9 @@ impl TableType { } } -#[derive(Debug, Clone, PartialEq)] - /// Represents a memory's type. -#[derive(Copy)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct MemoryType { pub arch: MemoryArch, pub page_count_initial: u64, @@ -246,16 +231,14 @@ impl MemoryType { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum MemoryArch { I32, I64, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Import { pub module: Box, pub name: Box, @@ -263,8 +246,7 @@ pub struct Import { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ImportKind { Function(TypeAddr), Table(TableType), @@ -285,8 +267,7 @@ impl From<&ImportKind> for ExternalKind { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Data { pub data: Box<[u8]>, pub range: Range, @@ -294,16 +275,14 @@ pub struct Data { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum DataKind { Active { mem: MemAddr, offset: ConstInstruction }, Passive, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub struct Element { pub kind: ElementKind, pub items: Box<[ElementItem]>, @@ -312,8 +291,7 @@ pub struct Element { } #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ElementKind { Passive, Active { table: TableAddr, offset: ConstInstruction }, @@ -321,8 +299,7 @@ pub enum ElementKind { } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ElementItem { Func(FuncAddr), Expr(ConstInstruction), diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index e46092b..bcd43e5 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -16,7 +16,8 @@ pub enum WasmValue { F32(f32), /// A 64-bit float. F64(f64), - + // /// A 128-bit vector + // V128(u128), RefExtern(ExternAddr), RefFunc(FuncAddr), RefNull(ValType), @@ -47,6 +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::RefFunc => Self::RefNull(ValType::RefFunc), ValType::RefExtern => Self::RefNull(ValType::RefExtern), } @@ -89,6 +91,7 @@ impl Debug for WasmValue { 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), @@ -105,6 +108,7 @@ impl WasmValue { Self::I64(_) => ValType::I64, Self::F32(_) => ValType::F32, Self::F64(_) => ValType::F64, + // Self::V128(_) => ValType::V128, Self::RefExtern(_) => ValType::RefExtern, Self::RefFunc(_) => ValType::RefFunc, Self::RefNull(ty) => *ty, @@ -114,8 +118,7 @@ impl WasmValue { /// Type of a WebAssembly value. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[cfg_attr(feature = "archive", archive(check_bytes))] +#[cfg_attr(feature = "archive", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize), archive(check_bytes))] pub enum ValType { /// A 32-bit integer. I32, @@ -125,6 +128,8 @@ pub enum ValType { F32, /// A 64-bit float. F64, + /// A 128-bit vector + // V128, /// A reference to a function. RefFunc, /// A reference to an external value. @@ -136,6 +141,29 @@ impl ValType { pub fn default_value(&self) -> WasmValue { 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, + } + } } macro_rules! impl_conversion_for_wasmvalue { diff --git a/crates/wasm-testsuite/Cargo.toml b/crates/wasm-testsuite/Cargo.toml index 04f478c..97c1f58 100644 --- a/crates/wasm-testsuite/Cargo.toml +++ b/crates/wasm-testsuite/Cargo.toml @@ -1,6 +1,6 @@ [package] name="wasm-testsuite" -version="0.2.1" +version="0.2.2" description="Mirror of the WebAssembly core testsuite for use in testing WebAssembly implementations" license="Apache-2.0" readme="README.md" diff --git a/crates/wasm-testsuite/data b/crates/wasm-testsuite/data index dc27dad..5a1a590 160000 --- a/crates/wasm-testsuite/data +++ b/crates/wasm-testsuite/data @@ -1 +1 @@ -Subproject commit dc27dad3e34e466bdbfea32fe3c73f5e31f88560 +Subproject commit 5a1a590603d81f40ef471abba70a90a9ae5f4627 diff --git a/examples/archive.rs b/examples/archive.rs index 7c93205..4dd714a 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -12,7 +12,7 @@ const WASM: &str = r#" fn main() -> Result<()> { let wasm = wat::parse_str(WASM).expect("failed to parse wat"); - let module = Parser::default().parse_module_bytes(&wasm)?; + let module = Parser::default().parse_module_bytes(wasm)?; let twasm = module.serialize_twasm(); // now, you could e.g. write twasm to a file called `add.twasm` diff --git a/examples/rust/README.md b/examples/rust/README.md index 1b6be2f..c24054c 100644 --- a/examples/rust/README.md +++ b/examples/rust/README.md @@ -2,3 +2,5 @@ This is a seperate crate that generates WebAssembly from Rust code. It is used by the `wasm-rust` example. + +Requires the `wasm32-unknown-unknown` target to be installed. diff --git a/examples/rust/analyze.py b/examples/rust/analyze.py new file mode 100644 index 0000000..a450a1a --- /dev/null +++ b/examples/rust/analyze.py @@ -0,0 +1,36 @@ +import re +import sys +from collections import Counter + +seq_len = 5 + +# Check if a file path was provided +if len(sys.argv) < 2: + print("Usage: python script.py path/to/yourfile.wat") + sys.exit(1) + +# The first command line argument is the file path +file_path = sys.argv[1] + +# Regex to match WASM operators, adjust as necessary +operator_pattern = re.compile(r'\b[a-z0-9_]+\.[a-z0-9_]+\b') + +# Read the file +with open(file_path, 'r') as file: + content = file.read() + +# Find all operators +operators = operator_pattern.findall(content) + +# Generate sequences of three consecutive operators +sequences = [' '.join(operators[i:i+seq_len]) for i in range(len(operators) - 2)] + +# Count occurrences of each sequence +sequence_counts = Counter(sequences) + +# Sort sequences by their count, this time in ascending order for reverse display +sorted_sequences = sorted(sequence_counts.items(), key=lambda x: x[1]) + +# Print the sequences, now from least common to most common +for sequence, count in sorted_sequences: + print(f"{sequence}: {count}") diff --git a/examples/rust/rust-toolchain.toml b/examples/rust/rust-toolchain.toml new file mode 100644 index 0000000..6c22ba5 --- /dev/null +++ b/examples/rust/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel="nightly-2024-02-11" diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index b57a1da..cff38f5 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -18,6 +18,8 @@ use tinywasm::{Extern, FuncContext, Imports, MemoryStringExt, Module, Store}; /// https://github.com/WebAssembly/binaryen /// fn main() -> Result<()> { + pretty_env_logger::init(); + let args = std::env::args().collect::>(); if args.len() < 2 { println!("Usage: cargo run --example wasm-rust "); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index c046a09..6c22ba5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel="nightly" +channel="nightly-2024-02-11" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy