From f7f77cd3026e39a2c29faf539575ef57165c81db Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Thu, 27 Nov 2025 22:11:43 +0100 Subject: [PATCH 01/47] chore: remove unstable-simd feature, rename logging feature to log Signed-off-by: Henry Gressmann --- Cargo.lock | 216 ++--- Cargo.toml | 10 +- README.md | 2 +- crates/parser/Cargo.toml | 4 +- crates/parser/README.md | 2 +- crates/parser/src/lib.rs | 5 +- crates/parser/src/visit.rs | 2 +- crates/tinywasm/Cargo.toml | 7 +- crates/tinywasm/src/instance.rs | 4 +- crates/tinywasm/src/interpreter/executor.rs | 915 +++++++++--------- crates/tinywasm/src/interpreter/mod.rs | 4 +- .../tinywasm/src/interpreter/no_std_floats.rs | 1 + .../tinywasm/src/interpreter/num_helpers.rs | 32 - .../src/interpreter/stack/call_stack.rs | 2 +- .../src/interpreter/stack/value_stack.rs | 7 +- crates/tinywasm/src/interpreter/value128.rs | 76 ++ crates/tinywasm/src/interpreter/values.rs | 46 +- crates/tinywasm/src/lib.rs | 7 +- crates/tinywasm/src/store/memory.rs | 25 - crates/tinywasm/src/store/mod.rs | 14 +- crates/types/Cargo.toml | 4 +- crates/types/src/archive.rs | 1 - crates/types/src/lib.rs | 4 +- 23 files changed, 674 insertions(+), 716 deletions(-) create mode 100644 crates/tinywasm/src/interpreter/value128.rs diff --git a/Cargo.lock b/Cargo.lock index 39e9f077..2547a991 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -63,9 +63,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" @@ -81,9 +81,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" @@ -114,18 +114,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", @@ -133,9 +133,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cobs" @@ -258,19 +258,20 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hermit-abi" @@ -311,9 +312,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -321,13 +322,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -353,9 +354,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -426,18 +427,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -464,9 +465,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.3" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -476,9 +477,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -487,9 +488,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rust-fuzzy-search" @@ -563,9 +564,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -627,7 +628,7 @@ dependencies = [ "tinywasm-parser", "tinywasm-types", "wasm-testsuite", - "wast", + "wast 242.0.0", "wat", ] @@ -640,7 +641,7 @@ dependencies = [ "log", "pretty_env_logger", "tinywasm", - "wast", + "wast 242.0.0", ] [[package]] @@ -649,7 +650,7 @@ version = "0.9.0-alpha.0" dependencies = [ "log", "tinywasm-types", - "wasmparser", + "wasmparser 0.242.0", ] [[package]] @@ -673,15 +674,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "walkdir" @@ -700,7 +701,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.239.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.242.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f90e55bc9c6ee6954a757cc6eb3424d96b442e5252ed10fea627e518878d36" +dependencies = [ + "leb128fmt", + "wasmparser 0.242.0", ] [[package]] @@ -710,7 +721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bc4691cd6c251229ed1580c7547800f4ffb4ec9586d7ed5f2a6e1f130d65aa5" dependencies = [ "include_dir", - "wast", + "wast 239.0.0", ] [[package]] @@ -724,6 +735,17 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.242.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3c6e611f4cd748d85c767815823b777dc56afca793fcda27beae4e85028849" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + [[package]] name = "wast" version = "239.0.0" @@ -734,16 +756,29 @@ dependencies = [ "leb128fmt", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.239.0", +] + +[[package]] +name = "wast" +version = "242.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a61ae2997784a4ae2a47b3a99f7cf0ad2a54db09624a28a0c2e9d7a24408ce" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.242.0", ] [[package]] name = "wat" -version = "1.239.0" +version = "1.242.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1c941927d34709f255558166f8901a2005f8ab4a9650432e9281b7cc6f3b75" +checksum = "5ae8cf6adfb79b5d89cb3fe68bd56aaab9409d9cf23b588097eae7d75585dae2" dependencies = [ - "wast", + "wast 242.0.0", ] [[package]] @@ -752,93 +787,40 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.1", + "windows-sys", ] [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] -name = "windows-sys" -version = "0.61.1" +name = "zerocopy" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ - "windows-link", + "zerocopy-derive", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "zerocopy-derive" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "proc-macro2", + "quote", + "syn", ] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 77702726..443c8d00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,22 +4,22 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="239" -wat="1.239" -wasmparser={version="0.239", default-features=false} +wast="242" +wat="1.242" +wasmparser={version="0.242", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" criterion={version="0.7", default-features=false, features=["cargo_bench_support", "rayon"]} wasm-testsuite={version="0.5"} -indexmap="2.11" +indexmap="2.12" owo-colors={version="4.2"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} [workspace.package] version="0.9.0-alpha.0" -rust-version="1.90" +rust-version="1.91" edition="2024" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] diff --git a/README.md b/README.md index ee1028fd..29b019fa 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ $ tinywasm-cli --help - **`std`**\ Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. -- **`logging`**\ +- **`log`**\ Enables logging using the `log` crate. This is enabled by default. - **`parser`**\ Enables the `tinywasm-parser` crate. This is enabled by default. diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 3cbeee10..598a5b7e 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -16,6 +16,6 @@ log={workspace=true, optional=true} tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} [features] -default=["std", "logging"] -logging=["log"] +default=["std", "log"] +log=["dep:log"] std=["tinywasm-types/std", "wasmparser/std"] diff --git a/crates/parser/README.md b/crates/parser/README.md index 563cd6bd..104cd506 100644 --- a/crates/parser/README.md +++ b/crates/parser/README.md @@ -6,7 +6,7 @@ It uses [my fork](https://crates.io/crates/tinywasm-wasmparser) of the [`wasmpar ## Features - `std`: Enables the use of `std` and `std::io` for parsing from files and streams. -- `logging`: Enables logging of the parsing process using the `log` crate. +- `log`: Enables logging of the parsing process using the `log` crate. ## Usage diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 3f45db87..3f543b62 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -13,12 +13,12 @@ extern crate alloc; extern crate std; // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] #[allow(clippy::single_component_path_imports, unused_imports)] use log; // noop fallback if logging is disabled. -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] #[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); @@ -67,6 +67,7 @@ impl Parser { bulk_memory_opt: true, call_indirect_overlong: true, + custom_descriptors: false, cm_threading: false, extended_const: false, wide_arithmetic: false, diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 4ae2cc17..9b9ab750 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -158,7 +158,7 @@ macro_rules! impl_visit_operator { (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { #[cold] - fn $visit(&mut self $($(,$arg: $argty)*)?) { + fn $visit(&mut self $($(,_: $argty)*)?) { self.unsupported(stringify!($visit)) } }; diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 2315b9b1..d1a08397 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -34,9 +34,9 @@ serde_json.workspace=true serde.workspace=true [features] -default=["std", "parser", "logging", "archive", "canonicalize_nans"] +default=["std", "parser", "log", "archive", "canonicalize_nans"] -logging=["log", "tinywasm-parser?/logging", "tinywasm-types/logging"] +log=["dep:log", "tinywasm-parser?/log", "tinywasm-types/log"] std=["tinywasm-parser?/std", "tinywasm-types/std"] # support for parsing WebAssembly @@ -48,9 +48,6 @@ archive=["tinywasm-types/archive"] # canonicalize all NaN values to a single representation canonicalize_nans=[] -# enable simd support (unstable / unfinished) -unstable-simd=[] - [[test]] name="test-wasm-1" harness=false diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index aaa3407d..0ca8f6f8 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -4,7 +4,7 @@ use tinywasm_types::*; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store}; -/// An instanciated WebAssembly module +/// An instantiated WebAssembly module /// /// Backed by an Rc, so cloning is cheap /// @@ -72,7 +72,7 @@ impl ModuleInstance { let global_addrs = store.init_globals(addrs.globals, module.0.globals.into(), &addrs.funcs, idx)?; let (elem_addrs, elem_trapped) = store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?; - let (data_addrs, data_trapped) = store.init_datas(&addrs.memories, module.0.data.into(), idx)?; + let (data_addrs, data_trapped) = store.init_data(&addrs.memories, module.0.data.into(), idx)?; let instance = ModuleInstanceInner { failed_to_instantiate: elem_trapped.is_some() || data_trapped.is_some(), diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 2e979bc9..8033a4ea 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -8,17 +8,10 @@ use core::ops::ControlFlow; use interpreter::stack::CallFrame; use tinywasm_types::*; -#[cfg(all(feature = "std", feature = "unstable-simd"))] -use crate::std::simd::StdFloat; -#[cfg(feature = "unstable-simd")] -use core::simd::{cmp::*, num::*, *}; - -#[cfg(feature = "unstable-simd")] -use core::ops::{Index, IndexMut, Shl, Shr}; - use super::num_helpers::*; use super::stack::{BlockFrame, BlockType, Stack}; use super::values::*; +use crate::interpreter::Value128; use crate::*; pub(crate) struct Executor<'store, 'stack> { @@ -316,410 +309,410 @@ impl<'store, 'stack> Executor<'store, 'stack> { LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), - #[cfg(feature = "unstable-simd")] V128Not => self.stack.values.replace_top_same::(|v| Ok(!v)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128AndNot => self.stack.values.calculate_same::(|a, b| Ok(a & (!b))).to_cf()?, - #[cfg(feature = "unstable-simd")] V128Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - #[cfg(feature = "unstable-simd")] V128Bitselect => self.stack.values.calculate_same_3::(|v1, v2, c| Ok((v1 & c) | (v2 & !c))).to_cf()?, - #[cfg(feature = "unstable-simd")] V128AnyTrue => self.stack.values.replace_top::(|v| Ok((v.reduce_or() != 0) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16Swizzle => self.stack.values.calculate_same::(|a, s| Ok(a.swizzle_dyn(s))).to_cf()?, - - #[cfg(feature = "unstable-simd")] V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, - #[cfg(feature = "unstable-simd")] V128Load8x8S(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load8x8U(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load16x4S(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load16x4U(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load32x2S(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load32x2U(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load8Splat(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load16Splat(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load32Splat(_arg) => unimplemented!(), - #[cfg(feature = "unstable-simd")] V128Load64Splat(_arg) => unimplemented!(), - - #[cfg(feature = "unstable-simd")] V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, - - #[cfg(feature = "unstable-simd")] V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. - #[cfg(feature = "unstable-simd")] V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - let bytes = v.to_le_bytes(); - u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - })?, - #[cfg(feature = "unstable-simd")] V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - let bytes = v.to_le_bytes(); - u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 0, 0, 0, 0, 0, 0, 0, 0]) - })?, - - #[cfg(feature = "unstable-simd")] V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].to_le_bytes().into()), - - #[cfg(feature = "unstable-simd")] I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - - #[cfg(feature = "unstable-simd")] V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - #[cfg(feature = "unstable-simd")] V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - #[cfg(feature = "unstable-simd")] I8x16ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4ReplaceLane(_lane) => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2ReplaceLane(_lane) => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] F32x4Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i64))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i64))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u8))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u16))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u32))).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u64))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16NarrowI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I8x16NarrowI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8NarrowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8NarrowI32x4U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - #[cfg(feature = "unstable-simd")] I16x8SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - - #[cfg(feature = "unstable-simd")] I8x16AvgrU => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8AvgrU => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I16x8ExtAddPairwiseI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtAddPairwiseI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtAddPairwiseI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtAddPairwiseI16x8U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I16x8ExtMulLowI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtMulLowI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtMulHighI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtMulHighI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulLowI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulLowI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulHighI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtMulHighI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulLowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulLowI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulHighI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtMulHighI32x4U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I16x8ExtendLowI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtendLowI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtendHighI8x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8ExtendHighI8x16U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendLowI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendLowI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendHighI16x8S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4ExtendHighI16x8U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendLowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendLowI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendHighI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2ExtendHighI32x4U => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones())).to_cf()?, - #[cfg(feature = "unstable-simd")] I8x16Shuffle(_idx) => unimplemented!(), - - #[cfg(feature = "unstable-simd")] - I16x8Q15MulrSatS => self.stack.values.calculate_same::(|a, b| { - let subq15mulr = |a,b| { - let a = a as i32; - let b = b as i32; - let r = (a * b + 0x4000) >> 15; - if r > i16::MAX as i32 { - i16::MAX - } else if r < i16::MIN as i32 { - i16::MIN - } else { - r as i16 - } - }; - Ok(Simd::::from_array([ - subq15mulr(a[0], b[0]), - subq15mulr(a[1], b[1]), - subq15mulr(a[2], b[2]), - subq15mulr(a[3], b[3]), - subq15mulr(a[4], b[4]), - subq15mulr(a[5], b[5]), - subq15mulr(a[6], b[6]), - subq15mulr(a[7], b[7]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - I32x4DotI16x8S => self.stack.values.calculate::(|a, b| { - Ok(Simd::::from_array([ - i32::from(a[0] * b[0] + a[1] * b[1]), - i32::from(a[2] * b[2] + a[3] * b[3]), - i32::from(a[4] * b[4] + a[5] * b[5]), - i32::from(a[6] * b[6] + a[7] * b[7]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] F32x4Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f32x4(v.sqrt()))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f64x2(v.sqrt()))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a + b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a + b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a - b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a - b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a * b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a * b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a / b))).to_cf()?, - #[cfg(feature = "unstable-simd")] F64x2Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a / b))).to_cf()?, - #[cfg(feature = "unstable-simd")] - F32x4Min => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_minimum(a[0]), - b[1].tw_minimum(a[1]), - b[2].tw_minimum(a[2]), - b[3].tw_minimum(a[3]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2Min => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_minimum(a[0]), - b[1].tw_minimum(a[1]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F32x4Max => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_maximum(a[0]), - b[1].tw_maximum(a[1]), - b[2].tw_maximum(a[2]), - b[3].tw_maximum(a[3]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2Max => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - b[0].tw_maximum(a[0]), - b[1].tw_maximum(a[1]), - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F32x4PMin => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] < a[0] { b[0] } else { a[0]}, - if b[1] < a[1] { b[1] } else { a[1]}, - if b[2] < a[2] { b[2] } else { a[2]}, - if b[3] < a[3] { b[3] } else { a[3]}, - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F32x4PMax => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] > a[0] { b[0] } else { a[0]}, - if b[1] > a[1] { b[1] } else { a[1]}, - if b[2] > a[2] { b[2] } else { a[2]}, - if b[3] > a[3] { b[3] } else { a[3]}, - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2PMin => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] < a[0] { b[0] } else { a[0]}, - if b[1] < a[1] { b[1] } else { a[1]}, - ])) - }).to_cf()?, - - #[cfg(feature = "unstable-simd")] - F64x2PMax => self.stack.values.calculate_same::(|a, b| { - Ok(Simd::::from_array([ - if b[0] > a[0] { b[0] } else { a[0]}, - if b[1] > a[1] { b[1] } else { a[1]}, - ])) - }).to_cf()?, - - // not correct - #[cfg(feature = "unstable-simd")] I32x4TruncSatF32x4S => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] I32x4TruncSatF32x4U => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, - #[cfg(feature = "unstable-simd")] F32x4ConvertI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4ConvertI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2ConvertLowI32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2ConvertLowI32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4DemoteF64x2Zero => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2PromoteLowF32x4 => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4TruncSatF64x2SZero => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4TruncSatF64x2UZero => unimplemented!(), - - #[cfg(feature = "unstable-simd")] I8x16RelaxedSwizzle => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF32x4S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF32x4U => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF64x2SZero => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedTruncF64x2UZero => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedMadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedNmadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedMadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedNmadd => unimplemented!(), - #[cfg(feature = "unstable-simd")] I8x16RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] I64x2RelaxedLaneselect => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedMin => unimplemented!(), - #[cfg(feature = "unstable-simd")] F32x4RelaxedMax => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedMin => unimplemented!(), - #[cfg(feature = "unstable-simd")] F64x2RelaxedMax => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8RelaxedQ15mulrS => unimplemented!(), - #[cfg(feature = "unstable-simd")] I16x8RelaxedDotI8x16I7x16S => unimplemented!(), - #[cfg(feature = "unstable-simd")] I32x4RelaxedDotI8x16I7x16AddS => unimplemented!(), + V128Not => self.stack.values.replace_top_same::(|v| Ok(!v)).to_cf()?, + V128And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, + V128AndNot => self.stack.values.calculate_same::(|a, b| Ok(a & (!b))).to_cf()?, + V128Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, + V128Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, + V128Bitselect => self.stack.values.calculate_same_3::(|v1, v2, c| Ok((v1 & c) | (v2 & !c))).to_cf()?, + V128AnyTrue => self.stack.values.replace_top::(|v| Ok((v.reduce_or() != 0) as i32)).to_cf()?, + I8x16Swizzle => self.stack.values.calculate_same::(|a, s| Ok(a.swizzle(s))).to_cf()?, + + // V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, + // V128Load8x8S(_arg) => unimplemented!(), + // V128Load8x8U(_arg) => unimplemented!(), + // V128Load16x4S(_arg) => unimplemented!(), + // V128Load16x4U(_arg) => unimplemented!(), + // V128Load32x2S(_arg) => unimplemented!(), + // V128Load32x2U(_arg) => unimplemented!(), + // V128Load8Splat(_arg) => unimplemented!(), + // V128Load16Splat(_arg) => unimplemented!(), + // V128Load32Splat(_arg) => unimplemented!(), + // V128Load64Splat(_arg) => unimplemented!(), + + // V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, + + // V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + + // // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. + // V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { + // let bytes = v.to_le_bytes(); + // u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + // })?, + // V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { + // let bytes = v.to_le_bytes(); + // u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 0, 0, 0, 0, 0, 0, 0, 0]) + // })?, + + // V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].to_le_bytes().into()), + + // I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + // I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + // F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + // F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + + // V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + + // I8x16ReplaceLane(_lane) => unimplemented!(), + // I16x8ReplaceLane(_lane) => unimplemented!(), + // I32x4ReplaceLane(_lane) => unimplemented!(), + // I64x2ReplaceLane(_lane) => unimplemented!(), + // F32x4ReplaceLane(_lane) => unimplemented!(), + // F64x2ReplaceLane(_lane) => unimplemented!(), + + // I8x16Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i8))).to_cf()?, + // I16x8Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i16))).to_cf()?, + // I32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + // I64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + // F32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + // F64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + + // I8x16Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + // I16x8Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + // I32x4Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + // I64x2Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + // F32x4Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + // F64x2Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, + + // I8x16Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + // I16x8Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + // I32x4Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + // I64x2Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + // F32x4Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + // F64x2Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, + + // I8x16LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // I16x8LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // I32x4LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // I64x2LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // I8x16LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // I16x8LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // I32x4LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // F32x4Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + // F64x2Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, + + // F32x4Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // F64x2Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + + // I8x16GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // I16x8GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // I32x4GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // I64x2GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // I64x2LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + // F32x4Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + // F64x2Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + + // I8x16GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // I16x8GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // I32x4GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, + // F32x4Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + // F64x2Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + + // I8x16LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + // I16x8LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + // I32x4LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + + // I8x16LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + // I16x8LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + // I32x4LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, + + // I8x16GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + // I16x8GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + // I32x4GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + // I64x2GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + + // I8x16GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + // I16x8GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + // I32x4GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, + + // I8x16Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + // I16x8Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + // I32x4Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + // I64x2Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, + + // I8x16Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + // I16x8Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + // I32x4Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + // I64x2Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, + + // I8x16AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + // I16x8AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + // I32x4AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + // I64x2AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, + + // I8x16Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + // I16x8Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + // I32x4Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + // I64x2Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, + + // I8x16Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i8))).to_cf()?, + // I16x8Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i16))).to_cf()?, + // I32x4Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a))).to_cf()?, + // I64x2Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i64))).to_cf()?, + + // I8x16ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i8))).to_cf()?, + // I16x8ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i16))).to_cf()?, + // I32x4ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a))).to_cf()?, + // I64x2ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i64))).to_cf()?, + + // I8x16ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u8))).to_cf()?, + // I16x8ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u16))).to_cf()?, + // I32x4ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u32))).to_cf()?, + // I64x2ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u64))).to_cf()?, + + // I8x16Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + // I16x8Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + // I32x4Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + // I64x2Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, + + // I8x16Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + // I16x8Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + // I32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + // I64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, + + // I8x16MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + // I16x8MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + // I32x4MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + + // I8x16MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + // I16x8MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + // I32x4MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, + + // I8x16MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + // I16x8MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + // I32x4MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + + // I8x16MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + // I16x8MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + // I32x4MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, + + // I64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + // I16x8Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + // I32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, + + // I8x16NarrowI16x8S => unimplemented!(), + // I8x16NarrowI16x8U => unimplemented!(), + // I16x8NarrowI32x4S => unimplemented!(), + // I16x8NarrowI32x4U => unimplemented!(), + + // I8x16AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + // I16x8AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + // I8x16AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + // I16x8AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, + // I8x16SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + // I16x8SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + // I8x16SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + // I16x8SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, + + // I8x16AvgrU => unimplemented!(), + // I16x8AvgrU => unimplemented!(), + + // I16x8ExtAddPairwiseI8x16S => unimplemented!(), + // I16x8ExtAddPairwiseI8x16U => unimplemented!(), + // I32x4ExtAddPairwiseI16x8S => unimplemented!(), + // I32x4ExtAddPairwiseI16x8U => unimplemented!(), + + // I16x8ExtMulLowI8x16S => unimplemented!(), + // I16x8ExtMulLowI8x16U => unimplemented!(), + // I16x8ExtMulHighI8x16S => unimplemented!(), + // I16x8ExtMulHighI8x16U => unimplemented!(), + // I32x4ExtMulLowI16x8S => unimplemented!(), + // I32x4ExtMulLowI16x8U => unimplemented!(), + // I32x4ExtMulHighI16x8S => unimplemented!(), + // I32x4ExtMulHighI16x8U => unimplemented!(), + // I64x2ExtMulLowI32x4S => unimplemented!(), + // I64x2ExtMulLowI32x4U => unimplemented!(), + // I64x2ExtMulHighI32x4S => unimplemented!(), + // I64x2ExtMulHighI32x4U => unimplemented!(), + + // I16x8ExtendLowI8x16S => unimplemented!(), + // I16x8ExtendLowI8x16U => unimplemented!(), + // I16x8ExtendHighI8x16S => unimplemented!(), + // I16x8ExtendHighI8x16U => unimplemented!(), + // I32x4ExtendLowI16x8S => unimplemented!(), + // I32x4ExtendLowI16x8U => unimplemented!(), + // I32x4ExtendHighI16x8S => unimplemented!(), + // I32x4ExtendHighI16x8U => unimplemented!(), + // I64x2ExtendLowI32x4S => unimplemented!(), + // I64x2ExtendLowI32x4U => unimplemented!(), + // I64x2ExtendHighI32x4S => unimplemented!(), + // I64x2ExtendHighI32x4U => unimplemented!(), + + // I8x16Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones())).to_cf()?, + // I8x16Shuffle(_idx) => unimplemented!(), + + + // I16x8Q15MulrSatS => self.stack.values.calculate_same::(|a, b| { + // let subq15mulr = |a,b| { + // let a = a as i32; + // let b = b as i32; + // let r = (a * b + 0x4000) >> 15; + // if r > i16::MAX as i32 { + // i16::MAX + // } else if r < i16::MIN as i32 { + // i16::MIN + // } else { + // r as i16 + // } + // }; + // Ok(Simd::::from_array([ + // subq15mulr(a[0], b[0]), + // subq15mulr(a[1], b[1]), + // subq15mulr(a[2], b[2]), + // subq15mulr(a[3], b[3]), + // subq15mulr(a[4], b[4]), + // subq15mulr(a[5], b[5]), + // subq15mulr(a[6], b[6]), + // subq15mulr(a[7], b[7]), + // ])) + // }).to_cf()?, + + + // I32x4DotI16x8S => self.stack.values.calculate::(|a, b| { + // Ok(Simd::::from_array([ + // i32::from(a[0] * b[0] + a[1] * b[1]), + // i32::from(a[2] * b[2] + a[3] * b[3]), + // i32::from(a[4] * b[4] + a[5] * b[5]), + // i32::from(a[6] * b[6] + a[7] * b[7]), + // ])) + // }).to_cf()?, + + // F32x4Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, + // F64x2Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, + // F32x4Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, + // F64x2Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, + // F32x4Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, + // F64x2Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, + // F32x4Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, + // F64x2Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, + // F32x4Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, + // F64x2Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, + // F32x4Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, + // F64x2Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, + // F32x4Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f32x4(v.sqrt()))).to_cf()?, + // F64x2Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f64x2(v.sqrt()))).to_cf()?, + // F32x4Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a + b))).to_cf()?, + // F64x2Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a + b))).to_cf()?, + // F32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a - b))).to_cf()?, + // F64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a - b))).to_cf()?, + // F32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a * b))).to_cf()?, + // F64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a * b))).to_cf()?, + // F32x4Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a / b))).to_cf()?, + // F64x2Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a / b))).to_cf()?, + + // F32x4Min => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // b[0].tw_minimum(a[0]), + // b[1].tw_minimum(a[1]), + // b[2].tw_minimum(a[2]), + // b[3].tw_minimum(a[3]), + // ])) + // }).to_cf()?, + + + // F64x2Min => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // b[0].tw_minimum(a[0]), + // b[1].tw_minimum(a[1]), + // ])) + // }).to_cf()?, + + + // F32x4Max => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // b[0].tw_maximum(a[0]), + // b[1].tw_maximum(a[1]), + // b[2].tw_maximum(a[2]), + // b[3].tw_maximum(a[3]), + // ])) + // }).to_cf()?, + + + // F64x2Max => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // b[0].tw_maximum(a[0]), + // b[1].tw_maximum(a[1]), + // ])) + // }).to_cf()?, + + + // F32x4PMin => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // if b[0] < a[0] { b[0] } else { a[0]}, + // if b[1] < a[1] { b[1] } else { a[1]}, + // if b[2] < a[2] { b[2] } else { a[2]}, + // if b[3] < a[3] { b[3] } else { a[3]}, + // ])) + // }).to_cf()?, + + + // F32x4PMax => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // if b[0] > a[0] { b[0] } else { a[0]}, + // if b[1] > a[1] { b[1] } else { a[1]}, + // if b[2] > a[2] { b[2] } else { a[2]}, + // if b[3] > a[3] { b[3] } else { a[3]}, + // ])) + // }).to_cf()?, + + + // F64x2PMin => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // if b[0] < a[0] { b[0] } else { a[0]}, + // if b[1] < a[1] { b[1] } else { a[1]}, + // ])) + // }).to_cf()?, + + + // F64x2PMax => self.stack.values.calculate_same::(|a, b| { + // Ok(Simd::::from_array([ + // if b[0] > a[0] { b[0] } else { a[0]}, + // if b[1] > a[1] { b[1] } else { a[1]}, + // ])) + // }).to_cf()?, + + // // not correct + // I32x4TruncSatF32x4S => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, + // I32x4TruncSatF32x4U => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, + // F32x4ConvertI32x4S => unimplemented!(), + // F32x4ConvertI32x4U => unimplemented!(), + // F64x2ConvertLowI32x4S => unimplemented!(), + // F64x2ConvertLowI32x4U => unimplemented!(), + // F32x4DemoteF64x2Zero => unimplemented!(), + // F64x2PromoteLowF32x4 => unimplemented!(), + // I32x4TruncSatF64x2SZero => unimplemented!(), + // I32x4TruncSatF64x2UZero => unimplemented!(), + + // I8x16RelaxedSwizzle => unimplemented!(), + // I32x4RelaxedTruncF32x4S => unimplemented!(), + // I32x4RelaxedTruncF32x4U => unimplemented!(), + // I32x4RelaxedTruncF64x2SZero => unimplemented!(), + // I32x4RelaxedTruncF64x2UZero => unimplemented!(), + // F32x4RelaxedMadd => unimplemented!(), + // F32x4RelaxedNmadd => unimplemented!(), + // F64x2RelaxedMadd => unimplemented!(), + // F64x2RelaxedNmadd => unimplemented!(), + // I8x16RelaxedLaneselect => unimplemented!(), + // I16x8RelaxedLaneselect => unimplemented!(), + // I32x4RelaxedLaneselect => unimplemented!(), + // I64x2RelaxedLaneselect => unimplemented!(), + // F32x4RelaxedMin => unimplemented!(), + // F32x4RelaxedMax => unimplemented!(), + // F64x2RelaxedMin => unimplemented!(), + // F64x2RelaxedMax => unimplemented!(), + // I16x8RelaxedQ15mulrS => unimplemented!(), + // I16x8RelaxedDotI8x16I7x16S => unimplemented!(), + // I32x4RelaxedDotI8x16I7x16AddS => unimplemented!(), #[allow(unreachable_patterns)] i => return ControlFlow::Break(Some(Error::UnsupportedFeature(format!("unimplemented opcode: {i:?}")))), @@ -995,7 +988,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { let data = self .store .data - .datas + .data .get(self.module.resolve_data_addr(data_index) as usize) .ok_or_else(|| Error::Other("data not found".to_string()))?; @@ -1045,33 +1038,32 @@ impl<'store, 'stack> Executor<'store, 'stack> { } } - #[cfg(feature = "unstable-simd")] - fn exec_mem_load_lane< - LOAD: MemLoadable, - INTO: InternalValue + IndexMut, - const LOAD_SIZE: usize, - >( - &mut self, - mem_addr: tinywasm_types::MemAddr, - offset: u64, - lane: u8, - ) -> ControlFlow> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); - let mut imm = self.stack.values.pop::(); - let val = self.stack.values.pop::() as u64; - let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { - cold(); - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: val as usize, - len: LOAD_SIZE, - max: 0, - }))); - }; - let val = mem.load_as::(addr).to_cf()?; - imm[lane as usize] = val; - self.stack.values.push(imm); - ControlFlow::Continue(()) - } + // fn exec_mem_load_lane< + // LOAD: MemLoadable, + // INTO: InternalValue + IndexMut, + // const LOAD_SIZE: usize, + // >( + // &mut self, + // mem_addr: tinywasm_types::MemAddr, + // offset: u64, + // lane: u8, + // ) -> ControlFlow> { + // let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); + // let mut imm = self.stack.values.pop::(); + // let val = self.stack.values.pop::() as u64; + // let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { + // cold(); + // return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { + // offset: val as usize, + // len: LOAD_SIZE, + // max: 0, + // }))); + // }; + // let val = mem.load_as::(addr).to_cf()?; + // imm[lane as usize] = val; + // self.stack.values.push(imm); + // ControlFlow::Continue(()) + // } fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( &mut self, @@ -1099,28 +1091,27 @@ impl<'store, 'stack> Executor<'store, 'stack> { ControlFlow::Continue(()) } - #[cfg(feature = "unstable-simd")] - fn exec_mem_store_lane, U: MemStorable + Copy, const N: usize>( - &mut self, - mem_addr: tinywasm_types::MemAddr, - offset: u64, - lane: u8, - ) -> ControlFlow> { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - let val = self.stack.values.pop::(); - let val = val[lane as usize].to_mem_bytes(); - - let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => self.stack.values.pop::() as u32 as u64, - }; - - if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return ControlFlow::Break(Some(e)); - } - - ControlFlow::Continue(()) - } + // fn exec_mem_store_lane, U: MemStorable + Copy, const N: usize>( + // &mut self, + // mem_addr: tinywasm_types::MemAddr, + // offset: u64, + // lane: u8, + // ) -> ControlFlow> { + // let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); + // let val = self.stack.values.pop::(); + // let val = val[lane as usize].to_mem_bytes(); + + // let addr = match mem.is_64bit() { + // true => self.stack.values.pop::() as u64, + // false => self.stack.values.pop::() as u32 as u64, + // }; + + // if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { + // return ControlFlow::Break(Some(e)); + // } + + // ControlFlow::Continue(()) + // } fn exec_mem_store, const N: usize>( &mut self, diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 0b7df2f9..6d94ce10 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -1,12 +1,14 @@ pub(crate) mod executor; pub(crate) mod num_helpers; pub(crate) mod stack; -mod values; +pub(crate) mod value128; +pub(crate) mod values; #[cfg(not(feature = "std"))] mod no_std_floats; use crate::{Result, Store}; +pub(crate) use value128::*; pub use values::*; /// The main `TinyWasm` runtime. diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs index e2731844..b6ce998a 100644 --- a/crates/tinywasm/src/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -1,3 +1,4 @@ +// see https://github.com/rust-lang/rust/issues/137578 :( pub(super) trait NoStdFloatExt { fn round(self) -> Self; fn ceil(self) -> Self; diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index da27f3de..03564032 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -190,35 +190,3 @@ macro_rules! impl_checked_wrapping_rem { } impl_checked_wrapping_rem! { i32 i64 u32 u64 } - -#[cfg(all(feature = "unstable-simd", not(feature = "canonicalize_nans")))] -#[inline] -pub(crate) fn canonicalize_f32x4(x: core::simd::f32x4) -> core::simd::f32x4 { - x // No need to do anything, as we are not replacing NaNs -} - -#[cfg(all(feature = "unstable-simd", feature = "canonicalize_nans"))] -/// replace all NaNs in a f32x4 with f32::NAN -#[inline] -pub(crate) fn canonicalize_f32x4(x: core::simd::f32x4) -> core::simd::f32x4 { - use core::simd::{Simd, num::SimdFloat}; - let nan = Simd::splat(f32::NAN); - let mask = x.is_nan(); - mask.select(nan, x) -} - -#[cfg(all(feature = "unstable-simd", not(feature = "canonicalize_nans")))] -#[inline] -pub(crate) fn canonicalize_f64x2(x: core::simd::f64x2) -> core::simd::f64x2 { - x // No need to do anything, as we are not replacing NaNs -} - -#[cfg(feature = "unstable-simd")] -/// replace all NaNs in a f64x2 with f64::NAN -#[inline] -pub(crate) fn canonicalize_f64x2(x: core::simd::f64x2) -> core::simd::f64x2 { - use core::simd::{Simd, num::SimdFloat}; - let nan = Simd::splat(f64::NAN); - let mask = x.is_nan(); - mask.select(nan, x) -} diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index c7f02b66..e0a4d6a6 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -2,7 +2,7 @@ use core::ops::ControlFlow; use super::BlockType; use crate::Trap; -use crate::interpreter::values::*; +use crate::interpreter::{Value128, values::*}; use crate::{Error, unlikely}; use alloc::boxed::Box; diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 054ffe3c..6e02b0f1 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -188,12 +188,7 @@ impl ValueStack { ValType::F64 => WasmValue::F64(self.pop()), ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(self.pop())), ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(self.pop())), - - #[cfg(not(feature = "unstable-simd"))] - ValType::V128 => WasmValue::V128(self.pop()), - - #[cfg(feature = "unstable-simd")] - ValType::V128 => WasmValue::V128(i128::from_le_bytes(self.pop::().to_array())), + ValType::V128 => WasmValue::V128(self.pop::().into()), } } diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs new file mode 100644 index 00000000..12d70e56 --- /dev/null +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -0,0 +1,76 @@ +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Value128(i128); + +impl Value128 { + pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self(i128::from_le_bytes(bytes)) + } + + pub const fn to_le_bytes(self) -> [u8; 16] { + self.0.to_le_bytes() + } + + pub const fn reduce_or(self) -> u8 { + let mut result = 0u8; + let bytes = self.to_le_bytes(); + let mut i = 0; + while i < 16 { + result |= bytes[i]; + i += 1; + } + result + } + + pub const fn swizzle(self, s: Self) -> Self { + let a_bytes = self.to_le_bytes(); + let s_bytes = s.to_le_bytes(); + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 16 { + let index = s_bytes[i] as usize; + result_bytes[i] = if index < 16 { a_bytes[index] } else { 0 }; + i += 1; + } + Self::from_le_bytes(result_bytes) + } +} + +impl From for i128 { + fn from(val: Value128) -> Self { + val.0 + } +} + +impl From for Value128 { + fn from(value: i128) -> Self { + Self(value) + } +} + +impl core::ops::Not for Value128 { + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } +} + +impl core::ops::BitAnd for Value128 { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } +} + +impl core::ops::BitOr for Value128 { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl core::ops::BitXor for Value128 { + type Output = Self; + fn bitxor(self, rhs: Self) -> Self::Output { + Self(self.0 ^ rhs.0) + } +} diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 9cf90caa..0940c97e 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,4 +1,4 @@ -use crate::Result; +use crate::{Result, interpreter::value128::Value128}; use super::stack::{Locals, ValueStack}; use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, WasmValue}; @@ -7,11 +7,6 @@ pub(crate) type Value32 = u32; pub(crate) type Value64 = u64; pub(crate) type ValueRef = Option; -#[cfg(feature = "unstable-simd")] -pub(crate) type Value128 = core::simd::u8x16; -#[cfg(not(feature = "unstable-simd"))] -pub(crate) type Value128 = i128; - #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// A untyped WebAssembly value pub enum TinyWasmValue { @@ -112,12 +107,7 @@ impl TinyWasmValue { ValType::F64 => WasmValue::F64(f64::from_bits(self.unwrap_64())), ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(self.unwrap_ref())), ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(self.unwrap_ref())), - - #[cfg(feature = "unstable-simd")] - ValType::V128 => WasmValue::V128(i128::from_le_bytes(self.unwrap_128().to_array())), - - #[cfg(not(feature = "unstable-simd"))] - ValType::V128 => WasmValue::V128(self.unwrap_128()), + ValType::V128 => WasmValue::V128(self.unwrap_128().into()), } } } @@ -131,12 +121,7 @@ impl From<&WasmValue> for TinyWasmValue { WasmValue::F64(v) => Self::Value64(v.to_bits()), WasmValue::RefExtern(v) => Self::ValueRef(v.addr()), WasmValue::RefFunc(v) => Self::ValueRef(v.addr()), - - #[cfg(not(feature = "unstable-simd"))] - WasmValue::V128(v) => Self::Value128(*v), - - #[cfg(feature = "unstable-simd")] - WasmValue::V128(v) => TinyWasmValue::Value128(v.to_le_bytes().into()), + WasmValue::V128(v) => Self::Value128((*v).into()), } } } @@ -147,6 +132,12 @@ impl From for TinyWasmValue { } } +impl From for TinyWasmValue { + fn from(value: i128) -> Self { + Self::Value128(Value128::from(value)) + } +} + mod sealed { #[expect(unreachable_pub)] pub trait Sealed {} @@ -272,22 +263,3 @@ impl_internalvalue! { ValueRef, stack_ref, locals_ref, ValueRef, ValueRef, |v| v, |v| v Value128, stack_128, locals_128, Value128, Value128, |v| v, |v| v } - -#[cfg(feature = "unstable-simd")] -use core::simd::{num::SimdUint, *}; - -#[cfg(feature = "unstable-simd")] -impl_internalvalue! { - Value128, stack_128, locals_128, u8x16, i128, |v: i128| v.to_le_bytes().into(), |v: u8x16| i128::from_le_bytes(v.into()) - Value128, stack_128, locals_128, u8x16, u128, |v: u128| v.to_le_bytes().into(), |v: u8x16| u128::from_le_bytes(v.into()) - Value128, stack_128, locals_128, u8x16, i8x16, |v: i8x16| v.to_le_bytes(), |v: u8x16| v.cast() - Value128, stack_128, locals_128, u8x16, i16x8, |v: i16x8| v.to_le_bytes(), |v: u8x16| i16x8::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, i32x4, |v: i32x4| v.to_le_bytes(), |v: u8x16| i32x4::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, i64x2, |v: i64x2| v.to_le_bytes(), |v: u8x16| i64x2::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, f32x4, |v: f32x4| v.to_le_bytes(), |v: u8x16| f32x4::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, f64x2, |v: f64x2| v.to_le_bytes(), |v: u8x16| f64x2::from_le_bytes(v) - - Value128, stack_128, locals_128, u8x16, u16x8, |v: u16x8| v.to_le_bytes(), |v: u8x16| u16x8::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, u32x4, |v: u32x4| v.to_le_bytes(), |v: u8x16| u32x4::from_le_bytes(v) - Value128, stack_128, locals_128, u8x16, u64x2, |v: u64x2| v.to_le_bytes(), |v: u8x16| u64x2::from_le_bytes(v) -} diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 00378294..a176670c 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -5,7 +5,6 @@ ))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] #![forbid(unsafe_code)] -#![cfg_attr(feature = "unstable-simd", feature(portable_simd))] //! A tiny WebAssembly Runtime written in Rust //! @@ -16,7 +15,7 @@ //! ## Features //!- **`std`**\ //! Enables the use of `std` and `std::io` for parsing from files and streams. This is enabled by default. -//!- **`logging`**\ +//!- **`log`**\ //! Enables logging using the `log` crate. This is enabled by default. //!- **`parser`**\ //! Enables the `tinywasm-parser` crate. This is enabled by default. @@ -97,12 +96,12 @@ mod std; extern crate alloc; // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] #[expect(clippy::single_component_path_imports)] use log; // noop fallback if logging is disabled. -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] #[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index b8ca1e84..267ca9a5 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -4,9 +4,6 @@ use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; use crate::{Error, Result, cold, interpreter::Value128, log}; -#[cfg(feature = "unstable-simd")] -use core::simd::ToBytes; - /// A WebAssembly Memory Instance /// /// See @@ -189,28 +186,6 @@ macro_rules! impl_mem_traits { impl_mem_traits!(u8, 1, i8, 1, u16, 2, i16, 2, u32, 4, i32, 4, f32, 4, u64, 8, i64, 8, f64, 8, Value128, 16); -#[cfg(feature = "unstable-simd")] -impl_mem_traits!( - core::simd::i8x16, - 16, - core::simd::i16x8, - 16, - core::simd::i32x4, - 16, - core::simd::i64x2, - 16, - core::simd::u16x8, - 16, - core::simd::u32x4, - 16, - core::simd::u64x2, - 16, - core::simd::f32x4, - 16, - core::simd::f64x2, - 16 -); - #[cfg(test)] mod memory_instance_tests { use super::*; diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 8446297d..9cafa7e4 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -111,7 +111,7 @@ pub(crate) struct StoreData { pub(crate) memories: Vec, pub(crate) globals: Vec, pub(crate) elements: Vec, - pub(crate) datas: Vec, + pub(crate) data: Vec, } impl Store { @@ -199,7 +199,7 @@ impl Store { /// Get the data at the actual index in the store #[inline] pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { - &mut self.data.datas[addr as usize] + &mut self.data.data[addr as usize] } /// Get the element at the actual index in the store @@ -370,15 +370,15 @@ impl Store { } /// Add data to the store, returning their addresses in the store - pub(crate) fn init_datas( + pub(crate) fn init_data( &mut self, mem_addrs: &[MemAddr], - datas: Vec, + data: Vec, idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { - let data_count = self.data.datas.len(); + let data_count = self.data.data.len(); let mut data_addrs = Vec::with_capacity(data_count); - for (i, data) in datas.into_iter().enumerate() { + for (i, data) in data.into_iter().enumerate() { let data_val = match data.kind { tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { let Some(mem_addr) = mem_addrs.get(mem_addr as usize) else { @@ -399,7 +399,7 @@ impl Store { tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), }; - self.data.datas.push(DataInstance::new(data_val, idx)); + self.data.data.push(DataInstance::new(data_val, idx)); data_addrs.push((i + data_count) as Addr); } diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index b986c2a3..5efcb8b1 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -16,8 +16,8 @@ postcard={version="1.1", optional=true, default-features=false, features=["alloc serde={version="1.0", optional=true, default-features=false, features=["alloc"]} [features] -default=["std", "logging", "archive"] +default=["std", "log", "archive"] std=["serde?/std"] archive=["dep:postcard", "dep:serde"] -logging=["dep:log"] +log=["dep:log"] diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 7d52049d..069addf3 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -51,7 +51,6 @@ impl TinyWasmModule { /// Creates a `TinyWasmModule` from a slice of bytes. pub fn from_twasm(wasm: &[u8]) -> Result { let len = validate_magic(wasm)?; - postcard::from_bytes(&wasm[len..]).map_err(TwasmError::InvalidArchive) } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index c34b705c..beaa8285 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -21,12 +21,12 @@ const fn max_page_count(page_size: u64) -> u64 { } // log for logging (optional). -#[cfg(feature = "logging")] +#[cfg(feature = "log")] #[allow(clippy::single_component_path_imports, unused_imports)] use log; // noop fallback if logging is disabled. -#[cfg(not(feature = "logging"))] +#[cfg(not(feature = "log"))] #[allow(unused_imports, unused_macros)] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); From 2911204b3b2508c729c2584016cccd3ef57ffc91 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 8 Dec 2025 21:58:16 +0100 Subject: [PATCH 02/47] chore: update everything, more simd stuff Signed-off-by: Henry Gressmann --- Cargo.lock | 145 +++++++++++------- Cargo.toml | 10 +- crates/parser/src/conversion.rs | 8 +- crates/tinywasm/src/interpreter/executor.rs | 134 ++++++++-------- crates/tinywasm/src/interpreter/value128.rs | 141 +++++++++++++++++ crates/tinywasm/src/store/memory.rs | 13 +- .../generated/wasm-custom-page-sizes.csv | 2 +- crates/tinywasm/tests/generated/wasm-simd.csv | 2 +- examples/simple2.rs | 21 +++ 9 files changed, 334 insertions(+), 142 deletions(-) create mode 100644 examples/simple2.rs diff --git a/Cargo.lock b/Cargo.lock index 2547a991..bc5a5fd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -79,6 +88,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -148,10 +167,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -160,6 +180,7 @@ dependencies = [ "itertools", "num-traits", "oorandom", + "page_size", "rayon", "regex", "serde", @@ -170,9 +191,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", "itertools", @@ -256,6 +277,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "half" version = "2.7.1" @@ -354,9 +381,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libm" @@ -403,6 +430,16 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "postcard" version = "1.1.3" @@ -562,6 +599,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" version = "2.0.111" @@ -628,7 +671,7 @@ dependencies = [ "tinywasm-parser", "tinywasm-types", "wasm-testsuite", - "wast 242.0.0", + "wast", "wat", ] @@ -641,7 +684,7 @@ dependencies = [ "log", "pretty_env_logger", "tinywasm", - "wast 242.0.0", + "wast", ] [[package]] @@ -650,7 +693,7 @@ version = "0.9.0-alpha.0" dependencies = [ "log", "tinywasm-types", - "wasmparser 0.242.0", + "wasmparser", ] [[package]] @@ -696,50 +739,29 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.239.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" dependencies = [ "leb128fmt", - "wasmparser 0.239.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.242.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67f90e55bc9c6ee6954a757cc6eb3424d96b442e5252ed10fea627e518878d36" -dependencies = [ - "leb128fmt", - "wasmparser 0.242.0", + "wasmparser", ] [[package]] name = "wasm-testsuite" -version = "0.5.12" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bc4691cd6c251229ed1580c7547800f4ffb4ec9586d7ed5f2a6e1f130d65aa5" +checksum = "cad9eeaabcdbbe221f3fd9a90eabab05362d8f444dc09bc0930a341ea9779e9e" dependencies = [ "include_dir", - "wast 239.0.0", -] - -[[package]] -name = "wasmparser" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" -dependencies = [ - "bitflags", - "indexmap", - "semver", + "wast", ] [[package]] name = "wasmparser" -version = "0.242.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3c6e611f4cd748d85c767815823b777dc56afca793fcda27beae4e85028849" +checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" dependencies = [ "bitflags", "indexmap", @@ -748,39 +770,42 @@ dependencies = [ [[package]] name = "wast" -version = "239.0.0" +version = "243.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9139176fe8a2590e0fb174cdcaf373b224cb93c3dde08e4297c1361d2ba1ea5d" +checksum = "df21d01c2d91e46cb7a221d79e58a2d210ea02020d57c092e79255cc2999ca7f" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", - "wasm-encoder 0.239.0", + "wasm-encoder", ] [[package]] -name = "wast" -version = "242.0.0" +name = "wat" +version = "1.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50a61ae2997784a4ae2a47b3a99f7cf0ad2a54db09624a28a0c2e9d7a24408ce" +checksum = "226a9a91cd80a50449312fef0c75c23478fcecfcc4092bdebe1dc8e760ef521b" dependencies = [ - "bumpalo", - "leb128fmt", - "memchr", - "unicode-width", - "wasm-encoder 0.242.0", + "wast", ] [[package]] -name = "wat" -version = "1.242.0" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae8cf6adfb79b5d89cb3fe68bd56aaab9409d9cf23b588097eae7d75585dae2" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "wast 242.0.0", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -790,6 +815,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" @@ -807,18 +838,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 443c8d00..e6d1cb41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,14 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="242" -wat="1.242" -wasmparser={version="0.242", default-features=false} +wast="243" +wat="1.243" +wasmparser={version="0.243", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" -criterion={version="0.7", default-features=false, features=["cargo_bench_support", "rayon"]} -wasm-testsuite={version="0.5"} +criterion={version="0.8", default-features=false, features=["cargo_bench_support", "rayon"]} +wasm-testsuite={version="0.6"} indexmap="2.12" owo-colors={version="4.2"} serde_json={version="1.0"} diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index a4e3b353..b62e670f 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -94,6 +94,12 @@ pub(crate) fn convert_module_import(import: wasmparser::Import<'_>) -> Result { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); } + _ => { + return Err(crate::ParseError::UnsupportedOperator(format!( + "Unsupported import kind: {:?}", + import.ty + ))); + } }, }) } @@ -156,7 +162,7 @@ pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result ExternalKind::Table, wasmparser::ExternalKind::Memory => ExternalKind::Memory, wasmparser::ExternalKind::Global => ExternalKind::Global, - wasmparser::ExternalKind::Tag => { + wasmparser::ExternalKind::Tag | wasmparser::ExternalKind::FuncExact => { return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported export kind: {:?}", export.kind))); } }; diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 8033a4ea..f026244f 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -318,45 +318,45 @@ impl<'store, 'stack> Executor<'store, 'stack> { V128AnyTrue => self.stack.values.replace_top::(|v| Ok((v.reduce_or() != 0) as i32)).to_cf()?, I8x16Swizzle => self.stack.values.calculate_same::(|a, s| Ok(a.swizzle(s))).to_cf()?, - // V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, - // V128Load8x8S(_arg) => unimplemented!(), - // V128Load8x8U(_arg) => unimplemented!(), - // V128Load16x4S(_arg) => unimplemented!(), - // V128Load16x4U(_arg) => unimplemented!(), - // V128Load32x2S(_arg) => unimplemented!(), - // V128Load32x2U(_arg) => unimplemented!(), - // V128Load8Splat(_arg) => unimplemented!(), - // V128Load16Splat(_arg) => unimplemented!(), - // V128Load32Splat(_arg) => unimplemented!(), - // V128Load64Splat(_arg) => unimplemented!(), - - // V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, - - // V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - // V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - // V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - // V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - // // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. - // V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - // let bytes = v.to_le_bytes(); - // u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - // })?, - // V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - // let bytes = v.to_le_bytes(); - // u8x16::from_array([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 0, 0, 0, 0, 0, 0, 0, 0]) - // })?, - - // V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].to_le_bytes().into()), - - // I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - // I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - // F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - // F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, + V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::extend_8_i8)?, + V128Load8x8U(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_8_u8)?, + V128Load16x4S(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_4_i16)?, + V128Load16x4U(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_4_u16)?, + V128Load32x2S(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_2_i32)?, + V128Load32x2U(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_2_u32)?, + V128Load8Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i8)?, + V128Load16Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i16)?, + V128Load32Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i32)?, + V128Load64Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i64)?, + + V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, + + V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + + // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. + V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { + let bytes = v.to_le_bytes(); + Value128::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + })?, + V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { + let bytes = v.to_le_bytes(); + Value128::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 0, 0, 0, 0, 0, 0, 0, 0]) + })?, + + V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].into()), + + // I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, + // I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + // I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + // F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + // F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, // V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, // V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, @@ -1038,18 +1038,14 @@ impl<'store, 'stack> Executor<'store, 'stack> { } } - // fn exec_mem_load_lane< - // LOAD: MemLoadable, - // INTO: InternalValue + IndexMut, - // const LOAD_SIZE: usize, - // >( + // fn exec_mem_load_lane, const LOAD_SIZE: usize>( // &mut self, // mem_addr: tinywasm_types::MemAddr, // offset: u64, // lane: u8, // ) -> ControlFlow> { // let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); - // let mut imm = self.stack.values.pop::(); + // let mut imm = self.stack.values.pop::().to_mem_bytes(); // let val = self.stack.values.pop::() as u64; // let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { // cold(); @@ -1059,13 +1055,15 @@ impl<'store, 'stack> Executor<'store, 'stack> { // max: 0, // }))); // }; - // let val = mem.load_as::(addr).to_cf()?; - // imm[lane as usize] = val; - // self.stack.values.push(imm); + // let val = mem.load_as::(addr).to_cf()?.to_mem_bytes(); + + // // imm[lane as usize] = val; + + // self.stack.values.push(Value128::from_mem_bytes(imm)); // ControlFlow::Continue(()) // } - fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( + fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, @@ -1091,29 +1089,29 @@ impl<'store, 'stack> Executor<'store, 'stack> { ControlFlow::Continue(()) } - // fn exec_mem_store_lane, U: MemStorable + Copy, const N: usize>( - // &mut self, - // mem_addr: tinywasm_types::MemAddr, - // offset: u64, - // lane: u8, - // ) -> ControlFlow> { - // let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - // let val = self.stack.values.pop::(); - // let val = val[lane as usize].to_mem_bytes(); + fn exec_mem_store_lane + Copy, const N: usize>( + &mut self, + mem_addr: tinywasm_types::MemAddr, + offset: u64, + lane: u8, + ) -> ControlFlow> { + let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); + let val = self.stack.values.pop::().to_mem_bytes(); + let val = val[lane as usize].to_mem_bytes(); - // let addr = match mem.is_64bit() { - // true => self.stack.values.pop::() as u64, - // false => self.stack.values.pop::() as u32 as u64, - // }; + let addr = match mem.is_64bit() { + true => self.stack.values.pop::() as u64, + false => self.stack.values.pop::() as u32 as u64, + }; - // if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - // return ControlFlow::Break(Some(e)); - // } + if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { + return ControlFlow::Break(Some(e)); + } - // ControlFlow::Continue(()) - // } + ControlFlow::Continue(()) + } - fn exec_mem_store, const N: usize>( + fn exec_mem_store, const N: usize>( &mut self, mem_addr: tinywasm_types::MemAddr, offset: u64, diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index 12d70e56..b9c96a9f 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -33,6 +33,147 @@ impl Value128 { } Self::from_le_bytes(result_bytes) } + + pub const fn extend_8_i8(src: i8) -> Self { + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 8 { + result_bytes[i * 2] = src as u8; + result_bytes[i * 2 + 1] = if src < 0 { 0xFF } else { 0x00 }; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn extend_8_u8(src: u8) -> Self { + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 8 { + result_bytes[i * 2] = src; + result_bytes[i * 2 + 1] = 0x00; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn extend_4_i16(src: i16) -> Self { + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 4 { + let bytes = src.to_le_bytes(); + result_bytes[i * 4] = bytes[0]; + result_bytes[i * 4 + 1] = bytes[1]; + result_bytes[i * 4 + 2] = if src < 0 { 0xFF } else { 0x00 }; + result_bytes[i * 4 + 3] = if src < 0 { 0xFF } else { 0x00 }; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn extend_4_u16(src: u16) -> Self { + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 4 { + let bytes = src.to_le_bytes(); + result_bytes[i * 4] = bytes[0]; + result_bytes[i * 4 + 1] = bytes[1]; + result_bytes[i * 4 + 2] = 0x00; + result_bytes[i * 4 + 3] = 0x00; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn extend_2_i32(src: i32) -> Self { + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 2 { + let bytes = src.to_le_bytes(); + result_bytes[i * 8] = bytes[0]; + result_bytes[i * 8 + 1] = bytes[1]; + result_bytes[i * 8 + 2] = bytes[2]; + result_bytes[i * 8 + 3] = bytes[3]; + result_bytes[i * 8 + 4] = if src < 0 { 0xFF } else { 0x00 }; + result_bytes[i * 8 + 5] = if src < 0 { 0xFF } else { 0x00 }; + result_bytes[i * 8 + 6] = if src < 0 { 0xFF } else { 0x00 }; + result_bytes[i * 8 + 7] = if src < 0 { 0xFF } else { 0x00 }; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn extend_2_u32(src: u32) -> Self { + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 2 { + let bytes = src.to_le_bytes(); + result_bytes[i * 8] = bytes[0]; + result_bytes[i * 8 + 1] = bytes[1]; + result_bytes[i * 8 + 2] = bytes[2]; + result_bytes[i * 8 + 3] = bytes[3]; + result_bytes[i * 8 + 4] = 0x00; + result_bytes[i * 8 + 5] = 0x00; + result_bytes[i * 8 + 6] = 0x00; + result_bytes[i * 8 + 7] = 0x00; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_i8(src: i8) -> Self { + let mut result_bytes = [0u8; 16]; + let byte = src as u8; + let mut i = 0; + while i < 16 { + result_bytes[i] = byte; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_i16(src: i16) -> Self { + let mut result_bytes = [0u8; 16]; + let bytes = src.to_le_bytes(); + let mut i = 0; + while i < 8 { + result_bytes[i * 2] = bytes[0]; + result_bytes[i * 2 + 1] = bytes[1]; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_i32(src: i32) -> Self { + let mut result_bytes = [0u8; 16]; + let bytes = src.to_le_bytes(); + let mut i = 0; + while i < 4 { + result_bytes[i * 4] = bytes[0]; + result_bytes[i * 4 + 1] = bytes[1]; + result_bytes[i * 4 + 2] = bytes[2]; + result_bytes[i * 4 + 3] = bytes[3]; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_i64(src: i64) -> Self { + let mut result_bytes = [0u8; 16]; + let bytes = src.to_le_bytes(); + let mut i = 0; + while i < 2 { + result_bytes[i * 8] = bytes[0]; + result_bytes[i * 8 + 1] = bytes[1]; + result_bytes[i * 8 + 2] = bytes[2]; + result_bytes[i * 8 + 3] = bytes[3]; + result_bytes[i * 8 + 4] = bytes[4]; + result_bytes[i * 8 + 5] = bytes[5]; + result_bytes[i * 8 + 6] = bytes[6]; + result_bytes[i * 8 + 7] = bytes[7]; + i += 1; + } + Self::from_le_bytes(result_bytes) + } } impl From for i128 { diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 267ca9a5..88982120 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -76,7 +76,7 @@ impl MemoryInstance { Ok(&self.data[addr..end]) } - pub(crate) fn load_as>(&self, addr: usize) -> Result { + pub(crate) fn load_as>(&self, addr: usize) -> Result { let Some(end) = addr.checked_add(SIZE) else { return Err(self.trap_oob(addr, SIZE)); }; @@ -152,14 +152,11 @@ impl MemoryInstance { } } -/// A trait for types that can be stored in memory -pub(crate) trait MemStorable { +/// A trait for types that can be converted to and from static byte arrays +pub(crate) trait MemValue: Copy + Sized { /// Store a value in memory fn to_mem_bytes(self) -> [u8; N]; -} -/// A trait for types that can be loaded from memory -pub(crate) trait MemLoadable: Sized + Copy { /// Load a value from memory fn from_mem_bytes(bytes: [u8; N]) -> Self; } @@ -167,14 +164,12 @@ pub(crate) trait MemLoadable: Sized + Copy { macro_rules! impl_mem_traits { ($($ty:ty, $size:expr),*) => { $( - impl MemLoadable<$size> for $ty { + impl MemValue<$size> for $ty { #[inline(always)] fn from_mem_bytes(bytes: [u8; $size]) -> Self { <$ty>::from_le_bytes(bytes.into()) } - } - impl MemStorable<$size> for $ty { #[inline(always)] fn to_mem_bytes(self) -> [u8; $size] { self.to_le_bytes().into() diff --git a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv index 5ba121e3..1761a0ad 100644 --- a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv +++ b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv @@ -1,2 +1,2 @@ 0.8.0,57,0,[{"name":"custom-page-sizes-invalid.wast","passed":22,"failed":0},{"name":"custom-page-sizes.wast","passed":35,"failed":0}] -0.9.0-alpha.0,76,0,[{"name":"custom-page-sizes-invalid.wast","passed":23,"failed":0},{"name":"custom-page-sizes.wast","passed":45,"failed":0},{"name":"memory_max.wast","passed":4,"failed":0},{"name":"memory_max_i64.wast","passed":4,"failed":0}] +0.9.0-alpha.0,80,0,[{"name":"custom-page-sizes-invalid.wast","passed":23,"failed":0},{"name":"custom-page-sizes.wast","passed":45,"failed":0},{"name":"memory_max.wast","passed":6,"failed":0},{"name":"memory_max_i64.wast","passed":6,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv index 73e3b920..f87cc691 100644 --- a/crates/tinywasm/tests/generated/wasm-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -1,2 +1,2 @@ 0.8.0,1300,24679,[{"name":"simd_address.wast","passed":4,"failed":45},{"name":"simd_align.wast","passed":46,"failed":54},{"name":"simd_bit_shift.wast","passed":39,"failed":213},{"name":"simd_bitwise.wast","passed":28,"failed":141},{"name":"simd_boolean.wast","passed":16,"failed":261},{"name":"simd_const.wast","passed":301,"failed":456},{"name":"simd_conversions.wast","passed":48,"failed":234},{"name":"simd_f32x4.wast","passed":16,"failed":774},{"name":"simd_f32x4_arith.wast","passed":16,"failed":1806},{"name":"simd_f32x4_cmp.wast","passed":24,"failed":2583},{"name":"simd_f32x4_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f32x4_rounding.wast","passed":24,"failed":177},{"name":"simd_f64x2.wast","passed":8,"failed":795},{"name":"simd_f64x2_arith.wast","passed":16,"failed":1809},{"name":"simd_f64x2_cmp.wast","passed":24,"failed":2661},{"name":"simd_f64x2_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f64x2_rounding.wast","passed":24,"failed":177},{"name":"simd_i16x8_arith.wast","passed":11,"failed":183},{"name":"simd_i16x8_arith2.wast","passed":19,"failed":153},{"name":"simd_i16x8_cmp.wast","passed":30,"failed":435},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":4,"failed":17},{"name":"simd_i16x8_extmul_i8x16.wast","passed":12,"failed":105},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":3,"failed":27},{"name":"simd_i16x8_sat_arith.wast","passed":16,"failed":206},{"name":"simd_i32x4_arith.wast","passed":11,"failed":183},{"name":"simd_i32x4_arith2.wast","passed":26,"failed":123},{"name":"simd_i32x4_cmp.wast","passed":40,"failed":435},{"name":"simd_i32x4_dot_i16x8.wast","passed":3,"failed":27},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":4,"failed":17},{"name":"simd_i32x4_extmul_i16x8.wast","passed":12,"failed":105},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":4,"failed":103},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":4,"failed":103},{"name":"simd_i64x2_arith.wast","passed":11,"failed":189},{"name":"simd_i64x2_arith2.wast","passed":2,"failed":23},{"name":"simd_i64x2_cmp.wast","passed":10,"failed":103},{"name":"simd_i64x2_extmul_i32x4.wast","passed":12,"failed":105},{"name":"simd_i8x16_arith.wast","passed":8,"failed":123},{"name":"simd_i8x16_arith2.wast","passed":25,"failed":186},{"name":"simd_i8x16_cmp.wast","passed":30,"failed":415},{"name":"simd_i8x16_sat_arith.wast","passed":24,"failed":190},{"name":"simd_int_to_int_extend.wast","passed":24,"failed":229},{"name":"simd_lane.wast","passed":189,"failed":286},{"name":"simd_linking.wast","passed":0,"failed":3},{"name":"simd_load.wast","passed":8,"failed":31},{"name":"simd_load16_lane.wast","passed":3,"failed":33},{"name":"simd_load32_lane.wast","passed":3,"failed":21},{"name":"simd_load64_lane.wast","passed":3,"failed":13},{"name":"simd_load8_lane.wast","passed":3,"failed":49},{"name":"simd_load_extend.wast","passed":18,"failed":86},{"name":"simd_load_splat.wast","passed":12,"failed":114},{"name":"simd_load_zero.wast","passed":10,"failed":29},{"name":"simd_splat.wast","passed":23,"failed":162},{"name":"simd_store.wast","passed":9,"failed":19},{"name":"simd_store16_lane.wast","passed":3,"failed":33},{"name":"simd_store32_lane.wast","passed":3,"failed":21},{"name":"simd_store64_lane.wast","passed":3,"failed":13},{"name":"simd_store8_lane.wast","passed":3,"failed":49}] -0.9.0-alpha.0,24450,1539,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":757,"failed":0},{"name":"simd_conversions.wast","passed":50,"failed":232},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":187,"failed":14},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1824,"failed":1},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":187,"failed":14},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":142,"failed":30},{"name":"simd_i16x8_cmp.wast","passed":436,"failed":29},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":5,"failed":16},{"name":"simd_i16x8_extmul_i8x16.wast","passed":13,"failed":104},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":450,"failed":25},{"name":"simd_i32x4_dot_i16x8.wast","passed":14,"failed":18},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":5,"failed":16},{"name":"simd_i32x4_extmul_i16x8.wast","passed":13,"failed":104},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":17,"failed":90},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":5,"failed":102},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":13,"failed":104},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":179,"failed":32},{"name":"simd_i8x16_cmp.wast","passed":409,"failed":36},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":25,"failed":228},{"name":"simd_lane.wast","passed":331,"failed":144},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":37,"failed":2},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":20,"failed":84},{"name":"simd_load_splat.wast","passed":14,"failed":112},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":183,"failed":2},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] +0.9.0-alpha.0,2460,23529,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":41,"failed":211},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":139,"failed":138},{"name":"simd_const.wast","passed":755,"failed":2},{"name":"simd_conversions.wast","passed":50,"failed":232},{"name":"simd_f32x4.wast","passed":18,"failed":772},{"name":"simd_f32x4_arith.wast","passed":19,"failed":1803},{"name":"simd_f32x4_cmp.wast","passed":26,"failed":2581},{"name":"simd_f32x4_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f32x4_rounding.wast","passed":25,"failed":176},{"name":"simd_f64x2.wast","passed":10,"failed":793},{"name":"simd_f64x2_arith.wast","passed":19,"failed":1806},{"name":"simd_f64x2_cmp.wast","passed":26,"failed":2659},{"name":"simd_f64x2_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f64x2_rounding.wast","passed":25,"failed":176},{"name":"simd_i16x8_arith.wast","passed":13,"failed":181},{"name":"simd_i16x8_arith2.wast","passed":21,"failed":151},{"name":"simd_i16x8_cmp.wast","passed":32,"failed":433},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":5,"failed":16},{"name":"simd_i16x8_extmul_i8x16.wast","passed":13,"failed":104},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":4,"failed":26},{"name":"simd_i16x8_sat_arith.wast","passed":18,"failed":204},{"name":"simd_i32x4_arith.wast","passed":13,"failed":181},{"name":"simd_i32x4_arith2.wast","passed":28,"failed":121},{"name":"simd_i32x4_cmp.wast","passed":42,"failed":433},{"name":"simd_i32x4_dot_i16x8.wast","passed":4,"failed":28},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":5,"failed":16},{"name":"simd_i32x4_extmul_i16x8.wast","passed":13,"failed":104},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":5,"failed":102},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":5,"failed":102},{"name":"simd_i64x2_arith.wast","passed":13,"failed":187},{"name":"simd_i64x2_arith2.wast","passed":4,"failed":21},{"name":"simd_i64x2_cmp.wast","passed":11,"failed":102},{"name":"simd_i64x2_extmul_i32x4.wast","passed":13,"failed":104},{"name":"simd_i8x16_arith.wast","passed":10,"failed":121},{"name":"simd_i8x16_arith2.wast","passed":27,"failed":184},{"name":"simd_i8x16_cmp.wast","passed":32,"failed":413},{"name":"simd_i8x16_sat_arith.wast","passed":26,"failed":188},{"name":"simd_int_to_int_extend.wast","passed":25,"failed":228},{"name":"simd_lane.wast","passed":213,"failed":262},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":29,"failed":10},{"name":"simd_load16_lane.wast","passed":4,"failed":32},{"name":"simd_load32_lane.wast","passed":4,"failed":20},{"name":"simd_load64_lane.wast","passed":4,"failed":12},{"name":"simd_load8_lane.wast","passed":4,"failed":48},{"name":"simd_load_extend.wast","passed":30,"failed":74},{"name":"simd_load_splat.wast","passed":122,"failed":4},{"name":"simd_load_zero.wast","passed":37,"failed":2},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":27,"failed":158},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":4,"failed":32},{"name":"simd_store32_lane.wast","passed":4,"failed":20},{"name":"simd_store64_lane.wast","passed":4,"failed":12},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] diff --git a/examples/simple2.rs b/examples/simple2.rs new file mode 100644 index 00000000..a3124f86 --- /dev/null +++ b/examples/simple2.rs @@ -0,0 +1,21 @@ +use eyre::Result; +use tinywasm::{Module, Store}; + +const WASM: &str = r#" +(module + (func $return (param $lhs i32) (param $rhs i64) (result i32 i64) + local.get $lhs + local.get $rhs) + (export "return" (func $return))) +"#; + +fn main() -> Result<()> { + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let add = instance.exported_func::<(i32, i64), (i32, i64)>(&store, "return")?; + + assert_eq!(add.call(&mut store, (1, 2))?, (1, 2)); + Ok(()) +} From dd8f0cad73c690be7d4c441e0d8f4aba6ceec227 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Mon, 5 Jan 2026 21:22:31 +0100 Subject: [PATCH 03/47] chore: more simd stuff Signed-off-by: Henry Gressmann --- Cargo.lock | 58 +++--- crates/tinywasm/src/interpreter/executor.rs | 168 ++++++++-------- crates/tinywasm/src/interpreter/value128.rs | 180 ++++++++++++++++++ crates/tinywasm/tests/generated/wasm-simd.csv | 2 +- 4 files changed, 293 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc5a5fd9..bd7be3a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,9 +78,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cast" @@ -90,9 +90,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "shlex", @@ -133,18 +133,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstyle", "clap_lex", @@ -279,9 +279,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "half" @@ -369,9 +369,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "leb128fmt" @@ -381,9 +381,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libm" @@ -393,9 +393,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -464,9 +464,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -535,12 +535,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -588,15 +582,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -607,9 +601,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" dependencies = [ "proc-macro2", "quote", @@ -855,3 +849,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index f026244f..c186c014 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -338,30 +338,24 @@ impl<'store, 'stack> Executor<'store, 'stack> { V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. - V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - let bytes = v.to_le_bytes(); - Value128::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - })?, - V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| { - let bytes = v.to_le_bytes(); - Value128::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], 0, 0, 0, 0, 0, 0, 0, 0]) - })?, + V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, + V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].into()), - // I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize] as i32)).to_cf()?, - // I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - // I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - // F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, - // F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v[*lane as usize])).to_cf()?, + I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i8(*lane) as i32)).to_cf()?, + I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_u8(*lane) as i32)).to_cf()?, + I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i16(*lane) as i32)).to_cf()?, + I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_u16(*lane) as i32)).to_cf()?, + I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i32(*lane))).to_cf()?, + I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i64(*lane))).to_cf()?, + F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_f32(*lane))).to_cf()?, + F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_f64(*lane))).to_cf()?, - // V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - // V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - // V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - // V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, // I8x16ReplaceLane(_lane) => unimplemented!(), // I16x8ReplaceLane(_lane) => unimplemented!(), @@ -370,12 +364,12 @@ impl<'store, 'stack> Executor<'store, 'stack> { // F32x4ReplaceLane(_lane) => unimplemented!(), // F64x2ReplaceLane(_lane) => unimplemented!(), - // I8x16Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i8))).to_cf()?, - // I16x8Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v as i16))).to_cf()?, - // I32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - // I64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - // F32x4Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, - // F64x2Splat => self.stack.values.replace_top::(|v| Ok(Simd::::splat(v))).to_cf()?, + I8x16Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i8(v as i8))).to_cf()?, + I16x8Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i16(v as i16))).to_cf()?, + I32x4Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i32(v))).to_cf()?, + I64x2Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i64(v))).to_cf()?, + F32x4Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_f32(v))).to_cf()?, + F64x2Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_f64(v))).to_cf()?, // I8x16Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, // I16x8Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, @@ -551,41 +545,44 @@ impl<'store, 'stack> Executor<'store, 'stack> { // I8x16Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones())).to_cf()?, // I8x16Shuffle(_idx) => unimplemented!(), - - // I16x8Q15MulrSatS => self.stack.values.calculate_same::(|a, b| { - // let subq15mulr = |a,b| { - // let a = a as i32; - // let b = b as i32; - // let r = (a * b + 0x4000) >> 15; - // if r > i16::MAX as i32 { - // i16::MAX - // } else if r < i16::MIN as i32 { - // i16::MIN - // } else { - // r as i16 - // } - // }; - // Ok(Simd::::from_array([ - // subq15mulr(a[0], b[0]), - // subq15mulr(a[1], b[1]), - // subq15mulr(a[2], b[2]), - // subq15mulr(a[3], b[3]), - // subq15mulr(a[4], b[4]), - // subq15mulr(a[5], b[5]), - // subq15mulr(a[6], b[6]), - // subq15mulr(a[7], b[7]), - // ])) - // }).to_cf()?, - - - // I32x4DotI16x8S => self.stack.values.calculate::(|a, b| { - // Ok(Simd::::from_array([ - // i32::from(a[0] * b[0] + a[1] * b[1]), - // i32::from(a[2] * b[2] + a[3] * b[3]), - // i32::from(a[4] * b[4] + a[5] * b[5]), - // i32::from(a[6] * b[6] + a[7] * b[7]), - // ])) - // }).to_cf()?, + I16x8Q15MulrSatS => self.stack.values.calculate_same::(|a, b| { + let subq15mulr = |a,b| { + let a = a as i32; + let b = b as i32; + let r = (a * b + 0x4000) >> 15; + if r > i16::MAX as i32 { + i16::MAX + } else if r < i16::MIN as i32 { + i16::MIN + } else { + r as i16 + } + }; + let a = a.as_i16x8(); + let b = b.as_i16x8(); + Ok(Value128::from_i16x8([ + subq15mulr(a[0], b[0]), + subq15mulr(a[1], b[1]), + subq15mulr(a[2], b[2]), + subq15mulr(a[3], b[3]), + subq15mulr(a[4], b[4]), + subq15mulr(a[5], b[5]), + subq15mulr(a[6], b[6]), + subq15mulr(a[7], b[7]), + ])) + }).to_cf()?, + + + I32x4DotI16x8S => self.stack.values.calculate::(|a, b| { + let a = a.as_i16x8(); + let b = b.as_i16x8(); + Ok(Value128::from_i32x4([ + i32::from(a[0] * b[0] + a[1] * b[1]), + i32::from(a[2] * b[2] + a[3] * b[3]), + i32::from(a[4] * b[4] + a[5] * b[5]), + i32::from(a[6] * b[6] + a[7] * b[7]), + ])) + }).to_cf()?, // F32x4Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, // F64x2Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, @@ -1038,30 +1035,31 @@ impl<'store, 'stack> Executor<'store, 'stack> { } } - // fn exec_mem_load_lane, const LOAD_SIZE: usize>( - // &mut self, - // mem_addr: tinywasm_types::MemAddr, - // offset: u64, - // lane: u8, - // ) -> ControlFlow> { - // let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); - // let mut imm = self.stack.values.pop::().to_mem_bytes(); - // let val = self.stack.values.pop::() as u64; - // let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { - // cold(); - // return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - // offset: val as usize, - // len: LOAD_SIZE, - // max: 0, - // }))); - // }; - // let val = mem.load_as::(addr).to_cf()?.to_mem_bytes(); - - // // imm[lane as usize] = val; - - // self.stack.values.push(Value128::from_mem_bytes(imm)); - // ControlFlow::Continue(()) - // } + fn exec_mem_load_lane, const LOAD_SIZE: usize>( + &mut self, + mem_addr: tinywasm_types::MemAddr, + offset: u64, + lane: u8, + ) -> ControlFlow> { + let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); + let mut imm = self.stack.values.pop::().to_mem_bytes(); + let val = self.stack.values.pop::() as u64; + let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { + cold(); + return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { + offset: val as usize, + len: LOAD_SIZE, + max: 0, + }))); + }; + let val = mem.load_as::(addr).to_cf()?.to_mem_bytes(); + + let offset = lane as usize * LOAD_SIZE; + imm[offset..offset + LOAD_SIZE].copy_from_slice(&val); + + self.stack.values.push(Value128::from_mem_bytes(imm)); + ControlFlow::Continue(()) + } fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( &mut self, diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index b9c96a9f..fcd208f9 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -10,6 +10,115 @@ impl Value128 { self.0.to_le_bytes() } + #[rustfmt::skip] + pub const fn as_i8x16(self) -> [i8; 16] { + let b = self.to_le_bytes(); + [b[0] as i8, b[1] as i8, b[2] as i8, b[3] as i8, b[4] as i8, b[5] as i8, b[6] as i8, b[7] as i8, b[8] as i8, b[9] as i8, b[10] as i8, b[11] as i8, b[12] as i8, b[13] as i8, b[14] as i8, b[15] as i8] + } + + #[rustfmt::skip] + pub const fn as_u8x16(self) -> [u8; 16] { + self.to_le_bytes() + } + + #[rustfmt::skip] + pub const fn from_i8x16(x: [i8; 16]) -> Self { + Self::from_le_bytes([x[0] as u8, x[1] as u8, x[2] as u8, x[3] as u8, x[4] as u8, x[5] as u8, x[6] as u8, x[7] as u8, x[8] as u8, x[9] as u8, x[10] as u8, x[11] as u8, x[12] as u8, x[13] as u8, x[14] as u8, x[15] as u8]) + } + + #[rustfmt::skip] + pub const fn from_u8x16(x: [u8; 16]) -> Self { + Self::from_le_bytes(x) + } + + #[rustfmt::skip] + pub const fn as_i16x8(self) -> [i16; 8] { + let b = self.to_le_bytes(); + [i16::from_le_bytes([b[0], b[1]]), i16::from_le_bytes([b[2], b[3]]), i16::from_le_bytes([b[4], b[5]]), i16::from_le_bytes([b[6], b[7]]), i16::from_le_bytes([b[8], b[9]]), i16::from_le_bytes([b[10], b[11]]), i16::from_le_bytes([b[12], b[13]]), i16::from_le_bytes([b[14], b[15]])] + } + + #[rustfmt::skip] + pub const fn as_u16x8(self) -> [u16; 8] { + let b = self.to_le_bytes(); + [u16::from_le_bytes([b[0], b[1]]), u16::from_le_bytes([b[2], b[3]]), u16::from_le_bytes([b[4], b[5]]), u16::from_le_bytes([b[6], b[7]]), u16::from_le_bytes([b[8], b[9]]), u16::from_le_bytes([b[10], b[11]]), u16::from_le_bytes([b[12], b[13]]), u16::from_le_bytes([b[14], b[15]])] + } + + #[rustfmt::skip] + pub const fn from_i16x8(x: [i16; 8]) -> Self { + Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) + } + + #[rustfmt::skip] + pub const fn from_u16x8(x: [u16; 8]) -> Self { + Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) + } + + #[rustfmt::skip] + pub const fn as_i32x4(self) -> [i32; 4] { + let b = self.to_le_bytes(); + [i32::from_le_bytes([b[0], b[1], b[2], b[3]]), i32::from_le_bytes([b[4], b[5], b[6], b[7]]), i32::from_le_bytes([b[8], b[9], b[10], b[11]]), i32::from_le_bytes([b[12], b[13], b[14], b[15]])] + } + + #[rustfmt::skip] + pub const fn as_u32x4(self) -> [u32; 4] { + let b = self.to_le_bytes(); + [u32::from_le_bytes([b[0], b[1], b[2], b[3]]), u32::from_le_bytes([b[4], b[5], b[6], b[7]]), u32::from_le_bytes([b[8], b[9], b[10], b[11]]), u32::from_le_bytes([b[12], b[13], b[14], b[15]])] + } + + #[rustfmt::skip] + pub const fn as_f32x4(self) -> [f32; 4] { + let b = self.to_le_bytes(); + [f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])), f32::from_bits(u32::from_le_bytes([b[4], b[5], b[6], b[7]])), f32::from_bits(u32::from_le_bytes([b[8], b[9], b[10], b[11]])), f32::from_bits(u32::from_le_bytes([b[12], b[13], b[14], b[15]]))] + } + + #[rustfmt::skip] + pub const fn from_i32x4(x: [i32; 4]) -> Self { + Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) + } + + #[rustfmt::skip] + pub const fn from_u32x4(x: [u32; 4]) -> Self { + Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) + } + + #[rustfmt::skip] + pub const fn from_f32x4(x: [f32; 4]) -> Self { + Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[2].to_bits().to_le_bytes()[0], x[2].to_bits().to_le_bytes()[1], x[2].to_bits().to_le_bytes()[2], x[2].to_bits().to_le_bytes()[3], x[3].to_bits().to_le_bytes()[0], x[3].to_bits().to_le_bytes()[1], x[3].to_bits().to_le_bytes()[2], x[3].to_bits().to_le_bytes()[3]]) + } + + #[rustfmt::skip] + pub const fn as_i64x2(self) -> [i64; 2] { + let b = self.to_le_bytes(); + [i64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), i64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] + } + + #[rustfmt::skip] + pub const fn as_u64x2(self) -> [u64; 2] { + let b = self.to_le_bytes(); + [u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] + } + + #[rustfmt::skip] + pub const fn as_f64x2(self) -> [f64; 2] { + let b = self.to_le_bytes(); + [f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])), f64::from_bits(u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]))] + } + + #[rustfmt::skip] + pub const fn from_i64x2(x: [i64; 2]) -> Self { + Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) + } + + #[rustfmt::skip] + pub const fn from_u64x2(x: [u64; 2]) -> Self { + Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) + } + + #[rustfmt::skip] + pub const fn from_f64x2(x: [f64; 2]) -> Self { + Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) + } + pub const fn reduce_or(self) -> u8 { let mut result = 0u8; let bytes = self.to_le_bytes(); @@ -174,6 +283,77 @@ impl Value128 { } Self::from_le_bytes(result_bytes) } + + pub const fn splat_f32(src: f32) -> Self { + Self::splat_i32(src.to_bits() as i32) + } + + pub const fn splat_f64(src: f64) -> Self { + Self::splat_i64(src.to_bits() as i64) + } + + pub const fn extract_lane_i8(self, lane: u8) -> i8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] as i8 + } + + pub const fn extract_lane_u8(self, lane: u8) -> u8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] + } + + pub const fn extract_lane_i16(self, lane: u8) -> i16 { + debug_assert!(lane < 8); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 2; + i16::from_le_bytes([bytes[start], bytes[start + 1]]) + } + + pub const fn extract_lane_u16(self, lane: u8) -> u16 { + debug_assert!(lane < 8); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 2; + u16::from_le_bytes([bytes[start], bytes[start + 1]]) + } + + pub const fn extract_lane_i32(self, lane: u8) -> i32 { + debug_assert!(lane < 4); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 4; + i32::from_le_bytes([bytes[start], bytes[start + 1], bytes[start + 2], bytes[start + 3]]) + } + + pub const fn extract_lane_i64(self, lane: u8) -> i64 { + debug_assert!(lane < 2); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 8; + i64::from_le_bytes([ + bytes[start], + bytes[start + 1], + bytes[start + 2], + bytes[start + 3], + bytes[start + 4], + bytes[start + 5], + bytes[start + 6], + bytes[start + 7], + ]) + } + + pub const fn extract_lane_f32(self, lane: u8) -> f32 { + f32::from_bits(self.extract_lane_i32(lane) as u32) + } + + pub const fn extract_lane_f64(self, lane: u8) -> f64 { + f64::from_bits(self.extract_lane_i64(lane) as u64) + } } impl From for i128 { diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv index f87cc691..5603ed09 100644 --- a/crates/tinywasm/tests/generated/wasm-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -1,2 +1,2 @@ 0.8.0,1300,24679,[{"name":"simd_address.wast","passed":4,"failed":45},{"name":"simd_align.wast","passed":46,"failed":54},{"name":"simd_bit_shift.wast","passed":39,"failed":213},{"name":"simd_bitwise.wast","passed":28,"failed":141},{"name":"simd_boolean.wast","passed":16,"failed":261},{"name":"simd_const.wast","passed":301,"failed":456},{"name":"simd_conversions.wast","passed":48,"failed":234},{"name":"simd_f32x4.wast","passed":16,"failed":774},{"name":"simd_f32x4_arith.wast","passed":16,"failed":1806},{"name":"simd_f32x4_cmp.wast","passed":24,"failed":2583},{"name":"simd_f32x4_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f32x4_rounding.wast","passed":24,"failed":177},{"name":"simd_f64x2.wast","passed":8,"failed":795},{"name":"simd_f64x2_arith.wast","passed":16,"failed":1809},{"name":"simd_f64x2_cmp.wast","passed":24,"failed":2661},{"name":"simd_f64x2_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f64x2_rounding.wast","passed":24,"failed":177},{"name":"simd_i16x8_arith.wast","passed":11,"failed":183},{"name":"simd_i16x8_arith2.wast","passed":19,"failed":153},{"name":"simd_i16x8_cmp.wast","passed":30,"failed":435},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":4,"failed":17},{"name":"simd_i16x8_extmul_i8x16.wast","passed":12,"failed":105},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":3,"failed":27},{"name":"simd_i16x8_sat_arith.wast","passed":16,"failed":206},{"name":"simd_i32x4_arith.wast","passed":11,"failed":183},{"name":"simd_i32x4_arith2.wast","passed":26,"failed":123},{"name":"simd_i32x4_cmp.wast","passed":40,"failed":435},{"name":"simd_i32x4_dot_i16x8.wast","passed":3,"failed":27},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":4,"failed":17},{"name":"simd_i32x4_extmul_i16x8.wast","passed":12,"failed":105},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":4,"failed":103},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":4,"failed":103},{"name":"simd_i64x2_arith.wast","passed":11,"failed":189},{"name":"simd_i64x2_arith2.wast","passed":2,"failed":23},{"name":"simd_i64x2_cmp.wast","passed":10,"failed":103},{"name":"simd_i64x2_extmul_i32x4.wast","passed":12,"failed":105},{"name":"simd_i8x16_arith.wast","passed":8,"failed":123},{"name":"simd_i8x16_arith2.wast","passed":25,"failed":186},{"name":"simd_i8x16_cmp.wast","passed":30,"failed":415},{"name":"simd_i8x16_sat_arith.wast","passed":24,"failed":190},{"name":"simd_int_to_int_extend.wast","passed":24,"failed":229},{"name":"simd_lane.wast","passed":189,"failed":286},{"name":"simd_linking.wast","passed":0,"failed":3},{"name":"simd_load.wast","passed":8,"failed":31},{"name":"simd_load16_lane.wast","passed":3,"failed":33},{"name":"simd_load32_lane.wast","passed":3,"failed":21},{"name":"simd_load64_lane.wast","passed":3,"failed":13},{"name":"simd_load8_lane.wast","passed":3,"failed":49},{"name":"simd_load_extend.wast","passed":18,"failed":86},{"name":"simd_load_splat.wast","passed":12,"failed":114},{"name":"simd_load_zero.wast","passed":10,"failed":29},{"name":"simd_splat.wast","passed":23,"failed":162},{"name":"simd_store.wast","passed":9,"failed":19},{"name":"simd_store16_lane.wast","passed":3,"failed":33},{"name":"simd_store32_lane.wast","passed":3,"failed":21},{"name":"simd_store64_lane.wast","passed":3,"failed":13},{"name":"simd_store8_lane.wast","passed":3,"failed":49}] -0.9.0-alpha.0,2460,23529,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":41,"failed":211},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":139,"failed":138},{"name":"simd_const.wast","passed":755,"failed":2},{"name":"simd_conversions.wast","passed":50,"failed":232},{"name":"simd_f32x4.wast","passed":18,"failed":772},{"name":"simd_f32x4_arith.wast","passed":19,"failed":1803},{"name":"simd_f32x4_cmp.wast","passed":26,"failed":2581},{"name":"simd_f32x4_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f32x4_rounding.wast","passed":25,"failed":176},{"name":"simd_f64x2.wast","passed":10,"failed":793},{"name":"simd_f64x2_arith.wast","passed":19,"failed":1806},{"name":"simd_f64x2_cmp.wast","passed":26,"failed":2659},{"name":"simd_f64x2_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f64x2_rounding.wast","passed":25,"failed":176},{"name":"simd_i16x8_arith.wast","passed":13,"failed":181},{"name":"simd_i16x8_arith2.wast","passed":21,"failed":151},{"name":"simd_i16x8_cmp.wast","passed":32,"failed":433},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":5,"failed":16},{"name":"simd_i16x8_extmul_i8x16.wast","passed":13,"failed":104},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":4,"failed":26},{"name":"simd_i16x8_sat_arith.wast","passed":18,"failed":204},{"name":"simd_i32x4_arith.wast","passed":13,"failed":181},{"name":"simd_i32x4_arith2.wast","passed":28,"failed":121},{"name":"simd_i32x4_cmp.wast","passed":42,"failed":433},{"name":"simd_i32x4_dot_i16x8.wast","passed":4,"failed":28},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":5,"failed":16},{"name":"simd_i32x4_extmul_i16x8.wast","passed":13,"failed":104},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":5,"failed":102},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":5,"failed":102},{"name":"simd_i64x2_arith.wast","passed":13,"failed":187},{"name":"simd_i64x2_arith2.wast","passed":4,"failed":21},{"name":"simd_i64x2_cmp.wast","passed":11,"failed":102},{"name":"simd_i64x2_extmul_i32x4.wast","passed":13,"failed":104},{"name":"simd_i8x16_arith.wast","passed":10,"failed":121},{"name":"simd_i8x16_arith2.wast","passed":27,"failed":184},{"name":"simd_i8x16_cmp.wast","passed":32,"failed":413},{"name":"simd_i8x16_sat_arith.wast","passed":26,"failed":188},{"name":"simd_int_to_int_extend.wast","passed":25,"failed":228},{"name":"simd_lane.wast","passed":213,"failed":262},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":29,"failed":10},{"name":"simd_load16_lane.wast","passed":4,"failed":32},{"name":"simd_load32_lane.wast","passed":4,"failed":20},{"name":"simd_load64_lane.wast","passed":4,"failed":12},{"name":"simd_load8_lane.wast","passed":4,"failed":48},{"name":"simd_load_extend.wast","passed":30,"failed":74},{"name":"simd_load_splat.wast","passed":122,"failed":4},{"name":"simd_load_zero.wast","passed":37,"failed":2},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":27,"failed":158},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":4,"failed":32},{"name":"simd_store32_lane.wast","passed":4,"failed":20},{"name":"simd_store64_lane.wast","passed":4,"failed":12},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] +0.9.0-alpha.0,2867,23122,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":41,"failed":211},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":139,"failed":138},{"name":"simd_const.wast","passed":755,"failed":2},{"name":"simd_conversions.wast","passed":50,"failed":232},{"name":"simd_f32x4.wast","passed":18,"failed":772},{"name":"simd_f32x4_arith.wast","passed":19,"failed":1803},{"name":"simd_f32x4_cmp.wast","passed":26,"failed":2581},{"name":"simd_f32x4_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f32x4_rounding.wast","passed":25,"failed":176},{"name":"simd_f64x2.wast","passed":10,"failed":793},{"name":"simd_f64x2_arith.wast","passed":19,"failed":1806},{"name":"simd_f64x2_cmp.wast","passed":26,"failed":2659},{"name":"simd_f64x2_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f64x2_rounding.wast","passed":25,"failed":176},{"name":"simd_i16x8_arith.wast","passed":13,"failed":181},{"name":"simd_i16x8_arith2.wast","passed":21,"failed":151},{"name":"simd_i16x8_cmp.wast","passed":32,"failed":433},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":5,"failed":16},{"name":"simd_i16x8_extmul_i8x16.wast","passed":13,"failed":104},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":18,"failed":204},{"name":"simd_i32x4_arith.wast","passed":13,"failed":181},{"name":"simd_i32x4_arith2.wast","passed":28,"failed":121},{"name":"simd_i32x4_cmp.wast","passed":42,"failed":433},{"name":"simd_i32x4_dot_i16x8.wast","passed":14,"failed":18},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":5,"failed":16},{"name":"simd_i32x4_extmul_i16x8.wast","passed":13,"failed":104},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":5,"failed":102},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":5,"failed":102},{"name":"simd_i64x2_arith.wast","passed":13,"failed":187},{"name":"simd_i64x2_arith2.wast","passed":4,"failed":21},{"name":"simd_i64x2_cmp.wast","passed":11,"failed":102},{"name":"simd_i64x2_extmul_i32x4.wast","passed":13,"failed":104},{"name":"simd_i8x16_arith.wast","passed":10,"failed":121},{"name":"simd_i8x16_arith2.wast","passed":27,"failed":184},{"name":"simd_i8x16_cmp.wast","passed":32,"failed":413},{"name":"simd_i8x16_sat_arith.wast","passed":26,"failed":188},{"name":"simd_int_to_int_extend.wast","passed":25,"failed":228},{"name":"simd_lane.wast","passed":328,"failed":147},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":30,"failed":9},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":36,"failed":68},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":158,"failed":27},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":4,"failed":32},{"name":"simd_store32_lane.wast","passed":4,"failed":20},{"name":"simd_store64_lane.wast","passed":4,"failed":12},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] From 7e3465b25af40aa6f2eb09b1f2cddd863ca40baa Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 25 Jan 2026 22:38:16 +0100 Subject: [PATCH 04/47] chore: update deps Signed-off-by: Henry --- Cargo.lock | 76 ++++++++++++++++++------------------- Cargo.toml | 10 ++--- crates/parser/src/lib.rs | 2 + crates/parser/src/module.rs | 2 +- 4 files changed, 46 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd7be3a2..6ae16fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.51" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "shlex", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cobs" @@ -279,9 +279,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "half" @@ -339,9 +339,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", @@ -381,15 +381,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "log" @@ -464,18 +464,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -582,9 +582,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.148" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -601,9 +601,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.113" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -621,18 +621,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.243.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", "wasmparser", @@ -753,9 +753,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.243.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "indexmap", @@ -764,9 +764,9 @@ dependencies = [ [[package]] name = "wast" -version = "243.0.0" +version = "244.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df21d01c2d91e46cb7a221d79e58a2d210ea02020d57c092e79255cc2999ca7f" +checksum = "b2e7b9f9e23311275920e3d6b56d64137c160cf8af4f84a7283b36cfecbf4acb" dependencies = [ "bumpalo", "leb128fmt", @@ -777,9 +777,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.243.0" +version = "1.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226a9a91cd80a50449312fef0c75c23478fcecfcc4092bdebe1dc8e760ef521b" +checksum = "bbf35b87ed352f9ab6cd0732abde5a67dd6153dfd02c493e61459218b19456fa" dependencies = [ "wast", ] @@ -832,18 +832,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -852,6 +852,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/Cargo.toml b/Cargo.toml index e6d1cb41..d6fe4c70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,22 +4,22 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="243" -wat="1.243" -wasmparser={version="0.243", default-features=false} +wast="244" +wat="1.244" +wasmparser={version="0.244", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" criterion={version="0.8", default-features=false, features=["cargo_bench_support", "rayon"]} wasm-testsuite={version="0.6"} -indexmap="2.12" +indexmap="2.13" owo-colors={version="4.2"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} [workspace.package] version="0.9.0-alpha.0" -rust-version="1.91" +rust-version="1.93" edition="2024" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 3f543b62..6fb8e907 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -67,6 +67,8 @@ impl Parser { bulk_memory_opt: true, call_indirect_overlong: true, + compact_imports: false, + cm_map: false, custom_descriptors: false, cm_threading: false, extended_const: false, diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 1651bf06..4754d317 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -147,7 +147,7 @@ impl ModuleReader { debug!("Found import section"); validator.import_section(&reader)?; - self.imports = conversion::convert_module_imports(reader)?; + self.imports = conversion::convert_module_imports(reader.into_imports())?; } ExportSection(reader) => { if !self.exports.is_empty() { From 384cb7ecb8e39fc6d4ec371bd753a889dfb83b33 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 12 Feb 2026 00:07:43 +0100 Subject: [PATCH 05/47] chore: upgrade deps Signed-off-by: Henry --- Cargo.lock | 87 ++++++++++++++++++---------------------- Cargo.toml | 6 +-- crates/parser/src/lib.rs | 2 +- 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ae16fc6..3bbbe567 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,20 +34,19 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "argh" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" +checksum = "7f384d96bfd3c0b3c41f24dae69ee9602c091d64fc432225cf5295b5abbe0036" dependencies = [ "argh_derive", "argh_shared", - "rust-fuzzy-search", ] [[package]] name = "argh_derive" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" +checksum = "938e5f66269c1f168035e29ed3fb437b084e476465e9314a0328f4005d7be599" dependencies = [ "argh_shared", "proc-macro2", @@ -57,9 +56,9 @@ dependencies = [ [[package]] name = "argh_shared" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" +checksum = "5127f8a5bc1cfb0faf1f6248491452b8a5b6901068d8da2d47cbb285986ae683" dependencies = [ "serde", ] @@ -90,9 +89,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -133,18 +132,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstyle", "clap_lex", @@ -167,9 +166,9 @@ dependencies = [ [[package]] name = "criterion" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ "alloca", "anes", @@ -191,9 +190,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools", @@ -279,9 +278,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "half" @@ -399,9 +398,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "num-traits" @@ -502,9 +501,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -514,9 +513,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -525,15 +524,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rust-fuzzy-search" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "same-file" @@ -733,9 +726,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.244.0" +version = "0.245.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "95d568e113f706ee7a7df9b33547bb80721f55abffc79b3dc4d09c368690e662" dependencies = [ "leb128fmt", "wasmparser", @@ -753,9 +746,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.244.0" +version = "0.245.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "48a767a48974f0c8b66f211b96e01aa77feed58b8ccce4e7f0cff0ae55b174d4" dependencies = [ "bitflags", "indexmap", @@ -764,9 +757,9 @@ dependencies = [ [[package]] name = "wast" -version = "244.0.0" +version = "245.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e7b9f9e23311275920e3d6b56d64137c160cf8af4f84a7283b36cfecbf4acb" +checksum = "75ffc7471e16a6f3c7a3c3a230314915b5dcd158e5ef13ccda2f43358a9df00c" dependencies = [ "bumpalo", "leb128fmt", @@ -777,9 +770,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.244.0" +version = "1.245.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf35b87ed352f9ab6cd0732abde5a67dd6153dfd02c493e61459218b19456fa" +checksum = "d6bcac6f915e2a84a4c0d9df9d41ad7518d99cda13f3bb83e3b8c22bf8726ab6" dependencies = [ "wast", ] @@ -832,18 +825,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -852,6 +845,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index d6fe4c70..1563d6c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,9 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="244" -wat="1.244" -wasmparser={version="0.244", default-features=false} +wast="245" +wat="1.245" +wasmparser={version="0.245", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 6fb8e907..b65c47b4 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -89,7 +89,7 @@ impl Parser { cm_nested_names: false, cm_values: false, cm_error_context: false, - cm_fixed_size_list: false, + cm_fixed_length_lists: false, cm_gc: false, }; Validator::new_with_features(features.into()) From 7db07e03b56577997706adda1358bb43155b2aeb Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 19 Mar 2026 21:45:31 +0100 Subject: [PATCH 06/47] feat: add all simd instructions, add stackop macro Signed-off-by: Henry --- Cargo.lock | 100 +- Cargo.toml | 2 +- crates/tinywasm/src/interpreter/executor.rs | 999 ++++--- .../tinywasm/src/interpreter/num_helpers.rs | 2 +- .../src/interpreter/stack/value_stack.rs | 14 +- crates/tinywasm/src/interpreter/value128.rs | 2412 ++++++++++++++++- crates/tinywasm/src/store/memory.rs | 2 +- crates/tinywasm/tests/generated/wasm-simd.csv | 2 +- 8 files changed, 2844 insertions(+), 689 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bbbe567..2271ac28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,15 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "argh" -version = "0.1.14" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f384d96bfd3c0b3c41f24dae69ee9602c091d64fc432225cf5295b5abbe0036" +checksum = "211818e820cda9ca6f167a64a5c808837366a6dfd807157c64c1304c486cd033" dependencies = [ "argh_derive", "argh_shared", @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "argh_derive" -version = "0.1.14" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938e5f66269c1f168035e29ed3fb437b084e476465e9314a0328f4005d7be599" +checksum = "c442a9d18cef5dde467405d27d461d080d68972d6d0dfd0408265b6749ec427d" dependencies = [ "argh_shared", "proc-macro2", @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "argh_shared" -version = "0.1.14" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5127f8a5bc1cfb0faf1f6248491452b8a5b6901068d8da2d47cbb285986ae683" +checksum = "e5ade012bac4db278517a0132c8c10c6427025868dca16c801087c28d5a411f1" dependencies = [ "serde", ] @@ -71,15 +71,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cast" @@ -89,9 +89,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -132,18 +132,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cobs" @@ -380,9 +380,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "oorandom" @@ -425,9 +425,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "page_size" @@ -472,9 +472,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -524,9 +524,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "same-file" @@ -594,9 +594,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.245.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95d568e113f706ee7a7df9b33547bb80721f55abffc79b3dc4d09c368690e662" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" dependencies = [ "leb128fmt", "wasmparser", @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "wasm-testsuite" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad9eeaabcdbbe221f3fd9a90eabab05362d8f444dc09bc0930a341ea9779e9e" +checksum = "921e4ecec67cf5017034abf411b7458c9f8ded6221d2af804994e8b282f3ff92" dependencies = [ "include_dir", "wast", @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.245.0" +version = "0.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48a767a48974f0c8b66f211b96e01aa77feed58b8ccce4e7f0cff0ae55b174d4" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" dependencies = [ "bitflags", "indexmap", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "wast" -version = "245.0.0" +version = "245.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ffc7471e16a6f3c7a3c3a230314915b5dcd158e5ef13ccda2f43358a9df00c" +checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" dependencies = [ "bumpalo", "leb128fmt", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.245.0" +version = "1.245.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bcac6f915e2a84a4c0d9df9d41ad7518d99cda13f3bb83e3b8c22bf8726ab6" +checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" dependencies = [ "wast", ] @@ -825,18 +825,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", @@ -845,6 +845,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 1563d6c5..f47f1f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ pretty_env_logger="0.5" criterion={version="0.8", default-features=false, features=["cargo_bench_support", "rayon"]} wasm-testsuite={version="0.6"} indexmap="2.13" -owo-colors={version="4.2"} +owo-colors={version="4.3"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index c186c014..08089f4d 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -44,6 +44,36 @@ impl<'store, 'stack> Executor<'store, 'stack> { fn exec_next(&mut self) -> ControlFlow> { use tinywasm_types::Instruction::*; + macro_rules! stack_op { + (simd_unary $method:ident) => { + self.stack.values.unary_same::(|v| Ok(v.$method())).to_cf()? + }; + (simd_binary $method:ident) => { + self.stack.values.binary_same::(|a, b| Ok(a.$method(b))).to_cf()? + }; + (unary $ty:ty, |$v:ident| $expr:expr) => { + self.stack.values.unary_same::<$ty>(|$v| Ok($expr)).to_cf()? + }; + (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { + self.stack.values.binary_same::<$ty>(|$a, $b| Ok($expr)).to_cf()? + }; + (binary_try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { + self.stack.values.binary_same::<$ty>(|$a, $b| $expr).to_cf()? + }; + (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { + self.stack.values.unary::<$from, $to>(|$v| Ok($expr)).to_cf()? + }; + (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { + self.stack.values.binary::<$from, $to>(|$a, $b| Ok($expr)).to_cf()? + }; + (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { + self.stack.values.binary_diff::<$a, $b, $b>(|$lhs, $rhs| Ok($expr)).to_cf()? + }; + (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { + self.stack.values.binary_diff::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? + }; + } + #[rustfmt::skip] match self.cf.fetch_instr() { Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} @@ -106,6 +136,8 @@ impl<'store, 'stack> Executor<'store, 'stack> { I64Const(val) => self.exec_const(*val), F32Const(val) => self.exec_const(*val), F64Const(val) => self.exec_const(*val), + + // Reference types RefFunc(func_idx) => self.exec_const::(Some(*func_idx)), RefNull(_) => self.exec_const::(None), RefIsNull => self.exec_ref_is_null(), @@ -119,8 +151,17 @@ impl<'store, 'stack> Executor<'store, 'stack> { MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx).to_cf()?, DataDrop(data_index) => self.exec_data_drop(*data_index), ElemDrop(elem_index) => self.exec_elem_drop(*elem_index), + + // Table instructions + TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, + TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, + TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, + TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, + TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, + TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, + // Core memory load/store operations I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, @@ -146,138 +187,140 @@ impl<'store, 'stack> Executor<'store, 'stack> { I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Eqz => self.stack.values.replace_top::(|v| Ok(i32::from(v == 0))).to_cf()?, - I32Eqz => self.stack.values.replace_top_same::(|v| Ok(i32::from(v == 0))).to_cf()?, - I32Eq => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a == b))).to_cf()?, - I64Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, - F32Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, - F64Eq => self.stack.values.calculate::(|a, b| Ok(i32::from(a == b))).to_cf()?, - - I32Ne => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a != b))).to_cf()?, - I64Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, - F32Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, - F64Ne => self.stack.values.calculate::(|a, b| Ok(i32::from(a != b))).to_cf()?, - - I32LtS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a < b))).to_cf()?, - I64LtS => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - I32LtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - I64LtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - F32Lt => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - F64Lt => self.stack.values.calculate::(|a, b| Ok(i32::from(a < b))).to_cf()?, - - I32LeS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - I64LeS => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - I32LeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - I64LeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - F32Le => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - F64Le => self.stack.values.calculate::(|a, b| Ok(i32::from(a <= b))).to_cf()?, - - I32GeS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - I64GeS => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - I32GeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - I64GeU => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - F32Ge => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - F64Ge => self.stack.values.calculate::(|a, b| Ok(i32::from(a >= b))).to_cf()?, - - I32GtS => self.stack.values.calculate_same::(|a, b| Ok(i32::from(a > b))).to_cf()?, - I64GtS => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - I32GtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - I64GtU => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - F32Gt => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - F64Gt => self.stack.values.calculate::(|a, b| Ok(i32::from(a > b))).to_cf()?, - - I32Add => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_add(b))).to_cf()?, - I64Add => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_add(b))).to_cf()?, - F32Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - F64Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - - I32Sub => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_sub(b))).to_cf()?, - I64Sub => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_sub(b))).to_cf()?, - F32Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - F64Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - - F32Div => self.stack.values.calculate_same::(|a, b| Ok(a / b)).to_cf()?, - F64Div => self.stack.values.calculate_same::(|a, b| Ok(a / b)).to_cf()?, - - I32Mul => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_mul(b))).to_cf()?, - I64Mul => self.stack.values.calculate_same::(|a, b| Ok(a.wrapping_mul(b))).to_cf()?, - F32Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - F64Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - - I32DivS => self.stack.values.calculate_same::(|a, b| a.wasm_checked_div(b)).to_cf()?, - I64DivS => self.stack.values.calculate_same::(|a, b| a.wasm_checked_div(b)).to_cf()?, - I32DivU => self.stack.values.calculate_same::(|a, b| a.checked_div(b).ok_or_else(trap_0)).to_cf()?, - I64DivU => self.stack.values.calculate_same::(|a, b| a.checked_div(b).ok_or_else(trap_0)).to_cf()?, - I32RemS => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - I64RemS => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - I32RemU => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - I64RemU => self.stack.values.calculate_same::(|a, b| a.checked_wrapping_rem(b)).to_cf()?, - - I32And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - I64And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - I32Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - I64Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - I32Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - I64Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - I32Shl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shl(b))).to_cf()?, - I64Shl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shl(b))).to_cf()?, - I32ShrS => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I64ShrS => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I32ShrU => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I64ShrU => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_shr(b))).to_cf()?, - I32Rotl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotl(b))).to_cf()?, - I64Rotl => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotl(b))).to_cf()?, - I32Rotr => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotr(b))).to_cf()?, - I64Rotr => self.stack.values.calculate_same::(|a, b| Ok(a.wasm_rotr(b))).to_cf()?, - - I32Clz => self.stack.values.replace_top_same::(|v| Ok(v.leading_zeros() as i32)).to_cf()?, - I64Clz => self.stack.values.replace_top_same::(|v| Ok(i64::from(v.leading_zeros()))).to_cf()?, - I32Ctz => self.stack.values.replace_top_same::(|v| Ok(v.trailing_zeros() as i32)).to_cf()?, - I64Ctz => self.stack.values.replace_top_same::(|v| Ok(i64::from(v.trailing_zeros()))).to_cf()?, - I32Popcnt => self.stack.values.replace_top_same::(|v| Ok(v.count_ones() as i32)).to_cf()?, - I64Popcnt => self.stack.values.replace_top_same::(|v| Ok(i64::from(v.count_ones()))).to_cf()?, - - F32ConvertI32S => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F32ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F64ConvertI32S => self.stack.values.replace_top::(|v| Ok(f64::from(v))).to_cf()?, - F64ConvertI64S => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, - F32ConvertI32U => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F32ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F64ConvertI32U => self.stack.values.replace_top::(|v| Ok(f64::from(v))).to_cf()?, - F64ConvertI64U => self.stack.values.replace_top::(|v| Ok(v as f64)).to_cf()?, - - I32Extend8S => self.stack.values.replace_top_same::(|v| Ok(i32::from(v as i8))).to_cf()?, - I32Extend16S => self.stack.values.replace_top_same::(|v| Ok(i32::from(v as i16))).to_cf()?, - I64Extend8S => self.stack.values.replace_top_same::(|v| Ok(i64::from(v as i8))).to_cf()?, - I64Extend16S => self.stack.values.replace_top_same::(|v| Ok(i64::from(v as i16))).to_cf()?, - I64Extend32S => self.stack.values.replace_top_same::(|v| Ok(i64::from(v as i32))).to_cf()?, - I64ExtendI32U => self.stack.values.replace_top::(|v| Ok(i64::from(v))).to_cf()?, - I64ExtendI32S => self.stack.values.replace_top::(|v| Ok(i64::from(v))).to_cf()?, - I32WrapI64 => self.stack.values.replace_top::(|v| Ok(v as i32)).to_cf()?, - - F32DemoteF64 => self.stack.values.replace_top::(|v| Ok(v as f32)).to_cf()?, - F64PromoteF32 => self.stack.values.replace_top::(|v| Ok(f64::from(v))).to_cf()?, - - F32Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - F64Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - F32Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - F64Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - F32Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - F64Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - F32Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - F64Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - F32Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - F64Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - F32Nearest => self.stack.values.replace_top_same::(|v| Ok(v.tw_nearest())).to_cf()?, - F64Nearest => self.stack.values.replace_top_same::(|v| Ok(v.tw_nearest())).to_cf()?, - F32Sqrt => self.stack.values.replace_top_same::(|v| Ok(v.sqrt())).to_cf()?, - F64Sqrt => self.stack.values.replace_top_same::(|v| Ok(v.sqrt())).to_cf()?, - F32Min => self.stack.values.calculate_same::(|a, b| Ok(a.tw_minimum(b))).to_cf()?, - F64Min => self.stack.values.calculate_same::(|a, b| Ok(a.tw_minimum(b))).to_cf()?, - F32Max => self.stack.values.calculate_same::(|a, b| Ok(a.tw_maximum(b))).to_cf()?, - F64Max => self.stack.values.calculate_same::(|a, b| Ok(a.tw_maximum(b))).to_cf()?, - F32Copysign => self.stack.values.calculate_same::(|a, b| Ok(a.copysign(b))).to_cf()?, - F64Copysign => self.stack.values.calculate_same::(|a, b| Ok(a.copysign(b))).to_cf()?, + I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), + I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), + I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), + I64Eq => stack_op!(binary i64 => i32, |a, b| i32::from(a == b)), + F32Eq => stack_op!(binary f32 => i32, |a, b| i32::from(a == b)), + F64Eq => stack_op!(binary f64 => i32, |a, b| i32::from(a == b)), + + I32Ne => stack_op!(binary i32, |a, b| i32::from(a != b)), + I64Ne => stack_op!(binary i64 => i32, |a, b| i32::from(a != b)), + F32Ne => stack_op!(binary f32 => i32, |a, b| i32::from(a != b)), + F64Ne => stack_op!(binary f64 => i32, |a, b| i32::from(a != b)), + + I32LtS => stack_op!(binary i32, |a, b| i32::from(a < b)), + I64LtS => stack_op!(binary i64 => i32, |a, b| i32::from(a < b)), + I32LtU => stack_op!(binary u32 => i32, |a, b| i32::from(a < b)), + I64LtU => stack_op!(binary u64 => i32, |a, b| i32::from(a < b)), + F32Lt => stack_op!(binary f32 => i32, |a, b| i32::from(a < b)), + F64Lt => stack_op!(binary f64 => i32, |a, b| i32::from(a < b)), + + I32LeS => stack_op!(binary i32, |a, b| i32::from(a <= b)), + I64LeS => stack_op!(binary i64 => i32, |a, b| i32::from(a <= b)), + I32LeU => stack_op!(binary u32 => i32, |a, b| i32::from(a <= b)), + I64LeU => stack_op!(binary u64 => i32, |a, b| i32::from(a <= b)), + F32Le => stack_op!(binary f32 => i32, |a, b| i32::from(a <= b)), + F64Le => stack_op!(binary f64 => i32, |a, b| i32::from(a <= b)), + + I32GeS => stack_op!(binary i32, |a, b| i32::from(a >= b)), + I64GeS => stack_op!(binary i64 => i32, |a, b| i32::from(a >= b)), + I32GeU => stack_op!(binary u32 => i32, |a, b| i32::from(a >= b)), + I64GeU => stack_op!(binary u64 => i32, |a, b| i32::from(a >= b)), + F32Ge => stack_op!(binary f32 => i32, |a, b| i32::from(a >= b)), + F64Ge => stack_op!(binary f64 => i32, |a, b| i32::from(a >= b)), + + I32GtS => stack_op!(binary i32, |a, b| i32::from(a > b)), + I64GtS => stack_op!(binary i64 => i32, |a, b| i32::from(a > b)), + I32GtU => stack_op!(binary u32 => i32, |a, b| i32::from(a > b)), + I64GtU => stack_op!(binary u64 => i32, |a, b| i32::from(a > b)), + F32Gt => stack_op!(binary f32 => i32, |a, b| i32::from(a > b)), + F64Gt => stack_op!(binary f64 => i32, |a, b| i32::from(a > b)), + + I32Add => stack_op!(binary i32, |a, b| a.wrapping_add(b)), + I64Add => stack_op!(binary i64, |a, b| a.wrapping_add(b)), + F32Add => stack_op!(binary f32, |a, b| a + b), + F64Add => stack_op!(binary f64, |a, b| a + b), + + I32Sub => stack_op!(binary i32, |a, b| a.wrapping_sub(b)), + I64Sub => stack_op!(binary i64, |a, b| a.wrapping_sub(b)), + F32Sub => stack_op!(binary f32, |a, b| a - b), + F64Sub => stack_op!(binary f64, |a, b| a - b), + + F32Div => stack_op!(binary f32, |a, b| a / b), + F64Div => stack_op!(binary f64, |a, b| a / b), + + I32Mul => stack_op!(binary i32, |a, b| a.wrapping_mul(b)), + I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), + F32Mul => stack_op!(binary f32, |a, b| a * b), + F64Mul => stack_op!(binary f64, |a, b| a * b), + + I32DivS => stack_op!(binary_try i32, |a, b| a.wasm_checked_div(b)), + I64DivS => stack_op!(binary_try i64, |a, b| a.wasm_checked_div(b)), + I32DivU => stack_op!(binary_try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I64DivU => stack_op!(binary_try u64, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I32RemS => stack_op!(binary_try i32, |a, b| a.checked_wrapping_rem(b)), + I64RemS => stack_op!(binary_try i64, |a, b| a.checked_wrapping_rem(b)), + I32RemU => stack_op!(binary_try u32, |a, b| a.checked_wrapping_rem(b)), + I64RemU => stack_op!(binary_try u64, |a, b| a.checked_wrapping_rem(b)), + + I32And => stack_op!(binary i32, |a, b| a & b), + I64And => stack_op!(binary i64, |a, b| a & b), + I32Or => stack_op!(binary i32, |a, b| a | b), + I64Or => stack_op!(binary i64, |a, b| a | b), + I32Xor => stack_op!(binary i32, |a, b| a ^ b), + I64Xor => stack_op!(binary i64, |a, b| a ^ b), + I32Shl => stack_op!(binary i32, |a, b| a.wasm_shl(b)), + I64Shl => stack_op!(binary i64, |a, b| a.wasm_shl(b)), + I32ShrS => stack_op!(binary i32, |a, b| a.wasm_shr(b)), + I64ShrS => stack_op!(binary i64, |a, b| a.wasm_shr(b)), + I32ShrU => stack_op!(binary u32, |a, b| a.wasm_shr(b)), + I64ShrU => stack_op!(binary u64, |a, b| a.wasm_shr(b)), + I32Rotl => stack_op!(binary i32, |a, b| a.wasm_rotl(b)), + I64Rotl => stack_op!(binary i64, |a, b| a.wasm_rotl(b)), + I32Rotr => stack_op!(binary i32, |a, b| a.wasm_rotr(b)), + I64Rotr => stack_op!(binary i64, |a, b| a.wasm_rotr(b)), + + I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), + I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), + I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), + I64Ctz => stack_op!(unary i64, |v| i64::from(v.trailing_zeros())), + I32Popcnt => stack_op!(unary i32, |v| v.count_ones() as i32), + I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), + + // Numeric conversion operations + F32ConvertI32S => stack_op!(unary i32 => f32, |v| v as f32), + F32ConvertI64S => stack_op!(unary i64 => f32, |v| v as f32), + F64ConvertI32S => stack_op!(unary i32 => f64, |v| f64::from(v)), + F64ConvertI64S => stack_op!(unary i64 => f64, |v| v as f64), + F32ConvertI32U => stack_op!(unary u32 => f32, |v| v as f32), + F32ConvertI64U => stack_op!(unary u64 => f32, |v| v as f32), + F64ConvertI32U => stack_op!(unary u32 => f64, |v| f64::from(v)), + F64ConvertI64U => stack_op!(unary u64 => f64, |v| v as f64), + + // Sign-extension operations + I32Extend8S => stack_op!(unary i32, |v| i32::from(v as i8)), + I32Extend16S => stack_op!(unary i32, |v| i32::from(v as i16)), + I64Extend8S => stack_op!(unary i64, |v| i64::from(v as i8)), + I64Extend16S => stack_op!(unary i64, |v| i64::from(v as i16)), + I64Extend32S => stack_op!(unary i64, |v| i64::from(v as i32)), + I64ExtendI32U => stack_op!(unary u32 => i64, |v| i64::from(v)), + I64ExtendI32S => stack_op!(unary i32 => i64, |v| i64::from(v)), + I32WrapI64 => stack_op!(unary i64 => i32, |v| v as i32), + + F32DemoteF64 => stack_op!(unary f64 => f32, |v| v as f32), + F64PromoteF32 => stack_op!(unary f32 => f64, |v| f64::from(v)), + + F32Abs => stack_op!(unary f32, |v| v.abs()), + F64Abs => stack_op!(unary f64, |v| v.abs()), + F32Neg => stack_op!(unary f32, |v| -v), + F64Neg => stack_op!(unary f64, |v| -v), + F32Ceil => stack_op!(unary f32, |v| v.ceil()), + F64Ceil => stack_op!(unary f64, |v| v.ceil()), + F32Floor => stack_op!(unary f32, |v| v.floor()), + F64Floor => stack_op!(unary f64, |v| v.floor()), + F32Trunc => stack_op!(unary f32, |v| v.trunc()), + F64Trunc => stack_op!(unary f64, |v| v.trunc()), + F32Nearest => stack_op!(unary f32, |v| v.tw_nearest()), + F64Nearest => stack_op!(unary f64, |v| v.tw_nearest()), + F32Sqrt => stack_op!(unary f32, |v| v.sqrt()), + F64Sqrt => stack_op!(unary f64, |v| v.sqrt()), + F32Min => stack_op!(binary f32, |a, b| a.tw_minimum(b)), + F64Min => stack_op!(binary f64, |a, b| a.tw_minimum(b)), + F32Max => stack_op!(binary f32, |a, b| a.tw_maximum(b)), + F64Max => stack_op!(binary f64, |a, b| a.tw_maximum(b)), + F32Copysign => stack_op!(binary f32, |a, b| a.copysign(b)), + F64Copysign => stack_op!(binary f64, |a, b| a.copysign(b)), I32TruncF32S => checked_conv_float!(f32, i32, self), I32TruncF64S => checked_conv_float!(f64, i32, self), @@ -288,408 +331,306 @@ impl<'store, 'stack> Executor<'store, 'stack> { I64TruncF32U => checked_conv_float!(f32, u64, i64, self), I64TruncF64U => checked_conv_float!(f64, u64, i64, self), - TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, - TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, - TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, - TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, - TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, - TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, - - I32TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32)).to_cf()?, - I32TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32)).to_cf()?, - I32TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i32)).to_cf()?, - I32TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u32)).to_cf()?, - I64TruncSatF32S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64)).to_cf()?, - I64TruncSatF32U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64)).to_cf()?, - I64TruncSatF64S => self.stack.values.replace_top::(|v| Ok(v.trunc() as i64)).to_cf()?, - I64TruncSatF64U => self.stack.values.replace_top::(|v| Ok(v.trunc() as u64)).to_cf()?, + // Non-trapping float-to-int conversions + I32TruncSatF32S => stack_op!(unary f32 => i32, |v| v.trunc() as i32), + I32TruncSatF32U => stack_op!(unary f32 => u32, |v| v.trunc() as u32), + I32TruncSatF64S => stack_op!(unary f64 => i32, |v| v.trunc() as i32), + I32TruncSatF64U => stack_op!(unary f64 => u32, |v| v.trunc() as u32), + I64TruncSatF32S => stack_op!(unary f32 => i64, |v| v.trunc() as i64), + I64TruncSatF32U => stack_op!(unary f32 => u64, |v| v.trunc() as u64), + I64TruncSatF64S => stack_op!(unary f64 => i64, |v| v.trunc() as i64), + I64TruncSatF64U => stack_op!(unary f64 => u64, |v| v.trunc() as u64), LocalCopy32(from, to) => self.exec_local_copy::(*from, *to), LocalCopy64(from, to) => self.exec_local_copy::(*from, *to), LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), - V128Not => self.stack.values.replace_top_same::(|v| Ok(!v)).to_cf()?, - V128And => self.stack.values.calculate_same::(|a, b| Ok(a & b)).to_cf()?, - V128AndNot => self.stack.values.calculate_same::(|a, b| Ok(a & (!b))).to_cf()?, - V128Or => self.stack.values.calculate_same::(|a, b| Ok(a | b)).to_cf()?, - V128Xor => self.stack.values.calculate_same::(|a, b| Ok(a ^ b)).to_cf()?, - V128Bitselect => self.stack.values.calculate_same_3::(|v1, v2, c| Ok((v1 & c) | (v2 & !c))).to_cf()?, - V128AnyTrue => self.stack.values.replace_top::(|v| Ok((v.reduce_or() != 0) as i32)).to_cf()?, - I8x16Swizzle => self.stack.values.calculate_same::(|a, s| Ok(a.swizzle(s))).to_cf()?, - - V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, - V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::extend_8_i8)?, - V128Load8x8U(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_8_u8)?, - V128Load16x4S(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_4_i16)?, - V128Load16x4U(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_4_u16)?, - V128Load32x2S(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_2_i32)?, - V128Load32x2U(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::extend_2_u32)?, - V128Load8Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i8)?, - V128Load16Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i16)?, - V128Load32Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i32)?, - V128Load64Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i64)?, - - V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, - - V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + // SIMD extension + V128Not => stack_op!(unary Value128, |v| v.v128_not()), + V128And => stack_op!(binary Value128, |a, b| a.v128_and(b)), + V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), + V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), + V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), + V128Bitselect => self.stack.values.ternary_same::(|v1, v2, c| Ok(Value128::v128_bitselect(v1, v2, c))).to_cf()?, + V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), + I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), + + V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, + V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, + V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, + V128Load16x4S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_s(v.to_le_bytes()))?, + V128Load16x4U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_u(v.to_le_bytes()))?, + V128Load32x2S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_s(v.to_le_bytes()))?, + V128Load32x2U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_u(v.to_le_bytes()))?, + V128Load8Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i8)?, + V128Load16Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i16)?, + V128Load32Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i32)?, + V128Load64Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i64)?, + + V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, + + V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. - V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, - V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, - - V128Const(arg) => self.exec_const::( self.cf.data().v128_constants[*arg as usize].into()), - - I8x16ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i8(*lane) as i32)).to_cf()?, - I8x16ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_u8(*lane) as i32)).to_cf()?, - I16x8ExtractLaneS(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i16(*lane) as i32)).to_cf()?, - I16x8ExtractLaneU(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_u16(*lane) as i32)).to_cf()?, - I32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i32(*lane))).to_cf()?, - I64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_i64(*lane))).to_cf()?, - F32x4ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_f32(*lane))).to_cf()?, - F64x2ExtractLane(lane) => self.stack.values.replace_top::(|v| Ok(v.extract_lane_f64(*lane))).to_cf()?, - - V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - // I8x16ReplaceLane(_lane) => unimplemented!(), - // I16x8ReplaceLane(_lane) => unimplemented!(), - // I32x4ReplaceLane(_lane) => unimplemented!(), - // I64x2ReplaceLane(_lane) => unimplemented!(), - // F32x4ReplaceLane(_lane) => unimplemented!(), - // F64x2ReplaceLane(_lane) => unimplemented!(), - - I8x16Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i8(v as i8))).to_cf()?, - I16x8Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i16(v as i16))).to_cf()?, - I32x4Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i32(v))).to_cf()?, - I64x2Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_i64(v))).to_cf()?, - F32x4Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_f32(v))).to_cf()?, - F64x2Splat => self.stack.values.replace_top::(|v| Ok(Value128::splat_f64(v))).to_cf()?, - - // I8x16Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - // I16x8Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - // I32x4Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - // I64x2Eq => self.stack.values.calculate_same::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - // F32x4Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - // F64x2Eq => self.stack.values.calculate::(|a, b| Ok(a.simd_eq(b).to_int())).to_cf()?, - - // I8x16Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - // I16x8Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - // I32x4Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - // I64x2Ne => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - // F32x4Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - // F64x2Ne => self.stack.values.calculate::(|a, b| Ok(a.simd_ne(b).to_int())).to_cf()?, - - // I8x16LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // I16x8LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // I32x4LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // I64x2LtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // I8x16LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // I16x8LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // I32x4LtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // F32x4Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - // F64x2Lt => self.stack.values.calculate::(|a, b| Ok(a.simd_lt(b).to_int())).to_cf()?, - - // F32x4Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // F64x2Gt => self.stack.values.calculate::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - - // I8x16GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // I16x8GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // I32x4GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // I64x2GtS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // I64x2LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - // F32x4Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - // F64x2Le => self.stack.values.calculate::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - // I8x16GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // I16x8GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // I32x4GtU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_gt(b).to_int())).to_cf()?, - // F32x4Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - // F64x2Ge => self.stack.values.calculate::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - // I8x16LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - // I16x8LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - // I32x4LeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - // I8x16LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - // I16x8LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - // I32x4LeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_le(b).to_int())).to_cf()?, - - // I8x16GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - // I16x8GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - // I32x4GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - // I64x2GeS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - // I8x16GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - // I16x8GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - // I32x4GeU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_ge(b).to_int())).to_cf()?, - - // I8x16Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - // I16x8Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - // I32x4Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - // I64x2Abs => self.stack.values.replace_top_same::(|a| Ok(a.abs())).to_cf()?, - - // I8x16Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - // I16x8Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - // I32x4Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - // I64x2Neg => self.stack.values.replace_top_same::(|a| Ok(-a)).to_cf()?, - - // I8x16AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - // I16x8AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - // I32x4AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - // I64x2AllTrue => self.stack.values.replace_top::(|v| Ok((v.simd_ne(Simd::splat(0)).all()) as i32)).to_cf()?, - - // I8x16Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - // I16x8Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - // I32x4Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - // I64x2Bitmask => self.stack.values.replace_top::(|v| Ok(v.simd_lt(Simd::splat(0)).to_bitmask() as i32)).to_cf()?, - - // I8x16Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i8))).to_cf()?, - // I16x8Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i16))).to_cf()?, - // I32x4Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a))).to_cf()?, - // I64x2Shl => self.stack.values.calculate_diff::(|a, b| Ok(b.shl(a as i64))).to_cf()?, - - // I8x16ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i8))).to_cf()?, - // I16x8ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i16))).to_cf()?, - // I32x4ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a))).to_cf()?, - // I64x2ShrS => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as i64))).to_cf()?, - - // I8x16ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u8))).to_cf()?, - // I16x8ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u16))).to_cf()?, - // I32x4ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u32))).to_cf()?, - // I64x2ShrU => self.stack.values.calculate_diff::(|a, b| Ok(b.shr(a as u64))).to_cf()?, - - // I8x16Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - // I16x8Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - // I32x4Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - // I64x2Add => self.stack.values.calculate_same::(|a, b| Ok(a + b)).to_cf()?, - - // I8x16Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - // I16x8Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - // I32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - // I64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(a - b)).to_cf()?, - - // I8x16MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - // I16x8MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - // I32x4MinS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - - // I8x16MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - // I16x8MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - // I32x4MinU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_min(b))).to_cf()?, - - // I8x16MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - // I16x8MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - // I32x4MaxS => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - - // I8x16MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - // I16x8MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - // I32x4MaxU => self.stack.values.calculate_same::(|a, b| Ok(a.simd_max(b))).to_cf()?, - - // I64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - // I16x8Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - // I32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(a * b)).to_cf()?, - - // I8x16NarrowI16x8S => unimplemented!(), - // I8x16NarrowI16x8U => unimplemented!(), - // I16x8NarrowI32x4S => unimplemented!(), - // I16x8NarrowI32x4U => unimplemented!(), - - // I8x16AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - // I16x8AddSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - // I8x16AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - // I16x8AddSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_add(b))).to_cf()?, - // I8x16SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - // I16x8SubSatS => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - // I8x16SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - // I16x8SubSatU => self.stack.values.calculate_same::(|a, b| Ok(a.saturating_sub(b))).to_cf()?, - - // I8x16AvgrU => unimplemented!(), - // I16x8AvgrU => unimplemented!(), - - // I16x8ExtAddPairwiseI8x16S => unimplemented!(), - // I16x8ExtAddPairwiseI8x16U => unimplemented!(), - // I32x4ExtAddPairwiseI16x8S => unimplemented!(), - // I32x4ExtAddPairwiseI16x8U => unimplemented!(), - - // I16x8ExtMulLowI8x16S => unimplemented!(), - // I16x8ExtMulLowI8x16U => unimplemented!(), - // I16x8ExtMulHighI8x16S => unimplemented!(), - // I16x8ExtMulHighI8x16U => unimplemented!(), - // I32x4ExtMulLowI16x8S => unimplemented!(), - // I32x4ExtMulLowI16x8U => unimplemented!(), - // I32x4ExtMulHighI16x8S => unimplemented!(), - // I32x4ExtMulHighI16x8U => unimplemented!(), - // I64x2ExtMulLowI32x4S => unimplemented!(), - // I64x2ExtMulLowI32x4U => unimplemented!(), - // I64x2ExtMulHighI32x4S => unimplemented!(), - // I64x2ExtMulHighI32x4U => unimplemented!(), - - // I16x8ExtendLowI8x16S => unimplemented!(), - // I16x8ExtendLowI8x16U => unimplemented!(), - // I16x8ExtendHighI8x16S => unimplemented!(), - // I16x8ExtendHighI8x16U => unimplemented!(), - // I32x4ExtendLowI16x8S => unimplemented!(), - // I32x4ExtendLowI16x8U => unimplemented!(), - // I32x4ExtendHighI16x8S => unimplemented!(), - // I32x4ExtendHighI16x8U => unimplemented!(), - // I64x2ExtendLowI32x4S => unimplemented!(), - // I64x2ExtendLowI32x4U => unimplemented!(), - // I64x2ExtendHighI32x4S => unimplemented!(), - // I64x2ExtendHighI32x4U => unimplemented!(), - - // I8x16Popcnt => self.stack.values.replace_top::(|v| Ok(v.count_ones())).to_cf()?, - // I8x16Shuffle(_idx) => unimplemented!(), - - I16x8Q15MulrSatS => self.stack.values.calculate_same::(|a, b| { - let subq15mulr = |a,b| { - let a = a as i32; - let b = b as i32; - let r = (a * b + 0x4000) >> 15; - if r > i16::MAX as i32 { - i16::MAX - } else if r < i16::MIN as i32 { - i16::MIN - } else { - r as i16 - } - }; - let a = a.as_i16x8(); - let b = b.as_i16x8(); - Ok(Value128::from_i16x8([ - subq15mulr(a[0], b[0]), - subq15mulr(a[1], b[1]), - subq15mulr(a[2], b[2]), - subq15mulr(a[3], b[3]), - subq15mulr(a[4], b[4]), - subq15mulr(a[5], b[5]), - subq15mulr(a[6], b[6]), - subq15mulr(a[7], b[7]), - ])) - }).to_cf()?, - - - I32x4DotI16x8S => self.stack.values.calculate::(|a, b| { - let a = a.as_i16x8(); - let b = b.as_i16x8(); - Ok(Value128::from_i32x4([ - i32::from(a[0] * b[0] + a[1] * b[1]), - i32::from(a[2] * b[2] + a[3] * b[3]), - i32::from(a[4] * b[4] + a[5] * b[5]), - i32::from(a[6] * b[6] + a[7] * b[7]), - ])) - }).to_cf()?, - - // F32x4Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - // F64x2Ceil => self.stack.values.replace_top_same::(|v| Ok(v.ceil())).to_cf()?, - // F32x4Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - // F64x2Floor => self.stack.values.replace_top_same::(|v| Ok(v.floor())).to_cf()?, - // F32x4Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - // F64x2Trunc => self.stack.values.replace_top_same::(|v| Ok(v.trunc())).to_cf()?, - // F32x4Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, - // F64x2Nearest => self.stack.values.replace_top_same::(|v| Ok(v.round())).to_cf()?, - // F32x4Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - // F64x2Abs => self.stack.values.replace_top_same::(|v| Ok(v.abs())).to_cf()?, - // F32x4Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - // F64x2Neg => self.stack.values.replace_top_same::(|v| Ok(-v)).to_cf()?, - // F32x4Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f32x4(v.sqrt()))).to_cf()?, - // F64x2Sqrt => self.stack.values.replace_top_same::(|v| Ok(canonicalize_f64x2(v.sqrt()))).to_cf()?, - // F32x4Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a + b))).to_cf()?, - // F64x2Add => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a + b))).to_cf()?, - // F32x4Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a - b))).to_cf()?, - // F64x2Sub => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a - b))).to_cf()?, - // F32x4Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a * b))).to_cf()?, - // F64x2Mul => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a * b))).to_cf()?, - // F32x4Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f32x4(a / b))).to_cf()?, - // F64x2Div => self.stack.values.calculate_same::(|a, b| Ok(canonicalize_f64x2(a / b))).to_cf()?, - - // F32x4Min => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // b[0].tw_minimum(a[0]), - // b[1].tw_minimum(a[1]), - // b[2].tw_minimum(a[2]), - // b[3].tw_minimum(a[3]), - // ])) - // }).to_cf()?, - - - // F64x2Min => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // b[0].tw_minimum(a[0]), - // b[1].tw_minimum(a[1]), - // ])) - // }).to_cf()?, - - - // F32x4Max => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // b[0].tw_maximum(a[0]), - // b[1].tw_maximum(a[1]), - // b[2].tw_maximum(a[2]), - // b[3].tw_maximum(a[3]), - // ])) - // }).to_cf()?, - - - // F64x2Max => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // b[0].tw_maximum(a[0]), - // b[1].tw_maximum(a[1]), - // ])) - // }).to_cf()?, - - - // F32x4PMin => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // if b[0] < a[0] { b[0] } else { a[0]}, - // if b[1] < a[1] { b[1] } else { a[1]}, - // if b[2] < a[2] { b[2] } else { a[2]}, - // if b[3] < a[3] { b[3] } else { a[3]}, - // ])) - // }).to_cf()?, - - - // F32x4PMax => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // if b[0] > a[0] { b[0] } else { a[0]}, - // if b[1] > a[1] { b[1] } else { a[1]}, - // if b[2] > a[2] { b[2] } else { a[2]}, - // if b[3] > a[3] { b[3] } else { a[3]}, - // ])) - // }).to_cf()?, - - - // F64x2PMin => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // if b[0] < a[0] { b[0] } else { a[0]}, - // if b[1] < a[1] { b[1] } else { a[1]}, - // ])) - // }).to_cf()?, - - - // F64x2PMax => self.stack.values.calculate_same::(|a, b| { - // Ok(Simd::::from_array([ - // if b[0] > a[0] { b[0] } else { a[0]}, - // if b[1] > a[1] { b[1] } else { a[1]}, - // ])) - // }).to_cf()?, - - // // not correct - // I32x4TruncSatF32x4S => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, - // I32x4TruncSatF32x4U => self.stack.values.replace_top::(|v| Ok(v.trunc())).to_cf()?, - // F32x4ConvertI32x4S => unimplemented!(), - // F32x4ConvertI32x4U => unimplemented!(), - // F64x2ConvertLowI32x4S => unimplemented!(), - // F64x2ConvertLowI32x4U => unimplemented!(), - // F32x4DemoteF64x2Zero => unimplemented!(), - // F64x2PromoteLowF32x4 => unimplemented!(), - // I32x4TruncSatF64x2SZero => unimplemented!(), - // I32x4TruncSatF64x2UZero => unimplemented!(), - + V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, + V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, + + V128Const(arg) => self.exec_const::(self.cf.data().v128_constants[*arg as usize].into()), + + I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), + I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), + I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), + I16x8ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u16(*lane) as i32), + I32x4ExtractLane(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i32(*lane)), + I64x2ExtractLane(lane) => stack_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), + F32x4ExtractLane(lane) => stack_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), + F64x2ExtractLane(lane) => stack_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), + + V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + + I8x16ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), + I16x8ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), + I32x4ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), + I64x2ReplaceLane(lane) => stack_op!(binary i64, Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), + F32x4ReplaceLane(lane) => stack_op!(binary f32, Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), + F64x2ReplaceLane(lane) => stack_op!(binary f64, Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), + + I8x16Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), + I16x8Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), + I32x4Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), + I64x2Splat => stack_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), + F32x4Splat => stack_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), + F64x2Splat => stack_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), + + I8x16Eq => stack_op!(binary Value128, |a, b| a.i8x16_eq(b)), + I16x8Eq => stack_op!(binary Value128, |a, b| a.i16x8_eq(b)), + I32x4Eq => stack_op!(binary Value128, |a, b| a.i32x4_eq(b)), + I64x2Eq => stack_op!(binary Value128, |a, b| a.i64x2_eq(b)), + F32x4Eq => stack_op!(binary Value128, |a, b| a.f32x4_eq(b)), + F64x2Eq => stack_op!(binary Value128, |a, b| a.f64x2_eq(b)), + + I8x16Ne => stack_op!(binary Value128, |a, b| a.i8x16_ne(b)), + I16x8Ne => stack_op!(binary Value128, |a, b| a.i16x8_ne(b)), + I32x4Ne => stack_op!(binary Value128, |a, b| a.i32x4_ne(b)), + I64x2Ne => stack_op!(binary Value128, |a, b| a.i64x2_ne(b)), + F32x4Ne => stack_op!(binary Value128, |a, b| a.f32x4_ne(b)), + F64x2Ne => stack_op!(binary Value128, |a, b| a.f64x2_ne(b)), + + I8x16LtS => stack_op!(binary Value128, |a, b| a.i8x16_lt_s(b)), + I16x8LtS => stack_op!(binary Value128, |a, b| a.i16x8_lt_s(b)), + I32x4LtS => stack_op!(binary Value128, |a, b| a.i32x4_lt_s(b)), + I64x2LtS => stack_op!(binary Value128, |a, b| a.i64x2_lt_s(b)), + I8x16LtU => stack_op!(binary Value128, |a, b| a.i8x16_lt_u(b)), + I16x8LtU => stack_op!(binary Value128, |a, b| a.i16x8_lt_u(b)), + I32x4LtU => stack_op!(binary Value128, |a, b| a.i32x4_lt_u(b)), + F32x4Lt => stack_op!(binary Value128, |a, b| a.f32x4_lt(b)), + F64x2Lt => stack_op!(binary Value128, |a, b| a.f64x2_lt(b)), + + F32x4Gt => stack_op!(binary Value128, |a, b| a.f32x4_gt(b)), + F64x2Gt => stack_op!(binary Value128, |a, b| a.f64x2_gt(b)), + + I8x16GtS => stack_op!(binary Value128, |a, b| a.i8x16_gt_s(b)), + I16x8GtS => stack_op!(binary Value128, |a, b| a.i16x8_gt_s(b)), + I32x4GtS => stack_op!(binary Value128, |a, b| a.i32x4_gt_s(b)), + I64x2GtS => stack_op!(binary Value128, |a, b| a.i64x2_gt_s(b)), + I64x2LeS => stack_op!(binary Value128, |a, b| a.i64x2_le_s(b)), + F32x4Le => stack_op!(binary Value128, |a, b| a.f32x4_le(b)), + F64x2Le => stack_op!(binary Value128, |a, b| a.f64x2_le(b)), + + I8x16GtU => stack_op!(binary Value128, |a, b| a.i8x16_gt_u(b)), + I16x8GtU => stack_op!(binary Value128, |a, b| a.i16x8_gt_u(b)), + I32x4GtU => stack_op!(binary Value128, |a, b| a.i32x4_gt_u(b)), + F32x4Ge => stack_op!(binary Value128, |a, b| a.f32x4_ge(b)), + F64x2Ge => stack_op!(binary Value128, |a, b| a.f64x2_ge(b)), + + I8x16LeS => stack_op!(binary Value128, |a, b| a.i8x16_le_s(b)), + I16x8LeS => stack_op!(binary Value128, |a, b| a.i16x8_le_s(b)), + I32x4LeS => stack_op!(binary Value128, |a, b| a.i32x4_le_s(b)), + + I8x16LeU => stack_op!(binary Value128, |a, b| a.i8x16_le_u(b)), + I16x8LeU => stack_op!(binary Value128, |a, b| a.i16x8_le_u(b)), + I32x4LeU => stack_op!(binary Value128, |a, b| a.i32x4_le_u(b)), + + I8x16GeS => stack_op!(binary Value128, |a, b| a.i8x16_ge_s(b)), + I16x8GeS => stack_op!(binary Value128, |a, b| a.i16x8_ge_s(b)), + I32x4GeS => stack_op!(binary Value128, |a, b| a.i32x4_ge_s(b)), + I64x2GeS => stack_op!(binary Value128, |a, b| a.i64x2_ge_s(b)), + + I8x16GeU => stack_op!(binary Value128, |a, b| a.i8x16_ge_u(b)), + I16x8GeU => stack_op!(binary Value128, |a, b| a.i16x8_ge_u(b)), + I32x4GeU => stack_op!(binary Value128, |a, b| a.i32x4_ge_u(b)), + + I8x16Abs => stack_op!(unary Value128, |a| a.i8x16_abs()), + I16x8Abs => stack_op!(unary Value128, |a| a.i16x8_abs()), + I32x4Abs => stack_op!(unary Value128, |a| a.i32x4_abs()), + I64x2Abs => stack_op!(unary Value128, |a| a.i64x2_abs()), + + I8x16Neg => stack_op!(unary Value128, |a| a.i8x16_neg()), + I16x8Neg => stack_op!(unary Value128, |a| a.i16x8_neg()), + I32x4Neg => stack_op!(unary Value128, |a| a.i32x4_neg()), + I64x2Neg => stack_op!(unary Value128, |a| a.i64x2_neg()), + + I8x16AllTrue => stack_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), + I16x8AllTrue => stack_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), + I32x4AllTrue => stack_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), + I64x2AllTrue => stack_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), + + I8x16Bitmask => stack_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), + I16x8Bitmask => stack_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), + I32x4Bitmask => stack_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), + I64x2Bitmask => stack_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), + + I8x16Shl => stack_op!(binary i32, Value128, |a, b| b.i8x16_shl(a as u32)), + I16x8Shl => stack_op!(binary i32, Value128, |a, b| b.i16x8_shl(a as u32)), + I32x4Shl => stack_op!(binary i32, Value128, |a, b| b.i32x4_shl(a as u32)), + I64x2Shl => stack_op!(binary i32, Value128, |a, b| b.i64x2_shl(a as u32)), + + I8x16ShrS => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_s(a as u32)), + I16x8ShrS => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_s(a as u32)), + I32x4ShrS => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_s(a as u32)), + I64x2ShrS => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_s(a as u32)), + + I8x16ShrU => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_u(a as u32)), + I16x8ShrU => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_u(a as u32)), + I32x4ShrU => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_u(a as u32)), + I64x2ShrU => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_u(a as u32)), + + I8x16Add => stack_op!(binary Value128, |a, b| a.i8x16_add(b)), + I16x8Add => stack_op!(binary Value128, |a, b| a.i16x8_add(b)), + I32x4Add => stack_op!(binary Value128, |a, b| a.i32x4_add(b)), + I64x2Add => stack_op!(binary Value128, |a, b| a.i64x2_add(b)), + + I8x16Sub => stack_op!(binary Value128, |a, b| a.i8x16_sub(b)), + I16x8Sub => stack_op!(binary Value128, |a, b| a.i16x8_sub(b)), + I32x4Sub => stack_op!(binary Value128, |a, b| a.i32x4_sub(b)), + I64x2Sub => stack_op!(binary Value128, |a, b| a.i64x2_sub(b)), + + I8x16MinS => stack_op!(binary Value128, |a, b| a.i8x16_min_s(b)), + I16x8MinS => stack_op!(binary Value128, |a, b| a.i16x8_min_s(b)), + I32x4MinS => stack_op!(binary Value128, |a, b| a.i32x4_min_s(b)), + + I8x16MinU => stack_op!(binary Value128, |a, b| a.i8x16_min_u(b)), + I16x8MinU => stack_op!(binary Value128, |a, b| a.i16x8_min_u(b)), + I32x4MinU => stack_op!(binary Value128, |a, b| a.i32x4_min_u(b)), + + I8x16MaxS => stack_op!(binary Value128, |a, b| a.i8x16_max_s(b)), + I16x8MaxS => stack_op!(binary Value128, |a, b| a.i16x8_max_s(b)), + I32x4MaxS => stack_op!(binary Value128, |a, b| a.i32x4_max_s(b)), + + I8x16MaxU => stack_op!(binary Value128, |a, b| a.i8x16_max_u(b)), + I16x8MaxU => stack_op!(binary Value128, |a, b| a.i16x8_max_u(b)), + I32x4MaxU => stack_op!(binary Value128, |a, b| a.i32x4_max_u(b)), + + I64x2Mul => stack_op!(binary Value128, |a, b| a.i64x2_mul(b)), + I16x8Mul => stack_op!(binary Value128, |a, b| a.i16x8_mul(b)), + I32x4Mul => stack_op!(binary Value128, |a, b| a.i32x4_mul(b)), + + I8x16NarrowI16x8S => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), + I8x16NarrowI16x8U => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), + I16x8NarrowI32x4S => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), + I16x8NarrowI32x4U => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), + + I8x16AddSatS => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_s(b)), + I16x8AddSatS => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_s(b)), + I8x16AddSatU => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_u(b)), + I16x8AddSatU => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_u(b)), + I8x16SubSatS => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_s(b)), + I16x8SubSatS => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_s(b)), + I8x16SubSatU => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_u(b)), + I16x8SubSatU => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_u(b)), + + I8x16AvgrU => stack_op!(binary Value128, |a, b| a.i8x16_avgr_u(b)), + I16x8AvgrU => stack_op!(binary Value128, |a, b| a.i16x8_avgr_u(b)), + + I16x8ExtAddPairwiseI8x16S => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), + I16x8ExtAddPairwiseI8x16U => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), + I32x4ExtAddPairwiseI16x8S => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), + I32x4ExtAddPairwiseI16x8U => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), + + I16x8ExtMulLowI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), + I16x8ExtMulLowI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), + I16x8ExtMulHighI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), + I16x8ExtMulHighI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_u(b)), + I32x4ExtMulLowI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_s(b)), + I32x4ExtMulLowI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_u(b)), + I32x4ExtMulHighI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_s(b)), + I32x4ExtMulHighI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_u(b)), + I64x2ExtMulLowI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_s(b)), + I64x2ExtMulLowI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), + I64x2ExtMulHighI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), + I64x2ExtMulHighI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), + + I16x8ExtendLowI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_s()), + I16x8ExtendLowI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_u()), + I16x8ExtendHighI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_s()), + I16x8ExtendHighI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_u()), + I32x4ExtendLowI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_s()), + I32x4ExtendLowI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_u()), + I32x4ExtendHighI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_s()), + I32x4ExtendHighI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_u()), + I64x2ExtendLowI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_s()), + I64x2ExtendLowI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_u()), + I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), + I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), + + I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), + I8x16Shuffle(idx) => { let idx = self.cf.data().v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } + + I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), + + I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), + + F32x4Ceil => stack_op!(simd_unary f32x4_ceil), + F64x2Ceil => stack_op!(simd_unary f64x2_ceil), + F32x4Floor => stack_op!(simd_unary f32x4_floor), + F64x2Floor => stack_op!(simd_unary f64x2_floor), + F32x4Trunc => stack_op!(simd_unary f32x4_trunc), + F64x2Trunc => stack_op!(simd_unary f64x2_trunc), + F32x4Nearest => stack_op!(simd_unary f32x4_nearest), + F64x2Nearest => stack_op!(simd_unary f64x2_nearest), + F32x4Abs => stack_op!(simd_unary f32x4_abs), + F64x2Abs => stack_op!(simd_unary f64x2_abs), + F32x4Neg => stack_op!(simd_unary f32x4_neg), + F64x2Neg => stack_op!(simd_unary f64x2_neg), + F32x4Sqrt => stack_op!(simd_unary f32x4_sqrt), + F64x2Sqrt => stack_op!(simd_unary f64x2_sqrt), + F32x4Add => stack_op!(simd_binary f32x4_add), + F64x2Add => stack_op!(simd_binary f64x2_add), + F32x4Sub => stack_op!(simd_binary f32x4_sub), + F64x2Sub => stack_op!(simd_binary f64x2_sub), + F32x4Mul => stack_op!(simd_binary f32x4_mul), + F64x2Mul => stack_op!(simd_binary f64x2_mul), + F32x4Div => stack_op!(simd_binary f32x4_div), + F64x2Div => stack_op!(simd_binary f64x2_div), + + F32x4Min => stack_op!(simd_binary f32x4_min), + F64x2Min => stack_op!(simd_binary f64x2_min), + F32x4Max => stack_op!(simd_binary f32x4_max), + F64x2Max => stack_op!(simd_binary f64x2_max), + F32x4PMin => stack_op!(simd_binary f32x4_pmin), + F32x4PMax => stack_op!(simd_binary f32x4_pmax), + F64x2PMin => stack_op!(simd_binary f64x2_pmin), + F64x2PMax => stack_op!(simd_binary f64x2_pmax), + + I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), + I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), + F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), + F32x4ConvertI32x4U => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_u()), + F64x2ConvertLowI32x4S => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_s()), + F64x2ConvertLowI32x4U => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_u()), + F32x4DemoteF64x2Zero => stack_op!(unary Value128, |v| v.f32x4_demote_f64x2_zero()), + F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), + I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), + I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), + + // Relaxed SIMD (not yet implemented) // I8x16RelaxedSwizzle => unimplemented!(), // I32x4RelaxedTruncF32x4S => unimplemented!(), // I32x4RelaxedTruncF32x4U => unimplemented!(), @@ -1094,8 +1035,10 @@ impl<'store, 'stack> Executor<'store, 'stack> { lane: u8, ) -> ControlFlow> { let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - let val = self.stack.values.pop::().to_mem_bytes(); - let val = val[lane as usize].to_mem_bytes(); + let bytes = self.stack.values.pop::().to_mem_bytes(); + let lane_offset = lane as usize * N; + let mut val = [0u8; N]; + val.copy_from_slice(&bytes[lane_offset..lane_offset + N]); let addr = match mem.is_64bit() { true => self.stack.values.pop::() as u64, diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 03564032..5b9e7b94 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -37,7 +37,7 @@ macro_rules! checked_conv_float { $self .stack .values - .replace_top::<$from, $to>(|v| { + .unary::<$from, $to>(|v| { let (min, max) = float_min_max!($from, $intermediate); if unlikely(v.is_nan()) { return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 6e02b0f1..2850e91a 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; -use crate::{Result, StackConfig, interpreter::*}; +use crate::{interpreter::*, Result, StackConfig}; use super::Locals; @@ -63,18 +63,18 @@ impl ValueStack { } #[inline] - pub(crate) fn calculate_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { + pub(crate) fn binary_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { T::stack_calculate(self, func) } #[inline] #[allow(dead_code)] - pub(crate) fn calculate_same_3(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { + pub(crate) fn ternary_same(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { T::stack_calculate3(self, func) } #[inline] - pub(crate) fn calculate( + pub(crate) fn binary( &mut self, func: impl FnOnce(T, T) -> Result, ) -> Result<()> { @@ -86,7 +86,7 @@ impl ValueStack { #[inline] #[allow(dead_code)] - pub(crate) fn calculate_diff( + pub(crate) fn binary_diff( &mut self, func: impl FnOnce(A, B) -> Result, ) -> Result<()> { @@ -97,7 +97,7 @@ impl ValueStack { } #[inline] - pub(crate) fn replace_top( + pub(crate) fn unary( &mut self, func: impl FnOnce(T) -> Result, ) -> Result<()> { @@ -107,7 +107,7 @@ impl ValueStack { } #[inline] - pub(crate) fn replace_top_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { + pub(crate) fn unary_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { T::replace_top(self, func) } diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index fcd208f9..6b807c9a 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -1,7 +1,84 @@ +use super::num_helpers::TinywasmFloatExt; + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Value128(i128); impl Value128 { + #[inline] + fn canonicalize_simd_f32_nan(x: f32) -> f32 { + if x.is_nan() { + f32::from_bits(0x7fc0_0000) + } else { + x + } + } + + #[inline] + fn canonicalize_simd_f64_nan(x: f64) -> f64 { + if x.is_nan() { + f64::from_bits(0x7ff8_0000_0000_0000) + } else { + x + } + } + + const fn saturate_i16_to_i8(x: i16) -> i8 { + if x > i8::MAX as i16 { + i8::MAX + } else if x < i8::MIN as i16 { + i8::MIN + } else { + x as i8 + } + } + + const fn saturate_i16_to_u8(x: i16) -> u8 { + if x <= 0 { + 0 + } else if x > u8::MAX as i16 { + u8::MAX + } else { + x as u8 + } + } + + const fn saturate_i32_to_i16(x: i32) -> i16 { + if x > i16::MAX as i32 { + i16::MAX + } else if x < i16::MIN as i32 { + i16::MIN + } else { + x as i16 + } + } + + const fn saturate_i32_to_u16(x: i32) -> u16 { + if x <= 0 { + 0 + } else if x > u16::MAX as i32 { + u16::MAX + } else { + x as u16 + } + } + + const fn replace_lane_bytes( + self, + lane: u8, + value: [u8; LANE_BYTES], + lane_count: u8, + ) -> Self { + debug_assert!(lane < lane_count); + let mut bytes = self.to_le_bytes(); + let mut i = 0; + let start = lane as usize * LANE_BYTES; + while i < LANE_BYTES { + bytes[start + i] = value[i]; + i += 1; + } + Self::from_le_bytes(bytes) + } + pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { Self(i128::from_le_bytes(bytes)) } @@ -119,6 +196,32 @@ impl Value128 { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) } + #[inline] + fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { + let lanes = self.as_f32x4(); + Self::from_f32x4([op(lanes[0]), op(lanes[1]), op(lanes[2]), op(lanes[3])]) + } + + #[inline] + fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { + let a = self.as_f32x4(); + let b = rhs.as_f32x4(); + Self::from_f32x4([op(a[0], b[0]), op(a[1], b[1]), op(a[2], b[2]), op(a[3], b[3])]) + } + + #[inline] + fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { + let lanes = self.as_f64x2(); + Self::from_f64x2([op(lanes[0]), op(lanes[1])]) + } + + #[inline] + fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { + let a = self.as_f64x2(); + let b = rhs.as_f64x2(); + Self::from_f64x2([op(a[0], b[0]), op(a[1], b[1])]) + } + pub const fn reduce_or(self) -> u8 { let mut result = 0u8; let bytes = self.to_le_bytes(); @@ -130,7 +233,111 @@ impl Value128 { result } + #[doc(alias = "v128.any_true")] + pub const fn v128_any_true(self) -> bool { + self.reduce_or() != 0 + } + + #[doc(alias = "v128.not")] + pub const fn v128_not(self) -> Self { + Self(!self.0) + } + + #[doc(alias = "v128.and")] + pub const fn v128_and(self, rhs: Self) -> Self { + Self(self.0 & rhs.0) + } + + #[doc(alias = "v128.andnot")] + pub const fn v128_andnot(self, rhs: Self) -> Self { + Self(self.0 & !rhs.0) + } + + #[doc(alias = "v128.or")] + pub const fn v128_or(self, rhs: Self) -> Self { + Self(self.0 | rhs.0) + } + + #[doc(alias = "v128.xor")] + pub const fn v128_xor(self, rhs: Self) -> Self { + Self(self.0 ^ rhs.0) + } + + #[doc(alias = "v128.bitselect")] + pub const fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { + Self((v1.0 & c.0) | (v2.0 & !c.0)) + } + pub const fn swizzle(self, s: Self) -> Self { + self.i8x16_swizzle(s) + } + + #[doc(alias = "v128.load8x8_s")] + pub const fn v128_load8x8_s(src: [u8; 8]) -> Self { + Self::from_i16x8([ + src[0] as i8 as i16, + src[1] as i8 as i16, + src[2] as i8 as i16, + src[3] as i8 as i16, + src[4] as i8 as i16, + src[5] as i8 as i16, + src[6] as i8 as i16, + src[7] as i8 as i16, + ]) + } + + #[doc(alias = "v128.load8x8_u")] + pub const fn v128_load8x8_u(src: [u8; 8]) -> Self { + Self::from_u16x8([ + src[0] as u16, + src[1] as u16, + src[2] as u16, + src[3] as u16, + src[4] as u16, + src[5] as u16, + src[6] as u16, + src[7] as u16, + ]) + } + + #[doc(alias = "v128.load16x4_s")] + pub const fn v128_load16x4_s(src: [u8; 8]) -> Self { + Self::from_i32x4([ + i16::from_le_bytes([src[0], src[1]]) as i32, + i16::from_le_bytes([src[2], src[3]]) as i32, + i16::from_le_bytes([src[4], src[5]]) as i32, + i16::from_le_bytes([src[6], src[7]]) as i32, + ]) + } + + #[doc(alias = "v128.load16x4_u")] + pub const fn v128_load16x4_u(src: [u8; 8]) -> Self { + Self::from_u32x4([ + u16::from_le_bytes([src[0], src[1]]) as u32, + u16::from_le_bytes([src[2], src[3]]) as u32, + u16::from_le_bytes([src[4], src[5]]) as u32, + u16::from_le_bytes([src[6], src[7]]) as u32, + ]) + } + + #[doc(alias = "v128.load32x2_s")] + pub const fn v128_load32x2_s(src: [u8; 8]) -> Self { + Self::from_i64x2([ + i32::from_le_bytes([src[0], src[1], src[2], src[3]]) as i64, + i32::from_le_bytes([src[4], src[5], src[6], src[7]]) as i64, + ]) + } + + #[doc(alias = "v128.load32x2_u")] + pub const fn v128_load32x2_u(src: [u8; 8]) -> Self { + Self::from_u64x2([ + u32::from_le_bytes([src[0], src[1], src[2], src[3]]) as u64, + u32::from_le_bytes([src[4], src[5], src[6], src[7]]) as u64, + ]) + } + + #[doc(alias = "i8x16.swizzle")] + pub const fn i8x16_swizzle(self, s: Self) -> Self { let a_bytes = self.to_le_bytes(); let s_bytes = s.to_le_bytes(); let mut result_bytes = [0u8; 16]; @@ -143,6 +350,20 @@ impl Value128 { Self::from_le_bytes(result_bytes) } + #[doc(alias = "i8x16.shuffle")] + pub const fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + let mut result_bytes = [0u8; 16]; + let mut i = 0; + while i < 16 { + let index = idx[i] as usize; + result_bytes[i] = if index < 16 { a_bytes[index] } else { b_bytes[index - 16] }; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + pub const fn extend_8_i8(src: i8) -> Self { let mut result_bytes = [0u8; 16]; let mut i = 0; @@ -240,152 +461,2095 @@ impl Value128 { Self::from_le_bytes(result_bytes) } - pub const fn splat_i16(src: i16) -> Self { - let mut result_bytes = [0u8; 16]; - let bytes = src.to_le_bytes(); + #[doc(alias = "i8x16.replace_lane")] + pub const fn i8x16_replace_lane(self, lane: u8, value: i8) -> Self { + self.replace_lane_bytes::<1>(lane, [value as u8], 16) + } + + #[doc(alias = "i16x8.replace_lane")] + pub const fn i16x8_replace_lane(self, lane: u8, value: i16) -> Self { + self.replace_lane_bytes::<2>(lane, value.to_le_bytes(), 8) + } + + #[doc(alias = "i32x4.replace_lane")] + pub const fn i32x4_replace_lane(self, lane: u8, value: i32) -> Self { + self.replace_lane_bytes::<4>(lane, value.to_le_bytes(), 4) + } + + #[doc(alias = "i64x2.replace_lane")] + pub const fn i64x2_replace_lane(self, lane: u8, value: i64) -> Self { + self.replace_lane_bytes::<8>(lane, value.to_le_bytes(), 2) + } + + #[doc(alias = "f32x4.replace_lane")] + pub const fn f32x4_replace_lane(self, lane: u8, value: f32) -> Self { + self.replace_lane_bytes::<4>(lane, value.to_bits().to_le_bytes(), 4) + } + + #[doc(alias = "f64x2.replace_lane")] + pub const fn f64x2_replace_lane(self, lane: u8, value: f64) -> Self { + self.replace_lane_bytes::<8>(lane, value.to_bits().to_le_bytes(), 2) + } + + #[doc(alias = "i8x16.all_true")] + pub const fn i8x16_all_true(self) -> bool { + let lanes = self.as_i8x16(); + let mut i = 0; + while i < 16 { + if lanes[i] == 0 { + return false; + } + i += 1; + } + true + } + + #[doc(alias = "i16x8.all_true")] + pub const fn i16x8_all_true(self) -> bool { + let lanes = self.as_i16x8(); let mut i = 0; while i < 8 { - result_bytes[i * 2] = bytes[0]; - result_bytes[i * 2 + 1] = bytes[1]; + if lanes[i] == 0 { + return false; + } i += 1; } - Self::from_le_bytes(result_bytes) + true } - pub const fn splat_i32(src: i32) -> Self { - let mut result_bytes = [0u8; 16]; - let bytes = src.to_le_bytes(); + #[doc(alias = "i32x4.all_true")] + pub const fn i32x4_all_true(self) -> bool { + let lanes = self.as_i32x4(); let mut i = 0; while i < 4 { - result_bytes[i * 4] = bytes[0]; - result_bytes[i * 4 + 1] = bytes[1]; - result_bytes[i * 4 + 2] = bytes[2]; - result_bytes[i * 4 + 3] = bytes[3]; + if lanes[i] == 0 { + return false; + } i += 1; } - Self::from_le_bytes(result_bytes) + true } - pub const fn splat_i64(src: i64) -> Self { - let mut result_bytes = [0u8; 16]; - let bytes = src.to_le_bytes(); + #[doc(alias = "i64x2.all_true")] + pub const fn i64x2_all_true(self) -> bool { + let lanes = self.as_i64x2(); let mut i = 0; while i < 2 { - result_bytes[i * 8] = bytes[0]; - result_bytes[i * 8 + 1] = bytes[1]; - result_bytes[i * 8 + 2] = bytes[2]; - result_bytes[i * 8 + 3] = bytes[3]; - result_bytes[i * 8 + 4] = bytes[4]; - result_bytes[i * 8 + 5] = bytes[5]; - result_bytes[i * 8 + 6] = bytes[6]; - result_bytes[i * 8 + 7] = bytes[7]; + if lanes[i] == 0 { + return false; + } i += 1; } - Self::from_le_bytes(result_bytes) + true } - pub const fn splat_f32(src: f32) -> Self { - Self::splat_i32(src.to_bits() as i32) + #[doc(alias = "i8x16.bitmask")] + pub const fn i8x16_bitmask(self) -> u32 { + let lanes = self.as_i8x16(); + let mut mask = 0u32; + let mut i = 0; + while i < 16 { + mask |= ((lanes[i] < 0) as u32) << i; + i += 1; + } + mask } - pub const fn splat_f64(src: f64) -> Self { - Self::splat_i64(src.to_bits() as i64) + #[doc(alias = "i16x8.bitmask")] + pub const fn i16x8_bitmask(self) -> u32 { + let lanes = self.as_i16x8(); + let mut mask = 0u32; + let mut i = 0; + while i < 8 { + mask |= ((lanes[i] < 0) as u32) << i; + i += 1; + } + mask } - pub const fn extract_lane_i8(self, lane: u8) -> i8 { - debug_assert!(lane < 16); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - bytes[lane] as i8 + #[doc(alias = "i32x4.bitmask")] + pub const fn i32x4_bitmask(self) -> u32 { + let lanes = self.as_i32x4(); + let mut mask = 0u32; + let mut i = 0; + while i < 4 { + mask |= ((lanes[i] < 0) as u32) << i; + i += 1; + } + mask } - pub const fn extract_lane_u8(self, lane: u8) -> u8 { - debug_assert!(lane < 16); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - bytes[lane] + #[doc(alias = "i64x2.bitmask")] + pub const fn i64x2_bitmask(self) -> u32 { + let lanes = self.as_i64x2(); + let mut mask = 0u32; + let mut i = 0; + while i < 2 { + mask |= ((lanes[i] < 0) as u32) << i; + i += 1; + } + mask } - pub const fn extract_lane_i16(self, lane: u8) -> i16 { - debug_assert!(lane < 8); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 2; - i16::from_le_bytes([bytes[start], bytes[start + 1]]) + #[doc(alias = "i8x16.popcnt")] + pub const fn i8x16_popcnt(self) -> Self { + let lanes = self.as_u8x16(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = lanes[i].count_ones() as u8; + i += 1; + } + Self::from_u8x16(out) } - pub const fn extract_lane_u16(self, lane: u8) -> u16 { - debug_assert!(lane < 8); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 2; - u16::from_le_bytes([bytes[start], bytes[start + 1]]) + #[doc(alias = "i8x16.shl")] + pub const fn i8x16_shl(self, shift: u32) -> Self { + let lanes = self.as_i8x16(); + let s = shift & 7; + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = lanes[i].wrapping_shl(s); + i += 1; + } + Self::from_i8x16(out) } - pub const fn extract_lane_i32(self, lane: u8) -> i32 { - debug_assert!(lane < 4); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 4; - i32::from_le_bytes([bytes[start], bytes[start + 1], bytes[start + 2], bytes[start + 3]]) + #[doc(alias = "i16x8.shl")] + pub const fn i16x8_shl(self, shift: u32) -> Self { + let lanes = self.as_i16x8(); + let s = shift & 15; + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = lanes[i].wrapping_shl(s); + i += 1; + } + Self::from_i16x8(out) } - pub const fn extract_lane_i64(self, lane: u8) -> i64 { - debug_assert!(lane < 2); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 8; - i64::from_le_bytes([ - bytes[start], - bytes[start + 1], - bytes[start + 2], - bytes[start + 3], - bytes[start + 4], - bytes[start + 5], - bytes[start + 6], - bytes[start + 7], - ]) + #[doc(alias = "i32x4.shl")] + pub const fn i32x4_shl(self, shift: u32) -> Self { + let lanes = self.as_i32x4(); + let s = shift & 31; + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = lanes[i].wrapping_shl(s); + i += 1; + } + Self::from_i32x4(out) } - pub const fn extract_lane_f32(self, lane: u8) -> f32 { - f32::from_bits(self.extract_lane_i32(lane) as u32) + #[doc(alias = "i64x2.shl")] + pub const fn i64x2_shl(self, shift: u32) -> Self { + let lanes = self.as_i64x2(); + let s = shift & 63; + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = lanes[i].wrapping_shl(s); + i += 1; + } + Self::from_i64x2(out) } - pub const fn extract_lane_f64(self, lane: u8) -> f64 { - f64::from_bits(self.extract_lane_i64(lane) as u64) + #[doc(alias = "i8x16.shr_s")] + pub const fn i8x16_shr_s(self, shift: u32) -> Self { + let lanes = self.as_i8x16(); + let s = shift & 7; + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_i8x16(out) } -} -impl From for i128 { - fn from(val: Value128) -> Self { - val.0 + #[doc(alias = "i16x8.shr_s")] + pub const fn i16x8_shr_s(self, shift: u32) -> Self { + let lanes = self.as_i16x8(); + let s = shift & 15; + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_i16x8(out) } -} -impl From for Value128 { - fn from(value: i128) -> Self { - Self(value) + #[doc(alias = "i32x4.shr_s")] + pub const fn i32x4_shr_s(self, shift: u32) -> Self { + let lanes = self.as_i32x4(); + let s = shift & 31; + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_i32x4(out) } -} -impl core::ops::Not for Value128 { - type Output = Self; - fn not(self) -> Self::Output { - Self(!self.0) + #[doc(alias = "i64x2.shr_s")] + pub const fn i64x2_shr_s(self, shift: u32) -> Self { + let lanes = self.as_i64x2(); + let s = shift & 63; + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_i64x2(out) } -} -impl core::ops::BitAnd for Value128 { - type Output = Self; - fn bitand(self, rhs: Self) -> Self::Output { - Self(self.0 & rhs.0) + #[doc(alias = "i8x16.shr_u")] + pub const fn i8x16_shr_u(self, shift: u32) -> Self { + let lanes = self.as_u8x16(); + let s = shift & 7; + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_u8x16(out) } -} -impl core::ops::BitOr for Value128 { - type Output = Self; - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) + #[doc(alias = "i16x8.shr_u")] + pub const fn i16x8_shr_u(self, shift: u32) -> Self { + let lanes = self.as_u16x8(); + let s = shift & 15; + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.shr_u")] + pub const fn i32x4_shr_u(self, shift: u32) -> Self { + let lanes = self.as_u32x4(); + let s = shift & 31; + let mut out = [0u32; 4]; + let mut i = 0; + while i < 4 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i64x2.shr_u")] + pub const fn i64x2_shr_u(self, shift: u32) -> Self { + let lanes = self.as_u64x2(); + let s = shift & 63; + let mut out = [0u64; 2]; + let mut i = 0; + while i < 2 { + out[i] = lanes[i] >> s; + i += 1; + } + Self::from_u64x2(out) + } + + #[doc(alias = "i8x16.add")] + pub const fn i8x16_add(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].wrapping_add(b[i]); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.add")] + pub const fn i16x8_add(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].wrapping_add(b[i]); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.add")] + pub const fn i32x4_add(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = a[i].wrapping_add(b[i]); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.add")] + pub const fn i64x2_add(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = a[i].wrapping_add(b[i]); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.sub")] + pub const fn i8x16_sub(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].wrapping_sub(b[i]); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.sub")] + pub const fn i16x8_sub(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].wrapping_sub(b[i]); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.sub")] + pub const fn i32x4_sub(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = a[i].wrapping_sub(b[i]); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.sub")] + pub const fn i64x2_sub(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = a[i].wrapping_sub(b[i]); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i16x8.mul")] + pub const fn i16x8_mul(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].wrapping_mul(b[i]); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.mul")] + pub const fn i32x4_mul(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = a[i].wrapping_mul(b[i]); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.mul")] + pub const fn i64x2_mul(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = a[i].wrapping_mul(b[i]); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.add_sat_s")] + pub const fn i8x16_add_sat_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].saturating_add(b[i]); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.add_sat_s")] + pub const fn i16x8_add_sat_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].saturating_add(b[i]); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i8x16.add_sat_u")] + pub const fn i8x16_add_sat_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].saturating_add(b[i]); + i += 1; + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.add_sat_u")] + pub const fn i16x8_add_sat_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].saturating_add(b[i]); + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i8x16.sub_sat_s")] + pub const fn i8x16_sub_sat_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].saturating_sub(b[i]); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.sub_sat_s")] + pub const fn i16x8_sub_sat_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].saturating_sub(b[i]); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i8x16.sub_sat_u")] + pub const fn i8x16_sub_sat_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].saturating_sub(b[i]); + i += 1; + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.sub_sat_u")] + pub const fn i16x8_sub_sat_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].saturating_sub(b[i]); + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i8x16.avgr_u")] + pub const fn i8x16_avgr_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = ((a[i] as u16 + b[i] as u16 + 1) >> 1) as u8; + i += 1; + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.avgr_u")] + pub const fn i16x8_avgr_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = ((a[i] as u32 + b[i] as u32 + 1) >> 1) as u16; + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i8x16.narrow_i16x8_s")] + pub const fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 8 { + out[i] = Self::saturate_i16_to_i8(av[i]); + out[i + 8] = Self::saturate_i16_to_i8(bv[i]); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i8x16.narrow_i16x8_u")] + pub const fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 8 { + out[i] = Self::saturate_i16_to_u8(av[i]); + out[i + 8] = Self::saturate_i16_to_u8(bv[i]); + i += 1; + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.narrow_i32x4_s")] + pub const fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 4 { + out[i] = Self::saturate_i32_to_i16(av[i]); + out[i + 4] = Self::saturate_i32_to_i16(bv[i]); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.narrow_i32x4_u")] + pub const fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 4 { + out[i] = Self::saturate_i32_to_u16(av[i]); + out[i + 4] = Self::saturate_i32_to_u16(bv[i]); + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] + pub const fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { + let lanes = self.as_i8x16(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + let j = i * 2; + out[i] = lanes[j] as i16 + lanes[j + 1] as i16; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] + pub const fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { + let lanes = self.as_u8x16(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + let j = i * 2; + out[i] = lanes[j] as u16 + lanes[j + 1] as u16; + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] + pub const fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { + let lanes = self.as_i16x8(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + let j = i * 2; + out[i] = lanes[j] as i32 + lanes[j + 1] as i32; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] + pub const fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { + let lanes = self.as_u16x8(); + let mut out = [0u32; 4]; + let mut i = 0; + while i < 4 { + let j = i * 2; + out[i] = lanes[j] as u32 + lanes[j + 1] as u32; + i += 1; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i16x8.extend_low_i8x16_s")] + pub const fn i16x8_extend_low_i8x16_s(self) -> Self { + let lanes = self.as_i8x16(); + Self::from_i16x8([ + lanes[0] as i16, + lanes[1] as i16, + lanes[2] as i16, + lanes[3] as i16, + lanes[4] as i16, + lanes[5] as i16, + lanes[6] as i16, + lanes[7] as i16, + ]) + } + + #[doc(alias = "i16x8.extend_low_i8x16_u")] + pub const fn i16x8_extend_low_i8x16_u(self) -> Self { + let lanes = self.as_u8x16(); + Self::from_u16x8([ + lanes[0] as u16, + lanes[1] as u16, + lanes[2] as u16, + lanes[3] as u16, + lanes[4] as u16, + lanes[5] as u16, + lanes[6] as u16, + lanes[7] as u16, + ]) + } + + #[doc(alias = "i16x8.extend_high_i8x16_s")] + pub const fn i16x8_extend_high_i8x16_s(self) -> Self { + let lanes = self.as_i8x16(); + Self::from_i16x8([ + lanes[8] as i16, + lanes[9] as i16, + lanes[10] as i16, + lanes[11] as i16, + lanes[12] as i16, + lanes[13] as i16, + lanes[14] as i16, + lanes[15] as i16, + ]) + } + + #[doc(alias = "i16x8.extend_high_i8x16_u")] + pub const fn i16x8_extend_high_i8x16_u(self) -> Self { + let lanes = self.as_u8x16(); + Self::from_u16x8([ + lanes[8] as u16, + lanes[9] as u16, + lanes[10] as u16, + lanes[11] as u16, + lanes[12] as u16, + lanes[13] as u16, + lanes[14] as u16, + lanes[15] as u16, + ]) + } + + #[doc(alias = "i32x4.extend_low_i16x8_s")] + pub const fn i32x4_extend_low_i16x8_s(self) -> Self { + let lanes = self.as_i16x8(); + Self::from_i32x4([lanes[0] as i32, lanes[1] as i32, lanes[2] as i32, lanes[3] as i32]) + } + + #[doc(alias = "i32x4.extend_low_i16x8_u")] + pub const fn i32x4_extend_low_i16x8_u(self) -> Self { + let lanes = self.as_u16x8(); + Self::from_u32x4([lanes[0] as u32, lanes[1] as u32, lanes[2] as u32, lanes[3] as u32]) + } + + #[doc(alias = "i32x4.extend_high_i16x8_s")] + pub const fn i32x4_extend_high_i16x8_s(self) -> Self { + let lanes = self.as_i16x8(); + Self::from_i32x4([lanes[4] as i32, lanes[5] as i32, lanes[6] as i32, lanes[7] as i32]) + } + + #[doc(alias = "i32x4.extend_high_i16x8_u")] + pub const fn i32x4_extend_high_i16x8_u(self) -> Self { + let lanes = self.as_u16x8(); + Self::from_u32x4([lanes[4] as u32, lanes[5] as u32, lanes[6] as u32, lanes[7] as u32]) + } + + #[doc(alias = "i64x2.extend_low_i32x4_s")] + pub const fn i64x2_extend_low_i32x4_s(self) -> Self { + let lanes = self.as_i32x4(); + Self::from_i64x2([lanes[0] as i64, lanes[1] as i64]) + } + + #[doc(alias = "i64x2.extend_low_i32x4_u")] + pub const fn i64x2_extend_low_i32x4_u(self) -> Self { + let lanes = self.as_u32x4(); + Self::from_u64x2([lanes[0] as u64, lanes[1] as u64]) + } + + #[doc(alias = "i64x2.extend_high_i32x4_s")] + pub const fn i64x2_extend_high_i32x4_s(self) -> Self { + let lanes = self.as_i32x4(); + Self::from_i64x2([lanes[2] as i64, lanes[3] as i64]) + } + + #[doc(alias = "i64x2.extend_high_i32x4_u")] + pub const fn i64x2_extend_high_i32x4_u(self) -> Self { + let lanes = self.as_u32x4(); + Self::from_u64x2([lanes[2] as u64, lanes[3] as u64]) + } + + #[doc(alias = "i16x8.extmul_low_i8x16_s")] + pub const fn i16x8_extmul_low_i8x16_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = (a[i] as i16).wrapping_mul(b[i] as i16); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.extmul_low_i8x16_u")] + pub const fn i16x8_extmul_low_i8x16_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = (a[i] as u16) * (b[i] as u16); + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i16x8.extmul_high_i8x16_s")] + pub const fn i16x8_extmul_high_i8x16_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = (a[i + 8] as i16).wrapping_mul(b[i + 8] as i16); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.extmul_high_i8x16_u")] + pub const fn i16x8_extmul_high_i8x16_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = (a[i + 8] as u16) * (b[i + 8] as u16); + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.extmul_low_i16x8_s")] + pub const fn i32x4_extmul_low_i16x8_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = (a[i] as i32).wrapping_mul(b[i] as i32); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i32x4.extmul_low_i16x8_u")] + pub const fn i32x4_extmul_low_i16x8_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u32; 4]; + let mut i = 0; + while i < 4 { + out[i] = (a[i] as u32) * (b[i] as u32); + i += 1; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i32x4.extmul_high_i16x8_s")] + pub const fn i32x4_extmul_high_i16x8_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = (a[i + 4] as i32).wrapping_mul(b[i + 4] as i32); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i32x4.extmul_high_i16x8_u")] + pub const fn i32x4_extmul_high_i16x8_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u32; 4]; + let mut i = 0; + while i < 4 { + out[i] = (a[i + 4] as u32) * (b[i + 4] as u32); + i += 1; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i64x2.extmul_low_i32x4_s")] + pub const fn i64x2_extmul_low_i32x4_s(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = (a[i] as i64).wrapping_mul(b[i] as i64); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i64x2.extmul_low_i32x4_u")] + pub const fn i64x2_extmul_low_i32x4_u(self, rhs: Self) -> Self { + let a = self.as_u32x4(); + let b = rhs.as_u32x4(); + let mut out = [0u64; 2]; + let mut i = 0; + while i < 2 { + out[i] = (a[i] as u64) * (b[i] as u64); + i += 1; + } + Self::from_u64x2(out) + } + + #[doc(alias = "i64x2.extmul_high_i32x4_s")] + pub const fn i64x2_extmul_high_i32x4_s(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = (a[i + 2] as i64).wrapping_mul(b[i + 2] as i64); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i64x2.extmul_high_i32x4_u")] + pub const fn i64x2_extmul_high_i32x4_u(self, rhs: Self) -> Self { + let a = self.as_u32x4(); + let b = rhs.as_u32x4(); + let mut out = [0u64; 2]; + let mut i = 0; + while i < 2 { + out[i] = (a[i + 2] as u64) * (b[i + 2] as u64); + i += 1; + } + Self::from_u64x2(out) + } + + #[doc(alias = "i16x8.q15mulr_sat_s")] + pub const fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + let r = ((a[i] as i32 * b[i] as i32) + 0x4000) >> 15; + out[i] = if r > i16::MAX as i32 { + i16::MAX + } else if r < i16::MIN as i32 { + i16::MIN + } else { + r as i16 + }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.dot_i16x8_s")] + pub const fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + Self::from_i32x4([ + (a[0] as i32).wrapping_mul(b[0] as i32).wrapping_add((a[1] as i32).wrapping_mul(b[1] as i32)), + (a[2] as i32).wrapping_mul(b[2] as i32).wrapping_add((a[3] as i32).wrapping_mul(b[3] as i32)), + (a[4] as i32).wrapping_mul(b[4] as i32).wrapping_add((a[5] as i32).wrapping_mul(b[5] as i32)), + (a[6] as i32).wrapping_mul(b[6] as i32).wrapping_add((a[7] as i32).wrapping_mul(b[7] as i32)), + ]) + } + + #[doc(alias = "i8x16.eq")] + pub const fn i8x16_eq(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] == b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.eq")] + pub const fn i16x8_eq(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] == b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.eq")] + pub const fn i32x4_eq(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] == b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.eq")] + pub const fn i64x2_eq(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] == b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.ne")] + pub const fn i8x16_ne(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] != b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.ne")] + pub const fn i16x8_ne(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] != b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.ne")] + pub const fn i32x4_ne(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] != b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.ne")] + pub const fn i64x2_ne(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] != b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.lt_s")] + pub const fn i8x16_lt_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.lt_s")] + pub const fn i16x8_lt_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.lt_s")] + pub const fn i32x4_lt_s(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.lt_s")] + pub const fn i64x2_lt_s(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.lt_u")] + pub const fn i8x16_lt_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.lt_u")] + pub const fn i16x8_lt_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.lt_u")] + pub const fn i32x4_lt_u(self, rhs: Self) -> Self { + let a = self.as_u32x4(); + let b = rhs.as_u32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.gt_s")] + pub const fn i8x16_gt_s(self, rhs: Self) -> Self { + rhs.i8x16_lt_s(self) + } + + #[doc(alias = "i16x8.gt_s")] + pub const fn i16x8_gt_s(self, rhs: Self) -> Self { + rhs.i16x8_lt_s(self) + } + + #[doc(alias = "i32x4.gt_s")] + pub const fn i32x4_gt_s(self, rhs: Self) -> Self { + rhs.i32x4_lt_s(self) + } + + #[doc(alias = "i64x2.gt_s")] + pub const fn i64x2_gt_s(self, rhs: Self) -> Self { + rhs.i64x2_lt_s(self) + } + + #[doc(alias = "i8x16.gt_u")] + pub const fn i8x16_gt_u(self, rhs: Self) -> Self { + rhs.i8x16_lt_u(self) + } + + #[doc(alias = "i16x8.gt_u")] + pub const fn i16x8_gt_u(self, rhs: Self) -> Self { + rhs.i16x8_lt_u(self) + } + + #[doc(alias = "i32x4.gt_u")] + pub const fn i32x4_gt_u(self, rhs: Self) -> Self { + rhs.i32x4_lt_u(self) + } + + #[doc(alias = "i8x16.le_s")] + pub const fn i8x16_le_s(self, rhs: Self) -> Self { + rhs.i8x16_ge_s(self) + } + + #[doc(alias = "i16x8.le_s")] + pub const fn i16x8_le_s(self, rhs: Self) -> Self { + rhs.i16x8_ge_s(self) + } + + #[doc(alias = "i32x4.le_s")] + pub const fn i32x4_le_s(self, rhs: Self) -> Self { + rhs.i32x4_ge_s(self) + } + + #[doc(alias = "i64x2.le_s")] + pub const fn i64x2_le_s(self, rhs: Self) -> Self { + rhs.i64x2_ge_s(self) + } + + #[doc(alias = "i8x16.le_u")] + pub const fn i8x16_le_u(self, rhs: Self) -> Self { + rhs.i8x16_ge_u(self) + } + + #[doc(alias = "i16x8.le_u")] + pub const fn i16x8_le_u(self, rhs: Self) -> Self { + rhs.i16x8_ge_u(self) + } + + #[doc(alias = "i32x4.le_u")] + pub const fn i32x4_le_u(self, rhs: Self) -> Self { + rhs.i32x4_ge_u(self) + } + + #[doc(alias = "i8x16.ge_s")] + pub const fn i8x16_ge_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.ge_s")] + pub const fn i16x8_ge_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.ge_s")] + pub const fn i32x4_ge_s(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.ge_s")] + pub const fn i64x2_ge_s(self, rhs: Self) -> Self { + let a = self.as_i64x2(); + let b = rhs.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.ge_u")] + pub const fn i8x16_ge_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.ge_u")] + pub const fn i16x8_ge_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.ge_u")] + pub const fn i32x4_ge_u(self, rhs: Self) -> Self { + let a = self.as_u32x4(); + let b = rhs.as_u32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.abs")] + pub const fn i8x16_abs(self) -> Self { + let a = self.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].wrapping_abs(); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.abs")] + pub const fn i16x8_abs(self) -> Self { + let a = self.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].wrapping_abs(); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.abs")] + pub const fn i32x4_abs(self) -> Self { + let a = self.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = a[i].wrapping_abs(); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.abs")] + pub const fn i64x2_abs(self) -> Self { + let a = self.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = a[i].wrapping_abs(); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.neg")] + pub const fn i8x16_neg(self) -> Self { + let a = self.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = a[i].wrapping_neg(); + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.neg")] + pub const fn i16x8_neg(self) -> Self { + let a = self.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = a[i].wrapping_neg(); + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.neg")] + pub const fn i32x4_neg(self) -> Self { + let a = self.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = a[i].wrapping_neg(); + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i64x2.neg")] + pub const fn i64x2_neg(self) -> Self { + let a = self.as_i64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = a[i].wrapping_neg(); + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "i8x16.min_s")] + pub const fn i8x16_min_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] < b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.min_s")] + pub const fn i16x8_min_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] < b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.min_s")] + pub const fn i32x4_min_s(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] < b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.min_u")] + pub const fn i8x16_min_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] < b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.min_u")] + pub const fn i16x8_min_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] < b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.min_u")] + pub const fn i32x4_min_u(self, rhs: Self) -> Self { + let a = self.as_u32x4(); + let b = rhs.as_u32x4(); + let mut out = [0u32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] < b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i8x16.max_s")] + pub const fn i8x16_max_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_i8x16(out) + } + + #[doc(alias = "i16x8.max_s")] + pub const fn i16x8_max_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.max_s")] + pub const fn i32x4_max_s(self, rhs: Self) -> Self { + let a = self.as_i32x4(); + let b = rhs.as_i32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.max_u")] + pub const fn i8x16_max_u(self, rhs: Self) -> Self { + let a = self.as_u8x16(); + let b = rhs.as_u8x16(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.max_u")] + pub const fn i16x8_max_u(self, rhs: Self) -> Self { + let a = self.as_u16x8(); + let b = rhs.as_u16x8(); + let mut out = [0u16; 8]; + let mut i = 0; + while i < 8 { + out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.max_u")] + pub const fn i32x4_max_u(self, rhs: Self) -> Self { + let a = self.as_u32x4(); + let b = rhs.as_u32x4(); + let mut out = [0u32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + i += 1; + } + Self::from_u32x4(out) + } + + #[doc(alias = "f32x4.eq")] + pub fn f32x4_eq(self, rhs: Self) -> Self { + let a = self.as_f32x4(); + let b = rhs.as_f32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] == b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "f64x2.eq")] + pub fn f64x2_eq(self, rhs: Self) -> Self { + let a = self.as_f64x2(); + let b = rhs.as_f64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] == b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "f32x4.ne")] + pub fn f32x4_ne(self, rhs: Self) -> Self { + let a = self.as_f32x4(); + let b = rhs.as_f32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] != b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "f64x2.ne")] + pub fn f64x2_ne(self, rhs: Self) -> Self { + let a = self.as_f64x2(); + let b = rhs.as_f64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] != b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "f32x4.lt")] + pub fn f32x4_lt(self, rhs: Self) -> Self { + let a = self.as_f32x4(); + let b = rhs.as_f32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "f64x2.lt")] + pub fn f64x2_lt(self, rhs: Self) -> Self { + let a = self.as_f64x2(); + let b = rhs.as_f64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] < b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "f32x4.gt")] + pub fn f32x4_gt(self, rhs: Self) -> Self { + rhs.f32x4_lt(self) + } + + #[doc(alias = "f64x2.gt")] + pub fn f64x2_gt(self, rhs: Self) -> Self { + rhs.f64x2_lt(self) + } + + #[doc(alias = "f32x4.le")] + pub fn f32x4_le(self, rhs: Self) -> Self { + let a = self.as_f32x4(); + let b = rhs.as_f32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] <= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "f64x2.le")] + pub fn f64x2_le(self, rhs: Self) -> Self { + let a = self.as_f64x2(); + let b = rhs.as_f64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] <= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "f32x4.ge")] + pub fn f32x4_ge(self, rhs: Self) -> Self { + let a = self.as_f32x4(); + let b = rhs.as_f32x4(); + let mut out = [0i32; 4]; + let mut i = 0; + while i < 4 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i32x4(out) + } + + #[doc(alias = "f64x2.ge")] + pub fn f64x2_ge(self, rhs: Self) -> Self { + let a = self.as_f64x2(); + let b = rhs.as_f64x2(); + let mut out = [0i64; 2]; + let mut i = 0; + while i < 2 { + out[i] = if a[i] >= b[i] { -1 } else { 0 }; + i += 1; + } + Self::from_i64x2(out) + } + + #[doc(alias = "f32x4.ceil")] + pub fn f32x4_ceil(self) -> Self { + self.map_f32x4(f32::ceil) + } + + #[doc(alias = "f64x2.ceil")] + pub fn f64x2_ceil(self) -> Self { + self.map_f64x2(f64::ceil) + } + + #[doc(alias = "f32x4.floor")] + pub fn f32x4_floor(self) -> Self { + self.map_f32x4(f32::floor) + } + + #[doc(alias = "f64x2.floor")] + pub fn f64x2_floor(self) -> Self { + self.map_f64x2(f64::floor) + } + + #[doc(alias = "f32x4.trunc")] + pub fn f32x4_trunc(self) -> Self { + self.map_f32x4(f32::trunc) + } + + #[doc(alias = "f64x2.trunc")] + pub fn f64x2_trunc(self) -> Self { + self.map_f64x2(f64::trunc) + } + + #[doc(alias = "f32x4.nearest")] + pub fn f32x4_nearest(self) -> Self { + self.map_f32x4(TinywasmFloatExt::tw_nearest) + } + + #[doc(alias = "f64x2.nearest")] + pub fn f64x2_nearest(self) -> Self { + self.map_f64x2(TinywasmFloatExt::tw_nearest) + } + + #[doc(alias = "f32x4.abs")] + pub fn f32x4_abs(self) -> Self { + self.map_f32x4(f32::abs) + } + + #[doc(alias = "f64x2.abs")] + pub fn f64x2_abs(self) -> Self { + self.map_f64x2(f64::abs) + } + + #[doc(alias = "f32x4.neg")] + pub fn f32x4_neg(self) -> Self { + self.map_f32x4(|x| -x) + } + + #[doc(alias = "f64x2.neg")] + pub fn f64x2_neg(self) -> Self { + self.map_f64x2(|x| -x) + } + + #[doc(alias = "f32x4.sqrt")] + pub fn f32x4_sqrt(self) -> Self { + self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.sqrt())) + } + + #[doc(alias = "f64x2.sqrt")] + pub fn f64x2_sqrt(self) -> Self { + self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.sqrt())) + } + + #[doc(alias = "f32x4.add")] + pub fn f32x4_add(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a + b)) + } + + #[doc(alias = "f64x2.add")] + pub fn f64x2_add(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a + b)) + } + + #[doc(alias = "f32x4.sub")] + pub fn f32x4_sub(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a - b)) + } + + #[doc(alias = "f64x2.sub")] + pub fn f64x2_sub(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a - b)) + } + + #[doc(alias = "f32x4.mul")] + pub fn f32x4_mul(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a * b)) + } + + #[doc(alias = "f64x2.mul")] + pub fn f64x2_mul(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a * b)) + } + + #[doc(alias = "f32x4.div")] + pub fn f32x4_div(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a / b)) + } + + #[doc(alias = "f64x2.div")] + pub fn f64x2_div(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a / b)) + } + + #[doc(alias = "f32x4.min")] + pub fn f32x4_min(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, TinywasmFloatExt::tw_minimum) + } + + #[doc(alias = "f64x2.min")] + pub fn f64x2_min(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, TinywasmFloatExt::tw_minimum) + } + + #[doc(alias = "f32x4.max")] + pub fn f32x4_max(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, TinywasmFloatExt::tw_maximum) + } + + #[doc(alias = "f64x2.max")] + pub fn f64x2_max(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, TinywasmFloatExt::tw_maximum) + } + + #[doc(alias = "f32x4.pmin")] + pub fn f32x4_pmin(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, |a, b| if b < a { b } else { a }) + } + + #[doc(alias = "f64x2.pmin")] + pub fn f64x2_pmin(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, |a, b| if b < a { b } else { a }) + } + + #[doc(alias = "f32x4.pmax")] + pub fn f32x4_pmax(self, rhs: Self) -> Self { + self.zip_f32x4(rhs, |a, b| if b > a { b } else { a }) + } + + #[doc(alias = "f64x2.pmax")] + pub fn f64x2_pmax(self, rhs: Self) -> Self { + self.zip_f64x2(rhs, |a, b| if b > a { b } else { a }) + } + + #[doc(alias = "i32x4.trunc_sat_f32x4_s")] + pub fn i32x4_trunc_sat_f32x4_s(self) -> Self { + let v = self.as_f32x4(); + Self::from_i32x4([ + trunc_sat_f32_to_i32(v[0]), + trunc_sat_f32_to_i32(v[1]), + trunc_sat_f32_to_i32(v[2]), + trunc_sat_f32_to_i32(v[3]), + ]) + } + + #[doc(alias = "i32x4.trunc_sat_f32x4_u")] + pub fn i32x4_trunc_sat_f32x4_u(self) -> Self { + let v = self.as_f32x4(); + Self::from_u32x4([ + trunc_sat_f32_to_u32(v[0]), + trunc_sat_f32_to_u32(v[1]), + trunc_sat_f32_to_u32(v[2]), + trunc_sat_f32_to_u32(v[3]), + ]) + } + + #[doc(alias = "i32x4.trunc_sat_f64x2_s_zero")] + pub fn i32x4_trunc_sat_f64x2_s_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_i32x4([trunc_sat_f64_to_i32(v[0]), trunc_sat_f64_to_i32(v[1]), 0, 0]) + } + + #[doc(alias = "i32x4.trunc_sat_f64x2_u_zero")] + pub fn i32x4_trunc_sat_f64x2_u_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_u32x4([trunc_sat_f64_to_u32(v[0]), trunc_sat_f64_to_u32(v[1]), 0, 0]) + } + + #[doc(alias = "f32x4.convert_i32x4_s")] + pub fn f32x4_convert_i32x4_s(self) -> Self { + let v = self.as_i32x4(); + Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) + } + + #[doc(alias = "f32x4.convert_i32x4_u")] + pub fn f32x4_convert_i32x4_u(self) -> Self { + let v = self.as_u32x4(); + Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) + } + + #[doc(alias = "f64x2.convert_low_i32x4_s")] + pub fn f64x2_convert_low_i32x4_s(self) -> Self { + let v = self.as_i32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "f64x2.convert_low_i32x4_u")] + pub fn f64x2_convert_low_i32x4_u(self) -> Self { + let v = self.as_u32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "f32x4.demote_f64x2_zero")] + pub fn f32x4_demote_f64x2_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_f32x4([v[0] as f32, v[1] as f32, 0.0, 0.0]) + } + + #[doc(alias = "f64x2.promote_low_f32x4")] + pub fn f64x2_promote_low_f32x4(self) -> Self { + let v = self.as_f32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + pub const fn splat_i16(src: i16) -> Self { + let mut result_bytes = [0u8; 16]; + let bytes = src.to_le_bytes(); + let mut i = 0; + while i < 8 { + result_bytes[i * 2] = bytes[0]; + result_bytes[i * 2 + 1] = bytes[1]; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_i32(src: i32) -> Self { + let mut result_bytes = [0u8; 16]; + let bytes = src.to_le_bytes(); + let mut i = 0; + while i < 4 { + result_bytes[i * 4] = bytes[0]; + result_bytes[i * 4 + 1] = bytes[1]; + result_bytes[i * 4 + 2] = bytes[2]; + result_bytes[i * 4 + 3] = bytes[3]; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_i64(src: i64) -> Self { + let mut result_bytes = [0u8; 16]; + let bytes = src.to_le_bytes(); + let mut i = 0; + while i < 2 { + result_bytes[i * 8] = bytes[0]; + result_bytes[i * 8 + 1] = bytes[1]; + result_bytes[i * 8 + 2] = bytes[2]; + result_bytes[i * 8 + 3] = bytes[3]; + result_bytes[i * 8 + 4] = bytes[4]; + result_bytes[i * 8 + 5] = bytes[5]; + result_bytes[i * 8 + 6] = bytes[6]; + result_bytes[i * 8 + 7] = bytes[7]; + i += 1; + } + Self::from_le_bytes(result_bytes) + } + + pub const fn splat_f32(src: f32) -> Self { + Self::splat_i32(src.to_bits() as i32) + } + + pub const fn splat_f64(src: f64) -> Self { + Self::splat_i64(src.to_bits() as i64) + } + + pub const fn extract_lane_i8(self, lane: u8) -> i8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] as i8 + } + + pub const fn extract_lane_u8(self, lane: u8) -> u8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] + } + + pub const fn extract_lane_i16(self, lane: u8) -> i16 { + debug_assert!(lane < 8); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 2; + i16::from_le_bytes([bytes[start], bytes[start + 1]]) + } + + pub const fn extract_lane_u16(self, lane: u8) -> u16 { + debug_assert!(lane < 8); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 2; + u16::from_le_bytes([bytes[start], bytes[start + 1]]) + } + + pub const fn extract_lane_i32(self, lane: u8) -> i32 { + debug_assert!(lane < 4); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 4; + i32::from_le_bytes([bytes[start], bytes[start + 1], bytes[start + 2], bytes[start + 3]]) + } + + pub const fn extract_lane_i64(self, lane: u8) -> i64 { + debug_assert!(lane < 2); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + let start = lane * 8; + i64::from_le_bytes([ + bytes[start], + bytes[start + 1], + bytes[start + 2], + bytes[start + 3], + bytes[start + 4], + bytes[start + 5], + bytes[start + 6], + bytes[start + 7], + ]) + } + + pub const fn extract_lane_f32(self, lane: u8) -> f32 { + f32::from_bits(self.extract_lane_i32(lane) as u32) + } + + pub const fn extract_lane_f64(self, lane: u8) -> f64 { + f64::from_bits(self.extract_lane_i64(lane) as u64) + } +} + +impl From for i128 { + fn from(val: Value128) -> Self { + val.0 + } +} + +impl From for Value128 { + fn from(value: i128) -> Self { + Self(value) + } +} + +impl core::ops::Not for Value128 { + type Output = Self; + fn not(self) -> Self::Output { + Self(!self.0) + } +} + +impl core::ops::BitAnd for Value128 { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } +} + +impl core::ops::BitOr for Value128 { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) } } @@ -395,3 +2559,51 @@ impl core::ops::BitXor for Value128 { Self(self.0 ^ rhs.0) } } + +#[inline] +fn trunc_sat_f32_to_i32(v: f32) -> i32 { + if v.is_nan() { + 0 + } else if v <= -2147483904.0_f32 { + i32::MIN + } else if v >= 2147483648.0_f32 { + i32::MAX + } else { + v.trunc() as i32 + } +} + +#[inline] +fn trunc_sat_f32_to_u32(v: f32) -> u32 { + if v.is_nan() || v <= -1.0_f32 { + 0 + } else if v >= 4294967296.0_f32 { + u32::MAX + } else { + v.trunc() as u32 + } +} + +#[inline] +fn trunc_sat_f64_to_i32(v: f64) -> i32 { + if v.is_nan() { + 0 + } else if v <= -2147483649.0_f64 { + i32::MIN + } else if v >= 2147483648.0_f64 { + i32::MAX + } else { + v.trunc() as i32 + } +} + +#[inline] +fn trunc_sat_f64_to_u32(v: f64) -> u32 { + if v.is_nan() || v <= -1.0_f64 { + 0 + } else if v >= 4294967296.0_f64 { + u32::MAX + } else { + v.trunc() as u32 + } +} diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 88982120..65c43619 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -2,7 +2,7 @@ use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; -use crate::{Error, Result, cold, interpreter::Value128, log}; +use crate::{cold, interpreter::Value128, log, Error, Result}; /// A WebAssembly Memory Instance /// diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv index 5603ed09..96d97966 100644 --- a/crates/tinywasm/tests/generated/wasm-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -1,2 +1,2 @@ 0.8.0,1300,24679,[{"name":"simd_address.wast","passed":4,"failed":45},{"name":"simd_align.wast","passed":46,"failed":54},{"name":"simd_bit_shift.wast","passed":39,"failed":213},{"name":"simd_bitwise.wast","passed":28,"failed":141},{"name":"simd_boolean.wast","passed":16,"failed":261},{"name":"simd_const.wast","passed":301,"failed":456},{"name":"simd_conversions.wast","passed":48,"failed":234},{"name":"simd_f32x4.wast","passed":16,"failed":774},{"name":"simd_f32x4_arith.wast","passed":16,"failed":1806},{"name":"simd_f32x4_cmp.wast","passed":24,"failed":2583},{"name":"simd_f32x4_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f32x4_rounding.wast","passed":24,"failed":177},{"name":"simd_f64x2.wast","passed":8,"failed":795},{"name":"simd_f64x2_arith.wast","passed":16,"failed":1809},{"name":"simd_f64x2_cmp.wast","passed":24,"failed":2661},{"name":"simd_f64x2_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f64x2_rounding.wast","passed":24,"failed":177},{"name":"simd_i16x8_arith.wast","passed":11,"failed":183},{"name":"simd_i16x8_arith2.wast","passed":19,"failed":153},{"name":"simd_i16x8_cmp.wast","passed":30,"failed":435},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":4,"failed":17},{"name":"simd_i16x8_extmul_i8x16.wast","passed":12,"failed":105},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":3,"failed":27},{"name":"simd_i16x8_sat_arith.wast","passed":16,"failed":206},{"name":"simd_i32x4_arith.wast","passed":11,"failed":183},{"name":"simd_i32x4_arith2.wast","passed":26,"failed":123},{"name":"simd_i32x4_cmp.wast","passed":40,"failed":435},{"name":"simd_i32x4_dot_i16x8.wast","passed":3,"failed":27},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":4,"failed":17},{"name":"simd_i32x4_extmul_i16x8.wast","passed":12,"failed":105},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":4,"failed":103},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":4,"failed":103},{"name":"simd_i64x2_arith.wast","passed":11,"failed":189},{"name":"simd_i64x2_arith2.wast","passed":2,"failed":23},{"name":"simd_i64x2_cmp.wast","passed":10,"failed":103},{"name":"simd_i64x2_extmul_i32x4.wast","passed":12,"failed":105},{"name":"simd_i8x16_arith.wast","passed":8,"failed":123},{"name":"simd_i8x16_arith2.wast","passed":25,"failed":186},{"name":"simd_i8x16_cmp.wast","passed":30,"failed":415},{"name":"simd_i8x16_sat_arith.wast","passed":24,"failed":190},{"name":"simd_int_to_int_extend.wast","passed":24,"failed":229},{"name":"simd_lane.wast","passed":189,"failed":286},{"name":"simd_linking.wast","passed":0,"failed":3},{"name":"simd_load.wast","passed":8,"failed":31},{"name":"simd_load16_lane.wast","passed":3,"failed":33},{"name":"simd_load32_lane.wast","passed":3,"failed":21},{"name":"simd_load64_lane.wast","passed":3,"failed":13},{"name":"simd_load8_lane.wast","passed":3,"failed":49},{"name":"simd_load_extend.wast","passed":18,"failed":86},{"name":"simd_load_splat.wast","passed":12,"failed":114},{"name":"simd_load_zero.wast","passed":10,"failed":29},{"name":"simd_splat.wast","passed":23,"failed":162},{"name":"simd_store.wast","passed":9,"failed":19},{"name":"simd_store16_lane.wast","passed":3,"failed":33},{"name":"simd_store32_lane.wast","passed":3,"failed":21},{"name":"simd_store64_lane.wast","passed":3,"failed":13},{"name":"simd_store8_lane.wast","passed":3,"failed":49}] -0.9.0-alpha.0,2867,23122,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":41,"failed":211},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":139,"failed":138},{"name":"simd_const.wast","passed":755,"failed":2},{"name":"simd_conversions.wast","passed":50,"failed":232},{"name":"simd_f32x4.wast","passed":18,"failed":772},{"name":"simd_f32x4_arith.wast","passed":19,"failed":1803},{"name":"simd_f32x4_cmp.wast","passed":26,"failed":2581},{"name":"simd_f32x4_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f32x4_rounding.wast","passed":25,"failed":176},{"name":"simd_f64x2.wast","passed":10,"failed":793},{"name":"simd_f64x2_arith.wast","passed":19,"failed":1806},{"name":"simd_f64x2_cmp.wast","passed":26,"failed":2659},{"name":"simd_f64x2_pmin_pmax.wast","passed":15,"failed":3872},{"name":"simd_f64x2_rounding.wast","passed":25,"failed":176},{"name":"simd_i16x8_arith.wast","passed":13,"failed":181},{"name":"simd_i16x8_arith2.wast","passed":21,"failed":151},{"name":"simd_i16x8_cmp.wast","passed":32,"failed":433},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":5,"failed":16},{"name":"simd_i16x8_extmul_i8x16.wast","passed":13,"failed":104},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":18,"failed":204},{"name":"simd_i32x4_arith.wast","passed":13,"failed":181},{"name":"simd_i32x4_arith2.wast","passed":28,"failed":121},{"name":"simd_i32x4_cmp.wast","passed":42,"failed":433},{"name":"simd_i32x4_dot_i16x8.wast","passed":14,"failed":18},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":5,"failed":16},{"name":"simd_i32x4_extmul_i16x8.wast","passed":13,"failed":104},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":5,"failed":102},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":5,"failed":102},{"name":"simd_i64x2_arith.wast","passed":13,"failed":187},{"name":"simd_i64x2_arith2.wast","passed":4,"failed":21},{"name":"simd_i64x2_cmp.wast","passed":11,"failed":102},{"name":"simd_i64x2_extmul_i32x4.wast","passed":13,"failed":104},{"name":"simd_i8x16_arith.wast","passed":10,"failed":121},{"name":"simd_i8x16_arith2.wast","passed":27,"failed":184},{"name":"simd_i8x16_cmp.wast","passed":32,"failed":413},{"name":"simd_i8x16_sat_arith.wast","passed":26,"failed":188},{"name":"simd_int_to_int_extend.wast","passed":25,"failed":228},{"name":"simd_lane.wast","passed":328,"failed":147},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":30,"failed":9},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":36,"failed":68},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":158,"failed":27},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":4,"failed":32},{"name":"simd_store32_lane.wast","passed":4,"failed":20},{"name":"simd_store64_lane.wast","passed":4,"failed":12},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] +0.9.0-alpha.0,25964,25,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":757,"failed":0},{"name":"simd_conversions.wast","passed":276,"failed":6},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":192,"failed":9},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1824,"failed":1},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":192,"failed":9},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":172,"failed":0},{"name":"simd_i16x8_cmp.wast","passed":465,"failed":0},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":21,"failed":0},{"name":"simd_i16x8_extmul_i8x16.wast","passed":117,"failed":0},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":475,"failed":0},{"name":"simd_i32x4_dot_i16x8.wast","passed":32,"failed":0},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":21,"failed":0},{"name":"simd_i32x4_extmul_i16x8.wast","passed":117,"failed":0},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":107,"failed":0},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":107,"failed":0},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":117,"failed":0},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":211,"failed":0},{"name":"simd_i8x16_cmp.wast","passed":445,"failed":0},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":253,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":39,"failed":0},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":104,"failed":0},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":185,"failed":0},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] From f8f2a71018c8829eb50e818c83d9914a7bcdcd63 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 19 Mar 2026 22:03:05 +0100 Subject: [PATCH 07/47] fix: canonicalize simd nans Signed-off-by: Henry --- .../src/interpreter/stack/value_stack.rs | 2 +- crates/tinywasm/src/interpreter/value128.rs | 26 +++++---- crates/tinywasm/src/store/memory.rs | 2 +- crates/tinywasm/tests/generated/wasm-simd.csv | 2 +- crates/types/src/value.rs | 58 ++++++++++++++++++- 5 files changed, 74 insertions(+), 16 deletions(-) diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 2850e91a..016b8239 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; -use crate::{interpreter::*, Result, StackConfig}; +use crate::{Result, StackConfig, interpreter::*}; use super::Locals; diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index 6b807c9a..60e709cd 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -6,20 +6,26 @@ pub struct Value128(i128); impl Value128 { #[inline] fn canonicalize_simd_f32_nan(x: f32) -> f32 { + #[cfg(feature = "canonicalize_nans")] if x.is_nan() { - f32::from_bits(0x7fc0_0000) + f32::NAN } else { x } + #[cfg(not(feature = "canonicalize_nans"))] + x } #[inline] fn canonicalize_simd_f64_nan(x: f64) -> f64 { + #[cfg(feature = "canonicalize_nans")] if x.is_nan() { - f64::from_bits(0x7ff8_0000_0000_0000) + f64::NAN } else { x } + #[cfg(not(feature = "canonicalize_nans"))] + x } const fn saturate_i16_to_i8(x: i16) -> i8 { @@ -2186,42 +2192,42 @@ impl Value128 { #[doc(alias = "f32x4.ceil")] pub fn f32x4_ceil(self) -> Self { - self.map_f32x4(f32::ceil) + self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.ceil())) } #[doc(alias = "f64x2.ceil")] pub fn f64x2_ceil(self) -> Self { - self.map_f64x2(f64::ceil) + self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.ceil())) } #[doc(alias = "f32x4.floor")] pub fn f32x4_floor(self) -> Self { - self.map_f32x4(f32::floor) + self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.floor())) } #[doc(alias = "f64x2.floor")] pub fn f64x2_floor(self) -> Self { - self.map_f64x2(f64::floor) + self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.floor())) } #[doc(alias = "f32x4.trunc")] pub fn f32x4_trunc(self) -> Self { - self.map_f32x4(f32::trunc) + self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.trunc())) } #[doc(alias = "f64x2.trunc")] pub fn f64x2_trunc(self) -> Self { - self.map_f64x2(f64::trunc) + self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.trunc())) } #[doc(alias = "f32x4.nearest")] pub fn f32x4_nearest(self) -> Self { - self.map_f32x4(TinywasmFloatExt::tw_nearest) + self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(TinywasmFloatExt::tw_nearest(x))) } #[doc(alias = "f64x2.nearest")] pub fn f64x2_nearest(self) -> Self { - self.map_f64x2(TinywasmFloatExt::tw_nearest) + self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(TinywasmFloatExt::tw_nearest(x))) } #[doc(alias = "f32x4.abs")] diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 65c43619..88982120 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -2,7 +2,7 @@ use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; -use crate::{cold, interpreter::Value128, log, Error, Result}; +use crate::{Error, Result, cold, interpreter::Value128, log}; /// A WebAssembly Memory Instance /// diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv index 96d97966..b05959cb 100644 --- a/crates/tinywasm/tests/generated/wasm-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -1,2 +1,2 @@ 0.8.0,1300,24679,[{"name":"simd_address.wast","passed":4,"failed":45},{"name":"simd_align.wast","passed":46,"failed":54},{"name":"simd_bit_shift.wast","passed":39,"failed":213},{"name":"simd_bitwise.wast","passed":28,"failed":141},{"name":"simd_boolean.wast","passed":16,"failed":261},{"name":"simd_const.wast","passed":301,"failed":456},{"name":"simd_conversions.wast","passed":48,"failed":234},{"name":"simd_f32x4.wast","passed":16,"failed":774},{"name":"simd_f32x4_arith.wast","passed":16,"failed":1806},{"name":"simd_f32x4_cmp.wast","passed":24,"failed":2583},{"name":"simd_f32x4_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f32x4_rounding.wast","passed":24,"failed":177},{"name":"simd_f64x2.wast","passed":8,"failed":795},{"name":"simd_f64x2_arith.wast","passed":16,"failed":1809},{"name":"simd_f64x2_cmp.wast","passed":24,"failed":2661},{"name":"simd_f64x2_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f64x2_rounding.wast","passed":24,"failed":177},{"name":"simd_i16x8_arith.wast","passed":11,"failed":183},{"name":"simd_i16x8_arith2.wast","passed":19,"failed":153},{"name":"simd_i16x8_cmp.wast","passed":30,"failed":435},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":4,"failed":17},{"name":"simd_i16x8_extmul_i8x16.wast","passed":12,"failed":105},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":3,"failed":27},{"name":"simd_i16x8_sat_arith.wast","passed":16,"failed":206},{"name":"simd_i32x4_arith.wast","passed":11,"failed":183},{"name":"simd_i32x4_arith2.wast","passed":26,"failed":123},{"name":"simd_i32x4_cmp.wast","passed":40,"failed":435},{"name":"simd_i32x4_dot_i16x8.wast","passed":3,"failed":27},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":4,"failed":17},{"name":"simd_i32x4_extmul_i16x8.wast","passed":12,"failed":105},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":4,"failed":103},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":4,"failed":103},{"name":"simd_i64x2_arith.wast","passed":11,"failed":189},{"name":"simd_i64x2_arith2.wast","passed":2,"failed":23},{"name":"simd_i64x2_cmp.wast","passed":10,"failed":103},{"name":"simd_i64x2_extmul_i32x4.wast","passed":12,"failed":105},{"name":"simd_i8x16_arith.wast","passed":8,"failed":123},{"name":"simd_i8x16_arith2.wast","passed":25,"failed":186},{"name":"simd_i8x16_cmp.wast","passed":30,"failed":415},{"name":"simd_i8x16_sat_arith.wast","passed":24,"failed":190},{"name":"simd_int_to_int_extend.wast","passed":24,"failed":229},{"name":"simd_lane.wast","passed":189,"failed":286},{"name":"simd_linking.wast","passed":0,"failed":3},{"name":"simd_load.wast","passed":8,"failed":31},{"name":"simd_load16_lane.wast","passed":3,"failed":33},{"name":"simd_load32_lane.wast","passed":3,"failed":21},{"name":"simd_load64_lane.wast","passed":3,"failed":13},{"name":"simd_load8_lane.wast","passed":3,"failed":49},{"name":"simd_load_extend.wast","passed":18,"failed":86},{"name":"simd_load_splat.wast","passed":12,"failed":114},{"name":"simd_load_zero.wast","passed":10,"failed":29},{"name":"simd_splat.wast","passed":23,"failed":162},{"name":"simd_store.wast","passed":9,"failed":19},{"name":"simd_store16_lane.wast","passed":3,"failed":33},{"name":"simd_store32_lane.wast","passed":3,"failed":21},{"name":"simd_store64_lane.wast","passed":3,"failed":13},{"name":"simd_store8_lane.wast","passed":3,"failed":49}] -0.9.0-alpha.0,25964,25,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":757,"failed":0},{"name":"simd_conversions.wast","passed":276,"failed":6},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":192,"failed":9},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1824,"failed":1},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":192,"failed":9},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":172,"failed":0},{"name":"simd_i16x8_cmp.wast","passed":465,"failed":0},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":21,"failed":0},{"name":"simd_i16x8_extmul_i8x16.wast","passed":117,"failed":0},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":475,"failed":0},{"name":"simd_i32x4_dot_i16x8.wast","passed":32,"failed":0},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":21,"failed":0},{"name":"simd_i32x4_extmul_i16x8.wast","passed":117,"failed":0},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":107,"failed":0},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":107,"failed":0},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":117,"failed":0},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":211,"failed":0},{"name":"simd_i8x16_cmp.wast","passed":445,"failed":0},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":253,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":39,"failed":0},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":104,"failed":0},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":185,"failed":0},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] +0.9.0-alpha.0,25989,0,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":757,"failed":0},{"name":"simd_conversions.wast","passed":282,"failed":0},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":201,"failed":0},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1825,"failed":0},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":201,"failed":0},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":172,"failed":0},{"name":"simd_i16x8_cmp.wast","passed":465,"failed":0},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":21,"failed":0},{"name":"simd_i16x8_extmul_i8x16.wast","passed":117,"failed":0},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":475,"failed":0},{"name":"simd_i32x4_dot_i16x8.wast","passed":32,"failed":0},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":21,"failed":0},{"name":"simd_i32x4_extmul_i16x8.wast","passed":117,"failed":0},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":107,"failed":0},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":107,"failed":0},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":117,"failed":0},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":211,"failed":0},{"name":"simd_i8x16_cmp.wast","passed":445,"failed":0},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":253,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":39,"failed":0},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":104,"failed":0},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":185,"failed":0},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 22bb4eb7..aa9e8c54 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -138,19 +138,23 @@ impl WasmValue { match (self, other) { (Self::I32(a), Self::I32(b)) => a == b, (Self::I64(a), Self::I64(b)) => a == b, - (Self::V128(a), Self::V128(b)) => a == b, + (Self::V128(a), Self::V128(b)) => { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + a_bytes == b_bytes || Self::v128_nan_eq(a_bytes, b_bytes) + } (Self::RefExtern(addr), Self::RefExtern(addr2)) => addr == addr2, (Self::RefFunc(addr), Self::RefFunc(addr2)) => addr == addr2, (Self::F32(a), Self::F32(b)) => { if a.is_nan() && b.is_nan() { - true // Both are NaN, treat them as equal + true } else { a.to_bits() == b.to_bits() } } (Self::F64(a), Self::F64(b)) => { if a.is_nan() && b.is_nan() { - true // Both are NaN, treat them as equal + true } else { a.to_bits() == b.to_bits() } @@ -159,6 +163,54 @@ impl WasmValue { } } + fn v128_nan_eq(a: [u8; 16], b: [u8; 16]) -> bool { + let a_f32x4: [f32; 4] = [ + f32::from_le_bytes([a[0], a[1], a[2], a[3]]), + f32::from_le_bytes([a[4], a[5], a[6], a[7]]), + f32::from_le_bytes([a[8], a[9], a[10], a[11]]), + f32::from_le_bytes([a[12], a[13], a[14], a[15]]), + ]; + let b_f32x4: [f32; 4] = [ + f32::from_le_bytes([b[0], b[1], b[2], b[3]]), + f32::from_le_bytes([b[4], b[5], b[6], b[7]]), + f32::from_le_bytes([b[8], b[9], b[10], b[11]]), + f32::from_le_bytes([b[12], b[13], b[14], b[15]]), + ]; + + let all_nan_match = a_f32x4.iter().zip(b_f32x4.iter()).all(|(x, y)| { + if x.is_nan() && y.is_nan() { + true + } else if x.is_nan() || y.is_nan() { + false + } else { + x.to_bits() == y.to_bits() + } + }); + + if all_nan_match && a_f32x4.iter().any(|x| x.is_nan()) { + return true; + } + + let a_f64x2: [f64; 2] = [ + f64::from_le_bytes([a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]]), + f64::from_le_bytes([a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]]), + ]; + let b_f64x2: [f64; 2] = [ + f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), + f64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]), + ]; + + a_f64x2.iter().zip(b_f64x2.iter()).all(|(x, y)| { + if x.is_nan() && y.is_nan() { + true + } else if x.is_nan() || y.is_nan() { + false + } else { + x.to_bits() == y.to_bits() + } + }) && a_f64x2.iter().any(|x| x.is_nan()) + } + #[doc(hidden)] pub fn as_i32(&self) -> Option { match self { From 02a5650a7eda96d08924ec3617d60730e920c467 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 19 Mar 2026 22:24:56 +0100 Subject: [PATCH 08/47] chore: replace magic numbers with expressions Signed-off-by: Henry --- .../tinywasm/src/interpreter/num_helpers.rs | 23 +- crates/tinywasm/src/interpreter/value128.rs | 260 +++++++++--------- .../tests/generated/wasm-annotations.csv | 2 +- .../tests/generated/wasm-memory64.csv | 2 +- .../tests/generated/wasm-multi-memory.csv | 2 +- .../tinywasm/tests/test-wasm-annotations.rs | 1 - .../tinywasm/tests/test-wasm-multi-memory.rs | 1 - examples/rust/src/tinywasm.rs | 2 +- examples/rust/src/tinywasm_no_std.rs | 2 +- 9 files changed, 149 insertions(+), 146 deletions(-) diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 5b9e7b94..12d3022c 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -9,20 +9,17 @@ where /// Doing the actual conversion from float to int is a bit tricky, because /// we need to check for overflow. This macro generates the min/max values /// for a specific conversion, which are then used in the actual conversion. -/// Rust sadly doesn't have wrapping casts for floats yet, maybe never. -/// Alternatively, could be used for this but -/// it's not worth the dependency. -#[rustfmt::skip] +/// Rust sadly doesn't have wrapping casts for floats yet. +#[rustfmt::skip] macro_rules! float_min_max { - (f32, i32) => {(-2147483904.0_f32, 2147483648.0_f32)}; - (f64, i32) => {(-2147483649.0_f64, 2147483648.0_f64)}; - (f32, u32) => {(-1.0_f32, 4294967296.0_f32)}; // 2^32 - (f64, u32) => {(-1.0_f64, 4294967296.0_f64)}; // 2^32 - (f32, i64) => {(-9223373136366403584.0_f32, 9223372036854775808.0_f32)}; // 2^63 + 2^40 | 2^63 - (f64, i64) => {(-9223372036854777856.0_f64, 9223372036854775808.0_f64)}; // 2^63 + 2^40 | 2^63 - (f32, u64) => {(-1.0_f32, 18446744073709551616.0_f32)}; // 2^64 - (f64, u64) => {(-1.0_f64, 18446744073709551616.0_f64)}; // 2^64 - // other conversions are not allowed + (f32, i32) => {(i32::MIN as f32 - (1 << 8) as f32, i32::MAX as f32 + 1.0)}; // 2^8: f32 precision margin + (f64, i32) => {(i32::MIN as f64 - 1.0, i32::MAX as f64 + 1.0)}; + (f32, u32) => {(-1.0_f32, u32::MAX as f32 + 1.0)}; + (f64, u32) => {(-1.0_f64, u32::MAX as f64 + 1.0)}; + (f32, i64) => {(i64::MIN as f32 - (1i64 << 40) as f32, i64::MAX as f32 + 1.0)}; // 2^40: f32 has 23 mantissa bits + (f64, i64) => {(i64::MIN as f64 - (1i64 << 11) as f64, i64::MAX as f64 + 1.0)}; // 2^11: f64 precision margin + (f32, u64) => {(-1.0_f32, u64::MAX as f32 + 1.0)}; + (f64, u64) => {(-1.0_f64, u64::MAX as f64 + 1.0)}; ($from:ty, $to:ty) => {compile_error!("invalid float conversion")}; } diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index 60e709cd..e2fa4481 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -1,90 +1,12 @@ use super::num_helpers::TinywasmFloatExt; +#[cfg(not(feature = "std"))] +use super::no_std_floats::NoStdFloatExt; + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Value128(i128); impl Value128 { - #[inline] - fn canonicalize_simd_f32_nan(x: f32) -> f32 { - #[cfg(feature = "canonicalize_nans")] - if x.is_nan() { - f32::NAN - } else { - x - } - #[cfg(not(feature = "canonicalize_nans"))] - x - } - - #[inline] - fn canonicalize_simd_f64_nan(x: f64) -> f64 { - #[cfg(feature = "canonicalize_nans")] - if x.is_nan() { - f64::NAN - } else { - x - } - #[cfg(not(feature = "canonicalize_nans"))] - x - } - - const fn saturate_i16_to_i8(x: i16) -> i8 { - if x > i8::MAX as i16 { - i8::MAX - } else if x < i8::MIN as i16 { - i8::MIN - } else { - x as i8 - } - } - - const fn saturate_i16_to_u8(x: i16) -> u8 { - if x <= 0 { - 0 - } else if x > u8::MAX as i16 { - u8::MAX - } else { - x as u8 - } - } - - const fn saturate_i32_to_i16(x: i32) -> i16 { - if x > i16::MAX as i32 { - i16::MAX - } else if x < i16::MIN as i32 { - i16::MIN - } else { - x as i16 - } - } - - const fn saturate_i32_to_u16(x: i32) -> u16 { - if x <= 0 { - 0 - } else if x > u16::MAX as i32 { - u16::MAX - } else { - x as u16 - } - } - - const fn replace_lane_bytes( - self, - lane: u8, - value: [u8; LANE_BYTES], - lane_count: u8, - ) -> Self { - debug_assert!(lane < lane_count); - let mut bytes = self.to_le_bytes(); - let mut i = 0; - let start = lane as usize * LANE_BYTES; - while i < LANE_BYTES { - bytes[start + i] = value[i]; - i += 1; - } - Self::from_le_bytes(bytes) - } - pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { Self(i128::from_le_bytes(bytes)) } @@ -228,6 +150,7 @@ impl Value128 { Self::from_f64x2([op(a[0], b[0]), op(a[1], b[1])]) } + #[inline] pub const fn reduce_or(self) -> u8 { let mut result = 0u8; let bytes = self.to_le_bytes(); @@ -1045,8 +968,8 @@ impl Value128 { let mut out = [0i8; 16]; let mut i = 0; while i < 8 { - out[i] = Self::saturate_i16_to_i8(av[i]); - out[i + 8] = Self::saturate_i16_to_i8(bv[i]); + out[i] = saturate_i16_to_i8(av[i]); + out[i + 8] = saturate_i16_to_i8(bv[i]); i += 1; } Self::from_i8x16(out) @@ -1059,8 +982,8 @@ impl Value128 { let mut out = [0u8; 16]; let mut i = 0; while i < 8 { - out[i] = Self::saturate_i16_to_u8(av[i]); - out[i + 8] = Self::saturate_i16_to_u8(bv[i]); + out[i] = saturate_i16_to_u8(av[i]); + out[i + 8] = saturate_i16_to_u8(bv[i]); i += 1; } Self::from_u8x16(out) @@ -1073,8 +996,8 @@ impl Value128 { let mut out = [0i16; 8]; let mut i = 0; while i < 4 { - out[i] = Self::saturate_i32_to_i16(av[i]); - out[i + 4] = Self::saturate_i32_to_i16(bv[i]); + out[i] = saturate_i32_to_i16(av[i]); + out[i + 4] = saturate_i32_to_i16(bv[i]); i += 1; } Self::from_i16x8(out) @@ -1087,8 +1010,8 @@ impl Value128 { let mut out = [0u16; 8]; let mut i = 0; while i < 4 { - out[i] = Self::saturate_i32_to_u16(av[i]); - out[i + 4] = Self::saturate_i32_to_u16(bv[i]); + out[i] = saturate_i32_to_u16(av[i]); + out[i + 4] = saturate_i32_to_u16(bv[i]); i += 1; } Self::from_u16x8(out) @@ -1417,7 +1340,7 @@ impl Value128 { let mut out = [0i16; 8]; let mut i = 0; while i < 8 { - let r = ((a[i] as i32 * b[i] as i32) + 0x4000) >> 15; + let r = ((a[i] as i32 * b[i] as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding out[i] = if r > i16::MAX as i32 { i16::MAX } else if r < i16::MIN as i32 { @@ -2051,7 +1974,7 @@ impl Value128 { } #[doc(alias = "f32x4.eq")] - pub fn f32x4_eq(self, rhs: Self) -> Self { + pub const fn f32x4_eq(self, rhs: Self) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); let mut out = [0i32; 4]; @@ -2064,7 +1987,7 @@ impl Value128 { } #[doc(alias = "f64x2.eq")] - pub fn f64x2_eq(self, rhs: Self) -> Self { + pub const fn f64x2_eq(self, rhs: Self) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); let mut out = [0i64; 2]; @@ -2077,7 +2000,7 @@ impl Value128 { } #[doc(alias = "f32x4.ne")] - pub fn f32x4_ne(self, rhs: Self) -> Self { + pub const fn f32x4_ne(self, rhs: Self) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); let mut out = [0i32; 4]; @@ -2090,7 +2013,7 @@ impl Value128 { } #[doc(alias = "f64x2.ne")] - pub fn f64x2_ne(self, rhs: Self) -> Self { + pub const fn f64x2_ne(self, rhs: Self) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); let mut out = [0i64; 2]; @@ -2103,7 +2026,7 @@ impl Value128 { } #[doc(alias = "f32x4.lt")] - pub fn f32x4_lt(self, rhs: Self) -> Self { + pub const fn f32x4_lt(self, rhs: Self) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); let mut out = [0i32; 4]; @@ -2116,7 +2039,7 @@ impl Value128 { } #[doc(alias = "f64x2.lt")] - pub fn f64x2_lt(self, rhs: Self) -> Self { + pub const fn f64x2_lt(self, rhs: Self) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); let mut out = [0i64; 2]; @@ -2129,17 +2052,17 @@ impl Value128 { } #[doc(alias = "f32x4.gt")] - pub fn f32x4_gt(self, rhs: Self) -> Self { + pub const fn f32x4_gt(self, rhs: Self) -> Self { rhs.f32x4_lt(self) } #[doc(alias = "f64x2.gt")] - pub fn f64x2_gt(self, rhs: Self) -> Self { + pub const fn f64x2_gt(self, rhs: Self) -> Self { rhs.f64x2_lt(self) } #[doc(alias = "f32x4.le")] - pub fn f32x4_le(self, rhs: Self) -> Self { + pub const fn f32x4_le(self, rhs: Self) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); let mut out = [0i32; 4]; @@ -2152,7 +2075,7 @@ impl Value128 { } #[doc(alias = "f64x2.le")] - pub fn f64x2_le(self, rhs: Self) -> Self { + pub const fn f64x2_le(self, rhs: Self) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); let mut out = [0i64; 2]; @@ -2165,7 +2088,7 @@ impl Value128 { } #[doc(alias = "f32x4.ge")] - pub fn f32x4_ge(self, rhs: Self) -> Self { + pub const fn f32x4_ge(self, rhs: Self) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); let mut out = [0i32; 4]; @@ -2178,7 +2101,7 @@ impl Value128 { } #[doc(alias = "f64x2.ge")] - pub fn f64x2_ge(self, rhs: Self) -> Self { + pub const fn f64x2_ge(self, rhs: Self) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); let mut out = [0i64; 2]; @@ -2192,42 +2115,42 @@ impl Value128 { #[doc(alias = "f32x4.ceil")] pub fn f32x4_ceil(self) -> Self { - self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.ceil())) + self.map_f32x4(|x| canonicalize_simd_f32_nan(x.ceil())) } #[doc(alias = "f64x2.ceil")] pub fn f64x2_ceil(self) -> Self { - self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.ceil())) + self.map_f64x2(|x| canonicalize_simd_f64_nan(x.ceil())) } #[doc(alias = "f32x4.floor")] pub fn f32x4_floor(self) -> Self { - self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.floor())) + self.map_f32x4(|x| canonicalize_simd_f32_nan(x.floor())) } #[doc(alias = "f64x2.floor")] pub fn f64x2_floor(self) -> Self { - self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.floor())) + self.map_f64x2(|x| canonicalize_simd_f64_nan(x.floor())) } #[doc(alias = "f32x4.trunc")] pub fn f32x4_trunc(self) -> Self { - self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.trunc())) + self.map_f32x4(|x| canonicalize_simd_f32_nan(x.trunc())) } #[doc(alias = "f64x2.trunc")] pub fn f64x2_trunc(self) -> Self { - self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.trunc())) + self.map_f64x2(|x| canonicalize_simd_f64_nan(x.trunc())) } #[doc(alias = "f32x4.nearest")] pub fn f32x4_nearest(self) -> Self { - self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(TinywasmFloatExt::tw_nearest(x))) + self.map_f32x4(|x| canonicalize_simd_f32_nan(TinywasmFloatExt::tw_nearest(x))) } #[doc(alias = "f64x2.nearest")] pub fn f64x2_nearest(self) -> Self { - self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(TinywasmFloatExt::tw_nearest(x))) + self.map_f64x2(|x| canonicalize_simd_f64_nan(TinywasmFloatExt::tw_nearest(x))) } #[doc(alias = "f32x4.abs")] @@ -2252,52 +2175,52 @@ impl Value128 { #[doc(alias = "f32x4.sqrt")] pub fn f32x4_sqrt(self) -> Self { - self.map_f32x4(|x| Self::canonicalize_simd_f32_nan(x.sqrt())) + self.map_f32x4(|x| canonicalize_simd_f32_nan(x.sqrt())) } #[doc(alias = "f64x2.sqrt")] pub fn f64x2_sqrt(self) -> Self { - self.map_f64x2(|x| Self::canonicalize_simd_f64_nan(x.sqrt())) + self.map_f64x2(|x| canonicalize_simd_f64_nan(x.sqrt())) } #[doc(alias = "f32x4.add")] pub fn f32x4_add(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a + b)) + self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a + b)) } #[doc(alias = "f64x2.add")] pub fn f64x2_add(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a + b)) + self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a + b)) } #[doc(alias = "f32x4.sub")] pub fn f32x4_sub(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a - b)) + self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a - b)) } #[doc(alias = "f64x2.sub")] pub fn f64x2_sub(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a - b)) + self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a - b)) } #[doc(alias = "f32x4.mul")] pub fn f32x4_mul(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a * b)) + self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a * b)) } #[doc(alias = "f64x2.mul")] pub fn f64x2_mul(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a * b)) + self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a * b)) } #[doc(alias = "f32x4.div")] pub fn f32x4_div(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| Self::canonicalize_simd_f32_nan(a / b)) + self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a / b)) } #[doc(alias = "f64x2.div")] pub fn f64x2_div(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| Self::canonicalize_simd_f64_nan(a / b)) + self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a / b)) } #[doc(alias = "f32x4.min")] @@ -2524,6 +2447,23 @@ impl Value128 { pub const fn extract_lane_f64(self, lane: u8) -> f64 { f64::from_bits(self.extract_lane_i64(lane) as u64) } + + const fn replace_lane_bytes( + self, + lane: u8, + value: [u8; LANE_BYTES], + lane_count: u8, + ) -> Self { + debug_assert!(lane < lane_count); + let mut bytes = self.to_le_bytes(); + let mut i = 0; + let start = lane as usize * LANE_BYTES; + while i < LANE_BYTES { + bytes[start + i] = value[i]; + i += 1; + } + Self::from_le_bytes(bytes) + } } impl From for i128 { @@ -2566,13 +2506,81 @@ impl core::ops::BitXor for Value128 { } } +#[inline] +const fn canonicalize_simd_f32_nan(x: f32) -> f32 { + #[cfg(feature = "canonicalize_nans")] + if x.is_nan() { + f32::NAN + } else { + x + } + #[cfg(not(feature = "canonicalize_nans"))] + x +} + +#[inline] +const fn canonicalize_simd_f64_nan(x: f64) -> f64 { + #[cfg(feature = "canonicalize_nans")] + if x.is_nan() { + f64::NAN + } else { + x + } + #[cfg(not(feature = "canonicalize_nans"))] + x +} + +#[inline] +const fn saturate_i16_to_i8(x: i16) -> i8 { + if x > i8::MAX as i16 { + i8::MAX + } else if x < i8::MIN as i16 { + i8::MIN + } else { + x as i8 + } +} + +#[inline] +const fn saturate_i16_to_u8(x: i16) -> u8 { + if x <= 0 { + 0 + } else if x > u8::MAX as i16 { + u8::MAX + } else { + x as u8 + } +} + +#[inline] +const fn saturate_i32_to_i16(x: i32) -> i16 { + if x > i16::MAX as i32 { + i16::MAX + } else if x < i16::MIN as i32 { + i16::MIN + } else { + x as i16 + } +} + +#[inline] +const fn saturate_i32_to_u16(x: i32) -> u16 { + if x <= 0 { + 0 + } else if x > u16::MAX as i32 { + u16::MAX + } else { + x as u16 + } +} + #[inline] fn trunc_sat_f32_to_i32(v: f32) -> i32 { if v.is_nan() { 0 - } else if v <= -2147483904.0_f32 { + } else if v <= i32::MIN as f32 - (1 << 8) as f32 { i32::MIN - } else if v >= 2147483648.0_f32 { + } else if v >= (i32::MAX as f32 + 1.0) { i32::MAX } else { v.trunc() as i32 @@ -2583,7 +2591,7 @@ fn trunc_sat_f32_to_i32(v: f32) -> i32 { fn trunc_sat_f32_to_u32(v: f32) -> u32 { if v.is_nan() || v <= -1.0_f32 { 0 - } else if v >= 4294967296.0_f32 { + } else if v >= (u32::MAX as f32 + 1.0) { u32::MAX } else { v.trunc() as u32 @@ -2594,9 +2602,9 @@ fn trunc_sat_f32_to_u32(v: f32) -> u32 { fn trunc_sat_f64_to_i32(v: f64) -> i32 { if v.is_nan() { 0 - } else if v <= -2147483649.0_f64 { + } else if v <= i32::MIN as f64 - 1.0_f64 { i32::MIN - } else if v >= 2147483648.0_f64 { + } else if v >= (i32::MAX as f64 + 1.0) { i32::MAX } else { v.trunc() as i32 @@ -2607,7 +2615,7 @@ fn trunc_sat_f64_to_i32(v: f64) -> i32 { fn trunc_sat_f64_to_u32(v: f64) -> u32 { if v.is_nan() || v <= -1.0_f64 { 0 - } else if v >= 4294967296.0_f64 { + } else if v >= (u32::MAX as f64 + 1.0) { u32::MAX } else { v.trunc() as u32 diff --git a/crates/tinywasm/tests/generated/wasm-annotations.csv b/crates/tinywasm/tests/generated/wasm-annotations.csv index 1b990330..b1bfe45d 100644 --- a/crates/tinywasm/tests/generated/wasm-annotations.csv +++ b/crates/tinywasm/tests/generated/wasm-annotations.csv @@ -1,2 +1,2 @@ 0.8.0,142,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"annotations/simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"token.wast","passed":61,"failed":0}] -0.9.0-alpha.0,142,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"token.wast","passed":61,"failed":0}] +0.9.0-alpha.0,617,0,[{"name":"annotations.wast","passed":74,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"token.wast","passed":61,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-memory64.csv b/crates/tinywasm/tests/generated/wasm-memory64.csv index 039fe8cf..20afc3b8 100644 --- a/crates/tinywasm/tests/generated/wasm-memory64.csv +++ b/crates/tinywasm/tests/generated/wasm-memory64.csv @@ -1,2 +1,2 @@ 0.8.0,15081,3214,[{"name":"address.wast","passed":260,"failed":0},{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"address64.wast","passed":0,"failed":242},{"name":"align.wast","passed":161,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"align64.wast","passed":83,"failed":73},{"name":"annotations.wast","passed":74,"failed":0},{"name":"array_copy.wast","passed":4,"failed":31},{"name":"array_fill.wast","passed":3,"failed":14},{"name":"array_init_data.wast","passed":2,"failed":31},{"name":"array_init_elem.wast","passed":3,"failed":20},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"binary-leb128.wast","passed":92,"failed":1},{"name":"binary.wast","passed":124,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_cast.wast","passed":6,"failed":31},{"name":"br_on_cast_fail.wast","passed":6,"failed":31},{"name":"br_on_non_null.wast","passed":1,"failed":9},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_indirect.wast","passed":47,"failed":124},{"name":"call_ref.wast","passed":4,"failed":31},{"name":"data.wast","passed":59,"failed":6},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"elem.wast","passed":137,"failed":14},{"name":"endianness64.wast","passed":0,"failed":69},{"name":"exports.wast","passed":97,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"float_memory64.wast","passed":0,"failed":90},{"name":"func.wast","passed":175,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":99,"failed":82},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking.wast","passed":122,"failed":41},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"load64.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory64.wast","passed":10,"failed":53},{"name":"memory64/array.wast (skipped)","passed":0,"failed":0},{"name":"memory64/extern.wast (skipped)","passed":0,"failed":0},{"name":"memory64/global.wast (skipped)","passed":0,"failed":0},{"name":"memory64/i31.wast (skipped)","passed":0,"failed":0},{"name":"memory64/ref_null.wast (skipped)","passed":0,"failed":0},{"name":"memory64/select.wast (skipped)","passed":0,"failed":0},{"name":"memory64/simd_address.wast (skipped)","passed":0,"failed":0},{"name":"memory64/simd_lane.wast (skipped)","passed":0,"failed":0},{"name":"memory64/struct.wast (skipped)","passed":0,"failed":0},{"name":"memory64/table.wast (skipped)","passed":0,"failed":0},{"name":"memory_copy.wast","passed":8385,"failed":515},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill.wast","passed":164,"failed":36},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_grow64.wast","passed":0,"failed":49},{"name":"memory_init.wast","passed":307,"failed":173},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_redundancy64.wast","passed":0,"failed":8},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"memory_trap64.wast","passed":0,"failed":172},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_cast.wast","passed":0,"failed":45},{"name":"ref_eq.wast","passed":6,"failed":83},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_test.wast","passed":0,"failed":71},{"name":"return_call.wast","passed":18,"failed":27},{"name":"return_call_indirect.wast","passed":31,"failed":45},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"simd_memory-multi.wast","passed":0,"failed":1},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table_copy.wast","passed":1742,"failed":30},{"name":"table_copy_mixed.wast","passed":3,"failed":1},{"name":"table_fill.wast","passed":9,"failed":71},{"name":"table_get.wast","passed":5,"failed":12},{"name":"table_grow.wast","passed":36,"failed":43},{"name":"table_init.wast","passed":588,"failed":288},{"name":"table_set.wast","passed":7,"failed":21},{"name":"table_size.wast","passed":2,"failed":38},{"name":"tag.wast","passed":1,"failed":8},{"name":"throw.wast","passed":3,"failed":10},{"name":"throw_ref.wast","passed":2,"failed":13},{"name":"token.wast","passed":61,"failed":0},{"name":"traps0.wast","passed":15,"failed":0},{"name":"try_table.wast","passed":11,"failed":51},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":6,"failed":14},{"name":"type-subtyping.wast","passed":16,"failed":86},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11}] -0.9.0-alpha.0,1556,42,[{"name":"address.wast","passed":260,"failed":0},{"name":"address64.wast","passed":242,"failed":0},{"name":"align64.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":93,"failed":0},{"name":"binary.wast","passed":169,"failed":0},{"name":"endianness64.wast","passed":69,"failed":0},{"name":"float_memory64.wast","passed":90,"failed":0},{"name":"load64.wast","passed":97,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory64.wast","passed":65,"failed":0},{"name":"memory_grow64.wast","passed":49,"failed":0},{"name":"memory_redundancy64.wast","passed":8,"failed":0},{"name":"memory_trap64.wast","passed":172,"failed":0},{"name":"simd_address.wast","passed":7,"failed":42}] +0.9.0-alpha.0,1598,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"address64.wast","passed":242,"failed":0},{"name":"align64.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":93,"failed":0},{"name":"binary.wast","passed":169,"failed":0},{"name":"endianness64.wast","passed":69,"failed":0},{"name":"float_memory64.wast","passed":90,"failed":0},{"name":"load64.wast","passed":97,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory64.wast","passed":65,"failed":0},{"name":"memory_grow64.wast","passed":49,"failed":0},{"name":"memory_redundancy64.wast","passed":8,"failed":0},{"name":"memory_trap64.wast","passed":172,"failed":0},{"name":"simd_address.wast","passed":49,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-multi-memory.csv b/crates/tinywasm/tests/generated/wasm-multi-memory.csv index 33debd64..c81e9e5d 100644 --- a/crates/tinywasm/tests/generated/wasm-multi-memory.csv +++ b/crates/tinywasm/tests/generated/wasm-multi-memory.csv @@ -1,2 +1,2 @@ 0.8.0,1872,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"multi-memory/simd_memory-multi.wast (skipped)","passed":0,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] -0.9.0-alpha.0,1871,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":6,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"simd_memory-multi.wast (skipped)","passed":0,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] +0.9.0-alpha.0,1872,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":6,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] diff --git a/crates/tinywasm/tests/test-wasm-annotations.rs b/crates/tinywasm/tests/test-wasm-annotations.rs index beac9eba..8655d152 100644 --- a/crates/tinywasm/tests/test-wasm-annotations.rs +++ b/crates/tinywasm/tests/test-wasm-annotations.rs @@ -7,7 +7,6 @@ fn main() -> Result<()> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); - test_suite.skip("simd_lane.wast"); test_suite.run_files(proposal(&Proposal::Annotations))?; test_suite.save_csv("./tests/generated/wasm-annotations.csv", env!("CARGO_PKG_VERSION"))?; test_suite.report_status() diff --git a/crates/tinywasm/tests/test-wasm-multi-memory.rs b/crates/tinywasm/tests/test-wasm-multi-memory.rs index 30052b07..e4487f11 100644 --- a/crates/tinywasm/tests/test-wasm-multi-memory.rs +++ b/crates/tinywasm/tests/test-wasm-multi-memory.rs @@ -7,7 +7,6 @@ fn main() -> Result<()> { TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); - test_suite.skip("simd_memory-multi.wast"); test_suite.run_files(proposal(&Proposal::MultiMemory))?; test_suite.save_csv("./tests/generated/wasm-multi-memory.csv", env!("CARGO_PKG_VERSION"))?; test_suite.report_status() diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index d3b3f8c2..11d76378 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -26,7 +26,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index 46f17857..6a31ff0c 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -43,7 +43,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&mut store, "add_and_print")?; + let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } From 9438505b0e4863984c0ea12637019e512530eece Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 23 Mar 2026 22:14:18 +0100 Subject: [PATCH 09/47] chore: re-use stack allocations Signed-off-by: Henry --- Cargo.lock | 7 + crates/tinywasm/Cargo.toml | 4 + crates/tinywasm/src/config.rs | 115 -------- crates/tinywasm/src/engine.rs | 140 ++++++++++ crates/tinywasm/src/func.rs | 264 ++++++++---------- crates/tinywasm/src/imports.rs | 8 +- crates/tinywasm/src/instance.rs | 14 +- crates/tinywasm/src/interpreter/executor.rs | 252 +++++++++-------- crates/tinywasm/src/interpreter/mod.rs | 8 +- .../tinywasm/src/interpreter/num_helpers.rs | 1 + .../src/interpreter/stack/block_stack.rs | 10 +- .../src/interpreter/stack/call_stack.rs | 11 +- crates/tinywasm/src/interpreter/stack/mod.rs | 17 +- .../src/interpreter/stack/value_stack.rs | 23 +- crates/tinywasm/src/lib.rs | 34 +-- crates/tinywasm/src/store/mod.rs | 168 ++++++----- 16 files changed, 533 insertions(+), 543 deletions(-) delete mode 100644 crates/tinywasm/src/config.rs create mode 100644 crates/tinywasm/src/engine.rs diff --git a/Cargo.lock b/Cargo.lock index 2271ac28..0c041540 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ dependencies = [ "cc", ] +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "anes" version = "0.1.6" @@ -646,6 +652,7 @@ dependencies = [ name = "tinywasm" version = "0.9.0-alpha.0" dependencies = [ + "allocator-api2", "criterion", "eyre", "indexmap", diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index d1a08397..1ff40322 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -20,6 +20,7 @@ log={workspace=true, optional=true} tinywasm-parser={version="0.9.0-alpha.0", path="../parser", default-features=false, optional=true} tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} libm={version="0.2", default-features=false} +allocator-api2={version="0.4", optional=true} [dev-dependencies] wasm-testsuite.workspace=true @@ -48,6 +49,9 @@ archive=["tinywasm-types/archive"] # canonicalize all NaN values to a single representation canonicalize_nans=[] +# support for the allocator-api2 crate +allocator-api2=["dep:allocator-api2"] + [[test]] name="test-wasm-1" harness=false diff --git a/crates/tinywasm/src/config.rs b/crates/tinywasm/src/config.rs deleted file mode 100644 index 70ca4db4..00000000 --- a/crates/tinywasm/src/config.rs +++ /dev/null @@ -1,115 +0,0 @@ -use core::fmt; - -/// Default initial size for the 32-bit value stack (i32, f32 values). -pub const DEFAULT_VALUE_STACK_32_INIT_SIZE: usize = 32 * 1024; // 32KB - -/// Default initial size for the 64-bit value stack (i64, f64 values). -pub const DEFAULT_VALUE_STACK_64_INIT_SIZE: usize = 16 * 1024; // 16KB - -/// Default initial size for the 128-bit value stack (v128 values). -pub const DEFAULT_VALUE_STACK_128_INIT_SIZE: usize = 8 * 1024; // 8KB - -/// Default initial size for the reference value stack (funcref, externref values). -pub const DEFAULT_VALUE_STACK_REF_INIT_SIZE: usize = 1024; // 1KB - -/// Default initial size for the block stack. -pub const DEFAULT_BLOCK_STACK_INIT_SIZE: usize = 128; - -/// Configuration for the WebAssembly interpreter's stack preallocation. -/// -/// This struct allows you to configure how much space is preallocated for the -/// different parts of the stack that the interpreter uses to store values. -#[derive(Debug, Clone)] -pub struct StackConfig { - value_stack_32_init_size: Option, - value_stack_64_init_size: Option, - value_stack_128_init_size: Option, - value_stack_ref_init_size: Option, - block_stack_init_size: Option, -} - -impl StackConfig { - /// Create a new stack configuration with default settings. - pub fn new() -> Self { - Self { - value_stack_32_init_size: None, - value_stack_64_init_size: None, - value_stack_128_init_size: None, - value_stack_ref_init_size: None, - block_stack_init_size: None, - } - } - - /// Get the initial size for the 32-bit value stack. - pub fn value_stack_32_init_size(&self) -> usize { - self.value_stack_32_init_size.unwrap_or(DEFAULT_VALUE_STACK_32_INIT_SIZE) - } - - /// Get the initial size for the 64-bit value stack. - pub fn value_stack_64_init_size(&self) -> usize { - self.value_stack_64_init_size.unwrap_or(DEFAULT_VALUE_STACK_64_INIT_SIZE) - } - - /// Get the initial size for the 128-bit value stack. - pub fn value_stack_128_init_size(&self) -> usize { - self.value_stack_128_init_size.unwrap_or(DEFAULT_VALUE_STACK_128_INIT_SIZE) - } - - /// Get the initial size for the reference value stack. - pub fn value_stack_ref_init_size(&self) -> usize { - self.value_stack_ref_init_size.unwrap_or(DEFAULT_VALUE_STACK_REF_INIT_SIZE) - } - - /// Get the initial size for the block stack. - pub fn block_stack_init_size(&self) -> usize { - self.block_stack_init_size.unwrap_or(DEFAULT_BLOCK_STACK_INIT_SIZE) - } - - /// Set the initial capacity for the 32-bit value stack. - pub fn with_value_stack_32_init_size(mut self, capacity: usize) -> Self { - self.value_stack_32_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the 64-bit value stack. - pub fn with_value_stack_64_init_size(mut self, capacity: usize) -> Self { - self.value_stack_64_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the 128-bit value stack. - pub fn with_value_stack_128_init_size(mut self, capacity: usize) -> Self { - self.value_stack_128_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the reference value stack. - pub fn with_value_stack_ref_init_size(mut self, capacity: usize) -> Self { - self.value_stack_ref_init_size = Some(capacity); - self - } - - /// Set the initial capacity for the block stack. - pub fn with_block_stack_init_size(mut self, capacity: usize) -> Self { - self.block_stack_init_size = Some(capacity); - self - } -} - -impl Default for StackConfig { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Display for StackConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "StackConfig {{ ")?; - write!(f, "value_stack_32: {}, ", self.value_stack_32_init_size())?; - write!(f, "value_stack_64: {}, ", self.value_stack_64_init_size())?; - write!(f, "value_stack_128: {}, ", self.value_stack_128_init_size())?; - write!(f, "value_stack_ref: {}, ", self.value_stack_ref_init_size())?; - write!(f, "block_stack: {} }}", self.block_stack_init_size())?; - Ok(()) - } -} diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs new file mode 100644 index 00000000..a70ed58f --- /dev/null +++ b/crates/tinywasm/src/engine.rs @@ -0,0 +1,140 @@ +use core::fmt::Debug; + +use alloc::sync::Arc; + +/// Global configuration for the WebAssembly interpreter +/// +/// Can be cheaply cloned and shared across multiple executions and threads. +#[derive(Clone)] +pub struct Engine { + pub(crate) inner: Arc, +} + +impl Debug for Engine { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Engine").finish() + } +} + +impl Engine { + /// Create a new engine with the given configuration + pub fn new(config: Config) -> Self { + Self { inner: Arc::new(EngineInner { config }) } + } + + /// Get a reference to the engine's configuration + pub fn config(&self) -> &Config { + &self.inner.config + } +} + +impl Default for Engine { + fn default() -> Engine { + Engine::new(Config::default()) + } +} + +pub(crate) struct EngineInner { + pub(crate) config: Config, + // pub(crate) allocator: Box, +} + +// pub(crate) trait Allocator {} +// pub(crate) struct DefaultAllocator; +// impl Allocator for DefaultAllocator {} + +/// Default initial size for the 32-bit value stack (i32, f32 values). +pub const DEFAULT_VALUE_STACK_32_INIT_SIZE: usize = 32 * 1024; // 32KB + +/// Default initial size for the 64-bit value stack (i64, f64 values). +pub const DEFAULT_VALUE_STACK_64_INIT_SIZE: usize = 16 * 1024; // 16KB + +/// Default initial size for the 128-bit value stack (v128 values). +pub const DEFAULT_VALUE_STACK_128_INIT_SIZE: usize = 8 * 1024; // 8KB + +/// Default initial size for the reference value stack (funcref, externref values). +pub const DEFAULT_VALUE_STACK_REF_INIT_SIZE: usize = 1024; // 1KB + +/// Default initial size for the block stack. +pub const DEFAULT_BLOCK_STACK_INIT_SIZE: usize = 128; + +/// Default initial size for the call stack. +pub const DEFAULT_CALL_STACK_INIT_SIZE: usize = 128; + +/// Configuration for the WebAssembly interpreter +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Config { + /// Initial size of the 32-bit value stack (i32, f32 values). + pub stack_32_init_size: usize, + /// Initial size of the 64-bit value stack (i64, f64 values). + pub stack_64_init_size: usize, + /// Initial size of the 128-bit value stack (v128 values). + pub stack_128_init_size: usize, + /// Initial size of the reference value stack (funcref, externref values). + pub stack_ref_init_size: usize, + /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. + pub stack_32_max_size: Option, + /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. + pub stack_64_max_size: Option, + /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. + pub stack_128_max_size: Option, + /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. + pub stack_ref_max_size: Option, + + /// Initial size of the call stack. + pub call_stack_init_size: usize, + /// The maximum size of the call stack. If set, the interpreter will enforce this limit and return an error if it is exceeded. + pub call_stack_max_size: Option, + + /// Initial size of the control stack (block stack). + pub block_stack_init_size: usize, + /// Optional maximum size for the control stack (block stack). If set, the interpreter will enforce this limit and return an error if it is exceeded. + pub block_stack_max_size: Option, +} + +impl Config { + /// Create a new stack configuration with default settings. + pub fn new() -> Self { + Self::default() + } + + /// Set the same maximum size for all stacks. If set, the interpreter will enforce this limit and return an error if it is exceeded. + pub fn with_max_stack_size(mut self, max_size: usize) -> Self { + self.stack_32_max_size = Some(max_size); + self.stack_64_max_size = Some(max_size); + self.stack_128_max_size = Some(max_size); + self.stack_ref_max_size = Some(max_size); + self.block_stack_max_size = Some(max_size); + self + } + + /// Set the same initial size for all stacks. + pub fn with_initial_stack_size(mut self, init_size: usize) -> Self { + self.stack_32_init_size = init_size; + self.stack_64_init_size = init_size; + self.stack_128_init_size = init_size; + self.stack_ref_init_size = init_size; + self.block_stack_init_size = init_size; + self + } +} + +impl Default for Config { + fn default() -> Self { + Self { + stack_32_init_size: DEFAULT_VALUE_STACK_32_INIT_SIZE, + stack_64_init_size: DEFAULT_VALUE_STACK_64_INIT_SIZE, + stack_128_init_size: DEFAULT_VALUE_STACK_128_INIT_SIZE, + stack_ref_init_size: DEFAULT_VALUE_STACK_REF_INIT_SIZE, + block_stack_init_size: DEFAULT_BLOCK_STACK_INIT_SIZE, + call_stack_init_size: DEFAULT_CALL_STACK_INIT_SIZE, + call_stack_max_size: None, + stack_32_max_size: None, + stack_64_max_size: None, + stack_128_max_size: None, + stack_ref_max_size: None, + block_stack_max_size: None, + } + } +} diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index e8055d19..87713bb7 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,7 +1,7 @@ -use crate::interpreter::stack::{CallFrame, Stack}; -use crate::{Error, FuncContext, Result, Store}; +use crate::interpreter::stack::CallFrame; +use crate::{Error, FuncContext, InterpreterRuntime, Result, Store}; use crate::{Function, log, unlikely}; -use alloc::{boxed::Box, format, string::String, string::ToString, vec, vec::Vec}; +use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec}; use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, ValType, WasmValue}; #[derive(Debug)] @@ -10,9 +10,6 @@ pub struct FuncHandle { pub(crate) module_addr: ModuleInstanceAddr, pub(crate) addr: u32, pub(crate) ty: FuncType, - - /// The name of the function, if it has one - pub name: Option, } impl FuncHandle { @@ -48,35 +45,32 @@ impl FuncHandle { return Err(Error::Other("Type mismatch".into())); } - let func_inst = store.get_func(self.addr); + let func_inst = store.state.get_func(self.addr); let wasm_func = match &func_inst.func { Function::Host(host_func) => { let host_func = host_func.clone(); let ctx = FuncContext { store, module_addr: self.module_addr }; return host_func.call(ctx, params); } - Function::Wasm(wasm_func) => wasm_func, + Function::Wasm(wasm_func) => wasm_func.clone(), }; // 6. Let f be the dummy frame - let call_frame = CallFrame::new(wasm_func.clone(), func_inst.owner, params, 0); + let callframe = CallFrame::new(wasm_func, func_inst.owner, params, 0); // 7. Push the frame f to the call stack // & 8. Push the values to the stack (Not needed since the call frame owns the values) - let mut stack = Stack::new(call_frame, &store.config); + store.stack.initialize(callframe); // 9. Invoke the function instance - let runtime = store.runtime(); - runtime.exec(store, &mut stack)?; + InterpreterRuntime::exec(store)?; // Once the function returns: - // let result_m = func_ty.results.len(); - // 1. Assert: m values are on the top of the stack (Ensured by validation) - // assert!(stack.values.len() >= result_m); + debug_assert!(store.stack.values.len() >= func_ty.results.len()); // 2. Pop m values from the stack - let res = stack.values.pop_results(&func_ty.results); + let res = store.stack.values.pop_results(&func_ty.results); // The values are returned as the results of the invocation. Ok(res) @@ -115,42 +109,86 @@ impl FuncHandleTyped { } } -macro_rules! impl_into_wasm_value_tuple { - ($($T:ident),*) => { - impl<$($T),*> IntoWasmValueTuple for ($($T,)*) +pub trait ValTypesFromTuple { + fn val_types() -> Box<[ValType]>; +} + +pub trait ToValType { + fn to_val_type() -> ValType; +} + +macro_rules! impl_scalar_wasm_traits { + ($($T:ty => $val_ty:ident),+ $(,)?) => { + $( + impl ToValType for $T { + #[inline] + fn to_val_type() -> ValType { + ValType::$val_ty + } + } + + impl ValTypesFromTuple for $T { + #[inline] + fn val_types() -> Box<[ValType]> { + Box::new([ValType::$val_ty]) + } + } + + impl IntoWasmValueTuple for $T { + #[inline] + fn into_wasm_value_tuple(self) -> Vec { + vec![self.into()] + } + } + + impl FromWasmValueTuple for $T { + #[inline] + fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { + let value = *values + .first() + .ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?; + <$T>::try_from(value).map_err(|e| { + Error::Other(format!( + "FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", + e + )) + }) + } + } + )+ + }; +} + +macro_rules! impl_tuple_traits { + ($($T:ident),+) => { + impl<$($T),+> ValTypesFromTuple for ($($T,)+) where - $($T: Into),* + $($T: ToValType,)+ { - #[allow(non_snake_case)] #[inline] - fn into_wasm_value_tuple(self) -> Vec { - let ($($T,)*) = self; - vec![$($T.into(),)*] + fn val_types() -> Box<[ValType]> { + Box::new([$($T::to_val_type(),)+]) } } - } -} -macro_rules! impl_into_wasm_value_tuple_single { - ($T:ident) => { - impl IntoWasmValueTuple for $T { + impl<$($T),+> IntoWasmValueTuple for ($($T,)+) + where + $($T: Into,)+ + { + #[allow(non_snake_case)] #[inline] fn into_wasm_value_tuple(self) -> Vec { - vec![self.into()] + let ($($T,)+) = self; + vec![$($T.into(),)+] } } - }; -} -macro_rules! impl_from_wasm_value_tuple { - ($($T:ident),*) => { - impl<$($T),*> FromWasmValueTuple for ($($T,)*) + impl<$($T),+> FromWasmValueTuple for ($($T,)+) where - $($T: TryFrom),* + $($T: TryFrom,)+ { #[inline] fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { - #[allow(unused_variables, unused_mut)] let mut iter = values.iter(); Ok(( @@ -159,91 +197,43 @@ macro_rules! impl_from_wasm_value_tuple { *iter.next() .ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))? ) - .map_err(|e| Error::Other(format!("FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", e, - )))?, - )* + .map_err(|e| Error::Other(format!( + "FromWasmValueTuple: Could not convert WasmValue to expected type: {:?}", + e, + )))?, + )+ )) } } } } -macro_rules! impl_from_wasm_value_tuple_single { - ($T:ident) => { - impl FromWasmValueTuple for $T { - #[inline] - fn from_wasm_value_tuple(values: &[WasmValue]) -> Result { - #[allow(unused_variables, unused_mut)] - let mut iter = values.iter(); - $T::try_from(*iter.next().ok_or(Error::Other("Not enough values in WasmValue vector".to_string()))?) - .map_err(|e| { - Error::Other(format!( - "FromWasmValueTupleSingle: Could not convert WasmValue to expected type: {:?}", - e - )) - }) - } - } +macro_rules! impl_tuple { + ($macro:ident) => { + $macro!(T1); + $macro!(T1, T2); + $macro!(T1, T2, T3); + $macro!(T1, T2, T3, T4); + $macro!(T1, T2, T3, T4, T5); + $macro!(T1, T2, T3, T4, T5, T6); + $macro!(T1, T2, T3, T4, T5, T6, T7); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + $macro!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); }; } -pub trait ValTypesFromTuple { - fn val_types() -> Box<[ValType]>; -} - -pub trait ToValType { - fn to_val_type() -> ValType; -} - -impl ToValType for i32 { - fn to_val_type() -> ValType { - ValType::I32 - } -} - -impl ToValType for i64 { - fn to_val_type() -> ValType { - ValType::I64 - } -} - -impl ToValType for f32 { - fn to_val_type() -> ValType { - ValType::F32 - } -} - -impl ToValType for f64 { - fn to_val_type() -> ValType { - ValType::F64 - } -} - -impl ToValType for FuncRef { - fn to_val_type() -> ValType { - ValType::RefFunc - } -} - -impl ToValType for ExternRef { - fn to_val_type() -> ValType { - ValType::RefExtern - } -} - -macro_rules! impl_val_types_from_tuple { - ($($t:ident),+) => { - impl<$($t),+> ValTypesFromTuple for ($($t,)+) - where - $($t: ToValType,)+ - { - #[inline] - fn val_types() -> Box<[ValType]> { - Box::new([$($t::to_val_type(),)+]) - } - } - }; -} +impl_scalar_wasm_traits!( + i32 => I32, + i64 => I64, + f32 => F32, + f64 => F64, + FuncRef => RefFunc, + ExternRef => RefExtern, +); +impl_tuple!(impl_tuple_traits); impl ValTypesFromTuple for () { #[inline] @@ -252,46 +242,16 @@ impl ValTypesFromTuple for () { } } -impl ValTypesFromTuple for T { +impl IntoWasmValueTuple for () { #[inline] - fn val_types() -> Box<[ValType]> { - Box::new([T::to_val_type()]) + fn into_wasm_value_tuple(self) -> Vec { + vec![] } } -impl_from_wasm_value_tuple_single!(i32); -impl_from_wasm_value_tuple_single!(i64); -impl_from_wasm_value_tuple_single!(f32); -impl_from_wasm_value_tuple_single!(f64); -impl_from_wasm_value_tuple_single!(FuncRef); -impl_from_wasm_value_tuple_single!(ExternRef); - -impl_into_wasm_value_tuple_single!(i32); -impl_into_wasm_value_tuple_single!(i64); -impl_into_wasm_value_tuple_single!(f32); -impl_into_wasm_value_tuple_single!(f64); -impl_into_wasm_value_tuple_single!(FuncRef); -impl_into_wasm_value_tuple_single!(ExternRef); - -impl_val_types_from_tuple!(T1); -impl_val_types_from_tuple!(T1, T2); -impl_val_types_from_tuple!(T1, T2, T3); -impl_val_types_from_tuple!(T1, T2, T3, T4); -impl_val_types_from_tuple!(T1, T2, T3, T4, T5); -impl_val_types_from_tuple!(T1, T2, T3, T4, T5, T6); - -impl_from_wasm_value_tuple!(); -impl_from_wasm_value_tuple!(T1); -impl_from_wasm_value_tuple!(T1, T2); -impl_from_wasm_value_tuple!(T1, T2, T3); -impl_from_wasm_value_tuple!(T1, T2, T3, T4); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_from_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); - -impl_into_wasm_value_tuple!(); -impl_into_wasm_value_tuple!(T1); -impl_into_wasm_value_tuple!(T1, T2); -impl_into_wasm_value_tuple!(T1, T2, T3); -impl_into_wasm_value_tuple!(T1, T2, T3, T4); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5); -impl_into_wasm_value_tuple!(T1, T2, T3, T4, T5, T6); +impl FromWasmValueTuple for () { + #[inline] + fn from_wasm_value_tuple(_values: &[WasmValue]) -> Result { + Ok(()) + } +} diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 563e586d..aa05ae95 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -403,25 +403,25 @@ impl Imports { match (val, &import.kind) { (ExternVal::Global(global_addr), ImportKind::Global(ty)) => { - let global = store.get_global(global_addr); + let global = store.state.get_global(global_addr); Self::compare_types(import, &global.ty, ty)?; imports.globals.push(global_addr); } (ExternVal::Table(table_addr), ImportKind::Table(ty)) => { - let table = store.get_table(table_addr); + let table = store.state.get_table(table_addr); let mut kind = table.kind.clone(); kind.size_initial = table.size() as u32; Self::compare_table_types(import, &kind, ty)?; imports.tables.push(table_addr); } (ExternVal::Memory(memory_addr), ImportKind::Memory(ty)) => { - let mem = store.get_mem(memory_addr); + let mem = store.state.get_mem(memory_addr); let (size, kind) = { (mem.page_count, mem.kind) }; Self::compare_memory_types(import, &kind, ty, Some(size))?; imports.memories.push(memory_addr); } (ExternVal::Func(func_addr), ImportKind::Function(ty)) => { - let func = store.get_func(func_addr); + let func = store.state.get_func(func_addr); let import_func_type = module .0 .func_types diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 0ca8f6f8..02ef5312 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,4 +1,4 @@ -use alloc::{boxed::Box, format, rc::Rc, string::ToString}; +use alloc::{boxed::Box, format, rc::Rc}; use tinywasm_types::*; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; @@ -174,8 +174,8 @@ impl ModuleInstance { return Err(Error::Other(format!("Export is not a function: {name}"))); }; - let ty = store.get_func(func_addr).func.ty(); - Ok(FuncHandle { addr: func_addr, module_addr: self.id(), name: Some(name.to_string()), ty: ty.clone() }) + let ty = store.state.get_func(func_addr).func.ty(); + Ok(FuncHandle { addr: func_addr, module_addr: self.id(), ty: ty.clone() }) } /// Get a typed exported function by name @@ -210,13 +210,13 @@ impl ModuleInstance { /// Get a memory by address pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result> { - let mem = store.get_mem(self.resolve_mem_addr(addr)); + let mem = store.state.get_mem(self.resolve_mem_addr(addr)); Ok(MemoryRef(mem)) } /// Get a memory by address (mutable) pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { - let mem = store.get_mem_mut(self.resolve_mem_addr(addr)); + let mem = store.state.get_mem_mut(self.resolve_mem_addr(addr)); Ok(MemoryRefMut(mem)) } @@ -244,10 +244,10 @@ impl ModuleInstance { }; let func_addr = self.resolve_func_addr(func_index); - let func_inst = store.get_func(func_addr); + let func_inst = store.state.get_func(func_addr); let ty = func_inst.func.ty(); - Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone(), name: None })) + Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone() })) } /// Invoke the start function of the module diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 08089f4d..fd248969 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -9,26 +9,24 @@ use interpreter::stack::CallFrame; use tinywasm_types::*; use super::num_helpers::*; -use super::stack::{BlockFrame, BlockType, Stack}; +use super::stack::{BlockFrame, BlockType}; use super::values::*; use crate::interpreter::Value128; use crate::*; -pub(crate) struct Executor<'store, 'stack> { +pub(crate) struct Executor<'store> { pub(crate) cf: CallFrame, pub(crate) module: ModuleInstance, pub(crate) store: &'store mut Store, - pub(crate) stack: &'stack mut Stack, } -impl<'store, 'stack> Executor<'store, 'stack> { - pub(crate) fn new(store: &'store mut Store, stack: &'stack mut Stack) -> Result { - let current_frame = stack.call_stack.pop().expect("no call frame, this is a bug"); +impl<'store> Executor<'store> { + pub(crate) fn new(store: &'store mut Store) -> Result { + let current_frame = store.stack.call_stack.pop().expect("no call frame, this is a bug"); let current_module = store.get_module_instance_raw(current_frame.module_addr()); - Ok(Self { cf: current_frame, module: current_module, stack, store }) + Ok(Self { cf: current_frame, module: current_module, store }) } - #[inline(always)] pub(crate) fn run_to_completion(&mut self) -> Result<()> { loop { if let ControlFlow::Break(res) = self.exec_next() { @@ -46,31 +44,31 @@ impl<'store, 'stack> Executor<'store, 'stack> { macro_rules! stack_op { (simd_unary $method:ident) => { - self.stack.values.unary_same::(|v| Ok(v.$method())).to_cf()? + self.store.stack.values.unary_same::(|v| Ok(v.$method())).to_cf()? }; (simd_binary $method:ident) => { - self.stack.values.binary_same::(|a, b| Ok(a.$method(b))).to_cf()? + self.store.stack.values.binary_same::(|a, b| Ok(a.$method(b))).to_cf()? }; (unary $ty:ty, |$v:ident| $expr:expr) => { - self.stack.values.unary_same::<$ty>(|$v| Ok($expr)).to_cf()? + self.store.stack.values.unary_same::<$ty>(|$v| Ok($expr)).to_cf()? }; (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { - self.stack.values.binary_same::<$ty>(|$a, $b| Ok($expr)).to_cf()? + self.store.stack.values.binary_same::<$ty>(|$a, $b| Ok($expr)).to_cf()? }; (binary_try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { - self.stack.values.binary_same::<$ty>(|$a, $b| $expr).to_cf()? + self.store.stack.values.binary_same::<$ty>(|$a, $b| $expr).to_cf()? }; (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { - self.stack.values.unary::<$from, $to>(|$v| Ok($expr)).to_cf()? + self.store.stack.values.unary::<$from, $to>(|$v| Ok($expr)).to_cf()? }; (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { - self.stack.values.binary::<$from, $to>(|$a, $b| Ok($expr)).to_cf()? + self.store.stack.values.binary::<$from, $to>(|$a, $b| Ok($expr)).to_cf()? }; (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { - self.stack.values.binary_diff::<$a, $b, $b>(|$lhs, $rhs| Ok($expr)).to_cf()? + self.store.stack.values.binary_diff::<$a, $b, $b>(|$lhs, $rhs| Ok($expr)).to_cf()? }; (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { - self.stack.values.binary_diff::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? + self.store.stack.values.binary_diff::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? }; } @@ -79,15 +77,15 @@ impl<'store, 'stack> Executor<'store, 'stack> { Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} Unreachable => self.exec_unreachable()?, - Drop32 => self.stack.values.drop::(), - Drop64 => self.stack.values.drop::(), - Drop128 => self.stack.values.drop::(), - DropRef => self.stack.values.drop::(), + Drop32 => self.store.stack.values.drop::(), + Drop64 => self.store.stack.values.drop::(), + Drop128 => self.store.stack.values.drop::(), + DropRef => self.store.stack.values.drop::(), - Select32 => self.stack.values.select::(), - Select64 => self.stack.values.select::(), - Select128 => self.stack.values.select::(), - SelectRef => self.stack.values.select::(), + Select32 => self.store.stack.values.select::(), + Select64 => self.store.stack.values.select::(), + Select128 => self.store.stack.values.select::(), + SelectRef => self.store.stack.values.select::(), Call(v) => return self.exec_call_direct::(*v), CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), @@ -352,7 +350,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), - V128Bitselect => self.stack.values.ternary_same::(|v1, v2, c| Ok(Value128::v128_bitselect(v1, v2, c))).to_cf()?, + V128Bitselect => self.store.stack.values.ternary_same::(|v1, v2, c| Ok(Value128::v128_bitselect(v1, v2, c))).to_cf()?, V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), @@ -670,31 +668,31 @@ impl<'store, 'stack> Executor<'store, 'stack> { wasm_func: Rc, owner: ModuleInstanceAddr, ) -> ControlFlow> { - let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals); + let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); if IS_RETURN_CALL { - self.cf.reuse_for(wasm_func, locals, self.stack.blocks.len() as u32, owner); + self.cf.reuse_for(wasm_func, locals, self.store.stack.blocks.len() as u32, owner); } else { - let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32); + let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.store.stack.blocks.len() as u32); self.cf.incr_instr_ptr(); // skip the call instruction - self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; + self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; } self.module.swap_with(self.cf.module_addr(), self.store); ControlFlow::Continue(()) } fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { - let params = self.stack.values.pop_params(&host_func.ty.params); + let params = self.store.stack.values.pop_params(&host_func.ty.params); let res = host_func .clone() .call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms) .to_cf()?; - self.stack.values.extend_from_wasmvalues(&res); + self.store.stack.values.extend_from_wasmvalues(&res); self.cf.incr_instr_ptr(); ControlFlow::Continue(()) } fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { - let func_inst = self.store.get_func(self.module.resolve_func_addr(v)); + let func_inst = self.store.state.get_func(self.module.resolve_func_addr(v)); match func_inst.func.clone() { crate::Function::Wasm(wasm_func) => self.exec_call::(wasm_func, func_inst.owner), crate::Function::Host(host_func) => self.exec_call_host(host_func), @@ -707,15 +705,15 @@ impl<'store, 'stack> Executor<'store, 'stack> { ) -> ControlFlow> { // verify that the table is of the right type, this should be validated by the parser already let func_ref = { - let table = self.store.get_table(self.module.resolve_table_addr(table_addr)); - let table_idx: u32 = self.stack.values.pop::() as u32; + let table_idx: u32 = self.store.stack.values.pop::() as u32; + let table = self.store.state.get_table(self.module.resolve_table_addr(table_addr)); assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); let table = table.get(table_idx).map_err(|_| Trap::UndefinedElement { index: table_idx as usize }.into()); let table = table.to_cf()?; table.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize }.into()).to_cf()? }; - let func_inst = self.store.get_func(func_ref); + let func_inst = self.store.state.get_func(func_ref); let call_ty = self.module.func_ty(type_addr); match func_inst.func.clone() { @@ -744,7 +742,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) { // truthy value is on the top of the stack, so enter the then block - if self.stack.values.pop::() != 0 { + if self.store.stack.values.pop::() != 0 { self.enter_block(end_offset, BlockType::If, (params, results)); return; } @@ -767,17 +765,17 @@ impl<'store, 'stack> Executor<'store, 'stack> { ((&*ty.params).into(), (&*ty.results).into()) } fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { - self.stack.blocks.push(BlockFrame { + self.store.stack.blocks.push(BlockFrame { instr_ptr: self.cf.instr_ptr(), end_instr_offset, - stack_ptr: self.stack.values.height(), + stack_ptr: self.store.stack.values.height(), results, params, ty, }); } fn exec_br(&mut self, to: u32) -> ControlFlow> { - if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { + if self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() { return self.exec_return(); } @@ -785,8 +783,8 @@ impl<'store, 'stack> Executor<'store, 'stack> { ControlFlow::Continue(()) } fn exec_br_if(&mut self, to: u32) -> ControlFlow> { - if self.stack.values.pop::() != 0 - && self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() + if self.store.stack.values.pop::() != 0 + && self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() { return self.exec_return(); } @@ -804,14 +802,14 @@ impl<'store, 'stack> Executor<'store, 'stack> { )))); } - let idx = self.stack.values.pop::(); + let idx = self.store.stack.values.pop::(); let to = match self.cf.instructions()[start..end].get(idx as usize) { None => default, Some(Instruction::BrLabel(to)) => *to, _ => return ControlFlow::Break(Some(Error::Other("br_table out of bounds".to_string()))), }; - if self.cf.break_to(to, &mut self.stack.values, &mut self.stack.blocks).is_none() { + if self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() { return self.exec_return(); } @@ -820,64 +818,68 @@ impl<'store, 'stack> Executor<'store, 'stack> { } fn exec_return(&mut self) -> ControlFlow> { let old = self.cf.block_ptr(); - match self.stack.call_stack.pop() { + match self.store.stack.call_stack.pop() { None => return ControlFlow::Break(None), Some(cf) => self.cf = cf, } if old > self.cf.block_ptr() { - self.stack.blocks.truncate(old); + self.store.stack.blocks.truncate(old); } self.module.swap_with(self.cf.module_addr(), self.store); ControlFlow::Continue(()) } fn exec_end_block(&mut self) { - let block = self.stack.blocks.pop(); - self.stack.values.truncate_keep(block.stack_ptr, block.results); + let block = self.store.stack.blocks.pop(); + self.store.stack.values.truncate_keep(block.stack_ptr, block.results); } fn exec_local_get(&mut self, local_index: u16) { let v = self.cf.locals.get::(local_index); - self.stack.values.push(v); + self.store.stack.values.push(v); } fn exec_local_set(&mut self, local_index: u16) { - let v = self.stack.values.pop::(); + let v = self.store.stack.values.pop::(); self.cf.locals.set(local_index, v); } fn exec_local_tee(&mut self, local_index: u16) { - let v = self.stack.values.peek::(); + let v = self.store.stack.values.peek::(); self.cf.locals.set(local_index, v); } fn exec_global_get(&mut self, global_index: u32) { - self.stack.values.push_dyn(self.store.get_global_val(self.module.resolve_global_addr(global_index))); + self.store + .stack + .values + .push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))); } fn exec_global_set(&mut self, global_index: u32) { - self.store.set_global_val(self.module.resolve_global_addr(global_index), self.stack.values.pop::().into()); + let val = self.store.stack.values.pop::().into(); + self.store.state.set_global_val(self.module.resolve_global_addr(global_index), val); } fn exec_const(&mut self, val: T) { - self.stack.values.push(val); + self.store.stack.values.push(val); } fn exec_ref_is_null(&mut self) { - let is_null = i32::from(self.stack.values.pop::().is_none()); - self.stack.values.push::(is_null); + let is_null = i32::from(self.store.stack.values.pop::().is_none()); + self.store.stack.values.push::(is_null); } fn exec_memory_size(&mut self, addr: u32) { - let mem = self.store.get_mem(self.module.resolve_mem_addr(addr)); + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(addr)); match mem.is_64bit() { - true => self.stack.values.push::(mem.page_count as i64), - false => self.stack.values.push::(mem.page_count as i32), + true => self.store.stack.values.push::(mem.page_count as i64), + false => self.store.stack.values.push::(mem.page_count as i32), } } fn exec_memory_grow(&mut self, addr: u32) { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(addr)); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); let prev_size = mem.page_count; let pages_delta = match mem.is_64bit() { - true => self.stack.values.pop::(), - false => i64::from(self.stack.values.pop::()), + true => self.store.stack.values.pop::(), + false => i64::from(self.store.stack.values.pop::()), }; match ( @@ -887,52 +889,52 @@ impl<'store, 'stack> Executor<'store, 'stack> { None => -1_i64, }, ) { - (true, size) => self.stack.values.push::(size), - (false, size) => self.stack.values.push::(size as i32), + (true, size) => self.store.stack.values.push::(size), + (false, size) => self.store.stack.values.push::(size as i32), }; } fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let src: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let src: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); if from == to { - let mem_from = self.store.get_mem_mut(self.module.resolve_mem_addr(from)); + let mem_from = self.store.state.get_mem_mut(self.module.resolve_mem_addr(from)); // copy within the same memory mem_from.copy_within(dst as usize, src as usize, size as usize)?; } else { // copy between two memories let (mem_from, mem_to) = - self.store.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?; + self.store.state.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?; mem_from.copy_from_slice(dst as usize, mem_to.load(src as usize, size as usize)?)?; } Ok(()) } fn exec_memory_fill(&mut self, addr: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let val: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let val: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(addr)); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); mem.fill(dst as usize, size as usize, val as u8) } fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let offset: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let offset: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); let data = self .store - .data + .state .data .get(self.module.resolve_data_addr(data_index) as usize) .ok_or_else(|| Error::Other("data not found".to_string()))?; let mem = self .store - .data + .state .memories .get_mut(self.module.resolve_mem_addr(mem_index) as usize) .ok_or_else(|| Error::Other("memory not found".to_string()))?; @@ -951,27 +953,29 @@ impl<'store, 'stack> Executor<'store, 'stack> { mem.store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)]) } fn exec_data_drop(&mut self, data_index: u32) { - self.store.get_data_mut(self.module.resolve_data_addr(data_index)).drop(); + self.store.state.get_data_mut(self.module.resolve_data_addr(data_index)).drop(); } fn exec_elem_drop(&mut self, elem_index: u32) { - self.store.get_elem_mut(self.module.resolve_elem_addr(elem_index)).drop(); + self.store.state.get_elem_mut(self.module.resolve_elem_addr(elem_index)).drop(); } fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); - let src: i32 = self.stack.values.pop(); - let dst: i32 = self.stack.values.pop(); + let size: i32 = self.store.stack.values.pop(); + let src: i32 = self.store.stack.values.pop(); + let dst: i32 = self.store.stack.values.pop(); if from == to { // copy within the same memory - self.store.get_table_mut(self.module.resolve_table_addr(from)).copy_within( + self.store.state.get_table_mut(self.module.resolve_table_addr(from)).copy_within( dst as usize, src as usize, size as usize, ) } else { // copy between two memories - let (table_from, table_to) = - self.store.get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?; + let (table_from, table_to) = self + .store + .state + .get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?; table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?) } } @@ -982,9 +986,9 @@ impl<'store, 'stack> Executor<'store, 'stack> { offset: u64, lane: u8, ) -> ControlFlow> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); - let mut imm = self.stack.values.pop::().to_mem_bytes(); - let val = self.stack.values.pop::() as u64; + let mut imm = self.store.stack.values.pop::().to_mem_bytes(); + let val = self.store.stack.values.pop::() as u64; + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { cold(); return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { @@ -998,7 +1002,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { let offset = lane as usize * LOAD_SIZE; imm[offset..offset + LOAD_SIZE].copy_from_slice(&val); - self.stack.values.push(Value128::from_mem_bytes(imm)); + self.store.stack.values.push(Value128::from_mem_bytes(imm)); ControlFlow::Continue(()) } @@ -1008,11 +1012,11 @@ impl<'store, 'stack> Executor<'store, 'stack> { offset: u64, cast: fn(LOAD) -> TARGET, ) -> ControlFlow> { - let mem = self.store.get_mem(self.module.resolve_mem_addr(mem_addr)); + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => u64::from(self.stack.values.pop::() as u32), + true => self.store.stack.values.pop::() as u64, + false => u64::from(self.store.stack.values.pop::() as u32), }; let Some(Ok(addr)) = offset.checked_add(addr).map(|a| a.try_into()) else { @@ -1024,7 +1028,7 @@ impl<'store, 'stack> Executor<'store, 'stack> { }))); }; let val = mem.load_as::(addr).to_cf()?; - self.stack.values.push(cast(val)); + self.store.stack.values.push(cast(val)); ControlFlow::Continue(()) } @@ -1034,15 +1038,15 @@ impl<'store, 'stack> Executor<'store, 'stack> { offset: u64, lane: u8, ) -> ControlFlow> { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - let bytes = self.stack.values.pop::().to_mem_bytes(); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); + let bytes = self.store.stack.values.pop::().to_mem_bytes(); let lane_offset = lane as usize * N; let mut val = [0u8; N]; val.copy_from_slice(&bytes[lane_offset..lane_offset + N]); let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => self.stack.values.pop::() as u32 as u64, + true => self.store.stack.values.pop::() as u64, + false => self.store.stack.values.pop::() as u32 as u64, }; if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { @@ -1058,13 +1062,13 @@ impl<'store, 'stack> Executor<'store, 'stack> { offset: u64, cast: fn(T) -> U, ) -> ControlFlow> { - let mem = self.store.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); - let val = self.stack.values.pop::(); + let val = self.store.stack.values.pop::(); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); let val = (cast(val)).to_mem_bytes(); let addr = match mem.is_64bit() { - true => self.stack.values.pop::() as u64, - false => u64::from(self.stack.values.pop::() as u32), + true => self.store.stack.values.pop::() as u64, + false => u64::from(self.store.stack.values.pop::() as u32), }; if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { @@ -1075,38 +1079,38 @@ impl<'store, 'stack> Executor<'store, 'stack> { } fn exec_table_get(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)); - let idx: i32 = self.stack.values.pop::(); + let idx: i32 = self.store.stack.values.pop::(); + let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); let v = table.get_wasm_val(idx as u32)?; - self.stack.values.push_dyn(v.into()); + self.store.stack.values.push_dyn(v.into()); Ok(()) } fn exec_table_set(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); - let val = self.stack.values.pop::(); - let idx = self.stack.values.pop::() as u32; + let val = self.store.stack.values.pop::(); + let idx = self.store.stack.values.pop::() as u32; + let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); table.set(idx, val.into()) } fn exec_table_size(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table(self.module.resolve_table_addr(table_index)); - self.stack.values.push_dyn(table.size().into()); + let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); + self.store.stack.values.push_dyn(table.size().into()); Ok(()) } fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { - let size: i32 = self.stack.values.pop(); // n - let offset: i32 = self.stack.values.pop(); // s - let dst: i32 = self.stack.values.pop(); // d + let size: i32 = self.store.stack.values.pop(); // n + let offset: i32 = self.store.stack.values.pop(); // s + let dst: i32 = self.store.stack.values.pop(); // d let elem = self .store - .data + .state .elements .get(self.module.resolve_elem_addr(elem_index) as usize) .ok_or_else(|| Error::Other("element not found".to_string()))?; let table = self .store - .data + .state .tables .get_mut(self.module.resolve_table_addr(table_index) as usize) .ok_or_else(|| Error::Other("table not found".to_string()))?; @@ -1133,25 +1137,25 @@ impl<'store, 'stack> Executor<'store, 'stack> { table.init(i64::from(dst), &items[offset as usize..(offset + size) as usize]) } fn exec_table_grow(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); let sz = table.size(); - let n = self.stack.values.pop::(); - let val = self.stack.values.pop::(); + let n = self.store.stack.values.pop::(); + let val = self.store.stack.values.pop::(); match table.grow(n, val.into()) { - Ok(()) => self.stack.values.push(sz), - Err(_) => self.stack.values.push(-1_i32), + Ok(()) => self.store.stack.values.push(sz), + Err(_) => self.store.stack.values.push(-1_i32), } Ok(()) } fn exec_table_fill(&mut self, table_index: u32) -> Result<()> { - let table = self.store.get_table_mut(self.module.resolve_table_addr(table_index)); + let table = self.store.state.get_table_mut(self.module.resolve_table_addr(table_index)); - let n = self.stack.values.pop::(); - let val = self.stack.values.pop::(); - let i = self.stack.values.pop::(); + let n = self.store.stack.values.pop::(); + let val = self.store.stack.values.pop::(); + let i = self.store.stack.values.pop::(); if unlikely(i + n > table.size()) { return Err(Error::Trap(Trap::TableOutOfBounds { diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 6d94ce10..6f296cee 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -9,16 +9,16 @@ mod no_std_floats; use crate::{Result, Store}; pub(crate) use value128::*; -pub use values::*; +pub(crate) use values::*; /// The main `TinyWasm` runtime. /// /// This is the default runtime used by `TinyWasm`. #[derive(Debug, Default)] -pub struct InterpreterRuntime {} +pub(crate) struct InterpreterRuntime; impl InterpreterRuntime { - pub(crate) fn exec(&self, store: &mut Store, stack: &mut stack::Stack) -> Result<()> { - executor::Executor::new(store, stack)?.run_to_completion() + pub(crate) fn exec(store: &mut Store) -> Result<()> { + executor::Executor::new(store)?.run_to_completion() } } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 12d3022c..35afca42 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -32,6 +32,7 @@ macro_rules! checked_conv_float { // Conversion with an intermediate unsigned type and error checking (three types) ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { $self + .store .stack .values .unary::<$from, $to>(|v| { diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs index e5d87a88..b35ad94f 100644 --- a/crates/tinywasm/src/interpreter/stack/block_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -1,4 +1,4 @@ -use crate::{StackConfig, unlikely}; +use crate::{engine::Config, unlikely}; use alloc::vec::Vec; use crate::interpreter::values::{StackHeight, StackLocation}; @@ -7,8 +7,12 @@ use crate::interpreter::values::{StackHeight, StackLocation}; pub(crate) struct BlockStack(Vec); impl BlockStack { - pub(crate) fn new(config: &StackConfig) -> Self { - Self(Vec::with_capacity(config.block_stack_init_size())) + pub(crate) fn new(config: &Config) -> Self { + Self(Vec::with_capacity(config.block_stack_init_size)) + } + + pub(crate) fn clear(&mut self) { + self.0.clear(); } #[inline(always)] diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index e0a4d6a6..9954beb0 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -6,7 +6,7 @@ use crate::interpreter::{Value128, values::*}; use crate::{Error, unlikely}; use alloc::boxed::Box; -use alloc::{rc::Rc, vec, vec::Vec}; +use alloc::{rc::Rc, vec::Vec}; use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; @@ -18,8 +18,13 @@ pub(crate) struct CallStack { impl CallStack { #[inline] - pub(crate) fn new(initial_frame: CallFrame) -> Self { - Self { stack: vec![initial_frame] } + pub(crate) fn new(config: &crate::engine::Config) -> Self { + Self { stack: Vec::with_capacity(config.call_stack_init_size) } + } + + pub(crate) fn reset(&mut self, call_frame: CallFrame) { + self.stack.clear(); + self.stack.push(call_frame); } #[inline] diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index a4706f18..3da262c1 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -6,7 +6,7 @@ pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; pub(crate) use call_stack::{CallFrame, CallStack, Locals}; pub(crate) use value_stack::ValueStack; -use crate::StackConfig; +use crate::engine::Config; /// A WebAssembly Stack #[derive(Debug)] @@ -17,11 +17,14 @@ pub(crate) struct Stack { } impl Stack { - pub(crate) fn new(call_frame: CallFrame, config: &StackConfig) -> Self { - Self { - values: ValueStack::new(config), - blocks: BlockStack::new(config), - call_stack: CallStack::new(call_frame), - } + pub(crate) fn new(config: &Config) -> Self { + Self { values: ValueStack::new(config), blocks: BlockStack::new(config), call_stack: CallStack::new(config) } + } + + /// Initialize the stack with the given call frame (used for starting execution) + pub(crate) fn initialize(&mut self, callframe: CallFrame) { + self.blocks.clear(); + self.values.clear(); + self.call_stack.reset(callframe); } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 016b8239..32dd3a83 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,7 +1,7 @@ use alloc::vec::Vec; use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; -use crate::{Result, StackConfig, interpreter::*}; +use crate::{Result, engine::Config, interpreter::*}; use super::Locals; @@ -14,15 +14,22 @@ pub(crate) struct ValueStack { } impl ValueStack { - pub(crate) fn new(config: &StackConfig) -> Self { + pub(crate) fn new(config: &Config) -> Self { Self { - stack_32: Vec::with_capacity(config.value_stack_32_init_size()), - stack_64: Vec::with_capacity(config.value_stack_64_init_size()), - stack_128: Vec::with_capacity(config.value_stack_128_init_size()), - stack_ref: Vec::with_capacity(config.value_stack_ref_init_size()), + stack_32: Vec::with_capacity(config.stack_32_init_size), + stack_64: Vec::with_capacity(config.stack_64_init_size), + stack_128: Vec::with_capacity(config.stack_128_init_size), + stack_ref: Vec::with_capacity(config.stack_ref_init_size), } } + pub(crate) fn clear(&mut self) { + self.stack_32.clear(); + self.stack_64.clear(); + self.stack_128.clear(); + self.stack_ref.clear(); + } + pub(crate) fn height(&self) -> StackLocation { StackLocation { s32: self.stack_32.len() as u32, @@ -32,6 +39,10 @@ impl ValueStack { } } + pub(crate) fn len(&self) -> usize { + self.stack_32.len() + self.stack_64.len() + self.stack_128.len() + self.stack_ref.len() + } + #[inline] pub(crate) fn peek(&self) -> T { T::stack_peek(self) diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index a176670c..07748b97 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -68,29 +68,6 @@ //! and other modules to be linked into the module when it is instantiated. //! //! See the [`Imports`] documentation for more information. -//! -//! ## Runtime Configuration -//! -//! For resource-constrained targets, you can configure the initial memory allocation: -//! -//! ```rust -//! use tinywasm::{Store, StackConfig}; -//! -//! // Create a store with minimal initial allocation (90% reduction in pre-allocated memory) -//! let config = StackConfig::new() -//! .with_value_stack_32_init_size(1024) // 1KB instead of 32KB -//! .with_value_stack_64_init_size(512) // 512B instead of 16KB -//! .with_value_stack_128_init_size(256) // 256B instead of 8KB -//! .with_value_stack_ref_init_size(128) // 128B instead of 1KB - -//! .with_block_stack_init_size(32); // 32 instead of 128 -//! let store = Store::with_config(config); -//! -//! // Or create a partial configuration (only override what you need) -//! let config = StackConfig::new() -//! .with_value_stack_32_init_size(2048); // Only override 32-bit stack size -//! let store = Store::with_config(config); -//! ``` mod std; extern crate alloc; @@ -128,13 +105,12 @@ mod module; mod reference; mod store; -/// Runtime for executing WebAssembly modules. -pub mod interpreter; -pub use interpreter::InterpreterRuntime; +mod interpreter; +use interpreter::InterpreterRuntime; -/// Configuration for the WebAssembly interpreter's stack preallocation. -pub mod config; -pub use config::StackConfig; +/// Global configuration for the WebAssembly interpreter +pub mod engine; +pub use engine::Engine; #[cfg(feature = "parser")] /// Re-export of [`tinywasm_parser`]. Requires `parser` feature. diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 9cafa7e4..70a2b016 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -3,8 +3,9 @@ use core::fmt::Debug; use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; -use crate::interpreter::{self, InterpreterRuntime, TinyWasmValue}; -use crate::{Error, Function, ModuleInstance, Result, StackConfig, Trap, cold}; +use crate::interpreter::TinyWasmValue; +use crate::interpreter::stack::Stack; +use crate::{Engine, Error, Function, ModuleInstance, Result, Trap, cold}; mod data; mod element; @@ -31,9 +32,9 @@ pub struct Store { id: usize, module_instances: Vec, - pub(crate) data: StoreData, - pub(crate) runtime: Runtime, - pub(crate) config: StackConfig, + pub(crate) engine: Engine, + pub(crate) state: State, + pub(crate) stack: Stack, } impl Debug for Store { @@ -42,28 +43,17 @@ impl Debug for Store { .field("id", &self.id) .field("module_instances", &self.module_instances) .field("data", &"...") - .field("runtime", &self.runtime) + .field("engine", &self.engine) .finish() } } -#[derive(Debug, Clone, Copy)] -pub(crate) enum Runtime { - Default, -} - impl Store { /// Create a new store pub fn new() -> Self { Self::default() } - /// Create a new store with the given stack configuration - pub fn with_config(config: StackConfig) -> Self { - let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - Self { id, module_instances: Vec::new(), data: StoreData::default(), runtime: Runtime::Default, config } - } - /// Get a module instance by the internal id pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { self.module_instances.get(addr as usize) @@ -72,13 +62,6 @@ impl Store { pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> ModuleInstance { self.module_instances[addr as usize].clone() } - - /// Create a new store with the given runtime - pub(crate) fn runtime(&self) -> interpreter::InterpreterRuntime { - match self.runtime { - Runtime::Default => InterpreterRuntime::default(), - } - } } impl PartialEq for Store { @@ -90,13 +73,8 @@ impl PartialEq for Store { impl Default for Store { fn default() -> Self { let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - Self { - id, - module_instances: Vec::new(), - data: StoreData::default(), - runtime: Runtime::Default, - config: StackConfig::default(), - } + let engine = Engine::default(); + Self { id, module_instances: Vec::new(), state: State::default(), stack: Stack::new(engine.config()), engine } } } @@ -105,7 +83,7 @@ impl Default for Store { /// /// Data should only be addressable by the module that owns it /// See -pub(crate) struct StoreData { +pub(crate) struct State { pub(crate) funcs: Vec, pub(crate) tables: Vec, pub(crate) memories: Vec, @@ -114,42 +92,23 @@ pub(crate) struct StoreData { pub(crate) data: Vec, } -impl Store { - /// Get the store's ID (unique per process) - pub fn id(&self) -> usize { - self.id - } - - pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { - self.module_instances.len() as ModuleInstanceAddr - } - - pub(crate) fn add_instance(&mut self, instance: ModuleInstance) { - assert!(instance.id() == self.module_instances.len() as ModuleInstanceAddr); - self.module_instances.push(instance); - } - - #[cold] - fn not_found_error(name: &str) -> Error { - Error::Other(format!("{name} not found")) - } - +impl State { /// Get the function at the actual index in the store #[inline] pub(crate) fn get_func(&self, addr: FuncAddr) -> &FunctionInstance { - &self.data.funcs[addr as usize] + &self.funcs[addr as usize] } /// Get the memory at the actual index in the store #[inline] pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { - &self.data.memories[addr as usize] + &self.memories[addr as usize] } /// Get the memory at the actual index in the store #[inline(always)] pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { - &mut self.data.memories[addr as usize] + &mut self.memories[addr as usize] } /// Get the memory at the actual index in the store @@ -159,7 +118,7 @@ impl Store { addr: MemAddr, addr2: MemAddr, ) -> Result<(&mut MemoryInstance, &mut MemoryInstance)> { - match get_pair_mut(&mut self.data.memories, addr as usize, addr2 as usize) { + match get_pair_mut(&mut self.memories, addr as usize, addr2 as usize) { Some(mems) => Ok(mems), None => { cold(); @@ -171,13 +130,13 @@ impl Store { /// Get the table at the actual index in the store #[inline] pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { - &self.data.tables[addr as usize] + &self.tables[addr as usize] } /// Get the table at the actual index in the store #[inline] pub(crate) fn get_table_mut(&mut self, addr: TableAddr) -> &mut TableInstance { - &mut self.data.tables[addr as usize] + &mut self.tables[addr as usize] } /// Get two mutable tables at the actual index in the store @@ -187,7 +146,7 @@ impl Store { addr: TableAddr, addr2: TableAddr, ) -> Result<(&mut TableInstance, &mut TableInstance)> { - match get_pair_mut(&mut self.data.tables, addr as usize, addr2 as usize) { + match get_pair_mut(&mut self.tables, addr as usize, addr2 as usize) { Some(tables) => Ok(tables), None => { cold(); @@ -199,31 +158,62 @@ impl Store { /// Get the data at the actual index in the store #[inline] pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { - &mut self.data.data[addr as usize] + &mut self.data[addr as usize] } /// Get the element at the actual index in the store #[inline] pub(crate) fn get_elem_mut(&mut self, addr: ElemAddr) -> &mut ElementInstance { - &mut self.data.elements[addr as usize] + &mut self.elements[addr as usize] } /// Get the global at the actual index in the store #[inline] pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { - &self.data.globals[addr as usize] + &self.globals[addr as usize] + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { + self.globals[addr as usize].value.get() + } + + /// Set the global at the actual index in the store + pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { + self.globals[addr as usize].value.set(value); + } + + #[cold] + fn not_found_error(name: &str) -> Error { + Error::Other(format!("{name} not found")) + } +} + +impl Store { + /// Get the store's ID (unique per process) + pub fn id(&self) -> usize { + self.id + } + + pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { + self.module_instances.len() as ModuleInstanceAddr + } + + pub(crate) fn add_instance(&mut self, instance: ModuleInstance) { + assert!(instance.id() == self.module_instances.len() as ModuleInstanceAddr); + self.module_instances.push(instance); } /// Get the global at the actual index in the store #[doc(hidden)] pub fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { - self.data.globals[addr as usize].value.get() + self.state.get_global_val(addr) } /// Set the global at the actual index in the store #[doc(hidden)] pub fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { - self.data.globals[addr as usize].value.set(value); + self.state.set_global_val(addr, value); } } @@ -231,10 +221,10 @@ impl Store { impl Store { /// Add functions to the store, returning their addresses in the store pub(crate) fn init_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Result> { - let func_count = self.data.funcs.len(); + let func_count = self.state.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); for (i, func) in funcs.into_iter().enumerate() { - self.data.funcs.push(FunctionInstance::new_wasm(func, idx)); + self.state.funcs.push(FunctionInstance::new_wasm(func, idx)); func_addrs.push((i + func_count) as FuncAddr); } Ok(func_addrs) @@ -242,10 +232,10 @@ impl Store { /// Add tables to the store, returning their addresses in the store pub(crate) fn init_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Result> { - let table_count = self.data.tables.len(); + let table_count = self.state.tables.len(); let mut table_addrs = Vec::with_capacity(table_count); for (i, table) in tables.into_iter().enumerate() { - self.data.tables.push(TableInstance::new(table, idx)); + self.state.tables.push(TableInstance::new(table, idx)); table_addrs.push((i + table_count) as TableAddr); } Ok(table_addrs) @@ -253,10 +243,10 @@ impl Store { /// Add memories to the store, returning their addresses in the store pub(crate) fn init_memories(&mut self, memories: Vec, idx: ModuleInstanceAddr) -> Result> { - let mem_count = self.data.memories.len(); + let mem_count = self.state.memories.len(); let mut mem_addrs = Vec::with_capacity(mem_count); for (i, mem) in memories.into_iter().enumerate() { - self.data.memories.push(MemoryInstance::new(mem, idx)); + self.state.memories.push(MemoryInstance::new(mem, idx)); mem_addrs.push((i + mem_count) as MemAddr); } Ok(mem_addrs) @@ -270,12 +260,12 @@ impl Store { func_addrs: &[FuncAddr], idx: ModuleInstanceAddr, ) -> Result> { - let global_count = self.data.globals.len(); + let global_count = self.state.globals.len(); imported_globals.reserve_exact(new_globals.len()); let mut global_addrs = imported_globals; for (i, global) in new_globals.iter().enumerate() { - self.data.globals.push(GlobalInstance::new( + self.state.globals.push(GlobalInstance::new( global.ty, self.eval_const(&global.init, &global_addrs, func_addrs)?, idx, @@ -299,7 +289,7 @@ impl Store { let addr = globals.get(*addr as usize).copied().ok_or_else(|| { Error::Other(format!("global {addr} not found. This should have been caught by the validator")) })?; - self.data.globals[addr as usize].value.get().unwrap_ref() + self.state.globals[addr as usize].value.get().unwrap_ref() } ElementItem::Expr(item) => { return Err(Error::UnsupportedFeature(format!("const expression other than ref: {item:?}"))); @@ -319,7 +309,7 @@ impl Store { elements: &[Element], idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { - let elem_count = self.data.elements.len(); + let elem_count = self.state.elements.len(); let mut elem_addrs = Vec::with_capacity(elem_count); for (i, element) in elements.iter().enumerate() { let init = element @@ -343,7 +333,7 @@ impl Store { .copied() .ok_or_else(|| Error::Other(format!("table {table} not found for element {i}")))?; - let Some(table) = self.data.tables.get_mut(table_addr as usize) else { + let Some(table) = self.state.tables.get_mut(table_addr as usize) else { return Err(Error::Other(format!("table {table} not found for element {i}"))); }; @@ -361,7 +351,7 @@ impl Store { } }; - self.data.elements.push(ElementInstance::new(element.kind, idx, items)); + self.state.elements.push(ElementInstance::new(element.kind, idx, items)); elem_addrs.push((i + elem_count) as Addr); } @@ -376,7 +366,7 @@ impl Store { data: Vec, idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { - let data_count = self.data.data.len(); + let data_count = self.state.data.len(); let mut data_addrs = Vec::with_capacity(data_count); for (i, data) in data.into_iter().enumerate() { let data_val = match data.kind { @@ -386,7 +376,7 @@ impl Store { }; let offset = self.eval_size_const(offset)?; - let Some(mem) = self.data.memories.get_mut(*mem_addr as usize) else { + let Some(mem) = self.state.memories.get_mut(*mem_addr as usize) else { return Err(Error::Other(format!("memory {mem_addr} not found for data segment {i}"))); }; @@ -399,7 +389,7 @@ impl Store { tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), }; - self.data.data.push(DataInstance::new(data_val, idx)); + self.state.data.push(DataInstance::new(data_val, idx)); data_addrs.push((i + data_count) as Addr); } @@ -408,26 +398,26 @@ impl Store { } pub(crate) fn add_global(&mut self, ty: GlobalType, value: TinyWasmValue, idx: ModuleInstanceAddr) -> Result { - self.data.globals.push(GlobalInstance::new(ty, value, idx)); - Ok(self.data.globals.len() as Addr - 1) + self.state.globals.push(GlobalInstance::new(ty, value, idx)); + Ok(self.state.globals.len() as Addr - 1) } pub(crate) fn add_table(&mut self, table: TableType, idx: ModuleInstanceAddr) -> Result { - self.data.tables.push(TableInstance::new(table, idx)); - Ok(self.data.tables.len() as TableAddr - 1) + self.state.tables.push(TableInstance::new(table, idx)); + Ok(self.state.tables.len() as TableAddr - 1) } pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { if let MemoryArch::I64 = mem.arch() { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } - self.data.memories.push(MemoryInstance::new(mem, idx)); - Ok(self.data.memories.len() as MemAddr - 1) + self.state.memories.push(MemoryInstance::new(mem, idx)); + Ok(self.state.memories.len() as MemAddr - 1) } pub(crate) fn add_func(&mut self, func: Function, idx: ModuleInstanceAddr) -> Result { - self.data.funcs.push(FunctionInstance { func, owner: idx }); - Ok(self.data.funcs.len() as FuncAddr - 1) + self.state.funcs.push(FunctionInstance { func, owner: idx }); + Ok(self.state.funcs.len() as FuncAddr - 1) } /// Evaluate a constant expression that's either a i32 or a i64 as a global or a const instruction @@ -435,7 +425,7 @@ impl Store { Ok(match const_instr { ConstInstruction::I32Const(i) => i64::from(i), ConstInstruction::I64Const(i) => i, - ConstInstruction::GlobalGet(addr) => match self.data.globals[addr as usize].value.get() { + ConstInstruction::GlobalGet(addr) => match self.state.globals[addr as usize].value.get() { TinyWasmValue::Value32(i) => i64::from(i), TinyWasmValue::Value64(i) => i as i64, o => return Err(Error::Other(format!("expected i32 or i64, got {o:?}"))), @@ -464,7 +454,7 @@ impl Store { })?; let global = - self.data.globals.get(*addr as usize).expect("global not found. This should be unreachable"); + self.state.globals.get(*addr as usize).expect("global not found. This should be unreachable"); global.value.get() } RefFunc(None) => TinyWasmValue::ValueRef(None), From 9d19098154714d5196a803e7f098105690decbbd Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 23 Mar 2026 23:24:37 +0100 Subject: [PATCH 10/47] chore: cleanup Signed-off-by: Henry --- Cargo.lock | 11 +- crates/cli/src/bin.rs | 2 +- crates/parser/src/lib.rs | 2 +- crates/tinywasm/Cargo.toml | 4 - crates/tinywasm/src/func.rs | 3 +- crates/tinywasm/src/interpreter/executor.rs | 249 +++++------------- .../src/interpreter/stack/call_stack.rs | 12 +- crates/tinywasm/src/interpreter/stack/mod.rs | 2 +- .../src/interpreter/stack/value_stack.rs | 26 +- crates/tinywasm/src/interpreter/values.rs | 8 +- 10 files changed, 96 insertions(+), 223 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c041540..04471e22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,12 +20,6 @@ dependencies = [ "cc", ] -[[package]] -name = "allocator-api2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" - [[package]] name = "anes" version = "0.1.6" @@ -374,9 +368,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "leb128fmt" @@ -652,7 +646,6 @@ dependencies = [ name = "tinywasm" version = "0.9.0-alpha.0" dependencies = [ - "allocator-api2", "criterion", "eyre", "indexmap", diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index d00848e3..235498c6 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -84,7 +84,7 @@ fn main() -> Result<()> { TinyWasmSubcommand::Run(Run { wasm_file, engine, args, func }) => { debug!("args: {args:?}"); - let path = cwd.join(wasm_file.clone()); + let path = cwd.join(&wasm_file); let module = match wasm_file.ends_with(".wat") { #[cfg(feature = "wat")] true => { diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index b65c47b4..73bded05 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -116,7 +116,7 @@ impl Parser { /// 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()) + let f = crate::std::fs::File::open(&path) .map_err(|e| ParseError::Other(format!("Error opening file {:?}: {}", path.as_ref(), e)))?; let mut reader = crate::std::io::BufReader::new(f); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 1ff40322..d1a08397 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -20,7 +20,6 @@ log={workspace=true, optional=true} tinywasm-parser={version="0.9.0-alpha.0", path="../parser", default-features=false, optional=true} tinywasm-types={version="0.9.0-alpha.0", path="../types", default-features=false} libm={version="0.2", default-features=false} -allocator-api2={version="0.4", optional=true} [dev-dependencies] wasm-testsuite.workspace=true @@ -49,9 +48,6 @@ archive=["tinywasm-types/archive"] # canonicalize all NaN values to a single representation canonicalize_nans=[] -# support for the allocator-api2 crate -allocator-api2=["dep:allocator-api2"] - [[test]] name="test-wasm-1" harness=false diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 87713bb7..5d5b56bb 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -70,7 +70,8 @@ impl FuncHandle { debug_assert!(store.stack.values.len() >= func_ty.results.len()); // 2. Pop m values from the stack - let res = store.stack.values.pop_results(&func_ty.results); + let mut res: Vec<_> = store.stack.values.pop_types(func_ty.results.iter().rev()).collect(); // pop in reverse order since the stack is LIFO + res.reverse(); // reverse to get the original order // The values are returned as the results of the invocation. Ok(res) diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index fd248969..dbeceb9b 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -2,6 +2,7 @@ #[allow(unused_imports)] use super::no_std_floats::NoStdFloatExt; +use alloc::boxed::Box; use alloc::{format, rc::Rc, string::ToString}; use core::ops::ControlFlow; @@ -75,24 +76,19 @@ impl<'store> Executor<'store> { #[rustfmt::skip] match self.cf.fetch_instr() { Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} - Unreachable => self.exec_unreachable()?, - + Unreachable => return ControlFlow::Break(Some(Trap::Unreachable.into())), Drop32 => self.store.stack.values.drop::(), Drop64 => self.store.stack.values.drop::(), Drop128 => self.store.stack.values.drop::(), DropRef => self.store.stack.values.drop::(), - Select32 => self.store.stack.values.select::(), Select64 => self.store.stack.values.select::(), Select128 => self.store.stack.values.select::(), SelectRef => self.store.stack.values.select::(), - Call(v) => return self.exec_call_direct::(*v), CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - ReturnCall(v) => return self.exec_call_direct::(*v), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())), IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())), IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)), @@ -108,141 +104,79 @@ impl<'store> Executor<'store> { BrTable(default, len) => return self.exec_brtable(*default, *len), Return => return self.exec_return(), EndBlockFrame => self.exec_end_block(), - LocalGet32(local_index) => self.exec_local_get::(*local_index), LocalGet64(local_index) => self.exec_local_get::(*local_index), LocalGet128(local_index) => self.exec_local_get::(*local_index), LocalGetRef(local_index) => self.exec_local_get::(*local_index), - LocalSet32(local_index) => self.exec_local_set::(*local_index), LocalSet64(local_index) => self.exec_local_set::(*local_index), LocalSet128(local_index) => self.exec_local_set::(*local_index), LocalSetRef(local_index) => self.exec_local_set::(*local_index), - + LocalCopy32(from, to) => self.exec_local_copy::(*from, *to), + LocalCopy64(from, to) => self.exec_local_copy::(*from, *to), + LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), + LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), LocalTee32(local_index) => self.exec_local_tee::(*local_index), LocalTee64(local_index) => self.exec_local_tee::(*local_index), LocalTee128(local_index) => self.exec_local_tee::(*local_index), LocalTeeRef(local_index) => self.exec_local_tee::(*local_index), - GlobalGet(global_index) => self.exec_global_get(*global_index), GlobalSet32(global_index) => self.exec_global_set::(*global_index), GlobalSet64(global_index) => self.exec_global_set::(*global_index), GlobalSet128(global_index) => self.exec_global_set::(*global_index), GlobalSetRef(global_index) => self.exec_global_set::(*global_index), - I32Const(val) => self.exec_const(*val), I64Const(val) => self.exec_const(*val), F32Const(val) => self.exec_const(*val), F64Const(val) => self.exec_const(*val), - - // Reference types - RefFunc(func_idx) => self.exec_const::(Some(*func_idx)), - RefNull(_) => self.exec_const::(None), - RefIsNull => self.exec_ref_is_null(), - - MemorySize(addr) => self.exec_memory_size(*addr), - MemoryGrow(addr) => self.exec_memory_grow(*addr), - - // Bulk memory operations - MemoryCopy(from, to) => self.exec_memory_copy(*from, *to).to_cf()?, - MemoryFill(addr) => self.exec_memory_fill(*addr).to_cf()?, - MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx).to_cf()?, - DataDrop(data_index) => self.exec_data_drop(*data_index), - ElemDrop(elem_index) => self.exec_elem_drop(*elem_index), - - // Table instructions - TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, - TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, - TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, - TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, - TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, - TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, - TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, - - // Core memory load/store operations - I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - F64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - I32Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, - I32Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, - I64Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, - I64Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, - I64Store32(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i32)?, - - I32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - I64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - F32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - F64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - I32Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), I64Eq => stack_op!(binary i64 => i32, |a, b| i32::from(a == b)), F32Eq => stack_op!(binary f32 => i32, |a, b| i32::from(a == b)), F64Eq => stack_op!(binary f64 => i32, |a, b| i32::from(a == b)), - I32Ne => stack_op!(binary i32, |a, b| i32::from(a != b)), I64Ne => stack_op!(binary i64 => i32, |a, b| i32::from(a != b)), F32Ne => stack_op!(binary f32 => i32, |a, b| i32::from(a != b)), F64Ne => stack_op!(binary f64 => i32, |a, b| i32::from(a != b)), - I32LtS => stack_op!(binary i32, |a, b| i32::from(a < b)), I64LtS => stack_op!(binary i64 => i32, |a, b| i32::from(a < b)), I32LtU => stack_op!(binary u32 => i32, |a, b| i32::from(a < b)), I64LtU => stack_op!(binary u64 => i32, |a, b| i32::from(a < b)), F32Lt => stack_op!(binary f32 => i32, |a, b| i32::from(a < b)), F64Lt => stack_op!(binary f64 => i32, |a, b| i32::from(a < b)), - I32LeS => stack_op!(binary i32, |a, b| i32::from(a <= b)), I64LeS => stack_op!(binary i64 => i32, |a, b| i32::from(a <= b)), I32LeU => stack_op!(binary u32 => i32, |a, b| i32::from(a <= b)), I64LeU => stack_op!(binary u64 => i32, |a, b| i32::from(a <= b)), F32Le => stack_op!(binary f32 => i32, |a, b| i32::from(a <= b)), F64Le => stack_op!(binary f64 => i32, |a, b| i32::from(a <= b)), - I32GeS => stack_op!(binary i32, |a, b| i32::from(a >= b)), I64GeS => stack_op!(binary i64 => i32, |a, b| i32::from(a >= b)), I32GeU => stack_op!(binary u32 => i32, |a, b| i32::from(a >= b)), I64GeU => stack_op!(binary u64 => i32, |a, b| i32::from(a >= b)), F32Ge => stack_op!(binary f32 => i32, |a, b| i32::from(a >= b)), F64Ge => stack_op!(binary f64 => i32, |a, b| i32::from(a >= b)), - I32GtS => stack_op!(binary i32, |a, b| i32::from(a > b)), I64GtS => stack_op!(binary i64 => i32, |a, b| i32::from(a > b)), I32GtU => stack_op!(binary u32 => i32, |a, b| i32::from(a > b)), I64GtU => stack_op!(binary u64 => i32, |a, b| i32::from(a > b)), F32Gt => stack_op!(binary f32 => i32, |a, b| i32::from(a > b)), F64Gt => stack_op!(binary f64 => i32, |a, b| i32::from(a > b)), - I32Add => stack_op!(binary i32, |a, b| a.wrapping_add(b)), I64Add => stack_op!(binary i64, |a, b| a.wrapping_add(b)), F32Add => stack_op!(binary f32, |a, b| a + b), F64Add => stack_op!(binary f64, |a, b| a + b), - I32Sub => stack_op!(binary i32, |a, b| a.wrapping_sub(b)), I64Sub => stack_op!(binary i64, |a, b| a.wrapping_sub(b)), F32Sub => stack_op!(binary f32, |a, b| a - b), F64Sub => stack_op!(binary f64, |a, b| a - b), - F32Div => stack_op!(binary f32, |a, b| a / b), F64Div => stack_op!(binary f64, |a, b| a / b), - I32Mul => stack_op!(binary i32, |a, b| a.wrapping_mul(b)), I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), F32Mul => stack_op!(binary f32, |a, b| a * b), F64Mul => stack_op!(binary f64, |a, b| a * b), - I32DivS => stack_op!(binary_try i32, |a, b| a.wasm_checked_div(b)), I64DivS => stack_op!(binary_try i64, |a, b| a.wasm_checked_div(b)), I32DivU => stack_op!(binary_try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), @@ -251,7 +185,6 @@ impl<'store> Executor<'store> { I64RemS => stack_op!(binary_try i64, |a, b| a.checked_wrapping_rem(b)), I32RemU => stack_op!(binary_try u32, |a, b| a.checked_wrapping_rem(b)), I64RemU => stack_op!(binary_try u64, |a, b| a.checked_wrapping_rem(b)), - I32And => stack_op!(binary i32, |a, b| a & b), I64And => stack_op!(binary i64, |a, b| a & b), I32Or => stack_op!(binary i32, |a, b| a | b), @@ -268,7 +201,6 @@ impl<'store> Executor<'store> { I64Rotl => stack_op!(binary i64, |a, b| a.wasm_rotl(b)), I32Rotr => stack_op!(binary i32, |a, b| a.wasm_rotr(b)), I64Rotr => stack_op!(binary i64, |a, b| a.wasm_rotr(b)), - I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), @@ -276,6 +208,54 @@ impl<'store> Executor<'store> { I32Popcnt => stack_op!(unary i32, |v| v.count_ones() as i32), I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), + // Reference types + RefFunc(func_idx) => self.exec_const::(Some(*func_idx)), + RefNull(_) => self.exec_const::(None), + RefIsNull => self.exec_ref_is_null(), + MemorySize(addr) => self.exec_memory_size(*addr), + MemoryGrow(addr) => self.exec_memory_grow(*addr), + + // Bulk memory operations + MemoryCopy(from, to) => self.exec_memory_copy(*from, *to).to_cf()?, + MemoryFill(addr) => self.exec_memory_fill(*addr).to_cf()?, + MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx).to_cf()?, + DataDrop(data_index) => self.store.state.get_data_mut(self.module.resolve_data_addr(*data_index)).drop(), + ElemDrop(elem_index) => self.store.state.get_elem_mut(self.module.resolve_elem_addr(*elem_index)).drop(), + + // Table instructions + TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, + TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, + TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, + TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, + TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, + TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, + TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, + + // Core memory load/store operations + I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + F64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + I32Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, + I32Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, + I64Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, + I64Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, + I64Store32(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i32)?, + I32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + I64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + F32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + F64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + I32Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + // Numeric conversion operations F32ConvertI32S => stack_op!(unary i32 => f32, |v| v as f32), F32ConvertI64S => stack_op!(unary i64 => f32, |v| v as f32), @@ -295,10 +275,8 @@ impl<'store> Executor<'store> { I64ExtendI32U => stack_op!(unary u32 => i64, |v| i64::from(v)), I64ExtendI32S => stack_op!(unary i32 => i64, |v| i64::from(v)), I32WrapI64 => stack_op!(unary i64 => i32, |v| v as i32), - F32DemoteF64 => stack_op!(unary f64 => f32, |v| v as f32), F64PromoteF32 => stack_op!(unary f32 => f64, |v| f64::from(v)), - F32Abs => stack_op!(unary f32, |v| v.abs()), F64Abs => stack_op!(unary f64, |v| v.abs()), F32Neg => stack_op!(unary f32, |v| -v), @@ -319,7 +297,6 @@ impl<'store> Executor<'store> { F64Max => stack_op!(binary f64, |a, b| a.tw_maximum(b)), F32Copysign => stack_op!(binary f32, |a, b| a.copysign(b)), F64Copysign => stack_op!(binary f64, |a, b| a.copysign(b)), - I32TruncF32S => checked_conv_float!(f32, i32, self), I32TruncF64S => checked_conv_float!(f64, i32, self), I32TruncF32U => checked_conv_float!(f32, u32, i32, self), @@ -339,11 +316,6 @@ impl<'store> Executor<'store> { I64TruncSatF64S => stack_op!(unary f64 => i64, |v| v.trunc() as i64), I64TruncSatF64U => stack_op!(unary f64 => u64, |v| v.trunc() as u64), - LocalCopy32(from, to) => self.exec_local_copy::(*from, *to), - LocalCopy64(from, to) => self.exec_local_copy::(*from, *to), - LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), - LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), - // SIMD extension V128Not => stack_op!(unary Value128, |v| v.v128_not()), V128And => stack_op!(binary Value128, |a, b| a.v128_and(b)), @@ -353,7 +325,6 @@ impl<'store> Executor<'store> { V128Bitselect => self.store.stack.values.ternary_same::(|v1, v2, c| Ok(Value128::v128_bitselect(v1, v2, c))).to_cf()?, V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), - V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, @@ -365,20 +336,14 @@ impl<'store> Executor<'store> { V128Load16Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i16)?, V128Load32Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i32)?, V128Load64Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i64)?, - V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, - V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - - // Load a single 32-bit or 64-bit element into the lowest bits of a v128 vector, and initialize all other bits of the v128 vector to zero. V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, - V128Const(arg) => self.exec_const::(self.cf.data().v128_constants[*arg as usize].into()), - I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), @@ -387,40 +352,34 @@ impl<'store> Executor<'store> { I64x2ExtractLane(lane) => stack_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), F32x4ExtractLane(lane) => stack_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), F64x2ExtractLane(lane) => stack_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), - V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - I8x16ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), I16x8ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), I32x4ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), I64x2ReplaceLane(lane) => stack_op!(binary i64, Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), F32x4ReplaceLane(lane) => stack_op!(binary f32, Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), F64x2ReplaceLane(lane) => stack_op!(binary f64, Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), - I8x16Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), I16x8Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), I32x4Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), I64x2Splat => stack_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), F32x4Splat => stack_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), F64x2Splat => stack_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), - I8x16Eq => stack_op!(binary Value128, |a, b| a.i8x16_eq(b)), I16x8Eq => stack_op!(binary Value128, |a, b| a.i16x8_eq(b)), I32x4Eq => stack_op!(binary Value128, |a, b| a.i32x4_eq(b)), I64x2Eq => stack_op!(binary Value128, |a, b| a.i64x2_eq(b)), F32x4Eq => stack_op!(binary Value128, |a, b| a.f32x4_eq(b)), F64x2Eq => stack_op!(binary Value128, |a, b| a.f64x2_eq(b)), - I8x16Ne => stack_op!(binary Value128, |a, b| a.i8x16_ne(b)), I16x8Ne => stack_op!(binary Value128, |a, b| a.i16x8_ne(b)), I32x4Ne => stack_op!(binary Value128, |a, b| a.i32x4_ne(b)), I64x2Ne => stack_op!(binary Value128, |a, b| a.i64x2_ne(b)), F32x4Ne => stack_op!(binary Value128, |a, b| a.f32x4_ne(b)), F64x2Ne => stack_op!(binary Value128, |a, b| a.f64x2_ne(b)), - I8x16LtS => stack_op!(binary Value128, |a, b| a.i8x16_lt_s(b)), I16x8LtS => stack_op!(binary Value128, |a, b| a.i16x8_lt_s(b)), I32x4LtS => stack_op!(binary Value128, |a, b| a.i32x4_lt_s(b)), @@ -430,10 +389,8 @@ impl<'store> Executor<'store> { I32x4LtU => stack_op!(binary Value128, |a, b| a.i32x4_lt_u(b)), F32x4Lt => stack_op!(binary Value128, |a, b| a.f32x4_lt(b)), F64x2Lt => stack_op!(binary Value128, |a, b| a.f64x2_lt(b)), - F32x4Gt => stack_op!(binary Value128, |a, b| a.f32x4_gt(b)), F64x2Gt => stack_op!(binary Value128, |a, b| a.f64x2_gt(b)), - I8x16GtS => stack_op!(binary Value128, |a, b| a.i8x16_gt_s(b)), I16x8GtS => stack_op!(binary Value128, |a, b| a.i16x8_gt_s(b)), I32x4GtS => stack_op!(binary Value128, |a, b| a.i32x4_gt_s(b)), @@ -441,100 +398,79 @@ impl<'store> Executor<'store> { I64x2LeS => stack_op!(binary Value128, |a, b| a.i64x2_le_s(b)), F32x4Le => stack_op!(binary Value128, |a, b| a.f32x4_le(b)), F64x2Le => stack_op!(binary Value128, |a, b| a.f64x2_le(b)), - I8x16GtU => stack_op!(binary Value128, |a, b| a.i8x16_gt_u(b)), I16x8GtU => stack_op!(binary Value128, |a, b| a.i16x8_gt_u(b)), I32x4GtU => stack_op!(binary Value128, |a, b| a.i32x4_gt_u(b)), F32x4Ge => stack_op!(binary Value128, |a, b| a.f32x4_ge(b)), F64x2Ge => stack_op!(binary Value128, |a, b| a.f64x2_ge(b)), - I8x16LeS => stack_op!(binary Value128, |a, b| a.i8x16_le_s(b)), I16x8LeS => stack_op!(binary Value128, |a, b| a.i16x8_le_s(b)), I32x4LeS => stack_op!(binary Value128, |a, b| a.i32x4_le_s(b)), - I8x16LeU => stack_op!(binary Value128, |a, b| a.i8x16_le_u(b)), I16x8LeU => stack_op!(binary Value128, |a, b| a.i16x8_le_u(b)), I32x4LeU => stack_op!(binary Value128, |a, b| a.i32x4_le_u(b)), - I8x16GeS => stack_op!(binary Value128, |a, b| a.i8x16_ge_s(b)), I16x8GeS => stack_op!(binary Value128, |a, b| a.i16x8_ge_s(b)), I32x4GeS => stack_op!(binary Value128, |a, b| a.i32x4_ge_s(b)), I64x2GeS => stack_op!(binary Value128, |a, b| a.i64x2_ge_s(b)), - I8x16GeU => stack_op!(binary Value128, |a, b| a.i8x16_ge_u(b)), I16x8GeU => stack_op!(binary Value128, |a, b| a.i16x8_ge_u(b)), I32x4GeU => stack_op!(binary Value128, |a, b| a.i32x4_ge_u(b)), - I8x16Abs => stack_op!(unary Value128, |a| a.i8x16_abs()), I16x8Abs => stack_op!(unary Value128, |a| a.i16x8_abs()), I32x4Abs => stack_op!(unary Value128, |a| a.i32x4_abs()), I64x2Abs => stack_op!(unary Value128, |a| a.i64x2_abs()), - I8x16Neg => stack_op!(unary Value128, |a| a.i8x16_neg()), I16x8Neg => stack_op!(unary Value128, |a| a.i16x8_neg()), I32x4Neg => stack_op!(unary Value128, |a| a.i32x4_neg()), I64x2Neg => stack_op!(unary Value128, |a| a.i64x2_neg()), - I8x16AllTrue => stack_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), I16x8AllTrue => stack_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), I32x4AllTrue => stack_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), I64x2AllTrue => stack_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), - I8x16Bitmask => stack_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), I16x8Bitmask => stack_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), I32x4Bitmask => stack_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), I64x2Bitmask => stack_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), - I8x16Shl => stack_op!(binary i32, Value128, |a, b| b.i8x16_shl(a as u32)), I16x8Shl => stack_op!(binary i32, Value128, |a, b| b.i16x8_shl(a as u32)), I32x4Shl => stack_op!(binary i32, Value128, |a, b| b.i32x4_shl(a as u32)), I64x2Shl => stack_op!(binary i32, Value128, |a, b| b.i64x2_shl(a as u32)), - I8x16ShrS => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_s(a as u32)), I16x8ShrS => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_s(a as u32)), I32x4ShrS => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_s(a as u32)), I64x2ShrS => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_s(a as u32)), - I8x16ShrU => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_u(a as u32)), I16x8ShrU => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_u(a as u32)), I32x4ShrU => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_u(a as u32)), I64x2ShrU => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_u(a as u32)), - I8x16Add => stack_op!(binary Value128, |a, b| a.i8x16_add(b)), I16x8Add => stack_op!(binary Value128, |a, b| a.i16x8_add(b)), I32x4Add => stack_op!(binary Value128, |a, b| a.i32x4_add(b)), I64x2Add => stack_op!(binary Value128, |a, b| a.i64x2_add(b)), - I8x16Sub => stack_op!(binary Value128, |a, b| a.i8x16_sub(b)), I16x8Sub => stack_op!(binary Value128, |a, b| a.i16x8_sub(b)), I32x4Sub => stack_op!(binary Value128, |a, b| a.i32x4_sub(b)), I64x2Sub => stack_op!(binary Value128, |a, b| a.i64x2_sub(b)), - I8x16MinS => stack_op!(binary Value128, |a, b| a.i8x16_min_s(b)), I16x8MinS => stack_op!(binary Value128, |a, b| a.i16x8_min_s(b)), I32x4MinS => stack_op!(binary Value128, |a, b| a.i32x4_min_s(b)), - I8x16MinU => stack_op!(binary Value128, |a, b| a.i8x16_min_u(b)), I16x8MinU => stack_op!(binary Value128, |a, b| a.i16x8_min_u(b)), I32x4MinU => stack_op!(binary Value128, |a, b| a.i32x4_min_u(b)), - I8x16MaxS => stack_op!(binary Value128, |a, b| a.i8x16_max_s(b)), I16x8MaxS => stack_op!(binary Value128, |a, b| a.i16x8_max_s(b)), I32x4MaxS => stack_op!(binary Value128, |a, b| a.i32x4_max_s(b)), - I8x16MaxU => stack_op!(binary Value128, |a, b| a.i8x16_max_u(b)), I16x8MaxU => stack_op!(binary Value128, |a, b| a.i16x8_max_u(b)), I32x4MaxU => stack_op!(binary Value128, |a, b| a.i32x4_max_u(b)), - I64x2Mul => stack_op!(binary Value128, |a, b| a.i64x2_mul(b)), I16x8Mul => stack_op!(binary Value128, |a, b| a.i16x8_mul(b)), I32x4Mul => stack_op!(binary Value128, |a, b| a.i32x4_mul(b)), - I8x16NarrowI16x8S => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), I8x16NarrowI16x8U => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), I16x8NarrowI32x4S => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), I16x8NarrowI32x4U => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), - I8x16AddSatS => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_s(b)), I16x8AddSatS => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_s(b)), I8x16AddSatU => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_u(b)), @@ -543,15 +479,12 @@ impl<'store> Executor<'store> { I16x8SubSatS => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_s(b)), I8x16SubSatU => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_u(b)), I16x8SubSatU => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_u(b)), - I8x16AvgrU => stack_op!(binary Value128, |a, b| a.i8x16_avgr_u(b)), I16x8AvgrU => stack_op!(binary Value128, |a, b| a.i16x8_avgr_u(b)), - I16x8ExtAddPairwiseI8x16S => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), I16x8ExtAddPairwiseI8x16U => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), I32x4ExtAddPairwiseI16x8S => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), I32x4ExtAddPairwiseI16x8U => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), - I16x8ExtMulLowI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), I16x8ExtMulLowI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), I16x8ExtMulHighI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), @@ -564,7 +497,6 @@ impl<'store> Executor<'store> { I64x2ExtMulLowI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), I64x2ExtMulHighI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), I64x2ExtMulHighI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), - I16x8ExtendLowI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_s()), I16x8ExtendLowI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_u()), I16x8ExtendHighI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_s()), @@ -577,14 +509,10 @@ impl<'store> Executor<'store> { I64x2ExtendLowI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_u()), I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), - I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), I8x16Shuffle(idx) => { let idx = self.cf.data().v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } - I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), - I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), - F32x4Ceil => stack_op!(simd_unary f32x4_ceil), F64x2Ceil => stack_op!(simd_unary f64x2_ceil), F32x4Floor => stack_op!(simd_unary f32x4_floor), @@ -607,7 +535,6 @@ impl<'store> Executor<'store> { F64x2Mul => stack_op!(simd_binary f64x2_mul), F32x4Div => stack_op!(simd_binary f32x4_div), F64x2Div => stack_op!(simd_binary f64x2_div), - F32x4Min => stack_op!(simd_binary f32x4_min), F64x2Min => stack_op!(simd_binary f64x2_min), F32x4Max => stack_op!(simd_binary f32x4_max), @@ -616,7 +543,6 @@ impl<'store> Executor<'store> { F32x4PMax => stack_op!(simd_binary f32x4_pmax), F64x2PMin => stack_op!(simd_binary f64x2_pmin), F64x2PMax => stack_op!(simd_binary f64x2_pmax), - I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), @@ -628,28 +554,6 @@ impl<'store> Executor<'store> { I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), - // Relaxed SIMD (not yet implemented) - // I8x16RelaxedSwizzle => unimplemented!(), - // I32x4RelaxedTruncF32x4S => unimplemented!(), - // I32x4RelaxedTruncF32x4U => unimplemented!(), - // I32x4RelaxedTruncF64x2SZero => unimplemented!(), - // I32x4RelaxedTruncF64x2UZero => unimplemented!(), - // F32x4RelaxedMadd => unimplemented!(), - // F32x4RelaxedNmadd => unimplemented!(), - // F64x2RelaxedMadd => unimplemented!(), - // F64x2RelaxedNmadd => unimplemented!(), - // I8x16RelaxedLaneselect => unimplemented!(), - // I16x8RelaxedLaneselect => unimplemented!(), - // I32x4RelaxedLaneselect => unimplemented!(), - // I64x2RelaxedLaneselect => unimplemented!(), - // F32x4RelaxedMin => unimplemented!(), - // F32x4RelaxedMax => unimplemented!(), - // F64x2RelaxedMin => unimplemented!(), - // F64x2RelaxedMax => unimplemented!(), - // I16x8RelaxedQ15mulrS => unimplemented!(), - // I16x8RelaxedDotI8x16I7x16S => unimplemented!(), - // I32x4RelaxedDotI8x16I7x16AddS => unimplemented!(), - #[allow(unreachable_patterns)] i => return ControlFlow::Break(Some(Error::UnsupportedFeature(format!("unimplemented opcode: {i:?}")))), }; @@ -658,11 +562,6 @@ impl<'store> Executor<'store> { ControlFlow::Continue(()) } - #[cold] - fn exec_unreachable(&self) -> ControlFlow> { - ControlFlow::Break(Some(Trap::Unreachable.into())) - } - fn exec_call( &mut self, wasm_func: Rc, @@ -681,12 +580,9 @@ impl<'store> Executor<'store> { self.module.swap_with(self.cf.module_addr(), self.store); ControlFlow::Continue(()) } - fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { - let params = self.store.stack.values.pop_params(&host_func.ty.params); - let res = host_func - .clone() - .call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms) - .to_cf()?; + fn exec_call_host(&mut self, host_func: &Rc) -> ControlFlow> { + let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::>(); + let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms).to_cf()?; self.store.stack.values.extend_from_wasmvalues(&res); self.cf.incr_instr_ptr(); ControlFlow::Continue(()) @@ -695,7 +591,7 @@ impl<'store> Executor<'store> { let func_inst = self.store.state.get_func(self.module.resolve_func_addr(v)); match func_inst.func.clone() { crate::Function::Wasm(wasm_func) => self.exec_call::(wasm_func, func_inst.owner), - crate::Function::Host(host_func) => self.exec_call_host(host_func), + crate::Function::Host(host_func) => self.exec_call_host(&host_func), } } fn exec_call_indirect( @@ -735,7 +631,7 @@ impl<'store> Executor<'store> { )); } - self.exec_call_host(host_func) + self.exec_call_host(&host_func) } } } @@ -925,19 +821,12 @@ impl<'store> Executor<'store> { let offset: i32 = self.store.stack.values.pop(); let dst: i32 = self.store.stack.values.pop(); - let data = self - .store - .state - .data - .get(self.module.resolve_data_addr(data_index) as usize) - .ok_or_else(|| Error::Other("data not found".to_string()))?; + let data_addr = self.module.resolve_data_addr(data_index) as usize; + let data = self.store.state.data.get(data_addr).ok_or_else(|| Error::Other("data not found".to_string()))?; - let mem = self - .store - .state - .memories - .get_mut(self.module.resolve_mem_addr(mem_index) as usize) - .ok_or_else(|| Error::Other("memory not found".to_string()))?; + let mem_addr = self.module.resolve_mem_addr(mem_index) as usize; + let mem = + self.store.state.memories.get_mut(mem_addr).ok_or_else(|| Error::Other("memory not found".to_string()))?; let data_len = data.data.as_ref().map_or(0, |d| d.len()); @@ -952,12 +841,6 @@ impl<'store> Executor<'store> { let Some(data) = &data.data else { return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()) }; mem.store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)]) } - fn exec_data_drop(&mut self, data_index: u32) { - self.store.state.get_data_mut(self.module.resolve_data_addr(data_index)).drop(); - } - fn exec_elem_drop(&mut self, elem_index: u32) { - self.store.state.get_elem_mut(self.module.resolve_elem_addr(elem_index)).drop(); - } fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { let size: i32 = self.store.stack.values.pop(); let src: i32 = self.store.stack.values.pop(); @@ -990,7 +873,6 @@ impl<'store> Executor<'store> { let val = self.store.stack.values.pop::() as u64; let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { - cold(); return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { offset: val as usize, len: LOAD_SIZE, @@ -1020,7 +902,6 @@ impl<'store> Executor<'store> { }; let Some(Ok(addr)) = offset.checked_add(addr).map(|a| a.try_into()) else { - cold(); return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { offset: addr as usize, len: LOAD_SIZE, @@ -1093,7 +974,7 @@ impl<'store> Executor<'store> { } fn exec_table_size(&mut self, table_index: u32) -> Result<()> { let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); - self.store.stack.values.push_dyn(table.size().into()); + self.store.stack.values.push(table.size()); Ok(()) } fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 9954beb0..ba74d3a6 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -176,14 +176,10 @@ impl CallFrame { block_ptr: u32, ) -> Self { let locals = { - let mut locals_32 = Vec::new(); - locals_32.reserve_exact(wasm_func_inst.locals.c32 as usize); - let mut locals_64 = Vec::new(); - locals_64.reserve_exact(wasm_func_inst.locals.c64 as usize); - let mut locals_128 = Vec::new(); - locals_128.reserve_exact(wasm_func_inst.locals.c128 as usize); - let mut locals_ref = Vec::new(); - locals_ref.reserve_exact(wasm_func_inst.locals.cref as usize); + let mut locals_32 = Vec::with_capacity(wasm_func_inst.locals.c32 as usize); + let mut locals_64 = Vec::with_capacity(wasm_func_inst.locals.c64 as usize); + let mut locals_128 = Vec::with_capacity(wasm_func_inst.locals.c128 as usize); + let mut locals_ref = Vec::with_capacity(wasm_func_inst.locals.cref as usize); for p in params { match p.into() { diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index 3da262c1..0635bae6 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -23,8 +23,8 @@ impl Stack { /// Initialize the stack with the given call frame (used for starting execution) pub(crate) fn initialize(&mut self, callframe: CallFrame) { - self.blocks.clear(); self.values.clear(); + self.blocks.clear(); self.call_stack.reset(callframe); } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 32dd3a83..cec08a3f 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -122,14 +122,12 @@ impl ValueStack { T::replace_top(self, func) } - pub(crate) fn pop_params(&mut self, val_types: &[ValType]) -> Vec { - val_types.iter().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>() - } - - pub(crate) fn pop_results(&mut self, val_types: &[ValType]) -> Vec { - let mut results = val_types.iter().rev().map(|val_type| self.pop_wasmvalue(*val_type)).collect::>(); - results.reverse(); - results + #[inline] + pub(crate) fn pop_types<'a>( + &'a mut self, + val_types: impl IntoIterator, + ) -> impl core::iter::Iterator { + val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) } #[inline] @@ -168,7 +166,7 @@ impl ValueStack { pub(crate) fn truncate_keep(&mut self, to: StackLocation, keep: StackHeight) { #[inline(always)] - fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { + fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { let len = data.len() as u32; if len <= n { return; // No need to truncate if the current size is already less than or equal to total_to_keep @@ -205,7 +203,15 @@ impl ValueStack { pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { for value in values { - self.push_dyn(value.into()); + match value { + WasmValue::I32(v) => self.stack_32.push(*v as u32), + WasmValue::I64(v) => self.stack_64.push(*v as u64), + WasmValue::F32(v) => self.stack_32.push(v.to_bits()), + WasmValue::F64(v) => self.stack_64.push(v.to_bits()), + WasmValue::RefExtern(v) => self.stack_ref.push(v.addr()), + WasmValue::RefFunc(v) => self.stack_ref.push(v.addr()), + WasmValue::V128(v) => self.stack_128.push((*v).into()), + } } } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 0940c97e..456fb93b 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -70,7 +70,7 @@ impl TinyWasmValue { pub fn unwrap_32(&self) -> Value32 { match self { Self::Value32(v) => *v, - _ => unreachable!("Expected Value32"), + _ => panic!("Expected Value32"), } } @@ -78,7 +78,7 @@ impl TinyWasmValue { pub fn unwrap_64(&self) -> Value64 { match self { Self::Value64(v) => *v, - _ => unreachable!("Expected Value64"), + _ => panic!("Expected Value64"), } } @@ -86,7 +86,7 @@ impl TinyWasmValue { pub fn unwrap_128(&self) -> Value128 { match self { Self::Value128(v) => *v, - _ => unreachable!("Expected Value128"), + _ => panic!("Expected Value128"), } } @@ -94,7 +94,7 @@ impl TinyWasmValue { pub fn unwrap_ref(&self) -> ValueRef { match self { Self::ValueRef(v) => *v, - _ => unreachable!("Expected ValueRef"), + _ => panic!("Expected ValueRef"), } } From 430ea402911855e8b8579f0e97b10699f22f758d Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 25 Mar 2026 23:41:50 +0100 Subject: [PATCH 11/47] chore: atomic module data Signed-off-by: Henry --- .gitignore | 1 + crates/parser/src/module.rs | 32 +++---- crates/parser/src/visit.rs | 6 +- crates/tinywasm/src/func.rs | 4 +- crates/tinywasm/src/imports.rs | 2 +- crates/tinywasm/src/instance.rs | 25 ++--- crates/tinywasm/src/interpreter/executor.rs | 92 ++++++++----------- crates/tinywasm/src/interpreter/mod.rs | 6 +- .../src/interpreter/stack/block_stack.rs | 6 +- .../src/interpreter/stack/call_stack.rs | 62 ++++++------- crates/tinywasm/src/interpreter/stack/mod.rs | 5 +- crates/tinywasm/src/lib.rs | 2 +- crates/tinywasm/src/module.rs | 6 +- crates/tinywasm/src/store/mod.rs | 24 ++--- crates/types/src/instructions.rs | 4 +- crates/types/src/lib.rs | 71 +++++++++++--- 16 files changed, 189 insertions(+), 159 deletions(-) diff --git a/.gitignore b/.gitignore index 14456f4b..807cb82f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ flamegraph.svg /.idea /*.iml profile.json +profile.json.gz diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 4754d317..d4981437 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,14 +1,15 @@ use crate::log::debug; use crate::{ParseError, Result, conversion}; use alloc::string::ToString; -use alloc::{boxed::Box, format, vec::Vec}; +use alloc::sync::Arc; +use alloc::{format, vec::Vec}; use tinywasm_types::{ - Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, ValueCounts, - ValueCountsSmall, WasmFunction, WasmFunctionData, + ArcSlice, Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, + ValueCounts, ValueCountsSmall, WasmFunction, WasmFunctionData, }; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Box<[Instruction]>, WasmFunctionData, ValueCounts); +pub(crate) type Code = (Arc<[Instruction]>, WasmFunctionData, ValueCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -195,25 +196,24 @@ impl ModuleReader { .map(|((instructions, data, locals), ty_idx)| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); let params = ValueCountsSmall::from(&ty.params); - WasmFunction { instructions, data, locals, params, ty } + WasmFunction { instructions: ArcSlice(instructions), data, locals, params, ty } }) - .collect::>() - .into_boxed_slice(); + .collect::>(); let globals = self.globals; let table_types = self.table_types; Ok(TinyWasmModule { - funcs, - func_types: self.func_types.into_boxed_slice(), - globals: globals.into_boxed_slice(), - table_types: table_types.into_boxed_slice(), - imports: self.imports.into_boxed_slice(), + funcs: funcs.into(), + func_types: self.func_types.into(), + globals: globals.into(), + table_types: table_types.into(), + imports: self.imports.into(), start_func: self.start_func, - data: self.data.into_boxed_slice(), - exports: self.exports.into_boxed_slice(), - elements: self.elements.into_boxed_slice(), - memory_types: self.memory_types.into_boxed_slice(), + data: self.data.into(), + exports: self.exports.into(), + elements: self.elements.into(), + memory_types: self.memory_types.into(), }) } } diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 9b9ab750..8212763a 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -2,7 +2,7 @@ use crate::Result; use crate::conversion::{convert_heaptype, convert_valtype}; use alloc::string::ToString; -use alloc::{boxed::Box, vec::Vec}; +use alloc::vec::Vec; use tinywasm_types::{Instruction, MemoryArg, WasmFunctionData}; use wasmparser::{ FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, VisitSimdOperator, WasmModuleResources, @@ -37,7 +37,7 @@ pub(crate) fn process_operators_and_validate( validator: FuncValidator, body: FunctionBody<'_>, local_addr_map: Vec, -) -> Result<(Box<[Instruction]>, WasmFunctionData, FuncValidatorAllocations)> { +) -> Result<(alloc::sync::Arc<[Instruction]>, WasmFunctionData, FuncValidatorAllocations)> { let mut reader = body.get_operators_reader()?; let remaining = reader.get_binary_reader().bytes_remaining(); let mut builder = FunctionBuilder::new(remaining, validator, local_addr_map); @@ -52,7 +52,7 @@ pub(crate) fn process_operators_and_validate( } Ok(( - builder.instructions.into_boxed_slice(), + alloc::sync::Arc::from(builder.instructions), WasmFunctionData { v128_constants: builder.v128_constants.into_boxed_slice() }, builder.validator.into_allocations(), )) diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 5d5b56bb..3a3985be 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -60,10 +60,10 @@ impl FuncHandle { // 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) - store.stack.initialize(callframe); + store.stack.clear(); // 9. Invoke the function instance - InterpreterRuntime::exec(store)?; + InterpreterRuntime::exec(store, callframe)?; // Once the function returns: // 1. Assert: m values are on the top of the stack (Ensured by validation) diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index aa05ae95..dcd5e12a 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -363,7 +363,7 @@ impl Imports { ) -> Result { let mut imports = ResolvedImports::new(); - for import in &module.0.imports { + for import in &*module.0.imports { let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?; match val { diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 02ef5312..7eddb101 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,4 +1,5 @@ -use alloc::{boxed::Box, format, rc::Rc}; +use alloc::boxed::Box; +use alloc::{format, rc::Rc}; use tinywasm_types::*; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; @@ -20,7 +21,7 @@ pub(crate) struct ModuleInstanceInner { pub(crate) store_id: usize, pub(crate) idx: ModuleInstanceAddr, - pub(crate) types: Box<[FuncType]>, + pub(crate) types: ArcSlice, pub(crate) func_addrs: Box<[FuncAddr]>, pub(crate) table_addrs: Box<[TableAddr]>, @@ -30,8 +31,8 @@ pub(crate) struct ModuleInstanceInner { pub(crate) data_addrs: Box<[DataAddr]>, pub(crate) func_start: Option, - pub(crate) imports: Box<[Import]>, - pub(crate) exports: Box<[Export]>, + pub(crate) imports: ArcSlice, + pub(crate) exports: ArcSlice, } impl ModuleInstance { @@ -65,20 +66,20 @@ impl ModuleInstance { let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?; - addrs.funcs.extend(store.init_funcs(module.0.funcs.into(), idx)?); - addrs.tables.extend(store.init_tables(module.0.table_types.into(), idx)?); - addrs.memories.extend(store.init_memories(module.0.memory_types.into(), idx)?); + addrs.funcs.extend(store.init_funcs(&module.0.funcs, idx)?); + addrs.tables.extend(store.init_tables(&module.0.table_types, idx)?); + addrs.memories.extend(store.init_memories(&module.0.memory_types, idx)?); - let global_addrs = store.init_globals(addrs.globals, module.0.globals.into(), &addrs.funcs, idx)?; + let global_addrs = store.init_globals(addrs.globals, &module.0.globals, &addrs.funcs, idx)?; let (elem_addrs, elem_trapped) = store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?; - let (data_addrs, data_trapped) = store.init_data(&addrs.memories, module.0.data.into(), idx)?; + let (data_addrs, data_trapped) = store.init_data(&addrs.memories, &module.0.data, idx)?; let instance = ModuleInstanceInner { failed_to_instantiate: elem_trapped.is_some() || data_trapped.is_some(), store_id: store.id(), idx, - types: module.0.func_types, + types: module.0.func_types.clone(), func_addrs: addrs.funcs.into_boxed_slice(), table_addrs: addrs.tables.into_boxed_slice(), mem_addrs: addrs.memories.into_boxed_slice(), @@ -86,8 +87,8 @@ impl ModuleInstance { elem_addrs, data_addrs, func_start: module.0.start_func, - imports: module.0.imports, - exports: module.0.exports, + imports: module.0.imports.clone(), + exports: module.0.exports.clone(), }; let instance = Self::new(instance); diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index dbeceb9b..8d2e211a 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -22,10 +22,9 @@ pub(crate) struct Executor<'store> { } impl<'store> Executor<'store> { - pub(crate) fn new(store: &'store mut Store) -> Result { - let current_frame = store.stack.call_stack.pop().expect("no call frame, this is a bug"); - let current_module = store.get_module_instance_raw(current_frame.module_addr()); - Ok(Self { cf: current_frame, module: current_module, store }) + pub(crate) fn new(store: &'store mut Store, cf: CallFrame) -> Result { + let module = store.get_module_instance_raw(cf.module_addr()); + Ok(Self { module, store, cf }) } pub(crate) fn run_to_completion(&mut self) -> Result<()> { @@ -104,22 +103,22 @@ impl<'store> Executor<'store> { BrTable(default, len) => return self.exec_brtable(*default, *len), Return => return self.exec_return(), EndBlockFrame => self.exec_end_block(), - LocalGet32(local_index) => self.exec_local_get::(*local_index), - LocalGet64(local_index) => self.exec_local_get::(*local_index), - LocalGet128(local_index) => self.exec_local_get::(*local_index), - LocalGetRef(local_index) => self.exec_local_get::(*local_index), - LocalSet32(local_index) => self.exec_local_set::(*local_index), - LocalSet64(local_index) => self.exec_local_set::(*local_index), - LocalSet128(local_index) => self.exec_local_set::(*local_index), - LocalSetRef(local_index) => self.exec_local_set::(*local_index), - LocalCopy32(from, to) => self.exec_local_copy::(*from, *to), - LocalCopy64(from, to) => self.exec_local_copy::(*from, *to), - LocalCopy128(from, to) => self.exec_local_copy::(*from, *to), - LocalCopyRef(from, to) => self.exec_local_copy::(*from, *to), - LocalTee32(local_index) => self.exec_local_tee::(*local_index), - LocalTee64(local_index) => self.exec_local_tee::(*local_index), - LocalTee128(local_index) => self.exec_local_tee::(*local_index), - LocalTeeRef(local_index) => self.exec_local_tee::(*local_index), + LocalGet32(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), + LocalGet64(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), + LocalGet128(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), + LocalGetRef(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), + LocalSet32(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), + LocalSet64(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), + LocalSet128(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), + LocalSetRef(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), + LocalCopy32(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), + LocalCopy64(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), + LocalCopy128(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), + LocalCopyRef(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), + LocalTee32(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), + LocalTee64(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), + LocalTee128(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), + LocalTeeRef(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), GlobalGet(global_index) => self.exec_global_get(*global_index), GlobalSet32(global_index) => self.exec_global_set::(*global_index), GlobalSet64(global_index) => self.exec_global_set::(*global_index), @@ -567,11 +566,11 @@ impl<'store> Executor<'store> { wasm_func: Rc, owner: ModuleInstanceAddr, ) -> ControlFlow> { - let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - if IS_RETURN_CALL { + let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); self.cf.reuse_for(wasm_func, locals, self.store.stack.blocks.len() as u32, owner); } else { + let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.store.stack.blocks.len() as u32); self.cf.incr_instr_ptr(); // skip the call instruction self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; @@ -580,7 +579,7 @@ impl<'store> Executor<'store> { self.module.swap_with(self.cf.module_addr(), self.store); ControlFlow::Continue(()) } - fn exec_call_host(&mut self, host_func: &Rc) -> ControlFlow> { + fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::>(); let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms).to_cf()?; self.store.stack.values.extend_from_wasmvalues(&res); @@ -589,9 +588,9 @@ impl<'store> Executor<'store> { } fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { let func_inst = self.store.state.get_func(self.module.resolve_func_addr(v)); - match func_inst.func.clone() { - crate::Function::Wasm(wasm_func) => self.exec_call::(wasm_func, func_inst.owner), - crate::Function::Host(host_func) => self.exec_call_host(&host_func), + match &func_inst.func { + crate::Function::Wasm(wasm_func) => self.exec_call::(wasm_func.clone(), func_inst.owner), + crate::Function::Host(host_func) => self.exec_call_host(host_func.clone()), } } fn exec_call_indirect( @@ -612,26 +611,26 @@ impl<'store> Executor<'store> { let func_inst = self.store.state.get_func(func_ref); let call_ty = self.module.func_ty(type_addr); - match func_inst.func.clone() { + match &func_inst.func { crate::Function::Wasm(wasm_func) => { - if unlikely(wasm_func.ty != *call_ty) { + if wasm_func.ty != *call_ty { return ControlFlow::Break(Some( Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() } .into(), )); } - self.exec_call::(wasm_func, func_inst.owner) + self.exec_call::(wasm_func.clone(), func_inst.owner) } crate::Function::Host(host_func) => { - if unlikely(host_func.ty != *call_ty) { + if host_func.ty != *call_ty { return ControlFlow::Break(Some( Trap::IndirectCallTypeMismatch { actual: host_func.ty.clone(), expected: call_ty.clone() } .into(), )); } - self.exec_call_host(&host_func) + self.exec_call_host(host_func.clone()) } } } @@ -645,16 +644,16 @@ impl<'store> Executor<'store> { // falsy value is on the top of the stack if else_offset == 0 { - self.cf.jump(end_offset as usize); + self.cf.jump(end_offset); return; } - self.cf.jump(else_offset as usize); + self.cf.jump(else_offset); self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)); } fn exec_else(&mut self, end_offset: u32) { self.exec_end_block(); - self.cf.jump(end_offset as usize); + self.cf.jump(end_offset); } fn resolve_functype(&self, idx: u32) -> (StackHeight, StackHeight) { let ty = self.module.func_ty(idx); @@ -662,7 +661,7 @@ impl<'store> Executor<'store> { } fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { self.store.stack.blocks.push(BlockFrame { - instr_ptr: self.cf.instr_ptr(), + instr_ptr: self.cf.instr_ptr() as u32, end_instr_offset, stack_ptr: self.store.stack.values.height(), results, @@ -730,18 +729,6 @@ impl<'store> Executor<'store> { let block = self.store.stack.blocks.pop(); self.store.stack.values.truncate_keep(block.stack_ptr, block.results); } - fn exec_local_get(&mut self, local_index: u16) { - let v = self.cf.locals.get::(local_index); - self.store.stack.values.push(v); - } - fn exec_local_set(&mut self, local_index: u16) { - let v = self.store.stack.values.pop::(); - self.cf.locals.set(local_index, v); - } - fn exec_local_tee(&mut self, local_index: u16) { - let v = self.store.stack.values.peek::(); - self.cf.locals.set(local_index, v); - } fn exec_global_get(&mut self, global_index: u32) { self.store @@ -830,7 +817,7 @@ impl<'store> Executor<'store> { let data_len = data.data.as_ref().map_or(0, |d| d.len()); - if unlikely(((size + offset) as usize > data_len) || ((dst + size) as usize > mem.len())) { + if ((size + offset) as usize > data_len) || ((dst + size) as usize > mem.len()) { return Err(Trap::MemoryOutOfBounds { offset: offset as usize, len: size as usize, max: data_len }.into()); } @@ -999,7 +986,7 @@ impl<'store> Executor<'store> { let elem_len = elem.items.as_ref().map_or(0, alloc::vec::Vec::len); let table_len = table.size(); - if unlikely(size < 0 || ((size + offset) as usize > elem_len) || ((dst + size) > table_len)) { + if size < 0 || ((size + offset) as usize > elem_len) || ((dst + size) > table_len) { return Err(Trap::TableOutOfBounds { offset: offset as usize, len: size as usize, max: elem_len }.into()); } @@ -1038,7 +1025,7 @@ impl<'store> Executor<'store> { let val = self.store.stack.values.pop::(); let i = self.store.stack.values.pop::(); - if unlikely(i + n > table.size()) { + if i + n > table.size() { return Err(Error::Trap(Trap::TableOutOfBounds { offset: i as usize, len: n as usize, @@ -1052,9 +1039,4 @@ impl<'store> Executor<'store> { table.fill(self.module.func_addrs(), i as usize, n as usize, val.into()) } - - fn exec_local_copy(&mut self, from: u16, to: u16) { - let v = self.cf.locals.get::(from); - self.cf.locals.set(to, v); - } } diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 6f296cee..1a9a0cc1 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod values; #[cfg(not(feature = "std"))] mod no_std_floats; -use crate::{Result, Store}; +use crate::{Result, Store, interpreter::stack::CallFrame}; pub(crate) use value128::*; pub(crate) use values::*; @@ -18,7 +18,7 @@ pub(crate) use values::*; pub(crate) struct InterpreterRuntime; impl InterpreterRuntime { - pub(crate) fn exec(store: &mut Store) -> Result<()> { - executor::Executor::new(store)?.run_to_completion() + pub(crate) fn exec(store: &mut Store, cf: CallFrame) -> Result<()> { + executor::Executor::new(store, cf)?.run_to_completion() } } diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs index b35ad94f..e4d4c280 100644 --- a/crates/tinywasm/src/interpreter/stack/block_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -1,4 +1,4 @@ -use crate::{engine::Config, unlikely}; +use crate::engine::Config; use alloc::vec::Vec; use crate::interpreter::values::{StackHeight, StackLocation}; @@ -31,7 +31,7 @@ impl BlockStack { let len = (self.0.len() as u32) - offset; // the vast majority of wasm functions don't use break to return - if unlikely(index >= len) { + if index >= len { return None; } @@ -52,7 +52,7 @@ impl BlockStack { #[derive(Debug)] pub(crate) struct BlockFrame { - pub(crate) instr_ptr: usize, // position of the instruction pointer when the block was entered + pub(crate) instr_ptr: u32, // position of the instruction pointer when the block was entered pub(crate) end_instr_offset: u32, // position of the end instruction of the block pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index ba74d3a6..4b0fad60 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -7,7 +7,7 @@ use crate::{Error, unlikely}; use alloc::boxed::Box; use alloc::{rc::Rc, vec::Vec}; -use tinywasm_types::{Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; +use tinywasm_types::{ArcSlice, Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; @@ -22,9 +22,8 @@ impl CallStack { Self { stack: Vec::with_capacity(config.call_stack_init_size) } } - pub(crate) fn reset(&mut self, call_frame: CallFrame) { + pub(crate) fn clear(&mut self) { self.stack.clear(); - self.stack.push(call_frame); } #[inline] @@ -81,14 +80,14 @@ impl CallFrame { &self.func_instance.data } - #[inline] + #[inline(always)] pub(crate) fn incr_instr_ptr(&mut self) { self.instr_ptr += 1; } #[inline] - pub(crate) fn jump(&mut self, offset: usize) { - self.instr_ptr += offset; + pub(crate) fn jump(&mut self, offset: u32) { + self.instr_ptr += offset as usize; } #[inline] @@ -96,19 +95,20 @@ impl CallFrame { self.module_addr } + #[inline(always)] + pub(crate) fn fetch_instr(&self) -> &Instruction { + self + .func_instance + .instructions + .get(self.instr_ptr) + .unwrap_or_else(|| unreachable!("Instruction pointer out of bounds, this is a bug")) + } + #[inline] pub(crate) fn block_ptr(&self) -> u32 { self.block_ptr } - #[inline(always)] - pub(crate) fn fetch_instr(&self) -> &Instruction { - match self.func_instance.instructions.get(self.instr_ptr) { - Some(instr) => instr, - None => unreachable!("Instruction out of bounds, this is a bug"), - } - } - pub(crate) fn reuse_for( &mut self, func: Rc, @@ -139,7 +139,7 @@ impl CallFrame { match break_to.ty { BlockType::Loop => { // this is a loop, so we want to jump back to the start of the loop - self.instr_ptr = break_to.instr_ptr; + self.instr_ptr = break_to.instr_ptr as usize; // We also want to push the params to the stack values.truncate_keep(break_to.stack_ptr, break_to.params); @@ -158,7 +158,7 @@ impl CallFrame { values.truncate_keep(break_to.stack_ptr, break_to.results); // (the inst_ptr will be incremented by 1 before the next instruction is executed) - self.instr_ptr = break_to.instr_ptr + break_to.end_instr_offset as usize; + self.instr_ptr = (break_to.instr_ptr + break_to.end_instr_offset) as usize; // we also want to trim the label stack, including the block blocks.truncate(blocks.len() as u32 - (break_to_relative + 1)); @@ -170,16 +170,16 @@ impl CallFrame { #[inline] pub(crate) fn new( - wasm_func_inst: Rc, - owner: ModuleInstanceAddr, + func_instance: Rc, + module_addr: ModuleInstanceAddr, params: &[WasmValue], block_ptr: u32, ) -> Self { let locals = { - let mut locals_32 = Vec::with_capacity(wasm_func_inst.locals.c32 as usize); - let mut locals_64 = Vec::with_capacity(wasm_func_inst.locals.c64 as usize); - let mut locals_128 = Vec::with_capacity(wasm_func_inst.locals.c128 as usize); - let mut locals_ref = Vec::with_capacity(wasm_func_inst.locals.cref as usize); + let mut locals_32 = Vec::with_capacity(func_instance.locals.c32 as usize); + let mut locals_64 = Vec::with_capacity(func_instance.locals.c64 as usize); + let mut locals_128 = Vec::with_capacity(func_instance.locals.c128 as usize); + let mut locals_ref = Vec::with_capacity(func_instance.locals.cref as usize); for p in params { match p.into() { @@ -190,10 +190,10 @@ impl CallFrame { } } - locals_32.resize_with(wasm_func_inst.locals.c32 as usize, Default::default); - locals_64.resize_with(wasm_func_inst.locals.c64 as usize, Default::default); - locals_128.resize_with(wasm_func_inst.locals.c128 as usize, Default::default); - locals_ref.resize_with(wasm_func_inst.locals.cref as usize, Default::default); + locals_32.resize_with(func_instance.locals.c32 as usize, Default::default); + locals_64.resize_with(func_instance.locals.c64 as usize, Default::default); + locals_128.resize_with(func_instance.locals.c128 as usize, Default::default); + locals_ref.resize_with(func_instance.locals.cref as usize, Default::default); Locals { locals_32: locals_32.into_boxed_slice(), @@ -203,21 +203,21 @@ impl CallFrame { } }; - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + Self { instr_ptr: 0, func_instance, module_addr, block_ptr, locals } } #[inline] pub(crate) fn new_raw( - wasm_func_inst: Rc, - owner: ModuleInstanceAddr, + func_instance: Rc, + module_addr: ModuleInstanceAddr, locals: Locals, block_ptr: u32, ) -> Self { - Self { instr_ptr: 0, func_instance: wasm_func_inst, module_addr: owner, block_ptr, locals } + Self { instr_ptr: 0, func_instance, module_addr, block_ptr, locals } } #[inline] - pub(crate) fn instructions(&self) -> &[Instruction] { + pub(crate) fn instructions(&self) -> &ArcSlice { &self.func_instance.instructions } } diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index 0635bae6..68b9164c 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -21,10 +21,9 @@ impl Stack { Self { values: ValueStack::new(config), blocks: BlockStack::new(config), call_stack: CallStack::new(config) } } - /// Initialize the stack with the given call frame (used for starting execution) - pub(crate) fn initialize(&mut self, callframe: CallFrame) { + pub(crate) fn clear(&mut self) { self.values.clear(); self.blocks.clear(); - self.call_stack.reset(callframe); + self.call_stack.clear(); } } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 07748b97..31d52f77 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -4,7 +4,7 @@ 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)] +#![deny(unsafe_code)] //! A tiny WebAssembly Runtime written in Rust //! diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 26cea9c9..e0685eb2 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -5,17 +5,17 @@ use tinywasm_types::TinyWasmModule; /// /// See #[derive(Debug, Clone)] -pub struct Module(pub(crate) TinyWasmModule); +pub struct Module(pub(crate) alloc::sync::Arc); impl From<&TinyWasmModule> for Module { fn from(data: &TinyWasmModule) -> Self { - Self(data.clone()) + Self(alloc::sync::Arc::new(data.clone())) } } impl From for Module { fn from(data: TinyWasmModule) -> Self { - Self(data) + Self(alloc::sync::Arc::new(data)) } } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 70a2b016..757cbb22 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -220,33 +220,33 @@ impl Store { // Linking related functions impl Store { /// Add functions to the store, returning their addresses in the store - pub(crate) fn init_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Result> { + pub(crate) fn init_funcs(&mut self, funcs: &[WasmFunction], idx: ModuleInstanceAddr) -> Result> { let func_count = self.state.funcs.len(); let mut func_addrs = Vec::with_capacity(func_count); - for (i, func) in funcs.into_iter().enumerate() { - self.state.funcs.push(FunctionInstance::new_wasm(func, idx)); + for (i, func) in funcs.iter().enumerate() { + self.state.funcs.push(FunctionInstance::new_wasm(func.clone(), idx)); func_addrs.push((i + func_count) as FuncAddr); } Ok(func_addrs) } /// Add tables to the store, returning their addresses in the store - pub(crate) fn init_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Result> { + pub(crate) fn init_tables(&mut self, tables: &[TableType], idx: ModuleInstanceAddr) -> Result> { let table_count = self.state.tables.len(); let mut table_addrs = Vec::with_capacity(table_count); - for (i, table) in tables.into_iter().enumerate() { - self.state.tables.push(TableInstance::new(table, idx)); + for (i, table) in tables.iter().enumerate() { + self.state.tables.push(TableInstance::new(table.clone(), idx)); table_addrs.push((i + table_count) as TableAddr); } Ok(table_addrs) } /// Add memories to the store, returning their addresses in the store - pub(crate) fn init_memories(&mut self, memories: Vec, idx: ModuleInstanceAddr) -> Result> { + pub(crate) fn init_memories(&mut self, memories: &[MemoryType], idx: ModuleInstanceAddr) -> Result> { let mem_count = self.state.memories.len(); let mut mem_addrs = Vec::with_capacity(mem_count); - for (i, mem) in memories.into_iter().enumerate() { - self.state.memories.push(MemoryInstance::new(mem, idx)); + for (i, mem) in memories.iter().enumerate() { + self.state.memories.push(MemoryInstance::new(*mem, idx)); mem_addrs.push((i + mem_count) as MemAddr); } Ok(mem_addrs) @@ -256,7 +256,7 @@ impl Store { pub(crate) fn init_globals( &mut self, mut imported_globals: Vec, - new_globals: Vec, + new_globals: &[Global], func_addrs: &[FuncAddr], idx: ModuleInstanceAddr, ) -> Result> { @@ -363,12 +363,12 @@ impl Store { pub(crate) fn init_data( &mut self, mem_addrs: &[MemAddr], - data: Vec, + data: &[Data], idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { let data_count = self.state.data.len(); let mut data_addrs = Vec::with_capacity(data_count); - for (i, data) in data.into_iter().enumerate() { + for (i, data) in data.iter().enumerate() { let data_val = match data.kind { tinywasm_types::DataKind::Active { mem: mem_addr, offset } => { let Some(mem_addr) = mem_addrs.get(mem_addr as usize) else { diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index aa57dfa4..ee99601f 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -68,7 +68,7 @@ pub enum Instruction { Block(EndOffset), BlockWithType(ValType, EndOffset), BlockWithFuncType(TypeAddr, EndOffset), - + Loop(EndOffset), LoopWithType(ValType, EndOffset), LoopWithFuncType(TypeAddr, EndOffset), @@ -141,7 +141,7 @@ pub enum Instruction { RefNull(ValType), RefFunc(FuncAddr), RefIsNull, - + // > Numeric Instructions // See I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU, diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index beaa8285..395c0560 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -9,8 +9,11 @@ //! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser). extern crate alloc; -use alloc::boxed::Box; -use core::{fmt::Debug, ops::Range}; +use alloc::{boxed::Box, sync::Arc}; +use core::{ + fmt::Debug, + ops::{Deref, Range}, +}; // Memory defaults const MEM_PAGE_SIZE: u64 = 65536; @@ -73,47 +76,47 @@ pub struct TinyWasmModule { /// Optimized and validated WebAssembly functions /// /// Contains data from to the `code`, `func`, and `type` sections of the original WebAssembly module. - pub funcs: Box<[WasmFunction]>, + pub funcs: ArcSlice, /// A vector of type definitions, indexed by `TypeAddr` /// /// Corresponds to the `type` section of the original WebAssembly module. - pub func_types: Box<[FuncType]>, + pub func_types: ArcSlice, /// Exported items of the WebAssembly module. /// /// Corresponds to the `export` section of the original WebAssembly module. - pub exports: Box<[Export]>, + pub exports: ArcSlice, /// Global components of the WebAssembly module. /// /// Corresponds to the `global` section of the original WebAssembly module. - pub globals: Box<[Global]>, + pub globals: ArcSlice, /// Table components of the WebAssembly module used to initialize tables. /// /// Corresponds to the `table` section of the original WebAssembly module. - pub table_types: Box<[TableType]>, + pub table_types: ArcSlice, /// Memory components of the WebAssembly module used to initialize memories. /// /// Corresponds to the `memory` section of the original WebAssembly module. - pub memory_types: Box<[MemoryType]>, + pub memory_types: ArcSlice, /// Imports of the WebAssembly module. /// /// Corresponds to the `import` section of the original WebAssembly module. - pub imports: Box<[Import]>, + pub imports: ArcSlice, /// Data segments of the WebAssembly module. /// /// Corresponds to the `data` section of the original WebAssembly module. - pub data: Box<[Data]>, + pub data: ArcSlice, /// Element segments of the WebAssembly module. /// /// Corresponds to the `elem` section of the original WebAssembly module. - pub elements: Box<[Element]>, + pub elements: ArcSlice, } /// A WebAssembly External Kind. @@ -249,13 +252,57 @@ impl<'a, T: IntoIterator> From for ValueCountsSmall { #[derive(Debug, Clone, PartialEq, Default)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunction { - pub instructions: Box<[Instruction]>, + pub instructions: ArcSlice, pub data: WasmFunctionData, pub locals: ValueCounts, pub params: ValueCountsSmall, pub ty: FuncType, } +#[derive(Clone, PartialEq)] +#[doc(hidden)] +// wrapper around Arc<[T]> to support serde serialization and deserialization +pub struct ArcSlice(pub Arc<[T]>); + +impl From> for ArcSlice { + fn from(vec: alloc::vec::Vec) -> Self { + Self(Arc::from(vec)) + } +} + +impl Default for ArcSlice { + fn default() -> Self { + Self(Arc::from([])) + } +} + +impl Deref for ArcSlice { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl Debug for ArcSlice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.as_ref().fmt(f) + } +} + +impl serde::Serialize for ArcSlice { + fn serialize(&self, serializer: S) -> Result { + self.0.as_ref().serialize(serializer) + } +} + +impl<'de, T: serde::Deserialize<'de> + Debug> serde::Deserialize<'de> for ArcSlice { + fn deserialize>(deserializer: D) -> Result { + let vec: alloc::vec::Vec = alloc::vec::Vec::deserialize(deserializer)?; + Ok(Self(Arc::from(vec))) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunctionData { From af3029bdc9461b2f8876b0564c3647dba27db97b Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 28 Mar 2026 14:36:00 +0100 Subject: [PATCH 12/47] chore: cleanup, make value & block stack fixed size Signed-off-by: Henry --- crates/parser/src/visit.rs | 30 ++- crates/tinywasm/src/engine.rs | 84 ++----- crates/tinywasm/src/error.rs | 12 +- crates/tinywasm/src/interpreter/executor.rs | 122 +++++----- .../src/interpreter/stack/block_stack.rs | 20 +- .../src/interpreter/stack/call_stack.rs | 40 +--- .../src/interpreter/stack/value_stack.rs | 208 +++++++++++------- crates/tinywasm/src/interpreter/values.rs | 61 ++--- crates/tinywasm/src/store/memory.rs | 2 +- crates/tinywasm/src/store/mod.rs | 80 ++++--- 10 files changed, 332 insertions(+), 327 deletions(-) diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 8212763a..cb9a3d25 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -157,7 +157,6 @@ macro_rules! impl_visit_operator { (@@tail_call $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { - #[cold] fn $visit(&mut self $($(,_: $argty)*)?) { self.unsupported(stringify!($visit)) } @@ -203,7 +202,11 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::V128 => Instruction::GlobalSet128(global_index), wasmparser::ValType::Ref(_) => Instruction::GlobalSetRef(global_index), }), - _ => self.visit_unreachable(), + _ => { + { + self.visit_unreachable(); + }; + } } } @@ -217,7 +220,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::V128 => Instruction::Drop128, wasmparser::ValType::Ref(_) => Instruction::DropRef, }), - _ => self.visit_unreachable(), + _ => { + self.visit_unreachable(); + } } } @@ -225,7 +230,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild match self.validator.get_operand_type(1) { Some(Some(t)) => self.visit_typed_select(t), _ => self.visit_unreachable(), - } + }; } fn visit_local_get(&mut self, idx: u32) -> Self::Output { @@ -245,7 +250,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::V128 => Instruction::LocalGet128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalGetRef(resolved_idx), }), - _ => self.visit_unreachable(), + _ => { + self.visit_unreachable(); + } } } @@ -276,7 +283,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::V128 => Instruction::LocalCopy128(from, resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalCopyRef(from, resolved_idx), }), - _ => self.visit_unreachable(), + _ => { + self.visit_unreachable(); + } } return; } @@ -290,7 +299,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::V128 => Instruction::LocalSet128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalSetRef(resolved_idx), }), - _ => self.visit_unreachable(), + _ => { + self.visit_unreachable(); + } } } @@ -311,7 +322,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::V128 => Instruction::LocalTee128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalTeeRef(resolved_idx), }), - _ => self.visit_unreachable(), + _ => { + self.visit_unreachable(); + } } } @@ -483,7 +496,6 @@ macro_rules! impl_visit_simd_operator { (@@simd $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { - #[cold] fn $visit(&mut self $($(,$arg: $argty)*)?) { self.unsupported(stringify!($visit)) } diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index a70ed58f..261f18cc 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -39,58 +39,40 @@ pub(crate) struct EngineInner { // pub(crate) allocator: Box, } -// pub(crate) trait Allocator {} -// pub(crate) struct DefaultAllocator; -// impl Allocator for DefaultAllocator {} - /// Default initial size for the 32-bit value stack (i32, f32 values). -pub const DEFAULT_VALUE_STACK_32_INIT_SIZE: usize = 32 * 1024; // 32KB +pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 64 * 1024; // 64k slots /// Default initial size for the 64-bit value stack (i64, f64 values). -pub const DEFAULT_VALUE_STACK_64_INIT_SIZE: usize = 16 * 1024; // 16KB +pub const DEFAULT_VALUE_STACK_64_SIZE: usize = 32 * 1024; // 32k slots /// Default initial size for the 128-bit value stack (v128 values). -pub const DEFAULT_VALUE_STACK_128_INIT_SIZE: usize = 8 * 1024; // 8KB +pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots /// Default initial size for the reference value stack (funcref, externref values). -pub const DEFAULT_VALUE_STACK_REF_INIT_SIZE: usize = 1024; // 1KB +pub const DEFAULT_VALUE_STACK_REF_SIZE: usize = 4 * 1024; // 4k slots -/// Default initial size for the block stack. -pub const DEFAULT_BLOCK_STACK_INIT_SIZE: usize = 128; +/// Default initial size for the block stack (control frames). +pub const DEFAULT_BLOCK_STACK_SIZE: usize = 1024; // 1024 frames -/// Default initial size for the call stack. -pub const DEFAULT_CALL_STACK_INIT_SIZE: usize = 128; +/// Default initial size for the call stack (function frames). +pub const DEFAULT_CALL_STACK_SIZE: usize = 1024; // 1024 frames /// Configuration for the WebAssembly interpreter #[derive(Debug, Clone)] #[non_exhaustive] pub struct Config { /// Initial size of the 32-bit value stack (i32, f32 values). - pub stack_32_init_size: usize, + pub stack_32_size: usize, /// Initial size of the 64-bit value stack (i64, f64 values). - pub stack_64_init_size: usize, + pub stack_64_size: usize, /// Initial size of the 128-bit value stack (v128 values). - pub stack_128_init_size: usize, + pub stack_128_size: usize, /// Initial size of the reference value stack (funcref, externref values). - pub stack_ref_init_size: usize, - /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. - pub stack_32_max_size: Option, - /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. - pub stack_64_max_size: Option, - /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. - pub stack_128_max_size: Option, - /// Optional maximum sizes for the stacks. If set, the interpreter will enforce these limits and return an error if they are exceeded. - pub stack_ref_max_size: Option, - + pub stack_ref_size: usize, /// Initial size of the call stack. - pub call_stack_init_size: usize, - /// The maximum size of the call stack. If set, the interpreter will enforce this limit and return an error if it is exceeded. - pub call_stack_max_size: Option, - + pub call_stack_size: usize, /// Initial size of the control stack (block stack). - pub block_stack_init_size: usize, - /// Optional maximum size for the control stack (block stack). If set, the interpreter will enforce this limit and return an error if it is exceeded. - pub block_stack_max_size: Option, + pub block_stack_size: usize, } impl Config { @@ -98,43 +80,17 @@ impl Config { pub fn new() -> Self { Self::default() } - - /// Set the same maximum size for all stacks. If set, the interpreter will enforce this limit and return an error if it is exceeded. - pub fn with_max_stack_size(mut self, max_size: usize) -> Self { - self.stack_32_max_size = Some(max_size); - self.stack_64_max_size = Some(max_size); - self.stack_128_max_size = Some(max_size); - self.stack_ref_max_size = Some(max_size); - self.block_stack_max_size = Some(max_size); - self - } - - /// Set the same initial size for all stacks. - pub fn with_initial_stack_size(mut self, init_size: usize) -> Self { - self.stack_32_init_size = init_size; - self.stack_64_init_size = init_size; - self.stack_128_init_size = init_size; - self.stack_ref_init_size = init_size; - self.block_stack_init_size = init_size; - self - } } impl Default for Config { fn default() -> Self { Self { - stack_32_init_size: DEFAULT_VALUE_STACK_32_INIT_SIZE, - stack_64_init_size: DEFAULT_VALUE_STACK_64_INIT_SIZE, - stack_128_init_size: DEFAULT_VALUE_STACK_128_INIT_SIZE, - stack_ref_init_size: DEFAULT_VALUE_STACK_REF_INIT_SIZE, - block_stack_init_size: DEFAULT_BLOCK_STACK_INIT_SIZE, - call_stack_init_size: DEFAULT_CALL_STACK_INIT_SIZE, - call_stack_max_size: None, - stack_32_max_size: None, - stack_64_max_size: None, - stack_128_max_size: None, - stack_ref_max_size: None, - block_stack_max_size: None, + stack_32_size: DEFAULT_VALUE_STACK_32_SIZE, + stack_64_size: DEFAULT_VALUE_STACK_64_SIZE, + stack_128_size: DEFAULT_VALUE_STACK_128_SIZE, + stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE, + call_stack_size: DEFAULT_CALL_STACK_SIZE, + block_stack_size: DEFAULT_BLOCK_STACK_SIZE, } } } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 4b3a9696..4788ac43 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,8 +1,8 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::{fmt::Display, ops::ControlFlow}; -use tinywasm_types::FuncType; use tinywasm_types::archive::TwasmError; +use tinywasm_types::FuncType; #[cfg(feature = "parser")] pub use tinywasm_parser::ParseError; @@ -121,6 +121,12 @@ pub enum Trap { /// Call stack overflow CallStackOverflow, + /// Block stack overflow + BlockStackOverflow, + + /// Value stack overflow + ValueStackOverflow, + /// An undefined element was encountered UndefinedElement { /// The element index @@ -153,6 +159,8 @@ impl Trap { Self::InvalidConversionToInt => "invalid conversion to integer", Self::IntegerOverflow => "integer overflow", Self::CallStackOverflow => "call stack exhausted", + Self::BlockStackOverflow => "block stack exhausted", + Self::ValueStackOverflow => "value stack exhausted", Self::UndefinedElement { .. } => "undefined element", Self::UninitializedElement { .. } => "uninitialized element", Self::IndirectCallTypeMismatch { .. } => "indirect call type mismatch", @@ -235,6 +243,8 @@ impl Display for Trap { Self::InvalidConversionToInt => write!(f, "invalid conversion to integer"), Self::IntegerOverflow => write!(f, "integer overflow"), Self::CallStackOverflow => write!(f, "call stack exhausted"), + Self::BlockStackOverflow => write!(f, "block stack exhausted"), + Self::ValueStackOverflow => write!(f, "value stack exhausted"), Self::UndefinedElement { index } => write!(f, "undefined element: index={index}"), Self::UninitializedElement { index } => { write!(f, "uninitialized element: index={index}") diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 8d2e211a..dcdd2f8b 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -80,33 +80,33 @@ impl<'store> Executor<'store> { Drop64 => self.store.stack.values.drop::(), Drop128 => self.store.stack.values.drop::(), DropRef => self.store.stack.values.drop::(), - Select32 => self.store.stack.values.select::(), - Select64 => self.store.stack.values.select::(), - Select128 => self.store.stack.values.select::(), - SelectRef => self.store.stack.values.select::(), + Select32 => self.store.stack.values.select::().to_cf()?, + Select64 => self.store.stack.values.select::().to_cf()?, + Select128 => self.store.stack.values.select::().to_cf()?, + SelectRef => self.store.stack.values.select::().to_cf()?, Call(v) => return self.exec_call_direct::(*v), CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), ReturnCall(v) => return self.exec_call_direct::(*v), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())), - IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())), - IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)), + If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())).to_cf()?, + IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())).to_cf()?, + IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)).to_cf()?, Else(end_offset) => self.exec_else(*end_offset), - Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())), - LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())), - LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)), - Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())), - BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())), - BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)), + Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())).to_cf()?, + LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())).to_cf()?, + LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)).to_cf()?, + Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())).to_cf()?, + BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())).to_cf()?, + BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)).to_cf()?, Br(v) => return self.exec_br(*v), BrIf(v) => return self.exec_br_if(*v), BrTable(default, len) => return self.exec_brtable(*default, *len), Return => return self.exec_return(), EndBlockFrame => self.exec_end_block(), - LocalGet32(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), - LocalGet64(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), - LocalGet128(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), - LocalGetRef(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)), + LocalGet32(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, + LocalGet64(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, + LocalGet128(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, + LocalGetRef(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, LocalSet32(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), LocalSet64(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), LocalSet128(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), @@ -119,15 +119,15 @@ impl<'store> Executor<'store> { LocalTee64(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), LocalTee128(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), LocalTeeRef(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), - GlobalGet(global_index) => self.exec_global_get(*global_index), + GlobalGet(global_index) => self.exec_global_get(*global_index).to_cf()?, GlobalSet32(global_index) => self.exec_global_set::(*global_index), GlobalSet64(global_index) => self.exec_global_set::(*global_index), GlobalSet128(global_index) => self.exec_global_set::(*global_index), GlobalSetRef(global_index) => self.exec_global_set::(*global_index), - I32Const(val) => self.exec_const(*val), - I64Const(val) => self.exec_const(*val), - F32Const(val) => self.exec_const(*val), - F64Const(val) => self.exec_const(*val), + I32Const(val) => self.exec_const(*val).to_cf()?, + I64Const(val) => self.exec_const(*val).to_cf()?, + F32Const(val) => self.exec_const(*val).to_cf()?, + F64Const(val) => self.exec_const(*val).to_cf()?, I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), @@ -208,11 +208,11 @@ impl<'store> Executor<'store> { I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), // Reference types - RefFunc(func_idx) => self.exec_const::(Some(*func_idx)), - RefNull(_) => self.exec_const::(None), - RefIsNull => self.exec_ref_is_null(), - MemorySize(addr) => self.exec_memory_size(*addr), - MemoryGrow(addr) => self.exec_memory_grow(*addr), + RefFunc(func_idx) => self.exec_const::(Some(*func_idx)).to_cf()?, + RefNull(_) => self.exec_const::(None).to_cf()?, + RefIsNull => self.exec_ref_is_null().to_cf()?, + MemorySize(addr) => self.exec_memory_size(*addr).to_cf()?, + MemoryGrow(addr) => self.exec_memory_grow(*addr).to_cf()?, // Bulk memory operations MemoryCopy(from, to) => self.exec_memory_copy(*from, *to).to_cf()?, @@ -342,7 +342,7 @@ impl<'store> Executor<'store> { V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, - V128Const(arg) => self.exec_const::(self.cf.data().v128_constants[*arg as usize].into()), + V128Const(arg) => self.exec_const::(self.cf.data().v128_constants[*arg as usize].into()).to_cf()?, I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), @@ -573,7 +573,7 @@ impl<'store> Executor<'store> { let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.store.stack.blocks.len() as u32); self.cf.incr_instr_ptr(); // skip the call instruction - self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?; + self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame)).to_cf()?; } self.module.swap_with(self.cf.module_addr(), self.store); @@ -582,7 +582,7 @@ impl<'store> Executor<'store> { fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::>(); let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms).to_cf()?; - self.store.stack.values.extend_from_wasmvalues(&res); + self.store.stack.values.extend_from_wasmvalues(&res).to_cf()?; self.cf.incr_instr_ptr(); ControlFlow::Continue(()) } @@ -635,21 +635,26 @@ impl<'store> Executor<'store> { } } - fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) { + fn exec_if( + &mut self, + else_offset: u32, + end_offset: u32, + (params, results): (StackHeight, StackHeight), + ) -> Result<()> { // truthy value is on the top of the stack, so enter the then block if self.store.stack.values.pop::() != 0 { - self.enter_block(end_offset, BlockType::If, (params, results)); - return; + self.enter_block(end_offset, BlockType::If, (params, results))?; + return Ok(()); } // falsy value is on the top of the stack if else_offset == 0 { self.cf.jump(end_offset); - return; + return Ok(()); } self.cf.jump(else_offset); - self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)); + self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)) } fn exec_else(&mut self, end_offset: u32) { self.exec_end_block(); @@ -659,7 +664,12 @@ impl<'store> Executor<'store> { let ty = self.module.func_ty(idx); ((&*ty.params).into(), (&*ty.results).into()) } - fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { + fn enter_block( + &mut self, + end_instr_offset: u32, + ty: BlockType, + (params, results): (StackHeight, StackHeight), + ) -> Result<()> { self.store.stack.blocks.push(BlockFrame { instr_ptr: self.cf.instr_ptr() as u32, end_instr_offset, @@ -667,7 +677,7 @@ impl<'store> Executor<'store> { results, params, ty, - }); + }) } fn exec_br(&mut self, to: u32) -> ControlFlow> { if self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() { @@ -730,25 +740,23 @@ impl<'store> Executor<'store> { self.store.stack.values.truncate_keep(block.stack_ptr, block.results); } - fn exec_global_get(&mut self, global_index: u32) { - self.store - .stack - .values - .push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))); + fn exec_global_get(&mut self, global_index: u32) -> Result<()> { + self.store.stack.values.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) } + fn exec_global_set(&mut self, global_index: u32) { let val = self.store.stack.values.pop::().into(); self.store.state.set_global_val(self.module.resolve_global_addr(global_index), val); } - fn exec_const(&mut self, val: T) { - self.store.stack.values.push(val); + fn exec_const(&mut self, val: T) -> Result<()> { + self.store.stack.values.push(val) } - fn exec_ref_is_null(&mut self) { + fn exec_ref_is_null(&mut self) -> Result<()> { let is_null = i32::from(self.store.stack.values.pop::().is_none()); - self.store.stack.values.push::(is_null); + self.store.stack.values.push::(is_null) } - fn exec_memory_size(&mut self, addr: u32) { + fn exec_memory_size(&mut self, addr: u32) -> Result<()> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(addr)); match mem.is_64bit() { @@ -756,7 +764,7 @@ impl<'store> Executor<'store> { false => self.store.stack.values.push::(mem.page_count as i32), } } - fn exec_memory_grow(&mut self, addr: u32) { + fn exec_memory_grow(&mut self, addr: u32) -> Result<()> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); let prev_size = mem.page_count; @@ -772,9 +780,11 @@ impl<'store> Executor<'store> { None => -1_i64, }, ) { - (true, size) => self.store.stack.values.push::(size), - (false, size) => self.store.stack.values.push::(size as i32), + (true, size) => self.store.stack.values.push::(size)?, + (false, size) => self.store.stack.values.push::(size as i32)?, }; + + Ok(()) } fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { @@ -871,7 +881,7 @@ impl<'store> Executor<'store> { let offset = lane as usize * LOAD_SIZE; imm[offset..offset + LOAD_SIZE].copy_from_slice(&val); - self.store.stack.values.push(Value128::from_mem_bytes(imm)); + self.store.stack.values.push(Value128::from_mem_bytes(imm)).to_cf()?; ControlFlow::Continue(()) } @@ -896,7 +906,7 @@ impl<'store> Executor<'store> { }))); }; let val = mem.load_as::(addr).to_cf()?; - self.store.stack.values.push(cast(val)); + self.store.stack.values.push(cast(val)).to_cf()?; ControlFlow::Continue(()) } @@ -950,7 +960,7 @@ impl<'store> Executor<'store> { let idx: i32 = self.store.stack.values.pop::(); let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); let v = table.get_wasm_val(idx as u32)?; - self.store.stack.values.push_dyn(v.into()); + self.store.stack.values.push_dyn(v.into())?; Ok(()) } fn exec_table_set(&mut self, table_index: u32) -> Result<()> { @@ -961,7 +971,7 @@ impl<'store> Executor<'store> { } fn exec_table_size(&mut self, table_index: u32) -> Result<()> { let table = self.store.state.get_table(self.module.resolve_table_addr(table_index)); - self.store.stack.values.push(table.size()); + self.store.stack.values.push(table.size())?; Ok(()) } fn exec_table_init(&mut self, elem_index: u32, table_index: u32) -> Result<()> { @@ -1012,8 +1022,8 @@ impl<'store> Executor<'store> { let val = self.store.stack.values.pop::(); match table.grow(n, val.into()) { - Ok(()) => self.store.stack.values.push(sz), - Err(_) => self.store.stack.values.push(-1_i32), + Ok(()) => self.store.stack.values.push(sz)?, + Err(_) => self.store.stack.values.push(-1_i32)?, } Ok(()) diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs index e4d4c280..68dda843 100644 --- a/crates/tinywasm/src/interpreter/stack/block_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -2,30 +2,33 @@ use crate::engine::Config; use alloc::vec::Vec; use crate::interpreter::values::{StackHeight, StackLocation}; +use crate::{Result, Trap}; #[derive(Debug)] pub(crate) struct BlockStack(Vec); impl BlockStack { pub(crate) fn new(config: &Config) -> Self { - Self(Vec::with_capacity(config.block_stack_init_size)) + Self(Vec::with_capacity(config.block_stack_size)) } pub(crate) fn clear(&mut self) { self.0.clear(); } - #[inline(always)] pub(crate) fn len(&self) -> usize { self.0.len() } - #[inline(always)] - pub(crate) fn push(&mut self, block: BlockFrame) { + pub(crate) fn push(&mut self, block: BlockFrame) -> Result<()> { + if self.0.len() >= self.0.capacity() { + return Err(Trap::BlockStackOverflow.into()); + } + self.0.push(block); + Ok(()) } - #[inline] /// get the label at the given index, where 0 is the top of the stack pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { let len = (self.0.len() as u32) - offset; @@ -38,13 +41,14 @@ impl BlockStack { Some(&self.0[self.0.len() - index as usize - 1]) } - #[inline(always)] pub(crate) fn pop(&mut self) -> BlockFrame { - self.0.pop().expect("block stack underflow, this is a bug") + match self.0.pop() { + Some(frame) => frame, + None => unreachable!("Block stack underflow, this is a bug"), + } } /// keep the top `len` blocks and discard the rest - #[inline(always)] pub(crate) fn truncate(&mut self, len: u32) { self.0.truncate(len as usize); } diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 4b0fad60..688dde81 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,43 +1,36 @@ -use core::ops::ControlFlow; - use super::BlockType; -use crate::Trap; use crate::interpreter::{Value128, values::*}; -use crate::{Error, unlikely}; +use crate::{Result, Trap, unlikely}; use alloc::boxed::Box; use alloc::{rc::Rc, vec::Vec}; use tinywasm_types::{ArcSlice, Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; -pub(crate) const MAX_CALL_STACK_SIZE: usize = 1024; - #[derive(Debug)] pub(crate) struct CallStack { stack: Vec, } impl CallStack { - #[inline] pub(crate) fn new(config: &crate::engine::Config) -> Self { - Self { stack: Vec::with_capacity(config.call_stack_init_size) } + Self { stack: Vec::with_capacity(config.call_stack_size) } } pub(crate) fn clear(&mut self) { self.stack.clear(); } - #[inline] pub(crate) fn pop(&mut self) -> Option { self.stack.pop() } - #[inline] - pub(crate) fn push(&mut self, call_frame: CallFrame) -> ControlFlow> { - if unlikely((self.stack.len() + 1) >= MAX_CALL_STACK_SIZE) { - return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { + if unlikely(self.stack.len() >= self.stack.capacity()) { + return Err(Trap::CallStackOverflow.into()); } + self.stack.push(call_frame); - ControlFlow::Continue(()) + Ok(()) } } @@ -69,42 +62,34 @@ impl Locals { } impl CallFrame { - #[inline] pub(crate) fn instr_ptr(&self) -> usize { self.instr_ptr } - #[inline] - #[allow(dead_code)] pub(crate) fn data(&self) -> &WasmFunctionData { &self.func_instance.data } - #[inline(always)] pub(crate) fn incr_instr_ptr(&mut self) { self.instr_ptr += 1; } - #[inline] pub(crate) fn jump(&mut self, offset: u32) { self.instr_ptr += offset as usize; } - #[inline] pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { self.module_addr } #[inline(always)] pub(crate) fn fetch_instr(&self) -> &Instruction { - self - .func_instance - .instructions - .get(self.instr_ptr) - .unwrap_or_else(|| unreachable!("Instruction pointer out of bounds, this is a bug")) + match self.func_instance.instructions.get(self.instr_ptr) { + Some(instr) => instr, + None => unreachable!("Instruction pointer out of bounds, this is a bug"), + } } - #[inline] pub(crate) fn block_ptr(&self) -> u32 { self.block_ptr } @@ -125,7 +110,6 @@ impl CallFrame { /// Break to a block at the given index (relative to the current frame) /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) - #[inline] pub(crate) fn break_to( &mut self, break_to_relative: u32, @@ -168,7 +152,6 @@ impl CallFrame { Some(()) } - #[inline] pub(crate) fn new( func_instance: Rc, module_addr: ModuleInstanceAddr, @@ -206,7 +189,6 @@ impl CallFrame { Self { instr_ptr: 0, func_instance, module_addr, block_ptr, locals } } - #[inline] pub(crate) fn new_raw( func_instance: Rc, module_addr: ModuleInstanceAddr, diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index cec08a3f..b579a04f 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,25 +1,108 @@ +use alloc::boxed::Box; use alloc::vec::Vec; use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; -use crate::{Result, engine::Config, interpreter::*}; +use crate::{Result, Trap, engine::Config, interpreter::*}; use super::Locals; #[derive(Debug)] pub(crate) struct ValueStack { - pub(crate) stack_32: Vec, - pub(crate) stack_64: Vec, - pub(crate) stack_128: Vec, - pub(crate) stack_ref: Vec, + pub(crate) stack_32: Stack, + pub(crate) stack_64: Stack, + pub(crate) stack_128: Stack, + pub(crate) stack_ref: Stack, +} + +#[derive(Debug)] +pub(crate) struct Stack { + data: Box<[T]>, + len: usize, +} + +impl Stack { + pub(crate) fn with_size(size: usize) -> Self { + let mut data = Vec::with_capacity(size); + data.resize_with(size, T::default); + Self { data: data.into_boxed_slice(), len: 0 } + } + + pub(crate) fn len(&self) -> usize { + self.len + } + + pub(crate) fn clear(&mut self) { + self.len = 0; + } + + pub(crate) fn push(&mut self, value: T) -> Result<()> { + if self.len >= self.data.len() { + return Err(Trap::ValueStackOverflow.into()); + } + + self.data[self.len] = value; + self.len += 1; + Ok(()) + } + + pub(crate) fn pop(&mut self) -> T { + if self.len == 0 { + unreachable!("ValueStack underflow, this is a bug"); + } + + self.len -= 1; + self.data[self.len] + } + + pub(crate) fn last(&self) -> &T { + if self.len == 0 { + unreachable!("ValueStack underflow, this is a bug"); + } + &self.data[self.len - 1] + } + + pub(crate) fn last_mut(&mut self) -> &mut T { + if self.len == 0 { + unreachable!("ValueStack underflow, this is a bug"); + } + &mut self.data[self.len - 1] + } + + pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { + if self.len <= n { + return; + } + + let keep_tail = end_keep.min(self.len - n); + if keep_tail == 0 { + self.len = n; + return; + } + + let tail_start = self.len - keep_tail; + self.data.copy_within(tail_start..self.len, n); + self.len = n + keep_tail; + } + + pub(crate) fn pop_to_locals(&mut self, param_count: usize, local_count: usize) -> Box<[T]> { + let mut locals = alloc::vec![T::default(); local_count].into_boxed_slice(); + let start = + self.len.checked_sub(param_count).unwrap_or_else(|| unreachable!("value stack underflow, this is a bug")); + debug_assert!(param_count <= local_count, "param count exceeds local count"); + + locals[..param_count].copy_from_slice(&self.data[start..self.len]); + self.len = start; + locals + } } impl ValueStack { pub(crate) fn new(config: &Config) -> Self { Self { - stack_32: Vec::with_capacity(config.stack_32_init_size), - stack_64: Vec::with_capacity(config.stack_64_init_size), - stack_128: Vec::with_capacity(config.stack_128_init_size), - stack_ref: Vec::with_capacity(config.stack_ref_init_size), + stack_32: Stack::with_size(config.stack_32_size), + stack_64: Stack::with_size(config.stack_64_size), + stack_128: Stack::with_size(config.stack_128_size), + stack_ref: Stack::with_size(config.stack_ref_size), } } @@ -43,86 +126,73 @@ impl ValueStack { self.stack_32.len() + self.stack_64.len() + self.stack_128.len() + self.stack_ref.len() } - #[inline] pub(crate) fn peek(&self) -> T { T::stack_peek(self) } - #[inline] pub(crate) fn pop(&mut self) -> T { T::stack_pop(self) } - #[inline] - pub(crate) fn push(&mut self, value: T) { - T::stack_push(self, value); + pub(crate) fn push(&mut self, value: T) -> Result<()> { + T::stack_push(self, value) } - #[inline] pub(crate) fn drop(&mut self) { T::stack_pop(self); } - #[inline] - pub(crate) fn select(&mut self) { + pub(crate) fn select(&mut self) -> Result<()> { let cond: i32 = self.pop(); let val2: T = self.pop(); if cond == 0 { self.drop::(); - self.push(val2); + self.push(val2)?; } + Ok(()) } - #[inline] pub(crate) fn binary_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { T::stack_calculate(self, func) } - #[inline] - #[allow(dead_code)] pub(crate) fn ternary_same(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { T::stack_calculate3(self, func) } - #[inline] pub(crate) fn binary( &mut self, func: impl FnOnce(T, T) -> Result, ) -> Result<()> { let v2 = T::stack_pop(self); let v1 = T::stack_pop(self); - U::stack_push(self, func(v1, v2)?); + U::stack_push(self, func(v1, v2)?)?; Ok(()) } - #[inline] - #[allow(dead_code)] pub(crate) fn binary_diff( &mut self, func: impl FnOnce(A, B) -> Result, ) -> Result<()> { let v2 = B::stack_pop(self); let v1 = A::stack_pop(self); - RES::stack_push(self, func(v1, v2)?); + RES::stack_push(self, func(v1, v2)?)?; Ok(()) } - #[inline] pub(crate) fn unary( &mut self, func: impl FnOnce(T) -> Result, ) -> Result<()> { let v1 = T::stack_pop(self); - U::stack_push(self, func(v1)?); + U::stack_push(self, func(v1)?)?; Ok(()) } - #[inline] pub(crate) fn unary_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { T::replace_top(self, func) } - #[inline] pub(crate) fn pop_types<'a>( &'a mut self, val_types: impl IntoIterator, @@ -130,63 +200,30 @@ impl ValueStack { val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) } - #[inline] pub(crate) fn pop_locals(&mut self, pc: ValueCountsSmall, lc: ValueCounts) -> Locals { Locals { - locals_32: { - let mut locals_32 = { alloc::vec![Value32::default(); lc.c32 as usize].into_boxed_slice() }; - locals_32[0..pc.c32 as usize] - .copy_from_slice(&self.stack_32[(self.stack_32.len() - pc.c32 as usize)..]); - self.stack_32.truncate(self.stack_32.len() - pc.c32 as usize); - locals_32 - }, - locals_64: { - let mut locals_64 = { alloc::vec![Value64::default(); lc.c64 as usize].into_boxed_slice() }; - locals_64[0..pc.c64 as usize] - .copy_from_slice(&self.stack_64[(self.stack_64.len() - pc.c64 as usize)..]); - self.stack_64.truncate(self.stack_64.len() - pc.c64 as usize); - locals_64 - }, - locals_128: { - let mut locals_128 = { alloc::vec![Value128::default(); lc.c128 as usize].into_boxed_slice() }; - locals_128[0..pc.c128 as usize] - .copy_from_slice(&self.stack_128[(self.stack_128.len() - pc.c128 as usize)..]); - self.stack_128.truncate(self.stack_128.len() - pc.c128 as usize); - locals_128 - }, - locals_ref: { - let mut locals_ref = { alloc::vec![ValueRef::default(); lc.cref as usize].into_boxed_slice() }; - locals_ref[0..pc.cref as usize] - .copy_from_slice(&self.stack_ref[(self.stack_ref.len() - pc.cref as usize)..]); - self.stack_ref.truncate(self.stack_ref.len() - pc.cref as usize); - locals_ref - }, + locals_32: self.stack_32.pop_to_locals(pc.c32 as usize, lc.c32 as usize), + locals_64: self.stack_64.pop_to_locals(pc.c64 as usize, lc.c64 as usize), + locals_128: self.stack_128.pop_to_locals(pc.c128 as usize, lc.c128 as usize), + locals_ref: self.stack_ref.pop_to_locals(pc.cref as usize, lc.cref as usize), } } pub(crate) fn truncate_keep(&mut self, to: StackLocation, keep: StackHeight) { - #[inline(always)] - fn truncate_keep(data: &mut Vec, n: u32, end_keep: u32) { - let len = data.len() as u32; - if len <= n { - return; // No need to truncate if the current size is already less than or equal to total_to_keep - } - data.drain((n as usize)..(len - end_keep) as usize); - } - - truncate_keep(&mut self.stack_32, to.s32, u32::from(keep.s32)); - truncate_keep(&mut self.stack_64, to.s64, u32::from(keep.s64)); - truncate_keep(&mut self.stack_128, to.s128, u32::from(keep.s128)); - truncate_keep(&mut self.stack_ref, to.sref, u32::from(keep.sref)); + self.stack_32.truncate_keep(to.s32 as usize, usize::from(keep.s32)); + self.stack_64.truncate_keep(to.s64 as usize, usize::from(keep.s64)); + self.stack_128.truncate_keep(to.s128 as usize, usize::from(keep.s128)); + self.stack_ref.truncate_keep(to.sref as usize, usize::from(keep.sref)); } - pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) { + pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<()> { match value { - TinyWasmValue::Value32(v) => self.stack_32.push(v), - TinyWasmValue::Value64(v) => self.stack_64.push(v), - TinyWasmValue::Value128(v) => self.stack_128.push(v), - TinyWasmValue::ValueRef(v) => self.stack_ref.push(v), + TinyWasmValue::Value32(v) => self.stack_32.push(v)?, + TinyWasmValue::Value64(v) => self.stack_64.push(v)?, + TinyWasmValue::Value128(v) => self.stack_128.push(v)?, + TinyWasmValue::ValueRef(v) => self.stack_ref.push(v)?, } + Ok(()) } pub(crate) fn pop_wasmvalue(&mut self, val_type: ValType) -> WasmValue { @@ -201,17 +238,18 @@ impl ValueStack { } } - pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) { + pub(crate) fn extend_from_wasmvalues(&mut self, values: &[WasmValue]) -> Result<()> { for value in values { match value { - WasmValue::I32(v) => self.stack_32.push(*v as u32), - WasmValue::I64(v) => self.stack_64.push(*v as u64), - WasmValue::F32(v) => self.stack_32.push(v.to_bits()), - WasmValue::F64(v) => self.stack_64.push(v.to_bits()), - WasmValue::RefExtern(v) => self.stack_ref.push(v.addr()), - WasmValue::RefFunc(v) => self.stack_ref.push(v.addr()), - WasmValue::V128(v) => self.stack_128.push((*v).into()), + WasmValue::I32(v) => self.stack_32.push(*v as u32)?, + WasmValue::I64(v) => self.stack_64.push(*v as u64)?, + WasmValue::F32(v) => self.stack_32.push(v.to_bits())?, + WasmValue::F64(v) => self.stack_64.push(v.to_bits())?, + WasmValue::RefExtern(v) => self.stack_ref.push(v.addr())?, + WasmValue::RefFunc(v) => self.stack_ref.push(v.addr())?, + WasmValue::V128(v) => self.stack_128.push((*v).into())?, } } + Ok(()) } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 456fb93b..ac05bd4b 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,4 +1,4 @@ -use crate::{Result, interpreter::value128::Value128}; +use crate::{interpreter::value128::Value128, Result}; use super::stack::{Locals, ValueStack}; use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, WasmValue}; @@ -100,14 +100,19 @@ impl TinyWasmValue { /// Attaches a type to the value (panics if the size of the value is not the same as the type) pub fn attach_type(&self, ty: ValType) -> WasmValue { - match ty { - ValType::I32 => WasmValue::I32(self.unwrap_32() as i32), - ValType::I64 => WasmValue::I64(self.unwrap_64() as i64), - ValType::F32 => WasmValue::F32(f32::from_bits(self.unwrap_32())), - ValType::F64 => WasmValue::F64(f64::from_bits(self.unwrap_64())), - ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(self.unwrap_ref())), - ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(self.unwrap_ref())), - ValType::V128 => WasmValue::V128(self.unwrap_128().into()), + match (self, ty) { + (Self::Value32(v), ValType::I32) => WasmValue::I32(*v as i32), + (Self::Value64(v), ValType::I64) => WasmValue::I64(*v as i64), + (Self::Value32(v), ValType::F32) => WasmValue::F32(f32::from_bits(*v)), + (Self::Value64(v), ValType::F64) => WasmValue::F64(f64::from_bits(*v)), + (Self::ValueRef(v), ValType::RefExtern) => WasmValue::RefExtern(ExternRef::new(*v)), + (Self::ValueRef(v), ValType::RefFunc) => WasmValue::RefFunc(FuncRef::new(*v)), + (Self::Value128(v), ValType::V128) => WasmValue::V128((*v).into()), + + (_, ValType::I32 | ValType::F32) => panic!("Expected Value32"), + (_, ValType::I64 | ValType::F64) => panic!("Expected Value64"), + (_, ValType::RefExtern | ValType::RefFunc) => panic!("Expected ValueRef"), + (_, ValType::V128) => panic!("Expected Value128"), } } } @@ -144,7 +149,7 @@ mod sealed { } pub(crate) trait InternalValue: sealed::Sealed + Into { - fn stack_push(stack: &mut ValueStack, value: Self); + fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> where Self: Sized; @@ -177,63 +182,42 @@ macro_rules! impl_internalvalue { } impl InternalValue for $outer { - #[inline(always)] - fn stack_push(stack: &mut ValueStack, value: Self) { - stack.$stack.push($to_internal(value)); + fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()> { + stack.$stack.push($to_internal(value)) } - #[inline(always)] fn stack_pop(stack: &mut ValueStack) -> Self { - match stack.$stack.pop() { - Some(v) => $to_outer(v), - None => unreachable!("ValueStack underflow, this is a bug"), - } + $to_outer(stack.$stack.pop()) } - #[inline(always)] fn stack_peek(stack: &ValueStack) -> Self { - match stack.$stack.last() { - Some(v) => $to_outer(*v), - None => unreachable!("ValueStack underflow, this is a bug"), - } + $to_outer(*stack.$stack.last()) } - #[inline(always)] fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); - let (Some(v1), Some(v2)) = (v1, v2) else { - unreachable!("ValueStack underflow, this is a bug"); - }; *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2))?); - return Ok(()) + Ok(()) } - #[inline(always)] fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { let v3 = stack.$stack.pop(); let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); - let (Some(v1), Some(v2), Some(v3)) = (v1, v2, v3) else { - unreachable!("ValueStack underflow, this is a bug"); - }; *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2), $to_outer(v3))?); - return Ok(()) + Ok(()) } - #[inline(always)] fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { - let Some(v) = stack.$stack.last_mut() else { - unreachable!("ValueStack underflow, this is a bug"); - }; + let v = stack.$stack.last_mut(); *v = $to_internal(func($to_outer(*v))?); Ok(()) } - #[inline(always)] fn local_get(locals: &Locals, index: LocalAddr) -> Self { match locals.$locals.get(index as usize) { Some(v) => $to_outer(*v), @@ -241,7 +225,6 @@ macro_rules! impl_internalvalue { } } - #[inline(always)] fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) { match locals.$locals.get_mut(index as usize) { Some(v) => *v = $to_internal(value), diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 88982120..022db0d1 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -33,7 +33,7 @@ impl MemoryInstance { matches!(self.kind.arch(), MemoryArch::I64) } - #[inline(always)] + #[inline] pub(crate) fn len(&self) -> usize { self.data.len() } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 757cbb22..a0a025c3 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -5,7 +5,7 @@ use tinywasm_types::*; use crate::interpreter::TinyWasmValue; use crate::interpreter::stack::Stack; -use crate::{Engine, Error, Function, ModuleInstance, Result, Trap, cold}; +use crate::{Engine, Error, Function, ModuleInstance, Result, Trap}; mod data; mod element; @@ -94,25 +94,31 @@ pub(crate) struct State { impl State { /// Get the function at the actual index in the store - #[inline] pub(crate) fn get_func(&self, addr: FuncAddr) -> &FunctionInstance { - &self.funcs[addr as usize] + match self.funcs.get(addr as usize) { + Some(func) => func, + None => unreachable!("function {addr} not found. This should be unreachable"), + } } /// Get the memory at the actual index in the store - #[inline] pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { - &self.memories[addr as usize] + match self.memories.get(addr as usize) { + Some(mem) => mem, + None => unreachable!("memory {addr} not found. This should be unreachable"), + } } /// Get the memory at the actual index in the store #[inline(always)] pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { - &mut self.memories[addr as usize] + match self.memories.get_mut(addr as usize) { + Some(mem) => mem, + None => unreachable!("memory {addr} not found. This should be unreachable"), + } } /// Get the memory at the actual index in the store - #[inline(always)] pub(crate) fn get_mems_mut( &mut self, addr: MemAddr, @@ -120,27 +126,27 @@ impl State { ) -> Result<(&mut MemoryInstance, &mut MemoryInstance)> { match get_pair_mut(&mut self.memories, addr as usize, addr2 as usize) { Some(mems) => Ok(mems), - None => { - cold(); - Err(Self::not_found_error("memory")) - } + None => unreachable!("memory {addr} or {addr2} not found. This should be unreachable"), } } /// Get the table at the actual index in the store - #[inline] pub(crate) fn get_table(&self, addr: TableAddr) -> &TableInstance { - &self.tables[addr as usize] + match self.tables.get(addr as usize) { + Some(table) => table, + None => unreachable!("table {addr} not found. This should be unreachable"), + } } /// Get the table at the actual index in the store - #[inline] pub(crate) fn get_table_mut(&mut self, addr: TableAddr) -> &mut TableInstance { - &mut self.tables[addr as usize] + match self.tables.get_mut(addr as usize) { + Some(table) => table, + None => unreachable!("table {addr} not found. This should be unreachable"), + } } /// Get two mutable tables at the actual index in the store - #[inline] pub(crate) fn get_tables_mut( &mut self, addr: TableAddr, @@ -148,44 +154,48 @@ impl State { ) -> Result<(&mut TableInstance, &mut TableInstance)> { match get_pair_mut(&mut self.tables, addr as usize, addr2 as usize) { Some(tables) => Ok(tables), - None => { - cold(); - Err(Self::not_found_error("table")) - } + None => unreachable!("table {addr} or {addr2} not found. This should be unreachable"), } } /// Get the data at the actual index in the store - #[inline] pub(crate) fn get_data_mut(&mut self, addr: DataAddr) -> &mut DataInstance { - &mut self.data[addr as usize] + match self.data.get_mut(addr as usize) { + Some(data) => data, + None => unreachable!("data {addr} not found. This should be unreachable"), + } } /// Get the element at the actual index in the store - #[inline] pub(crate) fn get_elem_mut(&mut self, addr: ElemAddr) -> &mut ElementInstance { - &mut self.elements[addr as usize] + match self.elements.get_mut(addr as usize) { + Some(elem) => elem, + None => unreachable!("element {addr} not found. This should be unreachable"), + } } /// Get the global at the actual index in the store - #[inline] pub(crate) fn get_global(&self, addr: GlobalAddr) -> &GlobalInstance { - &self.globals[addr as usize] + match self.globals.get(addr as usize) { + Some(global) => global, + None => unreachable!("global {addr} not found. This should be unreachable"), + } } /// Get the global at the actual index in the store pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { - self.globals[addr as usize].value.get() + match self.globals.get(addr as usize) { + Some(global) => global.value.get(), + None => unreachable!("global {addr} not found. This should be unreachable"), + } } /// Set the global at the actual index in the store pub(crate) fn set_global_val(&mut self, addr: MemAddr, value: TinyWasmValue) { - self.globals[addr as usize].value.set(value); - } - - #[cold] - fn not_found_error(name: &str) -> Error { - Error::Other(format!("{name} not found")) + match self.globals.get_mut(addr as usize) { + Some(global) => global.value.set(value), + None => unreachable!("global {addr} not found. This should be unreachable"), + } } } @@ -421,7 +431,7 @@ impl Store { } /// Evaluate a constant expression that's either a i32 or a i64 as a global or a const instruction - pub(crate) fn eval_size_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result { + fn eval_size_const(&self, const_instr: tinywasm_types::ConstInstruction) -> Result { Ok(match const_instr { ConstInstruction::I32Const(i) => i64::from(i), ConstInstruction::I64Const(i) => i, @@ -435,7 +445,7 @@ impl Store { } /// Evaluate a constant expression - pub(crate) fn eval_const( + fn eval_const( &self, const_instr: &tinywasm_types::ConstInstruction, module_global_addrs: &[Addr], From e25773f8356710225c0b7a71f1664d7b668c0fd2 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 28 Mar 2026 14:45:37 +0100 Subject: [PATCH 13/47] chore: undo BlockStack changes Signed-off-by: Henry --- crates/tinywasm/src/engine.rs | 11 ++--- crates/tinywasm/src/error.rs | 2 +- crates/tinywasm/src/interpreter/executor.rs | 40 +++++++------------ .../src/interpreter/stack/block_stack.rs | 14 ++----- crates/tinywasm/src/interpreter/values.rs | 2 +- crates/tinywasm/tests/test-wasm-tail-call.rs | 1 + 6 files changed, 28 insertions(+), 42 deletions(-) diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index 261f18cc..fe305138 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -52,10 +52,10 @@ pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots pub const DEFAULT_VALUE_STACK_REF_SIZE: usize = 4 * 1024; // 4k slots /// Default initial size for the block stack (control frames). -pub const DEFAULT_BLOCK_STACK_SIZE: usize = 1024; // 1024 frames +pub const DEFAULT_BLOCK_STACK_SIZE: usize = 2048; // 1024 frames /// Default initial size for the call stack (function frames). -pub const DEFAULT_CALL_STACK_SIZE: usize = 1024; // 1024 frames +pub const DEFAULT_CALL_STACK_SIZE: usize = 2048; // 1024 frames /// Configuration for the WebAssembly interpreter #[derive(Debug, Clone)] @@ -71,8 +71,9 @@ pub struct Config { pub stack_ref_size: usize, /// Initial size of the call stack. pub call_stack_size: usize, - /// Initial size of the control stack (block stack). - pub block_stack_size: usize, + + /// Initial size of the block stack. + pub block_stack_initial_size: usize, } impl Config { @@ -90,7 +91,7 @@ impl Default for Config { stack_128_size: DEFAULT_VALUE_STACK_128_SIZE, stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE, call_stack_size: DEFAULT_CALL_STACK_SIZE, - block_stack_size: DEFAULT_BLOCK_STACK_SIZE, + block_stack_initial_size: DEFAULT_BLOCK_STACK_SIZE, } } } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 4788ac43..51b5c467 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,8 +1,8 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::{fmt::Display, ops::ControlFlow}; -use tinywasm_types::archive::TwasmError; use tinywasm_types::FuncType; +use tinywasm_types::archive::TwasmError; #[cfg(feature = "parser")] pub use tinywasm_parser::ParseError; diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index dcdd2f8b..08a24cfc 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -88,16 +88,16 @@ impl<'store> Executor<'store> { CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), ReturnCall(v) => return self.exec_call_direct::(*v), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())).to_cf()?, - IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())).to_cf()?, - IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)).to_cf()?, + If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())), + IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())), + IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)), Else(end_offset) => self.exec_else(*end_offset), - Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())).to_cf()?, - LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())).to_cf()?, - LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)).to_cf()?, - Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())).to_cf()?, - BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())).to_cf()?, - BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)).to_cf()?, + Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())), + LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())), + LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)), + Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())), + BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())), + BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)), Br(v) => return self.exec_br(*v), BrIf(v) => return self.exec_br_if(*v), BrTable(default, len) => return self.exec_brtable(*default, *len), @@ -635,26 +635,21 @@ impl<'store> Executor<'store> { } } - fn exec_if( - &mut self, - else_offset: u32, - end_offset: u32, - (params, results): (StackHeight, StackHeight), - ) -> Result<()> { + fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) { // truthy value is on the top of the stack, so enter the then block if self.store.stack.values.pop::() != 0 { - self.enter_block(end_offset, BlockType::If, (params, results))?; - return Ok(()); + self.enter_block(end_offset, BlockType::If, (params, results)); + return; } // falsy value is on the top of the stack if else_offset == 0 { self.cf.jump(end_offset); - return Ok(()); + return; } self.cf.jump(else_offset); - self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)) + self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)); } fn exec_else(&mut self, end_offset: u32) { self.exec_end_block(); @@ -664,12 +659,7 @@ impl<'store> Executor<'store> { let ty = self.module.func_ty(idx); ((&*ty.params).into(), (&*ty.results).into()) } - fn enter_block( - &mut self, - end_instr_offset: u32, - ty: BlockType, - (params, results): (StackHeight, StackHeight), - ) -> Result<()> { + fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { self.store.stack.blocks.push(BlockFrame { instr_ptr: self.cf.instr_ptr() as u32, end_instr_offset, diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs index 68dda843..f653460f 100644 --- a/crates/tinywasm/src/interpreter/stack/block_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/block_stack.rs @@ -2,14 +2,13 @@ use crate::engine::Config; use alloc::vec::Vec; use crate::interpreter::values::{StackHeight, StackLocation}; -use crate::{Result, Trap}; #[derive(Debug)] pub(crate) struct BlockStack(Vec); impl BlockStack { pub(crate) fn new(config: &Config) -> Self { - Self(Vec::with_capacity(config.block_stack_size)) + Self(Vec::with_capacity(config.block_stack_initial_size)) } pub(crate) fn clear(&mut self) { @@ -20,20 +19,15 @@ impl BlockStack { self.0.len() } - pub(crate) fn push(&mut self, block: BlockFrame) -> Result<()> { - if self.0.len() >= self.0.capacity() { - return Err(Trap::BlockStackOverflow.into()); - } - + pub(crate) fn push(&mut self, block: BlockFrame) { self.0.push(block); - Ok(()) } /// get the label at the given index, where 0 is the top of the stack pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { - let len = (self.0.len() as u32) - offset; + let len = (self.0.len() as u32).checked_sub(offset)?; - // the vast majority of wasm functions don't use break to return + // the vast majority of wasm functions don't use break to return, but it is allowed in the spec if index >= len { return None; } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index ac05bd4b..447b1635 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,4 +1,4 @@ -use crate::{interpreter::value128::Value128, Result}; +use crate::{Result, interpreter::value128::Value128}; use super::stack::{Locals, ValueStack}; use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, WasmValue}; diff --git a/crates/tinywasm/tests/test-wasm-tail-call.rs b/crates/tinywasm/tests/test-wasm-tail-call.rs index f888f388..b6d8b2bf 100644 --- a/crates/tinywasm/tests/test-wasm-tail-call.rs +++ b/crates/tinywasm/tests/test-wasm-tail-call.rs @@ -8,6 +8,7 @@ fn main() -> Result<()> { let mut test_suite = TestSuite::new(); test_suite.run_files(proposal(&Proposal::TailCall))?; + test_suite.print_errors(); test_suite.save_csv("./tests/generated/wasm-tail-call.csv", env!("CARGO_PKG_VERSION"))?; test_suite.report_status() } From 1ff5945c3373bd86191123690c81aa80deca57c6 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 28 Mar 2026 18:17:12 +0100 Subject: [PATCH 14/47] chore: cleanup Signed-off-by: Henry --- crates/tinywasm/src/func.rs | 5 +- crates/tinywasm/src/imports.rs | 4 +- crates/tinywasm/src/instance.rs | 127 +++++++++--------- crates/tinywasm/src/interpreter/executor.rs | 99 +++++++++----- .../tinywasm/src/interpreter/no_std_floats.rs | 20 +-- .../tinywasm/src/interpreter/num_helpers.rs | 9 -- .../src/interpreter/stack/call_stack.rs | 125 +++++++---------- crates/tinywasm/src/interpreter/value128.rs | 15 --- crates/tinywasm/src/interpreter/values.rs | 3 - crates/tinywasm/src/store/memory.rs | 9 +- crates/tinywasm/src/store/mod.rs | 39 ++++-- crates/tinywasm/tests/testsuite/run.rs | 8 +- crates/tinywasm/tests/testsuite/util.rs | 2 +- crates/types/src/lib.rs | 3 - 14 files changed, 215 insertions(+), 253 deletions(-) diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 3a3985be..6cf0c8ce 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -56,12 +56,11 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let callframe = CallFrame::new(wasm_func, func_inst.owner, params, 0); + let callframe = CallFrame::new_with_params(wasm_func.locals, self.addr, func_inst.owner, params, 0); // 7. Push the frame f to the call stack - // & 8. Push the values to the stack (Not needed since the call frame owns the values) + // & 8. Push the values to the stack store.stack.clear(); - // 9. Invoke the function instance InterpreterRuntime::exec(store, callframe)?; diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index dcd5e12a..099396b0 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -68,7 +68,9 @@ impl FuncContext<'_> { /// Get a reference to the module instance pub fn module(&self) -> crate::ModuleInstance { - self.store.get_module_instance_raw(self.module_addr) + self.store.get_module_instance(self.module_addr).unwrap_or_else(|| { + unreachable!("invalid module instance address in host function context: {}", self.module_addr) + }) } /// Get a reference to an exported memory diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 7eddb101..45542f99 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -35,20 +35,68 @@ pub(crate) struct ModuleInstanceInner { pub(crate) exports: ArcSlice, } -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; +impl ModuleInstanceInner { + pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { + match self.types.get(addr as usize) { + Some(ty) => ty, + None => unreachable!("invalid function address: {addr}"), + } } - #[inline] - pub(crate) fn swap_with(&mut self, other_addr: ModuleInstanceAddr, store: &mut Store) { - if other_addr != self.id() { - self.swap(store.get_module_instance_raw(other_addr)) + pub(crate) fn func_addrs(&self) -> &[FuncAddr] { + &self.func_addrs + } + + // resolve a function address to the global store address + pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { + match self.func_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid function address: {addr}"), + } + } + + // resolve a table address to the global store address + pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { + match self.table_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid table address: {addr}"), + } + } + + // resolve a memory address to the global store address + pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { + match self.mem_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid memory address: {addr}"), } } + // resolve a data address to the global store address + pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { + match self.data_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid data address: {addr}"), + } + } + + // resolve a memory address to the global store address + pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { + match self.elem_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid element address: {addr}"), + } + } + + // resolve a global address to the global store address + pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { + match self.global_addrs.get(addr as usize) { + Some(addr) => *addr, + None => unreachable!("invalid global address: {addr}"), + } + } +} + +impl ModuleInstance { /// Get the module instance's address #[inline] pub fn id(&self) -> ModuleInstanceAddr { @@ -91,12 +139,12 @@ impl ModuleInstance { exports: module.0.exports.clone(), }; - let instance = Self::new(instance); + let instance = Rc::new(instance); store.add_instance(instance.clone()); match (elem_trapped, data_trapped) { (Some(trap), _) | (_, Some(trap)) => Err(trap.into()), - _ => Ok(instance), + _ => Ok(ModuleInstance(instance)), } } @@ -113,57 +161,6 @@ impl ModuleInstance { Some(ExternVal::new(exports.kind, *addr)) } - #[inline] - pub(crate) fn new(inner: ModuleInstanceInner) -> Self { - Self(Rc::new(inner)) - } - - #[inline] - pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { - &self.0.types[addr as usize] - } - - #[inline] - pub(crate) fn func_addrs(&self) -> &[FuncAddr] { - &self.0.func_addrs - } - - // resolve a function address to the global store address - #[inline] - pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { - self.0.func_addrs[addr as usize] - } - - // resolve a table address to the global store address - #[inline] - pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { - self.0.table_addrs[addr as usize] - } - - // resolve a memory address to the global store address - #[inline] - pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { - self.0.mem_addrs[addr as usize] - } - - // resolve a data address to the global store address - #[inline] - pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { - self.0.data_addrs[addr as usize] - } - - // resolve a memory address to the global store address - #[inline] - pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { - self.0.elem_addrs[addr as usize] - } - - // resolve a global address to the global store address - #[inline] - pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { - self.0.global_addrs[addr as usize] - } - /// Get an exported function by name pub fn exported_func_untyped(&self, store: &Store, name: &str) -> Result { if self.0.store_id != store.id() { @@ -211,13 +208,13 @@ impl ModuleInstance { /// Get a memory by address pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result> { - let mem = store.state.get_mem(self.resolve_mem_addr(addr)); + let mem = store.state.get_mem(self.0.resolve_mem_addr(addr)); Ok(MemoryRef(mem)) } /// Get a memory by address (mutable) pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { - let mem = store.state.get_mem_mut(self.resolve_mem_addr(addr)); + let mem = store.state.get_mem_mut(self.0.resolve_mem_addr(addr)); Ok(MemoryRefMut(mem)) } @@ -244,7 +241,7 @@ impl ModuleInstance { } }; - let func_addr = self.resolve_func_addr(func_index); + let func_addr = self.0.resolve_func_addr(func_index); let func_inst = store.state.get_func(func_addr); let ty = func_inst.func.ty(); diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 08a24cfc..5dfad851 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -12,19 +12,24 @@ use tinywasm_types::*; use super::num_helpers::*; use super::stack::{BlockFrame, BlockType}; use super::values::*; +use crate::instance::ModuleInstanceInner; use crate::interpreter::Value128; use crate::*; pub(crate) struct Executor<'store> { - pub(crate) cf: CallFrame, - pub(crate) module: ModuleInstance, - pub(crate) store: &'store mut Store, + cf: CallFrame, + instructions: ArcSlice, + func: Rc, + module: Rc, + store: &'store mut Store, } impl<'store> Executor<'store> { pub(crate) fn new(store: &'store mut Store, cf: CallFrame) -> Result { - let module = store.get_module_instance_raw(cf.module_addr()); - Ok(Self { module, store, cf }) + let module = store.get_module_instance_raw(cf.module_addr).clone(); + let func = store.state.get_wasm_func(cf.func_addr).clone(); + let instructions = func.instructions.clone(); + Ok(Self { module, store, cf, func, instructions }) } pub(crate) fn run_to_completion(&mut self) -> Result<()> { @@ -72,8 +77,17 @@ impl<'store> Executor<'store> { }; } + let next = match self.instructions.0.get(self.cf.instr_ptr) { + Some(instr) => instr, + None => unreachable!( + "Instruction pointer out of bounds: {} ({} instructions)", + self.cf.instr_ptr, + self.instructions.0.len() + ), + }; + #[rustfmt::skip] - match self.cf.fetch_instr() { + match next { Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} Unreachable => return ControlFlow::Break(Some(Trap::Unreachable.into())), Drop32 => self.store.stack.values.drop::(), @@ -342,7 +356,7 @@ impl<'store> Executor<'store> { V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, - V128Const(arg) => self.exec_const::(self.cf.data().v128_constants[*arg as usize].into()).to_cf()?, + V128Const(arg) => self.exec_const::(self.func.data.v128_constants[*arg as usize].into()).to_cf()?, I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), @@ -509,7 +523,7 @@ impl<'store> Executor<'store> { I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), - I8x16Shuffle(idx) => { let idx = self.cf.data().v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } + I8x16Shuffle(idx) => { let idx = self.func.data.v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), F32x4Ceil => stack_op!(simd_unary f32x4_ceil), @@ -564,32 +578,44 @@ impl<'store> Executor<'store> { fn exec_call( &mut self, wasm_func: Rc, + func_addr: FuncAddr, owner: ModuleInstanceAddr, ) -> ControlFlow> { + if self.func != wasm_func { + self.func = wasm_func.clone(); + self.instructions = wasm_func.instructions.clone(); + } + if IS_RETURN_CALL { let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - self.cf.reuse_for(wasm_func, locals, self.store.stack.blocks.len() as u32, owner); + self.cf.reuse_for(func_addr, locals, self.store.stack.blocks.len() as u32, owner); } else { let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.store.stack.blocks.len() as u32); + let new_call_frame = CallFrame::new(func_addr, owner, locals, self.store.stack.blocks.len() as u32); self.cf.incr_instr_ptr(); // skip the call instruction self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame)).to_cf()?; } - self.module.swap_with(self.cf.module_addr(), self.store); + if self.cf.module_addr != self.module.idx { + self.module = self.store.get_module_instance_raw(self.cf.module_addr).clone(); + } + ControlFlow::Continue(()) } fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::>(); - let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.id() }, ¶ms).to_cf()?; + let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.idx }, ¶ms).to_cf()?; self.store.stack.values.extend_from_wasmvalues(&res).to_cf()?; self.cf.incr_instr_ptr(); ControlFlow::Continue(()) } fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { - let func_inst = self.store.state.get_func(self.module.resolve_func_addr(v)); + let addr = self.module.resolve_func_addr(v); + let func_inst = self.store.state.get_func(addr); match &func_inst.func { - crate::Function::Wasm(wasm_func) => self.exec_call::(wasm_func.clone(), func_inst.owner), + crate::Function::Wasm(wasm_func) => { + self.exec_call::(wasm_func.clone(), addr, func_inst.owner) + } crate::Function::Host(host_func) => self.exec_call_host(host_func.clone()), } } @@ -620,7 +646,7 @@ impl<'store> Executor<'store> { )); } - self.exec_call::(wasm_func.clone(), func_inst.owner) + self.exec_call::(wasm_func.clone(), func_ref, func_inst.owner) } crate::Function::Host(host_func) => { if host_func.ty != *call_ty { @@ -661,7 +687,7 @@ impl<'store> Executor<'store> { } fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { self.store.stack.blocks.push(BlockFrame { - instr_ptr: self.cf.instr_ptr() as u32, + instr_ptr: self.cf.instr_ptr as u32, end_instr_offset, stack_ptr: self.store.stack.values.height(), results, @@ -687,18 +713,18 @@ impl<'store> Executor<'store> { ControlFlow::Continue(()) } fn exec_brtable(&mut self, default: u32, len: u32) -> ControlFlow> { - let start = self.cf.instr_ptr() + 1; + let start = self.cf.instr_ptr + 1; let end = start + len as usize; - if end > self.cf.instructions().len() { + if end > self.func.instructions.len() { return ControlFlow::Break(Some(Error::Other(format!( "br_table out of bounds: {} >= {}", end, - self.cf.instructions().len() + self.func.instructions.len() )))); } let idx = self.store.stack.values.pop::(); - let to = match self.cf.instructions()[start..end].get(idx as usize) { + let to = match self.func.instructions[start..end].get(idx as usize) { None => default, Some(Instruction::BrLabel(to)) => *to, _ => return ControlFlow::Break(Some(Error::Other("br_table out of bounds".to_string()))), @@ -712,17 +738,23 @@ impl<'store> Executor<'store> { ControlFlow::Continue(()) } fn exec_return(&mut self) -> ControlFlow> { - let old = self.cf.block_ptr(); - match self.store.stack.call_stack.pop() { - None => return ControlFlow::Break(None), - Some(cf) => self.cf = cf, + let old = self.cf.block_ptr; + let Some(cf) = self.store.stack.call_stack.pop() else { return ControlFlow::Break(None) }; + + if cf.func_addr != self.cf.func_addr { + self.func = self.store.state.get_wasm_func(cf.func_addr).clone(); + self.instructions = self.func.instructions.clone(); + + if cf.module_addr != self.module.idx { + self.module = self.store.get_module_instance_raw(cf.module_addr).clone(); + } } - if old > self.cf.block_ptr() { + if old > cf.block_ptr { self.store.stack.blocks.truncate(old); } - self.module.swap_with(self.cf.module_addr(), self.store); + self.cf = cf; ControlFlow::Continue(()) } fn exec_end_block(&mut self) { @@ -748,7 +780,6 @@ impl<'store> Executor<'store> { fn exec_memory_size(&mut self, addr: u32) -> Result<()> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(addr)); - match mem.is_64bit() { true => self.store.stack.values.push::(mem.page_count as i64), false => self.store.stack.values.push::(mem.page_count as i32), @@ -757,21 +788,15 @@ impl<'store> Executor<'store> { fn exec_memory_grow(&mut self, addr: u32) -> Result<()> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); let prev_size = mem.page_count; - let pages_delta = match mem.is_64bit() { true => self.store.stack.values.pop::(), false => i64::from(self.store.stack.values.pop::()), }; - match ( - mem.is_64bit(), - match mem.grow(pages_delta) { - Some(_) => prev_size as i64, - None => -1_i64, - }, - ) { - (true, size) => self.store.stack.values.push::(size)?, - (false, size) => self.store.stack.values.push::(size as i32)?, + let size = mem.grow(pages_delta).map(|_| prev_size as i64).unwrap_or(-1); + match mem.is_64bit() { + true => self.store.stack.values.push::(size)?, + false => self.store.stack.values.push::(size as i32)?, }; Ok(()) diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs index b6ce998a..0698f5de 100644 --- a/crates/tinywasm/src/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -9,18 +9,18 @@ pub(super) trait NoStdFloatExt { #[rustfmt::skip] impl NoStdFloatExt for f64 { - #[inline] fn round(self) -> Self { libm::round(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) } + fn round(self) -> Self { libm::round(self) } + fn ceil(self) -> Self { libm::ceil(self) } + fn floor(self) -> Self { libm::floor(self) } + fn trunc(self) -> Self { libm::trunc(self) } + fn sqrt(self) -> Self { libm::sqrt(self) } } #[rustfmt::skip] impl NoStdFloatExt for f32 { - #[inline] fn round(self) -> Self { libm::roundf(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) } + fn round(self) -> Self { libm::roundf(self) } + fn ceil(self) -> Self { libm::ceilf(self) } + fn floor(self) -> Self { libm::floorf(self) } + fn trunc(self) -> Self { libm::truncf(self) } + fn sqrt(self) -> Self { libm::sqrtf(self) } } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 35afca42..f2b617d8 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -70,7 +70,6 @@ macro_rules! impl_wasm_float_ops { ($($t:ty)*) => ($( impl TinywasmFloatExt for $t { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest - #[inline] fn tw_nearest(self) -> Self { match self { #[cfg(not(feature = "canonicalize_nans"))] @@ -95,7 +94,6 @@ 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 tw_minimum(self, other: Self) -> Self { match self.partial_cmp(&other) { Some(core::cmp::Ordering::Less) => self, @@ -110,7 +108,6 @@ macro_rules! impl_wasm_float_ops { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax // Based on f32::maximum (which is not yet stable) - #[inline] fn tw_maximum(self, other: Self) -> Self { match self.partial_cmp(&other) { Some(core::cmp::Ordering::Greater) => self, @@ -138,22 +135,18 @@ pub(crate) trait WasmIntOps { macro_rules! impl_wrapping_self_sh { ($($t:ty)*) => ($( impl WasmIntOps for $t { - #[inline] fn wasm_shl(self, rhs: Self) -> Self { self.wrapping_shl(rhs as u32) } - #[inline] fn wasm_shr(self, rhs: Self) -> Self { self.wrapping_shr(rhs as u32) } - #[inline] fn wasm_rotl(self, rhs: Self) -> Self { self.rotate_left(rhs as u32) } - #[inline] fn wasm_rotr(self, rhs: Self) -> Self { self.rotate_right(rhs as u32) } @@ -166,7 +159,6 @@ impl_wrapping_self_sh! { i32 i64 u32 u64 } macro_rules! impl_checked_wrapping_rem { ($($t:ty)*) => ($( impl TinywasmIntExt for $t { - #[inline] fn checked_wrapping_rem(self, rhs: Self) -> Result { if rhs == 0 { Err(Error::Trap(crate::Trap::DivisionByZero)) @@ -175,7 +167,6 @@ macro_rules! impl_checked_wrapping_rem { } } - #[inline] fn wasm_checked_div(self, rhs: Self) -> Result { if rhs == 0 { Err(Error::Trap(crate::Trap::DivisionByZero)) diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 688dde81..9c588b3d 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -3,8 +3,8 @@ use crate::interpreter::{Value128, values::*}; use crate::{Result, Trap, unlikely}; use alloc::boxed::Box; -use alloc::{rc::Rc, vec::Vec}; -use tinywasm_types::{ArcSlice, Instruction, LocalAddr, ModuleInstanceAddr, WasmFunction, WasmFunctionData, WasmValue}; +use alloc::vec::Vec; +use tinywasm_types::{FuncAddr, LocalAddr, ModuleInstanceAddr, ValueCounts, WasmValue}; #[derive(Debug)] pub(crate) struct CallStack { @@ -36,11 +36,11 @@ impl CallStack { #[derive(Debug)] pub(crate) struct CallFrame { - instr_ptr: usize, - func_instance: Rc, - block_ptr: u32, - module_addr: ModuleInstanceAddr, + pub(crate) instr_ptr: usize, + pub(crate) block_ptr: u32, pub(crate) locals: Locals, + pub(crate) module_addr: ModuleInstanceAddr, + pub(crate) func_addr: FuncAddr, } #[derive(Debug)] @@ -62,12 +62,46 @@ impl Locals { } impl CallFrame { - pub(crate) fn instr_ptr(&self) -> usize { - self.instr_ptr + pub(crate) fn new(func_addr: FuncAddr, module_addr: ModuleInstanceAddr, locals: Locals, block_ptr: u32) -> Self { + Self { instr_ptr: 0, func_addr, module_addr, block_ptr, locals } } - pub(crate) fn data(&self) -> &WasmFunctionData { - &self.func_instance.data + pub(crate) fn new_with_params( + local_count: ValueCounts, + func_addr: FuncAddr, + module_addr: ModuleInstanceAddr, + params: &[WasmValue], + block_ptr: u32, + ) -> Self { + let locals = { + let mut locals_32 = Vec::with_capacity(local_count.c32 as usize); + let mut locals_64 = Vec::with_capacity(local_count.c64 as usize); + let mut locals_128 = Vec::with_capacity(local_count.c128 as usize); + let mut locals_ref = Vec::with_capacity(local_count.cref as usize); + + for p in params { + match p.into() { + TinyWasmValue::Value32(v) => locals_32.push(v), + TinyWasmValue::Value64(v) => locals_64.push(v), + TinyWasmValue::Value128(v) => locals_128.push(v), + TinyWasmValue::ValueRef(v) => locals_ref.push(v), + } + } + + locals_32.resize_with(local_count.c32 as usize, Default::default); + locals_64.resize_with(local_count.c64 as usize, Default::default); + locals_128.resize_with(local_count.c128 as usize, Default::default); + locals_ref.resize_with(local_count.cref as usize, Default::default); + + Locals { + locals_32: locals_32.into_boxed_slice(), + locals_64: locals_64.into_boxed_slice(), + locals_128: locals_128.into_boxed_slice(), + locals_ref: locals_ref.into_boxed_slice(), + } + }; + + Self::new(func_addr, module_addr, locals, block_ptr) } pub(crate) fn incr_instr_ptr(&mut self) { @@ -78,30 +112,14 @@ impl CallFrame { self.instr_ptr += offset as usize; } - pub(crate) fn module_addr(&self) -> ModuleInstanceAddr { - self.module_addr - } - - #[inline(always)] - pub(crate) fn fetch_instr(&self) -> &Instruction { - match self.func_instance.instructions.get(self.instr_ptr) { - Some(instr) => instr, - None => unreachable!("Instruction pointer out of bounds, this is a bug"), - } - } - - pub(crate) fn block_ptr(&self) -> u32 { - self.block_ptr - } - pub(crate) fn reuse_for( &mut self, - func: Rc, + func_addr: FuncAddr, locals: Locals, block_depth: u32, module_addr: ModuleInstanceAddr, ) { - self.func_instance = func; + self.func_addr = func_addr; self.module_addr = module_addr; self.locals = locals; self.block_ptr = block_depth; @@ -151,55 +169,4 @@ impl CallFrame { Some(()) } - - pub(crate) fn new( - func_instance: Rc, - module_addr: ModuleInstanceAddr, - params: &[WasmValue], - block_ptr: u32, - ) -> Self { - let locals = { - let mut locals_32 = Vec::with_capacity(func_instance.locals.c32 as usize); - let mut locals_64 = Vec::with_capacity(func_instance.locals.c64 as usize); - let mut locals_128 = Vec::with_capacity(func_instance.locals.c128 as usize); - let mut locals_ref = Vec::with_capacity(func_instance.locals.cref as usize); - - for p in params { - match p.into() { - TinyWasmValue::Value32(v) => locals_32.push(v), - TinyWasmValue::Value64(v) => locals_64.push(v), - TinyWasmValue::Value128(v) => locals_128.push(v), - TinyWasmValue::ValueRef(v) => locals_ref.push(v), - } - } - - locals_32.resize_with(func_instance.locals.c32 as usize, Default::default); - locals_64.resize_with(func_instance.locals.c64 as usize, Default::default); - locals_128.resize_with(func_instance.locals.c128 as usize, Default::default); - locals_ref.resize_with(func_instance.locals.cref as usize, Default::default); - - Locals { - locals_32: locals_32.into_boxed_slice(), - locals_64: locals_64.into_boxed_slice(), - locals_128: locals_128.into_boxed_slice(), - locals_ref: locals_ref.into_boxed_slice(), - } - }; - - Self { instr_ptr: 0, func_instance, module_addr, block_ptr, locals } - } - - pub(crate) fn new_raw( - func_instance: Rc, - module_addr: ModuleInstanceAddr, - locals: Locals, - block_ptr: u32, - ) -> Self { - Self { instr_ptr: 0, func_instance, module_addr, block_ptr, locals } - } - - #[inline] - pub(crate) fn instructions(&self) -> &ArcSlice { - &self.func_instance.instructions - } } diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index e2fa4481..a16327ff 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -124,33 +124,28 @@ impl Value128 { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) } - #[inline] fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { let lanes = self.as_f32x4(); Self::from_f32x4([op(lanes[0]), op(lanes[1]), op(lanes[2]), op(lanes[3])]) } - #[inline] fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); Self::from_f32x4([op(a[0], b[0]), op(a[1], b[1]), op(a[2], b[2]), op(a[3], b[3])]) } - #[inline] fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { let lanes = self.as_f64x2(); Self::from_f64x2([op(lanes[0]), op(lanes[1])]) } - #[inline] fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); Self::from_f64x2([op(a[0], b[0]), op(a[1], b[1])]) } - #[inline] pub const fn reduce_or(self) -> u8 { let mut result = 0u8; let bytes = self.to_le_bytes(); @@ -2506,7 +2501,6 @@ impl core::ops::BitXor for Value128 { } } -#[inline] const fn canonicalize_simd_f32_nan(x: f32) -> f32 { #[cfg(feature = "canonicalize_nans")] if x.is_nan() { @@ -2518,7 +2512,6 @@ const fn canonicalize_simd_f32_nan(x: f32) -> f32 { x } -#[inline] const fn canonicalize_simd_f64_nan(x: f64) -> f64 { #[cfg(feature = "canonicalize_nans")] if x.is_nan() { @@ -2530,7 +2523,6 @@ const fn canonicalize_simd_f64_nan(x: f64) -> f64 { x } -#[inline] const fn saturate_i16_to_i8(x: i16) -> i8 { if x > i8::MAX as i16 { i8::MAX @@ -2541,7 +2533,6 @@ const fn saturate_i16_to_i8(x: i16) -> i8 { } } -#[inline] const fn saturate_i16_to_u8(x: i16) -> u8 { if x <= 0 { 0 @@ -2552,7 +2543,6 @@ const fn saturate_i16_to_u8(x: i16) -> u8 { } } -#[inline] const fn saturate_i32_to_i16(x: i32) -> i16 { if x > i16::MAX as i32 { i16::MAX @@ -2563,7 +2553,6 @@ const fn saturate_i32_to_i16(x: i32) -> i16 { } } -#[inline] const fn saturate_i32_to_u16(x: i32) -> u16 { if x <= 0 { 0 @@ -2574,7 +2563,6 @@ const fn saturate_i32_to_u16(x: i32) -> u16 { } } -#[inline] fn trunc_sat_f32_to_i32(v: f32) -> i32 { if v.is_nan() { 0 @@ -2587,7 +2575,6 @@ fn trunc_sat_f32_to_i32(v: f32) -> i32 { } } -#[inline] fn trunc_sat_f32_to_u32(v: f32) -> u32 { if v.is_nan() || v <= -1.0_f32 { 0 @@ -2598,7 +2585,6 @@ fn trunc_sat_f32_to_u32(v: f32) -> u32 { } } -#[inline] fn trunc_sat_f64_to_i32(v: f64) -> i32 { if v.is_nan() { 0 @@ -2611,7 +2597,6 @@ fn trunc_sat_f64_to_i32(v: f64) -> i32 { } } -#[inline] fn trunc_sat_f64_to_u32(v: f64) -> u32 { if v.is_nan() || v <= -1.0_f64 { 0 diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 447b1635..35357254 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -197,7 +197,6 @@ macro_rules! impl_internalvalue { fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2))?); Ok(()) } @@ -206,14 +205,12 @@ macro_rules! impl_internalvalue { let v3 = stack.$stack.pop(); let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2), $to_outer(v3))?); Ok(()) } fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { let v = stack.$stack.last_mut(); - *v = $to_internal(func($to_outer(*v))?); Ok(()) } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 022db0d1..c1df263d 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -28,18 +28,14 @@ impl MemoryInstance { } } - #[inline] pub(crate) fn is_64bit(&self) -> bool { matches!(self.kind.arch(), MemoryArch::I64) } - #[inline] pub(crate) fn len(&self) -> usize { self.data.len() } - #[inline(never)] - #[cold] fn trap_oob(&self, addr: usize, len: usize) -> Error { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }) } @@ -96,7 +92,7 @@ impl MemoryInstance { if end > self.data.len() { return Err(self.trap_oob(addr, len)); } - self.data[addr..end].fill_with(|| val); + self.data[addr..end].fill(val); Ok(()) } @@ -128,7 +124,6 @@ impl MemoryInstance { Ok(()) } - #[inline] pub(crate) fn grow(&mut self, pages_delta: i64) -> Option { let current_pages = self.page_count; let new_pages = current_pages as i64 + pages_delta; @@ -165,12 +160,10 @@ macro_rules! impl_mem_traits { ($($ty:ty, $size:expr),*) => { $( impl MemValue<$size> for $ty { - #[inline(always)] fn from_mem_bytes(bytes: [u8; $size]) -> Self { <$ty>::from_le_bytes(bytes.into()) } - #[inline(always)] fn to_mem_bytes(self) -> [u8; $size] { self.to_le_bytes().into() } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index a0a025c3..c1d20e6c 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,8 +1,10 @@ +use alloc::rc::Rc; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; use core::fmt::Debug; use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; +use crate::instance::ModuleInstanceInner; use crate::interpreter::TinyWasmValue; use crate::interpreter::stack::Stack; use crate::{Engine, Error, Function, ModuleInstance, Result, Trap}; @@ -30,7 +32,7 @@ static STORE_ID: AtomicUsize = AtomicUsize::new(0); /// See pub struct Store { id: usize, - module_instances: Vec, + module_instances: Vec>, pub(crate) engine: Engine, pub(crate) state: State, @@ -50,17 +52,18 @@ impl Debug for Store { impl Store { /// Create a new store - pub fn new() -> Self { - Self::default() + pub fn new(engine: Engine) -> Self { + let id = STORE_ID.fetch_add(1, Ordering::Relaxed); + Self { id, module_instances: Vec::new(), state: State::default(), stack: Stack::new(engine.config()), engine } } /// Get a module instance by the internal id - pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option<&ModuleInstance> { - self.module_instances.get(addr as usize) + pub fn get_module_instance(&self, addr: ModuleInstanceAddr) -> Option { + Some(ModuleInstance(self.module_instances.get(addr as usize)?.clone())) } - pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> ModuleInstance { - self.module_instances[addr as usize].clone() + pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> &Rc { + &self.module_instances[addr as usize] } } @@ -72,9 +75,7 @@ impl PartialEq for Store { impl Default for Store { fn default() -> Self { - let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - let engine = Engine::default(); - Self { id, module_instances: Vec::new(), state: State::default(), stack: Stack::new(engine.config()), engine } + Self::new(Engine::default()) } } @@ -101,6 +102,19 @@ impl State { } } + /// Get a wasm function at the actual index in the store, panicking if it's a host function (which should be guaranteed by the validator) + pub(crate) fn get_wasm_func(&self, addr: FuncAddr) -> &Rc { + match self.funcs.get(addr as usize) { + Some(func) => match &func.func { + Function::Wasm(wasm_func) => wasm_func, + Function::Host(_) => unreachable!( + "expected a wasm function at address {addr}, but found a host function. This should be unreachable" + ), + }, + None => unreachable!("function {addr} not found. This should be unreachable"), + } + } + /// Get the memory at the actual index in the store pub(crate) fn get_mem(&self, addr: MemAddr) -> &MemoryInstance { match self.memories.get(addr as usize) { @@ -110,7 +124,6 @@ impl State { } /// Get the memory at the actual index in the store - #[inline(always)] pub(crate) fn get_mem_mut(&mut self, addr: MemAddr) -> &mut MemoryInstance { match self.memories.get_mut(addr as usize) { Some(mem) => mem, @@ -209,8 +222,8 @@ impl Store { self.module_instances.len() as ModuleInstanceAddr } - pub(crate) fn add_instance(&mut self, instance: ModuleInstance) { - assert!(instance.id() == self.module_instances.len() as ModuleInstanceAddr); + pub(crate) fn add_instance(&mut self, instance: Rc) { + assert!(instance.idx == self.module_instances.len() as ModuleInstanceAddr); self.module_instances.push(instance); } diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index c46a2760..6352730d 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -57,16 +57,12 @@ impl ModuleRegistry { } } - fn get<'a>( - &self, - module_id: Option>, - store: &'a tinywasm::Store, - ) -> Option<&'a ModuleInstance> { + fn get(&self, module_id: Option>, store: &tinywasm::Store) -> Option { let addr = self.get_idx(module_id)?; store.get_module_instance(*addr) } - fn last<'a>(&self, store: &'a tinywasm::Store) -> Option<&'a ModuleInstance> { + fn last(&self, store: &tinywasm::Store) -> Option { store.get_module_instance(*self.last_module.as_ref()?) } } diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 59013adc..4699d699 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -40,7 +40,7 @@ pub fn exec_fn( return Err(tinywasm::Error::Other("no module found".to_string())); }; - let mut store = tinywasm::Store::new(); + let mut store = tinywasm::Store::default(); let module = tinywasm::Module::from(module); let instance = module.instantiate(&mut store, imports)?; instance.exported_func_untyped(&store, name)?.call(&mut store, args) diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 395c0560..969620d7 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -170,7 +170,6 @@ pub enum ExternVal { } impl ExternVal { - #[inline] pub fn kind(&self) -> ExternalKind { match self { Self::Func(_) => ExternalKind::Func, @@ -180,7 +179,6 @@ impl ExternVal { } } - #[inline] pub fn new(kind: ExternalKind, addr: Addr) -> Self { match kind { ExternalKind::Func => Self::Func(addr), @@ -418,7 +416,6 @@ pub enum ImportKind { } impl From<&ImportKind> for ExternalKind { - #[inline] fn from(kind: &ImportKind) -> Self { match kind { ImportKind::Function(_) => Self::Func, From 1b088144735db4e8c6ccb509d250295f8edf3196 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 30 Mar 2026 22:36:24 +0200 Subject: [PATCH 15/47] chore: remove block stack Signed-off-by: Henry --- .cargo/config.toml | 1 + .vscode/settings.json | 8 - crates/parser/src/visit.rs | 425 ++++++++++++++---- crates/tinywasm/Cargo.toml | 4 + crates/tinywasm/benches/argon2id.rs | 5 + crates/tinywasm/benches/tinywasm.rs | 6 +- crates/tinywasm/src/engine.rs | 7 - crates/tinywasm/src/error.rs | 5 - crates/tinywasm/src/func.rs | 2 +- crates/tinywasm/src/interpreter/executor.rs | 172 +++---- .../src/interpreter/stack/block_stack.rs | 69 --- .../src/interpreter/stack/call_stack.rs | 77 +--- crates/tinywasm/src/interpreter/stack/mod.rs | 8 +- .../src/interpreter/stack/value_stack.rs | 28 +- crates/tinywasm/src/interpreter/values.rs | 45 -- crates/tinywasm/src/store/memory.rs | 33 +- crates/tinywasm/tests/test-wasm-custom.rs | 19 + .../tests/wasm-custom/debug-if-then.wast | 12 + .../wasm-custom/dropkeep-small-zero-zero.wast | 38 ++ crates/types/src/instructions.rs | 44 +- crates/types/src/lib.rs | 3 +- 21 files changed, 559 insertions(+), 452 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 crates/tinywasm/src/interpreter/stack/block_stack.rs create mode 100644 crates/tinywasm/tests/test-wasm-custom.rs create mode 100644 crates/tinywasm/tests/wasm-custom/debug-if-then.wast create mode 100644 crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast diff --git a/.cargo/config.toml b/.cargo/config.toml index b27192b5..e7028026 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,4 @@ test-wasm-1="test --package tinywasm --test test-wasm-1 --release" test-wasm-2="test --package tinywasm --test test-wasm-2 --release" test-wasm-3="test --package tinywasm --test test-wasm-3 --release" test-wast="test --package tinywasm --test test-wast" +test-wasm-custom="test --package tinywasm --test test-wasm-custom --release" diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 48a2e0d4..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "search.exclude": { - "**/wasm-testsuite/data": true - }, - "rust-analyzer.linkedProjects": [ - "./Cargo.toml", - ], -} \ No newline at end of file diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index cb9a3d25..9a707afd 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -1,13 +1,37 @@ use crate::Result; -use crate::conversion::{convert_heaptype, convert_valtype}; +use crate::conversion::convert_heaptype; use alloc::string::ToString; +use alloc::vec; use alloc::vec::Vec; use tinywasm_types::{Instruction, MemoryArg, WasmFunctionData}; use wasmparser::{ - FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, VisitSimdOperator, WasmModuleResources, + FrameKind, FuncValidator, FuncValidatorAllocations, FunctionBody, VisitOperator, VisitSimdOperator, + WasmModuleResources, }; +#[derive(Debug, Clone, Copy)] +enum BlockKind { + Block, + Loop, + If, +} + +#[derive(Debug, Clone, Copy, Default)] +struct StackBase { + s32: u16, + s64: u16, + s128: u16, + sref: u16, +} + +struct LoweringCtx { + kind: BlockKind, + has_else: bool, + start_ip: usize, + branch_jumps: Vec, +} + struct ValidateThenVisit<'a, R: WasmModuleResources>(usize, &'a mut FunctionBuilder); macro_rules! validate_then_visit { @@ -112,7 +136,7 @@ pub(crate) struct FunctionBuilder { validator: FuncValidator, instructions: Vec, v128_constants: Vec, - label_ptrs: Vec, + ctx_stack: Vec, local_addr_map: Vec, errors: Vec, } @@ -124,23 +148,157 @@ impl FunctionBuilder { ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { self.validator.simd_visitor(offset) } -} -impl FunctionBuilder { pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { Self { validator, local_addr_map, instructions: Vec::with_capacity(instr_capacity), v128_constants: Vec::new(), - label_ptrs: Vec::with_capacity(256), + ctx_stack: Vec::with_capacity(256), errors: Vec::new(), } } + fn stack_base_at_frame(&self, depth: usize) -> StackBase { + let frame = match self.validator.get_control_frame(depth) { + Some(f) => f, + None => return StackBase::default(), + }; + let height = frame.height; + let current = self.validator.operand_stack_height() as usize; + + let mut base = StackBase::default(); + for i in 0..height { + let depth_from_top = current - 1 - i; + if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => base.s64 += 1, + wasmparser::ValType::V128 => base.s128 += 1, + wasmparser::ValType::Ref(_) => base.sref += 1, + } + } + } + + base + } + fn unsupported(&mut self, name: &str) { self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); } + + fn current_ip(&self) -> u32 { + self.instructions.len() as u32 + } + + fn is_unreachable(&self) -> bool { + self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) + } + + fn get_ctx_idx(&self, depth: u32) -> Option { + let len = self.ctx_stack.len(); + let idx = len.checked_sub(depth as usize + 1)?; + Some(idx) + } + + fn emit_dropkeep(&mut self, base: StackBase, c32: u16, c64: u16, c128: u16, cref: u16) { + let fits_u8 = base.s32 <= u8::MAX as u16 + && c32 <= u8::MAX as u16 + && base.s64 <= u8::MAX as u16 + && c64 <= u8::MAX as u16 + && base.s128 <= u8::MAX as u16 + && c128 <= u8::MAX as u16 + && base.sref <= u8::MAX as u16 + && cref <= u8::MAX as u16; + + if fits_u8 { + self.instructions.push(Instruction::DropKeepSmall { + base32: base.s32 as u8, + keep32: c32 as u8, + base64: base.s64 as u8, + keep64: c64 as u8, + base128: base.s128 as u8, + keep128: c128 as u8, + base_ref: base.sref as u8, + keep_ref: cref as u8, + }); + } else { + self.instructions.push(Instruction::DropKeep32(base.s32, c32)); + self.instructions.push(Instruction::DropKeep64(base.s64, c64)); + self.instructions.push(Instruction::DropKeep128(base.s128, c128)); + self.instructions.push(Instruction::DropKeepRef(base.sref, cref)); + } + } + + fn patch_jump(&mut self, jump_ip: usize, target: u32) { + if let Instruction::Jump(ip) = &mut self.instructions[jump_ip] { + *ip = target; + } + } + + fn patch_jump_if_zero(&mut self, jump_ip: usize, target: u32) { + if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { + *ip = target; + } + } + + fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { + let mut c32: u16 = 0; + let mut c64: u16 = 0; + let mut c128: u16 = 0; + let mut cref: u16 = 0; + + for ty in label_types { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => c64 += 1, + wasmparser::ValType::V128 => c128 += 1, + wasmparser::ValType::Ref(_) => cref += 1, + } + } + + (c32, c64, c128, cref) + } + + fn emit_dropkeep_to_label(&mut self, label_depth: u32) { + if self.is_unreachable() { + return; + } + + let frame = match self.validator.get_control_frame(label_depth as usize) { + Some(f) => f, + None => return, + }; + + let base = self.stack_base_at_frame(label_depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + + self.emit_dropkeep(base, c32, c64, c128, cref); + } + + fn label_types_for_frame(&self, frame: &wasmparser::Frame) -> Vec { + let ty = &frame.block_type; + match ty { + wasmparser::BlockType::Empty => Vec::new(), + wasmparser::BlockType::Type(ty) => match frame.kind { + FrameKind::Loop => Vec::new(), + _ => vec![*ty], + }, + wasmparser::BlockType::FuncType(idx) => { + let sub_type = self.validator.resources().sub_type_at(*idx); + let func_ty = match sub_type { + Some(st) => st.composite_type.unwrap_func(), + None => return Vec::new(), + }; + match frame.kind { + FrameKind::Loop => func_ty.params().to_vec(), + _ => func_ty.results().to_vec(), + } + } + } + } } macro_rules! impl_visit_operator { @@ -177,7 +335,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild define_operands! { // basic instructions - visit_br(Br, u32), visit_br_if(BrIf, u32), visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_return(Return), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_return(Return), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -328,110 +486,205 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } } - fn visit_block(&mut self, blockty: wasmparser::BlockType) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(match blockty { - wasmparser::BlockType::Empty => Instruction::Block(0), - wasmparser::BlockType::FuncType(idx) => Instruction::BlockWithFuncType(idx, 0), - wasmparser::BlockType::Type(ty) => Instruction::BlockWithType(convert_valtype(&ty), 0), + fn visit_block(&mut self, _blockty: wasmparser::BlockType) -> Self::Output { + let start_ip = self.current_ip() as usize; + self.ctx_stack.push(LoweringCtx { + kind: BlockKind::Block, + has_else: false, + start_ip, + branch_jumps: Vec::new(), }); } - fn visit_loop(&mut self, ty: wasmparser::BlockType) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(match ty { - wasmparser::BlockType::Empty => Instruction::Loop(0), - wasmparser::BlockType::FuncType(idx) => Instruction::LoopWithFuncType(idx, 0), - wasmparser::BlockType::Type(ty) => Instruction::LoopWithType(convert_valtype(&ty), 0), - }); + fn visit_loop(&mut self, _ty: wasmparser::BlockType) -> Self::Output { + let start_ip = self.current_ip() as usize; + self.ctx_stack.push(LoweringCtx { kind: BlockKind::Loop, has_else: false, start_ip, branch_jumps: Vec::new() }); } - fn visit_if(&mut self, ty: wasmparser::BlockType) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(match ty { - wasmparser::BlockType::Empty => Instruction::If(0, 0), - wasmparser::BlockType::FuncType(idx) => Instruction::IfWithFuncType(idx, 0, 0), - wasmparser::BlockType::Type(ty) => Instruction::IfWithType(convert_valtype(&ty), 0, 0), + fn visit_if(&mut self, _ty: wasmparser::BlockType) -> Self::Output { + let cond_jump_ip = self.current_ip() as usize; + self.instructions.push(Instruction::JumpIfZero(0)); + let start_ip = self.current_ip() as usize; + self.ctx_stack.push(LoweringCtx { + kind: BlockKind::If, + has_else: false, + start_ip, + branch_jumps: alloc::vec![cond_jump_ip], }); } fn visit_else(&mut self) -> Self::Output { - self.label_ptrs.push(self.instructions.len()); - self.instructions.push(Instruction::Else(0)); + let Some(cond_jump_ip) = self + .ctx_stack + .last() + .and_then(|ctx| if matches!(ctx.kind, BlockKind::If) { Some(ctx.branch_jumps[0]) } else { None }) + else { + return; + }; + + let jump_ip = self.current_ip() as usize; + self.instructions.push(Instruction::Jump(0)); + + let after_jump_ip = self.current_ip(); + let Some(ctx) = self.ctx_stack.last_mut() else { + return; + }; + ctx.has_else = true; + ctx.branch_jumps.push(jump_ip); + self.patch_jump_if_zero(cond_jump_ip, after_jump_ip); } fn visit_end(&mut self) -> Self::Output { - let Some(label_pointer) = self.label_ptrs.pop() else { - return self.instructions.push(Instruction::Return); - }; + if self.ctx_stack.is_empty() { + self.instructions.push(Instruction::Return); + return; + } - let current_instr_ptr = self.instructions.len(); - match self.instructions.get_mut(label_pointer) { - Some(Instruction::Else(else_instr_end_offset)) => { - *else_instr_end_offset = (current_instr_ptr - label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support if blocks that large"); + let ctx = self.ctx_stack.pop().unwrap(); + let end_ip = self.current_ip(); - // since we're ending an else block, we need to end the if block as well - let Some(if_label_pointer) = self.label_ptrs.pop() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but there was no if block to end".to_string(), - )); + match ctx.kind { + BlockKind::Block | BlockKind::Loop => { + let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip as u32 } else { end_ip }; + for &jump_ip in &ctx.branch_jumps { + self.patch_jump(jump_ip, target); + } + } + BlockKind::If => { + let cond_jump_ip = ctx.branch_jumps[0]; + if !ctx.has_else { + self.patch_jump_if_zero(cond_jump_ip, end_ip); + } + for &jump_ip in &ctx.branch_jumps[1..] { + self.patch_jump(jump_ip, end_ip); + } + } + } + } - return; - }; + fn visit_br(&mut self, depth: u32) -> Self::Output { + self.emit_dropkeep_to_label(depth); - let if_instruction = &mut self.instructions[if_label_pointer]; + if let Some(ctx_idx) = self.get_ctx_idx(depth) { + let jump_ip = self.current_ip() as usize; + self.instructions.push(Instruction::Jump(0)); + self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); + } else { + self.instructions.push(Instruction::Return); + } + } - let (Instruction::If(else_offset, end_offset) - | Instruction::IfWithFuncType(_, else_offset, end_offset) - | Instruction::IfWithType(_, else_offset, end_offset)) = if_instruction - else { - return self.errors.push(crate::ParseError::UnsupportedOperator( - "Expected to end an if block, but the last label was not an if".to_string(), - )); - }; + fn visit_br_if(&mut self, depth: u32) -> Self::Output { + let cond_jump_ip = self.current_ip() as usize; + self.instructions.push(Instruction::JumpIfZero(0)); - *else_offset = (label_pointer - if_label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); + self.emit_dropkeep_to_label(depth); - *end_offset = (current_instr_ptr - if_label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); - } - Some( - Instruction::Block(end_offset) - | Instruction::BlockWithType(_, end_offset) - | Instruction::BlockWithFuncType(_, end_offset) - | Instruction::Loop(end_offset) - | Instruction::LoopWithFuncType(_, end_offset) - | Instruction::LoopWithType(_, end_offset) - | Instruction::If(_, end_offset) - | Instruction::IfWithFuncType(_, _, end_offset) - | Instruction::IfWithType(_, _, end_offset), - ) => { - *end_offset = (current_instr_ptr - label_pointer) - .try_into() - .expect("else_instr_end_offset is too large, tinywasm does not support blocks that large"); - } - _ => { - unreachable!("Expected to end a block, but the last label was not a block") - } - }; + if let Some(ctx_idx) = self.get_ctx_idx(depth) { + let jump_ip = self.current_ip() as usize; + self.instructions.push(Instruction::Jump(0)); + self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); + } else { + self.instructions.push(Instruction::Return); + } - self.instructions.push(Instruction::EndBlockFrame); + self.patch_jump_if_zero(cond_jump_ip, self.current_ip()); } fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { - let def = targets.default(); - let instrs = targets + let ts = targets .targets() - .map(|t| t.map(Instruction::BrLabel)) - .collect::, wasmparser::BinaryReaderError>>() - .expect("visit_br_table: BrTable targets are invalid, this should have been caught by the validator"); + .collect::, wasmparser::BinaryReaderError>>() + .expect("visit_br_table: BrTable targets are invalid"); + + let default_depth = targets.default(); + let len = ts.len() as u32; + let target_depths: Vec = ts; + + let header_ip = self.current_ip() as usize; + self.instructions.push(Instruction::BranchTable(0, len)); - self.instructions.extend(([Instruction::BrTable(def, instrs.len() as u32)].into_iter()).chain(instrs)); + let target_table_ip = self.current_ip() as usize; + for _ in 0..len { + self.instructions.push(Instruction::BranchTableTarget(0)); + } + let default_target_ip = self.current_ip() as usize; + self.instructions.push(Instruction::BranchTableTarget(0)); + + let mut seen = alloc::collections::BTreeMap::::new(); + struct PadInfo { + depth: u32, + pad_start: u32, + jump_or_ret_ip: usize, + is_return: bool, + } + let mut pads: Vec = Vec::new(); + + for &depth in target_depths.iter().chain(core::iter::once(&default_depth)) { + if seen.contains_key(&depth) { + continue; + } + seen.insert(depth, pads.len()); + + let pad_start = self.current_ip(); + + let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; + let Some(frame) = frame else { + let ip = self.current_ip() as usize; + self.instructions.push(Instruction::Return); + pads.push(PadInfo { depth, pad_start, jump_or_ret_ip: ip, is_return: true }); + continue; + }; + + let base = self.stack_base_at_frame(depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + + self.emit_dropkeep(base, c32, c64, c128, cref); + + let jump_ip = self.current_ip() as usize; + self.instructions.push(Instruction::Jump(0)); + pads.push(PadInfo { depth, pad_start, jump_or_ret_ip: jump_ip, is_return: false }); + } + + for (i, &depth) in target_depths.iter().enumerate() { + let pad_idx = seen[&depth]; + if let Instruction::BranchTableTarget(ip) = &mut self.instructions[target_table_ip + i] { + *ip = pads[pad_idx].pad_start; + } + } + + let default_pad_idx = seen[&default_depth]; + if let Instruction::BranchTableTarget(ip) = &mut self.instructions[default_target_ip] { + *ip = pads[default_pad_idx].pad_start; + } + if let Instruction::BranchTable(default_ip, _) = &mut self.instructions[header_ip] { + *default_ip = pads[default_pad_idx].pad_start; + } + + for pad in &pads { + if pad.is_return { + continue; + } + let Some(frame) = self.validator.get_control_frame(pad.depth as usize) else { + self.instructions[pad.jump_or_ret_ip] = Instruction::Return; + continue; + }; + let Some(ctx_idx) = self.get_ctx_idx(pad.depth) else { + self.instructions[pad.jump_or_ret_ip] = Instruction::Return; + continue; + }; + match frame.kind { + FrameKind::Loop => { + if let Instruction::Jump(target) = &mut self.instructions[pad.jump_or_ret_ip] { + *target = self.ctx_stack[ctx_idx].start_ip as u32; + } + } + _ => { + self.ctx_stack[ctx_idx].branch_jumps.push(pad.jump_or_ret_ip); + } + } + } } fn visit_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index d1a08397..18f03659 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -72,6 +72,10 @@ harness=false name="test-wasm-custom-page-sizes" harness=false +[[test]] +name="test-wasm-custom" +harness=false + [[test]] name="test-wasm-tail-call" harness=false diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index aa8b38b6..74ce1ad3 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -5,6 +5,10 @@ use types::TinyWasmModule; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.opt.wasm"); +fn init_log() { + let _ = pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::Off).try_init(); +} + fn argon2id_parse() -> Result { let parser = tinywasm_parser::Parser::new(); let data = parser.parse_module_bytes(WASM)?; @@ -30,6 +34,7 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> { } fn criterion_benchmark(c: &mut Criterion) { + init_log(); let module = argon2id_parse().expect("argon2id_parse"); let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 4848a4a7..7ad20823 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -35,9 +35,9 @@ fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); - c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); - c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + // c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); + // c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); + // c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); c.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index fe305138..f03f749a 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -51,9 +51,6 @@ pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots /// Default initial size for the reference value stack (funcref, externref values). pub const DEFAULT_VALUE_STACK_REF_SIZE: usize = 4 * 1024; // 4k slots -/// Default initial size for the block stack (control frames). -pub const DEFAULT_BLOCK_STACK_SIZE: usize = 2048; // 1024 frames - /// Default initial size for the call stack (function frames). pub const DEFAULT_CALL_STACK_SIZE: usize = 2048; // 1024 frames @@ -71,9 +68,6 @@ pub struct Config { pub stack_ref_size: usize, /// Initial size of the call stack. pub call_stack_size: usize, - - /// Initial size of the block stack. - pub block_stack_initial_size: usize, } impl Config { @@ -91,7 +85,6 @@ impl Default for Config { stack_128_size: DEFAULT_VALUE_STACK_128_SIZE, stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE, call_stack_size: DEFAULT_CALL_STACK_SIZE, - block_stack_initial_size: DEFAULT_BLOCK_STACK_SIZE, } } } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 51b5c467..106f9d1f 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -121,9 +121,6 @@ pub enum Trap { /// Call stack overflow CallStackOverflow, - /// Block stack overflow - BlockStackOverflow, - /// Value stack overflow ValueStackOverflow, @@ -159,7 +156,6 @@ impl Trap { Self::InvalidConversionToInt => "invalid conversion to integer", Self::IntegerOverflow => "integer overflow", Self::CallStackOverflow => "call stack exhausted", - Self::BlockStackOverflow => "block stack exhausted", Self::ValueStackOverflow => "value stack exhausted", Self::UndefinedElement { .. } => "undefined element", Self::UninitializedElement { .. } => "uninitialized element", @@ -243,7 +239,6 @@ impl Display for Trap { Self::InvalidConversionToInt => write!(f, "invalid conversion to integer"), Self::IntegerOverflow => write!(f, "integer overflow"), Self::CallStackOverflow => write!(f, "call stack exhausted"), - Self::BlockStackOverflow => write!(f, "block stack exhausted"), Self::ValueStackOverflow => write!(f, "value stack exhausted"), Self::UndefinedElement { index } => write!(f, "undefined element: index={index}"), Self::UninitializedElement { index } => { diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 6cf0c8ce..a741e0f9 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -56,7 +56,7 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let callframe = CallFrame::new_with_params(wasm_func.locals, self.addr, func_inst.owner, params, 0); + let callframe = CallFrame::new_with_params(wasm_func.locals, self.addr, func_inst.owner, params); // 7. Push the frame f to the call stack // & 8. Push the values to the stack diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 5dfad851..9d98cce9 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -10,7 +10,6 @@ use interpreter::stack::CallFrame; use tinywasm_types::*; use super::num_helpers::*; -use super::stack::{BlockFrame, BlockType}; use super::values::*; use crate::instance::ModuleInstanceInner; use crate::interpreter::Value128; @@ -88,7 +87,7 @@ impl<'store> Executor<'store> { #[rustfmt::skip] match next { - Nop | BrLabel(_) | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} + Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} Unreachable => return ControlFlow::Break(Some(Trap::Unreachable.into())), Drop32 => self.store.stack.values.drop::(), Drop64 => self.store.stack.values.drop::(), @@ -102,21 +101,70 @@ impl<'store> Executor<'store> { CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), ReturnCall(v) => return self.exec_call_direct::(*v), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())), - IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())), - IfWithFuncType(ty, end, el) => self.exec_if(*end, *el, self.resolve_functype(*ty)), - Else(end_offset) => self.exec_else(*end_offset), - Loop(end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), StackHeight::default())), - LoopWithType(ty, end) => self.enter_block(*end, BlockType::Loop, (StackHeight::default(), (*ty).into())), - LoopWithFuncType(ty, end) => self.enter_block(*end, BlockType::Loop, self.resolve_functype(*ty)), - Block(end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), StackHeight::default())), - BlockWithType(ty, end) => self.enter_block(*end, BlockType::Block, (StackHeight::default(), (*ty).into())), - BlockWithFuncType(ty, end) => self.enter_block(*end, BlockType::Block, self.resolve_functype(*ty)), - Br(v) => return self.exec_br(*v), - BrIf(v) => return self.exec_br_if(*v), - BrTable(default, len) => return self.exec_brtable(*default, *len), + Jump(ip) => { + self.cf.instr_ptr = *ip as usize; + return ControlFlow::Continue(()); + } + JumpIfZero(ip) => { + let cond = self.store.stack.values.pop::(); + + if cond == 0 { + self.cf.instr_ptr = *ip as usize; + } else { + self.cf.incr_instr_ptr(); + } + return ControlFlow::Continue(()); + } + DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { + let b32 = self.cf.stack_base.s32 as usize + *base32 as usize; + let k32 = *keep32 as usize; + self.store.stack.values.stack_32.truncate_keep(b32, k32); + let b64 = self.cf.stack_base.s64 as usize + *base64 as usize; + let k64 = *keep64 as usize; + self.store.stack.values.stack_64.truncate_keep(b64, k64); + let b128 = self.cf.stack_base.s128 as usize + *base128 as usize; + let k128 = *keep128 as usize; + self.store.stack.values.stack_128.truncate_keep(b128, k128); + let bref = self.cf.stack_base.sref as usize + *base_ref as usize; + let kref = *keep_ref as usize; + self.store.stack.values.stack_ref.truncate_keep(bref, kref); + } + DropKeep32(base, keep) => { + let b = self.cf.stack_base.s32 as usize + *base as usize; + let k = *keep as usize; + self.store.stack.values.stack_32.truncate_keep(b, k); + } + DropKeep64(base, keep) => { + let b = self.cf.stack_base.s64 as usize + *base as usize; + let k = *keep as usize; + self.store.stack.values.stack_64.truncate_keep(b, k); + } + DropKeep128(base, keep) => { + let b = self.cf.stack_base.s128 as usize + *base as usize; + let k = *keep as usize; + self.store.stack.values.stack_128.truncate_keep(b, k); + } + DropKeepRef(base, keep) => { + let b = self.cf.stack_base.sref as usize + *base as usize; + let k = *keep as usize; + self.store.stack.values.stack_ref.truncate_keep(b, k); + } + BranchTable(default_ip, len) => { + let idx = self.store.stack.values.pop::(); + let start = self.cf.instr_ptr + 1; + + let target_ip = if idx >= 0 && (idx as u32) < *len { + match self.instructions.0.get(start + idx as usize) { + Some(Instruction::BranchTableTarget(ip)) => *ip, + _ => *default_ip, + } + } else { + *default_ip + }; + self.cf.instr_ptr = target_ip as usize; + return ControlFlow::Continue(()); + } Return => return self.exec_return(), - EndBlockFrame => self.exec_end_block(), LocalGet32(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, LocalGet64(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, LocalGet128(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, @@ -588,10 +636,12 @@ impl<'store> Executor<'store> { if IS_RETURN_CALL { let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - self.cf.reuse_for(func_addr, locals, self.store.stack.blocks.len() as u32, owner); + let stack_base = self.store.stack.values.height(); + self.cf.reuse_for(func_addr, locals, owner, stack_base); } else { let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - let new_call_frame = CallFrame::new(func_addr, owner, locals, self.store.stack.blocks.len() as u32); + let stack_base = self.store.stack.values.height(); + let new_call_frame = CallFrame::new(func_addr, owner, locals, stack_base); self.cf.incr_instr_ptr(); // skip the call instruction self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame)).to_cf()?; } @@ -661,84 +711,7 @@ impl<'store> Executor<'store> { } } - fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) { - // truthy value is on the top of the stack, so enter the then block - if self.store.stack.values.pop::() != 0 { - self.enter_block(end_offset, BlockType::If, (params, results)); - return; - } - - // falsy value is on the top of the stack - if else_offset == 0 { - self.cf.jump(end_offset); - return; - } - - self.cf.jump(else_offset); - self.enter_block(end_offset - else_offset, BlockType::Else, (params, results)); - } - fn exec_else(&mut self, end_offset: u32) { - self.exec_end_block(); - self.cf.jump(end_offset); - } - fn resolve_functype(&self, idx: u32) -> (StackHeight, StackHeight) { - let ty = self.module.func_ty(idx); - ((&*ty.params).into(), (&*ty.results).into()) - } - fn enter_block(&mut self, end_instr_offset: u32, ty: BlockType, (params, results): (StackHeight, StackHeight)) { - self.store.stack.blocks.push(BlockFrame { - instr_ptr: self.cf.instr_ptr as u32, - end_instr_offset, - stack_ptr: self.store.stack.values.height(), - results, - params, - ty, - }) - } - fn exec_br(&mut self, to: u32) -> ControlFlow> { - if self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() { - return self.exec_return(); - } - - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) - } - fn exec_br_if(&mut self, to: u32) -> ControlFlow> { - if self.store.stack.values.pop::() != 0 - && self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() - { - return self.exec_return(); - } - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) - } - fn exec_brtable(&mut self, default: u32, len: u32) -> ControlFlow> { - let start = self.cf.instr_ptr + 1; - let end = start + len as usize; - if end > self.func.instructions.len() { - return ControlFlow::Break(Some(Error::Other(format!( - "br_table out of bounds: {} >= {}", - end, - self.func.instructions.len() - )))); - } - - let idx = self.store.stack.values.pop::(); - let to = match self.func.instructions[start..end].get(idx as usize) { - None => default, - Some(Instruction::BrLabel(to)) => *to, - _ => return ControlFlow::Break(Some(Error::Other("br_table out of bounds".to_string()))), - }; - - if self.cf.break_to(to, &mut self.store.stack.values, &mut self.store.stack.blocks).is_none() { - return self.exec_return(); - } - - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) - } fn exec_return(&mut self) -> ControlFlow> { - let old = self.cf.block_ptr; let Some(cf) = self.store.stack.call_stack.pop() else { return ControlFlow::Break(None) }; if cf.func_addr != self.cf.func_addr { @@ -750,18 +723,9 @@ impl<'store> Executor<'store> { } } - if old > cf.block_ptr { - self.store.stack.blocks.truncate(old); - } - self.cf = cf; ControlFlow::Continue(()) } - fn exec_end_block(&mut self) { - let block = self.store.stack.blocks.pop(); - self.store.stack.values.truncate_keep(block.stack_ptr, block.results); - } - fn exec_global_get(&mut self, global_index: u32) -> Result<()> { self.store.stack.values.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) } diff --git a/crates/tinywasm/src/interpreter/stack/block_stack.rs b/crates/tinywasm/src/interpreter/stack/block_stack.rs deleted file mode 100644 index f653460f..00000000 --- a/crates/tinywasm/src/interpreter/stack/block_stack.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::engine::Config; -use alloc::vec::Vec; - -use crate::interpreter::values::{StackHeight, StackLocation}; - -#[derive(Debug)] -pub(crate) struct BlockStack(Vec); - -impl BlockStack { - pub(crate) fn new(config: &Config) -> Self { - Self(Vec::with_capacity(config.block_stack_initial_size)) - } - - pub(crate) fn clear(&mut self) { - self.0.clear(); - } - - pub(crate) fn len(&self) -> usize { - self.0.len() - } - - pub(crate) fn push(&mut self, block: BlockFrame) { - self.0.push(block); - } - - /// get the label at the given index, where 0 is the top of the stack - pub(crate) fn get_relative_to(&self, index: u32, offset: u32) -> Option<&BlockFrame> { - let len = (self.0.len() as u32).checked_sub(offset)?; - - // the vast majority of wasm functions don't use break to return, but it is allowed in the spec - if index >= len { - return None; - } - - Some(&self.0[self.0.len() - index as usize - 1]) - } - - pub(crate) fn pop(&mut self) -> BlockFrame { - match self.0.pop() { - Some(frame) => frame, - None => unreachable!("Block stack underflow, this is a bug"), - } - } - - /// keep the top `len` blocks and discard the rest - pub(crate) fn truncate(&mut self, len: u32) { - self.0.truncate(len as usize); - } -} - -#[derive(Debug)] -pub(crate) struct BlockFrame { - pub(crate) instr_ptr: u32, // position of the instruction pointer when the block was entered - pub(crate) end_instr_offset: u32, // position of the end instruction of the block - - pub(crate) stack_ptr: StackLocation, // stack pointer when the block was entered - pub(crate) results: StackHeight, - pub(crate) params: StackHeight, - - pub(crate) ty: BlockType, -} - -#[derive(Debug)] -pub(crate) enum BlockType { - Loop, - If, - Else, - Block, -} diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 9c588b3d..4750b03d 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,4 +1,3 @@ -use super::BlockType; use crate::interpreter::{Value128, values::*}; use crate::{Result, Trap, unlikely}; @@ -37,10 +36,18 @@ impl CallStack { #[derive(Debug)] pub(crate) struct CallFrame { pub(crate) instr_ptr: usize, - pub(crate) block_ptr: u32, pub(crate) locals: Locals, pub(crate) module_addr: ModuleInstanceAddr, pub(crate) func_addr: FuncAddr, + pub(crate) stack_base: StackBase, +} + +#[derive(Debug, Clone, Copy, Default)] +pub(crate) struct StackBase { + pub(crate) s32: u32, + pub(crate) s64: u32, + pub(crate) s128: u32, + pub(crate) sref: u32, } #[derive(Debug)] @@ -62,8 +69,13 @@ impl Locals { } impl CallFrame { - pub(crate) fn new(func_addr: FuncAddr, module_addr: ModuleInstanceAddr, locals: Locals, block_ptr: u32) -> Self { - Self { instr_ptr: 0, func_addr, module_addr, block_ptr, locals } + pub(crate) fn new( + func_addr: FuncAddr, + module_addr: ModuleInstanceAddr, + locals: Locals, + stack_base: StackBase, + ) -> Self { + Self { instr_ptr: 0, func_addr, module_addr, locals, stack_base } } pub(crate) fn new_with_params( @@ -71,7 +83,6 @@ impl CallFrame { func_addr: FuncAddr, module_addr: ModuleInstanceAddr, params: &[WasmValue], - block_ptr: u32, ) -> Self { let locals = { let mut locals_32 = Vec::with_capacity(local_count.c32 as usize); @@ -101,72 +112,24 @@ impl CallFrame { } }; - Self::new(func_addr, module_addr, locals, block_ptr) + Self::new(func_addr, module_addr, locals, StackBase::default()) } pub(crate) fn incr_instr_ptr(&mut self) { self.instr_ptr += 1; } - pub(crate) fn jump(&mut self, offset: u32) { - self.instr_ptr += offset as usize; - } - pub(crate) fn reuse_for( &mut self, func_addr: FuncAddr, locals: Locals, - block_depth: u32, module_addr: ModuleInstanceAddr, + stack_base: StackBase, ) { self.func_addr = func_addr; self.module_addr = module_addr; self.locals = locals; - self.block_ptr = block_depth; - self.instr_ptr = 0; // Reset to function entry - } - - /// Break to a block at the given index (relative to the current frame) - /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) - pub(crate) fn break_to( - &mut self, - break_to_relative: u32, - values: &mut super::ValueStack, - blocks: &mut super::BlockStack, - ) -> Option<()> { - let break_to = blocks.get_relative_to(break_to_relative, self.block_ptr)?; - - // instr_ptr points to the label instruction, but the next step - // will increment it by 1 since we're changing the "current" instr_ptr - match break_to.ty { - BlockType::Loop => { - // this is a loop, so we want to jump back to the start of the loop - self.instr_ptr = break_to.instr_ptr as usize; - - // We also want to push the params to the stack - values.truncate_keep(break_to.stack_ptr, break_to.params); - - // check if we're breaking to the loop - if break_to_relative != 0 { - // we also want to trim the label stack to the loop (but not including the loop) - blocks.truncate(blocks.len() as u32 - break_to_relative); - return Some(()); - } - } - - BlockType::Block | BlockType::If | BlockType::Else => { - // this is a block, so we want to jump to the next instruction after the block ends - // We also want to push the block's results to the stack - values.truncate_keep(break_to.stack_ptr, break_to.results); - - // (the inst_ptr will be incremented by 1 before the next instruction is executed) - self.instr_ptr = (break_to.instr_ptr + break_to.end_instr_offset) as usize; - - // we also want to trim the label stack, including the block - blocks.truncate(blocks.len() as u32 - (break_to_relative + 1)); - } - } - - Some(()) + self.stack_base = stack_base; + self.instr_ptr = 0; } } diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index 68b9164c..5912de19 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -1,9 +1,7 @@ -mod block_stack; mod call_stack; mod value_stack; -pub(crate) use block_stack::{BlockFrame, BlockStack, BlockType}; -pub(crate) use call_stack::{CallFrame, CallStack, Locals}; +pub(crate) use call_stack::{CallFrame, CallStack, Locals, StackBase}; pub(crate) use value_stack::ValueStack; use crate::engine::Config; @@ -12,18 +10,16 @@ use crate::engine::Config; #[derive(Debug)] pub(crate) struct Stack { pub(crate) values: ValueStack, - pub(crate) blocks: BlockStack, pub(crate) call_stack: CallStack, } impl Stack { pub(crate) fn new(config: &Config) -> Self { - Self { values: ValueStack::new(config), blocks: BlockStack::new(config), call_stack: CallStack::new(config) } + Self { values: ValueStack::new(config), call_stack: CallStack::new(config) } } pub(crate) fn clear(&mut self) { self.values.clear(); - self.blocks.clear(); self.call_stack.clear(); } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index b579a04f..9ce73d93 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -4,7 +4,7 @@ use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, use crate::{Result, Trap, engine::Config, interpreter::*}; -use super::Locals; +use super::{Locals, StackBase}; #[derive(Debug)] pub(crate) struct ValueStack { @@ -74,6 +74,7 @@ impl Stack { } let keep_tail = end_keep.min(self.len - n); + if keep_tail == 0 { self.len = n; return; @@ -113,19 +114,19 @@ impl ValueStack { self.stack_ref.clear(); } - pub(crate) fn height(&self) -> StackLocation { - StackLocation { - s32: self.stack_32.len() as u32, - s64: self.stack_64.len() as u32, - s128: self.stack_128.len() as u32, - sref: self.stack_ref.len() as u32, - } - } - pub(crate) fn len(&self) -> usize { self.stack_32.len() + self.stack_64.len() + self.stack_128.len() + self.stack_ref.len() } + pub(crate) fn height(&self) -> StackBase { + StackBase { + s32: u32::try_from(self.stack_32.len()).expect("stack32 height overflow"), + s64: u32::try_from(self.stack_64.len()).expect("stack64 height overflow"), + s128: u32::try_from(self.stack_128.len()).expect("stack128 height overflow"), + sref: u32::try_from(self.stack_ref.len()).expect("stack_ref height overflow"), + } + } + pub(crate) fn peek(&self) -> T { T::stack_peek(self) } @@ -209,13 +210,6 @@ impl ValueStack { } } - pub(crate) fn truncate_keep(&mut self, to: StackLocation, keep: StackHeight) { - self.stack_32.truncate_keep(to.s32 as usize, usize::from(keep.s32)); - self.stack_64.truncate_keep(to.s64 as usize, usize::from(keep.s64)); - self.stack_128.truncate_keep(to.s128 as usize, usize::from(keep.s128)); - self.stack_ref.truncate_keep(to.sref as usize, usize::from(keep.sref)); - } - pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<()> { match value { TinyWasmValue::Value32(v) => self.stack_32.push(v)?, diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 35357254..41db3832 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -20,51 +20,6 @@ pub enum TinyWasmValue { ValueRef(ValueRef), } -#[derive(Debug, Clone, Copy)] -pub(crate) struct StackLocation { - pub(crate) s32: u32, - pub(crate) s64: u32, - pub(crate) s128: u32, - pub(crate) sref: u32, -} - -#[derive(Debug, Clone, Copy, Default)] -pub(crate) struct StackHeight { - pub(crate) s32: u16, - pub(crate) s64: u16, - pub(crate) s128: u16, - pub(crate) sref: u16, -} - -impl From for StackHeight { - fn from(value: ValType) -> Self { - match value { - ValType::I32 | ValType::F32 => Self { s32: 1, ..Default::default() }, - ValType::I64 | ValType::F64 => Self { s64: 1, ..Default::default() }, - ValType::V128 => Self { s128: 1, ..Default::default() }, - ValType::RefExtern | ValType::RefFunc => Self { sref: 1, ..Default::default() }, - } - } -} - -impl From<&[ValType]> for StackHeight { - fn from(value: &[ValType]) -> Self { - let mut s32 = 0; - let mut s64 = 0; - let mut s128 = 0; - let mut sref = 0; - for val_type in value { - match val_type { - ValType::I32 | ValType::F32 => s32 += 1, - ValType::I64 | ValType::F64 => s64 += 1, - ValType::V128 => s128 += 1, - ValType::RefExtern | ValType::RefFunc => sref += 1, - } - } - Self { s32, s64, s128, sref } - } -} - impl TinyWasmValue { /// Asserts that the value is a 32-bit value and returns it (panics if the value is the wrong size) pub fn unwrap_32(&self) -> Value32 { diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index c1df263d..46226b9c 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -125,25 +125,29 @@ impl MemoryInstance { } pub(crate) fn grow(&mut self, pages_delta: i64) -> Option { + if pages_delta < 0 { + log::debug!("memory.grow failed: negative delta {}", pages_delta); + return None; + } + let current_pages = self.page_count; - let new_pages = current_pages as i64 + pages_delta; + let pages_delta = usize::try_from(pages_delta).ok()?; + let new_pages = current_pages.checked_add(pages_delta)?; - if new_pages < 0 || new_pages as usize > self.max_pages() { + if new_pages > self.max_pages() { log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, self.max_pages()); - log::debug!("{} {}", self.kind.page_count_max(), self.kind.page_size()); return None; } - let new_size = (new_pages as u64 * self.kind.page_size()) as usize; - if new_size as u64 > self.kind.max_size() { + let new_size = (new_pages as u64).checked_mul(self.kind.page_size())?; + if new_size > self.kind.max_size() { + log::debug!("memory.grow failed: new_size={}, max_size={}", new_size, self.kind.max_size()); return None; } - // Zero initialize the new pages - self.data.reserve_exact(new_size); - self.data.resize_with(new_size, Default::default); - self.page_count = new_pages as usize; - Some(current_pages as i64) + self.data.resize(usize::try_from(new_size).ok()?, 0); + self.page_count = new_pages; + i64::try_from(current_pages).ok() } } @@ -249,6 +253,15 @@ mod memory_instance_tests { assert_eq!(memory.grow(1), None); } + #[test] + fn test_memory_grow_negative_delta() { + let mut memory = create_test_memory(); + let original_pages = memory.page_count; + + assert_eq!(memory.grow(-1), None); + assert_eq!(memory.page_count, original_pages); + } + #[test] fn test_memory_custom_page_size_out_of_bounds() { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); diff --git a/crates/tinywasm/tests/test-wasm-custom.rs b/crates/tinywasm/tests/test-wasm-custom.rs new file mode 100644 index 00000000..ae081420 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-custom.rs @@ -0,0 +1,19 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let custom_dir = std::path::Path::new("./tests/wasm-custom"); + let mut files: Vec = std::fs::read_dir(custom_dir)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|p| p.extension().is_some_and(|ext| ext == "wast")) + .collect(); + files.sort(); + + let mut test_suite = TestSuite::new(); + test_suite.run_paths(&files)?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/wasm-custom/debug-if-then.wast b/crates/tinywasm/tests/wasm-custom/debug-if-then.wast new file mode 100644 index 00000000..6faf3392 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/debug-if-then.wast @@ -0,0 +1,12 @@ +(module + (func (export "as-if-then") (param i32 i32) (result i32) + (block (result i32) + (if (result i32) (local.get 0) + (then (br 1 (i32.const 3))) + (else (local.get 1)) + ) + ) + ) +) +(assert_return (invoke "as-if-then" (i32.const 0) (i32.const 6)) (i32.const 6)) +(assert_return (invoke "as-if-then" (i32.const 1) (i32.const 6)) (i32.const 3)) diff --git a/crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast b/crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast new file mode 100644 index 00000000..01a4f6be --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/dropkeep-small-zero-zero.wast @@ -0,0 +1,38 @@ +(module + (func $leak (result i32) + (block + (i64.const 7) + (i32.const 1) + (br_if 0) + (unreachable) + ) + (i32.const 0) + ) + + (func (export "run") (param i32) (result i32) + (local i32) + (local.get 0) + (local.set 1) + + (block + (loop + (local.get 1) + (i32.eqz) + (br_if 1) + + (call $leak) + (drop) + + (local.get 1) + (i32.const 1) + (i32.sub) + (local.set 1) + (br 0) + ) + ) + + (i32.const 0) + ) +) + +(assert_return (invoke "run" (i32.const 40000)) (i32.const 0)) diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index ee99601f..5c1627a8 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,4 +1,4 @@ -use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; +use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType}; use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. @@ -23,11 +23,6 @@ impl MemoryArg { } } -type BrTableDefault = u32; -type BrTableLen = u32; -type EndOffset = u32; -type ElseOffset = u32; - #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ConstInstruction { @@ -46,43 +41,26 @@ pub enum ConstInstruction { /// These are our own internal bytecode instructions so they may not match the spec exactly. /// Wasm Bytecode can map to multiple of these instructions. /// -/// # Differences to the spec -/// * `br_table` stores the jump labels in the following `br_label` instructions to keep this enum small. -/// * Lables/Blocks: we store the label end offset in the instruction itself and use `EndBlockFrame` to mark the end of a block. -/// This makes it easier to implement the label stack iteratively. -/// /// See #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -// should be kept as small as possible (16 bytes max) #[rustfmt::skip] pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), - // LocalsStore32(LocalAddr, LocalAddr, u32, MemAddr), LocalsStore64(LocalAddr, LocalAddr, u32, MemAddr), LocalsStore128(LocalAddr, LocalAddr, u32, MemAddr), LocalsStoreRef(LocalAddr, LocalAddr, u32, MemAddr), - // > Control Instructions + // > Control Instructions (jump-oriented, lowered from structured control during parsing) // See Unreachable, Nop, - - Block(EndOffset), - BlockWithType(ValType, EndOffset), - BlockWithFuncType(TypeAddr, EndOffset), - - Loop(EndOffset), - LoopWithType(ValType, EndOffset), - LoopWithFuncType(TypeAddr, EndOffset), - - If(ElseOffset, EndOffset), - IfWithType(ValType, ElseOffset, EndOffset), - IfWithFuncType(TypeAddr, ElseOffset, EndOffset), - - Else(EndOffset), - EndBlockFrame, - Br(LabelAddr), - BrIf(LabelAddr), - BrTable(BrTableDefault, BrTableLen), // has to be followed by multiple BrLabel instructions - BrLabel(LabelAddr), + Jump(u32), + JumpIfZero(u32), + DropKeepSmall { base32: u8, keep32: u8, base64: u8, keep64: u8, base128: u8, keep128: u8, base_ref: u8, keep_ref: u8 }, + DropKeep32(u16, u16), + DropKeep64(u16, u16), + DropKeep128(u16, u16), + DropKeepRef(u16, u16), + BranchTable(u32, u32), // (default_landing_pad_ip, target_count) — followed by BranchTableTarget entries + BranchTableTarget(u32), // (landing_pad_ip) Return, Call(FuncAddr), CallIndirect(TypeAddr, TableAddr), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 969620d7..ba2cb56c 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -155,7 +155,6 @@ pub type ConstIdx = Addr; // additional internal addresses pub type TypeAddr = Addr; pub type LocalAddr = u16; // there can't be more than 50.000 locals in a function -pub type LabelAddr = Addr; pub type ModuleInstanceAddr = Addr; /// A WebAssembly External Value. @@ -288,12 +287,14 @@ impl Debug for ArcSlice { } } +#[cfg(feature = "archive")] impl serde::Serialize for ArcSlice { fn serialize(&self, serializer: S) -> Result { self.0.as_ref().serialize(serializer) } } +#[cfg(feature = "archive")] impl<'de, T: serde::Deserialize<'de> + Debug> serde::Deserialize<'de> for ArcSlice { fn deserialize>(deserializer: D) -> Result { let vec: alloc::vec::Vec = alloc::vec::Vec::deserialize(deserializer)?; From 99f5168bbe5d1dc2bc24e2c5e76e1be4599cabde Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 30 Mar 2026 23:13:56 +0200 Subject: [PATCH 16/47] chore: cleanup Signed-off-by: Henry --- Cargo.lock | 16 ++++---- crates/tinywasm/benches/argon2id.rs | 5 --- crates/tinywasm/benches/tinywasm.rs | 6 +-- crates/tinywasm/src/interpreter/executor.rs | 38 +++++++++---------- .../src/interpreter/stack/call_stack.rs | 8 ++-- .../src/interpreter/stack/value_stack.rs | 13 +++++-- crates/tinywasm/src/store/memory.rs | 16 ++++---- .../generated/wasm-custom-page-sizes.csv | 2 +- crates/types/src/instructions.rs | 4 +- 9 files changed, 53 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04471e22..66bc3d3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "shlex", @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "wasm-testsuite" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921e4ecec67cf5017034abf411b7458c9f8ded6221d2af804994e8b282f3ff92" +checksum = "73fbb8e8f3d0776ab4a9c9fe5b9252d5ea9d4536e6d8c9e2a666a6e16aa1279f" dependencies = [ "include_dir", "wast", @@ -825,18 +825,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index 74ce1ad3..aa8b38b6 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -5,10 +5,6 @@ use types::TinyWasmModule; const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.opt.wasm"); -fn init_log() { - let _ = pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::Off).try_init(); -} - fn argon2id_parse() -> Result { let parser = tinywasm_parser::Parser::new(); let data = parser.parse_module_bytes(WASM)?; @@ -34,7 +30,6 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> { } fn criterion_benchmark(c: &mut Criterion) { - init_log(); let module = argon2id_parse().expect("argon2id_parse"); let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 7ad20823..4848a4a7 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -35,9 +35,9 @@ fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); - // c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - // c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); - // c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); + c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); + c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); c.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 9d98cce9..bf76e847 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -17,7 +17,6 @@ use crate::*; pub(crate) struct Executor<'store> { cf: CallFrame, - instructions: ArcSlice, func: Rc, module: Rc, store: &'store mut Store, @@ -27,8 +26,7 @@ impl<'store> Executor<'store> { pub(crate) fn new(store: &'store mut Store, cf: CallFrame) -> Result { let module = store.get_module_instance_raw(cf.module_addr).clone(); let func = store.state.get_wasm_func(cf.func_addr).clone(); - let instructions = func.instructions.clone(); - Ok(Self { module, store, cf, func, instructions }) + Ok(Self { module, store, cf, func }) } pub(crate) fn run_to_completion(&mut self) -> Result<()> { @@ -76,12 +74,12 @@ impl<'store> Executor<'store> { }; } - let next = match self.instructions.0.get(self.cf.instr_ptr) { + let next = match self.func.instructions.0.get(self.cf.instr_ptr) { Some(instr) => instr, None => unreachable!( "Instruction pointer out of bounds: {} ({} instructions)", self.cf.instr_ptr, - self.instructions.0.len() + self.func.instructions.0.len() ), }; @@ -116,36 +114,36 @@ impl<'store> Executor<'store> { return ControlFlow::Continue(()); } DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { - let b32 = self.cf.stack_base.s32 as usize + *base32 as usize; + let b32 = self.cf.stack_base.s32 + *base32 as usize; let k32 = *keep32 as usize; self.store.stack.values.stack_32.truncate_keep(b32, k32); - let b64 = self.cf.stack_base.s64 as usize + *base64 as usize; + let b64 = self.cf.stack_base.s64 + *base64 as usize; let k64 = *keep64 as usize; self.store.stack.values.stack_64.truncate_keep(b64, k64); - let b128 = self.cf.stack_base.s128 as usize + *base128 as usize; + let b128 = self.cf.stack_base.s128 + *base128 as usize; let k128 = *keep128 as usize; self.store.stack.values.stack_128.truncate_keep(b128, k128); - let bref = self.cf.stack_base.sref as usize + *base_ref as usize; + let bref = self.cf.stack_base.sref + *base_ref as usize; let kref = *keep_ref as usize; self.store.stack.values.stack_ref.truncate_keep(bref, kref); } DropKeep32(base, keep) => { - let b = self.cf.stack_base.s32 as usize + *base as usize; + let b = self.cf.stack_base.s32 + *base as usize; let k = *keep as usize; self.store.stack.values.stack_32.truncate_keep(b, k); } DropKeep64(base, keep) => { - let b = self.cf.stack_base.s64 as usize + *base as usize; + let b = self.cf.stack_base.s64 + *base as usize; let k = *keep as usize; self.store.stack.values.stack_64.truncate_keep(b, k); } DropKeep128(base, keep) => { - let b = self.cf.stack_base.s128 as usize + *base as usize; + let b = self.cf.stack_base.s128 + *base as usize; let k = *keep as usize; self.store.stack.values.stack_128.truncate_keep(b, k); } DropKeepRef(base, keep) => { - let b = self.cf.stack_base.sref as usize + *base as usize; + let b = self.cf.stack_base.sref + *base as usize; let k = *keep as usize; self.store.stack.values.stack_ref.truncate_keep(b, k); } @@ -154,7 +152,7 @@ impl<'store> Executor<'store> { let start = self.cf.instr_ptr + 1; let target_ip = if idx >= 0 && (idx as u32) < *len { - match self.instructions.0.get(start + idx as usize) { + match self.func.instructions.0.get(start + idx as usize) { Some(Instruction::BranchTableTarget(ip)) => *ip, _ => *default_ip, } @@ -629,9 +627,8 @@ impl<'store> Executor<'store> { func_addr: FuncAddr, owner: ModuleInstanceAddr, ) -> ControlFlow> { - if self.func != wasm_func { + if !Rc::ptr_eq(&self.func, &wasm_func) { self.func = wasm_func.clone(); - self.instructions = wasm_func.instructions.clone(); } if IS_RETURN_CALL { @@ -716,7 +713,6 @@ impl<'store> Executor<'store> { if cf.func_addr != self.cf.func_addr { self.func = self.store.state.get_wasm_func(cf.func_addr).clone(); - self.instructions = self.func.instructions.clone(); if cf.module_addr != self.module.idx { self.module = self.store.get_module_instance_raw(cf.module_addr).clone(); @@ -751,14 +747,14 @@ impl<'store> Executor<'store> { } fn exec_memory_grow(&mut self, addr: u32) -> Result<()> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); - let prev_size = mem.page_count; - let pages_delta = match mem.is_64bit() { + let is_64bit = mem.is_64bit(); + let pages_delta = match is_64bit { true => self.store.stack.values.pop::(), false => i64::from(self.store.stack.values.pop::()), }; - let size = mem.grow(pages_delta).map(|_| prev_size as i64).unwrap_or(-1); - match mem.is_64bit() { + let size = mem.grow(pages_delta).unwrap_or(-1); + match is_64bit { true => self.store.stack.values.push::(size)?, false => self.store.stack.values.push::(size as i32)?, }; diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 4750b03d..fc2e1fc8 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -44,10 +44,10 @@ pub(crate) struct CallFrame { #[derive(Debug, Clone, Copy, Default)] pub(crate) struct StackBase { - pub(crate) s32: u32, - pub(crate) s64: u32, - pub(crate) s128: u32, - pub(crate) sref: u32, + pub(crate) s32: usize, + pub(crate) s64: usize, + pub(crate) s128: usize, + pub(crate) sref: usize, } #[derive(Debug)] diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 9ce73d93..47672d7a 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -86,6 +86,11 @@ impl Stack { } pub(crate) fn pop_to_locals(&mut self, param_count: usize, local_count: usize) -> Box<[T]> { + if local_count == 0 { + debug_assert!(param_count == 0, "param count exceeds local count"); + return Box::new([]); + } + let mut locals = alloc::vec![T::default(); local_count].into_boxed_slice(); let start = self.len.checked_sub(param_count).unwrap_or_else(|| unreachable!("value stack underflow, this is a bug")); @@ -120,10 +125,10 @@ impl ValueStack { pub(crate) fn height(&self) -> StackBase { StackBase { - s32: u32::try_from(self.stack_32.len()).expect("stack32 height overflow"), - s64: u32::try_from(self.stack_64.len()).expect("stack64 height overflow"), - s128: u32::try_from(self.stack_128.len()).expect("stack128 height overflow"), - sref: u32::try_from(self.stack_ref.len()).expect("stack_ref height overflow"), + s32: self.stack_32.len(), + s64: self.stack_64.len(), + s128: self.stack_128.len(), + sref: self.stack_ref.len(), } } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 46226b9c..a37a9e6f 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -54,10 +54,6 @@ impl MemoryInstance { Ok(()) } - pub(crate) fn max_pages(&self) -> usize { - self.kind.page_count_max().try_into().unwrap_or(usize::MAX) - } - pub(crate) fn load(&self, addr: usize, len: usize) -> Result<&[u8]> { let Some(end) = addr.checked_add(len) else { cold(); @@ -133,9 +129,10 @@ impl MemoryInstance { let current_pages = self.page_count; let pages_delta = usize::try_from(pages_delta).ok()?; let new_pages = current_pages.checked_add(pages_delta)?; + let max_pages = self.kind.page_count_max().try_into().unwrap_or(usize::MAX); - if new_pages > self.max_pages() { - log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, self.max_pages()); + if new_pages > max_pages { + log::debug!("memory.grow failed: new_pages={}, max_pages={}", new_pages, max_pages); return None; } @@ -145,7 +142,12 @@ impl MemoryInstance { return None; } - self.data.resize(usize::try_from(new_size).ok()?, 0); + let new_size = usize::try_from(new_size).ok()?; + if new_size == self.data.len() { + return i64::try_from(current_pages).ok(); + } + + self.data.resize(new_size, 0); self.page_count = new_pages; i64::try_from(current_pages).ok() } diff --git a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv index 1761a0ad..cdfeeb52 100644 --- a/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv +++ b/crates/tinywasm/tests/generated/wasm-custom-page-sizes.csv @@ -1,2 +1,2 @@ 0.8.0,57,0,[{"name":"custom-page-sizes-invalid.wast","passed":22,"failed":0},{"name":"custom-page-sizes.wast","passed":35,"failed":0}] -0.9.0-alpha.0,80,0,[{"name":"custom-page-sizes-invalid.wast","passed":23,"failed":0},{"name":"custom-page-sizes.wast","passed":45,"failed":0},{"name":"memory_max.wast","passed":6,"failed":0},{"name":"memory_max_i64.wast","passed":6,"failed":0}] +0.9.0-alpha.0,211,0,[{"name":"binary.wast","passed":127,"failed":0},{"name":"custom-page-sizes-invalid.wast","passed":23,"failed":0},{"name":"custom-page-sizes.wast","passed":45,"failed":0},{"name":"memory_max.wast","passed":8,"failed":0},{"name":"memory_max_i64.wast","passed":8,"failed":0}] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 5c1627a8..f3acbf7c 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -59,14 +59,14 @@ pub enum Instruction { DropKeep64(u16, u16), DropKeep128(u16, u16), DropKeepRef(u16, u16), - BranchTable(u32, u32), // (default_landing_pad_ip, target_count) — followed by BranchTableTarget entries + BranchTable(u32, u32), // (default_landing_pad_ip, target_count) - followed by BranchTableTarget entries BranchTableTarget(u32), // (landing_pad_ip) Return, Call(FuncAddr), CallIndirect(TypeAddr, TableAddr), ReturnCall(FuncAddr), ReturnCallIndirect(TypeAddr, TableAddr), - + // > Parametric Instructions // See Drop32, Select32, From 2e40140c053c481820f99ce982201f8a68d21a54 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 31 Mar 2026 20:21:18 +0200 Subject: [PATCH 17/47] chore: move locals to stack Signed-off-by: Henry --- crates/tinywasm/src/func.rs | 7 +- crates/tinywasm/src/imports.rs | 12 +- crates/tinywasm/src/instance.rs | 8 ++ crates/tinywasm/src/interpreter/executor.rs | 117 +++++++++++----- .../tinywasm/src/interpreter/no_std_floats.rs | 20 +-- .../tinywasm/src/interpreter/num_helpers.rs | 7 + .../src/interpreter/stack/call_stack.rs | 114 +++++----------- crates/tinywasm/src/interpreter/stack/mod.rs | 2 +- .../src/interpreter/stack/value_stack.rs | 126 ++++++++++++++---- crates/tinywasm/src/interpreter/value128.rs | 5 + crates/tinywasm/src/interpreter/values.rs | 44 +++--- crates/tinywasm/src/store/memory.rs | 8 +- crates/tinywasm/src/store/mod.rs | 6 +- crates/types/src/instructions.rs | 24 ++-- crates/types/src/lib.rs | 37 +++-- crates/types/src/value.rs | 30 ++--- examples/rust/build.sh | 4 +- 17 files changed, 339 insertions(+), 232 deletions(-) diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index a741e0f9..0e42dd76 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -56,11 +56,14 @@ impl FuncHandle { }; // 6. Let f be the dummy frame - let callframe = CallFrame::new_with_params(wasm_func.locals, self.addr, func_inst.owner, params); - // 7. Push the frame f to the call stack // & 8. Push the values to the stack store.stack.clear(); + store.stack.values.extend_from_wasmvalues(params)?; + let (locals_base, _stack_base, stack_offset) = + store.stack.values.enter_locals(wasm_func.params, wasm_func.locals)?; + let callframe = CallFrame::new(self.addr, func_inst.owner, locals_base, stack_offset); + // 9. Invoke the function instance InterpreterRuntime::exec(store, callframe)?; diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 099396b0..ea8b965d 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -122,17 +122,17 @@ pub enum Extern { impl Extern { /// Create a new global import - pub fn global(val: WasmValue, mutable: bool) -> Self { + pub const fn global(val: WasmValue, mutable: bool) -> Self { Self::Global { ty: GlobalType { ty: val.val_type(), mutable }, val } } /// Create a new table import - pub fn table(ty: TableType, init: WasmValue) -> Self { + pub const fn table(ty: TableType, init: WasmValue) -> Self { Self::Table { ty, init } } /// Create a new memory import - pub fn memory(ty: MemoryType) -> Self { + pub const fn memory(ty: MemoryType) -> Self { Self::Memory { ty } } @@ -180,7 +180,7 @@ impl Extern { } /// Get the kind of the external value - pub fn kind(&self) -> ExternalKind { + pub const fn kind(&self) -> ExternalKind { match self { Self::Global { .. } => ExternalKind::Global, Self::Table { .. } => ExternalKind::Table, @@ -257,14 +257,14 @@ pub(crate) struct ResolvedImports { } impl ResolvedImports { - pub(crate) fn new() -> Self { + pub(crate) const fn new() -> Self { Self { globals: Vec::new(), tables: Vec::new(), memories: Vec::new(), funcs: Vec::new() } } } impl Imports { /// Create a new empty import set - pub fn new() -> Self { + pub const fn new() -> Self { Self { values: BTreeMap::new(), modules: BTreeMap::new() } } diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 45542f99..340bfd6e 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -36,6 +36,7 @@ pub(crate) struct ModuleInstanceInner { } impl ModuleInstanceInner { + #[inline] pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { match self.types.get(addr as usize) { Some(ty) => ty, @@ -43,11 +44,13 @@ impl ModuleInstanceInner { } } + #[inline] pub(crate) fn func_addrs(&self) -> &[FuncAddr] { &self.func_addrs } // resolve a function address to the global store address + #[inline] pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { match self.func_addrs.get(addr as usize) { Some(addr) => *addr, @@ -56,6 +59,7 @@ impl ModuleInstanceInner { } // resolve a table address to the global store address + #[inline] pub(crate) fn resolve_table_addr(&self, addr: TableAddr) -> TableAddr { match self.table_addrs.get(addr as usize) { Some(addr) => *addr, @@ -64,6 +68,7 @@ impl ModuleInstanceInner { } // resolve a memory address to the global store address + #[inline] pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { match self.mem_addrs.get(addr as usize) { Some(addr) => *addr, @@ -72,6 +77,7 @@ impl ModuleInstanceInner { } // resolve a data address to the global store address + #[inline] pub(crate) fn resolve_data_addr(&self, addr: DataAddr) -> DataAddr { match self.data_addrs.get(addr as usize) { Some(addr) => *addr, @@ -80,6 +86,7 @@ impl ModuleInstanceInner { } // resolve a memory address to the global store address + #[inline] pub(crate) fn resolve_elem_addr(&self, addr: ElemAddr) -> ElemAddr { match self.elem_addrs.get(addr as usize) { Some(addr) => *addr, @@ -88,6 +95,7 @@ impl ModuleInstanceInner { } // resolve a global address to the global store address + #[inline] pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { match self.global_addrs.get(addr as usize) { Some(addr) => *addr, diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index bf76e847..d5e6fea0 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -114,36 +114,36 @@ impl<'store> Executor<'store> { return ControlFlow::Continue(()); } DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { - let b32 = self.cf.stack_base.s32 + *base32 as usize; + let b32 = self.cf.stack_base().s32 + *base32 as usize; let k32 = *keep32 as usize; self.store.stack.values.stack_32.truncate_keep(b32, k32); - let b64 = self.cf.stack_base.s64 + *base64 as usize; + let b64 = self.cf.stack_base().s64 + *base64 as usize; let k64 = *keep64 as usize; self.store.stack.values.stack_64.truncate_keep(b64, k64); - let b128 = self.cf.stack_base.s128 + *base128 as usize; + let b128 = self.cf.stack_base().s128 + *base128 as usize; let k128 = *keep128 as usize; self.store.stack.values.stack_128.truncate_keep(b128, k128); - let bref = self.cf.stack_base.sref + *base_ref as usize; + let bref = self.cf.stack_base().sref + *base_ref as usize; let kref = *keep_ref as usize; self.store.stack.values.stack_ref.truncate_keep(bref, kref); } DropKeep32(base, keep) => { - let b = self.cf.stack_base.s32 + *base as usize; + let b = self.cf.stack_base().s32 + *base as usize; let k = *keep as usize; self.store.stack.values.stack_32.truncate_keep(b, k); } DropKeep64(base, keep) => { - let b = self.cf.stack_base.s64 + *base as usize; + let b = self.cf.stack_base().s64 + *base as usize; let k = *keep as usize; self.store.stack.values.stack_64.truncate_keep(b, k); } DropKeep128(base, keep) => { - let b = self.cf.stack_base.s128 + *base as usize; + let b = self.cf.stack_base().s128 + *base as usize; let k = *keep as usize; self.store.stack.values.stack_128.truncate_keep(b, k); } DropKeepRef(base, keep) => { - let b = self.cf.stack_base.sref + *base as usize; + let b = self.cf.stack_base().sref + *base as usize; let k = *keep as usize; self.store.stack.values.stack_ref.truncate_keep(b, k); } @@ -163,22 +163,58 @@ impl<'store> Executor<'store> { return ControlFlow::Continue(()); } Return => return self.exec_return(), - LocalGet32(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, - LocalGet64(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, - LocalGet128(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, - LocalGetRef(local_index) => self.store.stack.values.push(self.cf.locals.get::(*local_index)).to_cf()?, - LocalSet32(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), - LocalSet64(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), - LocalSet128(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), - LocalSetRef(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.pop::()), - LocalCopy32(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), - LocalCopy64(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), - LocalCopy128(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), - LocalCopyRef(from, to) => self.cf.locals.set(*to, self.cf.locals.get::(*from)), - LocalTee32(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), - LocalTee64(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), - LocalTee128(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), - LocalTeeRef(local_index) => self.cf.locals.set(*local_index, self.store.stack.values.peek::()), + LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_32(&self.cf, *local_index)).to_cf()?, + LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_64(&self.cf, *local_index)).to_cf()?, + LocalGet128(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_128(&self.cf, *local_index)).to_cf()?, + LocalGetRef(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_ref(&self.cf, *local_index)).to_cf()?, + LocalSet32(local_index) => { + let val = self.store.stack.values.pop::(); + self.store.stack.values.local_set_32(&self.cf, *local_index, val); + } + LocalSet64(local_index) => { + let val = self.store.stack.values.pop::(); + self.store.stack.values.local_set_64(&self.cf, *local_index, val); + } + LocalSet128(local_index) => { + let val = self.store.stack.values.pop::(); + self.store.stack.values.local_set_128(&self.cf, *local_index, val); + } + LocalSetRef(local_index) => { + let val = self.store.stack.values.pop::(); + self.store.stack.values.local_set_ref(&self.cf, *local_index, val); + } + LocalCopy32(from, to) => { + let val = self.store.stack.values.local_get_32(&self.cf, *from); + self.store.stack.values.local_set_32(&self.cf, *to, val); + } + LocalCopy64(from, to) => { + let val = self.store.stack.values.local_get_64(&self.cf, *from); + self.store.stack.values.local_set_64(&self.cf, *to, val); + } + LocalCopy128(from, to) => { + let val = self.store.stack.values.local_get_128(&self.cf, *from); + self.store.stack.values.local_set_128(&self.cf, *to, val); + } + LocalCopyRef(from, to) => { + let val = self.store.stack.values.local_get_ref(&self.cf, *from); + self.store.stack.values.local_set_ref(&self.cf, *to, val); + } + LocalTee32(local_index) => { + let val = self.store.stack.values.peek::(); + self.store.stack.values.local_set_32(&self.cf, *local_index, val); + } + LocalTee64(local_index) => { + let val = self.store.stack.values.peek::(); + self.store.stack.values.local_set_64(&self.cf, *local_index, val); + } + LocalTee128(local_index) => { + let val = self.store.stack.values.peek::(); + self.store.stack.values.local_set_128(&self.cf, *local_index, val); + } + LocalTeeRef(local_index) => { + let val = self.store.stack.values.peek::(); + self.store.stack.values.local_set_ref(&self.cf, *local_index, val); + } GlobalGet(global_index) => self.exec_global_get(*global_index).to_cf()?, GlobalSet32(global_index) => self.exec_global_set::(*global_index), GlobalSet64(global_index) => self.exec_global_set::(*global_index), @@ -627,20 +663,35 @@ impl<'store> Executor<'store> { func_addr: FuncAddr, owner: ModuleInstanceAddr, ) -> ControlFlow> { + if !IS_RETURN_CALL && self.store.stack.call_stack.is_full() { + return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + } + if !Rc::ptr_eq(&self.func, &wasm_func) { self.func = wasm_func.clone(); } if IS_RETURN_CALL { - let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - let stack_base = self.store.stack.values.height(); - self.cf.reuse_for(func_addr, locals, owner, stack_base); + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, wasm_func.params); + } + + let (locals_base, _stack_base, stack_offset) = + match self.store.stack.values.enter_locals(wasm_func.params, wasm_func.locals) { + Ok(v) => v, + Err(Error::Trap(Trap::ValueStackOverflow)) if !IS_RETURN_CALL => { + return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + } + Err(err) => return ControlFlow::Break(Some(err)), + }; + + let new_call_frame = CallFrame::new(func_addr, owner, locals_base, stack_offset); + + if IS_RETURN_CALL { + self.cf = new_call_frame; } else { - let locals = self.store.stack.values.pop_locals(wasm_func.params, wasm_func.locals); - let stack_base = self.store.stack.values.height(); - let new_call_frame = CallFrame::new(func_addr, owner, locals, stack_base); self.cf.incr_instr_ptr(); // skip the call instruction - self.store.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame)).to_cf()?; + self.store.stack.call_stack.push(self.cf).to_cf()?; + self.cf = new_call_frame; } if self.cf.module_addr != self.module.idx { @@ -709,6 +760,9 @@ impl<'store> Executor<'store> { } fn exec_return(&mut self) -> ControlFlow> { + let result_counts = ValueCountsSmall::from(self.func.ty.results.iter()); + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, result_counts); + let Some(cf) = self.store.stack.call_stack.pop() else { return ControlFlow::Break(None) }; if cf.func_addr != self.cf.func_addr { @@ -718,7 +772,6 @@ impl<'store> Executor<'store> { self.module = self.store.get_module_instance_raw(cf.module_addr).clone(); } } - self.cf = cf; ControlFlow::Continue(()) } diff --git a/crates/tinywasm/src/interpreter/no_std_floats.rs b/crates/tinywasm/src/interpreter/no_std_floats.rs index 0698f5de..b6ce998a 100644 --- a/crates/tinywasm/src/interpreter/no_std_floats.rs +++ b/crates/tinywasm/src/interpreter/no_std_floats.rs @@ -9,18 +9,18 @@ pub(super) trait NoStdFloatExt { #[rustfmt::skip] impl NoStdFloatExt for f64 { - fn round(self) -> Self { libm::round(self) } - fn ceil(self) -> Self { libm::ceil(self) } - fn floor(self) -> Self { libm::floor(self) } - fn trunc(self) -> Self { libm::trunc(self) } - fn sqrt(self) -> Self { libm::sqrt(self) } + #[inline] fn round(self) -> Self { libm::round(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) } } #[rustfmt::skip] impl NoStdFloatExt for f32 { - fn round(self) -> Self { libm::roundf(self) } - fn ceil(self) -> Self { libm::ceilf(self) } - fn floor(self) -> Self { libm::floorf(self) } - fn trunc(self) -> Self { libm::truncf(self) } - fn sqrt(self) -> Self { libm::sqrtf(self) } + #[inline] fn round(self) -> Self { libm::roundf(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) } } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index f2b617d8..f800575b 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -70,6 +70,7 @@ macro_rules! impl_wasm_float_ops { ($($t:ty)*) => ($( impl TinywasmFloatExt for $t { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fnearest + #[inline] fn tw_nearest(self) -> Self { match self { #[cfg(not(feature = "canonicalize_nans"))] @@ -94,6 +95,7 @@ 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 tw_minimum(self, other: Self) -> Self { match self.partial_cmp(&other) { Some(core::cmp::Ordering::Less) => self, @@ -108,6 +110,7 @@ macro_rules! impl_wasm_float_ops { // https://webassembly.github.io/spec/core/exec/numerics.html#op-fmax // Based on f32::maximum (which is not yet stable) + #[inline] fn tw_maximum(self, other: Self) -> Self { match self.partial_cmp(&other) { Some(core::cmp::Ordering::Greater) => self, @@ -135,18 +138,22 @@ pub(crate) trait WasmIntOps { macro_rules! impl_wrapping_self_sh { ($($t:ty)*) => ($( impl WasmIntOps for $t { + #[inline] fn wasm_shl(self, rhs: Self) -> Self { self.wrapping_shl(rhs as u32) } + #[inline] fn wasm_shr(self, rhs: Self) -> Self { self.wrapping_shr(rhs as u32) } + #[inline] fn wasm_rotl(self, rhs: Self) -> Self { self.rotate_left(rhs as u32) } + #[inline] fn wasm_rotr(self, rhs: Self) -> Self { self.rotate_right(rhs as u32) } diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index fc2e1fc8..2409d882 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,45 +1,56 @@ -use crate::interpreter::{Value128, values::*}; use crate::{Result, Trap, unlikely}; -use alloc::boxed::Box; use alloc::vec::Vec; -use tinywasm_types::{FuncAddr, LocalAddr, ModuleInstanceAddr, ValueCounts, WasmValue}; +use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCountsSmall}; #[derive(Debug)] pub(crate) struct CallStack { stack: Vec, + len: usize, } impl CallStack { pub(crate) fn new(config: &crate::engine::Config) -> Self { - Self { stack: Vec::with_capacity(config.call_stack_size) } + let mut stack = Vec::with_capacity(config.call_stack_size); + stack.resize_with(config.call_stack_size, CallFrame::default); + Self { stack, len: 0 } } pub(crate) fn clear(&mut self) { - self.stack.clear(); + self.len = 0; } pub(crate) fn pop(&mut self) -> Option { - self.stack.pop() + if self.len == 0 { + return None; + } + + self.len -= 1; + Some(self.stack[self.len]) + } + + pub(crate) fn is_full(&self) -> bool { + self.len >= self.stack.len() } pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - if unlikely(self.stack.len() >= self.stack.capacity()) { + if unlikely(self.is_full()) { return Err(Trap::CallStackOverflow.into()); } - self.stack.push(call_frame); + self.stack[self.len] = call_frame; + self.len += 1; Ok(()) } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy, Default)] pub(crate) struct CallFrame { pub(crate) instr_ptr: usize, - pub(crate) locals: Locals, pub(crate) module_addr: ModuleInstanceAddr, pub(crate) func_addr: FuncAddr, - pub(crate) stack_base: StackBase, + pub(crate) locals_base: StackBase, + pub(crate) stack_offset: ValueCountsSmall, } #[derive(Debug, Clone, Copy, Default)] @@ -50,86 +61,27 @@ pub(crate) struct StackBase { pub(crate) sref: usize, } -#[derive(Debug)] -pub(crate) struct Locals { - pub(crate) locals_32: Box<[Value32]>, - pub(crate) locals_64: Box<[Value64]>, - pub(crate) locals_128: Box<[Value128]>, - pub(crate) locals_ref: Box<[ValueRef]>, -} - -impl Locals { - pub(crate) fn get(&self, local_index: LocalAddr) -> T { - T::local_get(self, local_index) - } - - pub(crate) fn set(&mut self, local_index: LocalAddr, value: T) { - T::local_set(self, local_index, value); - } -} - impl CallFrame { pub(crate) fn new( func_addr: FuncAddr, module_addr: ModuleInstanceAddr, - locals: Locals, - stack_base: StackBase, + locals_base: StackBase, + stack_offset: ValueCountsSmall, ) -> Self { - Self { instr_ptr: 0, func_addr, module_addr, locals, stack_base } + Self { instr_ptr: 0, func_addr, module_addr, locals_base, stack_offset } } - pub(crate) fn new_with_params( - local_count: ValueCounts, - func_addr: FuncAddr, - module_addr: ModuleInstanceAddr, - params: &[WasmValue], - ) -> Self { - let locals = { - let mut locals_32 = Vec::with_capacity(local_count.c32 as usize); - let mut locals_64 = Vec::with_capacity(local_count.c64 as usize); - let mut locals_128 = Vec::with_capacity(local_count.c128 as usize); - let mut locals_ref = Vec::with_capacity(local_count.cref as usize); - - for p in params { - match p.into() { - TinyWasmValue::Value32(v) => locals_32.push(v), - TinyWasmValue::Value64(v) => locals_64.push(v), - TinyWasmValue::Value128(v) => locals_128.push(v), - TinyWasmValue::ValueRef(v) => locals_ref.push(v), - } - } - - locals_32.resize_with(local_count.c32 as usize, Default::default); - locals_64.resize_with(local_count.c64 as usize, Default::default); - locals_128.resize_with(local_count.c128 as usize, Default::default); - locals_ref.resize_with(local_count.cref as usize, Default::default); - - Locals { - locals_32: locals_32.into_boxed_slice(), - locals_64: locals_64.into_boxed_slice(), - locals_128: locals_128.into_boxed_slice(), - locals_ref: locals_ref.into_boxed_slice(), - } - }; - - Self::new(func_addr, module_addr, locals, StackBase::default()) + #[inline] + pub(crate) fn stack_base(&self) -> StackBase { + StackBase { + s32: self.locals_base.s32 + self.stack_offset.c32 as usize, + s64: self.locals_base.s64 + self.stack_offset.c64 as usize, + s128: self.locals_base.s128 + self.stack_offset.c128 as usize, + sref: self.locals_base.sref + self.stack_offset.cref as usize, + } } pub(crate) fn incr_instr_ptr(&mut self) { self.instr_ptr += 1; } - - pub(crate) fn reuse_for( - &mut self, - func_addr: FuncAddr, - locals: Locals, - module_addr: ModuleInstanceAddr, - stack_base: StackBase, - ) { - self.func_addr = func_addr; - self.module_addr = module_addr; - self.locals = locals; - self.stack_base = stack_base; - self.instr_ptr = 0; - } } diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index 5912de19..59456d35 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -1,7 +1,7 @@ mod call_stack; mod value_stack; -pub(crate) use call_stack::{CallFrame, CallStack, Locals, StackBase}; +pub(crate) use call_stack::{CallFrame, CallStack, StackBase}; pub(crate) use value_stack::ValueStack; use crate::engine::Config; diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 47672d7a..be79912e 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,10 +1,10 @@ use alloc::boxed::Box; use alloc::vec::Vec; -use tinywasm_types::{ExternRef, FuncRef, ValType, ValueCounts, ValueCountsSmall, WasmValue}; +use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCounts, ValueCountsSmall, WasmValue}; use crate::{Result, Trap, engine::Config, interpreter::*}; -use super::{Locals, StackBase}; +use super::{CallFrame, StackBase}; #[derive(Debug)] pub(crate) struct ValueStack { @@ -68,6 +68,20 @@ impl Stack { &mut self.data[self.len - 1] } + pub(crate) fn get(&self, index: usize) -> T { + match self.data.get(index) { + Some(v) => *v, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + + pub(crate) fn set(&mut self, index: usize, value: T) { + match self.data.get_mut(index) { + Some(v) => *v = value, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { if self.len <= n { return; @@ -85,20 +99,19 @@ impl Stack { self.len = n + keep_tail; } - pub(crate) fn pop_to_locals(&mut self, param_count: usize, local_count: usize) -> Box<[T]> { - if local_count == 0 { - debug_assert!(param_count == 0, "param count exceeds local count"); - return Box::new([]); - } - - let mut locals = alloc::vec![T::default(); local_count].into_boxed_slice(); + pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result<(usize, usize)> { + debug_assert!(param_count <= local_count, "param count exceeds local count"); let start = self.len.checked_sub(param_count).unwrap_or_else(|| unreachable!("value stack underflow, this is a bug")); - debug_assert!(param_count <= local_count, "param count exceeds local count"); - locals[..param_count].copy_from_slice(&self.data[start..self.len]); - self.len = start; - locals + let end = start + local_count; + if end > self.data.len() { + return Err(Trap::ValueStackOverflow.into()); + } + + self.data[(start + param_count)..end].fill(T::default()); + self.len = end; + Ok((start, end)) } } @@ -123,15 +136,6 @@ impl ValueStack { self.stack_32.len() + self.stack_64.len() + self.stack_128.len() + self.stack_ref.len() } - pub(crate) fn height(&self) -> StackBase { - StackBase { - s32: self.stack_32.len(), - s64: self.stack_64.len(), - s128: self.stack_128.len(), - sref: self.stack_ref.len(), - } - } - pub(crate) fn peek(&self) -> T { T::stack_peek(self) } @@ -206,13 +210,77 @@ impl ValueStack { val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) } - pub(crate) fn pop_locals(&mut self, pc: ValueCountsSmall, lc: ValueCounts) -> Locals { - Locals { - locals_32: self.stack_32.pop_to_locals(pc.c32 as usize, lc.c32 as usize), - locals_64: self.stack_64.pop_to_locals(pc.c64 as usize, lc.c64 as usize), - locals_128: self.stack_128.pop_to_locals(pc.c128 as usize, lc.c128 as usize), - locals_ref: self.stack_ref.pop_to_locals(pc.cref as usize, lc.cref as usize), - } + pub(crate) fn enter_locals( + &mut self, + params: ValueCountsSmall, + locals: ValueCounts, + ) -> Result<(StackBase, StackBase, ValueCountsSmall)> { + let stack_offset = ValueCountsSmall { + c32: u16::try_from(locals.c32).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + c64: u16::try_from(locals.c64).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + c128: u16::try_from(locals.c128).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + }; + + let (locals_base32, stack_base32) = self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)?; + let (locals_base64, stack_base64) = self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize)?; + let (locals_base128, stack_base128) = + self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize)?; + let (locals_baseref, stack_baseref) = + self.stack_ref.enter_locals(params.cref as usize, locals.cref as usize)?; + + Ok(( + StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128, sref: locals_baseref }, + StackBase { s32: stack_base32, s64: stack_base64, s128: stack_base128, sref: stack_baseref }, + stack_offset, + )) + } + + pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCountsSmall) { + self.stack_32.truncate_keep(base.s32, keep.c32 as usize); + self.stack_64.truncate_keep(base.s64, keep.c64 as usize); + self.stack_128.truncate_keep(base.s128, keep.c128 as usize); + self.stack_ref.truncate_keep(base.sref, keep.cref as usize); + } + + #[inline] + pub(crate) fn local_get_32(&self, frame: &CallFrame, index: LocalAddr) -> Value32 { + self.stack_32.get(frame.locals_base.s32 + index as usize) + } + + #[inline] + pub(crate) fn local_get_64(&self, frame: &CallFrame, index: LocalAddr) -> Value64 { + self.stack_64.get(frame.locals_base.s64 + index as usize) + } + + #[inline] + pub(crate) fn local_get_128(&self, frame: &CallFrame, index: LocalAddr) -> Value128 { + self.stack_128.get(frame.locals_base.s128 + index as usize) + } + + #[inline] + pub(crate) fn local_get_ref(&self, frame: &CallFrame, index: LocalAddr) -> ValueRef { + self.stack_ref.get(frame.locals_base.sref + index as usize) + } + + #[inline] + pub(crate) fn local_set_32(&mut self, frame: &CallFrame, index: LocalAddr, value: Value32) { + self.stack_32.set(frame.locals_base.s32 + index as usize, value); + } + + #[inline] + pub(crate) fn local_set_64(&mut self, frame: &CallFrame, index: LocalAddr, value: Value64) { + self.stack_64.set(frame.locals_base.s64 + index as usize, value); + } + + #[inline] + pub(crate) fn local_set_128(&mut self, frame: &CallFrame, index: LocalAddr, value: Value128) { + self.stack_128.set(frame.locals_base.s128 + index as usize, value); + } + + #[inline] + pub(crate) fn local_set_ref(&mut self, frame: &CallFrame, index: LocalAddr, value: ValueRef) { + self.stack_ref.set(frame.locals_base.sref + index as usize, value); } pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<()> { diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index a16327ff..edcf50a9 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -124,28 +124,33 @@ impl Value128 { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) } + #[inline(always)] fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { let lanes = self.as_f32x4(); Self::from_f32x4([op(lanes[0]), op(lanes[1]), op(lanes[2]), op(lanes[3])]) } + #[inline(always)] fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); Self::from_f32x4([op(a[0], b[0]), op(a[1], b[1]), op(a[2], b[2]), op(a[3], b[3])]) } + #[inline(always)] fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { let lanes = self.as_f64x2(); Self::from_f64x2([op(lanes[0]), op(lanes[1])]) } + #[inline(always)] fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); Self::from_f64x2([op(a[0], b[0]), op(a[1], b[1])]) } + #[inline] pub const fn reduce_or(self) -> u8 { let mut result = 0u8; let bytes = self.to_le_bytes(); diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 41db3832..b1530e7a 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,7 +1,7 @@ use crate::{Result, interpreter::value128::Value128}; -use super::stack::{Locals, ValueStack}; -use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, WasmValue}; +use super::stack::ValueStack; +use tinywasm_types::{ExternRef, FuncRef, ValType, WasmValue}; pub(crate) type Value32 = u32; pub(crate) type Value64 = u64; @@ -121,12 +121,10 @@ pub(crate) trait InternalValue: sealed::Sealed + Into { fn stack_peek(stack: &ValueStack) -> Self where Self: Sized; - fn local_get(locals: &Locals, index: LocalAddr) -> Self; - fn local_set(locals: &mut Locals, index: LocalAddr, value: Self); } macro_rules! impl_internalvalue { - ($( $variant:ident, $stack:ident, $locals:ident, $internal:ty, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { + ($( $variant:ident, $stack:ident, $internal:ty, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { $( impl sealed::Sealed for $outer {} @@ -137,18 +135,22 @@ macro_rules! impl_internalvalue { } impl InternalValue for $outer { + #[inline] fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()> { stack.$stack.push($to_internal(value)) } + #[inline] fn stack_pop(stack: &mut ValueStack) -> Self { $to_outer(stack.$stack.pop()) } + #[inline] fn stack_peek(stack: &ValueStack) -> Self { $to_outer(*stack.$stack.last()) } + #[inline] fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); @@ -156,6 +158,7 @@ macro_rules! impl_internalvalue { Ok(()) } + #[inline] fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { let v3 = stack.$stack.pop(); let v2 = stack.$stack.pop(); @@ -164,37 +167,24 @@ macro_rules! impl_internalvalue { Ok(()) } + #[inline] fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { let v = stack.$stack.last_mut(); *v = $to_internal(func($to_outer(*v))?); Ok(()) } - - fn local_get(locals: &Locals, index: LocalAddr) -> Self { - match locals.$locals.get(index as usize) { - Some(v) => $to_outer(*v), - None => unreachable!("Local variable out of bounds, this is a bug"), - } - } - - fn local_set(locals: &mut Locals, index: LocalAddr, value: Self) { - match locals.$locals.get_mut(index as usize) { - Some(v) => *v = $to_internal(value), - None => unreachable!("Local variable out of bounds, this is a bug"), - } - } } )* }; } impl_internalvalue! { - Value32, stack_32, locals_32, u32, u32, |v| v, |v| v - Value64, stack_64, locals_64, u64, u64, |v| v, |v| v - Value32, stack_32, locals_32, u32, i32, |v: i32| u32::from_ne_bytes(v.to_ne_bytes()), |v: u32| i32::from_ne_bytes(v.to_ne_bytes()) - Value64, stack_64, locals_64, u64, i64, |v: i64| u64::from_ne_bytes(v.to_ne_bytes()), |v: u64| i64::from_ne_bytes(v.to_ne_bytes()) - Value32, stack_32, locals_32, u32, f32, f32::to_bits, f32::from_bits - Value64, stack_64, locals_64, u64, f64, f64::to_bits, f64::from_bits - ValueRef, stack_ref, locals_ref, ValueRef, ValueRef, |v| v, |v| v - Value128, stack_128, locals_128, Value128, Value128, |v| v, |v| v + Value32, stack_32, u32, u32, |v| v, |v| v + Value64, stack_64, u64, u64, |v| v, |v| v + Value32, stack_32, u32, i32, |v: i32| u32::from_ne_bytes(v.to_ne_bytes()), |v: u32| i32::from_ne_bytes(v.to_ne_bytes()) + Value64, stack_64, u64, i64, |v: i64| u64::from_ne_bytes(v.to_ne_bytes()), |v: u64| i64::from_ne_bytes(v.to_ne_bytes()) + Value32, stack_32, u32, f32, f32::to_bits, f32::from_bits + Value64, stack_64, u64, f64, f64::to_bits, f64::from_bits + ValueRef, stack_ref, ValueRef, ValueRef, |v| v, |v| v + Value128, stack_128, Value128, Value128, |v| v, |v| v } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index a37a9e6f..5c08ab93 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -28,15 +28,15 @@ impl MemoryInstance { } } - pub(crate) fn is_64bit(&self) -> bool { + pub(crate) const fn is_64bit(&self) -> bool { matches!(self.kind.arch(), MemoryArch::I64) } - pub(crate) fn len(&self) -> usize { + pub(crate) const fn len(&self) -> usize { self.data.len() } - fn trap_oob(&self, addr: usize, len: usize) -> Error { + const fn trap_oob(&self, addr: usize, len: usize) -> Error { Error::Trap(crate::Trap::MemoryOutOfBounds { offset: addr, len, max: self.data.len() }) } @@ -166,10 +166,12 @@ macro_rules! impl_mem_traits { ($($ty:ty, $size:expr),*) => { $( impl MemValue<$size> for $ty { + #[inline(always)] fn from_mem_bytes(bytes: [u8; $size]) -> Self { <$ty>::from_le_bytes(bytes.into()) } + #[inline(always)] fn to_mem_bytes(self) -> [u8; $size] { self.to_le_bytes().into() } diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index c1d20e6c..c175b8e2 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -62,8 +62,12 @@ impl Store { Some(ModuleInstance(self.module_instances.get(addr as usize)?.clone())) } + #[inline] pub(crate) fn get_module_instance_raw(&self, addr: ModuleInstanceAddr) -> &Rc { - &self.module_instances[addr as usize] + match self.module_instances.get(addr as usize) { + Some(instance) => instance, + None => unreachable!("module instance {addr} not found. This should be unreachable"), + } } } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index f3acbf7c..b8843199 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -4,22 +4,26 @@ use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -pub struct MemoryArg([u8; 12]); +#[repr(Rust, packed)] +pub struct MemoryArg { + offset: u64, + mem_addr: MemAddr, +} impl MemoryArg { - pub fn new(offset: u64, mem_addr: MemAddr) -> Self { - let mut bytes = [0; 12]; - bytes[0..8].copy_from_slice(&offset.to_le_bytes()); - bytes[8..12].copy_from_slice(&mem_addr.to_le_bytes()); - Self(bytes) + #[inline] + pub const fn new(offset: u64, mem_addr: MemAddr) -> Self { + Self { offset, mem_addr } } - pub fn offset(&self) -> u64 { - u64::from_le_bytes(self.0[0..8].try_into().expect("invalid offset")) + #[inline] + pub const fn offset(self) -> u64 { + self.offset } - pub fn mem_addr(&self) -> MemAddr { - MemAddr::from_le_bytes(self.0[8..12].try_into().expect("invalid mem_addr")) + #[inline] + pub const fn mem_addr(self) -> MemAddr { + self.mem_addr } } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index ba2cb56c..71128348 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -169,7 +169,8 @@ pub enum ExternVal { } impl ExternVal { - pub fn kind(&self) -> ExternalKind { + #[inline] + pub const fn kind(&self) -> ExternalKind { match self { Self::Func(_) => ExternalKind::Func, Self::Table(_) => ExternalKind::Table, @@ -178,7 +179,8 @@ impl ExternVal { } } - pub fn new(kind: ExternalKind, addr: Addr) -> Self { + #[inline] + pub const fn new(kind: ExternalKind, addr: Addr) -> Self { match kind { ExternalKind::Func => Self::Func(addr), ExternalKind::Table => Self::Table(addr), @@ -217,6 +219,7 @@ pub struct ValueCountsSmall { } impl<'a, T: IntoIterator> From for ValueCounts { + #[inline] fn from(types: T) -> Self { let mut counts = Self::default(); for ty in types { @@ -232,6 +235,7 @@ impl<'a, T: IntoIterator> From for ValueCounts { } impl<'a, T: IntoIterator> From for ValueCountsSmall { + #[inline] fn from(types: T) -> Self { let mut counts = Self::default(); for ty in types { @@ -363,31 +367,42 @@ pub struct MemoryType { } impl MemoryType { - pub fn new(arch: MemoryArch, page_count_initial: u64, page_count_max: Option, page_size: Option) -> Self { + pub const fn new( + arch: MemoryArch, + page_count_initial: u64, + page_count_max: Option, + page_size: Option, + ) -> Self { Self { arch, page_count_initial, page_count_max, page_size } } - pub fn arch(&self) -> MemoryArch { + #[inline] + pub const fn arch(&self) -> MemoryArch { self.arch } - pub fn page_count_initial(&self) -> u64 { + #[inline] + pub const fn page_count_initial(&self) -> u64 { self.page_count_initial } - pub fn page_count_max(&self) -> u64 { - self.page_count_max.unwrap_or_else(|| max_page_count(self.page_size())) + #[inline] + pub const fn page_count_max(&self) -> u64 { + if let Some(page_count_max) = self.page_count_max { page_count_max } else { max_page_count(self.page_size()) } } - pub fn page_size(&self) -> u64 { - self.page_size.unwrap_or(MEM_PAGE_SIZE) + #[inline] + pub const fn page_size(&self) -> u64 { + if let Some(page_size) = self.page_size { page_size } else { MEM_PAGE_SIZE } } - pub fn initial_size(&self) -> u64 { + #[inline] + pub const fn initial_size(&self) -> u64 { self.page_count_initial * self.page_size() } - pub fn max_size(&self) -> u64 { + #[inline] + pub const fn max_size(&self) -> u64 { self.page_count_max() * self.page_size() } } diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index aa9e8c54..7b58948d 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -106,7 +106,7 @@ impl ExternRef { impl WasmValue { #[doc(hidden)] #[inline] - pub fn const_instr(&self) -> ConstInstruction { + pub const fn const_instr(&self) -> ConstInstruction { match self { Self::I32(i) => ConstInstruction::I32Const(*i), Self::I64(i) => ConstInstruction::I64Const(*i), @@ -114,13 +114,13 @@ impl WasmValue { Self::F64(i) => ConstInstruction::F64Const(*i), Self::V128(i) => ConstInstruction::V128Const(*i), Self::RefFunc(i) => ConstInstruction::RefFunc(i.addr()), - Self::RefExtern(_) => unimplemented!("no const_instr for RefExtern"), + Self::RefExtern(i) => ConstInstruction::RefExtern(i.addr()), } } /// Get the default value for a given type. #[inline] - pub fn default_for(ty: ValType) -> Self { + pub const fn default_for(ty: ValType) -> Self { match ty { ValType::I32 => Self::I32(0), ValType::I64 => Self::I64(0), @@ -212,7 +212,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_i32(&self) -> Option { + pub const fn as_i32(&self) -> Option { match self { Self::I32(i) => Some(*i), _ => None, @@ -220,7 +220,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_i64(&self) -> Option { + pub const fn as_i64(&self) -> Option { match self { Self::I64(i) => Some(*i), _ => None, @@ -228,7 +228,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_f32(&self) -> Option { + pub const fn as_f32(&self) -> Option { match self { Self::F32(i) => Some(*i), _ => None, @@ -236,7 +236,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_f64(&self) -> Option { + pub const fn as_f64(&self) -> Option { match self { Self::F64(i) => Some(*i), _ => None, @@ -244,7 +244,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_v128(&self) -> Option { + pub const fn as_v128(&self) -> Option { match self { Self::V128(i) => Some(*i), _ => None, @@ -252,7 +252,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_ref_extern(&self) -> Option { + pub const fn as_ref_extern(&self) -> Option { match self { Self::RefExtern(ref_extern) => Some(*ref_extern), _ => None, @@ -260,7 +260,7 @@ impl WasmValue { } #[doc(hidden)] - pub fn as_ref_func(&self) -> Option { + pub const fn as_ref_func(&self) -> Option { match self { Self::RefFunc(ref_func) => Some(*ref_func), _ => None, @@ -268,9 +268,6 @@ impl WasmValue { } } -#[cold] -fn cold() {} - impl Debug for WasmValue { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { @@ -288,7 +285,7 @@ impl Debug for WasmValue { impl WasmValue { /// Get the type of a [`WasmValue`] #[inline] - pub fn val_type(&self) -> ValType { + pub const fn val_type(&self) -> ValType { match self { Self::I32(_) => ValType::I32, Self::I64(_) => ValType::I64, @@ -323,13 +320,13 @@ pub enum ValType { impl ValType { #[inline] - pub fn default_value(&self) -> WasmValue { + pub const fn default_value(&self) -> WasmValue { WasmValue::default_for(*self) } #[doc(hidden)] #[inline] - pub fn is_simd(&self) -> bool { + pub const fn is_simd(&self) -> bool { matches!(self, Self::V128) } } @@ -354,7 +351,6 @@ macro_rules! impl_conversion_for_wasmvalue { if let WasmValue::$variant(i) = value { Ok(i) } else { - cold(); Err(()) } } diff --git a/examples/rust/build.sh b/examples/rust/build.sh index e1d320f8..567ed563 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -6,8 +6,8 @@ exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" -rust_features="+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" -wasmopt_features="--enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int" +rust_features="+simd128,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" +wasmopt_features="--enable-simd --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int" # ensure out dir exists mkdir -p "$dest_dir" From 55a7ac0afcde53a7911a9b7cef6e6255927361d6 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 31 Mar 2026 21:04:01 +0200 Subject: [PATCH 18/47] chore: add CallSelf/ReturnCallSelf Signed-off-by: Henry --- crates/parser/src/module.rs | 28 +++++++++++++--- crates/tinywasm/benches/fibonacci.rs | 8 ++--- crates/tinywasm/src/interpreter/executor.rs | 36 +++++++++++++++++++++ crates/types/src/instructions.rs | 2 ++ 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index d4981437..18bba667 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,11 +1,11 @@ use crate::log::debug; -use crate::{ParseError, Result, conversion}; +use crate::{conversion, ParseError, Result}; use alloc::string::ToString; use alloc::sync::Arc; use alloc::{format, vec::Vec}; use tinywasm_types::{ - ArcSlice, Data, Element, Export, FuncType, Global, Import, Instruction, MemoryType, TableType, TinyWasmModule, - ValueCounts, ValueCountsSmall, WasmFunction, WasmFunctionData, + ArcSlice, Data, Element, Export, FuncType, Global, Import, ImportKind, Instruction, MemoryType, TableType, + TinyWasmModule, ValueCounts, ValueCountsSmall, WasmFunction, WasmFunctionData, }; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; @@ -31,6 +31,16 @@ pub(crate) struct ModuleReader { } impl ModuleReader { + fn apply_instruction_rewrites(instructions: &mut [Instruction], self_func_addr: u32) { + for instr in instructions.iter_mut() { + if matches!(instr, Instruction::Call(addr) if *addr == self_func_addr) { + *instr = Instruction::CallSelf; + } else if matches!(instr, Instruction::ReturnCall(addr) if *addr == self_func_addr) { + *instr = Instruction::ReturnCallSelf; + } + } + } + pub(crate) fn new() -> Self { Self::default() } @@ -189,14 +199,22 @@ impl ModuleReader { return Err(ParseError::Other("Code and code type address count mismatch".to_string())); } + let imported_func_count = + self.imports.iter().filter(|i| matches!(&i.kind, ImportKind::Function(_))).count() as u32; + let funcs = self .code .into_iter() .zip(self.code_type_addrs) - .map(|((instructions, data, locals), ty_idx)| { + .enumerate() + .map(|(func_idx, ((instructions, data, locals), ty_idx))| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); let params = ValueCountsSmall::from(&ty.params); - WasmFunction { instructions: ArcSlice(instructions), data, locals, params, ty } + let self_func_addr = imported_func_count + func_idx as u32; + let mut instructions = instructions.to_vec(); + Self::apply_instruction_rewrites(&mut instructions, self_func_addr); + + WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } }) .collect::>(); diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index a5f1c726..eea28c48 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -36,11 +36,11 @@ fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> fn criterion_benchmark(c: &mut Criterion) { let module = fibonacci_parse().expect("fibonacci_parse"); - let twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm"); + let _twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm"); - c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); - c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); - c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); + // c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); + // c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); + // c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); c.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60))); c.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26))); } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index d5e6fea0..cb7df971 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -96,8 +96,10 @@ impl<'store> Executor<'store> { Select128 => self.store.stack.values.select::().to_cf()?, SelectRef => self.store.stack.values.select::().to_cf()?, Call(v) => return self.exec_call_direct::(*v), + CallSelf => return self.exec_call_self::(), CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), ReturnCall(v) => return self.exec_call_direct::(*v), + ReturnCallSelf => return self.exec_call_self::(), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), Jump(ip) => { self.cf.instr_ptr = *ip as usize; @@ -717,6 +719,40 @@ impl<'store> Executor<'store> { crate::Function::Host(host_func) => self.exec_call_host(host_func.clone()), } } + + fn exec_call_self(&mut self) -> ControlFlow> { + if !IS_RETURN_CALL && self.store.stack.call_stack.is_full() { + return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + } + + let params = self.func.params; + let locals = self.func.locals; + + if IS_RETURN_CALL { + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, params); + } + + let (locals_base, _stack_base, stack_offset) = match self.store.stack.values.enter_locals(params, locals) { + Ok(v) => v, + Err(Error::Trap(Trap::ValueStackOverflow)) if !IS_RETURN_CALL => { + return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + } + Err(err) => return ControlFlow::Break(Some(err)), + }; + + let new_call_frame = CallFrame::new(self.cf.func_addr, self.cf.module_addr, locals_base, stack_offset); + + if IS_RETURN_CALL { + self.cf = new_call_frame; + } else { + self.cf.incr_instr_ptr(); + self.store.stack.call_stack.push(self.cf).to_cf()?; + self.cf = new_call_frame; + } + + ControlFlow::Continue(()) + } + fn exec_call_indirect( &mut self, type_addr: u32, diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index b8843199..24740121 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -67,8 +67,10 @@ pub enum Instruction { BranchTableTarget(u32), // (landing_pad_ip) Return, Call(FuncAddr), + CallSelf, CallIndirect(TypeAddr, TableAddr), ReturnCall(FuncAddr), + ReturnCallSelf, ReturnCallIndirect(TypeAddr, TableAddr), // > Parametric Instructions From 885c6e98cee6c7b82995882412de0cf382635bd8 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 31 Mar 2026 21:39:19 +0200 Subject: [PATCH 19/47] chore: fix typed_select_multi, cleanup parser Signed-off-by: Henry --- crates/parser/src/module.rs | 2 +- crates/parser/src/visit.rs | 280 +++++++++--------- crates/tinywasm/src/interpreter/executor.rs | 1 + .../src/interpreter/stack/value_stack.rs | 24 ++ crates/types/src/instructions.rs | 3 +- 5 files changed, 161 insertions(+), 149 deletions(-) diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 18bba667..a4afad0d 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,5 +1,5 @@ use crate::log::debug; -use crate::{conversion, ParseError, Result}; +use crate::{ParseError, Result, conversion}; use alloc::string::ToString; use alloc::sync::Arc; use alloc::{format, vec::Vec}; diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 9a707afd..d02eb11b 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -161,16 +161,10 @@ impl FunctionBuilder { } fn stack_base_at_frame(&self, depth: usize) -> StackBase { - let frame = match self.validator.get_control_frame(depth) { - Some(f) => f, - None => return StackBase::default(), - }; - let height = frame.height; - let current = self.validator.operand_stack_height() as usize; - + let Some(frame) = self.validator.get_control_frame(depth) else { return StackBase::default() }; let mut base = StackBase::default(); - for i in 0..height { - let depth_from_top = current - 1 - i; + for i in 0..frame.height { + let depth_from_top = self.validator.operand_stack_height() as usize - 1 - i; if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { match ty { wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, @@ -188,10 +182,6 @@ impl FunctionBuilder { self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); } - fn current_ip(&self) -> u32 { - self.instructions.len() as u32 - } - fn is_unreachable(&self) -> bool { self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) } @@ -231,24 +221,20 @@ impl FunctionBuilder { } } - fn patch_jump(&mut self, jump_ip: usize, target: u32) { + fn patch_jump(&mut self, jump_ip: usize, target: usize) { if let Instruction::Jump(ip) = &mut self.instructions[jump_ip] { - *ip = target; + *ip = target as u32; } } - fn patch_jump_if_zero(&mut self, jump_ip: usize, target: u32) { + fn patch_jump_if_zero(&mut self, jump_ip: usize, target: usize) { if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { - *ip = target; + *ip = target as u32; } } fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { - let mut c32: u16 = 0; - let mut c64: u16 = 0; - let mut c128: u16 = 0; - let mut cref: u16 = 0; - + let (mut c32, mut c64, mut c128, mut cref) = (0, 0, 0, 0); for ty in label_types { match ty { wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, @@ -266,9 +252,8 @@ impl FunctionBuilder { return; } - let frame = match self.validator.get_control_frame(label_depth as usize) { - Some(f) => f, - None => return, + let Some(frame) = self.validator.get_control_frame(label_depth as usize) else { + return; }; let base = self.stack_base_at_frame(label_depth as usize); @@ -299,6 +284,76 @@ impl FunctionBuilder { } } } + + fn emit_branch_jump_or_return(&mut self, depth: u32) { + if let Some(ctx_idx) = self.get_ctx_idx(depth) { + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); + } else { + self.instructions.push(Instruction::Return); + } + } + + fn emit_br_table_pad(&mut self, depth: u32) -> (usize, usize, bool) { + let pad_start = self.instructions.len(); + let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; + let Some(frame) = frame else { + let ip = self.instructions.len(); + self.instructions.push(Instruction::Return); + return (pad_start, ip, true); + }; + + let base = self.stack_base_at_frame(depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + self.emit_dropkeep(base, c32, c64, c128, cref); + + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + (pad_start, jump_ip, false) + } + + fn patch_branch_jump_or_return(&mut self, depth: u32, jump_ip: usize) { + let Some(frame) = self.validator.get_control_frame(depth as usize) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + let Some(ctx_idx) = self.get_ctx_idx(depth) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + + match frame.kind { + FrameKind::Loop => { + if let Instruction::Jump(target) = &mut self.instructions[jump_ip] { + *target = self.ctx_stack[ctx_idx].start_ip as u32; + } + } + _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), + } + } + + fn patch_end_jumps(&mut self, ctx: LoweringCtx, end_ip: usize) { + match ctx.kind { + BlockKind::Block | BlockKind::Loop => { + let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip } else { end_ip }; + for jump_ip in ctx.branch_jumps { + self.patch_jump(jump_ip, target); + } + } + BlockKind::If => { + if let Some((&cond_jump_ip, branch_jumps)) = ctx.branch_jumps.split_first() { + if !ctx.has_else { + self.patch_jump_if_zero(cond_jump_ip, end_ip); + } + for &jump_ip in branch_jumps { + self.patch_jump(jump_ip, end_ip); + } + } + } + } + } } macro_rules! impl_visit_operator { @@ -335,7 +390,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild define_operands! { // basic instructions - visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_return(Return), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -391,6 +446,24 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild }; } + fn visit_return(&mut self) -> Self::Output { + if let Some(instr) = self.instructions.last_mut() { + match instr { + Instruction::Call(addr) => { + *instr = Instruction::ReturnCall(*addr); + return; + } + Instruction::CallIndirect(ty, table) => { + *instr = Instruction::ReturnCallIndirect(*ty, *table); + return; + } + _ => {} + } + } + + self.instructions.push(Instruction::Return); + } + fn visit_local_get(&mut self, idx: u32) -> Self::Output { let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { self.errors.push(crate::ParseError::UnsupportedOperator( @@ -487,7 +560,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_block(&mut self, _blockty: wasmparser::BlockType) -> Self::Output { - let start_ip = self.current_ip() as usize; + let start_ip = self.instructions.len(); self.ctx_stack.push(LoweringCtx { kind: BlockKind::Block, has_else: false, @@ -497,14 +570,14 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_loop(&mut self, _ty: wasmparser::BlockType) -> Self::Output { - let start_ip = self.current_ip() as usize; + let start_ip = self.instructions.len(); self.ctx_stack.push(LoweringCtx { kind: BlockKind::Loop, has_else: false, start_ip, branch_jumps: Vec::new() }); } fn visit_if(&mut self, _ty: wasmparser::BlockType) -> Self::Output { - let cond_jump_ip = self.current_ip() as usize; + let cond_jump_ip = self.instructions.len(); self.instructions.push(Instruction::JumpIfZero(0)); - let start_ip = self.current_ip() as usize; + let start_ip = self.instructions.len(); self.ctx_stack.push(LoweringCtx { kind: BlockKind::If, has_else: false, @@ -514,81 +587,37 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_else(&mut self) -> Self::Output { - let Some(cond_jump_ip) = self - .ctx_stack - .last() - .and_then(|ctx| if matches!(ctx.kind, BlockKind::If) { Some(ctx.branch_jumps[0]) } else { None }) - else { - return; - }; - - let jump_ip = self.current_ip() as usize; - self.instructions.push(Instruction::Jump(0)); - - let after_jump_ip = self.current_ip(); - let Some(ctx) = self.ctx_stack.last_mut() else { - return; + let last_if = self.ctx_stack.last().filter(|ctx| matches!(ctx.kind, BlockKind::If)); + if let Some(cond_jump_ip) = last_if.map(|ctx| ctx.branch_jumps[0]) { + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + if let Some(ctx) = self.ctx_stack.last_mut() { + ctx.has_else = true; + ctx.branch_jumps.push(jump_ip); + self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); + }; }; - ctx.has_else = true; - ctx.branch_jumps.push(jump_ip); - self.patch_jump_if_zero(cond_jump_ip, after_jump_ip); } fn visit_end(&mut self) -> Self::Output { - if self.ctx_stack.is_empty() { + if let Some(ctx) = self.ctx_stack.pop() { + self.patch_end_jumps(ctx, self.instructions.len()); + } else { self.instructions.push(Instruction::Return); - return; - } - - let ctx = self.ctx_stack.pop().unwrap(); - let end_ip = self.current_ip(); - - match ctx.kind { - BlockKind::Block | BlockKind::Loop => { - let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip as u32 } else { end_ip }; - for &jump_ip in &ctx.branch_jumps { - self.patch_jump(jump_ip, target); - } - } - BlockKind::If => { - let cond_jump_ip = ctx.branch_jumps[0]; - if !ctx.has_else { - self.patch_jump_if_zero(cond_jump_ip, end_ip); - } - for &jump_ip in &ctx.branch_jumps[1..] { - self.patch_jump(jump_ip, end_ip); - } - } } } fn visit_br(&mut self, depth: u32) -> Self::Output { self.emit_dropkeep_to_label(depth); - - if let Some(ctx_idx) = self.get_ctx_idx(depth) { - let jump_ip = self.current_ip() as usize; - self.instructions.push(Instruction::Jump(0)); - self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); - } else { - self.instructions.push(Instruction::Return); - } + self.emit_branch_jump_or_return(depth); } fn visit_br_if(&mut self, depth: u32) -> Self::Output { - let cond_jump_ip = self.current_ip() as usize; + let cond_jump_ip = self.instructions.len(); self.instructions.push(Instruction::JumpIfZero(0)); - self.emit_dropkeep_to_label(depth); - - if let Some(ctx_idx) = self.get_ctx_idx(depth) { - let jump_ip = self.current_ip() as usize; - self.instructions.push(Instruction::Jump(0)); - self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); - } else { - self.instructions.push(Instruction::Return); - } - - self.patch_jump_if_zero(cond_jump_ip, self.current_ip()); + self.emit_branch_jump_or_return(depth); + self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); } fn visit_br_table(&mut self, targets: wasmparser::BrTable<'_>) -> Self::Output { @@ -601,20 +630,20 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild let len = ts.len() as u32; let target_depths: Vec = ts; - let header_ip = self.current_ip() as usize; + let header_ip = self.instructions.len(); self.instructions.push(Instruction::BranchTable(0, len)); - let target_table_ip = self.current_ip() as usize; + let target_table_ip = self.instructions.len(); for _ in 0..len { self.instructions.push(Instruction::BranchTableTarget(0)); } - let default_target_ip = self.current_ip() as usize; + let default_target_ip = self.instructions.len(); self.instructions.push(Instruction::BranchTableTarget(0)); let mut seen = alloc::collections::BTreeMap::::new(); struct PadInfo { depth: u32, - pad_start: u32, + pad_start: usize, jump_or_ret_ip: usize, is_return: bool, } @@ -626,70 +655,37 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } seen.insert(depth, pads.len()); - let pad_start = self.current_ip(); - - let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; - let Some(frame) = frame else { - let ip = self.current_ip() as usize; - self.instructions.push(Instruction::Return); - pads.push(PadInfo { depth, pad_start, jump_or_ret_ip: ip, is_return: true }); - continue; - }; - - let base = self.stack_base_at_frame(depth as usize); - let label_types: Vec<_> = self.label_types_for_frame(frame); - let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); - - self.emit_dropkeep(base, c32, c64, c128, cref); - - let jump_ip = self.current_ip() as usize; - self.instructions.push(Instruction::Jump(0)); - pads.push(PadInfo { depth, pad_start, jump_or_ret_ip: jump_ip, is_return: false }); + let (pad_start, jump_or_ret_ip, is_return) = self.emit_br_table_pad(depth); + pads.push(PadInfo { depth, pad_start, jump_or_ret_ip, is_return }); } for (i, &depth) in target_depths.iter().enumerate() { let pad_idx = seen[&depth]; if let Instruction::BranchTableTarget(ip) = &mut self.instructions[target_table_ip + i] { - *ip = pads[pad_idx].pad_start; + *ip = pads[pad_idx].pad_start as u32; } } let default_pad_idx = seen[&default_depth]; if let Instruction::BranchTableTarget(ip) = &mut self.instructions[default_target_ip] { - *ip = pads[default_pad_idx].pad_start; + *ip = pads[default_pad_idx].pad_start as u32; } if let Instruction::BranchTable(default_ip, _) = &mut self.instructions[header_ip] { - *default_ip = pads[default_pad_idx].pad_start; + *default_ip = pads[default_pad_idx].pad_start as u32; } for pad in &pads { if pad.is_return { continue; } - let Some(frame) = self.validator.get_control_frame(pad.depth as usize) else { - self.instructions[pad.jump_or_ret_ip] = Instruction::Return; - continue; - }; - let Some(ctx_idx) = self.get_ctx_idx(pad.depth) else { - self.instructions[pad.jump_or_ret_ip] = Instruction::Return; - continue; - }; - match frame.kind { - FrameKind::Loop => { - if let Instruction::Jump(target) = &mut self.instructions[pad.jump_or_ret_ip] { - *target = self.ctx_stack[ctx_idx].start_ip as u32; - } - } - _ => { - self.ctx_stack[ctx_idx].branch_jumps.push(pad.jump_or_ret_ip); - } - } + self.patch_branch_jump_or_return(pad.depth, pad.jump_or_ret_ip); } } fn visit_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { self.instructions.push(Instruction::CallIndirect(ty, table)); } + fn visit_return_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output { self.instructions.push(Instruction::ReturnCallIndirect(ty, table)); } @@ -715,19 +711,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild self.instructions.push(Instruction::RefIsNull); } - fn visit_typed_select_multi(&mut self, _tys: Vec) -> Self::Output { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Typed select with multiple types is not supported".to_string(), - )); - - // self.instructions.extend(tys.into_iter().map(|ty| match ty { - // wasmparser::ValType::I32 => Instruction::Select32, - // wasmparser::ValType::F32 => Instruction::Select32, - // wasmparser::ValType::I64 => Instruction::Select64, - // wasmparser::ValType::F64 => Instruction::Select64, - // wasmparser::ValType::V128 => Instruction::Select128, - // wasmparser::ValType::Ref(_) => Instruction::SelectRef, - // })); + fn visit_typed_select_multi(&mut self, tys: Vec) -> Self::Output { + let (c32, c64, c128, cref) = Self::label_keep_counts(&tys); + self.instructions.push(Instruction::SelectMulti(tinywasm_types::ValueCountsSmall { c32, c64, c128, cref })); } fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index cb7df971..02e91c5b 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -95,6 +95,7 @@ impl<'store> Executor<'store> { Select64 => self.store.stack.values.select::().to_cf()?, Select128 => self.store.stack.values.select::().to_cf()?, SelectRef => self.store.stack.values.select::().to_cf()?, + SelectMulti(counts) => self.store.stack.values.select_multi(*counts), Call(v) => return self.exec_call_direct::(*v), CallSelf => return self.exec_call_self::(), CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index be79912e..76c4b28e 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -113,6 +113,22 @@ impl Stack { self.len = end; Ok((start, end)) } + + pub(crate) fn select_many(&mut self, count: usize, condition: bool) { + if count == 0 { + return; + } + if self.len < count * 2 { + unreachable!("Stack underflow, this is a bug"); + } + + if !condition { + let start = self.len - (count * 2); + let second_start = self.len - count; + self.data.copy_within(second_start..self.len, start); + } + self.len -= count; + } } impl ValueStack { @@ -162,6 +178,14 @@ impl ValueStack { Ok(()) } + pub(crate) fn select_multi(&mut self, counts: ValueCountsSmall) { + let condition = self.pop::() != 0; + self.stack_32.select_many(counts.c32 as usize, condition); + self.stack_64.select_many(counts.c64 as usize, condition); + self.stack_128.select_many(counts.c128 as usize, condition); + self.stack_ref.select_many(counts.cref as usize, condition); + } + pub(crate) fn binary_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { T::stack_calculate(self, func) } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 24740121..71b518f3 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,4 +1,4 @@ -use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType}; +use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType, ValueCountsSmall}; use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. @@ -79,6 +79,7 @@ pub enum Instruction { Drop64, Select64, Drop128, Select128, DropRef, SelectRef, + SelectMulti(ValueCountsSmall), // > Variable Instructions // See From 0ed3e1d29dca7d27e0d3aa1780f7cacc9481aa46 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 31 Mar 2026 23:43:20 +0200 Subject: [PATCH 20/47] chore: add more superinstructions Signed-off-by: Henry --- CHANGELOG.md | 2 +- crates/parser/src/visit.rs | 131 +++++++++++++++--- crates/tinywasm/benches/argon2id.rs | 8 +- crates/tinywasm/benches/tinywasm.rs | 8 +- crates/tinywasm/src/interpreter/executor.rs | 102 +++++++------- .../src/interpreter/stack/value_stack.rs | 38 +---- crates/tinywasm/src/interpreter/values.rs | 37 +++-- crates/types/src/instructions.rs | 7 +- 8 files changed, 211 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c47dead..7452dbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extern tables now correctly update their type after growing - Increased MSRV to 1.80.0 - Simplify and optimize the interpreter loop -- Use a seperate stack and locals for 32, 64 and 128 bit values and references (#21) +- Use a separate stack and locals for 32, 64 and 128 bit values and references (#21) - Updated to latest `wasmparser` version - Removed benchmarks comparing TinyWasm to other WebAssembly runtimes to reduce build dependencies - Memory and Data Instances are no longer reference counted diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index d02eb11b..b71185ea 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -325,11 +325,7 @@ impl FunctionBuilder { }; match frame.kind { - FrameKind::Loop => { - if let Instruction::Jump(target) = &mut self.instructions[jump_ip] { - *target = self.ctx_stack[ctx_idx].start_ip as u32; - } - } + FrameKind::Loop => self.patch_jump(jump_ip, self.ctx_stack[ctx_idx].start_ip), _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), } } @@ -385,12 +381,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } define_mem_operands! { - visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_i32_store( I32Store), visit_i64_store(I64Store), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32) + visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_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! { // basic instructions - visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -423,6 +419,23 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } } + fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { + let memarg = MemoryArg::new(memarg.offset, memarg.memory); + let len = self.instructions.len(); + if len >= 2 { + let addr = self.instructions[len - 2]; + let value = self.instructions[len - 1]; + if let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = (addr, value) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::I32StoreLocalLocal(memarg, addr_local, value_local)); + return; + } + } + + self.instructions.push(Instruction::I32Store(memarg)); + } + fn visit_drop(&mut self) -> Self::Output { match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { @@ -447,21 +460,67 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_return(&mut self) -> Self::Output { - if let Some(instr) = self.instructions.last_mut() { - match instr { - Instruction::Call(addr) => { - *instr = Instruction::ReturnCall(*addr); - return; - } - Instruction::CallIndirect(ty, table) => { - *instr = Instruction::ReturnCallIndirect(*ty, *table); - return; - } - _ => {} + self.instructions.push(Instruction::Return); + } + + fn visit_i32_add(&mut self) -> Self::Output { + let len = self.instructions.len(); + if len >= 2 { + let lhs = self.instructions[len - 2]; + let rhs = self.instructions[len - 1]; + if let (Instruction::LocalGet32(a), Instruction::LocalGet32(b)) = (lhs, rhs) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::I32AddLocals(a, b)); + return; } } - self.instructions.push(Instruction::Return); + if let Some(Instruction::I32Const(c)) = self.instructions.last().copied() { + self.instructions.pop(); + self.instructions.push(Instruction::I32AddConst(c)); + return; + } + + self.instructions.push(Instruction::I32Add); + } + + fn visit_i64_add(&mut self) -> Self::Output { + let len = self.instructions.len(); + if len >= 2 { + let lhs = self.instructions[len - 2]; + let rhs = self.instructions[len - 1]; + if let (Instruction::LocalGet64(a), Instruction::LocalGet64(b)) = (lhs, rhs) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::I64AddLocals(a, b)); + return; + } + } + + if let Some(Instruction::I64Const(c)) = self.instructions.last().copied() { + self.instructions.pop(); + self.instructions.push(Instruction::I64AddConst(c)); + return; + } + + self.instructions.push(Instruction::I64Add); + } + + fn visit_i64_rotl(&mut self) -> Self::Output { + let len = self.instructions.len(); + if len >= 2 { + let lhs = self.instructions[len - 2]; + let rhs = self.instructions[len - 1]; + if let (Instruction::I64Xor, Instruction::I64Const(c)) = (lhs, rhs) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::I64XorRotlConst(c)); + return; + } + } + + self.instructions.push(Instruction::I64Rotl); } fn visit_local_get(&mut self, idx: u32) -> Self::Output { @@ -544,6 +603,34 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild return; }; + if let Some(Instruction::I64XorRotlConst(c)) = self.instructions.last().copied() { + match self.validator.get_operand_type(0) { + Some(Some(wasmparser::ValType::I64)) | Some(Some(wasmparser::ValType::F64)) => { + self.instructions.pop(); + self.instructions.push(Instruction::I64XorRotlConstTee(c, resolved_idx)); + return; + } + _ => {} + } + } + + let len = self.instructions.len(); + if len >= 2 { + let addr = self.instructions[len - 2]; + let load = self.instructions[len - 1]; + if let (Instruction::LocalGet32(addr_local), Instruction::I32Load(memarg)) = (addr, load) { + match self.validator.get_operand_type(0) { + Some(Some(wasmparser::ValType::I32)) | Some(Some(wasmparser::ValType::F32)) => { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::I32LoadLocalTee(memarg, addr_local, resolved_idx)); + return; + } + _ => {} + } + } + } + match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), @@ -595,6 +682,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild ctx.has_else = true; ctx.branch_jumps.push(jump_ip); self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); + if !matches!(self.instructions.last(), Some(Instruction::Nop)) { + self.instructions.push(Instruction::Nop); + } }; }; } @@ -602,6 +692,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild fn visit_end(&mut self) -> Self::Output { if let Some(ctx) = self.ctx_stack.pop() { self.patch_end_jumps(ctx, self.instructions.len()); + if !matches!(self.instructions.last(), Some(Instruction::Nop)) { + self.instructions.push(Instruction::Nop); + } } else { self.instructions.push(Instruction::Return); } diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index aa8b38b6..ca083788 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -31,11 +31,11 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = argon2id_parse().expect("argon2id_parse"); - let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); + let _twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); - c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); - c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); - c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); + // c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); + // c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); + // c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); c.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone()))); } diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 4848a4a7..a949b52c 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -33,11 +33,11 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); - let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); + let _twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); - c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); - c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + // c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); + // c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); + // c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); c.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 02e91c5b..1da799ed 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -72,6 +72,10 @@ impl<'store> Executor<'store> { (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { self.store.stack.values.binary_diff::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? }; + (local_set_pop $ty:ty, $local_index:expr) => {{ + let val = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.local_set(&self.cf, *$local_index, val); + }}; } let next = match self.func.instructions.0.get(self.cf.instr_ptr) { @@ -166,58 +170,58 @@ impl<'store> Executor<'store> { return ControlFlow::Continue(()); } Return => return self.exec_return(), - LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_32(&self.cf, *local_index)).to_cf()?, - LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_64(&self.cf, *local_index)).to_cf()?, - LocalGet128(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_128(&self.cf, *local_index)).to_cf()?, - LocalGetRef(local_index) => self.store.stack.values.push(self.store.stack.values.local_get_ref(&self.cf, *local_index)).to_cf()?, - LocalSet32(local_index) => { - let val = self.store.stack.values.pop::(); - self.store.stack.values.local_set_32(&self.cf, *local_index, val); - } - LocalSet64(local_index) => { - let val = self.store.stack.values.pop::(); - self.store.stack.values.local_set_64(&self.cf, *local_index, val); - } - LocalSet128(local_index) => { - let val = self.store.stack.values.pop::(); - self.store.stack.values.local_set_128(&self.cf, *local_index, val); - } - LocalSetRef(local_index) => { - let val = self.store.stack.values.pop::(); - self.store.stack.values.local_set_ref(&self.cf, *local_index, val); - } - LocalCopy32(from, to) => { - let val = self.store.stack.values.local_get_32(&self.cf, *from); - self.store.stack.values.local_set_32(&self.cf, *to, val); - } - LocalCopy64(from, to) => { - let val = self.store.stack.values.local_get_64(&self.cf, *from); - self.store.stack.values.local_set_64(&self.cf, *to, val); - } - LocalCopy128(from, to) => { - let val = self.store.stack.values.local_get_128(&self.cf, *from); - self.store.stack.values.local_set_128(&self.cf, *to, val); - } - LocalCopyRef(from, to) => { - let val = self.store.stack.values.local_get_ref(&self.cf, *from); - self.store.stack.values.local_set_ref(&self.cf, *to, val); - } - LocalTee32(local_index) => { - let val = self.store.stack.values.peek::(); - self.store.stack.values.local_set_32(&self.cf, *local_index, val); - } - LocalTee64(local_index) => { - let val = self.store.stack.values.peek::(); - self.store.stack.values.local_set_64(&self.cf, *local_index, val); + LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, + LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, + LocalGet128(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, + LocalGetRef(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, + LocalSet32(local_index) => stack_op!(local_set_pop Value32, local_index), + LocalSet64(local_index) => stack_op!(local_set_pop Value64, local_index), + LocalSet128(local_index) => stack_op!(local_set_pop Value128, local_index), + LocalSetRef(local_index) => stack_op!(local_set_pop ValueRef, local_index), + LocalCopy32(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopy64(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopy128(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopyRef(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + I32AddLocals(a, b) => self.store.stack.values.push( + self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), + ).to_cf()?, + I64AddLocals(a, b) => self.store.stack.values.push( + self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), + ).to_cf()?, + I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), + I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), + I32StoreLocalLocal(m, addr_local, value_local) => { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); + let value = self.store.stack.values.local_get::(&self.cf, *value_local).to_mem_bytes(); + if let Err(e) = mem.store((m.offset() + addr) as usize, value.len(), &value) { + return ControlFlow::Break(Some(e)); + } } - LocalTee128(local_index) => { - let val = self.store.stack.values.peek::(); - self.store.stack.values.local_set_128(&self.cf, *local_index, val); + I32LoadLocalTee(m, addr_local, dst_local) => { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); + let Some(Ok(addr)) = m.offset().checked_add(addr).map(|a| a.try_into()) else { + return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { + offset: addr as usize, + len: 4, + max: 0, + }))); + }; + let value = mem.load_as::<4, i32>(addr).to_cf()?; + self.store.stack.values.local_set(&self.cf, *dst_local, value); + self.store.stack.values.push(value).to_cf()?; } - LocalTeeRef(local_index) => { - let val = self.store.stack.values.peek::(); - self.store.stack.values.local_set_ref(&self.cf, *local_index, val); + I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), + I64XorRotlConstTee(c, local_index) => { + self.store.stack.values.binary_same::(|lhs, rhs| Ok((lhs ^ rhs).rotate_left(*c as u32))).to_cf()?; + let val = self.store.stack.values.peek::(); + self.store.stack.values.local_set(&self.cf, *local_index, val); } + LocalTee32(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), + LocalTee64(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), + LocalTee128(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), + LocalTeeRef(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), GlobalGet(global_index) => self.exec_global_get(*global_index).to_cf()?, GlobalSet32(global_index) => self.exec_global_set::(*global_index), GlobalSet64(global_index) => self.exec_global_set::(*global_index), diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 76c4b28e..b461f7a4 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -268,43 +268,13 @@ impl ValueStack { } #[inline] - pub(crate) fn local_get_32(&self, frame: &CallFrame, index: LocalAddr) -> Value32 { - self.stack_32.get(frame.locals_base.s32 + index as usize) + pub(crate) fn local_get(&self, frame: &CallFrame, index: LocalAddr) -> T { + T::local_get(self, frame, index) } #[inline] - pub(crate) fn local_get_64(&self, frame: &CallFrame, index: LocalAddr) -> Value64 { - self.stack_64.get(frame.locals_base.s64 + index as usize) - } - - #[inline] - pub(crate) fn local_get_128(&self, frame: &CallFrame, index: LocalAddr) -> Value128 { - self.stack_128.get(frame.locals_base.s128 + index as usize) - } - - #[inline] - pub(crate) fn local_get_ref(&self, frame: &CallFrame, index: LocalAddr) -> ValueRef { - self.stack_ref.get(frame.locals_base.sref + index as usize) - } - - #[inline] - pub(crate) fn local_set_32(&mut self, frame: &CallFrame, index: LocalAddr, value: Value32) { - self.stack_32.set(frame.locals_base.s32 + index as usize, value); - } - - #[inline] - pub(crate) fn local_set_64(&mut self, frame: &CallFrame, index: LocalAddr, value: Value64) { - self.stack_64.set(frame.locals_base.s64 + index as usize, value); - } - - #[inline] - pub(crate) fn local_set_128(&mut self, frame: &CallFrame, index: LocalAddr, value: Value128) { - self.stack_128.set(frame.locals_base.s128 + index as usize, value); - } - - #[inline] - pub(crate) fn local_set_ref(&mut self, frame: &CallFrame, index: LocalAddr, value: ValueRef) { - self.stack_ref.set(frame.locals_base.sref + index as usize, value); + pub(crate) fn local_set(&mut self, frame: &CallFrame, index: LocalAddr, value: T) { + T::local_set(self, frame, index, value); } pub(crate) fn push_dyn(&mut self, value: TinyWasmValue) -> Result<()> { diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index b1530e7a..9b6e2c1f 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,6 +1,7 @@ use crate::{Result, interpreter::value128::Value128}; -use super::stack::ValueStack; +use super::stack::{CallFrame, ValueStack}; +use tinywasm_types::LocalAddr; use tinywasm_types::{ExternRef, FuncRef, ValType, WasmValue}; pub(crate) type Value32 = u32; @@ -105,6 +106,12 @@ mod sealed { pub(crate) trait InternalValue: sealed::Sealed + Into { fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; + fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self + where + Self: Sized; + fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) + where + Self: Sized; fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> where Self: Sized; @@ -124,7 +131,7 @@ pub(crate) trait InternalValue: sealed::Sealed + Into { } macro_rules! impl_internalvalue { - ($( $variant:ident, $stack:ident, $internal:ty, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { + ($( $variant:ident, $stack:ident, $stack_base:ident, $outer:ty, $to_internal:expr, $to_outer:expr )*) => { $( impl sealed::Sealed for $outer {} @@ -140,6 +147,16 @@ macro_rules! impl_internalvalue { stack.$stack.push($to_internal(value)) } + #[inline] + fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self { + $to_outer(stack.$stack.get(frame.locals_base.$stack_base + index as usize)) + } + + #[inline] + fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) { + stack.$stack.set(frame.locals_base.$stack_base + index as usize, $to_internal(value)); + } + #[inline] fn stack_pop(stack: &mut ValueStack) -> Self { $to_outer(stack.$stack.pop()) @@ -179,12 +196,12 @@ macro_rules! impl_internalvalue { } impl_internalvalue! { - Value32, stack_32, u32, u32, |v| v, |v| v - Value64, stack_64, u64, u64, |v| v, |v| v - Value32, stack_32, u32, i32, |v: i32| u32::from_ne_bytes(v.to_ne_bytes()), |v: u32| i32::from_ne_bytes(v.to_ne_bytes()) - Value64, stack_64, u64, i64, |v: i64| u64::from_ne_bytes(v.to_ne_bytes()), |v: u64| i64::from_ne_bytes(v.to_ne_bytes()) - Value32, stack_32, u32, f32, f32::to_bits, f32::from_bits - Value64, stack_64, u64, f64, f64::to_bits, f64::from_bits - ValueRef, stack_ref, ValueRef, ValueRef, |v| v, |v| v - Value128, stack_128, Value128, Value128, |v| v, |v| v + Value32, stack_32, s32, u32, |v| v, |v| v + Value64, stack_64, s64, u64, |v| v, |v| v + Value32, stack_32, s32, i32, |v: i32| u32::from_ne_bytes(v.to_ne_bytes()), |v: u32| i32::from_ne_bytes(v.to_ne_bytes()) + Value64, stack_64, s64, i64, |v: i64| u64::from_ne_bytes(v.to_ne_bytes()), |v: u64| i64::from_ne_bytes(v.to_ne_bytes()) + Value32, stack_32, s32, f32, f32::to_bits, f32::from_bits + Value64, stack_64, s64, f64, f64::to_bits, f64::from_bits + ValueRef, stack_ref, sref, ValueRef, |v| v, |v| v + Value128, stack_128, s128, Value128, |v| v, |v| v } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 71b518f3..7f0cef53 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -51,7 +51,12 @@ pub enum ConstInstruction { #[rustfmt::skip] pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), - + I32AddLocals(LocalAddr, LocalAddr), I64AddLocals(LocalAddr, LocalAddr), + I32AddConst(i32), I64AddConst(i64), + I32StoreLocalLocal(MemoryArg, LocalAddr, LocalAddr), + I32LoadLocalTee(MemoryArg, LocalAddr, LocalAddr), + I64XorRotlConst(i64), + I64XorRotlConstTee(i64, LocalAddr), // > Control Instructions (jump-oriented, lowered from structured control during parsing) // See Unreachable, From c3b1f582f44747cb4343b51ffb208334c973c67c Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 1 Apr 2026 20:39:33 +0200 Subject: [PATCH 21/47] chore: cleanup + add wasm intrinsics Signed-off-by: Henry --- .cargo/config.toml | 3 + crates/parser/src/visit.rs | 7 +- crates/tinywasm/benches/argon2id.rs | 2 +- crates/tinywasm/benches/fibonacci.rs | 2 +- crates/tinywasm/benches/tinywasm.rs | 2 +- crates/tinywasm/src/func.rs | 15 +- crates/tinywasm/src/interpreter/executor.rs | 65 +-- .../src/interpreter/stack/call_stack.rs | 44 +- .../src/interpreter/stack/value_stack.rs | 74 +-- crates/tinywasm/src/interpreter/value128.rs | 507 ++++++++++++++---- crates/tinywasm/src/interpreter/values.rs | 51 +- .../loop-boundary-superinstructions.wast | 26 + .../select-multi-reference-types.wast | 17 + crates/types/src/instructions.rs | 28 +- examples/rust/Cargo.toml | 2 +- examples/rust/build.sh | 11 +- examples/wasm-rust.rs | 10 +- 17 files changed, 600 insertions(+), 266 deletions(-) create mode 100644 crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast create mode 100644 crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast diff --git a/.cargo/config.toml b/.cargo/config.toml index e7028026..01909990 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,3 +5,6 @@ test-wasm-2="test --package tinywasm --test test-wasm-2 --release" test-wasm-3="test --package tinywasm --test test-wasm-3 --release" test-wast="test --package tinywasm --test test-wast" test-wasm-custom="test --package tinywasm --test test-wasm-custom --release" + +[target.x86_64-unknown-linux-gnu] +rustflags=["-C", "target-cpu=x86-64-v3"] diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index b71185ea..7eefa1ca 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -657,6 +657,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_loop(&mut self, _ty: wasmparser::BlockType) -> Self::Output { + if !matches!(self.instructions.last(), Some(Instruction::Nop)) { + self.instructions.push(Instruction::Nop); // add nop to ensure that no superinstruction can be merged across block boundaries + } let start_ip = self.instructions.len(); self.ctx_stack.push(LoweringCtx { kind: BlockKind::Loop, has_else: false, start_ip, branch_jumps: Vec::new() }); } @@ -683,7 +686,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild ctx.branch_jumps.push(jump_ip); self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); if !matches!(self.instructions.last(), Some(Instruction::Nop)) { - self.instructions.push(Instruction::Nop); + self.instructions.push(Instruction::Nop); // add nop to ensure that no superinstruction can be merged across block boundaries } }; }; @@ -693,7 +696,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild if let Some(ctx) = self.ctx_stack.pop() { self.patch_end_jumps(ctx, self.instructions.len()); if !matches!(self.instructions.last(), Some(Instruction::Nop)) { - self.instructions.push(Instruction::Nop); + self.instructions.push(Instruction::Nop); // add nop to ensure that no superinstruction can be merged across block boundaries } } else { self.instructions.push(Instruction::Return); diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index ca083788..4521722c 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -3,7 +3,7 @@ use eyre::Result; use tinywasm::{ModuleInstance, Store, types}; use types::TinyWasmModule; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.opt.wasm"); +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/argon2id.wasm"); fn argon2id_parse() -> Result { let parser = tinywasm_parser::Parser::new(); diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index eea28c48..ba1b3188 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -3,7 +3,7 @@ use eyre::Result; use tinywasm::{ModuleInstance, Store, types}; use types::TinyWasmModule; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.opt.wasm"); +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); fn fibonacci_parse() -> Result { let parser = tinywasm_parser::Parser::new(); diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index a949b52c..82a38784 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -3,7 +3,7 @@ use eyre::Result; use tinywasm::{Extern, FuncContext, Imports, ModuleInstance, Store, types}; use types::TinyWasmModule; -const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.opt.wasm"); +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); fn tinywasm_parse() -> Result { let parser = tinywasm_parser::Parser::new(); diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 0e42dd76..69d6dd2b 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -1,6 +1,6 @@ use crate::interpreter::stack::CallFrame; use crate::{Error, FuncContext, InterpreterRuntime, Result, Store}; -use crate::{Function, log, unlikely}; +use crate::{Function, unlikely}; use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec}; use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, ValType, WasmValue}; @@ -34,23 +34,14 @@ impl FuncHandle { } // 5. For each value type and the corresponding value, check if types match - if !(func_ty.params.iter().zip(params).enumerate().all(|(_i, (ty, param))| { - if ty == ¶m.val_type() { - true - } else { - log::error!("param type mismatch at index {_i}: expected {ty:?}, got {param:?}"); - false - } - })) { + if !(func_ty.params.iter().zip(params).all(|(ty, param)| ty == ¶m.val_type())) { return Err(Error::Other("Type mismatch".into())); } let func_inst = store.state.get_func(self.addr); let wasm_func = match &func_inst.func { Function::Host(host_func) => { - let host_func = host_func.clone(); - let ctx = FuncContext { store, module_addr: self.module_addr }; - return host_func.call(ctx, params); + return host_func.clone().call(FuncContext { store, module_addr: self.module_addr }, params); } Function::Wasm(wasm_func) => wasm_func.clone(), }; diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 1da799ed..91234cc8 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -3,7 +3,7 @@ use super::no_std_floats::NoStdFloatExt; use alloc::boxed::Box; -use alloc::{format, rc::Rc, string::ToString}; +use alloc::{rc::Rc, string::ToString}; use core::ops::ControlFlow; use interpreter::stack::CallFrame; @@ -78,7 +78,7 @@ impl<'store> Executor<'store> { }}; } - let next = match self.func.instructions.0.get(self.cf.instr_ptr) { + let next = match self.func.instructions.0.get(self.cf.instr_ptr as usize) { Some(instr) => instr, None => unreachable!( "Instruction pointer out of bounds: {} ({} instructions)", @@ -89,7 +89,7 @@ impl<'store> Executor<'store> { #[rustfmt::skip] match next { - Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} + Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 | BranchTableTarget {..} => {} Unreachable => return ControlFlow::Break(Some(Trap::Unreachable.into())), Drop32 => self.store.stack.values.drop::(), Drop64 => self.store.stack.values.drop::(), @@ -107,66 +107,66 @@ impl<'store> Executor<'store> { ReturnCallSelf => return self.exec_call_self::(), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), Jump(ip) => { - self.cf.instr_ptr = *ip as usize; + self.cf.instr_ptr = *ip; return ControlFlow::Continue(()); } JumpIfZero(ip) => { let cond = self.store.stack.values.pop::(); if cond == 0 { - self.cf.instr_ptr = *ip as usize; + self.cf.instr_ptr = *ip; } else { self.cf.incr_instr_ptr(); } return ControlFlow::Continue(()); } DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { - let b32 = self.cf.stack_base().s32 + *base32 as usize; + let b32 = self.cf.stack_base().s32 + *base32 as u32; let k32 = *keep32 as usize; - self.store.stack.values.stack_32.truncate_keep(b32, k32); - let b64 = self.cf.stack_base().s64 + *base64 as usize; + self.store.stack.values.stack_32.truncate_keep(b32 as usize, k32); + let b64 = self.cf.stack_base().s64 + *base64 as u32; let k64 = *keep64 as usize; - self.store.stack.values.stack_64.truncate_keep(b64, k64); - let b128 = self.cf.stack_base().s128 + *base128 as usize; + self.store.stack.values.stack_64.truncate_keep(b64 as usize, k64); + let b128 = self.cf.stack_base().s128 + *base128 as u32; let k128 = *keep128 as usize; - self.store.stack.values.stack_128.truncate_keep(b128, k128); - let bref = self.cf.stack_base().sref + *base_ref as usize; + self.store.stack.values.stack_128.truncate_keep(b128 as usize, k128); + let bref = self.cf.stack_base().sref + *base_ref as u32; let kref = *keep_ref as usize; - self.store.stack.values.stack_ref.truncate_keep(bref, kref); + self.store.stack.values.stack_ref.truncate_keep(bref as usize, kref); } DropKeep32(base, keep) => { - let b = self.cf.stack_base().s32 + *base as usize; + let b = self.cf.stack_base().s32 + *base as u32; let k = *keep as usize; - self.store.stack.values.stack_32.truncate_keep(b, k); + self.store.stack.values.stack_32.truncate_keep(b as usize, k); } DropKeep64(base, keep) => { - let b = self.cf.stack_base().s64 + *base as usize; + let b = self.cf.stack_base().s64 + *base as u32; let k = *keep as usize; - self.store.stack.values.stack_64.truncate_keep(b, k); + self.store.stack.values.stack_64.truncate_keep(b as usize, k); } DropKeep128(base, keep) => { - let b = self.cf.stack_base().s128 + *base as usize; + let b = self.cf.stack_base().s128 + *base as u32; let k = *keep as usize; - self.store.stack.values.stack_128.truncate_keep(b, k); + self.store.stack.values.stack_128.truncate_keep(b as usize, k); } DropKeepRef(base, keep) => { - let b = self.cf.stack_base().sref + *base as usize; + let b = self.cf.stack_base().sref + *base as u32; let k = *keep as usize; - self.store.stack.values.stack_ref.truncate_keep(b, k); + self.store.stack.values.stack_ref.truncate_keep(b as usize, k); } BranchTable(default_ip, len) => { let idx = self.store.stack.values.pop::(); let start = self.cf.instr_ptr + 1; let target_ip = if idx >= 0 && (idx as u32) < *len { - match self.func.instructions.0.get(start + idx as usize) { + match self.func.instructions.0.get((start + idx as u32) as usize) { Some(Instruction::BranchTableTarget(ip)) => *ip, _ => *default_ip, } } else { *default_ip }; - self.cf.instr_ptr = target_ip as usize; + self.cf.instr_ptr = target_ip; return ControlFlow::Continue(()); } Return => return self.exec_return(), @@ -434,10 +434,10 @@ impl<'store> Executor<'store> { V128Load16x4U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_u(v.to_le_bytes()))?, V128Load32x2S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_s(v.to_le_bytes()))?, V128Load32x2U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_u(v.to_le_bytes()))?, - V128Load8Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i8)?, - V128Load16Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i16)?, - V128Load32Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i32)?, - V128Load64Splat(_arg) => self.exec_mem_load::(_arg.mem_addr(), _arg.offset(), Value128::splat_i64)?, + V128Load8Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i8)?, + V128Load16Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i16)?, + V128Load32Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i32)?, + V128Load64Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i64)?, V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, @@ -655,9 +655,6 @@ impl<'store> Executor<'store> { F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), - - #[allow(unreachable_patterns)] - i => return ControlFlow::Break(Some(Error::UnsupportedFeature(format!("unimplemented opcode: {i:?}")))), }; self.cf.incr_instr_ptr(); @@ -670,10 +667,6 @@ impl<'store> Executor<'store> { func_addr: FuncAddr, owner: ModuleInstanceAddr, ) -> ControlFlow> { - if !IS_RETURN_CALL && self.store.stack.call_stack.is_full() { - return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); - } - if !Rc::ptr_eq(&self.func, &wasm_func) { self.func = wasm_func.clone(); } @@ -726,10 +719,6 @@ impl<'store> Executor<'store> { } fn exec_call_self(&mut self) -> ControlFlow> { - if !IS_RETURN_CALL && self.store.stack.call_stack.is_full() { - return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); - } - let params = self.func.params; let locals = self.func.locals; diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 2409d882..1b8117c5 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -6,47 +6,35 @@ use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCountsSmall}; #[derive(Debug)] pub(crate) struct CallStack { stack: Vec, - len: usize, } impl CallStack { pub(crate) fn new(config: &crate::engine::Config) -> Self { - let mut stack = Vec::with_capacity(config.call_stack_size); - stack.resize_with(config.call_stack_size, CallFrame::default); - Self { stack, len: 0 } + Self { stack: Vec::with_capacity(config.call_stack_size) } } pub(crate) fn clear(&mut self) { - self.len = 0; + self.stack.clear(); } + #[inline(always)] pub(crate) fn pop(&mut self) -> Option { - if self.len == 0 { - return None; - } - - self.len -= 1; - Some(self.stack[self.len]) - } - - pub(crate) fn is_full(&self) -> bool { - self.len >= self.stack.len() + self.stack.pop() } + #[inline(always)] pub(crate) fn push(&mut self, call_frame: CallFrame) -> Result<()> { - if unlikely(self.is_full()) { + if unlikely(self.stack.len() == self.stack.capacity()) { return Err(Trap::CallStackOverflow.into()); } - - self.stack[self.len] = call_frame; - self.len += 1; + self.stack.push(call_frame); Ok(()) } } #[derive(Debug, Clone, Copy, Default)] pub(crate) struct CallFrame { - pub(crate) instr_ptr: usize, + pub(crate) instr_ptr: u32, pub(crate) module_addr: ModuleInstanceAddr, pub(crate) func_addr: FuncAddr, pub(crate) locals_base: StackBase, @@ -55,10 +43,10 @@ pub(crate) struct CallFrame { #[derive(Debug, Clone, Copy, Default)] pub(crate) struct StackBase { - pub(crate) s32: usize, - pub(crate) s64: usize, - pub(crate) s128: usize, - pub(crate) sref: usize, + pub(crate) s32: u32, + pub(crate) s64: u32, + pub(crate) s128: u32, + pub(crate) sref: u32, } impl CallFrame { @@ -74,10 +62,10 @@ impl CallFrame { #[inline] pub(crate) fn stack_base(&self) -> StackBase { StackBase { - s32: self.locals_base.s32 + self.stack_offset.c32 as usize, - s64: self.locals_base.s64 + self.stack_offset.c64 as usize, - s128: self.locals_base.s128 + self.stack_offset.c128 as usize, - sref: self.locals_base.sref + self.stack_offset.cref as usize, + s32: self.locals_base.s32 + self.stack_offset.c32 as u32, + s64: self.locals_base.s64 + self.stack_offset.c64 as u32, + s128: self.locals_base.s128 + self.stack_offset.c128 as u32, + sref: self.locals_base.sref + self.stack_offset.cref as u32, } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index b461f7a4..81a5c5d4 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -2,7 +2,7 @@ use alloc::boxed::Box; use alloc::vec::Vec; use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCounts, ValueCountsSmall, WasmValue}; -use crate::{Result, Trap, engine::Config, interpreter::*}; +use crate::{Result, Trap, engine::Config, interpreter::*, unlikely}; use super::{CallFrame, StackBase}; @@ -15,7 +15,7 @@ pub(crate) struct ValueStack { } #[derive(Debug)] -pub(crate) struct Stack { +pub(crate) struct Stack { data: Box<[T]>, len: usize, } @@ -27,33 +27,30 @@ impl Stack { Self { data: data.into_boxed_slice(), len: 0 } } - pub(crate) fn len(&self) -> usize { - self.len - } - pub(crate) fn clear(&mut self) { self.len = 0; } + #[inline(always)] pub(crate) fn push(&mut self, value: T) -> Result<()> { - if self.len >= self.data.len() { + if unlikely(self.len >= self.data.len()) { return Err(Trap::ValueStackOverflow.into()); } - self.data[self.len] = value; self.len += 1; Ok(()) } + #[inline(always)] pub(crate) fn pop(&mut self) -> T { if self.len == 0 { unreachable!("ValueStack underflow, this is a bug"); } - self.len -= 1; self.data[self.len] } + #[inline(always)] pub(crate) fn last(&self) -> &T { if self.len == 0 { unreachable!("ValueStack underflow, this is a bug"); @@ -61,6 +58,7 @@ impl Stack { &self.data[self.len - 1] } + #[inline(always)] pub(crate) fn last_mut(&mut self) -> &mut T { if self.len == 0 { unreachable!("ValueStack underflow, this is a bug"); @@ -68,6 +66,7 @@ impl Stack { &mut self.data[self.len - 1] } + #[inline(always)] pub(crate) fn get(&self, index: usize) -> T { match self.data.get(index) { Some(v) => *v, @@ -75,6 +74,7 @@ impl Stack { } } + #[inline(always)] pub(crate) fn set(&mut self, index: usize, value: T) { match self.data.get_mut(index) { Some(v) => *v = value, @@ -83,35 +83,34 @@ impl Stack { } pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { - if self.len <= n { + debug_assert!(n <= self.len); + if n >= self.len { return; } - let keep_tail = end_keep.min(self.len - n); - - if keep_tail == 0 { - self.len = n; - return; + let keep = (self.len - n).min(end_keep); + if keep != 0 { + let src = self.len - keep; + self.data.copy_within(src..self.len, n); } - let tail_start = self.len - keep_tail; - self.data.copy_within(tail_start..self.len, n); - self.len = n + keep_tail; + self.len = n + keep; } - pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result<(usize, usize)> { - debug_assert!(param_count <= local_count, "param count exceeds local count"); - let start = - self.len.checked_sub(param_count).unwrap_or_else(|| unreachable!("value stack underflow, this is a bug")); - + pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result<(u32, u32)> { + debug_assert!(param_count <= local_count); + let start = self.len - param_count; let end = start + local_count; - if end > self.data.len() { + + if unlikely(end > self.data.len()) { return Err(Trap::ValueStackOverflow.into()); } - self.data[(start + param_count)..end].fill(T::default()); + let init_start = start + param_count; + self.data[init_start..end].fill(T::default()); self.len = end; - Ok((start, end)) + + Ok((start as u32, end as u32)) } pub(crate) fn select_many(&mut self, count: usize, condition: bool) { @@ -148,26 +147,32 @@ impl ValueStack { self.stack_ref.clear(); } + #[inline(always)] pub(crate) fn len(&self) -> usize { - self.stack_32.len() + self.stack_64.len() + self.stack_128.len() + self.stack_ref.len() + self.stack_32.len + self.stack_64.len + self.stack_128.len + self.stack_ref.len } + #[inline(always)] pub(crate) fn peek(&self) -> T { T::stack_peek(self) } + #[inline(always)] pub(crate) fn pop(&mut self) -> T { T::stack_pop(self) } + #[inline(always)] pub(crate) fn push(&mut self, value: T) -> Result<()> { T::stack_push(self, value) } + #[inline(always)] pub(crate) fn drop(&mut self) { T::stack_pop(self); } + #[inline] pub(crate) fn select(&mut self) -> Result<()> { let cond: i32 = self.pop(); let val2: T = self.pop(); @@ -178,6 +183,7 @@ impl ValueStack { Ok(()) } + #[inline] pub(crate) fn select_multi(&mut self, counts: ValueCountsSmall) { let condition = self.pop::() != 0; self.stack_32.select_many(counts.c32 as usize, condition); @@ -186,14 +192,17 @@ impl ValueStack { self.stack_ref.select_many(counts.cref as usize, condition); } + #[inline] pub(crate) fn binary_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { T::stack_calculate(self, func) } + #[inline] pub(crate) fn ternary_same(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { T::stack_calculate3(self, func) } + #[inline] pub(crate) fn binary( &mut self, func: impl FnOnce(T, T) -> Result, @@ -204,6 +213,7 @@ impl ValueStack { Ok(()) } + #[inline] pub(crate) fn binary_diff( &mut self, func: impl FnOnce(A, B) -> Result, @@ -214,6 +224,7 @@ impl ValueStack { Ok(()) } + #[inline] pub(crate) fn unary( &mut self, func: impl FnOnce(T) -> Result, @@ -223,6 +234,7 @@ impl ValueStack { Ok(()) } + #[inline] pub(crate) fn unary_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { T::replace_top(self, func) } @@ -261,10 +273,10 @@ impl ValueStack { } pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCountsSmall) { - self.stack_32.truncate_keep(base.s32, keep.c32 as usize); - self.stack_64.truncate_keep(base.s64, keep.c64 as usize); - self.stack_128.truncate_keep(base.s128, keep.c128 as usize); - self.stack_ref.truncate_keep(base.sref, keep.cref as usize); + self.stack_32.truncate_keep(base.s32 as usize, keep.c32 as usize); + self.stack_64.truncate_keep(base.s64 as usize, keep.c64 as usize); + self.stack_128.truncate_keep(base.s128 as usize, keep.c128 as usize); + self.stack_ref.truncate_keep(base.sref as usize, keep.cref as usize); } #[inline] diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index edcf50a9..bb2c686a 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -2,148 +2,207 @@ use super::num_helpers::TinywasmFloatExt; #[cfg(not(feature = "std"))] use super::no_std_floats::NoStdFloatExt; +#[cfg(target_arch = "wasm32")] +use core::arch::wasm32 as wasm; +#[cfg(target_arch = "wasm64")] +use core::arch::wasm64 as wasm; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Value128(i128); +#[cfg_attr(any(target_arch = "wasm32", target_arch = "wasm64"), allow(unreachable_code))] impl Value128 { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + #[inline(always)] + fn to_wasm_v128(self) -> wasm::v128 { + let b = self.to_le_bytes(); + wasm::u8x16( + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], + ) + } + + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + #[inline(always)] + fn from_wasm_v128(value: wasm::v128) -> Self { + Self::from_le_bytes([ + wasm::u8x16_extract_lane::<0>(value), + wasm::u8x16_extract_lane::<1>(value), + wasm::u8x16_extract_lane::<2>(value), + wasm::u8x16_extract_lane::<3>(value), + wasm::u8x16_extract_lane::<4>(value), + wasm::u8x16_extract_lane::<5>(value), + wasm::u8x16_extract_lane::<6>(value), + wasm::u8x16_extract_lane::<7>(value), + wasm::u8x16_extract_lane::<8>(value), + wasm::u8x16_extract_lane::<9>(value), + wasm::u8x16_extract_lane::<10>(value), + wasm::u8x16_extract_lane::<11>(value), + wasm::u8x16_extract_lane::<12>(value), + wasm::u8x16_extract_lane::<13>(value), + wasm::u8x16_extract_lane::<14>(value), + wasm::u8x16_extract_lane::<15>(value), + ]) + } + + #[inline] pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { Self(i128::from_le_bytes(bytes)) } + #[inline] pub const fn to_le_bytes(self) -> [u8; 16] { self.0.to_le_bytes() } #[rustfmt::skip] + #[inline] pub const fn as_i8x16(self) -> [i8; 16] { let b = self.to_le_bytes(); [b[0] as i8, b[1] as i8, b[2] as i8, b[3] as i8, b[4] as i8, b[5] as i8, b[6] as i8, b[7] as i8, b[8] as i8, b[9] as i8, b[10] as i8, b[11] as i8, b[12] as i8, b[13] as i8, b[14] as i8, b[15] as i8] } #[rustfmt::skip] + #[inline] pub const fn as_u8x16(self) -> [u8; 16] { self.to_le_bytes() } #[rustfmt::skip] + #[inline] pub const fn from_i8x16(x: [i8; 16]) -> Self { Self::from_le_bytes([x[0] as u8, x[1] as u8, x[2] as u8, x[3] as u8, x[4] as u8, x[5] as u8, x[6] as u8, x[7] as u8, x[8] as u8, x[9] as u8, x[10] as u8, x[11] as u8, x[12] as u8, x[13] as u8, x[14] as u8, x[15] as u8]) } #[rustfmt::skip] + #[inline] pub const fn from_u8x16(x: [u8; 16]) -> Self { Self::from_le_bytes(x) } #[rustfmt::skip] + #[inline] pub const fn as_i16x8(self) -> [i16; 8] { let b = self.to_le_bytes(); [i16::from_le_bytes([b[0], b[1]]), i16::from_le_bytes([b[2], b[3]]), i16::from_le_bytes([b[4], b[5]]), i16::from_le_bytes([b[6], b[7]]), i16::from_le_bytes([b[8], b[9]]), i16::from_le_bytes([b[10], b[11]]), i16::from_le_bytes([b[12], b[13]]), i16::from_le_bytes([b[14], b[15]])] } #[rustfmt::skip] + #[inline] pub const fn as_u16x8(self) -> [u16; 8] { let b = self.to_le_bytes(); [u16::from_le_bytes([b[0], b[1]]), u16::from_le_bytes([b[2], b[3]]), u16::from_le_bytes([b[4], b[5]]), u16::from_le_bytes([b[6], b[7]]), u16::from_le_bytes([b[8], b[9]]), u16::from_le_bytes([b[10], b[11]]), u16::from_le_bytes([b[12], b[13]]), u16::from_le_bytes([b[14], b[15]])] } #[rustfmt::skip] + #[inline] pub const fn from_i16x8(x: [i16; 8]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) } #[rustfmt::skip] + #[inline] pub const fn from_u16x8(x: [u16; 8]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) } #[rustfmt::skip] + #[inline] pub const fn as_i32x4(self) -> [i32; 4] { let b = self.to_le_bytes(); [i32::from_le_bytes([b[0], b[1], b[2], b[3]]), i32::from_le_bytes([b[4], b[5], b[6], b[7]]), i32::from_le_bytes([b[8], b[9], b[10], b[11]]), i32::from_le_bytes([b[12], b[13], b[14], b[15]])] } #[rustfmt::skip] + #[inline] pub const fn as_u32x4(self) -> [u32; 4] { let b = self.to_le_bytes(); [u32::from_le_bytes([b[0], b[1], b[2], b[3]]), u32::from_le_bytes([b[4], b[5], b[6], b[7]]), u32::from_le_bytes([b[8], b[9], b[10], b[11]]), u32::from_le_bytes([b[12], b[13], b[14], b[15]])] } #[rustfmt::skip] + #[inline] pub const fn as_f32x4(self) -> [f32; 4] { let b = self.to_le_bytes(); [f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])), f32::from_bits(u32::from_le_bytes([b[4], b[5], b[6], b[7]])), f32::from_bits(u32::from_le_bytes([b[8], b[9], b[10], b[11]])), f32::from_bits(u32::from_le_bytes([b[12], b[13], b[14], b[15]]))] } #[rustfmt::skip] + #[inline] pub const fn from_i32x4(x: [i32; 4]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) } #[rustfmt::skip] + #[inline] pub const fn from_u32x4(x: [u32; 4]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) } #[rustfmt::skip] + #[inline] pub const fn from_f32x4(x: [f32; 4]) -> Self { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[2].to_bits().to_le_bytes()[0], x[2].to_bits().to_le_bytes()[1], x[2].to_bits().to_le_bytes()[2], x[2].to_bits().to_le_bytes()[3], x[3].to_bits().to_le_bytes()[0], x[3].to_bits().to_le_bytes()[1], x[3].to_bits().to_le_bytes()[2], x[3].to_bits().to_le_bytes()[3]]) } #[rustfmt::skip] + #[inline] pub const fn as_i64x2(self) -> [i64; 2] { let b = self.to_le_bytes(); [i64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), i64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] } #[rustfmt::skip] + #[inline] pub const fn as_u64x2(self) -> [u64; 2] { let b = self.to_le_bytes(); [u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] } #[rustfmt::skip] + #[inline] pub const fn as_f64x2(self) -> [f64; 2] { let b = self.to_le_bytes(); [f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])), f64::from_bits(u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]))] } #[rustfmt::skip] + #[inline] pub const fn from_i64x2(x: [i64; 2]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) } #[rustfmt::skip] + #[inline] pub const fn from_u64x2(x: [u64; 2]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) } #[rustfmt::skip] + #[inline] pub const fn from_f64x2(x: [f64; 2]) -> Self { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) } - #[inline(always)] + #[inline] fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { let lanes = self.as_f32x4(); Self::from_f32x4([op(lanes[0]), op(lanes[1]), op(lanes[2]), op(lanes[3])]) } - #[inline(always)] + #[inline] fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); Self::from_f32x4([op(a[0], b[0]), op(a[1], b[1]), op(a[2], b[2]), op(a[3], b[3])]) } - #[inline(always)] + #[inline] fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { let lanes = self.as_f64x2(); Self::from_f64x2([op(lanes[0]), op(lanes[1])]) } - #[inline(always)] + #[inline] fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); @@ -163,41 +222,69 @@ impl Value128 { } #[doc(alias = "v128.any_true")] - pub const fn v128_any_true(self) -> bool { + pub fn v128_any_true(self) -> bool { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return wasm::v128_any_true(self.to_wasm_v128()); + } self.reduce_or() != 0 } #[doc(alias = "v128.not")] - pub const fn v128_not(self) -> Self { + pub fn v128_not(self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::v128_not(self.to_wasm_v128())); + } Self(!self.0) } #[doc(alias = "v128.and")] - pub const fn v128_and(self, rhs: Self) -> Self { + pub fn v128_and(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::v128_and(self.to_wasm_v128(), rhs.to_wasm_v128())); + } Self(self.0 & rhs.0) } #[doc(alias = "v128.andnot")] - pub const fn v128_andnot(self, rhs: Self) -> Self { + pub fn v128_andnot(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::v128_andnot(self.to_wasm_v128(), rhs.to_wasm_v128())); + } Self(self.0 & !rhs.0) } #[doc(alias = "v128.or")] - pub const fn v128_or(self, rhs: Self) -> Self { + pub fn v128_or(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::v128_or(self.to_wasm_v128(), rhs.to_wasm_v128())); + } Self(self.0 | rhs.0) } #[doc(alias = "v128.xor")] - pub const fn v128_xor(self, rhs: Self) -> Self { + pub fn v128_xor(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::v128_xor(self.to_wasm_v128(), rhs.to_wasm_v128())); + } Self(self.0 ^ rhs.0) } #[doc(alias = "v128.bitselect")] - pub const fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { + pub fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::v128_bitselect(v1.to_wasm_v128(), v2.to_wasm_v128(), c.to_wasm_v128())); + } Self((v1.0 & c.0) | (v2.0 & !c.0)) } - pub const fn swizzle(self, s: Self) -> Self { + pub fn swizzle(self, s: Self) -> Self { self.i8x16_swizzle(s) } @@ -266,7 +353,11 @@ impl Value128 { } #[doc(alias = "i8x16.swizzle")] - pub const fn i8x16_swizzle(self, s: Self) -> Self { + pub fn i8x16_swizzle(self, s: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); + } let a_bytes = self.to_le_bytes(); let s_bytes = s.to_le_bytes(); let mut result_bytes = [0u8; 16]; @@ -280,15 +371,13 @@ impl Value128 { } #[doc(alias = "i8x16.shuffle")] - pub const fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { + pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { let a_bytes = a.to_le_bytes(); let b_bytes = b.to_le_bytes(); let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 16 { - let index = idx[i] as usize; + for i in 0..16 { + let index = (idx[i] & 31) as usize; result_bytes[i] = if index < 16 { a_bytes[index] } else { b_bytes[index - 16] }; - i += 1; } Self::from_le_bytes(result_bytes) } @@ -689,7 +778,11 @@ impl Value128 { } #[doc(alias = "i8x16.add")] - pub const fn i8x16_add(self, rhs: Self) -> Self { + pub fn i8x16_add(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_add(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -702,7 +795,11 @@ impl Value128 { } #[doc(alias = "i16x8.add")] - pub const fn i16x8_add(self, rhs: Self) -> Self { + pub fn i16x8_add(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_add(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -715,7 +812,11 @@ impl Value128 { } #[doc(alias = "i32x4.add")] - pub const fn i32x4_add(self, rhs: Self) -> Self { + pub fn i32x4_add(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_add(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -728,7 +829,11 @@ impl Value128 { } #[doc(alias = "i64x2.add")] - pub const fn i64x2_add(self, rhs: Self) -> Self { + pub fn i64x2_add(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_add(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -741,7 +846,11 @@ impl Value128 { } #[doc(alias = "i8x16.sub")] - pub const fn i8x16_sub(self, rhs: Self) -> Self { + pub fn i8x16_sub(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -754,7 +863,11 @@ impl Value128 { } #[doc(alias = "i16x8.sub")] - pub const fn i16x8_sub(self, rhs: Self) -> Self { + pub fn i16x8_sub(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -767,7 +880,11 @@ impl Value128 { } #[doc(alias = "i32x4.sub")] - pub const fn i32x4_sub(self, rhs: Self) -> Self { + pub fn i32x4_sub(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -780,7 +897,11 @@ impl Value128 { } #[doc(alias = "i64x2.sub")] - pub const fn i64x2_sub(self, rhs: Self) -> Self { + pub fn i64x2_sub(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -793,7 +914,11 @@ impl Value128 { } #[doc(alias = "i16x8.mul")] - pub const fn i16x8_mul(self, rhs: Self) -> Self { + pub fn i16x8_mul(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_mul(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -806,7 +931,11 @@ impl Value128 { } #[doc(alias = "i32x4.mul")] - pub const fn i32x4_mul(self, rhs: Self) -> Self { + pub fn i32x4_mul(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_mul(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -819,7 +948,11 @@ impl Value128 { } #[doc(alias = "i64x2.mul")] - pub const fn i64x2_mul(self, rhs: Self) -> Self { + pub fn i64x2_mul(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_mul(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -832,7 +965,11 @@ impl Value128 { } #[doc(alias = "i8x16.add_sat_s")] - pub const fn i8x16_add_sat_s(self, rhs: Self) -> Self { + pub fn i8x16_add_sat_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -845,7 +982,11 @@ impl Value128 { } #[doc(alias = "i16x8.add_sat_s")] - pub const fn i16x8_add_sat_s(self, rhs: Self) -> Self { + pub fn i16x8_add_sat_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -858,7 +999,11 @@ impl Value128 { } #[doc(alias = "i8x16.add_sat_u")] - pub const fn i8x16_add_sat_u(self, rhs: Self) -> Self { + pub fn i8x16_add_sat_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0u8; 16]; @@ -871,7 +1016,11 @@ impl Value128 { } #[doc(alias = "i16x8.add_sat_u")] - pub const fn i16x8_add_sat_u(self, rhs: Self) -> Self { + pub fn i16x8_add_sat_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0u16; 8]; @@ -884,7 +1033,11 @@ impl Value128 { } #[doc(alias = "i8x16.sub_sat_s")] - pub const fn i8x16_sub_sat_s(self, rhs: Self) -> Self { + pub fn i8x16_sub_sat_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -897,7 +1050,11 @@ impl Value128 { } #[doc(alias = "i16x8.sub_sat_s")] - pub const fn i16x8_sub_sat_s(self, rhs: Self) -> Self { + pub fn i16x8_sub_sat_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -910,7 +1067,11 @@ impl Value128 { } #[doc(alias = "i8x16.sub_sat_u")] - pub const fn i8x16_sub_sat_u(self, rhs: Self) -> Self { + pub fn i8x16_sub_sat_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0u8; 16]; @@ -923,7 +1084,11 @@ impl Value128 { } #[doc(alias = "i16x8.sub_sat_u")] - pub const fn i16x8_sub_sat_u(self, rhs: Self) -> Self { + pub fn i16x8_sub_sat_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0u16; 8]; @@ -936,7 +1101,11 @@ impl Value128 { } #[doc(alias = "i8x16.avgr_u")] - pub const fn i8x16_avgr_u(self, rhs: Self) -> Self { + pub fn i8x16_avgr_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_avgr(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0u8; 16]; @@ -949,7 +1118,11 @@ impl Value128 { } #[doc(alias = "i16x8.avgr_u")] - pub const fn i16x8_avgr_u(self, rhs: Self) -> Self { + pub fn i16x8_avgr_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_avgr(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0u16; 8]; @@ -1366,7 +1539,11 @@ impl Value128 { } #[doc(alias = "i8x16.eq")] - pub const fn i8x16_eq(self, rhs: Self) -> Self { + pub fn i8x16_eq(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -1379,7 +1556,11 @@ impl Value128 { } #[doc(alias = "i16x8.eq")] - pub const fn i16x8_eq(self, rhs: Self) -> Self { + pub fn i16x8_eq(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -1392,7 +1573,11 @@ impl Value128 { } #[doc(alias = "i32x4.eq")] - pub const fn i32x4_eq(self, rhs: Self) -> Self { + pub fn i32x4_eq(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -1405,7 +1590,11 @@ impl Value128 { } #[doc(alias = "i64x2.eq")] - pub const fn i64x2_eq(self, rhs: Self) -> Self { + pub fn i64x2_eq(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -1418,7 +1607,11 @@ impl Value128 { } #[doc(alias = "i8x16.ne")] - pub const fn i8x16_ne(self, rhs: Self) -> Self { + pub fn i8x16_ne(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -1431,7 +1624,11 @@ impl Value128 { } #[doc(alias = "i16x8.ne")] - pub const fn i16x8_ne(self, rhs: Self) -> Self { + pub fn i16x8_ne(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -1444,7 +1641,11 @@ impl Value128 { } #[doc(alias = "i32x4.ne")] - pub const fn i32x4_ne(self, rhs: Self) -> Self { + pub fn i32x4_ne(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -1457,7 +1658,11 @@ impl Value128 { } #[doc(alias = "i64x2.ne")] - pub const fn i64x2_ne(self, rhs: Self) -> Self { + pub fn i64x2_ne(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -1470,7 +1675,11 @@ impl Value128 { } #[doc(alias = "i8x16.lt_s")] - pub const fn i8x16_lt_s(self, rhs: Self) -> Self { + pub fn i8x16_lt_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -1483,7 +1692,11 @@ impl Value128 { } #[doc(alias = "i16x8.lt_s")] - pub const fn i16x8_lt_s(self, rhs: Self) -> Self { + pub fn i16x8_lt_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -1496,7 +1709,11 @@ impl Value128 { } #[doc(alias = "i32x4.lt_s")] - pub const fn i32x4_lt_s(self, rhs: Self) -> Self { + pub fn i32x4_lt_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -1509,7 +1726,11 @@ impl Value128 { } #[doc(alias = "i64x2.lt_s")] - pub const fn i64x2_lt_s(self, rhs: Self) -> Self { + pub fn i64x2_lt_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -1522,7 +1743,11 @@ impl Value128 { } #[doc(alias = "i8x16.lt_u")] - pub const fn i8x16_lt_u(self, rhs: Self) -> Self { + pub fn i8x16_lt_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0i8; 16]; @@ -1535,7 +1760,11 @@ impl Value128 { } #[doc(alias = "i16x8.lt_u")] - pub const fn i16x8_lt_u(self, rhs: Self) -> Self { + pub fn i16x8_lt_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0i16; 8]; @@ -1548,7 +1777,11 @@ impl Value128 { } #[doc(alias = "i32x4.lt_u")] - pub const fn i32x4_lt_u(self, rhs: Self) -> Self { + pub fn i32x4_lt_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u32x4_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u32x4(); let b = rhs.as_u32x4(); let mut out = [0i32; 4]; @@ -1561,77 +1794,81 @@ impl Value128 { } #[doc(alias = "i8x16.gt_s")] - pub const fn i8x16_gt_s(self, rhs: Self) -> Self { + pub fn i8x16_gt_s(self, rhs: Self) -> Self { rhs.i8x16_lt_s(self) } #[doc(alias = "i16x8.gt_s")] - pub const fn i16x8_gt_s(self, rhs: Self) -> Self { + pub fn i16x8_gt_s(self, rhs: Self) -> Self { rhs.i16x8_lt_s(self) } #[doc(alias = "i32x4.gt_s")] - pub const fn i32x4_gt_s(self, rhs: Self) -> Self { + pub fn i32x4_gt_s(self, rhs: Self) -> Self { rhs.i32x4_lt_s(self) } #[doc(alias = "i64x2.gt_s")] - pub const fn i64x2_gt_s(self, rhs: Self) -> Self { + pub fn i64x2_gt_s(self, rhs: Self) -> Self { rhs.i64x2_lt_s(self) } #[doc(alias = "i8x16.gt_u")] - pub const fn i8x16_gt_u(self, rhs: Self) -> Self { + pub fn i8x16_gt_u(self, rhs: Self) -> Self { rhs.i8x16_lt_u(self) } #[doc(alias = "i16x8.gt_u")] - pub const fn i16x8_gt_u(self, rhs: Self) -> Self { + pub fn i16x8_gt_u(self, rhs: Self) -> Self { rhs.i16x8_lt_u(self) } #[doc(alias = "i32x4.gt_u")] - pub const fn i32x4_gt_u(self, rhs: Self) -> Self { + pub fn i32x4_gt_u(self, rhs: Self) -> Self { rhs.i32x4_lt_u(self) } #[doc(alias = "i8x16.le_s")] - pub const fn i8x16_le_s(self, rhs: Self) -> Self { + pub fn i8x16_le_s(self, rhs: Self) -> Self { rhs.i8x16_ge_s(self) } #[doc(alias = "i16x8.le_s")] - pub const fn i16x8_le_s(self, rhs: Self) -> Self { + pub fn i16x8_le_s(self, rhs: Self) -> Self { rhs.i16x8_ge_s(self) } #[doc(alias = "i32x4.le_s")] - pub const fn i32x4_le_s(self, rhs: Self) -> Self { + pub fn i32x4_le_s(self, rhs: Self) -> Self { rhs.i32x4_ge_s(self) } #[doc(alias = "i64x2.le_s")] - pub const fn i64x2_le_s(self, rhs: Self) -> Self { + pub fn i64x2_le_s(self, rhs: Self) -> Self { rhs.i64x2_ge_s(self) } #[doc(alias = "i8x16.le_u")] - pub const fn i8x16_le_u(self, rhs: Self) -> Self { + pub fn i8x16_le_u(self, rhs: Self) -> Self { rhs.i8x16_ge_u(self) } #[doc(alias = "i16x8.le_u")] - pub const fn i16x8_le_u(self, rhs: Self) -> Self { + pub fn i16x8_le_u(self, rhs: Self) -> Self { rhs.i16x8_ge_u(self) } #[doc(alias = "i32x4.le_u")] - pub const fn i32x4_le_u(self, rhs: Self) -> Self { + pub fn i32x4_le_u(self, rhs: Self) -> Self { rhs.i32x4_ge_u(self) } #[doc(alias = "i8x16.ge_s")] - pub const fn i8x16_ge_s(self, rhs: Self) -> Self { + pub fn i8x16_ge_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -1644,7 +1881,11 @@ impl Value128 { } #[doc(alias = "i16x8.ge_s")] - pub const fn i16x8_ge_s(self, rhs: Self) -> Self { + pub fn i16x8_ge_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -1657,7 +1898,11 @@ impl Value128 { } #[doc(alias = "i32x4.ge_s")] - pub const fn i32x4_ge_s(self, rhs: Self) -> Self { + pub fn i32x4_ge_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -1670,7 +1915,11 @@ impl Value128 { } #[doc(alias = "i64x2.ge_s")] - pub const fn i64x2_ge_s(self, rhs: Self) -> Self { + pub fn i64x2_ge_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i64x2(); let b = rhs.as_i64x2(); let mut out = [0i64; 2]; @@ -1683,7 +1932,11 @@ impl Value128 { } #[doc(alias = "i8x16.ge_u")] - pub const fn i8x16_ge_u(self, rhs: Self) -> Self { + pub fn i8x16_ge_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0i8; 16]; @@ -1696,7 +1949,11 @@ impl Value128 { } #[doc(alias = "i16x8.ge_u")] - pub const fn i16x8_ge_u(self, rhs: Self) -> Self { + pub fn i16x8_ge_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0i16; 8]; @@ -1709,7 +1966,11 @@ impl Value128 { } #[doc(alias = "i32x4.ge_u")] - pub const fn i32x4_ge_u(self, rhs: Self) -> Self { + pub fn i32x4_ge_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u32x4_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u32x4(); let b = rhs.as_u32x4(); let mut out = [0i32; 4]; @@ -1770,7 +2031,11 @@ impl Value128 { } #[doc(alias = "i8x16.neg")] - pub const fn i8x16_neg(self) -> Self { + pub fn i8x16_neg(self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_neg(self.to_wasm_v128())); + } let a = self.as_i8x16(); let mut out = [0i8; 16]; let mut i = 0; @@ -1782,7 +2047,11 @@ impl Value128 { } #[doc(alias = "i16x8.neg")] - pub const fn i16x8_neg(self) -> Self { + pub fn i16x8_neg(self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_neg(self.to_wasm_v128())); + } let a = self.as_i16x8(); let mut out = [0i16; 8]; let mut i = 0; @@ -1794,7 +2063,11 @@ impl Value128 { } #[doc(alias = "i32x4.neg")] - pub const fn i32x4_neg(self) -> Self { + pub fn i32x4_neg(self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_neg(self.to_wasm_v128())); + } let a = self.as_i32x4(); let mut out = [0i32; 4]; let mut i = 0; @@ -1806,7 +2079,11 @@ impl Value128 { } #[doc(alias = "i64x2.neg")] - pub const fn i64x2_neg(self) -> Self { + pub fn i64x2_neg(self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i64x2_neg(self.to_wasm_v128())); + } let a = self.as_i64x2(); let mut out = [0i64; 2]; let mut i = 0; @@ -1818,7 +2095,11 @@ impl Value128 { } #[doc(alias = "i8x16.min_s")] - pub const fn i8x16_min_s(self, rhs: Self) -> Self { + pub fn i8x16_min_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_min(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -1831,7 +2112,11 @@ impl Value128 { } #[doc(alias = "i16x8.min_s")] - pub const fn i16x8_min_s(self, rhs: Self) -> Self { + pub fn i16x8_min_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_min(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -1844,7 +2129,11 @@ impl Value128 { } #[doc(alias = "i32x4.min_s")] - pub const fn i32x4_min_s(self, rhs: Self) -> Self { + pub fn i32x4_min_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_min(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -1857,7 +2146,11 @@ impl Value128 { } #[doc(alias = "i8x16.min_u")] - pub const fn i8x16_min_u(self, rhs: Self) -> Self { + pub fn i8x16_min_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_min(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0u8; 16]; @@ -1870,7 +2163,11 @@ impl Value128 { } #[doc(alias = "i16x8.min_u")] - pub const fn i16x8_min_u(self, rhs: Self) -> Self { + pub fn i16x8_min_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_min(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0u16; 8]; @@ -1883,7 +2180,11 @@ impl Value128 { } #[doc(alias = "i32x4.min_u")] - pub const fn i32x4_min_u(self, rhs: Self) -> Self { + pub fn i32x4_min_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u32x4_min(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u32x4(); let b = rhs.as_u32x4(); let mut out = [0u32; 4]; @@ -1896,7 +2197,11 @@ impl Value128 { } #[doc(alias = "i8x16.max_s")] - pub const fn i8x16_max_s(self, rhs: Self) -> Self { + pub fn i8x16_max_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i8x16_max(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i8x16(); let b = rhs.as_i8x16(); let mut out = [0i8; 16]; @@ -1909,7 +2214,11 @@ impl Value128 { } #[doc(alias = "i16x8.max_s")] - pub const fn i16x8_max_s(self, rhs: Self) -> Self { + pub fn i16x8_max_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i16x8_max(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; @@ -1922,7 +2231,11 @@ impl Value128 { } #[doc(alias = "i32x4.max_s")] - pub const fn i32x4_max_s(self, rhs: Self) -> Self { + pub fn i32x4_max_s(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::i32x4_max(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_i32x4(); let b = rhs.as_i32x4(); let mut out = [0i32; 4]; @@ -1935,7 +2248,11 @@ impl Value128 { } #[doc(alias = "i8x16.max_u")] - pub const fn i8x16_max_u(self, rhs: Self) -> Self { + pub fn i8x16_max_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u8x16_max(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u8x16(); let b = rhs.as_u8x16(); let mut out = [0u8; 16]; @@ -1948,7 +2265,11 @@ impl Value128 { } #[doc(alias = "i16x8.max_u")] - pub const fn i16x8_max_u(self, rhs: Self) -> Self { + pub fn i16x8_max_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u16x8_max(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u16x8(); let b = rhs.as_u16x8(); let mut out = [0u16; 8]; @@ -1961,7 +2282,11 @@ impl Value128 { } #[doc(alias = "i32x4.max_u")] - pub const fn i32x4_max_u(self, rhs: Self) -> Self { + pub fn i32x4_max_u(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + return Self::from_wasm_v128(wasm::u32x4_max(self.to_wasm_v128(), rhs.to_wasm_v128())); + } let a = self.as_u32x4(); let b = rhs.as_u32x4(); let mut out = [0u32; 4]; diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 9b6e2c1f..b57008c4 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -104,30 +104,15 @@ mod sealed { pub trait Sealed {} } -pub(crate) trait InternalValue: sealed::Sealed + Into { +pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + Default { fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; - fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self - where - Self: Sized; - fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) - where - Self: Sized; - fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> - where - Self: Sized; - fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> - where - Self: Sized; - fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> - where - Self: Sized; - - fn stack_pop(stack: &mut ValueStack) -> Self - where - Self: Sized; - fn stack_peek(stack: &ValueStack) -> Self - where - Self: Sized; + fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self; + fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); + fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()>; + fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()>; + fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()>; + fn stack_pop(stack: &mut ValueStack) -> Self; + fn stack_peek(stack: &ValueStack) -> Self; } macro_rules! impl_internalvalue { @@ -142,32 +127,32 @@ macro_rules! impl_internalvalue { } impl InternalValue for $outer { - #[inline] + #[inline(always)] fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()> { stack.$stack.push($to_internal(value)) } - #[inline] + #[inline(always)] fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self { - $to_outer(stack.$stack.get(frame.locals_base.$stack_base + index as usize)) + $to_outer(stack.$stack.get(frame.locals_base.$stack_base as usize + index as usize)) } - #[inline] + #[inline(always)] fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) { - stack.$stack.set(frame.locals_base.$stack_base + index as usize, $to_internal(value)); + stack.$stack.set(frame.locals_base.$stack_base as usize + index as usize, $to_internal(value)); } - #[inline] + #[inline(always)] fn stack_pop(stack: &mut ValueStack) -> Self { $to_outer(stack.$stack.pop()) } - #[inline] + #[inline(always)] fn stack_peek(stack: &ValueStack) -> Self { $to_outer(*stack.$stack.last()) } - #[inline] + #[inline(always)] fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); @@ -175,7 +160,7 @@ macro_rules! impl_internalvalue { Ok(()) } - #[inline] + #[inline(always)] fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { let v3 = stack.$stack.pop(); let v2 = stack.$stack.pop(); @@ -184,7 +169,7 @@ macro_rules! impl_internalvalue { Ok(()) } - #[inline] + #[inline(always)] fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { let v = stack.$stack.last_mut(); *v = $to_internal(func($to_outer(*v))?); diff --git a/crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast b/crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast new file mode 100644 index 00000000..86471636 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/loop-boundary-superinstructions.wast @@ -0,0 +1,26 @@ +(module + (func (export "i32-add-locals-across-loop-boundary") (param i32) (result i32) + (block (result i32) + (local.get 0) + (loop (param i32) (result i32) + (local.get 0) + (i32.add) + (br 1) + ) + ) + ) + + (func (export "i64-add-locals-across-loop-boundary") (param i64) (result i64) + (block (result i64) + (local.get 0) + (loop (param i64) (result i64) + (local.get 0) + (i64.add) + (br 1) + ) + ) + ) +) + +(assert_return (invoke "i32-add-locals-across-loop-boundary" (i32.const 7)) (i32.const 14)) +(assert_return (invoke "i64-add-locals-across-loop-boundary" (i64.const 9)) (i64.const 18)) diff --git a/crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast b/crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast new file mode 100644 index 00000000..6f55b152 --- /dev/null +++ b/crates/tinywasm/tests/wasm-custom/select-multi-reference-types.wast @@ -0,0 +1,17 @@ +(module + (table 1 funcref) + (func $f) + (elem (i32.const 0) func $f) + + (func (export "select-funcref") (param i32) (result i32) + (ref.null func) + (i32.const 0) + (table.get 0) + (local.get 0) + (select (result funcref)) + (ref.is_null) + ) +) + +(assert_return (invoke "select-funcref" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "select-funcref" (i32.const 0)) (i32.const 0)) diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 7f0cef53..29c9cab7 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -234,18 +234,18 @@ pub enum Instruction { F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4, // > Relaxed SIMD - I8x16RelaxedSwizzle, - I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, - I32x4RelaxedTruncF64x2SZero, I32x4RelaxedTruncF64x2UZero, - F32x4RelaxedMadd, F32x4RelaxedNmadd, - F64x2RelaxedMadd, F64x2RelaxedNmadd, - I8x16RelaxedLaneselect, - I16x8RelaxedLaneselect, - I32x4RelaxedLaneselect, - I64x2RelaxedLaneselect, - F32x4RelaxedMin, F32x4RelaxedMax, - F64x2RelaxedMin, F64x2RelaxedMax, - I16x8RelaxedQ15mulrS, - I16x8RelaxedDotI8x16I7x16S, - I32x4RelaxedDotI8x16I7x16AddS + // I8x16RelaxedSwizzle, + // I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, + // I32x4RelaxedTruncF64x2SZero, I32x4RelaxedTruncF64x2UZero, + // F32x4RelaxedMadd, F32x4RelaxedNmadd, + // F64x2RelaxedMadd, F64x2RelaxedNmadd, + // I8x16RelaxedLaneselect, + // I16x8RelaxedLaneselect, + // I32x4RelaxedLaneselect, + // I64x2RelaxedLaneselect, + // F32x4RelaxedMin, F32x4RelaxedMax, + // F64x2RelaxedMin, F64x2RelaxedMax, + // I16x8RelaxedQ15mulrS, + // I16x8RelaxedDotI8x16I7x16S, + // I32x4RelaxedDotI8x16I7x16AddS } diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index da238ad6..430bcadc 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -48,7 +48,7 @@ path="src/argon2id.rs" [profile.wasm] opt-level=3 -lto="thin" +lto="fat" codegen-units=1 panic="abort" inherits="release" diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 567ed563..9df54478 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -2,12 +2,11 @@ cd "$(dirname "$0")" || exit bins=("host_fn" "hello" "fibonacci" "print" "tinywasm" "argon2id") -exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" -rust_features="+simd128,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" -wasmopt_features="--enable-simd --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int" +rust_features="+sign-ext,+simd128,+reference-types,+bulk-memory,+bulk-memory-opt,+multimemory,+call-indirect-overlong,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint,+extended-const,+tail-call" +# wasmopt_features="--enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int" # ensure out dir exists mkdir -p "$dest_dir" @@ -20,9 +19,5 @@ for bin in "${bins[@]}"; do RUSTFLAGS="-C target-feature=$rust_features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" - wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 $wasmopt_features - - if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then - wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" - fi + # wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 $wasmopt_features done diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 82d5c932..4d8cdf4f 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -72,7 +72,7 @@ fn main() -> Result<()> { } fn tinywasm() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/tinywasm.opt.wasm")?; + let module = Module::parse_file("./examples/rust/out/tinywasm.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -102,7 +102,7 @@ fn tinywasm_no_std() -> Result<()> { } fn hello() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/hello.opt.wasm")?; + let module = Module::parse_file("./examples/rust/out/hello.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -151,7 +151,7 @@ fn host_fn() -> Result<()> { } fn printi32() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/print.opt.wasm")?; + let module = Module::parse_file("./examples/rust/out/print.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -172,7 +172,7 @@ fn printi32() -> Result<()> { } fn fibonacci() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/fibonacci.opt.wasm")?; + let module = Module::parse_file("./examples/rust/out/fibonacci.wasm")?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; @@ -185,7 +185,7 @@ fn fibonacci() -> Result<()> { } fn argon2id() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/argon2id.opt.wasm")?; + let module = Module::parse_file("./examples/rust/out/argon2id.wasm")?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; From bfeefda25407d29a843d2aec713943843f8f1239 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 2 Apr 2026 00:54:43 +0200 Subject: [PATCH 22/47] chore: clean up interpreter Signed-off-by: Henry --- crates/tinywasm/src/error.rs | 1 + crates/tinywasm/src/interpreter/executor.rs | 162 +- .../tinywasm/src/interpreter/num_helpers.rs | 2 +- .../src/interpreter/stack/call_stack.rs | 1 + .../src/interpreter/stack/value_stack.rs | 58 +- crates/tinywasm/src/interpreter/value128.rs | 2995 ++++------------- crates/tinywasm/src/interpreter/values.rs | 24 +- crates/tinywasm/src/store/memory.rs | 6 +- .../tests/host_func_signature_check.rs | 103 +- examples/funcref_callbacks.rs | 71 +- 10 files changed, 859 insertions(+), 2564 deletions(-) diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 106f9d1f..24912e39 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -268,6 +268,7 @@ pub(crate) trait Controlify { } impl Controlify for Result { + #[inline(always)] fn to_cf(self) -> ControlFlow, T> { match self { Ok(value) => ControlFlow::Continue(value), diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 91234cc8..34571201 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -14,7 +14,6 @@ use super::values::*; use crate::instance::ModuleInstanceInner; use crate::interpreter::Value128; use crate::*; - pub(crate) struct Executor<'store> { cf: CallFrame, func: Rc, @@ -45,37 +44,24 @@ impl<'store> Executor<'store> { use tinywasm_types::Instruction::*; macro_rules! stack_op { - (simd_unary $method:ident) => { - self.store.stack.values.unary_same::(|v| Ok(v.$method())).to_cf()? - }; - (simd_binary $method:ident) => { - self.store.stack.values.binary_same::(|a, b| Ok(a.$method(b))).to_cf()? - }; - (unary $ty:ty, |$v:ident| $expr:expr) => { - self.store.stack.values.unary_same::<$ty>(|$v| Ok($expr)).to_cf()? - }; - (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { - self.store.stack.values.binary_same::<$ty>(|$a, $b| Ok($expr)).to_cf()? - }; - (binary_try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { - self.store.stack.values.binary_same::<$ty>(|$a, $b| $expr).to_cf()? - }; - (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { - self.store.stack.values.unary::<$from, $to>(|$v| Ok($expr)).to_cf()? - }; - (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { - self.store.stack.values.binary::<$from, $to>(|$a, $b| Ok($expr)).to_cf()? - }; - (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { - self.store.stack.values.binary_diff::<$a, $b, $b>(|$lhs, $rhs| Ok($expr)).to_cf()? - }; - (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { - self.store.stack.values.binary_diff::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? - }; + (simd_unary $method:ident) => { stack_op!(unary Value128, |v| v.$method()) }; + (simd_binary $method:ident) => { stack_op!(binary Value128, |a, b| a.$method(b)) }; + (unary $ty:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary::<$ty>(|$v| Ok($expr)).to_cf()? }; + (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| Ok($expr)).to_cf()? }; + (binary try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| $expr).to_cf()? }; + (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary_into::<$from, $to>(|$v| Ok($expr)).to_cf()? }; + (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary_into::<$from, $to>(|$a, $b| Ok($expr)).to_cf()? }; + (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { stack_op!(binary $a, $b => $b, |$lhs, $rhs| $expr) }; + (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { self.store.stack.values.binary_mixed::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? }; + (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => { self.store.stack.values.ternary::<$ty>(|$a, $b, $c| Ok($expr)).to_cf()? }; (local_set_pop $ty:ty, $local_index:expr) => {{ let val = self.store.stack.values.pop::<$ty>(); self.store.stack.values.local_set(&self.cf, *$local_index, val); }}; + (local_tee $ty:ty, $local_index:expr) => {{ + let val = self.store.stack.values.peek::<$ty>(); + self.store.stack.values.local_set(&self.cf, *$local_index, val); + }}; } let next = match self.func.instructions.0.get(self.cf.instr_ptr as usize) { @@ -89,7 +75,7 @@ impl<'store> Executor<'store> { #[rustfmt::skip] match next { - Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 | BranchTableTarget {..} => {} + Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} Unreachable => return ControlFlow::Break(Some(Trap::Unreachable.into())), Drop32 => self.store.stack.values.drop::(), Drop64 => self.store.stack.values.drop::(), @@ -106,20 +92,8 @@ impl<'store> Executor<'store> { ReturnCall(v) => return self.exec_call_direct::(*v), ReturnCallSelf => return self.exec_call_self::(), ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - Jump(ip) => { - self.cf.instr_ptr = *ip; - return ControlFlow::Continue(()); - } - JumpIfZero(ip) => { - let cond = self.store.stack.values.pop::(); - - if cond == 0 { - self.cf.instr_ptr = *ip; - } else { - self.cf.incr_instr_ptr(); - } - return ControlFlow::Continue(()); - } + Jump(ip) => return self.exec_jump(*ip), + JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { return ControlFlow::Continue(()); }, DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { let b32 = self.cf.stack_base().s32 + *base32 as u32; let k32 = *keep32 as usize; @@ -154,21 +128,8 @@ impl<'store> Executor<'store> { let k = *keep as usize; self.store.stack.values.stack_ref.truncate_keep(b as usize, k); } - BranchTable(default_ip, len) => { - let idx = self.store.stack.values.pop::(); - let start = self.cf.instr_ptr + 1; - - let target_ip = if idx >= 0 && (idx as u32) < *len { - match self.func.instructions.0.get((start + idx as u32) as usize) { - Some(Instruction::BranchTableTarget(ip)) => *ip, - _ => *default_ip, - } - } else { - *default_ip - }; - self.cf.instr_ptr = target_ip; - return ControlFlow::Continue(()); - } + BranchTable(default_ip, len) => return self.exec_branch_table(*default_ip, *len), + BranchTableTarget {..} => {}, Return => return self.exec_return(), LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, @@ -214,14 +175,13 @@ impl<'store> Executor<'store> { } I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), I64XorRotlConstTee(c, local_index) => { - self.store.stack.values.binary_same::(|lhs, rhs| Ok((lhs ^ rhs).rotate_left(*c as u32))).to_cf()?; - let val = self.store.stack.values.peek::(); - self.store.stack.values.local_set(&self.cf, *local_index, val); + stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)); + stack_op!(local_tee i64, local_index); } - LocalTee32(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), - LocalTee64(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), - LocalTee128(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), - LocalTeeRef(local_index) => self.store.stack.values.local_set(&self.cf, *local_index, self.store.stack.values.peek::()), + LocalTee32(local_index) => stack_op!(local_tee Value32, local_index), + LocalTee64(local_index) => stack_op!(local_tee Value64, local_index), + LocalTee128(local_index) => stack_op!(local_tee Value128, local_index), + LocalTeeRef(local_index) => stack_op!(local_tee ValueRef, local_index), GlobalGet(global_index) => self.exec_global_get(*global_index).to_cf()?, GlobalSet32(global_index) => self.exec_global_set::(*global_index), GlobalSet64(global_index) => self.exec_global_set::(*global_index), @@ -279,14 +239,14 @@ impl<'store> Executor<'store> { I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), F32Mul => stack_op!(binary f32, |a, b| a * b), F64Mul => stack_op!(binary f64, |a, b| a * b), - I32DivS => stack_op!(binary_try i32, |a, b| a.wasm_checked_div(b)), - I64DivS => stack_op!(binary_try i64, |a, b| a.wasm_checked_div(b)), - I32DivU => stack_op!(binary_try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), - I64DivU => stack_op!(binary_try u64, |a, b| a.checked_div(b).ok_or_else(trap_0)), - I32RemS => stack_op!(binary_try i32, |a, b| a.checked_wrapping_rem(b)), - I64RemS => stack_op!(binary_try i64, |a, b| a.checked_wrapping_rem(b)), - I32RemU => stack_op!(binary_try u32, |a, b| a.checked_wrapping_rem(b)), - I64RemU => stack_op!(binary_try u64, |a, b| a.checked_wrapping_rem(b)), + I32DivS => stack_op!(binary try i32, |a, b| a.wasm_checked_div(b)), + I64DivS => stack_op!(binary try i64, |a, b| a.wasm_checked_div(b)), + I32DivU => stack_op!(binary try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I64DivU => stack_op!(binary try u64, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I32RemS => stack_op!(binary try i32, |a, b| a.checked_wrapping_rem(b)), + I64RemS => stack_op!(binary try i64, |a, b| a.checked_wrapping_rem(b)), + I32RemU => stack_op!(binary try u32, |a, b| a.checked_wrapping_rem(b)), + I64RemU => stack_op!(binary try u64, |a, b| a.checked_wrapping_rem(b)), I32And => stack_op!(binary i32, |a, b| a & b), I64And => stack_op!(binary i64, |a, b| a & b), I32Or => stack_op!(binary i32, |a, b| a | b), @@ -424,7 +384,7 @@ impl<'store> Executor<'store> { V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), - V128Bitselect => self.store.stack.values.ternary_same::(|v1, v2, c| Ok(Value128::v128_bitselect(v1, v2, c))).to_cf()?, + V128Bitselect => stack_op!(ternary Value128, |v1, v2, c| Value128::v128_bitselect(v1, v2, c)), V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, @@ -661,6 +621,39 @@ impl<'store> Executor<'store> { ControlFlow::Continue(()) } + #[inline(always)] + fn exec_jump(&mut self, ip: u32) -> ControlFlow> { + self.cf.instr_ptr = ip; + ControlFlow::Continue(()) + } + + #[inline(always)] + fn exec_jump_if_zero(&mut self, ip: u32) -> bool { + if self.store.stack.values.pop::() == 0 { + self.cf.instr_ptr = ip; + return true; + } + false + } + + #[inline(always)] + fn exec_branch_table(&mut self, default_ip: u32, len: u32) -> ControlFlow> { + let idx = self.store.stack.values.pop::(); + let start = self.cf.instr_ptr + 1; + + let target_ip = if idx >= 0 && (idx as u32) < len { + match self.func.instructions.0.get((start + idx as u32) as usize) { + Some(Instruction::BranchTableTarget(ip)) => *ip, + _ => default_ip, + } + } else { + default_ip + }; + + self.cf.instr_ptr = target_ip; + ControlFlow::Continue(()) + } + fn exec_call( &mut self, wasm_func: Rc, @@ -943,6 +936,7 @@ impl<'store> Executor<'store> { ControlFlow::Continue(()) } + #[inline(always)] fn exec_mem_load, const LOAD_SIZE: usize, TARGET: InternalValue>( &mut self, mem_addr: tinywasm_types::MemAddr, @@ -951,18 +945,28 @@ impl<'store> Executor<'store> { ) -> ControlFlow> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); - let addr = match mem.is_64bit() { - true => self.store.stack.values.pop::() as u64, - false => u64::from(self.store.stack.values.pop::() as u32), + let base: u64 = if mem.is_64bit() { + self.store.stack.values.pop::() as u64 + } else { + self.store.stack.values.pop::() as u32 as u64 }; - let Some(Ok(addr)) = offset.checked_add(addr).map(|a| a.try_into()) else { + let Some(addr) = base.checked_add(offset) else { return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: addr as usize, + offset: base as usize, len: LOAD_SIZE, max: 0, }))); }; + + let Ok(addr) = usize::try_from(addr) else { + return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { + offset: base as usize, + len: LOAD_SIZE, + max: 0, + }))); + }; + let val = mem.load_as::(addr).to_cf()?; self.store.stack.values.push(cast(val)).to_cf()?; ControlFlow::Continue(()) diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index f800575b..cf13260e 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -35,7 +35,7 @@ macro_rules! checked_conv_float { .store .stack .values - .unary::<$from, $to>(|v| { + .unary_into::<$from, $to>(|v| { let (min, max) = float_min_max!($from, $intermediate); if unlikely(v.is_nan()) { return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 1b8117c5..133f9106 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -69,6 +69,7 @@ impl CallFrame { } } + #[inline(always)] pub(crate) fn incr_instr_ptr(&mut self) { self.instr_ptr += 1; } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 81a5c5d4..7fa0502d 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -172,7 +172,7 @@ impl ValueStack { T::stack_pop(self); } - #[inline] + #[inline(always)] pub(crate) fn select(&mut self) -> Result<()> { let cond: i32 = self.pop(); let val2: T = self.pop(); @@ -192,51 +192,51 @@ impl ValueStack { self.stack_ref.select_many(counts.cref as usize, condition); } - #[inline] - pub(crate) fn binary_same(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { - T::stack_calculate(self, func) - } - - #[inline] - pub(crate) fn ternary_same(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { - T::stack_calculate3(self, func) + #[inline(always)] + pub(crate) fn unary(&mut self, func: impl FnOnce(T) -> Result) -> Result<()> { + T::stack_apply1(self, func) } - #[inline] - pub(crate) fn binary( + #[inline(always)] + pub(crate) fn unary_into( &mut self, - func: impl FnOnce(T, T) -> Result, + func: impl FnOnce(IN) -> Result, ) -> Result<()> { - let v2 = T::stack_pop(self); - let v1 = T::stack_pop(self); - U::stack_push(self, func(v1, v2)?)?; + let v = IN::stack_pop(self); + OUT::stack_push(self, func(v)?)?; Ok(()) } - #[inline] - pub(crate) fn binary_diff( + #[inline(always)] + pub(crate) fn binary(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { + T::stack_apply2(self, func) + } + + #[inline(always)] + pub(crate) fn binary_into( &mut self, - func: impl FnOnce(A, B) -> Result, + func: impl FnOnce(IN, IN) -> Result, ) -> Result<()> { - let v2 = B::stack_pop(self); - let v1 = A::stack_pop(self); - RES::stack_push(self, func(v1, v2)?)?; + let rhs = IN::stack_pop(self); + let lhs = IN::stack_pop(self); + OUT::stack_push(self, func(lhs, rhs)?)?; Ok(()) } - #[inline] - pub(crate) fn unary( + #[inline(always)] + pub(crate) fn binary_mixed( &mut self, - func: impl FnOnce(T) -> Result, + func: impl FnOnce(A, B) -> Result, ) -> Result<()> { - let v1 = T::stack_pop(self); - U::stack_push(self, func(v1)?)?; + let rhs = B::stack_pop(self); + let lhs = A::stack_pop(self); + OUT::stack_push(self, func(lhs, rhs)?)?; Ok(()) } - #[inline] - pub(crate) fn unary_same(&mut self, func: impl Fn(T) -> Result) -> Result<()> { - T::replace_top(self, func) + #[inline(always)] + pub(crate) fn ternary(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { + T::stack_apply3(self, func) } pub(crate) fn pop_types<'a>( diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index bb2c686a..7de4dcaf 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -8,8 +8,281 @@ use core::arch::wasm32 as wasm; use core::arch::wasm64 as wasm; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +/// A 128-bit SIMD value pub struct Value128(i128); +impl From for i128 { + fn from(val: Value128) -> Self { + val.0 + } +} + +impl From for Value128 { + fn from(value: i128) -> Self { + Self(value) + } +} + +macro_rules! simd_wrapping_binop { + ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); + + let a = self.$as_lanes(); + let b = rhs.$as_lanes(); + Self::$from_lanes(core::array::from_fn(|i| a[i].$op(b[i]))) + } + }; +} + +macro_rules! simd_sat_binop { + ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); + + let a = self.$as_lanes(); + let b = rhs.$as_lanes(); + Self::$from_lanes(core::array::from_fn(|i| a[i].$op(b[i]))) + } + }; +} + +macro_rules! simd_all_true { + ($name:ident, $doc:literal, $as_lanes:ident, $count:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self) -> bool { + let lanes = self.$as_lanes(); + let mut i = 0; + while i < $count { + if lanes[i] == 0 { + return false; + } + i += 1; + } + true + } + }; +} + +macro_rules! simd_bitmask { + ($name:ident, $doc:literal, $as_lanes:ident, $count:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self) -> u32 { + let lanes = self.$as_lanes(); + let mut mask = 0u32; + let mut i = 0; + while i < $count { + mask |= ((lanes[i] < 0) as u32) << i; + i += 1; + } + mask + } + }; +} + +macro_rules! simd_shift_left { + ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self, shift: u32) -> Self { + let lanes = self.$as_lanes(); + let s = shift & $mask; + let mut out = [0 as $lane_ty; $count]; + let mut i = 0; + while i < $count { + out[i] = lanes[i].wrapping_shl(s); + i += 1; + } + Self::$from_lanes(out) + } + }; +} + +macro_rules! simd_shift_right { + ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self, shift: u32) -> Self { + let lanes = self.$as_lanes(); + let s = shift & $mask; + let mut out = [0 as $lane_ty; $count]; + let mut i = 0; + while i < $count { + out[i] = lanes[i] >> s; + i += 1; + } + Self::$from_lanes(out) + } + }; +} + +macro_rules! simd_avgr_u { + ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $wide_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); + + let a = self.$as_lanes(); + let b = rhs.$as_lanes(); + Self::$from_lanes(core::array::from_fn(|i| ((a[i] as $wide_ty + b[i] as $wide_ty + 1) >> 1) as $lane_ty)) + } + }; +} + +macro_rules! simd_extend_cast { + ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self) -> Self { + let lanes = self.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + let mut i = 0; + while i < $dst_count { + out[i] = lanes[i + $offset] as $dst_ty; + i += 1; + } + Self::$dst_from(out) + } + }; +} + +macro_rules! simd_extmul_signed { + ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self, rhs: Self) -> Self { + let a = self.$src_as(); + let b = rhs.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + let mut i = 0; + while i < $dst_count { + out[i] = (a[i + $offset] as $dst_ty).wrapping_mul(b[i + $offset] as $dst_ty); + i += 1; + } + Self::$dst_from(out) + } + }; +} + +macro_rules! simd_extmul_unsigned { + ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { + #[doc(alias = $doc)] + pub const fn $name(self, rhs: Self) -> Self { + let a = self.$src_as(); + let b = rhs.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + let mut i = 0; + while i < $dst_count { + out[i] = (a[i + $offset] as $dst_ty) * (b[i + $offset] as $dst_ty); + i += 1; + } + Self::$dst_from(out) + } + }; +} + +macro_rules! simd_cmp_mask { + ($name:ident, $doc:literal, $wasm_op:ident, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); + + let a = self.$as_lanes(); + let b = rhs.$as_lanes(); + Self::$from_lanes(core::array::from_fn(|i| if a[i] $cmp b[i] { -1 } else { 0 })) + } + }; +} + +macro_rules! simd_cmp_mask_const { + ($name:ident, $doc:literal, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { + #[doc(alias = $doc)] + pub const fn $name(self, rhs: Self) -> Self { + let a = self.$as_lanes(); + let b = rhs.$as_lanes(); + let mut out = [0 as $out_ty; $count]; + let mut i = 0; + while i < $count { + out[i] = if a[i] $cmp b[i] { -1 } else { 0 }; + i += 1; + } + Self::$from_lanes(out) + } + }; +} + +macro_rules! simd_cmp_delegate { + ($name:ident, $doc:literal, $delegate:ident) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + rhs.$delegate(self) + } + }; +} + +macro_rules! simd_abs_const { + ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { + #[doc(alias = $doc)] + pub const fn $name(self) -> Self { + let a = self.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + let mut i = 0; + while i < $count { + out[i] = a[i].wrapping_abs(); + i += 1; + } + Self::$from_lanes(out) + } + }; +} + +macro_rules! simd_neg { + ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { + #[doc(alias = $doc)] + pub fn $name(self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128())); + + let a = self.$as_lanes(); + Self::$from_lanes(core::array::from_fn(|i| a[i].wrapping_neg())) + } + }; +} + +macro_rules! simd_minmax { + ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); + + let a = self.$as_lanes(); + let b = rhs.$as_lanes(); + Self::$from_lanes(core::array::from_fn(|i| if a[i] $cmp b[i] { a[i] } else { b[i] })) + } + }; +} + +macro_rules! simd_float_unary { + ($name:ident, $doc:literal, $map:ident, $op:expr) => { + #[doc(alias = $doc)] + pub fn $name(self) -> Self { + self.$map($op) + } + }; +} + +macro_rules! simd_float_binary { + ($name:ident, $doc:literal, $zip:ident, $op:expr) => { + #[doc(alias = $doc)] + pub fn $name(self, rhs: Self) -> Self { + self.$zip(rhs, $op) + } + }; +} + #[cfg_attr(any(target_arch = "wasm32", target_arch = "wasm64"), allow(unreachable_code))] impl Value128 { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] @@ -24,24 +297,8 @@ impl Value128 { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] #[inline(always)] fn from_wasm_v128(value: wasm::v128) -> Self { - Self::from_le_bytes([ - wasm::u8x16_extract_lane::<0>(value), - wasm::u8x16_extract_lane::<1>(value), - wasm::u8x16_extract_lane::<2>(value), - wasm::u8x16_extract_lane::<3>(value), - wasm::u8x16_extract_lane::<4>(value), - wasm::u8x16_extract_lane::<5>(value), - wasm::u8x16_extract_lane::<6>(value), - wasm::u8x16_extract_lane::<7>(value), - wasm::u8x16_extract_lane::<8>(value), - wasm::u8x16_extract_lane::<9>(value), - wasm::u8x16_extract_lane::<10>(value), - wasm::u8x16_extract_lane::<11>(value), - wasm::u8x16_extract_lane::<12>(value), - wasm::u8x16_extract_lane::<13>(value), - wasm::u8x16_extract_lane::<14>(value), - wasm::u8x16_extract_lane::<15>(value), - ]) + #[rustfmt::skip] + Self::from_le_bytes([ wasm::u8x16_extract_lane::<0>(value), wasm::u8x16_extract_lane::<1>(value), wasm::u8x16_extract_lane::<2>(value), wasm::u8x16_extract_lane::<3>(value), wasm::u8x16_extract_lane::<4>(value), wasm::u8x16_extract_lane::<5>(value), wasm::u8x16_extract_lane::<6>(value), wasm::u8x16_extract_lane::<7>(value), wasm::u8x16_extract_lane::<8>(value), wasm::u8x16_extract_lane::<9>(value), wasm::u8x16_extract_lane::<10>(value), wasm::u8x16_extract_lane::<11>(value), wasm::u8x16_extract_lane::<12>(value), wasm::u8x16_extract_lane::<13>(value), wasm::u8x16_extract_lane::<14>(value), wasm::u8x16_extract_lane::<15>(value)]) } #[inline] @@ -54,163 +311,163 @@ impl Value128 { self.0.to_le_bytes() } - #[rustfmt::skip] #[inline] - pub const fn as_i8x16(self) -> [i8; 16] { + #[rustfmt::skip] + const fn as_i8x16(self) -> [i8; 16] { let b = self.to_le_bytes(); [b[0] as i8, b[1] as i8, b[2] as i8, b[3] as i8, b[4] as i8, b[5] as i8, b[6] as i8, b[7] as i8, b[8] as i8, b[9] as i8, b[10] as i8, b[11] as i8, b[12] as i8, b[13] as i8, b[14] as i8, b[15] as i8] } - #[rustfmt::skip] #[inline] - pub const fn as_u8x16(self) -> [u8; 16] { + #[rustfmt::skip] + const fn as_u8x16(self) -> [u8; 16] { self.to_le_bytes() } - #[rustfmt::skip] #[inline] - pub const fn from_i8x16(x: [i8; 16]) -> Self { + #[rustfmt::skip] + const fn from_i8x16(x: [i8; 16]) -> Self { Self::from_le_bytes([x[0] as u8, x[1] as u8, x[2] as u8, x[3] as u8, x[4] as u8, x[5] as u8, x[6] as u8, x[7] as u8, x[8] as u8, x[9] as u8, x[10] as u8, x[11] as u8, x[12] as u8, x[13] as u8, x[14] as u8, x[15] as u8]) } - #[rustfmt::skip] #[inline] - pub const fn from_u8x16(x: [u8; 16]) -> Self { + #[rustfmt::skip] + const fn from_u8x16(x: [u8; 16]) -> Self { Self::from_le_bytes(x) } - #[rustfmt::skip] #[inline] - pub const fn as_i16x8(self) -> [i16; 8] { + #[rustfmt::skip] + const fn as_i16x8(self) -> [i16; 8] { let b = self.to_le_bytes(); [i16::from_le_bytes([b[0], b[1]]), i16::from_le_bytes([b[2], b[3]]), i16::from_le_bytes([b[4], b[5]]), i16::from_le_bytes([b[6], b[7]]), i16::from_le_bytes([b[8], b[9]]), i16::from_le_bytes([b[10], b[11]]), i16::from_le_bytes([b[12], b[13]]), i16::from_le_bytes([b[14], b[15]])] } - #[rustfmt::skip] #[inline] - pub const fn as_u16x8(self) -> [u16; 8] { + #[rustfmt::skip] + const fn as_u16x8(self) -> [u16; 8] { let b = self.to_le_bytes(); [u16::from_le_bytes([b[0], b[1]]), u16::from_le_bytes([b[2], b[3]]), u16::from_le_bytes([b[4], b[5]]), u16::from_le_bytes([b[6], b[7]]), u16::from_le_bytes([b[8], b[9]]), u16::from_le_bytes([b[10], b[11]]), u16::from_le_bytes([b[12], b[13]]), u16::from_le_bytes([b[14], b[15]])] } - #[rustfmt::skip] #[inline] - pub const fn from_i16x8(x: [i16; 8]) -> Self { + #[rustfmt::skip] + const fn from_i16x8(x: [i16; 8]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) } - #[rustfmt::skip] #[inline] - pub const fn from_u16x8(x: [u16; 8]) -> Self { + #[rustfmt::skip] + const fn from_u16x8(x: [u16; 8]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) } - #[rustfmt::skip] #[inline] - pub const fn as_i32x4(self) -> [i32; 4] { + #[rustfmt::skip] + const fn as_i32x4(self) -> [i32; 4] { let b = self.to_le_bytes(); [i32::from_le_bytes([b[0], b[1], b[2], b[3]]), i32::from_le_bytes([b[4], b[5], b[6], b[7]]), i32::from_le_bytes([b[8], b[9], b[10], b[11]]), i32::from_le_bytes([b[12], b[13], b[14], b[15]])] } - #[rustfmt::skip] #[inline] - pub const fn as_u32x4(self) -> [u32; 4] { + #[rustfmt::skip] + const fn as_u32x4(self) -> [u32; 4] { let b = self.to_le_bytes(); [u32::from_le_bytes([b[0], b[1], b[2], b[3]]), u32::from_le_bytes([b[4], b[5], b[6], b[7]]), u32::from_le_bytes([b[8], b[9], b[10], b[11]]), u32::from_le_bytes([b[12], b[13], b[14], b[15]])] } - #[rustfmt::skip] #[inline] - pub const fn as_f32x4(self) -> [f32; 4] { + #[rustfmt::skip] + const fn as_f32x4(self) -> [f32; 4] { let b = self.to_le_bytes(); [f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])), f32::from_bits(u32::from_le_bytes([b[4], b[5], b[6], b[7]])), f32::from_bits(u32::from_le_bytes([b[8], b[9], b[10], b[11]])), f32::from_bits(u32::from_le_bytes([b[12], b[13], b[14], b[15]]))] } - #[rustfmt::skip] #[inline] + #[rustfmt::skip] pub const fn from_i32x4(x: [i32; 4]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) } - #[rustfmt::skip] #[inline] - pub const fn from_u32x4(x: [u32; 4]) -> Self { + #[rustfmt::skip] + const fn from_u32x4(x: [u32; 4]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) } - #[rustfmt::skip] #[inline] - pub const fn from_f32x4(x: [f32; 4]) -> Self { + #[rustfmt::skip] + const fn from_f32x4(x: [f32; 4]) -> Self { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[2].to_bits().to_le_bytes()[0], x[2].to_bits().to_le_bytes()[1], x[2].to_bits().to_le_bytes()[2], x[2].to_bits().to_le_bytes()[3], x[3].to_bits().to_le_bytes()[0], x[3].to_bits().to_le_bytes()[1], x[3].to_bits().to_le_bytes()[2], x[3].to_bits().to_le_bytes()[3]]) } - #[rustfmt::skip] #[inline] - pub const fn as_i64x2(self) -> [i64; 2] { + #[rustfmt::skip] + const fn as_i64x2(self) -> [i64; 2] { let b = self.to_le_bytes(); [i64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), i64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] } - #[rustfmt::skip] #[inline] - pub const fn as_u64x2(self) -> [u64; 2] { + #[rustfmt::skip] + const fn as_u64x2(self) -> [u64; 2] { let b = self.to_le_bytes(); [u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] } - #[rustfmt::skip] #[inline] - pub const fn as_f64x2(self) -> [f64; 2] { + #[rustfmt::skip] + const fn as_f64x2(self) -> [f64; 2] { let b = self.to_le_bytes(); [f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])), f64::from_bits(u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]))] } - #[rustfmt::skip] #[inline] + #[rustfmt::skip] pub const fn from_i64x2(x: [i64; 2]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) } - #[rustfmt::skip] #[inline] - pub const fn from_u64x2(x: [u64; 2]) -> Self { + #[rustfmt::skip] + const fn from_u64x2(x: [u64; 2]) -> Self { Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) } - #[rustfmt::skip] #[inline] - pub const fn from_f64x2(x: [f64; 2]) -> Self { + #[rustfmt::skip] + const fn from_f64x2(x: [f64; 2]) -> Self { Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) } #[inline] fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { let lanes = self.as_f32x4(); - Self::from_f32x4([op(lanes[0]), op(lanes[1]), op(lanes[2]), op(lanes[3])]) + Self::from_f32x4(core::array::from_fn(|i| op(lanes[i]))) } #[inline] fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { let a = self.as_f32x4(); let b = rhs.as_f32x4(); - Self::from_f32x4([op(a[0], b[0]), op(a[1], b[1]), op(a[2], b[2]), op(a[3], b[3])]) + Self::from_f32x4(core::array::from_fn(|i| op(a[i], b[i]))) } #[inline] fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { let lanes = self.as_f64x2(); - Self::from_f64x2([op(lanes[0]), op(lanes[1])]) + Self::from_f64x2(core::array::from_fn(|i| op(lanes[i]))) } #[inline] fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { let a = self.as_f64x2(); let b = rhs.as_f64x2(); - Self::from_f64x2([op(a[0], b[0]), op(a[1], b[1])]) + Self::from_f64x2(core::array::from_fn(|i| op(a[i], b[i]))) } #[inline] - pub const fn reduce_or(self) -> u8 { + const fn reduce_or(self) -> u8 { let mut result = 0u8; let bytes = self.to_le_bytes(); let mut i = 0; @@ -224,70 +481,52 @@ impl Value128 { #[doc(alias = "v128.any_true")] pub fn v128_any_true(self) -> bool { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return wasm::v128_any_true(self.to_wasm_v128()); - } + return wasm::v128_any_true(self.to_wasm_v128()); self.reduce_or() != 0 } #[doc(alias = "v128.not")] pub fn v128_not(self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::v128_not(self.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::v128_not(self.to_wasm_v128())); Self(!self.0) } #[doc(alias = "v128.and")] pub fn v128_and(self, rhs: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::v128_and(self.to_wasm_v128(), rhs.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::v128_and(self.to_wasm_v128(), rhs.to_wasm_v128())); Self(self.0 & rhs.0) } #[doc(alias = "v128.andnot")] pub fn v128_andnot(self, rhs: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::v128_andnot(self.to_wasm_v128(), rhs.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::v128_andnot(self.to_wasm_v128(), rhs.to_wasm_v128())); Self(self.0 & !rhs.0) } #[doc(alias = "v128.or")] pub fn v128_or(self, rhs: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::v128_or(self.to_wasm_v128(), rhs.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::v128_or(self.to_wasm_v128(), rhs.to_wasm_v128())); Self(self.0 | rhs.0) } #[doc(alias = "v128.xor")] pub fn v128_xor(self, rhs: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::v128_xor(self.to_wasm_v128(), rhs.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::v128_xor(self.to_wasm_v128(), rhs.to_wasm_v128())); Self(self.0 ^ rhs.0) } #[doc(alias = "v128.bitselect")] pub fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::v128_bitselect(v1.to_wasm_v128(), v2.to_wasm_v128(), c.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::v128_bitselect(v1.to_wasm_v128(), v2.to_wasm_v128(), c.to_wasm_v128())); Self((v1.0 & c.0) | (v2.0 & !c.0)) } - pub fn swizzle(self, s: Self) -> Self { - self.i8x16_swizzle(s) - } - #[doc(alias = "v128.load8x8_s")] pub const fn v128_load8x8_s(src: [u8; 8]) -> Self { Self::from_i16x8([ @@ -355,9 +594,7 @@ impl Value128 { #[doc(alias = "i8x16.swizzle")] pub fn i8x16_swizzle(self, s: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); - } + return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); let a_bytes = self.to_le_bytes(); let s_bytes = s.to_le_bytes(); let mut result_bytes = [0u8; 16]; @@ -382,92 +619,6 @@ impl Value128 { Self::from_le_bytes(result_bytes) } - pub const fn extend_8_i8(src: i8) -> Self { - let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 8 { - result_bytes[i * 2] = src as u8; - result_bytes[i * 2 + 1] = if src < 0 { 0xFF } else { 0x00 }; - i += 1; - } - Self::from_le_bytes(result_bytes) - } - - pub const fn extend_8_u8(src: u8) -> Self { - let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 8 { - result_bytes[i * 2] = src; - result_bytes[i * 2 + 1] = 0x00; - i += 1; - } - Self::from_le_bytes(result_bytes) - } - - pub const fn extend_4_i16(src: i16) -> Self { - let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 4 { - let bytes = src.to_le_bytes(); - result_bytes[i * 4] = bytes[0]; - result_bytes[i * 4 + 1] = bytes[1]; - result_bytes[i * 4 + 2] = if src < 0 { 0xFF } else { 0x00 }; - result_bytes[i * 4 + 3] = if src < 0 { 0xFF } else { 0x00 }; - i += 1; - } - Self::from_le_bytes(result_bytes) - } - - pub const fn extend_4_u16(src: u16) -> Self { - let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 4 { - let bytes = src.to_le_bytes(); - result_bytes[i * 4] = bytes[0]; - result_bytes[i * 4 + 1] = bytes[1]; - result_bytes[i * 4 + 2] = 0x00; - result_bytes[i * 4 + 3] = 0x00; - i += 1; - } - Self::from_le_bytes(result_bytes) - } - - pub const fn extend_2_i32(src: i32) -> Self { - let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 2 { - let bytes = src.to_le_bytes(); - result_bytes[i * 8] = bytes[0]; - result_bytes[i * 8 + 1] = bytes[1]; - result_bytes[i * 8 + 2] = bytes[2]; - result_bytes[i * 8 + 3] = bytes[3]; - result_bytes[i * 8 + 4] = if src < 0 { 0xFF } else { 0x00 }; - result_bytes[i * 8 + 5] = if src < 0 { 0xFF } else { 0x00 }; - result_bytes[i * 8 + 6] = if src < 0 { 0xFF } else { 0x00 }; - result_bytes[i * 8 + 7] = if src < 0 { 0xFF } else { 0x00 }; - i += 1; - } - Self::from_le_bytes(result_bytes) - } - - pub const fn extend_2_u32(src: u32) -> Self { - let mut result_bytes = [0u8; 16]; - let mut i = 0; - while i < 2 { - let bytes = src.to_le_bytes(); - result_bytes[i * 8] = bytes[0]; - result_bytes[i * 8 + 1] = bytes[1]; - result_bytes[i * 8 + 2] = bytes[2]; - result_bytes[i * 8 + 3] = bytes[3]; - result_bytes[i * 8 + 4] = 0x00; - result_bytes[i * 8 + 5] = 0x00; - result_bytes[i * 8 + 6] = 0x00; - result_bytes[i * 8 + 7] = 0x00; - i += 1; - } - Self::from_le_bytes(result_bytes) - } - pub const fn splat_i8(src: i8) -> Self { let mut result_bytes = [0u8; 16]; let byte = src as u8; @@ -509,1872 +660,301 @@ impl Value128 { self.replace_lane_bytes::<8>(lane, value.to_bits().to_le_bytes(), 2) } - #[doc(alias = "i8x16.all_true")] - pub const fn i8x16_all_true(self) -> bool { - let lanes = self.as_i8x16(); + simd_all_true!(i8x16_all_true, "i8x16.all_true", as_i8x16, 16); + simd_all_true!(i16x8_all_true, "i16x8.all_true", as_i16x8, 8); + simd_all_true!(i32x4_all_true, "i32x4.all_true", as_i32x4, 4); + simd_all_true!(i64x2_all_true, "i64x2.all_true", as_i64x2, 2); + + simd_bitmask!(i8x16_bitmask, "i8x16.bitmask", as_i8x16, 16); + simd_bitmask!(i16x8_bitmask, "i16x8.bitmask", as_i16x8, 8); + simd_bitmask!(i32x4_bitmask, "i32x4.bitmask", as_i32x4, 4); + simd_bitmask!(i64x2_bitmask, "i64x2.bitmask", as_i64x2, 2); + + #[doc(alias = "i8x16.popcnt")] + pub const fn i8x16_popcnt(self) -> Self { + let lanes = self.as_u8x16(); + let mut out = [0u8; 16]; let mut i = 0; while i < 16 { - if lanes[i] == 0 { - return false; - } + out[i] = lanes[i].count_ones() as u8; i += 1; } - true + Self::from_u8x16(out) } - #[doc(alias = "i16x8.all_true")] - pub const fn i16x8_all_true(self) -> bool { - let lanes = self.as_i16x8(); + simd_shift_left!(i8x16_shl, "i8x16.shl", i8, 16, as_i8x16, from_i8x16, 7); + simd_shift_left!(i16x8_shl, "i16x8.shl", i16, 8, as_i16x8, from_i16x8, 15); + simd_shift_left!(i32x4_shl, "i32x4.shl", i32, 4, as_i32x4, from_i32x4, 31); + simd_shift_left!(i64x2_shl, "i64x2.shl", i64, 2, as_i64x2, from_i64x2, 63); + + simd_shift_right!(i8x16_shr_s, "i8x16.shr_s", i8, 16, as_i8x16, from_i8x16, 7); + simd_shift_right!(i16x8_shr_s, "i16x8.shr_s", i16, 8, as_i16x8, from_i16x8, 15); + simd_shift_right!(i32x4_shr_s, "i32x4.shr_s", i32, 4, as_i32x4, from_i32x4, 31); + simd_shift_right!(i64x2_shr_s, "i64x2.shr_s", i64, 2, as_i64x2, from_i64x2, 63); + + simd_shift_right!(i8x16_shr_u, "i8x16.shr_u", u8, 16, as_u8x16, from_u8x16, 7); + simd_shift_right!(i16x8_shr_u, "i16x8.shr_u", u16, 8, as_u16x8, from_u16x8, 15); + simd_shift_right!(i32x4_shr_u, "i32x4.shr_u", u32, 4, as_u32x4, from_u32x4, 31); + simd_shift_right!(i64x2_shr_u, "i64x2.shr_u", u64, 2, as_u64x2, from_u64x2, 63); + + simd_wrapping_binop!(i8x16_add, "i8x16.add", i8x16_add, i8, 16, as_i8x16, from_i8x16, wrapping_add); + simd_wrapping_binop!(i16x8_add, "i16x8.add", i16x8_add, i16, 8, as_i16x8, from_i16x8, wrapping_add); + simd_wrapping_binop!(i32x4_add, "i32x4.add", i32x4_add, i32, 4, as_i32x4, from_i32x4, wrapping_add); + simd_wrapping_binop!(i64x2_add, "i64x2.add", i64x2_add, i64, 2, as_i64x2, from_i64x2, wrapping_add); + simd_wrapping_binop!(i8x16_sub, "i8x16.sub", i8x16_sub, i8, 16, as_i8x16, from_i8x16, wrapping_sub); + simd_wrapping_binop!(i16x8_sub, "i16x8.sub", i16x8_sub, i16, 8, as_i16x8, from_i16x8, wrapping_sub); + simd_wrapping_binop!(i32x4_sub, "i32x4.sub", i32x4_sub, i32, 4, as_i32x4, from_i32x4, wrapping_sub); + simd_wrapping_binop!(i64x2_sub, "i64x2.sub", i64x2_sub, i64, 2, as_i64x2, from_i64x2, wrapping_sub); + simd_wrapping_binop!(i16x8_mul, "i16x8.mul", i16x8_mul, i16, 8, as_i16x8, from_i16x8, wrapping_mul); + simd_wrapping_binop!(i32x4_mul, "i32x4.mul", i32x4_mul, i32, 4, as_i32x4, from_i32x4, wrapping_mul); + simd_wrapping_binop!(i64x2_mul, "i64x2.mul", i64x2_mul, i64, 2, as_i64x2, from_i64x2, wrapping_mul); + + simd_sat_binop!(i8x16_add_sat_s, "i8x16.add_sat_s", i8x16_add_sat, i8, 16, as_i8x16, from_i8x16, saturating_add); + simd_sat_binop!(i16x8_add_sat_s, "i16x8.add_sat_s", i16x8_add_sat, i16, 8, as_i16x8, from_i16x8, saturating_add); + simd_sat_binop!(i8x16_add_sat_u, "i8x16.add_sat_u", u8x16_add_sat, u8, 16, as_u8x16, from_u8x16, saturating_add); + simd_sat_binop!(i16x8_add_sat_u, "i16x8.add_sat_u", u16x8_add_sat, u16, 8, as_u16x8, from_u16x8, saturating_add); + simd_sat_binop!(i8x16_sub_sat_s, "i8x16.sub_sat_s", i8x16_sub_sat, i8, 16, as_i8x16, from_i8x16, saturating_sub); + simd_sat_binop!(i16x8_sub_sat_s, "i16x8.sub_sat_s", i16x8_sub_sat, i16, 8, as_i16x8, from_i16x8, saturating_sub); + simd_sat_binop!(i8x16_sub_sat_u, "i8x16.sub_sat_u", u8x16_sub_sat, u8, 16, as_u8x16, from_u8x16, saturating_sub); + simd_sat_binop!(i16x8_sub_sat_u, "i16x8.sub_sat_u", u16x8_sub_sat, u16, 8, as_u16x8, from_u16x8, saturating_sub); + + simd_avgr_u!(i8x16_avgr_u, "i8x16.avgr_u", u8x16_avgr, u8, u16, 16, as_u8x16, from_u8x16); + simd_avgr_u!(i16x8_avgr_u, "i16x8.avgr_u", u16x8_avgr, u16, u32, 8, as_u16x8, from_u16x8); + + #[doc(alias = "i8x16.narrow_i16x8_s")] + pub const fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0i8; 16]; let mut i = 0; while i < 8 { - if lanes[i] == 0 { - return false; - } + out[i] = saturate_i16_to_i8(av[i]); + out[i + 8] = saturate_i16_to_i8(bv[i]); i += 1; } - true + Self::from_i8x16(out) } - #[doc(alias = "i32x4.all_true")] - pub const fn i32x4_all_true(self) -> bool { - let lanes = self.as_i32x4(); + #[doc(alias = "i8x16.narrow_i16x8_u")] + pub const fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0u8; 16]; let mut i = 0; - while i < 4 { - if lanes[i] == 0 { - return false; - } + while i < 8 { + out[i] = saturate_i16_to_u8(av[i]); + out[i + 8] = saturate_i16_to_u8(bv[i]); i += 1; } - true + Self::from_u8x16(out) } - #[doc(alias = "i64x2.all_true")] - pub const fn i64x2_all_true(self) -> bool { - let lanes = self.as_i64x2(); + #[doc(alias = "i16x8.narrow_i32x4_s")] + pub const fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0i16; 8]; let mut i = 0; - while i < 2 { - if lanes[i] == 0 { - return false; - } + while i < 4 { + out[i] = saturate_i32_to_i16(av[i]); + out[i + 4] = saturate_i32_to_i16(bv[i]); i += 1; } - true + Self::from_i16x8(out) } - #[doc(alias = "i8x16.bitmask")] - pub const fn i8x16_bitmask(self) -> u32 { - let lanes = self.as_i8x16(); - let mut mask = 0u32; + #[doc(alias = "i16x8.narrow_i32x4_u")] + pub const fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0u16; 8]; let mut i = 0; - while i < 16 { - mask |= ((lanes[i] < 0) as u32) << i; + while i < 4 { + out[i] = saturate_i32_to_u16(av[i]); + out[i + 4] = saturate_i32_to_u16(bv[i]); i += 1; } - mask + Self::from_u16x8(out) } - #[doc(alias = "i16x8.bitmask")] - pub const fn i16x8_bitmask(self) -> u32 { - let lanes = self.as_i16x8(); - let mut mask = 0u32; + #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] + pub const fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { + let lanes = self.as_i8x16(); + let mut out = [0i16; 8]; let mut i = 0; while i < 8 { - mask |= ((lanes[i] < 0) as u32) << i; + let j = i * 2; + out[i] = lanes[j] as i16 + lanes[j + 1] as i16; i += 1; } - mask + Self::from_i16x8(out) } - #[doc(alias = "i32x4.bitmask")] - pub const fn i32x4_bitmask(self) -> u32 { - let lanes = self.as_i32x4(); - let mut mask = 0u32; + #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] + pub const fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { + let lanes = self.as_u8x16(); + let mut out = [0u16; 8]; let mut i = 0; - while i < 4 { - mask |= ((lanes[i] < 0) as u32) << i; + while i < 8 { + let j = i * 2; + out[i] = lanes[j] as u16 + lanes[j + 1] as u16; i += 1; } - mask + Self::from_u16x8(out) } - #[doc(alias = "i64x2.bitmask")] - pub const fn i64x2_bitmask(self) -> u32 { - let lanes = self.as_i64x2(); - let mut mask = 0u32; + #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] + pub const fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { + let lanes = self.as_i16x8(); + let mut out = [0i32; 4]; let mut i = 0; - while i < 2 { - mask |= ((lanes[i] < 0) as u32) << i; + while i < 4 { + let j = i * 2; + out[i] = lanes[j] as i32 + lanes[j + 1] as i32; i += 1; } - mask + Self::from_i32x4(out) } - #[doc(alias = "i8x16.popcnt")] - pub const fn i8x16_popcnt(self) -> Self { - let lanes = self.as_u8x16(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = lanes[i].count_ones() as u8; - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i8x16.shl")] - pub const fn i8x16_shl(self, shift: u32) -> Self { - let lanes = self.as_i8x16(); - let s = shift & 7; - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = lanes[i].wrapping_shl(s); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.shl")] - pub const fn i16x8_shl(self, shift: u32) -> Self { - let lanes = self.as_i16x8(); - let s = shift & 15; - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = lanes[i].wrapping_shl(s); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.shl")] - pub const fn i32x4_shl(self, shift: u32) -> Self { - let lanes = self.as_i32x4(); - let s = shift & 31; - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = lanes[i].wrapping_shl(s); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.shl")] - pub const fn i64x2_shl(self, shift: u32) -> Self { - let lanes = self.as_i64x2(); - let s = shift & 63; - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = lanes[i].wrapping_shl(s); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.shr_s")] - pub const fn i8x16_shr_s(self, shift: u32) -> Self { - let lanes = self.as_i8x16(); - let s = shift & 7; - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.shr_s")] - pub const fn i16x8_shr_s(self, shift: u32) -> Self { - let lanes = self.as_i16x8(); - let s = shift & 15; - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.shr_s")] - pub const fn i32x4_shr_s(self, shift: u32) -> Self { - let lanes = self.as_i32x4(); - let s = shift & 31; - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.shr_s")] - pub const fn i64x2_shr_s(self, shift: u32) -> Self { - let lanes = self.as_i64x2(); - let s = shift & 63; - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.shr_u")] - pub const fn i8x16_shr_u(self, shift: u32) -> Self { - let lanes = self.as_u8x16(); - let s = shift & 7; - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.shr_u")] - pub const fn i16x8_shr_u(self, shift: u32) -> Self { - let lanes = self.as_u16x8(); - let s = shift & 15; - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i32x4.shr_u")] - pub const fn i32x4_shr_u(self, shift: u32) -> Self { - let lanes = self.as_u32x4(); - let s = shift & 31; - let mut out = [0u32; 4]; - let mut i = 0; - while i < 4 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_u32x4(out) - } - - #[doc(alias = "i64x2.shr_u")] - pub const fn i64x2_shr_u(self, shift: u32) -> Self { - let lanes = self.as_u64x2(); - let s = shift & 63; - let mut out = [0u64; 2]; - let mut i = 0; - while i < 2 { - out[i] = lanes[i] >> s; - i += 1; - } - Self::from_u64x2(out) - } - - #[doc(alias = "i8x16.add")] - pub fn i8x16_add(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_add(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].wrapping_add(b[i]); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.add")] - pub fn i16x8_add(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_add(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].wrapping_add(b[i]); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.add")] - pub fn i32x4_add(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_add(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = a[i].wrapping_add(b[i]); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.add")] - pub fn i64x2_add(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_add(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = a[i].wrapping_add(b[i]); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.sub")] - pub fn i8x16_sub(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].wrapping_sub(b[i]); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.sub")] - pub fn i16x8_sub(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].wrapping_sub(b[i]); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.sub")] - pub fn i32x4_sub(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = a[i].wrapping_sub(b[i]); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.sub")] - pub fn i64x2_sub(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_sub(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = a[i].wrapping_sub(b[i]); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i16x8.mul")] - pub fn i16x8_mul(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_mul(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].wrapping_mul(b[i]); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.mul")] - pub fn i32x4_mul(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_mul(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = a[i].wrapping_mul(b[i]); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.mul")] - pub fn i64x2_mul(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_mul(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = a[i].wrapping_mul(b[i]); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.add_sat_s")] - pub fn i8x16_add_sat_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].saturating_add(b[i]); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.add_sat_s")] - pub fn i16x8_add_sat_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].saturating_add(b[i]); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i8x16.add_sat_u")] - pub fn i8x16_add_sat_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].saturating_add(b[i]); - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.add_sat_u")] - pub fn i16x8_add_sat_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_add_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].saturating_add(b[i]); - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i8x16.sub_sat_s")] - pub fn i8x16_sub_sat_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].saturating_sub(b[i]); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.sub_sat_s")] - pub fn i16x8_sub_sat_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].saturating_sub(b[i]); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i8x16.sub_sat_u")] - pub fn i8x16_sub_sat_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].saturating_sub(b[i]); - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.sub_sat_u")] - pub fn i16x8_sub_sat_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_sub_sat(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].saturating_sub(b[i]); - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i8x16.avgr_u")] - pub fn i8x16_avgr_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_avgr(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = ((a[i] as u16 + b[i] as u16 + 1) >> 1) as u8; - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.avgr_u")] - pub fn i16x8_avgr_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_avgr(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = ((a[i] as u32 + b[i] as u32 + 1) >> 1) as u16; - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i8x16.narrow_i16x8_s")] - pub const fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { - let av = a.as_i16x8(); - let bv = b.as_i16x8(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 8 { - out[i] = saturate_i16_to_i8(av[i]); - out[i + 8] = saturate_i16_to_i8(bv[i]); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i8x16.narrow_i16x8_u")] - pub const fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { - let av = a.as_i16x8(); - let bv = b.as_i16x8(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 8 { - out[i] = saturate_i16_to_u8(av[i]); - out[i + 8] = saturate_i16_to_u8(bv[i]); - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.narrow_i32x4_s")] - pub const fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { - let av = a.as_i32x4(); - let bv = b.as_i32x4(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 4 { - out[i] = saturate_i32_to_i16(av[i]); - out[i + 4] = saturate_i32_to_i16(bv[i]); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i16x8.narrow_i32x4_u")] - pub const fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { - let av = a.as_i32x4(); - let bv = b.as_i32x4(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 4 { - out[i] = saturate_i32_to_u16(av[i]); - out[i + 4] = saturate_i32_to_u16(bv[i]); - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] - pub const fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { - let lanes = self.as_i8x16(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - let j = i * 2; - out[i] = lanes[j] as i16 + lanes[j + 1] as i16; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] - pub const fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { - let lanes = self.as_u8x16(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - let j = i * 2; - out[i] = lanes[j] as u16 + lanes[j + 1] as u16; - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] - pub const fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { - let lanes = self.as_i16x8(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - let j = i * 2; - out[i] = lanes[j] as i32 + lanes[j + 1] as i32; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] - pub const fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { - let lanes = self.as_u16x8(); - let mut out = [0u32; 4]; - let mut i = 0; - while i < 4 { - let j = i * 2; - out[i] = lanes[j] as u32 + lanes[j + 1] as u32; - i += 1; - } - Self::from_u32x4(out) - } - - #[doc(alias = "i16x8.extend_low_i8x16_s")] - pub const fn i16x8_extend_low_i8x16_s(self) -> Self { - let lanes = self.as_i8x16(); - Self::from_i16x8([ - lanes[0] as i16, - lanes[1] as i16, - lanes[2] as i16, - lanes[3] as i16, - lanes[4] as i16, - lanes[5] as i16, - lanes[6] as i16, - lanes[7] as i16, - ]) - } - - #[doc(alias = "i16x8.extend_low_i8x16_u")] - pub const fn i16x8_extend_low_i8x16_u(self) -> Self { - let lanes = self.as_u8x16(); - Self::from_u16x8([ - lanes[0] as u16, - lanes[1] as u16, - lanes[2] as u16, - lanes[3] as u16, - lanes[4] as u16, - lanes[5] as u16, - lanes[6] as u16, - lanes[7] as u16, - ]) - } - - #[doc(alias = "i16x8.extend_high_i8x16_s")] - pub const fn i16x8_extend_high_i8x16_s(self) -> Self { - let lanes = self.as_i8x16(); - Self::from_i16x8([ - lanes[8] as i16, - lanes[9] as i16, - lanes[10] as i16, - lanes[11] as i16, - lanes[12] as i16, - lanes[13] as i16, - lanes[14] as i16, - lanes[15] as i16, - ]) - } - - #[doc(alias = "i16x8.extend_high_i8x16_u")] - pub const fn i16x8_extend_high_i8x16_u(self) -> Self { - let lanes = self.as_u8x16(); - Self::from_u16x8([ - lanes[8] as u16, - lanes[9] as u16, - lanes[10] as u16, - lanes[11] as u16, - lanes[12] as u16, - lanes[13] as u16, - lanes[14] as u16, - lanes[15] as u16, - ]) - } - - #[doc(alias = "i32x4.extend_low_i16x8_s")] - pub const fn i32x4_extend_low_i16x8_s(self) -> Self { - let lanes = self.as_i16x8(); - Self::from_i32x4([lanes[0] as i32, lanes[1] as i32, lanes[2] as i32, lanes[3] as i32]) - } - - #[doc(alias = "i32x4.extend_low_i16x8_u")] - pub const fn i32x4_extend_low_i16x8_u(self) -> Self { - let lanes = self.as_u16x8(); - Self::from_u32x4([lanes[0] as u32, lanes[1] as u32, lanes[2] as u32, lanes[3] as u32]) - } - - #[doc(alias = "i32x4.extend_high_i16x8_s")] - pub const fn i32x4_extend_high_i16x8_s(self) -> Self { - let lanes = self.as_i16x8(); - Self::from_i32x4([lanes[4] as i32, lanes[5] as i32, lanes[6] as i32, lanes[7] as i32]) - } - - #[doc(alias = "i32x4.extend_high_i16x8_u")] - pub const fn i32x4_extend_high_i16x8_u(self) -> Self { - let lanes = self.as_u16x8(); - Self::from_u32x4([lanes[4] as u32, lanes[5] as u32, lanes[6] as u32, lanes[7] as u32]) - } - - #[doc(alias = "i64x2.extend_low_i32x4_s")] - pub const fn i64x2_extend_low_i32x4_s(self) -> Self { - let lanes = self.as_i32x4(); - Self::from_i64x2([lanes[0] as i64, lanes[1] as i64]) - } - - #[doc(alias = "i64x2.extend_low_i32x4_u")] - pub const fn i64x2_extend_low_i32x4_u(self) -> Self { - let lanes = self.as_u32x4(); - Self::from_u64x2([lanes[0] as u64, lanes[1] as u64]) - } - - #[doc(alias = "i64x2.extend_high_i32x4_s")] - pub const fn i64x2_extend_high_i32x4_s(self) -> Self { - let lanes = self.as_i32x4(); - Self::from_i64x2([lanes[2] as i64, lanes[3] as i64]) - } - - #[doc(alias = "i64x2.extend_high_i32x4_u")] - pub const fn i64x2_extend_high_i32x4_u(self) -> Self { - let lanes = self.as_u32x4(); - Self::from_u64x2([lanes[2] as u64, lanes[3] as u64]) - } - - #[doc(alias = "i16x8.extmul_low_i8x16_s")] - pub const fn i16x8_extmul_low_i8x16_s(self, rhs: Self) -> Self { - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = (a[i] as i16).wrapping_mul(b[i] as i16); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i16x8.extmul_low_i8x16_u")] - pub const fn i16x8_extmul_low_i8x16_u(self, rhs: Self) -> Self { - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = (a[i] as u16) * (b[i] as u16); - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i16x8.extmul_high_i8x16_s")] - pub const fn i16x8_extmul_high_i8x16_s(self, rhs: Self) -> Self { - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = (a[i + 8] as i16).wrapping_mul(b[i + 8] as i16); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i16x8.extmul_high_i8x16_u")] - pub const fn i16x8_extmul_high_i8x16_u(self, rhs: Self) -> Self { - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = (a[i + 8] as u16) * (b[i + 8] as u16); - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i32x4.extmul_low_i16x8_s")] - pub const fn i32x4_extmul_low_i16x8_s(self, rhs: Self) -> Self { - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = (a[i] as i32).wrapping_mul(b[i] as i32); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i32x4.extmul_low_i16x8_u")] - pub const fn i32x4_extmul_low_i16x8_u(self, rhs: Self) -> Self { - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u32; 4]; - let mut i = 0; - while i < 4 { - out[i] = (a[i] as u32) * (b[i] as u32); - i += 1; - } - Self::from_u32x4(out) - } - - #[doc(alias = "i32x4.extmul_high_i16x8_s")] - pub const fn i32x4_extmul_high_i16x8_s(self, rhs: Self) -> Self { - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = (a[i + 4] as i32).wrapping_mul(b[i + 4] as i32); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i32x4.extmul_high_i16x8_u")] - pub const fn i32x4_extmul_high_i16x8_u(self, rhs: Self) -> Self { - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u32; 4]; - let mut i = 0; - while i < 4 { - out[i] = (a[i + 4] as u32) * (b[i + 4] as u32); - i += 1; - } - Self::from_u32x4(out) - } - - #[doc(alias = "i64x2.extmul_low_i32x4_s")] - pub const fn i64x2_extmul_low_i32x4_s(self, rhs: Self) -> Self { - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = (a[i] as i64).wrapping_mul(b[i] as i64); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i64x2.extmul_low_i32x4_u")] - pub const fn i64x2_extmul_low_i32x4_u(self, rhs: Self) -> Self { - let a = self.as_u32x4(); - let b = rhs.as_u32x4(); - let mut out = [0u64; 2]; - let mut i = 0; - while i < 2 { - out[i] = (a[i] as u64) * (b[i] as u64); - i += 1; - } - Self::from_u64x2(out) - } - - #[doc(alias = "i64x2.extmul_high_i32x4_s")] - pub const fn i64x2_extmul_high_i32x4_s(self, rhs: Self) -> Self { - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = (a[i + 2] as i64).wrapping_mul(b[i + 2] as i64); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i64x2.extmul_high_i32x4_u")] - pub const fn i64x2_extmul_high_i32x4_u(self, rhs: Self) -> Self { - let a = self.as_u32x4(); - let b = rhs.as_u32x4(); - let mut out = [0u64; 2]; - let mut i = 0; - while i < 2 { - out[i] = (a[i + 2] as u64) * (b[i + 2] as u64); - i += 1; - } - Self::from_u64x2(out) - } - - #[doc(alias = "i16x8.q15mulr_sat_s")] - pub const fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - let r = ((a[i] as i32 * b[i] as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding - out[i] = if r > i16::MAX as i32 { - i16::MAX - } else if r < i16::MIN as i32 { - i16::MIN - } else { - r as i16 - }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.dot_i16x8_s")] - pub const fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - Self::from_i32x4([ - (a[0] as i32).wrapping_mul(b[0] as i32).wrapping_add((a[1] as i32).wrapping_mul(b[1] as i32)), - (a[2] as i32).wrapping_mul(b[2] as i32).wrapping_add((a[3] as i32).wrapping_mul(b[3] as i32)), - (a[4] as i32).wrapping_mul(b[4] as i32).wrapping_add((a[5] as i32).wrapping_mul(b[5] as i32)), - (a[6] as i32).wrapping_mul(b[6] as i32).wrapping_add((a[7] as i32).wrapping_mul(b[7] as i32)), - ]) - } - - #[doc(alias = "i8x16.eq")] - pub fn i8x16_eq(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] == b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.eq")] - pub fn i16x8_eq(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] == b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.eq")] - pub fn i32x4_eq(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] == b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.eq")] - pub fn i64x2_eq(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_eq(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] == b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.ne")] - pub fn i8x16_ne(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] != b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.ne")] - pub fn i16x8_ne(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] != b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.ne")] - pub fn i32x4_ne(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] != b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.ne")] - pub fn i64x2_ne(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_ne(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] != b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.lt_s")] - pub fn i8x16_lt_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.lt_s")] - pub fn i16x8_lt_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.lt_s")] - pub fn i32x4_lt_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.lt_s")] - pub fn i64x2_lt_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.lt_u")] - pub fn i8x16_lt_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.lt_u")] - pub fn i16x8_lt_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.lt_u")] - pub fn i32x4_lt_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u32x4_lt(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u32x4(); - let b = rhs.as_u32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i8x16.gt_s")] - pub fn i8x16_gt_s(self, rhs: Self) -> Self { - rhs.i8x16_lt_s(self) - } - - #[doc(alias = "i16x8.gt_s")] - pub fn i16x8_gt_s(self, rhs: Self) -> Self { - rhs.i16x8_lt_s(self) - } - - #[doc(alias = "i32x4.gt_s")] - pub fn i32x4_gt_s(self, rhs: Self) -> Self { - rhs.i32x4_lt_s(self) - } - - #[doc(alias = "i64x2.gt_s")] - pub fn i64x2_gt_s(self, rhs: Self) -> Self { - rhs.i64x2_lt_s(self) - } - - #[doc(alias = "i8x16.gt_u")] - pub fn i8x16_gt_u(self, rhs: Self) -> Self { - rhs.i8x16_lt_u(self) - } - - #[doc(alias = "i16x8.gt_u")] - pub fn i16x8_gt_u(self, rhs: Self) -> Self { - rhs.i16x8_lt_u(self) - } - - #[doc(alias = "i32x4.gt_u")] - pub fn i32x4_gt_u(self, rhs: Self) -> Self { - rhs.i32x4_lt_u(self) - } - - #[doc(alias = "i8x16.le_s")] - pub fn i8x16_le_s(self, rhs: Self) -> Self { - rhs.i8x16_ge_s(self) - } - - #[doc(alias = "i16x8.le_s")] - pub fn i16x8_le_s(self, rhs: Self) -> Self { - rhs.i16x8_ge_s(self) - } - - #[doc(alias = "i32x4.le_s")] - pub fn i32x4_le_s(self, rhs: Self) -> Self { - rhs.i32x4_ge_s(self) - } - - #[doc(alias = "i64x2.le_s")] - pub fn i64x2_le_s(self, rhs: Self) -> Self { - rhs.i64x2_ge_s(self) - } - - #[doc(alias = "i8x16.le_u")] - pub fn i8x16_le_u(self, rhs: Self) -> Self { - rhs.i8x16_ge_u(self) - } - - #[doc(alias = "i16x8.le_u")] - pub fn i16x8_le_u(self, rhs: Self) -> Self { - rhs.i16x8_ge_u(self) - } - - #[doc(alias = "i32x4.le_u")] - pub fn i32x4_le_u(self, rhs: Self) -> Self { - rhs.i32x4_ge_u(self) - } - - #[doc(alias = "i8x16.ge_s")] - pub fn i8x16_ge_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.ge_s")] - pub fn i16x8_ge_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.ge_s")] - pub fn i32x4_ge_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.ge_s")] - pub fn i64x2_ge_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i64x2(); - let b = rhs.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.ge_u")] - pub fn i8x16_ge_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.ge_u")] - pub fn i16x8_ge_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.ge_u")] - pub fn i32x4_ge_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u32x4_ge(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u32x4(); - let b = rhs.as_u32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i8x16.abs")] - pub const fn i8x16_abs(self) -> Self { - let a = self.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].wrapping_abs(); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.abs")] - pub const fn i16x8_abs(self) -> Self { - let a = self.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].wrapping_abs(); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.abs")] - pub const fn i32x4_abs(self) -> Self { - let a = self.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = a[i].wrapping_abs(); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.abs")] - pub const fn i64x2_abs(self) -> Self { - let a = self.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = a[i].wrapping_abs(); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.neg")] - pub fn i8x16_neg(self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_neg(self.to_wasm_v128())); - } - let a = self.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = a[i].wrapping_neg(); - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.neg")] - pub fn i16x8_neg(self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_neg(self.to_wasm_v128())); - } - let a = self.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = a[i].wrapping_neg(); - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.neg")] - pub fn i32x4_neg(self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_neg(self.to_wasm_v128())); - } - let a = self.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = a[i].wrapping_neg(); - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i64x2.neg")] - pub fn i64x2_neg(self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i64x2_neg(self.to_wasm_v128())); - } - let a = self.as_i64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = a[i].wrapping_neg(); - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "i8x16.min_s")] - pub fn i8x16_min_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_min(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] < b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.min_s")] - pub fn i16x8_min_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_min(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] < b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.min_s")] - pub fn i32x4_min_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_min(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] < b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i8x16.min_u")] - pub fn i8x16_min_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_min(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] < b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.min_u")] - pub fn i16x8_min_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_min(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] < b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i32x4.min_u")] - pub fn i32x4_min_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u32x4_min(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u32x4(); - let b = rhs.as_u32x4(); - let mut out = [0u32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] < b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_u32x4(out) - } - - #[doc(alias = "i8x16.max_s")] - pub fn i8x16_max_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i8x16_max(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] > b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_i8x16(out) - } - - #[doc(alias = "i16x8.max_s")] - pub fn i16x8_max_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i16x8_max(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] > b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.max_s")] - pub fn i32x4_max_s(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::i32x4_max(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_i32x4(); - let b = rhs.as_i32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] > b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i8x16.max_u")] - pub fn i8x16_max_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u8x16_max(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u8x16(); - let b = rhs.as_u8x16(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = if a[i] > b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.max_u")] - pub fn i16x8_max_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u16x8_max(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u16x8(); - let b = rhs.as_u16x8(); - let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - out[i] = if a[i] > b[i] { a[i] } else { b[i] }; - i += 1; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i32x4.max_u")] - pub fn i32x4_max_u(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - { - return Self::from_wasm_v128(wasm::u32x4_max(self.to_wasm_v128(), rhs.to_wasm_v128())); - } - let a = self.as_u32x4(); - let b = rhs.as_u32x4(); + #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] + pub const fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { + let lanes = self.as_u16x8(); let mut out = [0u32; 4]; let mut i = 0; while i < 4 { - out[i] = if a[i] > b[i] { a[i] } else { b[i] }; + let j = i * 2; + out[i] = lanes[j] as u32 + lanes[j + 1] as u32; i += 1; } Self::from_u32x4(out) } - #[doc(alias = "f32x4.eq")] - pub const fn f32x4_eq(self, rhs: Self) -> Self { - let a = self.as_f32x4(); - let b = rhs.as_f32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] == b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "f64x2.eq")] - pub const fn f64x2_eq(self, rhs: Self) -> Self { - let a = self.as_f64x2(); - let b = rhs.as_f64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] == b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "f32x4.ne")] - pub const fn f32x4_ne(self, rhs: Self) -> Self { - let a = self.as_f32x4(); - let b = rhs.as_f32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] != b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } + simd_extend_cast!(i16x8_extend_low_i8x16_s, "i16x8.extend_low_i8x16_s", as_i8x16, from_i16x8, i16, 8, 0); + simd_extend_cast!(i16x8_extend_low_i8x16_u, "i16x8.extend_low_i8x16_u", as_u8x16, from_u16x8, u16, 8, 0); + simd_extend_cast!(i16x8_extend_high_i8x16_s, "i16x8.extend_high_i8x16_s", as_i8x16, from_i16x8, i16, 8, 8); + simd_extend_cast!(i16x8_extend_high_i8x16_u, "i16x8.extend_high_i8x16_u", as_u8x16, from_u16x8, u16, 8, 8); + simd_extend_cast!(i32x4_extend_low_i16x8_s, "i32x4.extend_low_i16x8_s", as_i16x8, from_i32x4, i32, 4, 0); + simd_extend_cast!(i32x4_extend_low_i16x8_u, "i32x4.extend_low_i16x8_u", as_u16x8, from_u32x4, u32, 4, 0); + simd_extend_cast!(i32x4_extend_high_i16x8_s, "i32x4.extend_high_i16x8_s", as_i16x8, from_i32x4, i32, 4, 4); + simd_extend_cast!(i32x4_extend_high_i16x8_u, "i32x4.extend_high_i16x8_u", as_u16x8, from_u32x4, u32, 4, 4); + simd_extend_cast!(i64x2_extend_low_i32x4_s, "i64x2.extend_low_i32x4_s", as_i32x4, from_i64x2, i64, 2, 0); + simd_extend_cast!(i64x2_extend_low_i32x4_u, "i64x2.extend_low_i32x4_u", as_u32x4, from_u64x2, u64, 2, 0); + simd_extend_cast!(i64x2_extend_high_i32x4_s, "i64x2.extend_high_i32x4_s", as_i32x4, from_i64x2, i64, 2, 2); + simd_extend_cast!(i64x2_extend_high_i32x4_u, "i64x2.extend_high_i32x4_u", as_u32x4, from_u64x2, u64, 2, 2); + + simd_extmul_signed!(i16x8_extmul_low_i8x16_s, "i16x8.extmul_low_i8x16_s", as_i8x16, from_i16x8, i16, 8, 0); + simd_extmul_unsigned!(i16x8_extmul_low_i8x16_u, "i16x8.extmul_low_i8x16_u", as_u8x16, from_u16x8, u16, 8, 0); + simd_extmul_signed!(i16x8_extmul_high_i8x16_s, "i16x8.extmul_high_i8x16_s", as_i8x16, from_i16x8, i16, 8, 8); + simd_extmul_unsigned!(i16x8_extmul_high_i8x16_u, "i16x8.extmul_high_i8x16_u", as_u8x16, from_u16x8, u16, 8, 8); + simd_extmul_signed!(i32x4_extmul_low_i16x8_s, "i32x4.extmul_low_i16x8_s", as_i16x8, from_i32x4, i32, 4, 0); + simd_extmul_unsigned!(i32x4_extmul_low_i16x8_u, "i32x4.extmul_low_i16x8_u", as_u16x8, from_u32x4, u32, 4, 0); + simd_extmul_signed!(i32x4_extmul_high_i16x8_s, "i32x4.extmul_high_i16x8_s", as_i16x8, from_i32x4, i32, 4, 4); + simd_extmul_unsigned!(i32x4_extmul_high_i16x8_u, "i32x4.extmul_high_i16x8_u", as_u16x8, from_u32x4, u32, 4, 4); + simd_extmul_signed!(i64x2_extmul_low_i32x4_s, "i64x2.extmul_low_i32x4_s", as_i32x4, from_i64x2, i64, 2, 0); + simd_extmul_unsigned!(i64x2_extmul_low_i32x4_u, "i64x2.extmul_low_i32x4_u", as_u32x4, from_u64x2, u64, 2, 0); + simd_extmul_signed!(i64x2_extmul_high_i32x4_s, "i64x2.extmul_high_i32x4_s", as_i32x4, from_i64x2, i64, 2, 2); + simd_extmul_unsigned!(i64x2_extmul_high_i32x4_u, "i64x2.extmul_high_i32x4_u", as_u32x4, from_u64x2, u64, 2, 2); - #[doc(alias = "f64x2.ne")] - pub const fn f64x2_ne(self, rhs: Self) -> Self { - let a = self.as_f64x2(); - let b = rhs.as_f64x2(); - let mut out = [0i64; 2]; + #[doc(alias = "i16x8.q15mulr_sat_s")] + pub const fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; let mut i = 0; - while i < 2 { - out[i] = if a[i] != b[i] { -1 } else { 0 }; + while i < 8 { + let r = ((a[i] as i32 * b[i] as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding + out[i] = if r > i16::MAX as i32 { + i16::MAX + } else if r < i16::MIN as i32 { + i16::MIN + } else { + r as i16 + }; i += 1; } - Self::from_i64x2(out) + Self::from_i16x8(out) } - #[doc(alias = "f32x4.lt")] - pub const fn f32x4_lt(self, rhs: Self) -> Self { - let a = self.as_f32x4(); - let b = rhs.as_f32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) + #[doc(alias = "i32x4.dot_i16x8_s")] + pub const fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + Self::from_i32x4([ + (a[0] as i32).wrapping_mul(b[0] as i32).wrapping_add((a[1] as i32).wrapping_mul(b[1] as i32)), + (a[2] as i32).wrapping_mul(b[2] as i32).wrapping_add((a[3] as i32).wrapping_mul(b[3] as i32)), + (a[4] as i32).wrapping_mul(b[4] as i32).wrapping_add((a[5] as i32).wrapping_mul(b[5] as i32)), + (a[6] as i32).wrapping_mul(b[6] as i32).wrapping_add((a[7] as i32).wrapping_mul(b[7] as i32)), + ]) } - #[doc(alias = "f64x2.lt")] - pub const fn f64x2_lt(self, rhs: Self) -> Self { - let a = self.as_f64x2(); - let b = rhs.as_f64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] < b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } + simd_cmp_mask!(i8x16_eq, "i8x16.eq", i8x16_eq, i8, 16, as_i8x16, from_i8x16, ==); + simd_cmp_mask!(i16x8_eq, "i16x8.eq", i16x8_eq, i16, 8, as_i16x8, from_i16x8, ==); + simd_cmp_mask!(i32x4_eq, "i32x4.eq", i32x4_eq, i32, 4, as_i32x4, from_i32x4, ==); + simd_cmp_mask!(i64x2_eq, "i64x2.eq", i64x2_eq, i64, 2, as_i64x2, from_i64x2, ==); + simd_cmp_mask!(i8x16_ne, "i8x16.ne", i8x16_ne, i8, 16, as_i8x16, from_i8x16, !=); + simd_cmp_mask!(i16x8_ne, "i16x8.ne", i16x8_ne, i16, 8, as_i16x8, from_i16x8, !=); + simd_cmp_mask!(i32x4_ne, "i32x4.ne", i32x4_ne, i32, 4, as_i32x4, from_i32x4, !=); + simd_cmp_mask!(i64x2_ne, "i64x2.ne", i64x2_ne, i64, 2, as_i64x2, from_i64x2, !=); + simd_cmp_mask!(i8x16_lt_s, "i8x16.lt_s", i8x16_lt, i8, 16, as_i8x16, from_i8x16, <); + simd_cmp_mask!(i16x8_lt_s, "i16x8.lt_s", i16x8_lt, i16, 8, as_i16x8, from_i16x8, <); + simd_cmp_mask!(i32x4_lt_s, "i32x4.lt_s", i32x4_lt, i32, 4, as_i32x4, from_i32x4, <); + simd_cmp_mask!(i64x2_lt_s, "i64x2.lt_s", i64x2_lt, i64, 2, as_i64x2, from_i64x2, <); + simd_cmp_mask!(i8x16_lt_u, "i8x16.lt_u", u8x16_lt, i8, 16, as_u8x16, from_i8x16, <); + simd_cmp_mask!(i16x8_lt_u, "i16x8.lt_u", u16x8_lt, i16, 8, as_u16x8, from_i16x8, <); + simd_cmp_mask!(i32x4_lt_u, "i32x4.lt_u", u32x4_lt, i32, 4, as_u32x4, from_i32x4, <); + + simd_cmp_delegate!(i8x16_gt_s, "i8x16.gt_s", i8x16_lt_s); + simd_cmp_delegate!(i16x8_gt_s, "i16x8.gt_s", i16x8_lt_s); + simd_cmp_delegate!(i32x4_gt_s, "i32x4.gt_s", i32x4_lt_s); + simd_cmp_delegate!(i64x2_gt_s, "i64x2.gt_s", i64x2_lt_s); + simd_cmp_delegate!(i8x16_gt_u, "i8x16.gt_u", i8x16_lt_u); + simd_cmp_delegate!(i16x8_gt_u, "i16x8.gt_u", i16x8_lt_u); + simd_cmp_delegate!(i32x4_gt_u, "i32x4.gt_u", i32x4_lt_u); + simd_cmp_delegate!(i8x16_le_s, "i8x16.le_s", i8x16_ge_s); + simd_cmp_delegate!(i16x8_le_s, "i16x8.le_s", i16x8_ge_s); + simd_cmp_delegate!(i32x4_le_s, "i32x4.le_s", i32x4_ge_s); + simd_cmp_delegate!(i64x2_le_s, "i64x2.le_s", i64x2_ge_s); + simd_cmp_delegate!(i8x16_le_u, "i8x16.le_u", i8x16_ge_u); + simd_cmp_delegate!(i16x8_le_u, "i16x8.le_u", i16x8_ge_u); + simd_cmp_delegate!(i32x4_le_u, "i32x4.le_u", i32x4_ge_u); + + simd_cmp_mask!(i8x16_ge_s, "i8x16.ge_s", i8x16_ge, i8, 16, as_i8x16, from_i8x16, >=); + simd_cmp_mask!(i16x8_ge_s, "i16x8.ge_s", i16x8_ge, i16, 8, as_i16x8, from_i16x8, >=); + simd_cmp_mask!(i32x4_ge_s, "i32x4.ge_s", i32x4_ge, i32, 4, as_i32x4, from_i32x4, >=); + simd_cmp_mask!(i64x2_ge_s, "i64x2.ge_s", i64x2_ge, i64, 2, as_i64x2, from_i64x2, >=); + simd_cmp_mask!(i8x16_ge_u, "i8x16.ge_u", u8x16_ge, i8, 16, as_u8x16, from_i8x16, >=); + simd_cmp_mask!(i16x8_ge_u, "i16x8.ge_u", u16x8_ge, i16, 8, as_u16x8, from_i16x8, >=); + simd_cmp_mask!(i32x4_ge_u, "i32x4.ge_u", u32x4_ge, i32, 4, as_u32x4, from_i32x4, >=); + + simd_abs_const!(i8x16_abs, "i8x16.abs", i8, 16, as_i8x16, from_i8x16); + simd_abs_const!(i16x8_abs, "i16x8.abs", i16, 8, as_i16x8, from_i16x8); + simd_abs_const!(i32x4_abs, "i32x4.abs", i32, 4, as_i32x4, from_i32x4); + simd_abs_const!(i64x2_abs, "i64x2.abs", i64, 2, as_i64x2, from_i64x2); + + simd_neg!(i8x16_neg, "i8x16.neg", i8x16_neg, i8, 16, as_i8x16, from_i8x16); + simd_neg!(i16x8_neg, "i16x8.neg", i16x8_neg, i16, 8, as_i16x8, from_i16x8); + simd_neg!(i32x4_neg, "i32x4.neg", i32x4_neg, i32, 4, as_i32x4, from_i32x4); + simd_neg!(i64x2_neg, "i64x2.neg", i64x2_neg, i64, 2, as_i64x2, from_i64x2); + + simd_minmax!(i8x16_min_s, "i8x16.min_s", i8x16_min, i8, 16, as_i8x16, from_i8x16, <); + simd_minmax!(i16x8_min_s, "i16x8.min_s", i16x8_min, i16, 8, as_i16x8, from_i16x8, <); + simd_minmax!(i32x4_min_s, "i32x4.min_s", i32x4_min, i32, 4, as_i32x4, from_i32x4, <); + simd_minmax!(i8x16_min_u, "i8x16.min_u", u8x16_min, u8, 16, as_u8x16, from_u8x16, <); + simd_minmax!(i16x8_min_u, "i16x8.min_u", u16x8_min, u16, 8, as_u16x8, from_u16x8, <); + simd_minmax!(i32x4_min_u, "i32x4.min_u", u32x4_min, u32, 4, as_u32x4, from_u32x4, <); + simd_minmax!(i8x16_max_s, "i8x16.max_s", i8x16_max, i8, 16, as_i8x16, from_i8x16, >); + simd_minmax!(i16x8_max_s, "i16x8.max_s", i16x8_max, i16, 8, as_i16x8, from_i16x8, >); + simd_minmax!(i32x4_max_s, "i32x4.max_s", i32x4_max, i32, 4, as_i32x4, from_i32x4, >); + simd_minmax!(i8x16_max_u, "i8x16.max_u", u8x16_max, u8, 16, as_u8x16, from_u8x16, >); + simd_minmax!(i16x8_max_u, "i16x8.max_u", u16x8_max, u16, 8, as_u16x8, from_u16x8, >); + simd_minmax!(i32x4_max_u, "i32x4.max_u", u32x4_max, u32, 4, as_u32x4, from_u32x4, >); + + simd_cmp_mask_const!(f32x4_eq, "f32x4.eq", i32, 4, as_f32x4, from_i32x4, ==); + simd_cmp_mask_const!(f64x2_eq, "f64x2.eq", i64, 2, as_f64x2, from_i64x2, ==); + simd_cmp_mask_const!(f32x4_ne, "f32x4.ne", i32, 4, as_f32x4, from_i32x4, !=); + simd_cmp_mask_const!(f64x2_ne, "f64x2.ne", i64, 2, as_f64x2, from_i64x2, !=); + simd_cmp_mask_const!(f32x4_lt, "f32x4.lt", i32, 4, as_f32x4, from_i32x4, <); + simd_cmp_mask_const!(f64x2_lt, "f64x2.lt", i64, 2, as_f64x2, from_i64x2, <); #[doc(alias = "f32x4.gt")] pub const fn f32x4_gt(self, rhs: Self) -> Self { @@ -2386,207 +966,46 @@ impl Value128 { rhs.f64x2_lt(self) } - #[doc(alias = "f32x4.le")] - pub const fn f32x4_le(self, rhs: Self) -> Self { - let a = self.as_f32x4(); - let b = rhs.as_f32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] <= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "f64x2.le")] - pub const fn f64x2_le(self, rhs: Self) -> Self { - let a = self.as_f64x2(); - let b = rhs.as_f64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] <= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "f32x4.ge")] - pub const fn f32x4_ge(self, rhs: Self) -> Self { - let a = self.as_f32x4(); - let b = rhs.as_f32x4(); - let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i32x4(out) - } - - #[doc(alias = "f64x2.ge")] - pub const fn f64x2_ge(self, rhs: Self) -> Self { - let a = self.as_f64x2(); - let b = rhs.as_f64x2(); - let mut out = [0i64; 2]; - let mut i = 0; - while i < 2 { - out[i] = if a[i] >= b[i] { -1 } else { 0 }; - i += 1; - } - Self::from_i64x2(out) - } - - #[doc(alias = "f32x4.ceil")] - pub fn f32x4_ceil(self) -> Self { - self.map_f32x4(|x| canonicalize_simd_f32_nan(x.ceil())) - } - - #[doc(alias = "f64x2.ceil")] - pub fn f64x2_ceil(self) -> Self { - self.map_f64x2(|x| canonicalize_simd_f64_nan(x.ceil())) - } - - #[doc(alias = "f32x4.floor")] - pub fn f32x4_floor(self) -> Self { - self.map_f32x4(|x| canonicalize_simd_f32_nan(x.floor())) - } - - #[doc(alias = "f64x2.floor")] - pub fn f64x2_floor(self) -> Self { - self.map_f64x2(|x| canonicalize_simd_f64_nan(x.floor())) - } - - #[doc(alias = "f32x4.trunc")] - pub fn f32x4_trunc(self) -> Self { - self.map_f32x4(|x| canonicalize_simd_f32_nan(x.trunc())) - } - - #[doc(alias = "f64x2.trunc")] - pub fn f64x2_trunc(self) -> Self { - self.map_f64x2(|x| canonicalize_simd_f64_nan(x.trunc())) - } - - #[doc(alias = "f32x4.nearest")] - pub fn f32x4_nearest(self) -> Self { - self.map_f32x4(|x| canonicalize_simd_f32_nan(TinywasmFloatExt::tw_nearest(x))) - } - - #[doc(alias = "f64x2.nearest")] - pub fn f64x2_nearest(self) -> Self { - self.map_f64x2(|x| canonicalize_simd_f64_nan(TinywasmFloatExt::tw_nearest(x))) - } - - #[doc(alias = "f32x4.abs")] - pub fn f32x4_abs(self) -> Self { - self.map_f32x4(f32::abs) - } - - #[doc(alias = "f64x2.abs")] - pub fn f64x2_abs(self) -> Self { - self.map_f64x2(f64::abs) - } - - #[doc(alias = "f32x4.neg")] - pub fn f32x4_neg(self) -> Self { - self.map_f32x4(|x| -x) - } - - #[doc(alias = "f64x2.neg")] - pub fn f64x2_neg(self) -> Self { - self.map_f64x2(|x| -x) - } - - #[doc(alias = "f32x4.sqrt")] - pub fn f32x4_sqrt(self) -> Self { - self.map_f32x4(|x| canonicalize_simd_f32_nan(x.sqrt())) - } - - #[doc(alias = "f64x2.sqrt")] - pub fn f64x2_sqrt(self) -> Self { - self.map_f64x2(|x| canonicalize_simd_f64_nan(x.sqrt())) - } - - #[doc(alias = "f32x4.add")] - pub fn f32x4_add(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a + b)) - } - - #[doc(alias = "f64x2.add")] - pub fn f64x2_add(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a + b)) - } - - #[doc(alias = "f32x4.sub")] - pub fn f32x4_sub(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a - b)) - } - - #[doc(alias = "f64x2.sub")] - pub fn f64x2_sub(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a - b)) - } - - #[doc(alias = "f32x4.mul")] - pub fn f32x4_mul(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a * b)) - } - - #[doc(alias = "f64x2.mul")] - pub fn f64x2_mul(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a * b)) - } - - #[doc(alias = "f32x4.div")] - pub fn f32x4_div(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| canonicalize_simd_f32_nan(a / b)) - } - - #[doc(alias = "f64x2.div")] - pub fn f64x2_div(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| canonicalize_simd_f64_nan(a / b)) - } - - #[doc(alias = "f32x4.min")] - pub fn f32x4_min(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, TinywasmFloatExt::tw_minimum) - } - - #[doc(alias = "f64x2.min")] - pub fn f64x2_min(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, TinywasmFloatExt::tw_minimum) - } - - #[doc(alias = "f32x4.max")] - pub fn f32x4_max(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, TinywasmFloatExt::tw_maximum) - } - - #[doc(alias = "f64x2.max")] - pub fn f64x2_max(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, TinywasmFloatExt::tw_maximum) - } - - #[doc(alias = "f32x4.pmin")] - pub fn f32x4_pmin(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| if b < a { b } else { a }) - } - - #[doc(alias = "f64x2.pmin")] - pub fn f64x2_pmin(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| if b < a { b } else { a }) - } - - #[doc(alias = "f32x4.pmax")] - pub fn f32x4_pmax(self, rhs: Self) -> Self { - self.zip_f32x4(rhs, |a, b| if b > a { b } else { a }) - } - - #[doc(alias = "f64x2.pmax")] - pub fn f64x2_pmax(self, rhs: Self) -> Self { - self.zip_f64x2(rhs, |a, b| if b > a { b } else { a }) - } + simd_cmp_mask_const!(f32x4_le, "f32x4.le", i32, 4, as_f32x4, from_i32x4, <=); + simd_cmp_mask_const!(f64x2_le, "f64x2.le", i64, 2, as_f64x2, from_i64x2, <=); + simd_cmp_mask_const!(f32x4_ge, "f32x4.ge", i32, 4, as_f32x4, from_i32x4, >=); + simd_cmp_mask_const!(f64x2_ge, "f64x2.ge", i64, 2, as_f64x2, from_i64x2, >=); + + simd_float_unary!(f32x4_ceil, "f32x4.ceil", map_f32x4, |x| canonicalize_simd_f32_nan(x.ceil())); + simd_float_unary!(f64x2_ceil, "f64x2.ceil", map_f64x2, |x| canonicalize_simd_f64_nan(x.ceil())); + simd_float_unary!(f32x4_floor, "f32x4.floor", map_f32x4, |x| canonicalize_simd_f32_nan(x.floor())); + simd_float_unary!(f64x2_floor, "f64x2.floor", map_f64x2, |x| canonicalize_simd_f64_nan(x.floor())); + simd_float_unary!(f32x4_trunc, "f32x4.trunc", map_f32x4, |x| canonicalize_simd_f32_nan(x.trunc())); + simd_float_unary!(f64x2_trunc, "f64x2.trunc", map_f64x2, |x| canonicalize_simd_f64_nan(x.trunc())); + simd_float_unary!(f32x4_nearest, "f32x4.nearest", map_f32x4, |x| canonicalize_simd_f32_nan( + TinywasmFloatExt::tw_nearest(x) + )); + simd_float_unary!(f64x2_nearest, "f64x2.nearest", map_f64x2, |x| canonicalize_simd_f64_nan( + TinywasmFloatExt::tw_nearest(x) + )); + simd_float_unary!(f32x4_abs, "f32x4.abs", map_f32x4, f32::abs); + simd_float_unary!(f64x2_abs, "f64x2.abs", map_f64x2, f64::abs); + simd_float_unary!(f32x4_neg, "f32x4.neg", map_f32x4, |x| -x); + simd_float_unary!(f64x2_neg, "f64x2.neg", map_f64x2, |x| -x); + simd_float_unary!(f32x4_sqrt, "f32x4.sqrt", map_f32x4, |x| canonicalize_simd_f32_nan(x.sqrt())); + simd_float_unary!(f64x2_sqrt, "f64x2.sqrt", map_f64x2, |x| canonicalize_simd_f64_nan(x.sqrt())); + + simd_float_binary!(f32x4_add, "f32x4.add", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a + b)); + simd_float_binary!(f64x2_add, "f64x2.add", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a + b)); + simd_float_binary!(f32x4_sub, "f32x4.sub", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a - b)); + simd_float_binary!(f64x2_sub, "f64x2.sub", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a - b)); + simd_float_binary!(f32x4_mul, "f32x4.mul", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a * b)); + simd_float_binary!(f64x2_mul, "f64x2.mul", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a * b)); + simd_float_binary!(f32x4_div, "f32x4.div", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a / b)); + simd_float_binary!(f64x2_div, "f64x2.div", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a / b)); + simd_float_binary!(f32x4_min, "f32x4.min", zip_f32x4, TinywasmFloatExt::tw_minimum); + simd_float_binary!(f64x2_min, "f64x2.min", zip_f64x2, TinywasmFloatExt::tw_minimum); + simd_float_binary!(f32x4_max, "f32x4.max", zip_f32x4, TinywasmFloatExt::tw_maximum); + simd_float_binary!(f64x2_max, "f64x2.max", zip_f64x2, TinywasmFloatExt::tw_maximum); + simd_float_binary!(f32x4_pmin, "f32x4.pmin", zip_f32x4, |a, b| if b < a { b } else { a }); + simd_float_binary!(f64x2_pmin, "f64x2.pmin", zip_f64x2, |a, b| if b < a { b } else { a }); + simd_float_binary!(f32x4_pmax, "f32x4.pmax", zip_f32x4, |a, b| if b > a { b } else { a }); + simd_float_binary!(f64x2_pmax, "f64x2.pmax", zip_f64x2, |a, b| if b > a { b } else { a }); #[doc(alias = "i32x4.trunc_sat_f32x4_s")] pub fn i32x4_trunc_sat_f32x4_s(self) -> Self { @@ -2659,47 +1078,15 @@ impl Value128 { } pub const fn splat_i16(src: i16) -> Self { - let mut result_bytes = [0u8; 16]; - let bytes = src.to_le_bytes(); - let mut i = 0; - while i < 8 { - result_bytes[i * 2] = bytes[0]; - result_bytes[i * 2 + 1] = bytes[1]; - i += 1; - } - Self::from_le_bytes(result_bytes) + Self::from_i16x8([src; 8]) } pub const fn splat_i32(src: i32) -> Self { - let mut result_bytes = [0u8; 16]; - let bytes = src.to_le_bytes(); - let mut i = 0; - while i < 4 { - result_bytes[i * 4] = bytes[0]; - result_bytes[i * 4 + 1] = bytes[1]; - result_bytes[i * 4 + 2] = bytes[2]; - result_bytes[i * 4 + 3] = bytes[3]; - i += 1; - } - Self::from_le_bytes(result_bytes) + Self::from_i32x4([src; 4]) } pub const fn splat_i64(src: i64) -> Self { - let mut result_bytes = [0u8; 16]; - let bytes = src.to_le_bytes(); - let mut i = 0; - while i < 2 { - result_bytes[i * 8] = bytes[0]; - result_bytes[i * 8 + 1] = bytes[1]; - result_bytes[i * 8 + 2] = bytes[2]; - result_bytes[i * 8 + 3] = bytes[3]; - result_bytes[i * 8 + 4] = bytes[4]; - result_bytes[i * 8 + 5] = bytes[5]; - result_bytes[i * 8 + 6] = bytes[6]; - result_bytes[i * 8 + 7] = bytes[7]; - i += 1; - } - Self::from_le_bytes(result_bytes) + Self::from_i64x2([src; 2]) } pub const fn splat_f32(src: f32) -> Self { @@ -2725,44 +1112,19 @@ impl Value128 { } pub const fn extract_lane_i16(self, lane: u8) -> i16 { - debug_assert!(lane < 8); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 2; - i16::from_le_bytes([bytes[start], bytes[start + 1]]) + i16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) } pub const fn extract_lane_u16(self, lane: u8) -> u16 { - debug_assert!(lane < 8); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 2; - u16::from_le_bytes([bytes[start], bytes[start + 1]]) + u16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) } pub const fn extract_lane_i32(self, lane: u8) -> i32 { - debug_assert!(lane < 4); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 4; - i32::from_le_bytes([bytes[start], bytes[start + 1], bytes[start + 2], bytes[start + 3]]) + i32::from_le_bytes(self.extract_lane_bytes::<4>(lane, 4)) } pub const fn extract_lane_i64(self, lane: u8) -> i64 { - debug_assert!(lane < 2); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - let start = lane * 8; - i64::from_le_bytes([ - bytes[start], - bytes[start + 1], - bytes[start + 2], - bytes[start + 3], - bytes[start + 4], - bytes[start + 5], - bytes[start + 6], - bytes[start + 7], - ]) + i64::from_le_bytes(self.extract_lane_bytes::<8>(lane, 2)) } pub const fn extract_lane_f32(self, lane: u8) -> f32 { @@ -2773,6 +1135,19 @@ impl Value128 { f64::from_bits(self.extract_lane_i64(lane) as u64) } + const fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { + debug_assert!(lane < lane_count); + let bytes = self.to_le_bytes(); + let start = lane as usize * LANE_BYTES; + let mut out = [0u8; LANE_BYTES]; + let mut i = 0; + while i < LANE_BYTES { + out[i] = bytes[start + i]; + i += 1; + } + out + } + const fn replace_lane_bytes( self, lane: u8, @@ -2791,46 +1166,6 @@ impl Value128 { } } -impl From for i128 { - fn from(val: Value128) -> Self { - val.0 - } -} - -impl From for Value128 { - fn from(value: i128) -> Self { - Self(value) - } -} - -impl core::ops::Not for Value128 { - type Output = Self; - fn not(self) -> Self::Output { - Self(!self.0) - } -} - -impl core::ops::BitAnd for Value128 { - type Output = Self; - fn bitand(self, rhs: Self) -> Self::Output { - Self(self.0 & rhs.0) - } -} - -impl core::ops::BitOr for Value128 { - type Output = Self; - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl core::ops::BitXor for Value128 { - type Output = Self; - fn bitxor(self, rhs: Self) -> Self::Output { - Self(self.0 ^ rhs.0) - } -} - const fn canonicalize_simd_f32_nan(x: f32) -> f32 { #[cfg(feature = "canonicalize_nans")] if x.is_nan() { @@ -2854,85 +1189,67 @@ const fn canonicalize_simd_f64_nan(x: f64) -> f64 { } const fn saturate_i16_to_i8(x: i16) -> i8 { - if x > i8::MAX as i16 { - i8::MAX - } else if x < i8::MIN as i16 { - i8::MIN - } else { - x as i8 + match x { + v if v > i8::MAX as i16 => i8::MAX, + v if v < i8::MIN as i16 => i8::MIN, + v => v as i8, } } const fn saturate_i16_to_u8(x: i16) -> u8 { - if x <= 0 { - 0 - } else if x > u8::MAX as i16 { - u8::MAX - } else { - x as u8 + match x { + v if v <= 0 => 0, + v if v > u8::MAX as i16 => u8::MAX, + v => v as u8, } } const fn saturate_i32_to_i16(x: i32) -> i16 { - if x > i16::MAX as i32 { - i16::MAX - } else if x < i16::MIN as i32 { - i16::MIN - } else { - x as i16 + match x { + v if v > i16::MAX as i32 => i16::MAX, + v if v < i16::MIN as i32 => i16::MIN, + v => v as i16, } } const fn saturate_i32_to_u16(x: i32) -> u16 { - if x <= 0 { - 0 - } else if x > u16::MAX as i32 { - u16::MAX - } else { - x as u16 + match x { + v if v <= 0 => 0, + v if v > u16::MAX as i32 => u16::MAX, + v => v as u16, } } fn trunc_sat_f32_to_i32(v: f32) -> i32 { - if v.is_nan() { - 0 - } else if v <= i32::MIN as f32 - (1 << 8) as f32 { - i32::MIN - } else if v >= (i32::MAX as f32 + 1.0) { - i32::MAX - } else { - v.trunc() as i32 + match v { + x if x.is_nan() => 0, + x if x <= i32::MIN as f32 - (1 << 8) as f32 => i32::MIN, + x if x >= (i32::MAX as f32 + 1.0) => i32::MAX, + x => x.trunc() as i32, } } fn trunc_sat_f32_to_u32(v: f32) -> u32 { - if v.is_nan() || v <= -1.0_f32 { - 0 - } else if v >= (u32::MAX as f32 + 1.0) { - u32::MAX - } else { - v.trunc() as u32 + match v { + x if x.is_nan() || x <= -1.0_f32 => 0, + x if x >= (u32::MAX as f32 + 1.0) => u32::MAX, + x => x.trunc() as u32, } } fn trunc_sat_f64_to_i32(v: f64) -> i32 { - if v.is_nan() { - 0 - } else if v <= i32::MIN as f64 - 1.0_f64 { - i32::MIN - } else if v >= (i32::MAX as f64 + 1.0) { - i32::MAX - } else { - v.trunc() as i32 + match v { + x if x.is_nan() => 0, + x if x <= i32::MIN as f64 - 1.0_f64 => i32::MIN, + x if x >= (i32::MAX as f64 + 1.0) => i32::MAX, + x => x.trunc() as i32, } } fn trunc_sat_f64_to_u32(v: f64) -> u32 { - if v.is_nan() || v <= -1.0_f64 { - 0 - } else if v >= (u32::MAX as f64 + 1.0) { - u32::MAX - } else { - v.trunc() as u32 + match v { + x if x.is_nan() || x <= -1.0_f64 => 0, + x if x >= (u32::MAX as f64 + 1.0) => u32::MAX, + x => x.trunc() as u32, } } diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index b57008c4..a127cf67 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -108,11 +108,11 @@ pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + De fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self; fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); - fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()>; - fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()>; - fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()>; fn stack_pop(stack: &mut ValueStack) -> Self; fn stack_peek(stack: &ValueStack) -> Self; + fn stack_apply1(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()>; + fn stack_apply2(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()>; + fn stack_apply3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()>; } macro_rules! impl_internalvalue { @@ -153,7 +153,14 @@ macro_rules! impl_internalvalue { } #[inline(always)] - fn stack_calculate(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { + fn stack_apply1(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { + let top = stack.$stack.last_mut(); + *top = $to_internal(func($to_outer(*top))?); + Ok(()) + } + + #[inline(always)] + fn stack_apply2(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2))?); @@ -161,20 +168,13 @@ macro_rules! impl_internalvalue { } #[inline(always)] - fn stack_calculate3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { + fn stack_apply3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { let v3 = stack.$stack.pop(); let v2 = stack.$stack.pop(); let v1 = stack.$stack.last_mut(); *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2), $to_outer(v3))?); Ok(()) } - - #[inline(always)] - fn replace_top(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { - let v = stack.$stack.last_mut(); - *v = $to_internal(func($to_outer(*v))?); - Ok(()) - } } )* }; diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index 5c08ab93..a0c5c694 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -154,7 +154,7 @@ impl MemoryInstance { } /// A trait for types that can be converted to and from static byte arrays -pub(crate) trait MemValue: Copy + Sized { +pub(crate) trait MemValue: Copy + Default { /// Store a value in memory fn to_mem_bytes(self) -> [u8; N]; @@ -168,12 +168,12 @@ macro_rules! impl_mem_traits { impl MemValue<$size> for $ty { #[inline(always)] fn from_mem_bytes(bytes: [u8; $size]) -> Self { - <$ty>::from_le_bytes(bytes.into()) + <$ty>::from_le_bytes(bytes) } #[inline(always)] fn to_mem_bytes(self) -> [u8; $size] { - self.to_le_bytes().into() + self.to_le_bytes() } } )* diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 93894359..3b682b80 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -9,69 +9,62 @@ use tinywasm_types::ExternRef; const VAL_LISTS: &[&[WasmValue]] = &[ &[], &[WasmValue::I32(0)], - &[WasmValue::I32(0), WasmValue::I32(0)], // 2 of the same - &[WasmValue::I32(0), WasmValue::I32(0), WasmValue::F64(0.0)], // add another type - &[WasmValue::I32(0), WasmValue::F64(0.0), WasmValue::I32(0)], // reorder - &[WasmValue::RefExtern(ExternRef::null()), WasmValue::F64(0.0), WasmValue::I32(0)], // all different types + &[WasmValue::I32(0), WasmValue::I32(0)], + &[WasmValue::I32(0), WasmValue::I32(0), WasmValue::F64(0.0)], + &[WasmValue::I32(0), WasmValue::F64(0.0), WasmValue::I32(0)], + &[WasmValue::RefExtern(ExternRef::null()), WasmValue::F64(0.0), WasmValue::I32(0)], ]; -// (f64, i32, i32) and (f64) can be used to "match_none" -fn get_type_lists() -> impl Iterator + Clone> + Clone { - VAL_LISTS.iter().map(|l| l.iter().map(WasmValue::val_type)) +fn value_types(values: &[WasmValue]) -> Box<[ValType]> { + values.iter().map(WasmValue::val_type).collect() } -fn get_modules() -> Vec<(Module, FuncType, Vec)> { - let mut result = Vec::<(Module, FuncType, Vec)>::new(); - let val_and_tys = get_type_lists().zip(VAL_LISTS); - for res_types in get_type_lists() { - for (arg_types, arg_vals) in val_and_tys.clone() { - let ty = FuncType { results: res_types.clone().collect(), params: arg_types.collect() }; - result.push((proxy_module(&ty), ty, arg_vals.to_vec())); + +fn module_cases() -> Vec<(Module, FuncType, Vec)> { + let mut cases = Vec::<(Module, FuncType, Vec)>::new(); + for results in VAL_LISTS { + for params in VAL_LISTS { + let func_ty = FuncType { results: value_types(results), params: value_types(params) }; + cases.push((proxy_module(&func_ty), func_ty, params.to_vec())); } } - result + cases } #[test] fn test_return_invalid_type() -> Result<()> { - // try to return from host functions types that don't match their signatures - let mod_list = get_modules(); + let cases = module_cases(); - for (module, func_ty, test_args) in mod_list { - for result_to_try in VAL_LISTS { + for (module, func_ty, args) in cases { + for returned_values in VAL_LISTS { let mut store = Store::default(); let mut imports = Imports::new(); imports - .define("host", "hfn", Extern::func(&func_ty, |_: FuncContext<'_>, _| Ok(result_to_try.to_vec()))) + .define("host", "hfn", Extern::func(&func_ty, |_: FuncContext<'_>, _| Ok(returned_values.to_vec()))) .unwrap(); let instance = module.clone().instantiate(&mut store, Some(imports)).unwrap(); let caller = instance.exported_func_untyped(&store, "call_hfn").unwrap(); - let res_types_returned = result_to_try.iter().map(WasmValue::val_type); - dbg!(&res_types_returned, &func_ty); - let res_types_expected = &func_ty.results; - let should_succeed = res_types_returned.eq(res_types_expected.iter().cloned()); - // Extern::func that returns wrong type(s) can only be detected when it runs - let call_res = caller.call(&mut store, &test_args); - dbg!(&call_res); + // Return-type mismatch is only observable at call time. + let should_succeed = returned_values.iter().map(WasmValue::val_type).eq(func_ty.results.iter().copied()); + let call_res = caller.call(&mut store, &args); assert_eq!(call_res.is_ok(), should_succeed); - println!("this time ok"); } } + Ok(()) } #[test] fn test_linking_invalid_untyped_func() -> Result<()> { - // try to import host functions with function types no matching those expected by modules - let mod_list = get_modules(); - for (module, actual_func_ty, _) in &mod_list { - for (_, func_ty_to_try, _) in &mod_list { + let cases = module_cases(); + for (module, expected_func_ty, _) in &cases { + for (_, func_ty_to_try, _) in &cases { let tried_fn = Extern::func(func_ty_to_try, |_: FuncContext<'_>, _| panic!("not intended to be called")); let mut store = Store::default(); let mut imports = Imports::new(); imports.define("host", "hfn", tried_fn).unwrap(); - let should_succeed = func_ty_to_try == actual_func_ty; + let should_succeed = func_ty_to_try == expected_func_ty; let link_res = module.clone().instantiate(&mut store, Some(imports)); assert_eq!(link_res.is_ok(), should_succeed); @@ -83,28 +76,27 @@ fn test_linking_invalid_untyped_func() -> Result<()> { #[test] fn test_linking_invalid_typed_func() -> Result<()> { type Existing = (i32, i32, f64); - type NonMatchingOne = f64; - type NonMatchingMul = (f64, i32, i32); + type NonMatchingSingle = f64; + type NonMatchingTuple = (f64, i32, i32); const DONT_CALL: &str = "not meant to be called"; - // they don't match any signature from get_modules() - #[rustfmt::skip] // to make it table-like - let matching_none= &[ - Extern::typed_func(|_, _: NonMatchingMul| -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingMul| -> tinywasm::Result<()> { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result<()> { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: Existing | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: Existing | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: () | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: () | -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result { panic!("{DONT_CALL}") } ), - Extern::typed_func(|_, _: NonMatchingOne| -> tinywasm::Result { panic!("{DONT_CALL}") } ), + // None of these typed host signatures are produced by module_cases(). + let matching_none = vec![ + Extern::typed_func(|_, _: NonMatchingTuple| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingTuple| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result<()> { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: Existing| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: Existing| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: ()| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), + Extern::typed_func(|_, _: NonMatchingSingle| -> tinywasm::Result { panic!("{DONT_CALL}") }), ]; - let mod_list = get_modules(); - for (module, _, _) in mod_list { - for typed_fn in matching_none.clone() { + let cases = module_cases(); + for (module, _, _) in cases { + for typed_fn in matching_none.iter().cloned() { let mut store = Store::default(); let mut imports = Imports::new(); imports.define("host", "hfn", typed_fn).unwrap(); @@ -113,7 +105,6 @@ fn test_linking_invalid_typed_func() -> Result<()> { } } - // the valid cases are well-checked in other tests Ok(()) } @@ -129,9 +120,6 @@ fn to_name(ty: &ValType) -> &str { } } -// make a module with imported function {module:"host", name:"hfn"} that takes specified results and returns specified params -// and 2 wasm functions: call_hfn takes params, passes them to hfn and returns it's results -// and 2 wasm functions: call_hfn_discard takes params, passes them to hfn and drops it's results fn proxy_module(func_ty: &FuncType) -> Module { let results = func_ty.results.as_ref(); let params = func_ty.params.as_ref(); @@ -139,7 +127,7 @@ fn proxy_module(func_ty: &FuncType) -> Module { if list.is_empty() { return "".to_string(); } - let step = list.iter().map(|ty| format!("{} ", to_name(ty)).to_string()).collect::(); + let step = list.iter().map(|ty| format!("{} ", to_name(ty))).collect::(); format!("({keyword} {step})") }; @@ -151,7 +139,7 @@ fn proxy_module(func_ty: &FuncType) -> Module { acc }); - let result_drops = "(drop)\n".repeat(results.len()).to_string(); + let result_drops = "(drop)\n".repeat(results.len()); let wasm_text = format!( r#"(module (import "host" "hfn" (func $host_fn {params_text} {results_text})) @@ -162,6 +150,7 @@ fn proxy_module(func_ty: &FuncType) -> Module { (func (export "call_hfn_discard") {params_text} {params_gets} (call $host_fn) + ;; Keep stack balanced for arbitrary result arity. {result_drops} ) ) diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index 01f833cf..4ee74384 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -1,21 +1,17 @@ use eyre::Result; use tinywasm::{Extern, FuncContext, Imports, Module, Store, types::FuncRef}; +const LHS: i32 = 5; +const RHS: i32 = 3; + fn main() -> Result<()> { - by_func_ref_passed()?; - by_func_ref_returned()?; + run_passed_funcref_example()?; + run_returned_funcref_example()?; Ok(()) } -/// Example of passing Wasm functions (as `funcref`) to an imported host function -/// and the imported host function calling them. -fn by_func_ref_passed() -> Result<()> { - // A module with: - // - Imported function "host.call_this" that accepts a callback. - // - Exported Wasm function "tell_host_to_call" that calls "host.call_this" with Wasm functions $add and $sub. - // - Wasm functions $add and $sub and an imported function $mul used as callbacks - // (just to show that imported functions can be referenced too). - // - Exported Wasm function "call_binop_by_ref", a proxy used by the host to call func-references of type (i32, i32) -> i32. +fn run_passed_funcref_example() -> Result<()> { + // Host receives funcref and calls it via an exported proxy. const WASM: &str = r#" (module (import "host" "call_this" (func $host_callback_caller (param funcref))) @@ -30,7 +26,7 @@ fn by_func_ref_passed() -> Result<()> { (type $binop (func (param i32 i32) (result i32))) (table 3 funcref) - (elem (i32.const 0) $add $sub $host_mul) ;; Function can only be referenced if added to a table. + (elem (i32.const 0) $add $sub $host_mul) (func $add (param $x i32) (param $y i32) (result i32) local.get $x local.get $y @@ -50,51 +46,41 @@ fn by_func_ref_passed() -> Result<()> { ) "#; - let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - // Import host function that takes callbacks and calls them. imports.define( "host", "call_this", - Extern::typed_func(|mut ctx: FuncContext<'_>, fn_ref: FuncRef| -> tinywasm::Result<()> { - let proxy_caller = + Extern::typed_func(|mut ctx: FuncContext<'_>, func_ref: FuncRef| -> tinywasm::Result<()> { + // Host cannot call a funcref directly, so it routes through Wasm. + let call_by_ref = ctx.module().exported_func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; - // Call the callback we got as an argument using "call_binop_by_ref". - let res = proxy_caller.call(ctx.store_mut(), (fn_ref, 5, 3))?; - println!("(funcref {fn_ref:?})(5,3) results in {res}"); + let result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; + println!("(funcref {func_ref:?})({LHS},{RHS}) results in {result}"); Ok(()) }), )?; - // Import host.mul function (one of the functions whose references are taken). imports.define( "host", "mul", - Extern::typed_func(|_, args: (i32, i32)| -> tinywasm::Result { Ok(args.0 * args.1) }), + Extern::typed_func(|_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }), )?; let instance = module.instantiate(&mut store, Some(imports))?; let caller = instance.exported_func::<(), ()>(&store, "tell_host_to_call")?; - // Call "tell_host_to_call". caller.call(&mut store, ())?; - // An interesting detail is that neither $add, $sub, nor $mul were exported, - // but with a little help from the proxy "call_binop_by_ref", references to them are callable by the host. + Ok(()) } -/// Example of returning a Wasm function as a callback to a host function -/// and the host function calling it. -fn by_func_ref_returned() -> Result<()> { - // A module with: - // - An exported function "what_should_host_call" that returns 3 `funcref`s. - // - Wasm functions $add and $sub and an imported function $mul used as callbacks - // (just to show that imported functions can be referenced too). - // - Another exported Wasm function "call_binop_by_ref", a proxy used by the host to call func-references of type (i32, i32) -> i32 +fn run_returned_funcref_example() -> Result<()> { + // Wasm returns funcref values, host executes them through the same proxy. const WASM: &str = r#" (module (import "host" "mul" (func $host_mul (param $x i32) (param $y i32) (result i32))) @@ -125,33 +111,30 @@ fn by_func_ref_returned() -> Result<()> { ) "#; - let wasm = wat::parse_str(WASM).expect("Failed to parse WAT"); + let wasm = wat::parse_str(WASM).expect("failed to parse wat"); let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let mut imports = Imports::new(); - // Import host.mul function (one of the possible operations). imports.define( "host", "mul", - Extern::typed_func(|_, args: (i32, i32)| -> tinywasm::Result { Ok(args.0 * args.1) }), + Extern::typed_func(|_, (lhs, rhs): (i32, i32)| -> tinywasm::Result { Ok(lhs * rhs) }), )?; let instance = module.instantiate(&mut store, Some(imports))?; - // Ask the module what to call. - let funcrefs = { - let address_getter = + let (add_ref, sub_ref, mul_ref) = { + let get_funcrefs = instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; - address_getter.call(&mut store, ())? + get_funcrefs.call(&mut store, ())? }; - let proxy_caller = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; + let call_by_ref = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; - for (idx, func_ref) in [funcrefs.0, funcrefs.1, funcrefs.2].iter().enumerate() { - // Call those `funcref`s via "call_binop_by_ref". - let res = proxy_caller.call(&mut store, (*func_ref, 5, 3))?; - println!("At idx: {idx}, funcref {func_ref:?}(5,3) results in {res}"); + for (idx, func_ref) in [add_ref, sub_ref, mul_ref].iter().enumerate() { + let result = call_by_ref.call(&mut store, (*func_ref, LHS, RHS))?; + println!("At idx: {idx}, funcref {func_ref:?}({LHS},{RHS}) results in {result}"); } Ok(()) } From ca6c66af87106b097a599e344d5c0cb538250abb Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 00:36:57 +0200 Subject: [PATCH 23/47] feat: add resumable calls / fuel tracking Signed-off-by: Henry --- crates/tinywasm/Cargo.toml | 4 + crates/tinywasm/benches/tinywasm.rs | 10 +- crates/tinywasm/benches/tinywasm_modes.rs | 102 +++++++++ crates/tinywasm/src/engine.rs | 25 +++ crates/tinywasm/src/func.rs | 236 +++++++++++++++++--- crates/tinywasm/src/imports.rs | 15 ++ crates/tinywasm/src/interpreter/executor.rs | 93 +++++++- crates/tinywasm/src/interpreter/mod.rs | 21 +- crates/tinywasm/src/interpreter/value128.rs | 2 +- crates/tinywasm/src/lib.rs | 2 +- crates/tinywasm/src/store/mod.rs | 10 +- crates/tinywasm/tests/resume_execution.rs | 116 ++++++++++ examples/resumable.rs | 46 ++++ examples/rust/Cargo.toml | 1 + examples/rust/build.sh | 15 +- examples/wasm-rust.rs | 14 +- 16 files changed, 651 insertions(+), 61 deletions(-) create mode 100644 crates/tinywasm/benches/tinywasm_modes.rs create mode 100644 crates/tinywasm/tests/resume_execution.rs create mode 100644 examples/resumable.rs diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 18f03659..f0f8ab09 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -116,3 +116,7 @@ harness=false [[bench]] name="tinywasm" harness=false + +[[bench]] +name="tinywasm_modes" +harness=false diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 82a38784..f30ad5dc 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -34,11 +34,13 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); let _twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); + let mut group = c.benchmark_group("tinywasm"); + group.measurement_time(std::time::Duration::from_secs(10)); - // c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - // c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); - // c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); - c.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); + // group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); + // group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); + // group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + group.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } criterion_group!(benches, criterion_benchmark); diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs new file mode 100644 index 00000000..bf6cbfc7 --- /dev/null +++ b/crates/tinywasm/benches/tinywasm_modes.rs @@ -0,0 +1,102 @@ +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; +use eyre::Result; +use tinywasm::engine::{Config, FuelPolicy}; +use tinywasm::types::TinyWasmModule; +use tinywasm::{Engine, ExecProgress, Extern, FuncContext, FuncHandleTyped, Imports, ModuleInstance, Store}; + +const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm"); +const FUEL_PER_ROUND: u32 = 512; +const TIME_BUDGET_PER_ROUND: core::time::Duration = core::time::Duration::from_micros(50); +const BENCH_MEASUREMENT_TIME: core::time::Duration = core::time::Duration::from_secs(10); + +fn tinywasm_parse() -> Result { + let parser = tinywasm_parser::Parser::new(); + Ok(parser.parse_module_bytes(WASM)?) +} + +fn setup_typed_func(module: TinyWasmModule, engine: Option) -> Result<(Store, FuncHandleTyped<(), ()>)> { + let mut store = match engine { + Some(engine) => Store::new(engine), + None => Store::default(), + }; + + let mut imports = Imports::default(); + imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?; + + let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports))?; + let func = instance.exported_func::<(), ()>(&store, "hello")?; + Ok((store, func)) +} + +fn run_call(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { + func.call(store, ())?; + Ok(()) +} + +fn run_resume_with_fuel(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { + let mut execution = func.call_resumable(store, ())?; + loop { + match execution.resume_with_fuel(FUEL_PER_ROUND)? { + ExecProgress::Completed(_) => return Ok(()), + ExecProgress::Suspended => {} + } + } +} + +fn run_resume_with_time_budget(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> { + let mut execution = func.call_resumable(store, ())?; + loop { + match execution.resume_with_time_budget(TIME_BUDGET_PER_ROUND)? { + ExecProgress::Completed(_) => return Ok(()), + ExecProgress::Suspended => {} + } + } +} + +fn criterion_benchmark(c: &mut Criterion) { + let module = tinywasm_parse().expect("tinywasm_parse"); + let mut group = c.benchmark_group("tinywasm_modes"); + group.measurement_time(BENCH_MEASUREMENT_TIME); + + group.bench_function("call", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), None).expect("setup call"), + |(store, func)| run_call(store, func).expect("run call"), + BatchSize::LargeInput, + ) + }); + + let per_instruction_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::PerInstruction)); + group.bench_function("resume_fuel_per_instruction", |b| { + b.iter_batched_ref( + || { + setup_typed_func(module.clone(), Some(per_instruction_engine.clone())) + .expect("setup fuel per-instruction") + }, + |(store, func)| run_resume_with_fuel(store, func).expect("run fuel per-instruction"), + BatchSize::LargeInput, + ) + }); + + let weighted_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted)); + group.bench_function("resume_fuel_weighted", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), Some(weighted_engine.clone())).expect("setup fuel weighted"), + |(store, func)| run_resume_with_fuel(store, func).expect("run fuel weighted"), + BatchSize::LargeInput, + ) + }); + + group.bench_function("resume_time_budget", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), None).expect("setup time budget"), + |(store, func)| run_resume_with_time_budget(store, func).expect("run time budget"), + BatchSize::LargeInput, + ) + }); + + group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index f03f749a..e1ea0444 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -39,6 +39,22 @@ pub(crate) struct EngineInner { // pub(crate) allocator: Box, } +/// Fuel accounting policy for budgeted execution. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum FuelPolicy { + /// Charge one fuel unit per retired instruction. + PerInstruction, + /// Charge one fuel unit per instruction plus predefined extra cost for specific operations. + Weighted, +} + +impl Default for FuelPolicy { + fn default() -> Self { + Self::PerInstruction + } +} + /// Default initial size for the 32-bit value stack (i32, f32 values). pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 64 * 1024; // 64k slots @@ -68,6 +84,8 @@ pub struct Config { pub stack_ref_size: usize, /// Initial size of the call stack. pub call_stack_size: usize, + /// Fuel accounting policy used by budgeted execution. + pub fuel_policy: FuelPolicy, } impl Config { @@ -75,6 +93,12 @@ impl Config { pub fn new() -> Self { Self::default() } + + /// Set the fuel accounting policy for budgeted execution. + pub fn fuel_policy(mut self, fuel_policy: FuelPolicy) -> Self { + self.fuel_policy = fuel_policy; + self + } } impl Default for Config { @@ -85,6 +109,7 @@ impl Default for Config { stack_128_size: DEFAULT_VALUE_STACK_128_SIZE, stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE, call_stack_size: DEFAULT_CALL_STACK_SIZE, + fuel_policy: FuelPolicy::default(), } } } diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 69d6dd2b..74610f51 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -4,6 +4,20 @@ use crate::{Function, unlikely}; use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec}; use tinywasm_types::{ExternRef, FuncRef, FuncType, ModuleInstanceAddr, ValType, WasmValue}; +#[derive(Debug, Clone, PartialEq, Eq)] +/// Progress for fuel-limited function execution. +pub enum ExecProgress { + /// Execution completed and produced a result. + Completed(T), + /// Execution suspended after exhausting fuel or time budget. + Suspended, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct ExecutionState { + pub(crate) callframe: CallFrame, +} + #[derive(Debug)] /// A function handle pub struct FuncHandle { @@ -12,31 +26,33 @@ pub struct FuncHandle { pub(crate) ty: FuncType, } +#[derive(Debug)] +/// Resumable execution for an untyped function call. +pub struct FuncExecution<'store> { + store: &'store mut Store, + state: FuncExecutionState, +} + +#[derive(Debug)] +enum FuncExecutionState { + Running { exec_state: ExecutionState, root_func_addr: u32 }, + Completed { result: Option> }, +} + +#[derive(Debug)] +/// Resumable execution for a typed function call. +pub struct FuncExecutionTyped<'store, R> { + execution: FuncExecution<'store>, + marker: core::marker::PhantomData, +} + impl FuncHandle { /// Call a function (Invocation) /// /// See #[inline] pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { - // Comments are ordered by the steps in the spec - // In this implementation, some steps are combined and ordered differently for performance reasons - - // 3. Let func_ty be the function type - let func_ty = &self.ty; - - // 4. If the length of the provided argument values is different from the number of expected arguments, then fail - if unlikely(func_ty.params.len() != params.len()) { - return Err(Error::Other(format!( - "param count mismatch: expected {}, got {}", - func_ty.params.len(), - params.len() - ))); - } - - // 5. For each value type and the corresponding value, check if types match - if !(func_ty.params.iter().zip(params).all(|(ty, param)| ty == ¶m.val_type())) { - return Err(Error::Other("Type mismatch".into())); - } + validate_call_params(&self.ty, params)?; let func_inst = store.state.get_func(self.addr); let wasm_func = match &func_inst.func { @@ -46,29 +62,153 @@ impl FuncHandle { Function::Wasm(wasm_func) => wasm_func.clone(), }; - // 6. Let f be the dummy frame - // 7. Push the frame f to the call stack - // & 8. Push the values to the stack + // Reset stack, push args, allocate locals, create entry frame. store.stack.clear(); store.stack.values.extend_from_wasmvalues(params)?; let (locals_base, _stack_base, stack_offset) = store.stack.values.enter_locals(wasm_func.params, wasm_func.locals)?; let callframe = CallFrame::new(self.addr, func_inst.owner, locals_base, stack_offset); - // 9. Invoke the function instance + // Execute until completion and then collect result values from the stack. InterpreterRuntime::exec(store, callframe)?; - // Once the function returns: - // 1. Assert: m values are on the top of the stack (Ensured by validation) - debug_assert!(store.stack.values.len() >= func_ty.results.len()); + collect_call_results(store, &self.ty) + } + + /// Call a function and return a resumable execution handle. + /// + /// The returned handle keeps a mutable borrow of the [`Store`] until it + /// completes. Use [`FuncExecution::resume_with_fuel`] (or + /// [`FuncExecution::resume_with_time_budget`] with `std`) to continue. + pub fn call_resumable<'store>( + &self, + store: &'store mut Store, + params: &[WasmValue], + ) -> Result> { + validate_call_params(&self.ty, params)?; + + let func_inst = store.state.get_func(self.addr); + let func_inst_owner = func_inst.owner; + let func = func_inst.func.clone(); + + match func { + Function::Host(host_func) => { + let result = host_func.call(FuncContext { store, module_addr: self.module_addr }, params)?; + Ok(FuncExecution { store, state: FuncExecutionState::Completed { result: Some(result) } }) + } + Function::Wasm(wasm_func) => { + store.stack.clear(); + store.stack.values.extend_from_wasmvalues(params)?; + let (locals_base, _stack_base, stack_offset) = + store.stack.values.enter_locals(wasm_func.params, wasm_func.locals)?; + let callframe = CallFrame::new(self.addr, func_inst_owner, locals_base, stack_offset); + + Ok(FuncExecution { + store, + state: FuncExecutionState::Running { + exec_state: ExecutionState { callframe }, + root_func_addr: self.addr, + }, + }) + } + } + } +} + +impl<'store> FuncExecution<'store> { + /// Resume execution with up to `fuel` units of fuel. + /// + /// Fuel is accounted in chunks, so execution may overshoot the requested + /// fuel before returning [`ExecProgress::Suspended`]. + /// + /// Returns [`ExecProgress::Suspended`] when fuel is exhausted, or + /// [`ExecProgress::Completed`] with the final values once the invocation + /// returns. + pub fn resume_with_fuel(&mut self, fuel: u32) -> Result>> { + let FuncExecutionState::Running { exec_state, root_func_addr } = &mut self.state else { + let FuncExecutionState::Completed { result } = &mut self.state else { + unreachable!("invalid function execution state") + }; + return result + .take() + .map(ExecProgress::Completed) + .ok_or_else(|| Error::Other("execution already completed".to_string())); + }; + + match InterpreterRuntime::exec_with_fuel(self.store, exec_state.callframe, fuel)? { + crate::interpreter::ExecState::Completed => { + let result_ty = self.store.state.get_func(*root_func_addr).func.ty().clone(); + let result = collect_call_results(self.store, &result_ty)?; + self.state = FuncExecutionState::Completed { result: None }; + Ok(ExecProgress::Completed(result)) + } + crate::interpreter::ExecState::Suspended(callframe) => { + exec_state.callframe = callframe; + Ok(ExecProgress::Suspended) + } + } + } + + #[cfg(feature = "std")] + /// Resume execution for at most `time_budget` wall-clock time. + /// + /// Time is checked periodically, so execution may overshoot the requested + /// time budget before returning [`ExecProgress::Suspended`]. + /// + /// Returns [`ExecProgress::Suspended`] when the budget is exhausted, or + /// [`ExecProgress::Completed`] with the final values once the invocation + /// returns. + pub fn resume_with_time_budget( + &mut self, + time_budget: crate::std::time::Duration, + ) -> Result>> { + let FuncExecutionState::Running { exec_state, root_func_addr } = &mut self.state else { + let FuncExecutionState::Completed { result } = &mut self.state else { + unreachable!("invalid function execution state") + }; + return result + .take() + .map(ExecProgress::Completed) + .ok_or_else(|| Error::Other("execution already completed".to_string())); + }; + + match InterpreterRuntime::exec_with_time_budget(self.store, exec_state.callframe, time_budget)? { + crate::interpreter::ExecState::Completed => { + let result_ty = self.store.state.get_func(*root_func_addr).func.ty().clone(); + let result = collect_call_results(self.store, &result_ty)?; + self.state = FuncExecutionState::Completed { result: None }; + Ok(ExecProgress::Completed(result)) + } + crate::interpreter::ExecState::Suspended(callframe) => { + exec_state.callframe = callframe; + Ok(ExecProgress::Suspended) + } + } + } +} - // 2. Pop m values from the stack - let mut res: Vec<_> = store.stack.values.pop_types(func_ty.results.iter().rev()).collect(); // pop in reverse order since the stack is LIFO - res.reverse(); // reverse to get the original order +fn validate_call_params(func_ty: &FuncType, params: &[WasmValue]) -> Result<()> { + if unlikely(func_ty.params.len() != params.len()) { + return Err(Error::Other(format!( + "param count mismatch: expected {}, got {}", + func_ty.params.len(), + params.len() + ))); + } - // The values are returned as the results of the invocation. - Ok(res) + if !(func_ty.params.iter().zip(params).all(|(ty, param)| ty == ¶m.val_type())) { + return Err(Error::Other("Type mismatch".into())); } + + Ok(()) +} + +fn collect_call_results(store: &mut Store, func_ty: &FuncType) -> Result> { + // m values are on the top of the stack (Ensured by validation) + debug_assert!(store.stack.values.len() >= func_ty.results.len()); + let mut res: Vec<_> = store.stack.values.pop_types(func_ty.results.iter().rev()).collect(); // pop in reverse order since the stack is LIFO + res.reverse(); // reverse to get the original order + Ok(res) } #[derive(Debug)] @@ -101,6 +241,40 @@ impl FuncHandleTyped { // Convert the Vec back to R R::from_wasm_value_tuple(&result) } + + /// Call a typed function and return a resumable execution handle. + /// + /// The handle keeps a mutable borrow of the [`Store`] until completion. + pub fn call_resumable<'store>(&self, store: &'store mut Store, params: P) -> Result> { + let wasm_values = params.into_wasm_value_tuple(); + let execution = self.func.call_resumable(store, &wasm_values)?; + Ok(FuncExecutionTyped { execution, marker: core::marker::PhantomData }) + } +} + +impl<'store, R: FromWasmValueTuple> FuncExecutionTyped<'store, R> { + /// Resume typed execution with up to `fuel` units of fuel. + /// + /// Fuel is accounted in chunks, so execution may overshoot the requested + /// fuel before returning [`ExecProgress::Suspended`]. + pub fn resume_with_fuel(&mut self, fuel: u32) -> Result> { + match self.execution.resume_with_fuel(fuel)? { + ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_value_tuple(&values)?)), + ExecProgress::Suspended => Ok(ExecProgress::Suspended), + } + } + + #[cfg(feature = "std")] + /// Resume typed execution for at most `time_budget` wall-clock time. + /// + /// Time is checked periodically, so execution may overshoot the requested + /// time budget before returning [`ExecProgress::Suspended`]. + pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result> { + match self.execution.resume_with_time_budget(time_budget)? { + ExecProgress::Completed(values) => Ok(ExecProgress::Completed(R::from_wasm_value_tuple(&values)?)), + ExecProgress::Suspended => Ok(ExecProgress::Suspended), + } + } } pub trait ValTypesFromTuple { diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index ea8b965d..7ce9f401 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -82,6 +82,21 @@ impl FuncContext<'_> { pub fn exported_memory_mut(&mut self, name: &str) -> Result> { self.module().exported_memory_mut(self.store, name) } + + /// Charge additional fuel from the currently running resumable invocation. + /// + /// This is a no-op when the current invocation is not using fuel-based + /// resumption. + pub fn charge_fuel(&mut self, fuel: u32) { + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(fuel); + } + + /// Get remaining fuel for the current invocation. + /// + /// Returns `0` when fuel-based resumption is not active. + pub fn remaining_fuel(&self) -> u32 { + self.store.execution_fuel + } } impl Debug for HostFunction { diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 34571201..2e2bd0cd 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -9,33 +9,42 @@ use core::ops::ControlFlow; use interpreter::stack::CallFrame; use tinywasm_types::*; +use super::ExecState; use super::num_helpers::*; use super::values::*; +use crate::engine::FuelPolicy; use crate::instance::ModuleInstanceInner; use crate::interpreter::Value128; use crate::*; -pub(crate) struct Executor<'store> { + +const FUEL_ACCOUNTING_INTERVAL: u32 = 1024; +#[cfg(feature = "std")] +const TIME_BUDGET_CHECK_INTERVAL: u32 = 2048; +const FUEL_COST_CALL_TOTAL: u32 = 5; + +pub(crate) struct Executor<'store, const BUDGETED: bool> { cf: CallFrame, func: Rc, module: Rc, store: &'store mut Store, } -impl<'store> Executor<'store> { +impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { pub(crate) fn new(store: &'store mut Store, cf: CallFrame) -> Result { let module = store.get_module_instance_raw(cf.module_addr).clone(); let func = store.state.get_wasm_func(cf.func_addr).clone(); Ok(Self { module, store, cf, func }) } - pub(crate) fn run_to_completion(&mut self) -> Result<()> { - loop { - if let ControlFlow::Break(res) = self.exec_next() { - return match res { - Some(e) => Err(e), - None => Ok(()), - }; - } + #[inline(always)] + fn charge_call_fuel(&mut self, total_fuel_cost: u32) { + if BUDGETED { + let extra = match self.store.engine.config().fuel_policy { + FuelPolicy::PerInstruction => 0, + FuelPolicy::Weighted => total_fuel_cost.saturating_sub(1), + }; + + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(extra); } } @@ -701,6 +710,7 @@ impl<'store> Executor<'store> { ControlFlow::Continue(()) } fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { + self.charge_call_fuel(FUEL_COST_CALL_TOTAL); let addr = self.module.resolve_func_addr(v); let func_inst = self.store.state.get_func(addr); match &func_inst.func { @@ -712,6 +722,7 @@ impl<'store> Executor<'store> { } fn exec_call_self(&mut self) -> ControlFlow> { + self.charge_call_fuel(FUEL_COST_CALL_TOTAL); let params = self.func.params; let locals = self.func.locals; @@ -745,6 +756,7 @@ impl<'store> Executor<'store> { type_addr: u32, table_addr: u32, ) -> ControlFlow> { + self.charge_call_fuel(FUEL_COST_CALL_TOTAL); // verify that the table is of the right type, this should be validated by the parser already let func_ref = { let table_idx: u32 = self.store.stack.values.pop::() as u32; @@ -1112,3 +1124,64 @@ impl<'store> Executor<'store> { table.fill(self.module.func_addrs(), i as usize, n as usize, val.into()) } } + +impl<'store> Executor<'store, false> { + #[inline(always)] + pub(crate) fn run_to_completion(&mut self) -> Result<()> { + loop { + match self.exec_next() { + ControlFlow::Continue(()) => continue, + ControlFlow::Break(res) => break res.map_or(Ok(()), Err), + } + } + } + + #[cfg(feature = "std")] + #[inline(always)] + pub(crate) fn run_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result { + use crate::std::time::Instant; + let start = Instant::now(); + if time_budget.is_zero() { + return Ok(ExecState::Suspended(self.cf)); + } + + loop { + for _ in 0..TIME_BUDGET_CHECK_INTERVAL { + match self.exec_next() { + ControlFlow::Continue(()) => {} + ControlFlow::Break(None) => return Ok(ExecState::Completed), + ControlFlow::Break(Some(e)) => return Err(e), + } + } + + if start.elapsed() >= time_budget { + return Ok(ExecState::Suspended(self.cf)); + } + } + } +} + +impl<'store> Executor<'store, true> { + #[inline(always)] + pub(crate) fn run_with_fuel(&mut self, fuel: u32) -> Result { + self.store.execution_fuel = fuel; + if self.store.execution_fuel == 0 { + return Ok(ExecState::Suspended(self.cf)); + } + + loop { + for _ in 0..FUEL_ACCOUNTING_INTERVAL { + match self.exec_next() { + ControlFlow::Continue(()) => {} + ControlFlow::Break(None) => return Ok(ExecState::Completed), + ControlFlow::Break(Some(e)) => return Err(e), + } + } + + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(FUEL_ACCOUNTING_INTERVAL); + if self.store.execution_fuel == 0 { + return Ok(ExecState::Suspended(self.cf)); + } + } + } +} diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 1a9a0cc1..8c3a840e 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -11,6 +11,12 @@ use crate::{Result, Store, interpreter::stack::CallFrame}; pub(crate) use value128::*; pub(crate) use values::*; +#[derive(Debug, Clone, Copy)] +pub(crate) enum ExecState { + Completed, + Suspended(CallFrame), +} + /// The main `TinyWasm` runtime. /// /// This is the default runtime used by `TinyWasm`. @@ -19,6 +25,19 @@ pub(crate) struct InterpreterRuntime; impl InterpreterRuntime { pub(crate) fn exec(store: &mut Store, cf: CallFrame) -> Result<()> { - executor::Executor::new(store, cf)?.run_to_completion() + executor::Executor::::new(store, cf)?.run_to_completion() + } + + pub(crate) fn exec_with_fuel(store: &mut Store, cf: CallFrame, fuel: u32) -> Result { + executor::Executor::::new(store, cf)?.run_with_fuel(fuel) + } + + #[cfg(feature = "std")] + pub(crate) fn exec_with_time_budget( + store: &mut Store, + cf: CallFrame, + time_budget: crate::std::time::Duration, + ) -> Result { + executor::Executor::::new(store, cf)?.run_with_time_budget(time_budget) } } diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index 7de4dcaf..a41d43f6 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -296,8 +296,8 @@ impl Value128 { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] #[inline(always)] + #[rustfmt::skip] fn from_wasm_v128(value: wasm::v128) -> Self { - #[rustfmt::skip] Self::from_le_bytes([ wasm::u8x16_extract_lane::<0>(value), wasm::u8x16_extract_lane::<1>(value), wasm::u8x16_extract_lane::<2>(value), wasm::u8x16_extract_lane::<3>(value), wasm::u8x16_extract_lane::<4>(value), wasm::u8x16_extract_lane::<5>(value), wasm::u8x16_extract_lane::<6>(value), wasm::u8x16_extract_lane::<7>(value), wasm::u8x16_extract_lane::<8>(value), wasm::u8x16_extract_lane::<9>(value), wasm::u8x16_extract_lane::<10>(value), wasm::u8x16_extract_lane::<11>(value), wasm::u8x16_extract_lane::<12>(value), wasm::u8x16_extract_lane::<13>(value), wasm::u8x16_extract_lane::<14>(value), wasm::u8x16_extract_lane::<15>(value)]) } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 31d52f77..b77dc090 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -91,7 +91,7 @@ pub(crate) mod log { mod error; pub use error::*; -pub use func::{FuncHandle, FuncHandleTyped}; +pub use func::{ExecProgress, FuncExecution, FuncExecutionTyped, FuncHandle, FuncHandleTyped}; pub use imports::*; pub use instance::ModuleInstance; pub use module::Module; diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index c175b8e2..b409a0c1 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -35,6 +35,7 @@ pub struct Store { module_instances: Vec>, pub(crate) engine: Engine, + pub(crate) execution_fuel: u32, pub(crate) state: State, pub(crate) stack: Stack, } @@ -54,7 +55,14 @@ impl Store { /// Create a new store pub fn new(engine: Engine) -> Self { let id = STORE_ID.fetch_add(1, Ordering::Relaxed); - Self { id, module_instances: Vec::new(), state: State::default(), stack: Stack::new(engine.config()), engine } + Self { + id, + module_instances: Vec::new(), + state: State::default(), + stack: Stack::new(engine.config()), + engine, + execution_fuel: 0, + } } /// Get a module instance by the internal id diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs new file mode 100644 index 00000000..0f45e206 --- /dev/null +++ b/crates/tinywasm/tests/resume_execution.rs @@ -0,0 +1,116 @@ +use eyre::Result; +use tinywasm::engine::{Config, FuelPolicy}; +use tinywasm::{ExecProgress, Module, types::WasmValue}; + +#[cfg(feature = "std")] +use std::time::Duration; + +const FIBONACCI_WASM: &[u8] = include_bytes!("../../../examples/rust/out/fibonacci.wasm"); +const ADD_WASM: &[u8] = include_bytes!("../../../examples/wasm/add.wasm"); + +#[test] +fn typed_resume_matches_non_budgeted_call() -> Result<()> { + let module = Module::parse_bytes(FIBONACCI_WASM)?; + + let mut store_full = tinywasm::Store::default(); + let instance_full = module.clone().instantiate(&mut store_full, None)?; + let func_full = instance_full.exported_func::(&store_full, "fibonacci_recursive")?; + let expected = func_full.call(&mut store_full, 20)?; + + let mut store_budgeted = tinywasm::Store::default(); + let instance_budgeted = module.instantiate(&mut store_budgeted, None)?; + let func_budgeted = instance_budgeted.exported_func::(&store_budgeted, "fibonacci_recursive")?; + + let mut exec = func_budgeted.call_resumable(&mut store_budgeted, 20)?; + let mut saw_suspended = false; + let actual = loop { + match exec.resume_with_fuel(64)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => saw_suspended = true, + } + }; + + assert!(saw_suspended, "expected at least one suspension for recursive fibonacci"); + assert_eq!(actual, expected); + + Ok(()) +} + +#[test] +fn untyped_resume_supports_zero_fuel() -> Result<()> { + let module = Module::parse_bytes(ADD_WASM)?; + let mut store = tinywasm::Store::default(); + let instance = module.instantiate(&mut store, None)?; + let func = instance.exported_func_untyped(&store, "add")?; + + let mut exec = func.call_resumable(&mut store, &[WasmValue::I32(20), WasmValue::I32(22)])?; + assert!(matches!(exec.resume_with_fuel(0)?, ExecProgress::Suspended)); + + match exec.resume_with_fuel(16)? { + ExecProgress::Completed(values) => assert_eq!(values, vec![WasmValue::I32(42)]), + ExecProgress::Suspended => panic!("expected completion"), + } + + Ok(()) +} + +#[test] +fn weighted_call_fuel_requires_more_rounds() -> Result<()> { + let module = Module::parse_bytes(FIBONACCI_WASM)?; + + let mut per_instr_store = tinywasm::Store::default(); + let instance_per_instr = module.clone().instantiate(&mut per_instr_store, None)?; + let func_per_instr = instance_per_instr.exported_func::(&per_instr_store, "fibonacci_recursive")?; + + let mut weighted_store = + tinywasm::Store::new(tinywasm::Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted))); + let instance_weighted = module.instantiate(&mut weighted_store, None)?; + let func_weighted = instance_weighted.exported_func::(&weighted_store, "fibonacci_recursive")?; + + let fuel = 64; + let n = 20; + + let mut per_exec = func_per_instr.call_resumable(&mut per_instr_store, n)?; + let mut per_rounds = 0; + let per_result = loop { + per_rounds += 1; + match per_exec.resume_with_fuel(fuel)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => {} + } + }; + + let mut weighted_exec = func_weighted.call_resumable(&mut weighted_store, n)?; + let mut weighted_rounds = 0; + let weighted_result = loop { + weighted_rounds += 1; + match weighted_exec.resume_with_fuel(fuel)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => {} + } + }; + + assert_eq!(weighted_result, per_result); + assert!(weighted_rounds >= per_rounds, "weighted call fuel should not use fewer rounds than per-instruction"); + + Ok(()) +} + +#[cfg(feature = "std")] +#[test] +fn time_budget_zero_suspends_then_completes() -> Result<()> { + let module = Module::parse_bytes(ADD_WASM)?; + let mut store = tinywasm::Store::default(); + let instance = module.instantiate(&mut store, None)?; + let func = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + + let mut exec = func.call_resumable(&mut store, (20, 22))?; + assert!(matches!(exec.resume_with_time_budget(Duration::ZERO)?, ExecProgress::Suspended)); + + match exec.resume_with_time_budget(Duration::from_millis(1))? { + ExecProgress::Completed(value) => assert_eq!(value, 42), + ExecProgress::Suspended => panic!("expected completion"), + } + + Ok(()) +} diff --git a/examples/resumable.rs b/examples/resumable.rs new file mode 100644 index 00000000..6838e20f --- /dev/null +++ b/examples/resumable.rs @@ -0,0 +1,46 @@ +use eyre::Result; +use tinywasm::{ExecProgress, Module, Store}; + +const WASM: &str = r#" +(module + (func (export "count_down") (param $n i32) (result i32) + (local $cur i32) + local.get $n + local.set $cur + block + loop + local.get $cur + i32.eqz + br_if 1 + local.get $cur + i32.const 1 + i32.sub + local.set $cur + br 0 + end + end + local.get $cur)) +"#; + +fn main() -> Result<()> { + let wasm = wat::parse_str(WASM)?; + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + let count_down = instance.exported_func::(&store, "count_down")?; + + let mut execution = count_down.call_resumable(&mut store, 10_000)?; + let fuel_per_round = 128; + let mut fuel_rounds = 0; + + let result = loop { + fuel_rounds += 1; + match execution.resume_with_fuel(fuel_per_round)? { + ExecProgress::Completed(value) => break value, + ExecProgress::Suspended => {} + } + }; + + println!("completed in {fuel_rounds} rounds of {fuel_per_round} fuel, result={result}"); + Ok(()) +} diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 430bcadc..6bf8a8f7 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -52,3 +52,4 @@ lto="fat" codegen-units=1 panic="abort" inherits="release" +strip=true diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 9df54478..4574d23c 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -2,11 +2,12 @@ cd "$(dirname "$0")" || exit bins=("host_fn" "hello" "fibonacci" "print" "tinywasm" "argon2id") +exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" -rust_features="+sign-ext,+simd128,+reference-types,+bulk-memory,+bulk-memory-opt,+multimemory,+call-indirect-overlong,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint,+extended-const,+tail-call" -# wasmopt_features="--enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int" +rust_features="+simd128,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" +wasmopt_features="--enable-simd --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int --duplicate-function-elimination" # ensure out dir exists mkdir -p "$dest_dir" @@ -16,8 +17,12 @@ cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profi cp "$out_dir/tinywasm_no_std.wasm" "$dest_dir/" for bin in "${bins[@]}"; do - RUSTFLAGS="-C target-feature=$rust_features -C panic=abort" cargo build --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" + RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none -C target-feature=$rust_features -C panic=abort" cargo build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" - # wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 $wasmopt_features -done + wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 -Oz $wasmopt_features + + if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then + wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" + fi +done \ No newline at end of file diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 4d8cdf4f..49792b00 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -72,7 +72,7 @@ fn main() -> Result<()> { } fn tinywasm() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/tinywasm.wasm")?; + let module = Module::parse_file("./examples/rust/out/tinywasm.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -87,7 +87,7 @@ fn tinywasm() -> Result<()> { } fn tinywasm_no_std() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/tinywasm_no_std.wasm")?; + let module = Module::parse_file("./examples/rust/out/tinywasm_no_std.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -102,7 +102,7 @@ fn tinywasm_no_std() -> Result<()> { } fn hello() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/hello.wasm")?; + let module = Module::parse_file("./examples/rust/out/hello.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -131,7 +131,7 @@ fn hello() -> Result<()> { } fn host_fn() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/host_fn.wasm")?; + let module = Module::parse_file("./examples/rust/out/host_fn.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); imports.define( @@ -151,7 +151,7 @@ fn host_fn() -> Result<()> { } fn printi32() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/print.wasm")?; + let module = Module::parse_file("./examples/rust/out/print.opt.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); @@ -172,7 +172,7 @@ fn printi32() -> Result<()> { } fn fibonacci() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/fibonacci.wasm")?; + let module = Module::parse_file("./examples/rust/out/fibonacci.opt.wasm")?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; @@ -185,7 +185,7 @@ fn fibonacci() -> Result<()> { } fn argon2id() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/argon2id.wasm")?; + let module = Module::parse_file("./examples/rust/out/argon2id.opt.wasm")?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; From bb439dae3c6dd5ea203f904cb99585e06d32beb5 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 01:59:58 +0200 Subject: [PATCH 24/47] chore: try to make simd ops more likely to autovectorize Signed-off-by: Henry --- .github/workflows/test.yaml | 17 +- crates/tinywasm/benches/argon2id.rs | 8 +- crates/tinywasm/benches/fibonacci.rs | 8 +- crates/tinywasm/benches/tinywasm.rs | 8 +- crates/tinywasm/src/engine.rs | 8 +- crates/tinywasm/src/interpreter/value128.rs | 496 +++++++++++--------- 6 files changed, 290 insertions(+), 255 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0a75fb2b..02d47ceb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,13 +13,14 @@ jobs: name: Build wasm runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4 with: - toolchain: stable + toolchain: nightly + components: rust-src rustflags: "" target: wasm32-unknown-unknown - name: Install Binaryen and WABT @@ -27,7 +28,7 @@ jobs: - name: Build wasm run: ./examples/rust/build.sh - name: Save artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: wasm path: examples/rust/out @@ -67,12 +68,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@150fca883cd4034361b621bd4e6a9d34e5143606 # v1.15.4 with: toolchain: ${{ matrix.rust }} rustflags: "" @@ -80,7 +81,7 @@ jobs: if: matrix.target == '' - name: Load wasm - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: wasm path: examples/rust/out @@ -94,7 +95,7 @@ jobs: if: matrix.target == '' - name: Run tests (${{ matrix.target }}) - uses: houseabsolute/actions-rust-cross@v0.0.13 + uses: houseabsolute/actions-rust-cross@a8cc74d61047fa553b4e908b4b10e70029f00ca6 # v1.0.6 with: command: test target: ${{ matrix.target }} diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index 4521722c..74c51e3b 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -31,11 +31,11 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = argon2id_parse().expect("argon2id_parse"); - let _twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); + let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); - // c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); - // c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); - // c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); + c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); + c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); + c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); c.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone()))); } diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index ba1b3188..75d17a43 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -36,11 +36,11 @@ fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> fn criterion_benchmark(c: &mut Criterion) { let module = fibonacci_parse().expect("fibonacci_parse"); - let _twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm"); + let twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm"); - // c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); - // c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); - // c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); + c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); + c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); + c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); c.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60))); c.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26))); } diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index f30ad5dc..73096a50 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -33,13 +33,13 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); - let _twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); + let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); let mut group = c.benchmark_group("tinywasm"); group.measurement_time(std::time::Duration::from_secs(10)); - // group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); - // group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); - // group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); + group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); + group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); group.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index e1ea0444..80936bfb 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -42,19 +42,15 @@ pub(crate) struct EngineInner { /// Fuel accounting policy for budgeted execution. #[derive(Debug, Clone, Copy)] #[non_exhaustive] +#[derive(Default)] pub enum FuelPolicy { /// Charge one fuel unit per retired instruction. + #[default] PerInstruction, /// Charge one fuel unit per instruction plus predefined extra cost for specific operations. Weighted, } -impl Default for FuelPolicy { - fn default() -> Self { - Self::PerInstruction - } -} - /// Default initial size for the 32-bit value stack (i32, f32 values). pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 64 * 1024; // 64k slots diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index a41d43f6..9ccb7fc9 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -32,7 +32,11 @@ macro_rules! simd_wrapping_binop { let a = self.$as_lanes(); let b = rhs.$as_lanes(); - Self::$from_lanes(core::array::from_fn(|i| a[i].$op(b[i]))) + let mut out = [0 as $lane_ty; $lane_count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = lhs.$op(rhs); + } + Self::$from_lanes(out) } }; } @@ -46,40 +50,11 @@ macro_rules! simd_sat_binop { let a = self.$as_lanes(); let b = rhs.$as_lanes(); - Self::$from_lanes(core::array::from_fn(|i| a[i].$op(b[i]))) - } - }; -} - -macro_rules! simd_all_true { - ($name:ident, $doc:literal, $as_lanes:ident, $count:expr) => { - #[doc(alias = $doc)] - pub const fn $name(self) -> bool { - let lanes = self.$as_lanes(); - let mut i = 0; - while i < $count { - if lanes[i] == 0 { - return false; - } - i += 1; + let mut out = [0 as $lane_ty; $lane_count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = lhs.$op(rhs); } - true - } - }; -} - -macro_rules! simd_bitmask { - ($name:ident, $doc:literal, $as_lanes:ident, $count:expr) => { - #[doc(alias = $doc)] - pub const fn $name(self) -> u32 { - let lanes = self.$as_lanes(); - let mut mask = 0u32; - let mut i = 0; - while i < $count { - mask |= ((lanes[i] < 0) as u32) << i; - i += 1; - } - mask + Self::$from_lanes(out) } }; } @@ -87,14 +62,12 @@ macro_rules! simd_bitmask { macro_rules! simd_shift_left { ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => { #[doc(alias = $doc)] - pub const fn $name(self, shift: u32) -> Self { + pub fn $name(self, shift: u32) -> Self { let lanes = self.$as_lanes(); let s = shift & $mask; let mut out = [0 as $lane_ty; $count]; - let mut i = 0; - while i < $count { - out[i] = lanes[i].wrapping_shl(s); - i += 1; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane.wrapping_shl(s); } Self::$from_lanes(out) } @@ -104,14 +77,12 @@ macro_rules! simd_shift_left { macro_rules! simd_shift_right { ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => { #[doc(alias = $doc)] - pub const fn $name(self, shift: u32) -> Self { + pub fn $name(self, shift: u32) -> Self { let lanes = self.$as_lanes(); let s = shift & $mask; let mut out = [0 as $lane_ty; $count]; - let mut i = 0; - while i < $count { - out[i] = lanes[i] >> s; - i += 1; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane >> s; } Self::$from_lanes(out) } @@ -127,7 +98,11 @@ macro_rules! simd_avgr_u { let a = self.$as_lanes(); let b = rhs.$as_lanes(); - Self::$from_lanes(core::array::from_fn(|i| ((a[i] as $wide_ty + b[i] as $wide_ty + 1) >> 1) as $lane_ty)) + let mut out = [0 as $lane_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = ((lhs as $wide_ty + rhs as $wide_ty + 1) >> 1) as $lane_ty; + } + Self::$from_lanes(out) } }; } @@ -135,13 +110,11 @@ macro_rules! simd_avgr_u { macro_rules! simd_extend_cast { ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { #[doc(alias = $doc)] - pub const fn $name(self) -> Self { + pub fn $name(self) -> Self { let lanes = self.$src_as(); let mut out = [0 as $dst_ty; $dst_count]; - let mut i = 0; - while i < $dst_count { - out[i] = lanes[i + $offset] as $dst_ty; - i += 1; + for (dst, src) in out.iter_mut().zip(lanes[$offset..($offset + $dst_count)].iter()) { + *dst = *src as $dst_ty; } Self::$dst_from(out) } @@ -151,14 +124,16 @@ macro_rules! simd_extend_cast { macro_rules! simd_extmul_signed { ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { #[doc(alias = $doc)] - pub const fn $name(self, rhs: Self) -> Self { + pub fn $name(self, rhs: Self) -> Self { let a = self.$src_as(); let b = rhs.$src_as(); let mut out = [0 as $dst_ty; $dst_count]; - let mut i = 0; - while i < $dst_count { - out[i] = (a[i + $offset] as $dst_ty).wrapping_mul(b[i + $offset] as $dst_ty); - i += 1; + for ((dst, lhs), rhs) in out + .iter_mut() + .zip(a[$offset..($offset + $dst_count)].iter()) + .zip(b[$offset..($offset + $dst_count)].iter()) + { + *dst = (*lhs as $dst_ty).wrapping_mul(*rhs as $dst_ty); } Self::$dst_from(out) } @@ -168,14 +143,16 @@ macro_rules! simd_extmul_signed { macro_rules! simd_extmul_unsigned { ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { #[doc(alias = $doc)] - pub const fn $name(self, rhs: Self) -> Self { + pub fn $name(self, rhs: Self) -> Self { let a = self.$src_as(); let b = rhs.$src_as(); let mut out = [0 as $dst_ty; $dst_count]; - let mut i = 0; - while i < $dst_count { - out[i] = (a[i + $offset] as $dst_ty) * (b[i + $offset] as $dst_ty); - i += 1; + for ((dst, lhs), rhs) in out + .iter_mut() + .zip(a[$offset..($offset + $dst_count)].iter()) + .zip(b[$offset..($offset + $dst_count)].iter()) + { + *dst = (*lhs as $dst_ty) * (*rhs as $dst_ty); } Self::$dst_from(out) } @@ -191,7 +168,11 @@ macro_rules! simd_cmp_mask { let a = self.$as_lanes(); let b = rhs.$as_lanes(); - Self::$from_lanes(core::array::from_fn(|i| if a[i] $cmp b[i] { -1 } else { 0 })) + let mut out = [0 as $out_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { -1 } else { 0 }; + } + Self::$from_lanes(out) } }; } @@ -199,14 +180,12 @@ macro_rules! simd_cmp_mask { macro_rules! simd_cmp_mask_const { ($name:ident, $doc:literal, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { #[doc(alias = $doc)] - pub const fn $name(self, rhs: Self) -> Self { + pub fn $name(self, rhs: Self) -> Self { let a = self.$as_lanes(); let b = rhs.$as_lanes(); let mut out = [0 as $out_ty; $count]; - let mut i = 0; - while i < $count { - out[i] = if a[i] $cmp b[i] { -1 } else { 0 }; - i += 1; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { -1 } else { 0 }; } Self::$from_lanes(out) } @@ -225,13 +204,11 @@ macro_rules! simd_cmp_delegate { macro_rules! simd_abs_const { ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { #[doc(alias = $doc)] - pub const fn $name(self) -> Self { + pub fn $name(self) -> Self { let a = self.$as_lanes(); let mut out = [0 as $lane_ty; $count]; - let mut i = 0; - while i < $count { - out[i] = a[i].wrapping_abs(); - i += 1; + for (dst, lane) in out.iter_mut().zip(a) { + *dst = lane.wrapping_abs(); } Self::$from_lanes(out) } @@ -246,7 +223,11 @@ macro_rules! simd_neg { return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128())); let a = self.$as_lanes(); - Self::$from_lanes(core::array::from_fn(|i| a[i].wrapping_neg())) + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(a) { + *dst = lane.wrapping_neg(); + } + Self::$from_lanes(out) } }; } @@ -260,7 +241,11 @@ macro_rules! simd_minmax { let a = self.$as_lanes(); let b = rhs.$as_lanes(); - Self::$from_lanes(core::array::from_fn(|i| if a[i] $cmp b[i] { a[i] } else { b[i] })) + let mut out = [0 as $lane_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { lhs } else { rhs }; + } + Self::$from_lanes(out) } }; } @@ -442,47 +427,62 @@ impl Value128 { #[inline] fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { - let lanes = self.as_f32x4(); - Self::from_f32x4(core::array::from_fn(|i| op(lanes[i]))) + let bytes = self.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + for (src, dst) in bytes.chunks_exact(4).zip(out_bytes.chunks_exact_mut(4)) { + let lane = f32::from_bits(u32::from_le_bytes([src[0], src[1], src[2], src[3]])); + dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); + } + Self::from_le_bytes(out_bytes) } #[inline] fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { - let a = self.as_f32x4(); - let b = rhs.as_f32x4(); - Self::from_f32x4(core::array::from_fn(|i| op(a[i], b[i]))) + let a_bytes = self.to_le_bytes(); + let b_bytes = rhs.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + + for ((a, b), dst) in a_bytes.chunks_exact(4).zip(b_bytes.chunks_exact(4)).zip(out_bytes.chunks_exact_mut(4)) { + let a_lane = f32::from_bits(u32::from_le_bytes([a[0], a[1], a[2], a[3]])); + let b_lane = f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])); + dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); + } + + Self::from_le_bytes(out_bytes) } #[inline] fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { - let lanes = self.as_f64x2(); - Self::from_f64x2(core::array::from_fn(|i| op(lanes[i]))) + let bytes = self.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + for (src, dst) in bytes.chunks_exact(8).zip(out_bytes.chunks_exact_mut(8)) { + let lane = + f64::from_bits(u64::from_le_bytes([src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7]])); + dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); + } + Self::from_le_bytes(out_bytes) } #[inline] fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { - let a = self.as_f64x2(); - let b = rhs.as_f64x2(); - Self::from_f64x2(core::array::from_fn(|i| op(a[i], b[i]))) - } + let a_bytes = self.to_le_bytes(); + let b_bytes = rhs.to_le_bytes(); + let mut out_bytes = [0u8; 16]; - #[inline] - const fn reduce_or(self) -> u8 { - let mut result = 0u8; - let bytes = self.to_le_bytes(); - let mut i = 0; - while i < 16 { - result |= bytes[i]; - i += 1; + for ((a, b), dst) in a_bytes.chunks_exact(8).zip(b_bytes.chunks_exact(8)).zip(out_bytes.chunks_exact_mut(8)) { + let a_lane = f64::from_bits(u64::from_le_bytes([a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]])); + let b_lane = f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])); + dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); } - result + + Self::from_le_bytes(out_bytes) } #[doc(alias = "v128.any_true")] pub fn v128_any_true(self) -> bool { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] return wasm::v128_any_true(self.to_wasm_v128()); - self.reduce_or() != 0 + self.0 != 0 } #[doc(alias = "v128.not")] @@ -595,91 +595,159 @@ impl Value128 { pub fn i8x16_swizzle(self, s: Self) -> Self { #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); - let a_bytes = self.to_le_bytes(); - let s_bytes = s.to_le_bytes(); - let mut result_bytes = [0u8; 16]; + + let a = self.to_le_bytes(); + let idx = s.to_le_bytes(); + let mut out = [0u8; 16]; let mut i = 0; while i < 16 { - let index = s_bytes[i] as usize; - result_bytes[i] = if index < 16 { a_bytes[index] } else { 0 }; + let j = idx[i]; + let lane = a[(j & 0x0f) as usize]; + out[i] = if j < 16 { lane } else { 0 }; i += 1; } - Self::from_le_bytes(result_bytes) + Self::from_le_bytes(out) } #[doc(alias = "i8x16.shuffle")] pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { - let a_bytes = a.to_le_bytes(); - let b_bytes = b.to_le_bytes(); - let mut result_bytes = [0u8; 16]; + let mut src = [0u8; 32]; + src[..16].copy_from_slice(&a.to_le_bytes()); + src[16..].copy_from_slice(&b.to_le_bytes()); + let mut out = [0u8; 16]; for i in 0..16 { - let index = (idx[i] & 31) as usize; - result_bytes[i] = if index < 16 { a_bytes[index] } else { b_bytes[index - 16] }; + out[i] = src[(idx[i] & 31) as usize]; } - Self::from_le_bytes(result_bytes) + Self::from_le_bytes(out) } - pub const fn splat_i8(src: i8) -> Self { - let mut result_bytes = [0u8; 16]; - let byte = src as u8; - let mut i = 0; - while i < 16 { - result_bytes[i] = byte; - i += 1; - } - Self::from_le_bytes(result_bytes) + pub fn splat_i8(src: i8) -> Self { + Self::from_le_bytes([src as u8; 16]) } #[doc(alias = "i8x16.replace_lane")] - pub const fn i8x16_replace_lane(self, lane: u8, value: i8) -> Self { + pub fn i8x16_replace_lane(self, lane: u8, value: i8) -> Self { self.replace_lane_bytes::<1>(lane, [value as u8], 16) } #[doc(alias = "i16x8.replace_lane")] - pub const fn i16x8_replace_lane(self, lane: u8, value: i16) -> Self { + pub fn i16x8_replace_lane(self, lane: u8, value: i16) -> Self { self.replace_lane_bytes::<2>(lane, value.to_le_bytes(), 8) } #[doc(alias = "i32x4.replace_lane")] - pub const fn i32x4_replace_lane(self, lane: u8, value: i32) -> Self { + pub fn i32x4_replace_lane(self, lane: u8, value: i32) -> Self { self.replace_lane_bytes::<4>(lane, value.to_le_bytes(), 4) } #[doc(alias = "i64x2.replace_lane")] - pub const fn i64x2_replace_lane(self, lane: u8, value: i64) -> Self { + pub fn i64x2_replace_lane(self, lane: u8, value: i64) -> Self { self.replace_lane_bytes::<8>(lane, value.to_le_bytes(), 2) } #[doc(alias = "f32x4.replace_lane")] - pub const fn f32x4_replace_lane(self, lane: u8, value: f32) -> Self { + pub fn f32x4_replace_lane(self, lane: u8, value: f32) -> Self { self.replace_lane_bytes::<4>(lane, value.to_bits().to_le_bytes(), 4) } #[doc(alias = "f64x2.replace_lane")] - pub const fn f64x2_replace_lane(self, lane: u8, value: f64) -> Self { + pub fn f64x2_replace_lane(self, lane: u8, value: f64) -> Self { self.replace_lane_bytes::<8>(lane, value.to_bits().to_le_bytes(), 2) } - simd_all_true!(i8x16_all_true, "i8x16.all_true", as_i8x16, 16); - simd_all_true!(i16x8_all_true, "i16x8.all_true", as_i16x8, 8); - simd_all_true!(i32x4_all_true, "i32x4.all_true", as_i32x4, 4); - simd_all_true!(i64x2_all_true, "i64x2.all_true", as_i64x2, 2); + #[doc(alias = "i8x16.all_true")] + pub fn i8x16_all_true(self) -> bool { + for byte in self.to_le_bytes() { + if byte == 0 { + return false; + } + } + true + } - simd_bitmask!(i8x16_bitmask, "i8x16.bitmask", as_i8x16, 16); - simd_bitmask!(i16x8_bitmask, "i16x8.bitmask", as_i16x8, 8); - simd_bitmask!(i32x4_bitmask, "i32x4.bitmask", as_i32x4, 4); - simd_bitmask!(i64x2_bitmask, "i64x2.bitmask", as_i64x2, 2); + #[doc(alias = "i16x8.all_true")] + pub fn i16x8_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(2) { + if u16::from_le_bytes([lane[0], lane[1]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i32x4.all_true")] + pub fn i32x4_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(4) { + if u32::from_le_bytes([lane[0], lane[1], lane[2], lane[3]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i64x2.all_true")] + pub fn i64x2_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(8) { + if u64::from_le_bytes([lane[0], lane[1], lane[2], lane[3], lane[4], lane[5], lane[6], lane[7]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i8x16.bitmask")] + pub fn i8x16_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, byte) in bytes.into_iter().enumerate() { + if (byte & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i16x8.bitmask")] + pub fn i16x8_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, lane) in bytes.chunks_exact(2).enumerate() { + if (lane[1] & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i32x4.bitmask")] + pub fn i32x4_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, lane) in bytes.chunks_exact(4).enumerate() { + if (lane[3] & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i64x2.bitmask")] + pub fn i64x2_bitmask(self) -> u32 { + let x = u128::from_le_bytes(self.to_le_bytes()); + (((x >> 63) & 1) as u32) | ((((x >> 127) & 1) as u32) << 1) + } #[doc(alias = "i8x16.popcnt")] - pub const fn i8x16_popcnt(self) -> Self { - let lanes = self.as_u8x16(); + pub fn i8x16_popcnt(self) -> Self { + let lanes = self.to_le_bytes(); let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - out[i] = lanes[i].count_ones() as u8; - i += 1; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane.count_ones() as u8; } - Self::from_u8x16(out) + Self::from_le_bytes(out) } simd_shift_left!(i8x16_shl, "i8x16.shl", i8, 16, as_i8x16, from_i8x16, 7); @@ -722,109 +790,93 @@ impl Value128 { simd_avgr_u!(i16x8_avgr_u, "i16x8.avgr_u", u16x8_avgr, u16, u32, 8, as_u16x8, from_u16x8); #[doc(alias = "i8x16.narrow_i16x8_s")] - pub const fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { + pub fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { let av = a.as_i16x8(); let bv = b.as_i16x8(); let mut out = [0i8; 16]; - let mut i = 0; - while i < 8 { - out[i] = saturate_i16_to_i8(av[i]); - out[i + 8] = saturate_i16_to_i8(bv[i]); - i += 1; + let (lo, hi) = out.split_at_mut(8); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i16_to_i8(a_lane); + *dst_hi = saturate_i16_to_i8(b_lane); } Self::from_i8x16(out) } #[doc(alias = "i8x16.narrow_i16x8_u")] - pub const fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { + pub fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { let av = a.as_i16x8(); let bv = b.as_i16x8(); let mut out = [0u8; 16]; - let mut i = 0; - while i < 8 { - out[i] = saturate_i16_to_u8(av[i]); - out[i + 8] = saturate_i16_to_u8(bv[i]); - i += 1; + let (lo, hi) = out.split_at_mut(8); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i16_to_u8(a_lane); + *dst_hi = saturate_i16_to_u8(b_lane); } Self::from_u8x16(out) } #[doc(alias = "i16x8.narrow_i32x4_s")] - pub const fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { + pub fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { let av = a.as_i32x4(); let bv = b.as_i32x4(); let mut out = [0i16; 8]; - let mut i = 0; - while i < 4 { - out[i] = saturate_i32_to_i16(av[i]); - out[i + 4] = saturate_i32_to_i16(bv[i]); - i += 1; + let (lo, hi) = out.split_at_mut(4); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i32_to_i16(a_lane); + *dst_hi = saturate_i32_to_i16(b_lane); } Self::from_i16x8(out) } #[doc(alias = "i16x8.narrow_i32x4_u")] - pub const fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { + pub fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { let av = a.as_i32x4(); let bv = b.as_i32x4(); let mut out = [0u16; 8]; - let mut i = 0; - while i < 4 { - out[i] = saturate_i32_to_u16(av[i]); - out[i + 4] = saturate_i32_to_u16(bv[i]); - i += 1; + let (lo, hi) = out.split_at_mut(4); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i32_to_u16(a_lane); + *dst_hi = saturate_i32_to_u16(b_lane); } Self::from_u16x8(out) } #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] - pub const fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { + pub fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { let lanes = self.as_i8x16(); let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - let j = i * 2; - out[i] = lanes[j] as i16 + lanes[j + 1] as i16; - i += 1; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as i16 + pair[1] as i16; } Self::from_i16x8(out) } #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] - pub const fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { + pub fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { let lanes = self.as_u8x16(); let mut out = [0u16; 8]; - let mut i = 0; - while i < 8 { - let j = i * 2; - out[i] = lanes[j] as u16 + lanes[j + 1] as u16; - i += 1; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as u16 + pair[1] as u16; } Self::from_u16x8(out) } #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] - pub const fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { + pub fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { let lanes = self.as_i16x8(); let mut out = [0i32; 4]; - let mut i = 0; - while i < 4 { - let j = i * 2; - out[i] = lanes[j] as i32 + lanes[j + 1] as i32; - i += 1; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as i32 + pair[1] as i32; } Self::from_i32x4(out) } #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] - pub const fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { + pub fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { let lanes = self.as_u16x8(); let mut out = [0u32; 4]; - let mut i = 0; - while i < 4 { - let j = i * 2; - out[i] = lanes[j] as u32 + lanes[j + 1] as u32; - i += 1; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as u32 + pair[1] as u32; } Self::from_u32x4(out) } @@ -856,35 +908,34 @@ impl Value128 { simd_extmul_unsigned!(i64x2_extmul_high_i32x4_u, "i64x2.extmul_high_i32x4_u", as_u32x4, from_u64x2, u64, 2, 2); #[doc(alias = "i16x8.q15mulr_sat_s")] - pub const fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { + pub fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { let a = self.as_i16x8(); let b = rhs.as_i16x8(); let mut out = [0i16; 8]; - let mut i = 0; - while i < 8 { - let r = ((a[i] as i32 * b[i] as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding - out[i] = if r > i16::MAX as i32 { + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + let r = ((lhs as i32 * rhs as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding + *dst = if r > i16::MAX as i32 { i16::MAX } else if r < i16::MIN as i32 { i16::MIN } else { r as i16 }; - i += 1; } Self::from_i16x8(out) } #[doc(alias = "i32x4.dot_i16x8_s")] - pub const fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { + pub fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { let a = self.as_i16x8(); let b = rhs.as_i16x8(); - Self::from_i32x4([ - (a[0] as i32).wrapping_mul(b[0] as i32).wrapping_add((a[1] as i32).wrapping_mul(b[1] as i32)), - (a[2] as i32).wrapping_mul(b[2] as i32).wrapping_add((a[3] as i32).wrapping_mul(b[3] as i32)), - (a[4] as i32).wrapping_mul(b[4] as i32).wrapping_add((a[5] as i32).wrapping_mul(b[5] as i32)), - (a[6] as i32).wrapping_mul(b[6] as i32).wrapping_add((a[7] as i32).wrapping_mul(b[7] as i32)), - ]) + let mut out = [0i32; 4]; + for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { + *dst = (a_pair[0] as i32) + .wrapping_mul(b_pair[0] as i32) + .wrapping_add((a_pair[1] as i32).wrapping_mul(b_pair[1] as i32)); + } + Self::from_i32x4(out) } simd_cmp_mask!(i8x16_eq, "i8x16.eq", i8x16_eq, i8, 16, as_i8x16, from_i8x16, ==); @@ -957,12 +1008,12 @@ impl Value128 { simd_cmp_mask_const!(f64x2_lt, "f64x2.lt", i64, 2, as_f64x2, from_i64x2, <); #[doc(alias = "f32x4.gt")] - pub const fn f32x4_gt(self, rhs: Self) -> Self { + pub fn f32x4_gt(self, rhs: Self) -> Self { rhs.f32x4_lt(self) } #[doc(alias = "f64x2.gt")] - pub const fn f64x2_gt(self, rhs: Self) -> Self { + pub fn f64x2_gt(self, rhs: Self) -> Self { rhs.f64x2_lt(self) } @@ -1077,91 +1128,78 @@ impl Value128 { Self::from_f64x2([v[0] as f64, v[1] as f64]) } - pub const fn splat_i16(src: i16) -> Self { + pub fn splat_i16(src: i16) -> Self { Self::from_i16x8([src; 8]) } - pub const fn splat_i32(src: i32) -> Self { + pub fn splat_i32(src: i32) -> Self { Self::from_i32x4([src; 4]) } - pub const fn splat_i64(src: i64) -> Self { + pub fn splat_i64(src: i64) -> Self { Self::from_i64x2([src; 2]) } - pub const fn splat_f32(src: f32) -> Self { + pub fn splat_f32(src: f32) -> Self { Self::splat_i32(src.to_bits() as i32) } - pub const fn splat_f64(src: f64) -> Self { + pub fn splat_f64(src: f64) -> Self { Self::splat_i64(src.to_bits() as i64) } - pub const fn extract_lane_i8(self, lane: u8) -> i8 { + pub fn extract_lane_i8(self, lane: u8) -> i8 { debug_assert!(lane < 16); let lane = lane as usize; let bytes = self.to_le_bytes(); bytes[lane] as i8 } - pub const fn extract_lane_u8(self, lane: u8) -> u8 { + pub fn extract_lane_u8(self, lane: u8) -> u8 { debug_assert!(lane < 16); let lane = lane as usize; let bytes = self.to_le_bytes(); bytes[lane] } - pub const fn extract_lane_i16(self, lane: u8) -> i16 { + pub fn extract_lane_i16(self, lane: u8) -> i16 { i16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) } - pub const fn extract_lane_u16(self, lane: u8) -> u16 { + pub fn extract_lane_u16(self, lane: u8) -> u16 { u16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) } - pub const fn extract_lane_i32(self, lane: u8) -> i32 { + pub fn extract_lane_i32(self, lane: u8) -> i32 { i32::from_le_bytes(self.extract_lane_bytes::<4>(lane, 4)) } - pub const fn extract_lane_i64(self, lane: u8) -> i64 { + pub fn extract_lane_i64(self, lane: u8) -> i64 { i64::from_le_bytes(self.extract_lane_bytes::<8>(lane, 2)) } - pub const fn extract_lane_f32(self, lane: u8) -> f32 { + pub fn extract_lane_f32(self, lane: u8) -> f32 { f32::from_bits(self.extract_lane_i32(lane) as u32) } - pub const fn extract_lane_f64(self, lane: u8) -> f64 { + pub fn extract_lane_f64(self, lane: u8) -> f64 { f64::from_bits(self.extract_lane_i64(lane) as u64) } - const fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { + fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { debug_assert!(lane < lane_count); let bytes = self.to_le_bytes(); let start = lane as usize * LANE_BYTES; let mut out = [0u8; LANE_BYTES]; - let mut i = 0; - while i < LANE_BYTES { - out[i] = bytes[start + i]; - i += 1; - } + out.copy_from_slice(&bytes[start..start + LANE_BYTES]); out } - const fn replace_lane_bytes( - self, - lane: u8, - value: [u8; LANE_BYTES], - lane_count: u8, - ) -> Self { + fn replace_lane_bytes(self, lane: u8, value: [u8; LANE_BYTES], lane_count: u8) -> Self { debug_assert!(lane < lane_count); let mut bytes = self.to_le_bytes(); - let mut i = 0; let start = lane as usize * LANE_BYTES; - while i < LANE_BYTES { - bytes[start + i] = value[i]; - i += 1; - } + bytes[start..start + LANE_BYTES].copy_from_slice(&value); Self::from_le_bytes(bytes) } } From a8d057903cca3b7dd0a010b5649a9bd8173acc1b Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 02:07:17 +0200 Subject: [PATCH 25/47] chore: update Signed-off-by: Henry --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 6 +++--- examples/wasm-rust.rs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66bc3d3e..31bf8221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -338,9 +338,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown", @@ -380,9 +380,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.245.1" +version = "0.246.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" +checksum = "1e1929aad146499e47362c876fcbcbb0363f730951d93438f511178626e999a8" dependencies = [ "leb128fmt", "wasmparser", @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.245.1" +version = "0.246.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +checksum = "2d991c35d79bf8336dc1cd632ed4aacf0dc5fac4bc466c670625b037b972bb9c" dependencies = [ "bitflags", "indexmap", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "wast" -version = "245.0.1" +version = "246.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" +checksum = "96cf2d50bc7478dcca61d00df4dadf922ef46c5924db20a97e6daaf09fe1cb09" dependencies = [ "bumpalo", "leb128fmt", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.245.1" +version = "1.246.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" +checksum = "723f2473b47f738c12fc11c8e0bb8b27ce7cf9c78cf1a29dadbc2d34a2513292" dependencies = [ "wast", ] diff --git a/Cargo.toml b/Cargo.toml index f47f1f4e..7cc731e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,9 @@ default-members=[".", "crates/tinywasm", "crates/types", "crates/parser"] resolver="2" [workspace.dependencies] -wast="245" -wat="1.245" -wasmparser={version="0.245", default-features=false} +wast="246" +wat="1.246" +wasmparser={version="0.246", default-features=false} eyre="0.6" log="0.4" pretty_env_logger="0.5" diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index 49792b00..d376130a 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -87,7 +87,7 @@ fn tinywasm() -> Result<()> { } fn tinywasm_no_std() -> Result<()> { - let module = Module::parse_file("./examples/rust/out/tinywasm_no_std.opt.wasm")?; + let module = Module::parse_file("./examples/rust/out/tinywasm_no_std.wasm")?; let mut store = Store::default(); let mut imports = Imports::new(); From 065dc608ab25030d1719fef4dd55271c95245884 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 17:24:38 +0200 Subject: [PATCH 26/47] chore: put debug impl behind feature flag, cleanup Signed-off-by: Henry --- README.md | 2 +- crates/tinywasm/Cargo.toml | 5 +- crates/tinywasm/src/engine.rs | 7 ++- crates/tinywasm/src/error.rs | 2 +- crates/tinywasm/src/func.rs | 5 +- crates/tinywasm/src/imports.rs | 5 +- crates/tinywasm/src/instance.rs | 5 +- crates/tinywasm/src/interpreter/mod.rs | 6 +- .../src/interpreter/stack/call_stack.rs | 8 ++- crates/tinywasm/src/interpreter/stack/mod.rs | 2 +- .../src/interpreter/stack/value_stack.rs | 4 +- crates/tinywasm/src/module.rs | 3 +- crates/tinywasm/src/reference.rs | 4 +- crates/tinywasm/src/store/data.rs | 8 +-- crates/tinywasm/src/store/element.rs | 7 +-- crates/tinywasm/src/store/global.rs | 7 +-- crates/tinywasm/src/store/memory.rs | 24 +++---- crates/tinywasm/src/store/mod.rs | 46 +++++++------- crates/tinywasm/src/store/table.rs | 18 +++--- crates/types/Cargo.toml | 3 +- crates/types/src/instructions.rs | 9 ++- crates/types/src/lib.rs | 63 ++++++++++++------- crates/types/src/value.rs | 3 +- 23 files changed, 136 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 29b019fa..66f0c5d9 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Safety wise, TinyWasm doesn't use any unsafe code and is designed to be complete | [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` | | [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | | [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | -| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🚧 | N/A | +| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🟢 | `next` | ## Usage diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index f0f8ab09..b89c907e 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -34,7 +34,7 @@ serde_json.workspace=true serde.workspace=true [features] -default=["std", "parser", "log", "archive", "canonicalize_nans"] +default=["std", "parser", "log", "archive", "canonicalize_nans", "debug"] log=["dep:log", "tinywasm-parser?/log", "tinywasm-types/log"] std=["tinywasm-parser?/std", "tinywasm-types/std"] @@ -48,6 +48,9 @@ archive=["tinywasm-types/archive"] # canonicalize all NaN values to a single representation canonicalize_nans=[] +# derive Debug for runtime/types structs +debug=["tinywasm-types/debug"] + [[test]] name="test-wasm-1" harness=false diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index 80936bfb..8b938c53 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -40,9 +40,9 @@ pub(crate) struct EngineInner { } /// Fuel accounting policy for budgeted execution. -#[derive(Debug, Clone, Copy)] #[non_exhaustive] -#[derive(Default)] +#[derive(Default, Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum FuelPolicy { /// Charge one fuel unit per retired instruction. #[default] @@ -67,7 +67,8 @@ pub const DEFAULT_VALUE_STACK_REF_SIZE: usize = 4 * 1024; // 4k slots pub const DEFAULT_CALL_STACK_SIZE: usize = 2048; // 1024 frames /// Configuration for the WebAssembly interpreter -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] pub struct Config { /// Initial size of the 32-bit value stack (i32, f32 values). diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 24912e39..4844e489 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -8,7 +8,7 @@ use tinywasm_types::archive::TwasmError; pub use tinywasm_parser::ParseError; /// Errors that can occur for `TinyWasm` operations -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] pub enum Error { /// A WebAssembly trap occurred diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 74610f51..b75a6de5 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -13,7 +13,8 @@ pub enum ExecProgress { Suspended, } -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ExecutionState { pub(crate) callframe: CallFrame, } @@ -33,7 +34,7 @@ pub struct FuncExecution<'store> { state: FuncExecutionState, } -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] enum FuncExecutionState { Running { exec_state: ExecutionState, root_func_addr: u32 }, Completed { result: Option> }, diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 7ce9f401..fc5bf373 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -10,7 +10,8 @@ use crate::{LinkingError, MemoryRef, MemoryRefMut, Result, log}; use tinywasm_types::*; /// The internal representation of a function -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum Function { /// A host function Host(Rc), @@ -49,7 +50,7 @@ impl HostFunction { pub(crate) type HostFuncInner = Box, &[WasmValue]) -> Result>>; /// The context of a host-function call -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncContext<'a> { pub(crate) store: &'a mut crate::Store, pub(crate) module_addr: ModuleInstanceAddr, diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 340bfd6e..27824024 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -10,11 +10,12 @@ use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut /// Backed by an Rc, so cloning is cheap /// /// See -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct ModuleInstance(pub(crate) Rc); #[expect(dead_code)] -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ModuleInstanceInner { pub(crate) failed_to_instantiate: bool, diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 8c3a840e..933e782b 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -11,7 +11,8 @@ use crate::{Result, Store, interpreter::stack::CallFrame}; pub(crate) use value128::*; pub(crate) use values::*; -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) enum ExecState { Completed, Suspended(CallFrame), @@ -20,7 +21,8 @@ pub(crate) enum ExecState { /// The main `TinyWasm` runtime. /// /// This is the default runtime used by `TinyWasm`. -#[derive(Debug, Default)] +#[derive(Default)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct InterpreterRuntime; impl InterpreterRuntime { diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 133f9106..4f3ace1d 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -3,7 +3,7 @@ use crate::{Result, Trap, unlikely}; use alloc::vec::Vec; use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCountsSmall}; -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct CallStack { stack: Vec, } @@ -32,7 +32,8 @@ impl CallStack { } } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Clone, Copy, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct CallFrame { pub(crate) instr_ptr: u32, pub(crate) module_addr: ModuleInstanceAddr, @@ -41,7 +42,8 @@ pub(crate) struct CallFrame { pub(crate) stack_offset: ValueCountsSmall, } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Clone, Copy, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct StackBase { pub(crate) s32: u32, pub(crate) s64: u32, diff --git a/crates/tinywasm/src/interpreter/stack/mod.rs b/crates/tinywasm/src/interpreter/stack/mod.rs index 59456d35..bb18fd3a 100644 --- a/crates/tinywasm/src/interpreter/stack/mod.rs +++ b/crates/tinywasm/src/interpreter/stack/mod.rs @@ -7,7 +7,7 @@ pub(crate) use value_stack::ValueStack; use crate::engine::Config; /// A WebAssembly Stack -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct Stack { pub(crate) values: ValueStack, pub(crate) call_stack: CallStack, diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 7fa0502d..395f0a49 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -6,7 +6,7 @@ use crate::{Result, Trap, engine::Config, interpreter::*, unlikely}; use super::{CallFrame, StackBase}; -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ValueStack { pub(crate) stack_32: Stack, pub(crate) stack_64: Stack, @@ -14,7 +14,7 @@ pub(crate) struct ValueStack { pub(crate) stack_ref: Stack, } -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct Stack { data: Box<[T]>, len: usize, diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index e0685eb2..d6c52100 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -4,7 +4,8 @@ use tinywasm_types::TinyWasmModule; /// A WebAssembly Module /// /// See -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct Module(pub(crate) alloc::sync::Arc); impl From<&TinyWasmModule> for Module { diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 790c3139..7901e572 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -8,11 +8,11 @@ use crate::{MemoryInstance, Result}; // This module essentially contains the public APIs to interact with the data stored in the store /// A reference to a memory instance -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); /// A borrowed reference to a memory instance -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance); impl MemoryRefLoad for MemoryRef<'_> { diff --git a/crates/tinywasm/src/store/data.rs b/crates/tinywasm/src/store/data.rs index 935e7612..fe6c4e7a 100644 --- a/crates/tinywasm/src/store/data.rs +++ b/crates/tinywasm/src/store/data.rs @@ -1,18 +1,16 @@ use alloc::vec::Vec; -use tinywasm_types::*; /// A WebAssembly Data Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct DataInstance { pub(crate) data: Option>, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl DataInstance { - pub(crate) fn new(data: Option>, owner: ModuleInstanceAddr) -> Self { - Self { data, _owner: owner } + pub(crate) fn new(data: Option>) -> Self { + Self { data } } pub(crate) fn drop(&mut self) { diff --git a/crates/tinywasm/src/store/element.rs b/crates/tinywasm/src/store/element.rs index 40301a5b..c643f80c 100644 --- a/crates/tinywasm/src/store/element.rs +++ b/crates/tinywasm/src/store/element.rs @@ -5,16 +5,15 @@ use tinywasm_types::*; /// A WebAssembly Element Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ElementInstance { pub(crate) kind: ElementKind, pub(crate) items: Option>, // none is the element was dropped - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl ElementInstance { - pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr, items: Option>) -> Self { - Self { kind, _owner: owner, items } + pub(crate) fn new(kind: ElementKind, items: Option>) -> Self { + Self { kind, items } } pub(crate) fn drop(&mut self) { diff --git a/crates/tinywasm/src/store/global.rs b/crates/tinywasm/src/store/global.rs index b7a47d65..95af73fb 100644 --- a/crates/tinywasm/src/store/global.rs +++ b/crates/tinywasm/src/store/global.rs @@ -5,15 +5,14 @@ use tinywasm_types::*; /// A WebAssembly Global Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct GlobalInstance { pub(crate) value: Cell, pub(crate) ty: GlobalType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl GlobalInstance { - pub(crate) fn new(ty: GlobalType, value: TinyWasmValue, owner: ModuleInstanceAddr) -> Self { - Self { ty, value: value.into(), _owner: owner } + pub(crate) fn new(ty: GlobalType, value: TinyWasmValue) -> Self { + Self { ty, value: value.into() } } } diff --git a/crates/tinywasm/src/store/memory.rs b/crates/tinywasm/src/store/memory.rs index a0c5c694..d20fac0f 100644 --- a/crates/tinywasm/src/store/memory.rs +++ b/crates/tinywasm/src/store/memory.rs @@ -1,31 +1,24 @@ use alloc::vec; use alloc::vec::Vec; -use tinywasm_types::{MemoryArch, MemoryType, ModuleInstanceAddr}; +use tinywasm_types::{MemoryArch, MemoryType}; use crate::{Error, Result, cold, interpreter::Value128, log}; /// A WebAssembly Memory Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct MemoryInstance { pub(crate) kind: MemoryType, pub(crate) data: Vec, pub(crate) page_count: usize, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl MemoryInstance { - pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { + pub(crate) fn new(kind: MemoryType) -> Self { assert!(kind.page_count_initial() <= kind.page_count_max()); log::debug!("initializing memory with {} pages of {} bytes", kind.page_count_initial(), kind.page_size()); - - Self { - kind, - data: vec![0; kind.initial_size() as usize], - page_count: kind.page_count_initial() as usize, - _owner: owner, - } + Self { kind, data: vec![0; kind.initial_size() as usize], page_count: kind.page_count_initial() as usize } } pub(crate) const fn is_64bit(&self) -> bool { @@ -189,8 +182,7 @@ mod memory_instance_tests { fn create_test_memory() -> MemoryInstance { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), None); - let owner = ModuleInstanceAddr::default(); - MemoryInstance::new(kind, owner) + MemoryInstance::new(kind) } #[test] @@ -269,8 +261,7 @@ mod memory_instance_tests { #[test] fn test_memory_custom_page_size_out_of_bounds() { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); - let owner = ModuleInstanceAddr::default(); - let mut memory = MemoryInstance::new(kind, owner); + let mut memory = MemoryInstance::new(kind); let data_to_store = [1, 2]; assert!(memory.store(0, data_to_store.len(), &data_to_store).is_err()); @@ -279,8 +270,7 @@ mod memory_instance_tests { #[test] fn test_memory_custom_page_size_grow() { let kind = MemoryType::new(MemoryArch::I32, 1, Some(2), Some(1)); - let owner = ModuleInstanceAddr::default(); - let mut memory = MemoryInstance::new(kind, owner); + let mut memory = MemoryInstance::new(kind); assert_eq!(memory.grow(1), Some(1)); diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index b409a0c1..8100faf5 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -266,22 +266,22 @@ impl Store { } /// Add tables to the store, returning their addresses in the store - pub(crate) fn init_tables(&mut self, tables: &[TableType], idx: ModuleInstanceAddr) -> Result> { + pub(crate) fn init_tables(&mut self, tables: &[TableType], _idx: ModuleInstanceAddr) -> Result> { let table_count = self.state.tables.len(); let mut table_addrs = Vec::with_capacity(table_count); for (i, table) in tables.iter().enumerate() { - self.state.tables.push(TableInstance::new(table.clone(), idx)); + self.state.tables.push(TableInstance::new(table.clone())); table_addrs.push((i + table_count) as TableAddr); } Ok(table_addrs) } /// Add memories to the store, returning their addresses in the store - pub(crate) fn init_memories(&mut self, memories: &[MemoryType], idx: ModuleInstanceAddr) -> Result> { + pub(crate) fn init_memories(&mut self, memories: &[MemoryType], _idx: ModuleInstanceAddr) -> Result> { let mem_count = self.state.memories.len(); let mut mem_addrs = Vec::with_capacity(mem_count); for (i, mem) in memories.iter().enumerate() { - self.state.memories.push(MemoryInstance::new(*mem, idx)); + self.state.memories.push(MemoryInstance::new(*mem)); mem_addrs.push((i + mem_count) as MemAddr); } Ok(mem_addrs) @@ -293,18 +293,15 @@ impl Store { mut imported_globals: Vec, new_globals: &[Global], func_addrs: &[FuncAddr], - idx: ModuleInstanceAddr, + _idx: ModuleInstanceAddr, ) -> Result> { let global_count = self.state.globals.len(); imported_globals.reserve_exact(new_globals.len()); let mut global_addrs = imported_globals; for (i, global) in new_globals.iter().enumerate() { - self.state.globals.push(GlobalInstance::new( - global.ty, - self.eval_const(&global.init, &global_addrs, func_addrs)?, - idx, - )); + let value = self.eval_const(&global.init, &global_addrs, func_addrs)?; + self.state.globals.push(GlobalInstance::new(global.ty, value)); global_addrs.push((i + global_count) as Addr); } @@ -342,7 +339,7 @@ impl Store { func_addrs: &[FuncAddr], global_addrs: &[Addr], elements: &[Element], - idx: ModuleInstanceAddr, + _idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { let elem_count = self.state.elements.len(); let mut elem_addrs = Vec::with_capacity(elem_count); @@ -386,7 +383,7 @@ impl Store { } }; - self.state.elements.push(ElementInstance::new(element.kind, idx, items)); + self.state.elements.push(ElementInstance::new(element.kind, items)); elem_addrs.push((i + elem_count) as Addr); } @@ -399,7 +396,7 @@ impl Store { &mut self, mem_addrs: &[MemAddr], data: &[Data], - idx: ModuleInstanceAddr, + _idx: ModuleInstanceAddr, ) -> Result<(Box<[Addr]>, Option)> { let data_count = self.state.data.len(); let mut data_addrs = Vec::with_capacity(data_count); @@ -424,7 +421,7 @@ impl Store { tinywasm_types::DataKind::Passive => Some(data.data.to_vec()), }; - self.state.data.push(DataInstance::new(data_val, idx)); + self.state.data.push(DataInstance::new(data_val)); data_addrs.push((i + data_count) as Addr); } @@ -432,21 +429,26 @@ impl Store { Ok((data_addrs.into_boxed_slice(), None)) } - pub(crate) fn add_global(&mut self, ty: GlobalType, value: TinyWasmValue, idx: ModuleInstanceAddr) -> Result { - self.state.globals.push(GlobalInstance::new(ty, value, idx)); + pub(crate) fn add_global( + &mut self, + ty: GlobalType, + value: TinyWasmValue, + _idx: ModuleInstanceAddr, + ) -> Result { + self.state.globals.push(GlobalInstance::new(ty, value)); Ok(self.state.globals.len() as Addr - 1) } - pub(crate) fn add_table(&mut self, table: TableType, idx: ModuleInstanceAddr) -> Result { - self.state.tables.push(TableInstance::new(table, idx)); + pub(crate) fn add_table(&mut self, table: TableType, _idx: ModuleInstanceAddr) -> Result { + self.state.tables.push(TableInstance::new(table)); Ok(self.state.tables.len() as TableAddr - 1) } - pub(crate) fn add_mem(&mut self, mem: MemoryType, idx: ModuleInstanceAddr) -> Result { + pub(crate) fn add_mem(&mut self, mem: MemoryType, _idx: ModuleInstanceAddr) -> Result { if let MemoryArch::I64 = mem.arch() { return Err(Error::UnsupportedFeature("64-bit memories".to_string())); } - self.state.memories.push(MemoryInstance::new(mem, idx)); + self.state.memories.push(MemoryInstance::new(mem)); Ok(self.state.memories.len() as MemAddr - 1) } @@ -463,9 +465,9 @@ impl Store { ConstInstruction::GlobalGet(addr) => match self.state.globals[addr as usize].value.get() { TinyWasmValue::Value32(i) => i64::from(i), TinyWasmValue::Value64(i) => i as i64, - o => return Err(Error::Other(format!("expected i32 or i64, got {o:?}"))), + other => return Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), }, - o => return Err(Error::Other(format!("expected i32, got {o:?}"))), + other => return Err(Error::Other(format!("expected i32, got {other:?}"))), }) } diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index bc4429da..627e886d 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -8,16 +8,15 @@ const MAX_TABLE_SIZE: u32 = 10_000_000; /// A WebAssembly Table Instance /// /// See -#[derive(Debug)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct TableInstance { pub(crate) elements: Vec, pub(crate) kind: TableType, - pub(crate) _owner: ModuleInstanceAddr, // index into store.module_instances } impl TableInstance { - pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { - Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind, _owner: owner } + pub(crate) fn new(kind: TableType) -> Self { + Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind } } #[inline(never)] @@ -150,7 +149,8 @@ impl TableInstance { } } -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) enum TableElement { Uninitialized, Initialized(TableAddr), @@ -193,14 +193,14 @@ mod tests { #[test] fn test_table_instance_creation() { let kind = dummy_table_type(); - let table_instance = TableInstance::new(kind.clone(), 0); + let table_instance = TableInstance::new(kind.clone()); assert_eq!(table_instance.size(), kind.size_initial as i32, "Table instance creation failed: size mismatch"); } #[test] fn test_get_wasm_val() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); + let mut table_instance = TableInstance::new(kind); table_instance.set(0, TableElement::Initialized(0)).expect("Setting table element failed"); table_instance.set(1, TableElement::Uninitialized).expect("Setting table element failed"); @@ -224,7 +224,7 @@ mod tests { #[test] fn test_set_and_get() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); + let mut table_instance = TableInstance::new(kind); let result = table_instance.set(0, TableElement::Initialized(1)); assert!(result.is_ok(), "Setting table element failed"); @@ -239,7 +239,7 @@ mod tests { #[test] fn test_table_init() { let kind = dummy_table_type(); - let mut table_instance = TableInstance::new(kind, 0); + let mut table_instance = TableInstance::new(kind); let init_elements = vec![TableElement::Initialized(0); 5]; let result = table_instance.init(0, &init_elements); diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 5efcb8b1..94832c17 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -16,8 +16,9 @@ postcard={version="1.1", optional=true, default-features=false, features=["alloc serde={version="1.0", optional=true, default-features=false, features=["alloc"]} [features] -default=["std", "log", "archive"] +default=["std", "log", "archive", "debug"] std=["serde?/std"] archive=["dep:postcard", "dep:serde"] log=["dep:log"] +debug=[] diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 29c9cab7..1165c066 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -2,7 +2,8 @@ use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType, Value use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] #[repr(Rust, packed)] pub struct MemoryArg { @@ -27,7 +28,8 @@ impl MemoryArg { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ConstInstruction { I32Const(i32), @@ -46,7 +48,8 @@ pub enum ConstInstruction { /// Wasm Bytecode can map to multiple of these instructions. /// /// See -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] #[rustfmt::skip] pub enum Instruction { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 71128348..78b83d0b 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -65,7 +65,8 @@ pub mod archive { /// This is the internal representation of a WebAssembly module in `TinyWasm`. /// `TinyWasmModules` are validated before being created, so they are guaranteed to be valid (as long as they were created by `TinyWasm`). /// This means you should not trust a `TinyWasmModule` created by a third party to be valid. -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Clone, Default, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TinyWasmModule { /// Optional address of the start function @@ -122,7 +123,8 @@ pub struct TinyWasmModule { /// A WebAssembly External Kind. /// /// See -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ExternalKind { /// A WebAssembly Function. @@ -160,7 +162,8 @@ pub type ModuleInstanceAddr = Addr; /// A WebAssembly External Value. /// /// See -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum ExternVal { Func(FuncAddr), Table(TableAddr), @@ -193,14 +196,16 @@ impl ExternVal { /// The type of a WebAssembly Function. /// /// See -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct FuncType { pub params: Box<[ValType]>, pub results: Box<[ValType]>, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct ValueCounts { pub c32: u32, @@ -209,7 +214,8 @@ pub struct ValueCounts { pub cref: u32, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Default, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct ValueCountsSmall { pub c32: u16, @@ -250,7 +256,8 @@ impl<'a, T: IntoIterator> From for ValueCountsSmall { } } -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Clone, PartialEq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunction { pub instructions: ArcSlice, @@ -306,14 +313,16 @@ impl<'de, T: serde::Deserialize<'de> + Debug> serde::Deserialize<'de> for ArcSli } } -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunctionData { pub v128_constants: Box<[i128]>, } /// A WebAssembly Module Export -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Export { /// The name of the export. @@ -324,21 +333,24 @@ pub struct Export { pub index: u32, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Global { pub ty: GlobalType, pub init: ConstInstruction, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct GlobalType { pub mutable: bool, pub ty: ValType, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct TableType { pub element_type: ValType, @@ -357,7 +369,8 @@ impl TableType { } /// Represents a memory's type. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct MemoryType { arch: MemoryArch, @@ -407,14 +420,16 @@ impl MemoryType { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum MemoryArch { I32, I64, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Import { pub module: Box, @@ -422,7 +437,8 @@ pub struct Import { pub kind: ImportKind, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ImportKind { Function(TypeAddr), @@ -442,7 +458,8 @@ impl From<&ImportKind> for ExternalKind { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Data { pub data: Box<[u8]>, @@ -450,14 +467,16 @@ pub struct Data { pub kind: DataKind, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum DataKind { Active { mem: MemAddr, offset: ConstInstruction }, Passive, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct Element { pub kind: ElementKind, @@ -466,7 +485,8 @@ pub struct Element { pub ty: ValType, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ElementKind { Passive, @@ -474,7 +494,8 @@ pub enum ElementKind { Declared, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ElementItem { Func(FuncAddr), diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index 7b58948d..afaa0d3c 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -299,7 +299,8 @@ impl WasmValue { } /// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub enum ValType { /// A 32-bit integer. From 51bac3ce0e72195fa3ebf3ef4fb287bc5451b6f7 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 17:55:37 +0200 Subject: [PATCH 27/47] chore: fix debug feature Signed-off-by: Henry --- crates/parser/src/lib.rs | 2 +- crates/tinywasm/src/engine.rs | 48 +++++++------------ crates/tinywasm/src/error.rs | 18 +++++-- crates/tinywasm/src/func.rs | 8 ++-- crates/tinywasm/src/imports.rs | 22 +++++++-- .../src/interpreter/stack/call_stack.rs | 2 +- crates/tinywasm/src/lib.rs | 2 +- crates/tinywasm/src/store/function.rs | 3 +- crates/tinywasm/src/store/mod.rs | 12 ++++- crates/tinywasm/src/store/table.rs | 3 -- .../tests/host_func_signature_check.rs | 3 +- crates/tinywasm/tests/resume_execution.rs | 4 +- crates/tinywasm/tests/testsuite/mod.rs | 8 ++-- crates/types/src/archive.rs | 12 +---- crates/types/src/instructions.rs | 2 +- crates/types/src/lib.rs | 22 ++++----- crates/types/src/value.rs | 36 ++++++++------ examples/funcref_callbacks.rs | 9 ++-- 18 files changed, 117 insertions(+), 99 deletions(-) diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 73bded05..ff2cacfa 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -3,7 +3,7 @@ no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +#![warn(missing_docs, rust_2018_idioms, unreachable_pub)] #![forbid(unsafe_code)] //! See [`tinywasm`](https://docs.rs/tinywasm) for documentation. diff --git a/crates/tinywasm/src/engine.rs b/crates/tinywasm/src/engine.rs index 8b938c53..ce84f3d6 100644 --- a/crates/tinywasm/src/engine.rs +++ b/crates/tinywasm/src/engine.rs @@ -1,21 +1,14 @@ -use core::fmt::Debug; - use alloc::sync::Arc; /// Global configuration for the WebAssembly interpreter /// /// Can be cheaply cloned and shared across multiple executions and threads. -#[derive(Clone)] +#[derive(Clone, Default)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct Engine { pub(crate) inner: Arc, } -impl Debug for Engine { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Engine").finish() - } -} - impl Engine { /// Create a new engine with the given configuration pub fn new(config: Config) -> Self { @@ -28,15 +21,10 @@ impl Engine { } } -impl Default for Engine { - fn default() -> Engine { - Engine::new(Config::default()) - } -} - +#[derive(Default)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct EngineInner { pub(crate) config: Config, - // pub(crate) allocator: Box, } /// Fuel accounting policy for budgeted execution. @@ -51,36 +39,36 @@ pub enum FuelPolicy { Weighted, } -/// Default initial size for the 32-bit value stack (i32, f32 values). -pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 64 * 1024; // 64k slots +/// Default size for the 32-bit value stack (i32, f32 values). +pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 32 * 1024; // 32k slots -/// Default initial size for the 64-bit value stack (i64, f64 values). +/// Default size for the 64-bit value stack (i64, f64 values). pub const DEFAULT_VALUE_STACK_64_SIZE: usize = 32 * 1024; // 32k slots -/// Default initial size for the 128-bit value stack (v128 values). +/// Default size for the 128-bit value stack (v128 values). pub const DEFAULT_VALUE_STACK_128_SIZE: usize = 4 * 1024; // 4k slots -/// Default initial size for the reference value stack (funcref, externref values). +/// Default size for the reference value stack (funcref, externref values). pub const DEFAULT_VALUE_STACK_REF_SIZE: usize = 4 * 1024; // 4k slots -/// Default initial size for the call stack (function frames). -pub const DEFAULT_CALL_STACK_SIZE: usize = 2048; // 1024 frames +/// Default maximum size for the call stack (function frames). +pub const DEFAULT_MAX_CALL_STACK_SIZE: usize = 1024; // 1024 frames /// Configuration for the WebAssembly interpreter #[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] pub struct Config { - /// Initial size of the 32-bit value stack (i32, f32 values). + /// Size of the 32-bit value stack (i32, f32 values). pub stack_32_size: usize, - /// Initial size of the 64-bit value stack (i64, f64 values). + /// Size of the 64-bit value stack (i64, f64 values). pub stack_64_size: usize, - /// Initial size of the 128-bit value stack (v128 values). + /// Size of the 128-bit value stack (v128 values). pub stack_128_size: usize, - /// Initial size of the reference value stack (funcref, externref values). + /// Size of the reference value stack (funcref, externref values). pub stack_ref_size: usize, - /// Initial size of the call stack. - pub call_stack_size: usize, + /// Maximum size of the call stack + pub max_call_stack_size: usize, /// Fuel accounting policy used by budgeted execution. pub fuel_policy: FuelPolicy, } @@ -105,7 +93,7 @@ impl Default for Config { stack_64_size: DEFAULT_VALUE_STACK_64_SIZE, stack_128_size: DEFAULT_VALUE_STACK_128_SIZE, stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE, - call_stack_size: DEFAULT_CALL_STACK_SIZE, + max_call_stack_size: DEFAULT_MAX_CALL_STACK_SIZE, fuel_policy: FuelPolicy::default(), } } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 4844e489..ade5089d 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,5 +1,6 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; +use core::fmt::Debug; use core::{fmt::Display, ops::ControlFlow}; use tinywasm_types::FuncType; use tinywasm_types::archive::TwasmError; @@ -8,7 +9,6 @@ use tinywasm_types::archive::TwasmError; pub use tinywasm_parser::ParseError; /// Errors that can occur for `TinyWasm` operations -#[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] pub enum Error { /// A WebAssembly trap occurred @@ -49,9 +49,9 @@ pub enum Error { Twasm(TwasmError), } -#[derive(Debug)] /// Errors that can occur when linking a WebAssembly module #[non_exhaustive] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum LinkingError { /// An unknown import was encountered UnknownImport { @@ -80,11 +80,11 @@ impl LinkingError { } } -#[derive(Debug)] /// A WebAssembly trap /// /// See #[non_exhaustive] +#[cfg_attr(feature = "debug", derive(Debug))] pub enum Trap { /// An unreachable instruction was executed Unreachable, @@ -206,9 +206,12 @@ impl Display for Error { Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {message}"), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {feature}"), + #[cfg(feature = "debug")] Self::InvalidHostFnReturn { expected, actual } => { write!(f, "invalid host function return: expected={expected:?}, actual={actual:?}") } + #[cfg(not(feature = "debug"))] + Self::InvalidHostFnReturn { .. } => write!(f, "invalid host function return"), Self::InvalidStore => write!(f, "invalid store"), } } @@ -244,13 +247,22 @@ impl Display for Trap { Self::UninitializedElement { index } => { write!(f, "uninitialized element: index={index}") } + #[cfg(feature = "debug")] Self::IndirectCallTypeMismatch { expected, actual } => { write!(f, "indirect call type mismatch: expected={expected:?}, actual={actual:?}") } + #[cfg(not(feature = "debug"))] + Self::IndirectCallTypeMismatch { .. } => write!(f, "indirect call type mismatch"), } } } +impl Debug for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self) + } +} + impl core::error::Error for Error {} #[cfg(feature = "parser")] diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index b75a6de5..98de9a2e 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -19,16 +19,16 @@ pub(crate) struct ExecutionState { pub(crate) callframe: CallFrame, } -#[derive(Debug)] /// A function handle +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncHandle { pub(crate) module_addr: ModuleInstanceAddr, pub(crate) addr: u32, pub(crate) ty: FuncType, } -#[derive(Debug)] /// Resumable execution for an untyped function call. +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncExecution<'store> { store: &'store mut Store, state: FuncExecutionState, @@ -40,8 +40,8 @@ enum FuncExecutionState { Completed { result: Option> }, } -#[derive(Debug)] /// Resumable execution for a typed function call. +#[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncExecutionTyped<'store, R> { execution: FuncExecution<'store>, marker: core::marker::PhantomData, @@ -212,8 +212,8 @@ fn collect_call_results(store: &mut Store, func_ty: &FuncType) -> Result { /// The underlying function handle pub func: FuncHandle, diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index fc5bf373..d55536ff 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -100,13 +100,15 @@ impl FuncContext<'_> { } } +#[cfg(feature = "debug")] impl Debug for HostFunction { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("HostFunction").field("ty", &self.ty).field("func", &"...").finish() } } -#[derive(Debug, Clone)] +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] #[non_exhaustive] /// An external value pub enum Extern { @@ -219,7 +221,6 @@ impl From<&Import> for ExternName { } } -#[derive(Debug, Default)] /// Imports for a module instance /// /// This is used to link a module instance to its imports @@ -254,7 +255,8 @@ impl From<&Import> for ExternName { /// /// Note that module instance addresses for [`Imports::link_module`] can be obtained from [`crate::ModuleInstance::id`]. /// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`]. -#[derive(Clone)] +#[derive(Default, Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub struct Imports { values: BTreeMap, modules: BTreeMap, @@ -322,9 +324,19 @@ impl Imports { None } - fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { + #[cfg(not(feature = "debug"))] + fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { + if expected != actual { + log::error!("failed to link import {}", import.name); + return Err(LinkingError::incompatible_import_type(import).into()); + } + Ok(()) + } + + #[cfg(feature = "debug")] + fn compare_types(import: &Import, actual: &T, expected: &T) -> Result<()> { if expected != actual { - log::error!("failed to link import {}, expected {:?}, got {:?}", import.name, expected, actual); + log::error!("failed to link import {}: expected {:?}, got {:?}", import.name, expected, actual); return Err(LinkingError::incompatible_import_type(import).into()); } Ok(()) diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index 4f3ace1d..a91f9382 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -10,7 +10,7 @@ pub(crate) struct CallStack { impl CallStack { pub(crate) fn new(config: &crate::engine::Config) -> Self { - Self { stack: Vec::with_capacity(config.call_stack_size) } + Self { stack: Vec::with_capacity(config.max_call_stack_size) } } pub(crate) fn clear(&mut self) { diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index b77dc090..cdcb1a14 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -3,7 +3,7 @@ no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +#![warn(missing_docs, rust_2018_idioms, unreachable_pub)] #![deny(unsafe_code)] //! A tiny WebAssembly Runtime written in Rust diff --git a/crates/tinywasm/src/store/function.rs b/crates/tinywasm/src/store/function.rs index ef370c23..3067a8e8 100644 --- a/crates/tinywasm/src/store/function.rs +++ b/crates/tinywasm/src/store/function.rs @@ -2,10 +2,11 @@ use crate::Function; use alloc::rc::Rc; use tinywasm_types::*; -#[derive(Debug, Clone)] /// A WebAssembly Function Instance /// /// See +#[derive(Clone)] +#[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct FunctionInstance { pub(crate) func: Function, pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances, none for host functions diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index 8100faf5..aae8b351 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -1,6 +1,5 @@ use alloc::rc::Rc; use alloc::{boxed::Box, format, string::ToString, vec::Vec}; -use core::fmt::Debug; use core::sync::atomic::{AtomicUsize, Ordering}; use tinywasm_types::*; @@ -40,7 +39,8 @@ pub struct Store { pub(crate) stack: Stack, } -impl Debug for Store { +#[cfg(feature = "debug")] +impl core::fmt::Debug for Store { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Store") .field("id", &self.id) @@ -323,9 +323,14 @@ impl Store { })?; self.state.globals[addr as usize].value.get().unwrap_ref() } + #[cfg(feature = "debug")] ElementItem::Expr(item) => { return Err(Error::UnsupportedFeature(format!("const expression other than ref: {item:?}"))); } + #[cfg(not(feature = "debug"))] + ElementItem::Expr(_) => { + return Err(Error::UnsupportedFeature("const expression other than ref".to_string())); + } }; Ok(res) @@ -467,7 +472,10 @@ impl Store { TinyWasmValue::Value64(i) => i as i64, other => return Err(Error::Other(format!("expected i32 or i64, got {other:?}"))), }, + #[cfg(feature = "debug")] other => return Err(Error::Other(format!("expected i32, got {other:?}"))), + #[cfg(not(feature = "debug"))] + _ => return Err(Error::Other("expected i32 or i64".to_string())), }) } diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index 627e886d..7bbda838 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -1,4 +1,3 @@ -use crate::log; use crate::{Error, Result, Trap}; use alloc::{vec, vec::Vec}; use tinywasm_types::*; @@ -142,9 +141,7 @@ impl TableInstance { if end > self.elements.len() || end < offset { return Err(crate::Trap::TableOutOfBounds { offset, len: init.len(), max: self.elements.len() }.into()); } - self.elements[offset..end].copy_from_slice(init); - log::debug!("table: {:?}", self.elements); Ok(()) } } diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 3b682b80..9b0a5308 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -100,8 +100,7 @@ fn test_linking_invalid_typed_func() -> Result<()> { let mut store = Store::default(); let mut imports = Imports::new(); imports.define("host", "hfn", typed_fn).unwrap(); - let link_failure = module.clone().instantiate(&mut store, Some(imports)); - link_failure.expect_err("no func in matching_none list should link to any mod"); + module.clone().instantiate(&mut store, Some(imports))?; } } diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs index 0f45e206..93b1c27f 100644 --- a/crates/tinywasm/tests/resume_execution.rs +++ b/crates/tinywasm/tests/resume_execution.rs @@ -47,7 +47,9 @@ fn untyped_resume_supports_zero_fuel() -> Result<()> { assert!(matches!(exec.resume_with_fuel(0)?, ExecProgress::Suspended)); match exec.resume_with_fuel(16)? { - ExecProgress::Completed(values) => assert_eq!(values, vec![WasmValue::I32(42)]), + ExecProgress::Completed(values) => { + assert_eq!(values, vec![WasmValue::I32(42)]) + } ExecProgress::Suspended => panic!("expected completion"), } diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index 2d443ba7..0221c6aa 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -2,6 +2,7 @@ use eyre::{Result, eyre}; use indexmap::IndexMap; use owo_colors::OwoColorize; +use std::fmt::Display; use std::io::{BufRead, Seek, SeekFrom}; use std::{ collections::BTreeMap, @@ -31,9 +32,9 @@ impl TestSuite { pub fn report_status(&self) -> Result<()> { if self.failed() { println!(); - Err(eyre!(format!("{}:\n{:#?}", "failed one or more tests".red().bold(), self))) + Err(eyre!(format!("{}:\n{self}", "failed one or more tests".red().bold()))) } else { - println!("\n\npassed all tests:\n{self:#?}"); + println!("\n\npassed all tests:\n{self}"); Ok(()) } } @@ -119,7 +120,7 @@ fn link(name: &str, file: &str, line: Option) -> String { format!("\x1b]8;;file://{path}\x1b\\{name}\x1b]8;;\x1b\\") } -impl Debug for TestSuite { +impl Display for TestSuite { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut total_passed = 0; let mut total_failed = 0; @@ -145,6 +146,7 @@ impl Debug for TestSuite { } } +#[derive(Debug)] struct TestGroup { tests: IndexMap, file: String, diff --git a/crates/types/src/archive.rs b/crates/types/src/archive.rs index 069addf3..dc98e309 100644 --- a/crates/types/src/archive.rs +++ b/crates/types/src/archive.rs @@ -4,10 +4,10 @@ use alloc::vec::Vec; use crate::TinyWasmModule; -const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; -const TWASM_VERSION: &[u8; 2] = b"03"; #[rustfmt::skip] const TWASM_MAGIC: [u8; 16] = [ TWASM_MAGIC_PREFIX[0], TWASM_MAGIC_PREFIX[1], TWASM_MAGIC_PREFIX[2], TWASM_MAGIC_PREFIX[3], TWASM_VERSION[0], TWASM_VERSION[1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +const TWASM_MAGIC_PREFIX: &[u8; 4] = b"TWAS"; +const TWASM_VERSION: &[u8; 2] = b"03"; fn validate_magic(wasm: &[u8]) -> Result { if wasm.len() < TWASM_MAGIC.len() || &wasm[..TWASM_MAGIC_PREFIX.len()] != TWASM_MAGIC_PREFIX { @@ -65,14 +65,6 @@ impl TinyWasmModule { mod tests { use super::*; - #[test] - fn test_serialize() { - let wasm = TinyWasmModule::default(); - let twasm = wasm.serialize_twasm().expect("should serialize"); - let wasm2 = TinyWasmModule::from_twasm(&twasm).unwrap(); - assert_eq!(wasm, wasm2); - } - #[test] fn test_invalid_magic() { let wasm = TinyWasmModule::default(); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 1165c066..83d0da8c 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -48,10 +48,10 @@ pub enum ConstInstruction { /// Wasm Bytecode can map to multiple of these instructions. /// /// See +#[rustfmt::skip] #[derive(Clone, Copy, PartialEq)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -#[rustfmt::skip] pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), I32AddLocals(LocalAddr, LocalAddr), I64AddLocals(LocalAddr, LocalAddr), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 78b83d0b..8a815cb1 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -2,9 +2,9 @@ no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] -#![warn(missing_debug_implementations, rust_2018_idioms, unreachable_pub)] +#![warn(rust_2018_idioms, unreachable_pub)] #![no_std] -#![forbid(unsafe_code)] +#![deny(unsafe_code)] //! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser). @@ -50,7 +50,7 @@ pub mod archive; #[cfg(not(feature = "archive"))] pub mod archive { - #[derive(Debug)] + #[cfg_attr(feature = "debug", derive(Debug))] pub enum TwasmError {} impl core::fmt::Display for TwasmError { fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -272,6 +272,12 @@ pub struct WasmFunction { // wrapper around Arc<[T]> to support serde serialization and deserialization pub struct ArcSlice(pub Arc<[T]>); +impl Debug for ArcSlice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.as_ref().fmt(f) + } +} + impl From> for ArcSlice { fn from(vec: alloc::vec::Vec) -> Self { Self(Arc::from(vec)) @@ -292,21 +298,15 @@ impl Deref for ArcSlice { } } -impl Debug for ArcSlice { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.0.as_ref().fmt(f) - } -} - #[cfg(feature = "archive")] -impl serde::Serialize for ArcSlice { +impl serde::Serialize for ArcSlice { fn serialize(&self, serializer: S) -> Result { self.0.as_ref().serialize(serializer) } } #[cfg(feature = "archive")] -impl<'de, T: serde::Deserialize<'de> + Debug> serde::Deserialize<'de> for ArcSlice { +impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ArcSlice { fn deserialize>(deserializer: D) -> Result { let vec: alloc::vec::Vec = alloc::vec::Vec::deserialize(deserializer)?; Ok(Self(Arc::from(vec))) diff --git a/crates/types/src/value.rs b/crates/types/src/value.rs index afaa0d3c..8cc7f1b7 100644 --- a/crates/types/src/value.rs +++ b/crates/types/src/value.rs @@ -23,12 +23,33 @@ pub enum WasmValue { RefFunc(FuncRef), } +impl Debug for WasmValue { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::I32(i) => write!(f, "i32({i})"), + Self::I64(i) => write!(f, "i64({i})"), + Self::F32(i) => write!(f, "f32({i})"), + Self::F64(i) => write!(f, "f64({i})"), + Self::V128(i) => write!(f, "v128({i:?})"), + #[cfg(feature = "debug")] + Self::RefExtern(i) => write!(f, "ref({i:?})"), + #[cfg(feature = "debug")] + Self::RefFunc(i) => write!(f, "func({i:?})"), + #[cfg(not(feature = "debug"))] + Self::RefExtern(_) => write!(f, "ref()"), + #[cfg(not(feature = "debug"))] + Self::RefFunc(_) => write!(f, "func()"), + } + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub struct ExternRef(Option); #[derive(Clone, Copy, PartialEq, Eq)] pub struct FuncRef(Option); +#[cfg(feature = "debug")] impl Debug for ExternRef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self.0 { @@ -38,6 +59,7 @@ impl Debug for ExternRef { } } +#[cfg(feature = "debug")] impl Debug for FuncRef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self.0 { @@ -268,20 +290,6 @@ impl WasmValue { } } -impl Debug for WasmValue { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::I32(i) => write!(f, "i32({i})"), - Self::I64(i) => write!(f, "i64({i})"), - Self::F32(i) => write!(f, "f32({i})"), - Self::F64(i) => write!(f, "f64({i})"), - Self::V128(i) => write!(f, "v128({i:?})"), - Self::RefExtern(i) => write!(f, "ref({i:?})"), - Self::RefFunc(i) => write!(f, "func({i:?})"), - } - } -} - impl WasmValue { /// Get the type of a [`WasmValue`] #[inline] diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index 4ee74384..a460df83 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -58,9 +58,7 @@ fn run_passed_funcref_example() -> Result<()> { // Host cannot call a funcref directly, so it routes through Wasm. let call_by_ref = ctx.module().exported_func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; - let result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; - println!("(funcref {func_ref:?})({LHS},{RHS}) results in {result}"); - + let _result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; Ok(()) }), )?; @@ -132,9 +130,8 @@ fn run_returned_funcref_example() -> Result<()> { let call_by_ref = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; - for (idx, func_ref) in [add_ref, sub_ref, mul_ref].iter().enumerate() { - let result = call_by_ref.call(&mut store, (*func_ref, LHS, RHS))?; - println!("At idx: {idx}, funcref {func_ref:?}({LHS},{RHS}) results in {result}"); + for func_ref in [add_ref, sub_ref, mul_ref] { + let _result = call_by_ref.call(&mut store, (func_ref, LHS, RHS))?; } Ok(()) } From 3ebed3310b2a548a8e9c13883ca03e1749547f78 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 18:18:27 +0200 Subject: [PATCH 28/47] docs: update readme Signed-off-by: Henry --- README.md | 100 +++++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 66f0c5d9..f8f4f7f4 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,18 @@ -> [!NOTE] -> TinyWasm is currently on pause and not actively developed. _Eventually_ this may change but for now it is archived (as of October 2025). +# `tinywasm`  [![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust&style=flat-square)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust&style=flat-square)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg?style=flat-square)](./LICENSE-APACHE) -
-
- -
-

TinyWasm

- A tiny WebAssembly Runtime written in safe Rust -
+## Why `tinywasm`? -
- -[![docs.rs](https://img.shields.io/docsrs/tinywasm?logo=rust)](https://docs.rs/tinywasm) [![Crates.io](https://img.shields.io/crates/v/tinywasm.svg?logo=rust)](https://crates.io/crates/tinywasm) [![Crates.io](https://img.shields.io/crates/l/tinywasm.svg)](./LICENSE-APACHE) - -## Why TinyWasm? - -- **Tiny**: TinyWasm is designed to be as small as possible without significantly compromising performance or functionality (< 4000 LLOC). -- **Portable**: TinyWasm runs on any platform that Rust can target, including `no_std`, with minimal external dependencies. -- **Safe**: No unsafe code is used in the runtime +- **Tiny**: Keeps the runtime small and focused while still being practical for real workloads. +- **Portable**: Runs anywhere Rust runs, supports `no_std`, and keeps external dependencies to a minimum. +- **Secure**: Written entirely safe Rust (`#[deny(unsafe_code)]`) and designed to prevent untrusted code from crashing the runtime ## Current Status -TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, the current 2.0 WebAssembly is mostly supported, with the exception of the SIMD and Memory64 proposals. See the [Supported Proposals](#supported-proposals) section for more information. - -## Safety - -Safety wise, TinyWasm doesn't use any unsafe code and is designed to be completely memory-safe. Untrusted WebAssembly code should not be able to crash the runtime or access memory outside of its sandbox, however currently there is no protection against infinite loops or excessive memory usage. Unvalidated Wasm and untrusted, precompilled twasm bytecode is safe to run too but can crash the runtime. - -## Supported Proposals - -**Legend**\ -🌑 -- not available\ -🚧 -- in development/partially supported\ -🟢 -- fully supported - -| Proposal | Status | TinyWasm Version | -| --------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------- | -| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | 🟢 | 0.2.0 | -| [**Non-trapping float-to-int Conversion**](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) | 🟢 | 0.2.0 | -| [**Sign-extension operators**](https://github.com/WebAssembly/sign-extension-ops) | 🟢 | 0.2.0 | -| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | 🟢 | 0.2.0 | -| [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) | 🟢 | 0.4.0 | -| [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | 🟢 | 0.7.0 | -| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 | -| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` | -| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | -| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | -| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🟢 | `next` | +`tinywasm` passes all WebAssembly MVP and WebAssembly 2.0 tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, support for WebAssembly 3.0 is mostly done. See the [Supported Proposals](#supported-proposals) section for more information. ## Usage -See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information on how to use TinyWasm. +See the [examples](./examples) directory and [documentation](https://docs.rs/tinywasm) for more information on how to use `tinywasm`. For testing purposes, you can also use the `tinywasm-cli` tool: ```sh @@ -69,21 +31,53 @@ $ tinywasm-cli --help - **`archive`**\ Enables pre-parsing of archives. This is enabled by default. -With all these features disabled, TinyWasm only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. +With all these features disabled, `tinywasm` only depends on `core`, `alloc`, and `libm` and can be used in `no_std` environments. Since `libm` is not as performant as the compiler's math intrinsics, it is recommended to use the `std` feature if possible (at least [for now](https://github.com/rust-lang/rfcs/issues/2505)), especially on `wasm32` targets. + +## Safety -## Inspiration +Untrusted WebAssembly code should not be able to crash the runtime or access memory outside of its sandbox. Unvalidated Wasm and untrusted, precompiled twasm bytecode is safe to run as well, but can lead to panics if the bytecode is malformed. In general, it is recommended to validate Wasm bytecode before running it, and to only run trusted twasm bytecode. -Big thanks to the authors of the following projects, which have inspired and influenced TinyWasm: +## Supported Proposals -- [wasmi](https://github.com/wasmi-labs/wasmi) - an efficient and lightweight WebAssembly interpreter that also runs in `no_std` environments -- [wasm3](https://github.com/wasm3/wasm3) - a high-performance WebAssembly interpreter written in C +| Proposal | Status | `tinywasm` Version | +| --------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------ | +| [**Multi-value**](https://github.com/WebAssembly/spec/blob/master/proposals/multi-value/Overview.md) | 🟢 | 0.2.0 | +| [**Mutable Globals**](https://github.com/WebAssembly/mutable-global/blob/master/proposals/mutable-global/Overview.md) | 🟢 | 0.2.0 | +| [**Non-trapping float-to-int Conversion**](https://github.com/WebAssembly/nontrapping-float-to-int-conversions) | 🟢 | 0.2.0 | +| [**Sign-extension operators**](https://github.com/WebAssembly/sign-extension-ops) | 🟢 | 0.2.0 | +| [**Bulk Memory Operations**](https://github.com/WebAssembly/spec/blob/master/proposals/bulk-memory-operations/Overview.md) | 🟢 | 0.4.0 | +| [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | 🟢 | 0.7.0 | +| [**Multi-memory**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 | +| [**Annotations**](https://github.com/WebAssembly/annotations/blob/main/proposals/annotations/Overview.md) | 🟢 | `next` | +| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` | +| [**Extended Const**](https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/Overview.md) | 🟢 | `next` | +| [**Fixed-Width SIMD**](https://github.com/WebAssembly/simd/blob/main/proposals/simd/Overview.md) | 🟢 | `next` | +| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | +| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | +| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🚧 | - | +| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🚧 | - | +| [**Custom Descriptors**](https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md) | 🌑 | - | +| [**Exception Handling**](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) | 🌑 | - | +| [**Function References**](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md) | 🌑 | - | +| [**Garbage Collection**](https://github.com/WebAssembly/gc/blob/main/proposals/gc/Overview.md) | 🌑 | - | +| [**Threads**](https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md) | 🌑 | - | + +**Legend**\ +🌑 -- not available\ +🚧 -- in development/partially supported\ +🟢 -- fully supported + +## See Also + +I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes: + +- [wasmi](https://github.com/wasmi-labs/wasmi) - efficient and versatile WebAssembly interpreter for embedded systems +- [wasm3](https://github.com/wasm3/wasm3) - a fast WebAssembly interpreter written in C - [wazero](https://wazero.io/) - a zero-dependency WebAssembly interpreter written in Go - [wain](https://github.com/rhysd/wain) - a zero-dependency WebAssembly interpreter written in Rust -I encourage you to check these projects out if you're looking for more mature and feature-complete WebAssembly runtimes. - ## License Licensed under either of [Apache License, Version 2.0](./LICENSE-APACHE) or [MIT license](./LICENSE-MIT) at your option. -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in TinyWasm by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `tinywasm` by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. From 303abc7ddaab670299e300b39b0c8f24a0aed139 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 19:45:12 +0200 Subject: [PATCH 29/47] chore: refactor interpreter loop Signed-off-by: Henry --- crates/tinywasm/benches/tinywasm_modes.rs | 16 +- crates/tinywasm/src/error.rs | 16 +- crates/tinywasm/src/interpreter/executor.rs | 1276 ++++++++--------- crates/tinywasm/src/interpreter/mod.rs | 2 +- .../tinywasm/src/interpreter/num_helpers.rs | 25 +- .../tests/host_func_signature_check.rs | 3 +- examples/rust/build.sh | 2 +- 7 files changed, 650 insertions(+), 690 deletions(-) diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs index bf6cbfc7..edb472dc 100644 --- a/crates/tinywasm/benches/tinywasm_modes.rs +++ b/crates/tinywasm/benches/tinywasm_modes.rs @@ -58,14 +58,6 @@ fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("tinywasm_modes"); group.measurement_time(BENCH_MEASUREMENT_TIME); - group.bench_function("call", |b| { - b.iter_batched_ref( - || setup_typed_func(module.clone(), None).expect("setup call"), - |(store, func)| run_call(store, func).expect("run call"), - BatchSize::LargeInput, - ) - }); - let per_instruction_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::PerInstruction)); group.bench_function("resume_fuel_per_instruction", |b| { b.iter_batched_ref( @@ -95,6 +87,14 @@ fn criterion_benchmark(c: &mut Criterion) { ) }); + group.bench_function("call", |b| { + b.iter_batched_ref( + || setup_typed_func(module.clone(), None).expect("setup call"), + |(store, func)| run_call(store, func).expect("run call"), + BatchSize::LargeInput, + ) + }); + group.finish(); } diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index ade5089d..591ba99f 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -1,7 +1,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::fmt::Debug; -use core::{fmt::Display, ops::ControlFlow}; +use core::fmt::Display; use tinywasm_types::FuncType; use tinywasm_types::archive::TwasmError; @@ -274,17 +274,3 @@ impl From for Error { /// A wrapper around [`core::result::Result`] for tinywasm operations pub type Result = crate::std::result::Result; - -pub(crate) trait Controlify { - fn to_cf(self) -> ControlFlow, T>; -} - -impl Controlify for Result { - #[inline(always)] - fn to_cf(self) -> ControlFlow, T> { - match self { - Ok(value) => ControlFlow::Continue(value), - Err(err) => ControlFlow::Break(Some(err)), - } - } -} diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 2e2bd0cd..76464cb9 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -4,7 +4,6 @@ use super::no_std_floats::NoStdFloatExt; use alloc::boxed::Box; use alloc::{rc::Rc, string::ToString}; -use core::ops::ControlFlow; use interpreter::stack::CallFrame; use tinywasm_types::*; @@ -17,9 +16,9 @@ use crate::instance::ModuleInstanceInner; use crate::interpreter::Value128; use crate::*; -const FUEL_ACCOUNTING_INTERVAL: u32 = 1024; #[cfg(feature = "std")] -const TIME_BUDGET_CHECK_INTERVAL: u32 = 2048; +const TIME_BUDGET_CHECK_INTERVAL: usize = 2048; +const FUEL_ACCOUNTING_INTERVAL: usize = 1024; const FUEL_COST_CALL_TOTAL: u32 = 5; pub(crate) struct Executor<'store, const BUDGETED: bool> { @@ -49,20 +48,21 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } #[inline(always)] - fn exec_next(&mut self) -> ControlFlow> { - use tinywasm_types::Instruction::*; + fn exec(&mut self) -> Result> { + for _ in 0..ITERATIONS { + use tinywasm_types::Instruction::*; - macro_rules! stack_op { + macro_rules! stack_op { (simd_unary $method:ident) => { stack_op!(unary Value128, |v| v.$method()) }; (simd_binary $method:ident) => { stack_op!(binary Value128, |a, b| a.$method(b)) }; - (unary $ty:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary::<$ty>(|$v| Ok($expr)).to_cf()? }; - (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| Ok($expr)).to_cf()? }; - (binary try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| $expr).to_cf()? }; - (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary_into::<$from, $to>(|$v| Ok($expr)).to_cf()? }; - (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary_into::<$from, $to>(|$a, $b| Ok($expr)).to_cf()? }; + (unary $ty:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary::<$ty>(|$v| Ok($expr))? }; + (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| Ok($expr))? }; + (binary try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| $expr)? }; + (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary_into::<$from, $to>(|$v| Ok($expr))? }; + (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary_into::<$from, $to>(|$a, $b| Ok($expr))? }; (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { stack_op!(binary $a, $b => $b, |$lhs, $rhs| $expr) }; - (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { self.store.stack.values.binary_mixed::<$a, $b, $res>(|$lhs, $rhs| Ok($expr)).to_cf()? }; - (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => { self.store.stack.values.ternary::<$ty>(|$a, $b, $c| Ok($expr)).to_cf()? }; + (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { self.store.stack.values.binary_mixed::<$a, $b, $res>(|$lhs, $rhs| Ok($expr))? }; + (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => { self.store.stack.values.ternary::<$ty>(|$a, $b, $c| Ok($expr))? }; (local_set_pop $ty:ty, $local_index:expr) => {{ let val = self.store.stack.values.pop::<$ty>(); self.store.stack.values.local_set(&self.cf, *$local_index, val); @@ -73,567 +73,568 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }}; } - let next = match self.func.instructions.0.get(self.cf.instr_ptr as usize) { - Some(instr) => instr, - None => unreachable!( - "Instruction pointer out of bounds: {} ({} instructions)", - self.cf.instr_ptr, - self.func.instructions.0.len() - ), - }; + let next = match self.func.instructions.0.get(self.cf.instr_ptr as usize) { + Some(instr) => instr, + None => unreachable!( + "Instruction pointer out of bounds: {} ({} instructions)", + self.cf.instr_ptr, + self.func.instructions.0.len() + ), + }; - #[rustfmt::skip] - match next { - Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} - Unreachable => return ControlFlow::Break(Some(Trap::Unreachable.into())), - Drop32 => self.store.stack.values.drop::(), - Drop64 => self.store.stack.values.drop::(), - Drop128 => self.store.stack.values.drop::(), - DropRef => self.store.stack.values.drop::(), - Select32 => self.store.stack.values.select::().to_cf()?, - Select64 => self.store.stack.values.select::().to_cf()?, - Select128 => self.store.stack.values.select::().to_cf()?, - SelectRef => self.store.stack.values.select::().to_cf()?, - SelectMulti(counts) => self.store.stack.values.select_multi(*counts), - Call(v) => return self.exec_call_direct::(*v), - CallSelf => return self.exec_call_self::(), - CallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - ReturnCall(v) => return self.exec_call_direct::(*v), - ReturnCallSelf => return self.exec_call_self::(), - ReturnCallIndirect(ty, table) => return self.exec_call_indirect::(*ty, *table), - Jump(ip) => return self.exec_jump(*ip), - JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { return ControlFlow::Continue(()); }, - DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { - let b32 = self.cf.stack_base().s32 + *base32 as u32; - let k32 = *keep32 as usize; - self.store.stack.values.stack_32.truncate_keep(b32 as usize, k32); - let b64 = self.cf.stack_base().s64 + *base64 as u32; - let k64 = *keep64 as usize; - self.store.stack.values.stack_64.truncate_keep(b64 as usize, k64); - let b128 = self.cf.stack_base().s128 + *base128 as u32; - let k128 = *keep128 as usize; - self.store.stack.values.stack_128.truncate_keep(b128 as usize, k128); - let bref = self.cf.stack_base().sref + *base_ref as u32; - let kref = *keep_ref as usize; - self.store.stack.values.stack_ref.truncate_keep(bref as usize, kref); - } - DropKeep32(base, keep) => { - let b = self.cf.stack_base().s32 + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_32.truncate_keep(b as usize, k); - } - DropKeep64(base, keep) => { - let b = self.cf.stack_base().s64 + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_64.truncate_keep(b as usize, k); - } - DropKeep128(base, keep) => { - let b = self.cf.stack_base().s128 + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_128.truncate_keep(b as usize, k); - } - DropKeepRef(base, keep) => { - let b = self.cf.stack_base().sref + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_ref.truncate_keep(b as usize, k); - } - BranchTable(default_ip, len) => return self.exec_branch_table(*default_ip, *len), - BranchTableTarget {..} => {}, - Return => return self.exec_return(), - LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, - LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, - LocalGet128(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, - LocalGetRef(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index)).to_cf()?, - LocalSet32(local_index) => stack_op!(local_set_pop Value32, local_index), - LocalSet64(local_index) => stack_op!(local_set_pop Value64, local_index), - LocalSet128(local_index) => stack_op!(local_set_pop Value128, local_index), - LocalSetRef(local_index) => stack_op!(local_set_pop ValueRef, local_index), - LocalCopy32(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), - LocalCopy64(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), - LocalCopy128(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), - LocalCopyRef(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), - I32AddLocals(a, b) => self.store.stack.values.push( - self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), - ).to_cf()?, - I64AddLocals(a, b) => self.store.stack.values.push( - self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), - ).to_cf()?, - I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), - I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), - I32StoreLocalLocal(m, addr_local, value_local) => { - let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); - let value = self.store.stack.values.local_get::(&self.cf, *value_local).to_mem_bytes(); - if let Err(e) = mem.store((m.offset() + addr) as usize, value.len(), &value) { - return ControlFlow::Break(Some(e)); + #[rustfmt::skip] + match next { + Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} + Unreachable => return Err(Trap::Unreachable.into()), + Drop32 => self.store.stack.values.drop::(), + Drop64 => self.store.stack.values.drop::(), + Drop128 => self.store.stack.values.drop::(), + DropRef => self.store.stack.values.drop::(), + Select32 => self.store.stack.values.select::()?, + Select64 => self.store.stack.values.select::()?, + Select128 => self.store.stack.values.select::()?, + SelectRef => self.store.stack.values.select::()?, + SelectMulti(counts) => self.store.stack.values.select_multi(*counts), + Call(v) => { self.exec_call_direct::(*v)?; continue; } + CallSelf => { self.exec_call_self::()?; continue; } + CallIndirect(ty, table) => { self.exec_call_indirect::(*ty, *table)?; continue; } + ReturnCall(v) => { self.exec_call_direct::(*v)?; continue; } + ReturnCallSelf => { self.exec_call_self::()?; continue; } + ReturnCallIndirect(ty, table) => { self.exec_call_indirect::(*ty, *table)?; continue; } + Jump(ip) => { self.exec_jump(*ip); continue; } + JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { continue; }, + DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { + let b32 = self.cf.stack_base().s32 + *base32 as u32; + let k32 = *keep32 as usize; + self.store.stack.values.stack_32.truncate_keep(b32 as usize, k32); + let b64 = self.cf.stack_base().s64 + *base64 as u32; + let k64 = *keep64 as usize; + self.store.stack.values.stack_64.truncate_keep(b64 as usize, k64); + let b128 = self.cf.stack_base().s128 + *base128 as u32; + let k128 = *keep128 as usize; + self.store.stack.values.stack_128.truncate_keep(b128 as usize, k128); + let bref = self.cf.stack_base().sref + *base_ref as u32; + let kref = *keep_ref as usize; + self.store.stack.values.stack_ref.truncate_keep(bref as usize, kref); } - } - I32LoadLocalTee(m, addr_local, dst_local) => { - let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); - let Some(Ok(addr)) = m.offset().checked_add(addr).map(|a| a.try_into()) else { - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: addr as usize, - len: 4, - max: 0, - }))); - }; - let value = mem.load_as::<4, i32>(addr).to_cf()?; - self.store.stack.values.local_set(&self.cf, *dst_local, value); - self.store.stack.values.push(value).to_cf()?; - } - I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), - I64XorRotlConstTee(c, local_index) => { - stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)); - stack_op!(local_tee i64, local_index); - } - LocalTee32(local_index) => stack_op!(local_tee Value32, local_index), - LocalTee64(local_index) => stack_op!(local_tee Value64, local_index), - LocalTee128(local_index) => stack_op!(local_tee Value128, local_index), - LocalTeeRef(local_index) => stack_op!(local_tee ValueRef, local_index), - GlobalGet(global_index) => self.exec_global_get(*global_index).to_cf()?, - GlobalSet32(global_index) => self.exec_global_set::(*global_index), - GlobalSet64(global_index) => self.exec_global_set::(*global_index), - GlobalSet128(global_index) => self.exec_global_set::(*global_index), - GlobalSetRef(global_index) => self.exec_global_set::(*global_index), - I32Const(val) => self.exec_const(*val).to_cf()?, - I64Const(val) => self.exec_const(*val).to_cf()?, - F32Const(val) => self.exec_const(*val).to_cf()?, - F64Const(val) => self.exec_const(*val).to_cf()?, - I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), - I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), - I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), - I64Eq => stack_op!(binary i64 => i32, |a, b| i32::from(a == b)), - F32Eq => stack_op!(binary f32 => i32, |a, b| i32::from(a == b)), - F64Eq => stack_op!(binary f64 => i32, |a, b| i32::from(a == b)), - I32Ne => stack_op!(binary i32, |a, b| i32::from(a != b)), - I64Ne => stack_op!(binary i64 => i32, |a, b| i32::from(a != b)), - F32Ne => stack_op!(binary f32 => i32, |a, b| i32::from(a != b)), - F64Ne => stack_op!(binary f64 => i32, |a, b| i32::from(a != b)), - I32LtS => stack_op!(binary i32, |a, b| i32::from(a < b)), - I64LtS => stack_op!(binary i64 => i32, |a, b| i32::from(a < b)), - I32LtU => stack_op!(binary u32 => i32, |a, b| i32::from(a < b)), - I64LtU => stack_op!(binary u64 => i32, |a, b| i32::from(a < b)), - F32Lt => stack_op!(binary f32 => i32, |a, b| i32::from(a < b)), - F64Lt => stack_op!(binary f64 => i32, |a, b| i32::from(a < b)), - I32LeS => stack_op!(binary i32, |a, b| i32::from(a <= b)), - I64LeS => stack_op!(binary i64 => i32, |a, b| i32::from(a <= b)), - I32LeU => stack_op!(binary u32 => i32, |a, b| i32::from(a <= b)), - I64LeU => stack_op!(binary u64 => i32, |a, b| i32::from(a <= b)), - F32Le => stack_op!(binary f32 => i32, |a, b| i32::from(a <= b)), - F64Le => stack_op!(binary f64 => i32, |a, b| i32::from(a <= b)), - I32GeS => stack_op!(binary i32, |a, b| i32::from(a >= b)), - I64GeS => stack_op!(binary i64 => i32, |a, b| i32::from(a >= b)), - I32GeU => stack_op!(binary u32 => i32, |a, b| i32::from(a >= b)), - I64GeU => stack_op!(binary u64 => i32, |a, b| i32::from(a >= b)), - F32Ge => stack_op!(binary f32 => i32, |a, b| i32::from(a >= b)), - F64Ge => stack_op!(binary f64 => i32, |a, b| i32::from(a >= b)), - I32GtS => stack_op!(binary i32, |a, b| i32::from(a > b)), - I64GtS => stack_op!(binary i64 => i32, |a, b| i32::from(a > b)), - I32GtU => stack_op!(binary u32 => i32, |a, b| i32::from(a > b)), - I64GtU => stack_op!(binary u64 => i32, |a, b| i32::from(a > b)), - F32Gt => stack_op!(binary f32 => i32, |a, b| i32::from(a > b)), - F64Gt => stack_op!(binary f64 => i32, |a, b| i32::from(a > b)), - I32Add => stack_op!(binary i32, |a, b| a.wrapping_add(b)), - I64Add => stack_op!(binary i64, |a, b| a.wrapping_add(b)), - F32Add => stack_op!(binary f32, |a, b| a + b), - F64Add => stack_op!(binary f64, |a, b| a + b), - I32Sub => stack_op!(binary i32, |a, b| a.wrapping_sub(b)), - I64Sub => stack_op!(binary i64, |a, b| a.wrapping_sub(b)), - F32Sub => stack_op!(binary f32, |a, b| a - b), - F64Sub => stack_op!(binary f64, |a, b| a - b), - F32Div => stack_op!(binary f32, |a, b| a / b), - F64Div => stack_op!(binary f64, |a, b| a / b), - I32Mul => stack_op!(binary i32, |a, b| a.wrapping_mul(b)), - I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), - F32Mul => stack_op!(binary f32, |a, b| a * b), - F64Mul => stack_op!(binary f64, |a, b| a * b), - I32DivS => stack_op!(binary try i32, |a, b| a.wasm_checked_div(b)), - I64DivS => stack_op!(binary try i64, |a, b| a.wasm_checked_div(b)), - I32DivU => stack_op!(binary try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), - I64DivU => stack_op!(binary try u64, |a, b| a.checked_div(b).ok_or_else(trap_0)), - I32RemS => stack_op!(binary try i32, |a, b| a.checked_wrapping_rem(b)), - I64RemS => stack_op!(binary try i64, |a, b| a.checked_wrapping_rem(b)), - I32RemU => stack_op!(binary try u32, |a, b| a.checked_wrapping_rem(b)), - I64RemU => stack_op!(binary try u64, |a, b| a.checked_wrapping_rem(b)), - I32And => stack_op!(binary i32, |a, b| a & b), - I64And => stack_op!(binary i64, |a, b| a & b), - I32Or => stack_op!(binary i32, |a, b| a | b), - I64Or => stack_op!(binary i64, |a, b| a | b), - I32Xor => stack_op!(binary i32, |a, b| a ^ b), - I64Xor => stack_op!(binary i64, |a, b| a ^ b), - I32Shl => stack_op!(binary i32, |a, b| a.wasm_shl(b)), - I64Shl => stack_op!(binary i64, |a, b| a.wasm_shl(b)), - I32ShrS => stack_op!(binary i32, |a, b| a.wasm_shr(b)), - I64ShrS => stack_op!(binary i64, |a, b| a.wasm_shr(b)), - I32ShrU => stack_op!(binary u32, |a, b| a.wasm_shr(b)), - I64ShrU => stack_op!(binary u64, |a, b| a.wasm_shr(b)), - I32Rotl => stack_op!(binary i32, |a, b| a.wasm_rotl(b)), - I64Rotl => stack_op!(binary i64, |a, b| a.wasm_rotl(b)), - I32Rotr => stack_op!(binary i32, |a, b| a.wasm_rotr(b)), - I64Rotr => stack_op!(binary i64, |a, b| a.wasm_rotr(b)), - I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), - I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), - I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), - I64Ctz => stack_op!(unary i64, |v| i64::from(v.trailing_zeros())), - I32Popcnt => stack_op!(unary i32, |v| v.count_ones() as i32), - I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), - - // Reference types - RefFunc(func_idx) => self.exec_const::(Some(*func_idx)).to_cf()?, - RefNull(_) => self.exec_const::(None).to_cf()?, - RefIsNull => self.exec_ref_is_null().to_cf()?, - MemorySize(addr) => self.exec_memory_size(*addr).to_cf()?, - MemoryGrow(addr) => self.exec_memory_grow(*addr).to_cf()?, - - // Bulk memory operations - MemoryCopy(from, to) => self.exec_memory_copy(*from, *to).to_cf()?, - MemoryFill(addr) => self.exec_memory_fill(*addr).to_cf()?, - MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx).to_cf()?, - DataDrop(data_index) => self.store.state.get_data_mut(self.module.resolve_data_addr(*data_index)).drop(), - ElemDrop(elem_index) => self.store.state.get_elem_mut(self.module.resolve_elem_addr(*elem_index)).drop(), - - // Table instructions - TableGet(table_idx) => self.exec_table_get(*table_idx).to_cf()?, - TableSet(table_idx) => self.exec_table_set(*table_idx).to_cf()?, - TableSize(table_idx) => self.exec_table_size(*table_idx).to_cf()?, - TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx).to_cf()?, - TableGrow(table_idx) => self.exec_table_grow(*table_idx).to_cf()?, - TableFill(table_idx) => self.exec_table_fill(*table_idx).to_cf()?, - TableCopy { from, to } => self.exec_table_copy(*from, *to).to_cf()?, - - // Core memory load/store operations - I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - F64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, - I32Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, - I32Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, - I64Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, - I64Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, - I64Store32(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i32)?, - I32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - I64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - F32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - F64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, - I32Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, - I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, - - // Numeric conversion operations - F32ConvertI32S => stack_op!(unary i32 => f32, |v| v as f32), - F32ConvertI64S => stack_op!(unary i64 => f32, |v| v as f32), - F64ConvertI32S => stack_op!(unary i32 => f64, |v| f64::from(v)), - F64ConvertI64S => stack_op!(unary i64 => f64, |v| v as f64), - F32ConvertI32U => stack_op!(unary u32 => f32, |v| v as f32), - F32ConvertI64U => stack_op!(unary u64 => f32, |v| v as f32), - F64ConvertI32U => stack_op!(unary u32 => f64, |v| f64::from(v)), - F64ConvertI64U => stack_op!(unary u64 => f64, |v| v as f64), - - // Sign-extension operations - I32Extend8S => stack_op!(unary i32, |v| i32::from(v as i8)), - I32Extend16S => stack_op!(unary i32, |v| i32::from(v as i16)), - I64Extend8S => stack_op!(unary i64, |v| i64::from(v as i8)), - I64Extend16S => stack_op!(unary i64, |v| i64::from(v as i16)), - I64Extend32S => stack_op!(unary i64, |v| i64::from(v as i32)), - I64ExtendI32U => stack_op!(unary u32 => i64, |v| i64::from(v)), - I64ExtendI32S => stack_op!(unary i32 => i64, |v| i64::from(v)), - I32WrapI64 => stack_op!(unary i64 => i32, |v| v as i32), - F32DemoteF64 => stack_op!(unary f64 => f32, |v| v as f32), - F64PromoteF32 => stack_op!(unary f32 => f64, |v| f64::from(v)), - F32Abs => stack_op!(unary f32, |v| v.abs()), - F64Abs => stack_op!(unary f64, |v| v.abs()), - F32Neg => stack_op!(unary f32, |v| -v), - F64Neg => stack_op!(unary f64, |v| -v), - F32Ceil => stack_op!(unary f32, |v| v.ceil()), - F64Ceil => stack_op!(unary f64, |v| v.ceil()), - F32Floor => stack_op!(unary f32, |v| v.floor()), - F64Floor => stack_op!(unary f64, |v| v.floor()), - F32Trunc => stack_op!(unary f32, |v| v.trunc()), - F64Trunc => stack_op!(unary f64, |v| v.trunc()), - F32Nearest => stack_op!(unary f32, |v| v.tw_nearest()), - F64Nearest => stack_op!(unary f64, |v| v.tw_nearest()), - F32Sqrt => stack_op!(unary f32, |v| v.sqrt()), - F64Sqrt => stack_op!(unary f64, |v| v.sqrt()), - F32Min => stack_op!(binary f32, |a, b| a.tw_minimum(b)), - F64Min => stack_op!(binary f64, |a, b| a.tw_minimum(b)), - F32Max => stack_op!(binary f32, |a, b| a.tw_maximum(b)), - F64Max => stack_op!(binary f64, |a, b| a.tw_maximum(b)), - F32Copysign => stack_op!(binary f32, |a, b| a.copysign(b)), - F64Copysign => stack_op!(binary f64, |a, b| a.copysign(b)), - I32TruncF32S => checked_conv_float!(f32, i32, self), - I32TruncF64S => checked_conv_float!(f64, i32, self), - I32TruncF32U => checked_conv_float!(f32, u32, i32, self), - I32TruncF64U => checked_conv_float!(f64, u32, i32, self), - I64TruncF32S => checked_conv_float!(f32, i64, self), - I64TruncF64S => checked_conv_float!(f64, i64, self), - I64TruncF32U => checked_conv_float!(f32, u64, i64, self), - I64TruncF64U => checked_conv_float!(f64, u64, i64, self), - - // Non-trapping float-to-int conversions - I32TruncSatF32S => stack_op!(unary f32 => i32, |v| v.trunc() as i32), - I32TruncSatF32U => stack_op!(unary f32 => u32, |v| v.trunc() as u32), - I32TruncSatF64S => stack_op!(unary f64 => i32, |v| v.trunc() as i32), - I32TruncSatF64U => stack_op!(unary f64 => u32, |v| v.trunc() as u32), - I64TruncSatF32S => stack_op!(unary f32 => i64, |v| v.trunc() as i64), - I64TruncSatF32U => stack_op!(unary f32 => u64, |v| v.trunc() as u64), - I64TruncSatF64S => stack_op!(unary f64 => i64, |v| v.trunc() as i64), - I64TruncSatF64U => stack_op!(unary f64 => u64, |v| v.trunc() as u64), - - // SIMD extension - V128Not => stack_op!(unary Value128, |v| v.v128_not()), - V128And => stack_op!(binary Value128, |a, b| a.v128_and(b)), - V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), - V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), - V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), - V128Bitselect => stack_op!(ternary Value128, |v1, v2, c| Value128::v128_bitselect(v1, v2, c)), - V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), - I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), - V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, - V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, - V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, - V128Load16x4S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_s(v.to_le_bytes()))?, - V128Load16x4U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_u(v.to_le_bytes()))?, - V128Load32x2S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_s(v.to_le_bytes()))?, - V128Load32x2U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_u(v.to_le_bytes()))?, - V128Load8Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i8)?, - V128Load16Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i16)?, - V128Load32Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i32)?, - V128Load64Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i64)?, - V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, - V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, - V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, - V128Const(arg) => self.exec_const::(self.func.data.v128_constants[*arg as usize].into()).to_cf()?, - I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), - I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), - I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), - I16x8ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u16(*lane) as i32), - I32x4ExtractLane(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i32(*lane)), - I64x2ExtractLane(lane) => stack_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), - F32x4ExtractLane(lane) => stack_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), - F64x2ExtractLane(lane) => stack_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), - V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, - I8x16ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), - I16x8ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), - I32x4ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), - I64x2ReplaceLane(lane) => stack_op!(binary i64, Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), - F32x4ReplaceLane(lane) => stack_op!(binary f32, Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), - F64x2ReplaceLane(lane) => stack_op!(binary f64, Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), - I8x16Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), - I16x8Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), - I32x4Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), - I64x2Splat => stack_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), - F32x4Splat => stack_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), - F64x2Splat => stack_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), - I8x16Eq => stack_op!(binary Value128, |a, b| a.i8x16_eq(b)), - I16x8Eq => stack_op!(binary Value128, |a, b| a.i16x8_eq(b)), - I32x4Eq => stack_op!(binary Value128, |a, b| a.i32x4_eq(b)), - I64x2Eq => stack_op!(binary Value128, |a, b| a.i64x2_eq(b)), - F32x4Eq => stack_op!(binary Value128, |a, b| a.f32x4_eq(b)), - F64x2Eq => stack_op!(binary Value128, |a, b| a.f64x2_eq(b)), - I8x16Ne => stack_op!(binary Value128, |a, b| a.i8x16_ne(b)), - I16x8Ne => stack_op!(binary Value128, |a, b| a.i16x8_ne(b)), - I32x4Ne => stack_op!(binary Value128, |a, b| a.i32x4_ne(b)), - I64x2Ne => stack_op!(binary Value128, |a, b| a.i64x2_ne(b)), - F32x4Ne => stack_op!(binary Value128, |a, b| a.f32x4_ne(b)), - F64x2Ne => stack_op!(binary Value128, |a, b| a.f64x2_ne(b)), - I8x16LtS => stack_op!(binary Value128, |a, b| a.i8x16_lt_s(b)), - I16x8LtS => stack_op!(binary Value128, |a, b| a.i16x8_lt_s(b)), - I32x4LtS => stack_op!(binary Value128, |a, b| a.i32x4_lt_s(b)), - I64x2LtS => stack_op!(binary Value128, |a, b| a.i64x2_lt_s(b)), - I8x16LtU => stack_op!(binary Value128, |a, b| a.i8x16_lt_u(b)), - I16x8LtU => stack_op!(binary Value128, |a, b| a.i16x8_lt_u(b)), - I32x4LtU => stack_op!(binary Value128, |a, b| a.i32x4_lt_u(b)), - F32x4Lt => stack_op!(binary Value128, |a, b| a.f32x4_lt(b)), - F64x2Lt => stack_op!(binary Value128, |a, b| a.f64x2_lt(b)), - F32x4Gt => stack_op!(binary Value128, |a, b| a.f32x4_gt(b)), - F64x2Gt => stack_op!(binary Value128, |a, b| a.f64x2_gt(b)), - I8x16GtS => stack_op!(binary Value128, |a, b| a.i8x16_gt_s(b)), - I16x8GtS => stack_op!(binary Value128, |a, b| a.i16x8_gt_s(b)), - I32x4GtS => stack_op!(binary Value128, |a, b| a.i32x4_gt_s(b)), - I64x2GtS => stack_op!(binary Value128, |a, b| a.i64x2_gt_s(b)), - I64x2LeS => stack_op!(binary Value128, |a, b| a.i64x2_le_s(b)), - F32x4Le => stack_op!(binary Value128, |a, b| a.f32x4_le(b)), - F64x2Le => stack_op!(binary Value128, |a, b| a.f64x2_le(b)), - I8x16GtU => stack_op!(binary Value128, |a, b| a.i8x16_gt_u(b)), - I16x8GtU => stack_op!(binary Value128, |a, b| a.i16x8_gt_u(b)), - I32x4GtU => stack_op!(binary Value128, |a, b| a.i32x4_gt_u(b)), - F32x4Ge => stack_op!(binary Value128, |a, b| a.f32x4_ge(b)), - F64x2Ge => stack_op!(binary Value128, |a, b| a.f64x2_ge(b)), - I8x16LeS => stack_op!(binary Value128, |a, b| a.i8x16_le_s(b)), - I16x8LeS => stack_op!(binary Value128, |a, b| a.i16x8_le_s(b)), - I32x4LeS => stack_op!(binary Value128, |a, b| a.i32x4_le_s(b)), - I8x16LeU => stack_op!(binary Value128, |a, b| a.i8x16_le_u(b)), - I16x8LeU => stack_op!(binary Value128, |a, b| a.i16x8_le_u(b)), - I32x4LeU => stack_op!(binary Value128, |a, b| a.i32x4_le_u(b)), - I8x16GeS => stack_op!(binary Value128, |a, b| a.i8x16_ge_s(b)), - I16x8GeS => stack_op!(binary Value128, |a, b| a.i16x8_ge_s(b)), - I32x4GeS => stack_op!(binary Value128, |a, b| a.i32x4_ge_s(b)), - I64x2GeS => stack_op!(binary Value128, |a, b| a.i64x2_ge_s(b)), - I8x16GeU => stack_op!(binary Value128, |a, b| a.i8x16_ge_u(b)), - I16x8GeU => stack_op!(binary Value128, |a, b| a.i16x8_ge_u(b)), - I32x4GeU => stack_op!(binary Value128, |a, b| a.i32x4_ge_u(b)), - I8x16Abs => stack_op!(unary Value128, |a| a.i8x16_abs()), - I16x8Abs => stack_op!(unary Value128, |a| a.i16x8_abs()), - I32x4Abs => stack_op!(unary Value128, |a| a.i32x4_abs()), - I64x2Abs => stack_op!(unary Value128, |a| a.i64x2_abs()), - I8x16Neg => stack_op!(unary Value128, |a| a.i8x16_neg()), - I16x8Neg => stack_op!(unary Value128, |a| a.i16x8_neg()), - I32x4Neg => stack_op!(unary Value128, |a| a.i32x4_neg()), - I64x2Neg => stack_op!(unary Value128, |a| a.i64x2_neg()), - I8x16AllTrue => stack_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), - I16x8AllTrue => stack_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), - I32x4AllTrue => stack_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), - I64x2AllTrue => stack_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), - I8x16Bitmask => stack_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), - I16x8Bitmask => stack_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), - I32x4Bitmask => stack_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), - I64x2Bitmask => stack_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), - I8x16Shl => stack_op!(binary i32, Value128, |a, b| b.i8x16_shl(a as u32)), - I16x8Shl => stack_op!(binary i32, Value128, |a, b| b.i16x8_shl(a as u32)), - I32x4Shl => stack_op!(binary i32, Value128, |a, b| b.i32x4_shl(a as u32)), - I64x2Shl => stack_op!(binary i32, Value128, |a, b| b.i64x2_shl(a as u32)), - I8x16ShrS => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_s(a as u32)), - I16x8ShrS => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_s(a as u32)), - I32x4ShrS => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_s(a as u32)), - I64x2ShrS => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_s(a as u32)), - I8x16ShrU => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_u(a as u32)), - I16x8ShrU => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_u(a as u32)), - I32x4ShrU => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_u(a as u32)), - I64x2ShrU => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_u(a as u32)), - I8x16Add => stack_op!(binary Value128, |a, b| a.i8x16_add(b)), - I16x8Add => stack_op!(binary Value128, |a, b| a.i16x8_add(b)), - I32x4Add => stack_op!(binary Value128, |a, b| a.i32x4_add(b)), - I64x2Add => stack_op!(binary Value128, |a, b| a.i64x2_add(b)), - I8x16Sub => stack_op!(binary Value128, |a, b| a.i8x16_sub(b)), - I16x8Sub => stack_op!(binary Value128, |a, b| a.i16x8_sub(b)), - I32x4Sub => stack_op!(binary Value128, |a, b| a.i32x4_sub(b)), - I64x2Sub => stack_op!(binary Value128, |a, b| a.i64x2_sub(b)), - I8x16MinS => stack_op!(binary Value128, |a, b| a.i8x16_min_s(b)), - I16x8MinS => stack_op!(binary Value128, |a, b| a.i16x8_min_s(b)), - I32x4MinS => stack_op!(binary Value128, |a, b| a.i32x4_min_s(b)), - I8x16MinU => stack_op!(binary Value128, |a, b| a.i8x16_min_u(b)), - I16x8MinU => stack_op!(binary Value128, |a, b| a.i16x8_min_u(b)), - I32x4MinU => stack_op!(binary Value128, |a, b| a.i32x4_min_u(b)), - I8x16MaxS => stack_op!(binary Value128, |a, b| a.i8x16_max_s(b)), - I16x8MaxS => stack_op!(binary Value128, |a, b| a.i16x8_max_s(b)), - I32x4MaxS => stack_op!(binary Value128, |a, b| a.i32x4_max_s(b)), - I8x16MaxU => stack_op!(binary Value128, |a, b| a.i8x16_max_u(b)), - I16x8MaxU => stack_op!(binary Value128, |a, b| a.i16x8_max_u(b)), - I32x4MaxU => stack_op!(binary Value128, |a, b| a.i32x4_max_u(b)), - I64x2Mul => stack_op!(binary Value128, |a, b| a.i64x2_mul(b)), - I16x8Mul => stack_op!(binary Value128, |a, b| a.i16x8_mul(b)), - I32x4Mul => stack_op!(binary Value128, |a, b| a.i32x4_mul(b)), - I8x16NarrowI16x8S => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), - I8x16NarrowI16x8U => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), - I16x8NarrowI32x4S => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), - I16x8NarrowI32x4U => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), - I8x16AddSatS => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_s(b)), - I16x8AddSatS => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_s(b)), - I8x16AddSatU => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_u(b)), - I16x8AddSatU => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_u(b)), - I8x16SubSatS => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_s(b)), - I16x8SubSatS => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_s(b)), - I8x16SubSatU => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_u(b)), - I16x8SubSatU => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_u(b)), - I8x16AvgrU => stack_op!(binary Value128, |a, b| a.i8x16_avgr_u(b)), - I16x8AvgrU => stack_op!(binary Value128, |a, b| a.i16x8_avgr_u(b)), - I16x8ExtAddPairwiseI8x16S => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), - I16x8ExtAddPairwiseI8x16U => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), - I32x4ExtAddPairwiseI16x8S => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), - I32x4ExtAddPairwiseI16x8U => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), - I16x8ExtMulLowI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), - I16x8ExtMulLowI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), - I16x8ExtMulHighI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), - I16x8ExtMulHighI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_u(b)), - I32x4ExtMulLowI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_s(b)), - I32x4ExtMulLowI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_u(b)), - I32x4ExtMulHighI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_s(b)), - I32x4ExtMulHighI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_u(b)), - I64x2ExtMulLowI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_s(b)), - I64x2ExtMulLowI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), - I64x2ExtMulHighI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), - I64x2ExtMulHighI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), - I16x8ExtendLowI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_s()), - I16x8ExtendLowI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_u()), - I16x8ExtendHighI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_s()), - I16x8ExtendHighI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_u()), - I32x4ExtendLowI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_s()), - I32x4ExtendLowI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_u()), - I32x4ExtendHighI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_s()), - I32x4ExtendHighI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_u()), - I64x2ExtendLowI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_s()), - I64x2ExtendLowI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_u()), - I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), - I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), - I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), - I8x16Shuffle(idx) => { let idx = self.func.data.v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } - I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), - I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), - F32x4Ceil => stack_op!(simd_unary f32x4_ceil), - F64x2Ceil => stack_op!(simd_unary f64x2_ceil), - F32x4Floor => stack_op!(simd_unary f32x4_floor), - F64x2Floor => stack_op!(simd_unary f64x2_floor), - F32x4Trunc => stack_op!(simd_unary f32x4_trunc), - F64x2Trunc => stack_op!(simd_unary f64x2_trunc), - F32x4Nearest => stack_op!(simd_unary f32x4_nearest), - F64x2Nearest => stack_op!(simd_unary f64x2_nearest), - F32x4Abs => stack_op!(simd_unary f32x4_abs), - F64x2Abs => stack_op!(simd_unary f64x2_abs), - F32x4Neg => stack_op!(simd_unary f32x4_neg), - F64x2Neg => stack_op!(simd_unary f64x2_neg), - F32x4Sqrt => stack_op!(simd_unary f32x4_sqrt), - F64x2Sqrt => stack_op!(simd_unary f64x2_sqrt), - F32x4Add => stack_op!(simd_binary f32x4_add), - F64x2Add => stack_op!(simd_binary f64x2_add), - F32x4Sub => stack_op!(simd_binary f32x4_sub), - F64x2Sub => stack_op!(simd_binary f64x2_sub), - F32x4Mul => stack_op!(simd_binary f32x4_mul), - F64x2Mul => stack_op!(simd_binary f64x2_mul), - F32x4Div => stack_op!(simd_binary f32x4_div), - F64x2Div => stack_op!(simd_binary f64x2_div), - F32x4Min => stack_op!(simd_binary f32x4_min), - F64x2Min => stack_op!(simd_binary f64x2_min), - F32x4Max => stack_op!(simd_binary f32x4_max), - F64x2Max => stack_op!(simd_binary f64x2_max), - F32x4PMin => stack_op!(simd_binary f32x4_pmin), - F32x4PMax => stack_op!(simd_binary f32x4_pmax), - F64x2PMin => stack_op!(simd_binary f64x2_pmin), - F64x2PMax => stack_op!(simd_binary f64x2_pmax), - I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), - I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), - F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), - F32x4ConvertI32x4U => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_u()), - F64x2ConvertLowI32x4S => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_s()), - F64x2ConvertLowI32x4U => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_u()), - F32x4DemoteF64x2Zero => stack_op!(unary Value128, |v| v.f32x4_demote_f64x2_zero()), - F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), - I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), - I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), - }; + DropKeep32(base, keep) => { + let b = self.cf.stack_base().s32 + *base as u32; + let k = *keep as usize; + self.store.stack.values.stack_32.truncate_keep(b as usize, k); + } + DropKeep64(base, keep) => { + let b = self.cf.stack_base().s64 + *base as u32; + let k = *keep as usize; + self.store.stack.values.stack_64.truncate_keep(b as usize, k); + } + DropKeep128(base, keep) => { + let b = self.cf.stack_base().s128 + *base as u32; + let k = *keep as usize; + self.store.stack.values.stack_128.truncate_keep(b as usize, k); + } + DropKeepRef(base, keep) => { + let b = self.cf.stack_base().sref + *base as u32; + let k = *keep as usize; + self.store.stack.values.stack_ref.truncate_keep(b as usize, k); + } + BranchTable(default_ip, len) => { self.exec_branch_table(*default_ip, *len); continue; } + BranchTableTarget {..} => {}, + Return => { if self.exec_return() { return Ok(Some(())); } continue; } + LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalGet128(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalGetRef(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, + LocalSet32(local_index) => stack_op!(local_set_pop Value32, local_index), + LocalSet64(local_index) => stack_op!(local_set_pop Value64, local_index), + LocalSet128(local_index) => stack_op!(local_set_pop Value128, local_index), + LocalSetRef(local_index) => stack_op!(local_set_pop ValueRef, local_index), + LocalCopy32(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopy64(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopy128(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + LocalCopyRef(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), + I32AddLocals(a, b) => self.store.stack.values.push( + self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), + )?, + I64AddLocals(a, b) => self.store.stack.values.push( + self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), + )?, + I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), + I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), + I32StoreLocalLocal(m, addr_local, value_local) => { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); + let value = self.store.stack.values.local_get::(&self.cf, *value_local).to_mem_bytes(); + if let Err(e) = mem.store((m.offset() + addr) as usize, value.len(), &value) { + return Err(e); + } + } + I32LoadLocalTee(m, addr_local, dst_local) => { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); + let Some(Ok(addr)) = m.offset().checked_add(addr).map(|a| a.try_into()) else { + return Err(Error::Trap(Trap::MemoryOutOfBounds { + offset: addr as usize, + len: 4, + max: 0, + })); + }; + let value = mem.load_as::<4, i32>(addr)?; + self.store.stack.values.local_set(&self.cf, *dst_local, value); + self.store.stack.values.push(value)?; + } + I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), + I64XorRotlConstTee(c, local_index) => { + stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)); + stack_op!(local_tee i64, local_index); + } + LocalTee32(local_index) => stack_op!(local_tee Value32, local_index), + LocalTee64(local_index) => stack_op!(local_tee Value64, local_index), + LocalTee128(local_index) => stack_op!(local_tee Value128, local_index), + LocalTeeRef(local_index) => stack_op!(local_tee ValueRef, local_index), + GlobalGet(global_index) => self.exec_global_get(*global_index)?, + GlobalSet32(global_index) => self.exec_global_set::(*global_index), + GlobalSet64(global_index) => self.exec_global_set::(*global_index), + GlobalSet128(global_index) => self.exec_global_set::(*global_index), + GlobalSetRef(global_index) => self.exec_global_set::(*global_index), + I32Const(val) => self.exec_const(*val)?, + I64Const(val) => self.exec_const(*val)?, + F32Const(val) => self.exec_const(*val)?, + F64Const(val) => self.exec_const(*val)?, + I64Eqz => stack_op!(unary i64 => i32, |v| i32::from(v == 0)), + I32Eqz => stack_op!(unary i32, |v| i32::from(v == 0)), + I32Eq => stack_op!(binary i32, |a, b| i32::from(a == b)), + I64Eq => stack_op!(binary i64 => i32, |a, b| i32::from(a == b)), + F32Eq => stack_op!(binary f32 => i32, |a, b| i32::from(a == b)), + F64Eq => stack_op!(binary f64 => i32, |a, b| i32::from(a == b)), + I32Ne => stack_op!(binary i32, |a, b| i32::from(a != b)), + I64Ne => stack_op!(binary i64 => i32, |a, b| i32::from(a != b)), + F32Ne => stack_op!(binary f32 => i32, |a, b| i32::from(a != b)), + F64Ne => stack_op!(binary f64 => i32, |a, b| i32::from(a != b)), + I32LtS => stack_op!(binary i32, |a, b| i32::from(a < b)), + I64LtS => stack_op!(binary i64 => i32, |a, b| i32::from(a < b)), + I32LtU => stack_op!(binary u32 => i32, |a, b| i32::from(a < b)), + I64LtU => stack_op!(binary u64 => i32, |a, b| i32::from(a < b)), + F32Lt => stack_op!(binary f32 => i32, |a, b| i32::from(a < b)), + F64Lt => stack_op!(binary f64 => i32, |a, b| i32::from(a < b)), + I32LeS => stack_op!(binary i32, |a, b| i32::from(a <= b)), + I64LeS => stack_op!(binary i64 => i32, |a, b| i32::from(a <= b)), + I32LeU => stack_op!(binary u32 => i32, |a, b| i32::from(a <= b)), + I64LeU => stack_op!(binary u64 => i32, |a, b| i32::from(a <= b)), + F32Le => stack_op!(binary f32 => i32, |a, b| i32::from(a <= b)), + F64Le => stack_op!(binary f64 => i32, |a, b| i32::from(a <= b)), + I32GeS => stack_op!(binary i32, |a, b| i32::from(a >= b)), + I64GeS => stack_op!(binary i64 => i32, |a, b| i32::from(a >= b)), + I32GeU => stack_op!(binary u32 => i32, |a, b| i32::from(a >= b)), + I64GeU => stack_op!(binary u64 => i32, |a, b| i32::from(a >= b)), + F32Ge => stack_op!(binary f32 => i32, |a, b| i32::from(a >= b)), + F64Ge => stack_op!(binary f64 => i32, |a, b| i32::from(a >= b)), + I32GtS => stack_op!(binary i32, |a, b| i32::from(a > b)), + I64GtS => stack_op!(binary i64 => i32, |a, b| i32::from(a > b)), + I32GtU => stack_op!(binary u32 => i32, |a, b| i32::from(a > b)), + I64GtU => stack_op!(binary u64 => i32, |a, b| i32::from(a > b)), + F32Gt => stack_op!(binary f32 => i32, |a, b| i32::from(a > b)), + F64Gt => stack_op!(binary f64 => i32, |a, b| i32::from(a > b)), + I32Add => stack_op!(binary i32, |a, b| a.wrapping_add(b)), + I64Add => stack_op!(binary i64, |a, b| a.wrapping_add(b)), + F32Add => stack_op!(binary f32, |a, b| a + b), + F64Add => stack_op!(binary f64, |a, b| a + b), + I32Sub => stack_op!(binary i32, |a, b| a.wrapping_sub(b)), + I64Sub => stack_op!(binary i64, |a, b| a.wrapping_sub(b)), + F32Sub => stack_op!(binary f32, |a, b| a - b), + F64Sub => stack_op!(binary f64, |a, b| a - b), + F32Div => stack_op!(binary f32, |a, b| a / b), + F64Div => stack_op!(binary f64, |a, b| a / b), + I32Mul => stack_op!(binary i32, |a, b| a.wrapping_mul(b)), + I64Mul => stack_op!(binary i64, |a, b| a.wrapping_mul(b)), + F32Mul => stack_op!(binary f32, |a, b| a * b), + F64Mul => stack_op!(binary f64, |a, b| a * b), + I32DivS => stack_op!(binary try i32, |a, b| a.wasm_checked_div(b)), + I64DivS => stack_op!(binary try i64, |a, b| a.wasm_checked_div(b)), + I32DivU => stack_op!(binary try u32, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I64DivU => stack_op!(binary try u64, |a, b| a.checked_div(b).ok_or_else(trap_0)), + I32RemS => stack_op!(binary try i32, |a, b| a.checked_wrapping_rem(b)), + I64RemS => stack_op!(binary try i64, |a, b| a.checked_wrapping_rem(b)), + I32RemU => stack_op!(binary try u32, |a, b| a.checked_wrapping_rem(b)), + I64RemU => stack_op!(binary try u64, |a, b| a.checked_wrapping_rem(b)), + I32And => stack_op!(binary i32, |a, b| a & b), + I64And => stack_op!(binary i64, |a, b| a & b), + I32Or => stack_op!(binary i32, |a, b| a | b), + I64Or => stack_op!(binary i64, |a, b| a | b), + I32Xor => stack_op!(binary i32, |a, b| a ^ b), + I64Xor => stack_op!(binary i64, |a, b| a ^ b), + I32Shl => stack_op!(binary i32, |a, b| a.wasm_shl(b)), + I64Shl => stack_op!(binary i64, |a, b| a.wasm_shl(b)), + I32ShrS => stack_op!(binary i32, |a, b| a.wasm_shr(b)), + I64ShrS => stack_op!(binary i64, |a, b| a.wasm_shr(b)), + I32ShrU => stack_op!(binary u32, |a, b| a.wasm_shr(b)), + I64ShrU => stack_op!(binary u64, |a, b| a.wasm_shr(b)), + I32Rotl => stack_op!(binary i32, |a, b| a.wasm_rotl(b)), + I64Rotl => stack_op!(binary i64, |a, b| a.wasm_rotl(b)), + I32Rotr => stack_op!(binary i32, |a, b| a.wasm_rotr(b)), + I64Rotr => stack_op!(binary i64, |a, b| a.wasm_rotr(b)), + I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), + I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), + I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), + I64Ctz => stack_op!(unary i64, |v| i64::from(v.trailing_zeros())), + I32Popcnt => stack_op!(unary i32, |v| v.count_ones() as i32), + I64Popcnt => stack_op!(unary i64, |v| i64::from(v.count_ones())), + + // Reference types + RefFunc(func_idx) => self.exec_const::(Some(*func_idx))?, + RefNull(_) => self.exec_const::(None)?, + RefIsNull => self.exec_ref_is_null()?, + MemorySize(addr) => self.exec_memory_size(*addr)?, + MemoryGrow(addr) => self.exec_memory_grow(*addr)?, + + // Bulk memory operations + MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, + MemoryFill(addr) => self.exec_memory_fill(*addr)?, + MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, + DataDrop(data_index) => self.store.state.get_data_mut(self.module.resolve_data_addr(*data_index)).drop(), + ElemDrop(elem_index) => self.store.state.get_elem_mut(self.module.resolve_elem_addr(*elem_index)).drop(), + + // Table instructions + TableGet(table_idx) => self.exec_table_get(*table_idx)?, + TableSet(table_idx) => self.exec_table_set(*table_idx)?, + TableSize(table_idx) => self.exec_table_size(*table_idx)?, + TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx)?, + TableGrow(table_idx) => self.exec_table_grow(*table_idx)?, + TableFill(table_idx) => self.exec_table_fill(*table_idx)?, + TableCopy { from, to } => self.exec_table_copy(*from, *to)?, + + // Core memory load/store operations + I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + I64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + F32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + F64Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, + I32Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, + I32Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, + I64Store8(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i8)?, + I64Store16(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i16)?, + I64Store32(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v as i32)?, + I32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + I64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + F32Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + F64Load(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), |v| v)?, + I32Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I32Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i32::from)?, + I64Load8S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load8U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load16S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load16U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load32S(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + I64Load32U(m) => self.exec_mem_load::(m.mem_addr(), m.offset(), i64::from)?, + + // Numeric conversion operations + F32ConvertI32S => stack_op!(unary i32 => f32, |v| v as f32), + F32ConvertI64S => stack_op!(unary i64 => f32, |v| v as f32), + F64ConvertI32S => stack_op!(unary i32 => f64, |v| f64::from(v)), + F64ConvertI64S => stack_op!(unary i64 => f64, |v| v as f64), + F32ConvertI32U => stack_op!(unary u32 => f32, |v| v as f32), + F32ConvertI64U => stack_op!(unary u64 => f32, |v| v as f32), + F64ConvertI32U => stack_op!(unary u32 => f64, |v| f64::from(v)), + F64ConvertI64U => stack_op!(unary u64 => f64, |v| v as f64), + + // Sign-extension operations + I32Extend8S => stack_op!(unary i32, |v| i32::from(v as i8)), + I32Extend16S => stack_op!(unary i32, |v| i32::from(v as i16)), + I64Extend8S => stack_op!(unary i64, |v| i64::from(v as i8)), + I64Extend16S => stack_op!(unary i64, |v| i64::from(v as i16)), + I64Extend32S => stack_op!(unary i64, |v| i64::from(v as i32)), + I64ExtendI32U => stack_op!(unary u32 => i64, |v| i64::from(v)), + I64ExtendI32S => stack_op!(unary i32 => i64, |v| i64::from(v)), + I32WrapI64 => stack_op!(unary i64 => i32, |v| v as i32), + F32DemoteF64 => stack_op!(unary f64 => f32, |v| v as f32), + F64PromoteF32 => stack_op!(unary f32 => f64, |v| f64::from(v)), + F32Abs => stack_op!(unary f32, |v| v.abs()), + F64Abs => stack_op!(unary f64, |v| v.abs()), + F32Neg => stack_op!(unary f32, |v| -v), + F64Neg => stack_op!(unary f64, |v| -v), + F32Ceil => stack_op!(unary f32, |v| v.ceil()), + F64Ceil => stack_op!(unary f64, |v| v.ceil()), + F32Floor => stack_op!(unary f32, |v| v.floor()), + F64Floor => stack_op!(unary f64, |v| v.floor()), + F32Trunc => stack_op!(unary f32, |v| v.trunc()), + F64Trunc => stack_op!(unary f64, |v| v.trunc()), + F32Nearest => stack_op!(unary f32, |v| v.tw_nearest()), + F64Nearest => stack_op!(unary f64, |v| v.tw_nearest()), + F32Sqrt => stack_op!(unary f32, |v| v.sqrt()), + F64Sqrt => stack_op!(unary f64, |v| v.sqrt()), + F32Min => stack_op!(binary f32, |a, b| a.tw_minimum(b)), + F64Min => stack_op!(binary f64, |a, b| a.tw_minimum(b)), + F32Max => stack_op!(binary f32, |a, b| a.tw_maximum(b)), + F64Max => stack_op!(binary f64, |a, b| a.tw_maximum(b)), + F32Copysign => stack_op!(binary f32, |a, b| a.copysign(b)), + F64Copysign => stack_op!(binary f64, |a, b| a.copysign(b)), + I32TruncF32S => checked_conv_float!(f32, i32, self), + I32TruncF64S => checked_conv_float!(f64, i32, self), + I32TruncF32U => checked_conv_float!(f32, u32, i32, self), + I32TruncF64U => checked_conv_float!(f64, u32, i32, self), + I64TruncF32S => checked_conv_float!(f32, i64, self), + I64TruncF64S => checked_conv_float!(f64, i64, self), + I64TruncF32U => checked_conv_float!(f32, u64, i64, self), + I64TruncF64U => checked_conv_float!(f64, u64, i64, self), + + // Non-trapping float-to-int conversions + I32TruncSatF32S => stack_op!(unary f32 => i32, |v| v.trunc() as i32), + I32TruncSatF32U => stack_op!(unary f32 => u32, |v| v.trunc() as u32), + I32TruncSatF64S => stack_op!(unary f64 => i32, |v| v.trunc() as i32), + I32TruncSatF64U => stack_op!(unary f64 => u32, |v| v.trunc() as u32), + I64TruncSatF32S => stack_op!(unary f32 => i64, |v| v.trunc() as i64), + I64TruncSatF32U => stack_op!(unary f32 => u64, |v| v.trunc() as u64), + I64TruncSatF64S => stack_op!(unary f64 => i64, |v| v.trunc() as i64), + I64TruncSatF64U => stack_op!(unary f64 => u64, |v| v.trunc() as u64), + + // SIMD extension + V128Not => stack_op!(unary Value128, |v| v.v128_not()), + V128And => stack_op!(binary Value128, |a, b| a.v128_and(b)), + V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), + V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), + V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), + V128Bitselect => stack_op!(ternary Value128, |v1, v2, c| Value128::v128_bitselect(v1, v2, c)), + V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), + I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), + V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, + V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, + V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, + V128Load16x4S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_s(v.to_le_bytes()))?, + V128Load16x4U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load16x4_u(v.to_le_bytes()))?, + V128Load32x2S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_s(v.to_le_bytes()))?, + V128Load32x2U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load32x2_u(v.to_le_bytes()))?, + V128Load8Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i8)?, + V128Load16Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i16)?, + V128Load32Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i32)?, + V128Load64Splat(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), Value128::splat_i64)?, + V128Store(arg) => self.exec_mem_store::(arg.mem_addr(), arg.offset(), |v| v)?, + V128Store8Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store16Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store32Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Store64Lane(arg, lane) => self.exec_mem_store_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load32Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i32x4([v, 0, 0, 0]))?, + V128Load64Zero(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::from_i64x2([v, 0]))?, + V128Const(arg) => self.exec_const::(self.func.data.v128_constants[*arg as usize].into())?, + I8x16ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i8(*lane) as i32), + I8x16ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u8(*lane) as i32), + I16x8ExtractLaneS(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i16(*lane) as i32), + I16x8ExtractLaneU(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_u16(*lane) as i32), + I32x4ExtractLane(lane) => stack_op!(unary Value128 => i32, |v| v.extract_lane_i32(*lane)), + I64x2ExtractLane(lane) => stack_op!(unary Value128 => i64, |v| v.extract_lane_i64(*lane)), + F32x4ExtractLane(lane) => stack_op!(unary Value128 => f32, |v| v.extract_lane_f32(*lane)), + F64x2ExtractLane(lane) => stack_op!(unary Value128 => f64, |v| v.extract_lane_f64(*lane)), + V128Load8Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load16Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load32Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + V128Load64Lane(arg, lane) => self.exec_mem_load_lane::(arg.mem_addr(), arg.offset(), *lane)?, + I8x16ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i8x16_replace_lane(*lane, value as i8)), + I16x8ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i16x8_replace_lane(*lane, value as i16)), + I32x4ReplaceLane(lane) => stack_op!(binary i32, Value128, |value, vec| vec.i32x4_replace_lane(*lane, value)), + I64x2ReplaceLane(lane) => stack_op!(binary i64, Value128, |value, vec| vec.i64x2_replace_lane(*lane, value)), + F32x4ReplaceLane(lane) => stack_op!(binary f32, Value128, |value, vec| vec.f32x4_replace_lane(*lane, value)), + F64x2ReplaceLane(lane) => stack_op!(binary f64, Value128, |value, vec| vec.f64x2_replace_lane(*lane, value)), + I8x16Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i8(v as i8)), + I16x8Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i16(v as i16)), + I32x4Splat => stack_op!(unary i32 => Value128, |v| Value128::splat_i32(v)), + I64x2Splat => stack_op!(unary i64 => Value128, |v| Value128::splat_i64(v)), + F32x4Splat => stack_op!(unary f32 => Value128, |v| Value128::splat_f32(v)), + F64x2Splat => stack_op!(unary f64 => Value128, |v| Value128::splat_f64(v)), + I8x16Eq => stack_op!(binary Value128, |a, b| a.i8x16_eq(b)), + I16x8Eq => stack_op!(binary Value128, |a, b| a.i16x8_eq(b)), + I32x4Eq => stack_op!(binary Value128, |a, b| a.i32x4_eq(b)), + I64x2Eq => stack_op!(binary Value128, |a, b| a.i64x2_eq(b)), + F32x4Eq => stack_op!(binary Value128, |a, b| a.f32x4_eq(b)), + F64x2Eq => stack_op!(binary Value128, |a, b| a.f64x2_eq(b)), + I8x16Ne => stack_op!(binary Value128, |a, b| a.i8x16_ne(b)), + I16x8Ne => stack_op!(binary Value128, |a, b| a.i16x8_ne(b)), + I32x4Ne => stack_op!(binary Value128, |a, b| a.i32x4_ne(b)), + I64x2Ne => stack_op!(binary Value128, |a, b| a.i64x2_ne(b)), + F32x4Ne => stack_op!(binary Value128, |a, b| a.f32x4_ne(b)), + F64x2Ne => stack_op!(binary Value128, |a, b| a.f64x2_ne(b)), + I8x16LtS => stack_op!(binary Value128, |a, b| a.i8x16_lt_s(b)), + I16x8LtS => stack_op!(binary Value128, |a, b| a.i16x8_lt_s(b)), + I32x4LtS => stack_op!(binary Value128, |a, b| a.i32x4_lt_s(b)), + I64x2LtS => stack_op!(binary Value128, |a, b| a.i64x2_lt_s(b)), + I8x16LtU => stack_op!(binary Value128, |a, b| a.i8x16_lt_u(b)), + I16x8LtU => stack_op!(binary Value128, |a, b| a.i16x8_lt_u(b)), + I32x4LtU => stack_op!(binary Value128, |a, b| a.i32x4_lt_u(b)), + F32x4Lt => stack_op!(binary Value128, |a, b| a.f32x4_lt(b)), + F64x2Lt => stack_op!(binary Value128, |a, b| a.f64x2_lt(b)), + F32x4Gt => stack_op!(binary Value128, |a, b| a.f32x4_gt(b)), + F64x2Gt => stack_op!(binary Value128, |a, b| a.f64x2_gt(b)), + I8x16GtS => stack_op!(binary Value128, |a, b| a.i8x16_gt_s(b)), + I16x8GtS => stack_op!(binary Value128, |a, b| a.i16x8_gt_s(b)), + I32x4GtS => stack_op!(binary Value128, |a, b| a.i32x4_gt_s(b)), + I64x2GtS => stack_op!(binary Value128, |a, b| a.i64x2_gt_s(b)), + I64x2LeS => stack_op!(binary Value128, |a, b| a.i64x2_le_s(b)), + F32x4Le => stack_op!(binary Value128, |a, b| a.f32x4_le(b)), + F64x2Le => stack_op!(binary Value128, |a, b| a.f64x2_le(b)), + I8x16GtU => stack_op!(binary Value128, |a, b| a.i8x16_gt_u(b)), + I16x8GtU => stack_op!(binary Value128, |a, b| a.i16x8_gt_u(b)), + I32x4GtU => stack_op!(binary Value128, |a, b| a.i32x4_gt_u(b)), + F32x4Ge => stack_op!(binary Value128, |a, b| a.f32x4_ge(b)), + F64x2Ge => stack_op!(binary Value128, |a, b| a.f64x2_ge(b)), + I8x16LeS => stack_op!(binary Value128, |a, b| a.i8x16_le_s(b)), + I16x8LeS => stack_op!(binary Value128, |a, b| a.i16x8_le_s(b)), + I32x4LeS => stack_op!(binary Value128, |a, b| a.i32x4_le_s(b)), + I8x16LeU => stack_op!(binary Value128, |a, b| a.i8x16_le_u(b)), + I16x8LeU => stack_op!(binary Value128, |a, b| a.i16x8_le_u(b)), + I32x4LeU => stack_op!(binary Value128, |a, b| a.i32x4_le_u(b)), + I8x16GeS => stack_op!(binary Value128, |a, b| a.i8x16_ge_s(b)), + I16x8GeS => stack_op!(binary Value128, |a, b| a.i16x8_ge_s(b)), + I32x4GeS => stack_op!(binary Value128, |a, b| a.i32x4_ge_s(b)), + I64x2GeS => stack_op!(binary Value128, |a, b| a.i64x2_ge_s(b)), + I8x16GeU => stack_op!(binary Value128, |a, b| a.i8x16_ge_u(b)), + I16x8GeU => stack_op!(binary Value128, |a, b| a.i16x8_ge_u(b)), + I32x4GeU => stack_op!(binary Value128, |a, b| a.i32x4_ge_u(b)), + I8x16Abs => stack_op!(unary Value128, |a| a.i8x16_abs()), + I16x8Abs => stack_op!(unary Value128, |a| a.i16x8_abs()), + I32x4Abs => stack_op!(unary Value128, |a| a.i32x4_abs()), + I64x2Abs => stack_op!(unary Value128, |a| a.i64x2_abs()), + I8x16Neg => stack_op!(unary Value128, |a| a.i8x16_neg()), + I16x8Neg => stack_op!(unary Value128, |a| a.i16x8_neg()), + I32x4Neg => stack_op!(unary Value128, |a| a.i32x4_neg()), + I64x2Neg => stack_op!(unary Value128, |a| a.i64x2_neg()), + I8x16AllTrue => stack_op!(unary Value128 => i32, |v| v.i8x16_all_true() as i32), + I16x8AllTrue => stack_op!(unary Value128 => i32, |v| v.i16x8_all_true() as i32), + I32x4AllTrue => stack_op!(unary Value128 => i32, |v| v.i32x4_all_true() as i32), + I64x2AllTrue => stack_op!(unary Value128 => i32, |v| v.i64x2_all_true() as i32), + I8x16Bitmask => stack_op!(unary Value128 => i32, |v| v.i8x16_bitmask() as i32), + I16x8Bitmask => stack_op!(unary Value128 => i32, |v| v.i16x8_bitmask() as i32), + I32x4Bitmask => stack_op!(unary Value128 => i32, |v| v.i32x4_bitmask() as i32), + I64x2Bitmask => stack_op!(unary Value128 => i32, |v| v.i64x2_bitmask() as i32), + I8x16Shl => stack_op!(binary i32, Value128, |a, b| b.i8x16_shl(a as u32)), + I16x8Shl => stack_op!(binary i32, Value128, |a, b| b.i16x8_shl(a as u32)), + I32x4Shl => stack_op!(binary i32, Value128, |a, b| b.i32x4_shl(a as u32)), + I64x2Shl => stack_op!(binary i32, Value128, |a, b| b.i64x2_shl(a as u32)), + I8x16ShrS => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_s(a as u32)), + I16x8ShrS => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_s(a as u32)), + I32x4ShrS => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_s(a as u32)), + I64x2ShrS => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_s(a as u32)), + I8x16ShrU => stack_op!(binary i32, Value128, |a, b| b.i8x16_shr_u(a as u32)), + I16x8ShrU => stack_op!(binary i32, Value128, |a, b| b.i16x8_shr_u(a as u32)), + I32x4ShrU => stack_op!(binary i32, Value128, |a, b| b.i32x4_shr_u(a as u32)), + I64x2ShrU => stack_op!(binary i32, Value128, |a, b| b.i64x2_shr_u(a as u32)), + I8x16Add => stack_op!(binary Value128, |a, b| a.i8x16_add(b)), + I16x8Add => stack_op!(binary Value128, |a, b| a.i16x8_add(b)), + I32x4Add => stack_op!(binary Value128, |a, b| a.i32x4_add(b)), + I64x2Add => stack_op!(binary Value128, |a, b| a.i64x2_add(b)), + I8x16Sub => stack_op!(binary Value128, |a, b| a.i8x16_sub(b)), + I16x8Sub => stack_op!(binary Value128, |a, b| a.i16x8_sub(b)), + I32x4Sub => stack_op!(binary Value128, |a, b| a.i32x4_sub(b)), + I64x2Sub => stack_op!(binary Value128, |a, b| a.i64x2_sub(b)), + I8x16MinS => stack_op!(binary Value128, |a, b| a.i8x16_min_s(b)), + I16x8MinS => stack_op!(binary Value128, |a, b| a.i16x8_min_s(b)), + I32x4MinS => stack_op!(binary Value128, |a, b| a.i32x4_min_s(b)), + I8x16MinU => stack_op!(binary Value128, |a, b| a.i8x16_min_u(b)), + I16x8MinU => stack_op!(binary Value128, |a, b| a.i16x8_min_u(b)), + I32x4MinU => stack_op!(binary Value128, |a, b| a.i32x4_min_u(b)), + I8x16MaxS => stack_op!(binary Value128, |a, b| a.i8x16_max_s(b)), + I16x8MaxS => stack_op!(binary Value128, |a, b| a.i16x8_max_s(b)), + I32x4MaxS => stack_op!(binary Value128, |a, b| a.i32x4_max_s(b)), + I8x16MaxU => stack_op!(binary Value128, |a, b| a.i8x16_max_u(b)), + I16x8MaxU => stack_op!(binary Value128, |a, b| a.i16x8_max_u(b)), + I32x4MaxU => stack_op!(binary Value128, |a, b| a.i32x4_max_u(b)), + I64x2Mul => stack_op!(binary Value128, |a, b| a.i64x2_mul(b)), + I16x8Mul => stack_op!(binary Value128, |a, b| a.i16x8_mul(b)), + I32x4Mul => stack_op!(binary Value128, |a, b| a.i32x4_mul(b)), + I8x16NarrowI16x8S => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_s(a, b)), + I8x16NarrowI16x8U => stack_op!(binary Value128, |a, b| Value128::i8x16_narrow_i16x8_u(a, b)), + I16x8NarrowI32x4S => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_s(a, b)), + I16x8NarrowI32x4U => stack_op!(binary Value128, |a, b| Value128::i16x8_narrow_i32x4_u(a, b)), + I8x16AddSatS => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_s(b)), + I16x8AddSatS => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_s(b)), + I8x16AddSatU => stack_op!(binary Value128, |a, b| a.i8x16_add_sat_u(b)), + I16x8AddSatU => stack_op!(binary Value128, |a, b| a.i16x8_add_sat_u(b)), + I8x16SubSatS => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_s(b)), + I16x8SubSatS => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_s(b)), + I8x16SubSatU => stack_op!(binary Value128, |a, b| a.i8x16_sub_sat_u(b)), + I16x8SubSatU => stack_op!(binary Value128, |a, b| a.i16x8_sub_sat_u(b)), + I8x16AvgrU => stack_op!(binary Value128, |a, b| a.i8x16_avgr_u(b)), + I16x8AvgrU => stack_op!(binary Value128, |a, b| a.i16x8_avgr_u(b)), + I16x8ExtAddPairwiseI8x16S => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_s()), + I16x8ExtAddPairwiseI8x16U => stack_op!(unary Value128, |a| a.i16x8_extadd_pairwise_i8x16_u()), + I32x4ExtAddPairwiseI16x8S => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_s()), + I32x4ExtAddPairwiseI16x8U => stack_op!(unary Value128, |a| a.i32x4_extadd_pairwise_i16x8_u()), + I16x8ExtMulLowI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_s(b)), + I16x8ExtMulLowI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_low_i8x16_u(b)), + I16x8ExtMulHighI8x16S => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_s(b)), + I16x8ExtMulHighI8x16U => stack_op!(binary Value128, |a, b| a.i16x8_extmul_high_i8x16_u(b)), + I32x4ExtMulLowI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_s(b)), + I32x4ExtMulLowI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_low_i16x8_u(b)), + I32x4ExtMulHighI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_s(b)), + I32x4ExtMulHighI16x8U => stack_op!(binary Value128, |a, b| a.i32x4_extmul_high_i16x8_u(b)), + I64x2ExtMulLowI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_s(b)), + I64x2ExtMulLowI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_low_i32x4_u(b)), + I64x2ExtMulHighI32x4S => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_s(b)), + I64x2ExtMulHighI32x4U => stack_op!(binary Value128, |a, b| a.i64x2_extmul_high_i32x4_u(b)), + I16x8ExtendLowI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_s()), + I16x8ExtendLowI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_low_i8x16_u()), + I16x8ExtendHighI8x16S => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_s()), + I16x8ExtendHighI8x16U => stack_op!(unary Value128, |a| a.i16x8_extend_high_i8x16_u()), + I32x4ExtendLowI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_s()), + I32x4ExtendLowI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_low_i16x8_u()), + I32x4ExtendHighI16x8S => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_s()), + I32x4ExtendHighI16x8U => stack_op!(unary Value128, |a| a.i32x4_extend_high_i16x8_u()), + I64x2ExtendLowI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_s()), + I64x2ExtendLowI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_low_i32x4_u()), + I64x2ExtendHighI32x4S => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_s()), + I64x2ExtendHighI32x4U => stack_op!(unary Value128, |a| a.i64x2_extend_high_i32x4_u()), + I8x16Popcnt => stack_op!(unary Value128, |v| v.i8x16_popcnt()), + I8x16Shuffle(idx) => { let idx = self.func.data.v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } + I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), + I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), + F32x4Ceil => stack_op!(simd_unary f32x4_ceil), + F64x2Ceil => stack_op!(simd_unary f64x2_ceil), + F32x4Floor => stack_op!(simd_unary f32x4_floor), + F64x2Floor => stack_op!(simd_unary f64x2_floor), + F32x4Trunc => stack_op!(simd_unary f32x4_trunc), + F64x2Trunc => stack_op!(simd_unary f64x2_trunc), + F32x4Nearest => stack_op!(simd_unary f32x4_nearest), + F64x2Nearest => stack_op!(simd_unary f64x2_nearest), + F32x4Abs => stack_op!(simd_unary f32x4_abs), + F64x2Abs => stack_op!(simd_unary f64x2_abs), + F32x4Neg => stack_op!(simd_unary f32x4_neg), + F64x2Neg => stack_op!(simd_unary f64x2_neg), + F32x4Sqrt => stack_op!(simd_unary f32x4_sqrt), + F64x2Sqrt => stack_op!(simd_unary f64x2_sqrt), + F32x4Add => stack_op!(simd_binary f32x4_add), + F64x2Add => stack_op!(simd_binary f64x2_add), + F32x4Sub => stack_op!(simd_binary f32x4_sub), + F64x2Sub => stack_op!(simd_binary f64x2_sub), + F32x4Mul => stack_op!(simd_binary f32x4_mul), + F64x2Mul => stack_op!(simd_binary f64x2_mul), + F32x4Div => stack_op!(simd_binary f32x4_div), + F64x2Div => stack_op!(simd_binary f64x2_div), + F32x4Min => stack_op!(simd_binary f32x4_min), + F64x2Min => stack_op!(simd_binary f64x2_min), + F32x4Max => stack_op!(simd_binary f32x4_max), + F64x2Max => stack_op!(simd_binary f64x2_max), + F32x4PMin => stack_op!(simd_binary f32x4_pmin), + F32x4PMax => stack_op!(simd_binary f32x4_pmax), + F64x2PMin => stack_op!(simd_binary f64x2_pmin), + F64x2PMax => stack_op!(simd_binary f64x2_pmax), + I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), + I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), + F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), + F32x4ConvertI32x4U => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_u()), + F64x2ConvertLowI32x4S => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_s()), + F64x2ConvertLowI32x4U => stack_op!(unary Value128, |v| v.f64x2_convert_low_i32x4_u()), + F32x4DemoteF64x2Zero => stack_op!(unary Value128, |v| v.f32x4_demote_f64x2_zero()), + F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), + I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), + I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), + }; - self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) + self.cf.incr_instr_ptr(); + } + + Ok(None) } #[inline(always)] - fn exec_jump(&mut self, ip: u32) -> ControlFlow> { + fn exec_jump(&mut self, ip: u32) { self.cf.instr_ptr = ip; - ControlFlow::Continue(()) } #[inline(always)] @@ -646,7 +647,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } #[inline(always)] - fn exec_branch_table(&mut self, default_ip: u32, len: u32) -> ControlFlow> { + fn exec_branch_table(&mut self, default_ip: u32, len: u32) { let idx = self.store.stack.values.pop::(); let start = self.cf.instr_ptr + 1; @@ -660,7 +661,6 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }; self.cf.instr_ptr = target_ip; - ControlFlow::Continue(()) } fn exec_call( @@ -668,7 +668,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { wasm_func: Rc, func_addr: FuncAddr, owner: ModuleInstanceAddr, - ) -> ControlFlow> { + ) -> Result<()> { if !Rc::ptr_eq(&self.func, &wasm_func) { self.func = wasm_func.clone(); } @@ -681,9 +681,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { match self.store.stack.values.enter_locals(wasm_func.params, wasm_func.locals) { Ok(v) => v, Err(Error::Trap(Trap::ValueStackOverflow)) if !IS_RETURN_CALL => { - return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + return Err(Trap::CallStackOverflow.into()); } - Err(err) => return ControlFlow::Break(Some(err)), + Err(err) => return Err(err), }; let new_call_frame = CallFrame::new(func_addr, owner, locals_base, stack_offset); @@ -692,7 +692,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.cf = new_call_frame; } else { self.cf.incr_instr_ptr(); // skip the call instruction - self.store.stack.call_stack.push(self.cf).to_cf()?; + self.store.stack.call_stack.push(self.cf)?; self.cf = new_call_frame; } @@ -700,16 +700,16 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.module = self.store.get_module_instance_raw(self.cf.module_addr).clone(); } - ControlFlow::Continue(()) + Ok(()) } - fn exec_call_host(&mut self, host_func: Rc) -> ControlFlow> { + fn exec_call_host(&mut self, host_func: Rc) -> Result<()> { let params = self.store.stack.values.pop_types(&host_func.ty.params).collect::>(); - let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.idx }, ¶ms).to_cf()?; - self.store.stack.values.extend_from_wasmvalues(&res).to_cf()?; + let res = host_func.call(FuncContext { store: self.store, module_addr: self.module.idx }, ¶ms)?; + self.store.stack.values.extend_from_wasmvalues(&res)?; self.cf.incr_instr_ptr(); - ControlFlow::Continue(()) + Ok(()) } - fn exec_call_direct(&mut self, v: u32) -> ControlFlow> { + fn exec_call_direct(&mut self, v: u32) -> Result<()> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); let addr = self.module.resolve_func_addr(v); let func_inst = self.store.state.get_func(addr); @@ -721,7 +721,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } } - fn exec_call_self(&mut self) -> ControlFlow> { + fn exec_call_self(&mut self) -> Result<()> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); let params = self.func.params; let locals = self.func.locals; @@ -733,9 +733,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let (locals_base, _stack_base, stack_offset) = match self.store.stack.values.enter_locals(params, locals) { Ok(v) => v, Err(Error::Trap(Trap::ValueStackOverflow)) if !IS_RETURN_CALL => { - return ControlFlow::Break(Some(Trap::CallStackOverflow.into())); + return Err(Trap::CallStackOverflow.into()); } - Err(err) => return ControlFlow::Break(Some(err)), + Err(err) => return Err(err), }; let new_call_frame = CallFrame::new(self.cf.func_addr, self.cf.module_addr, locals_base, stack_offset); @@ -744,27 +744,23 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.cf = new_call_frame; } else { self.cf.incr_instr_ptr(); - self.store.stack.call_stack.push(self.cf).to_cf()?; + self.store.stack.call_stack.push(self.cf)?; self.cf = new_call_frame; } - ControlFlow::Continue(()) + Ok(()) } - fn exec_call_indirect( - &mut self, - type_addr: u32, - table_addr: u32, - ) -> ControlFlow> { + fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> Result<()> { self.charge_call_fuel(FUEL_COST_CALL_TOTAL); // verify that the table is of the right type, this should be validated by the parser already let func_ref = { let table_idx: u32 = self.store.stack.values.pop::() as u32; let table = self.store.state.get_table(self.module.resolve_table_addr(table_addr)); assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref"); - let table = table.get(table_idx).map_err(|_| Trap::UndefinedElement { index: table_idx as usize }.into()); - let table = table.to_cf()?; - table.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize }.into()).to_cf()? + let table = + table.get(table_idx).map_err(|_| Error::from(Trap::UndefinedElement { index: table_idx as usize }))?; + table.addr().ok_or_else(|| Error::from(Trap::UninitializedElement { index: table_idx as usize }))? }; let func_inst = self.store.state.get_func(func_ref); @@ -773,20 +769,22 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { match &func_inst.func { crate::Function::Wasm(wasm_func) => { if wasm_func.ty != *call_ty { - return ControlFlow::Break(Some( - Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() } - .into(), - )); + return Err(Trap::IndirectCallTypeMismatch { + actual: wasm_func.ty.clone(), + expected: call_ty.clone(), + } + .into()); } self.exec_call::(wasm_func.clone(), func_ref, func_inst.owner) } crate::Function::Host(host_func) => { if host_func.ty != *call_ty { - return ControlFlow::Break(Some( - Trap::IndirectCallTypeMismatch { actual: host_func.ty.clone(), expected: call_ty.clone() } - .into(), - )); + return Err(Trap::IndirectCallTypeMismatch { + actual: host_func.ty.clone(), + expected: call_ty.clone(), + } + .into()); } self.exec_call_host(host_func.clone()) @@ -794,11 +792,11 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } } - fn exec_return(&mut self) -> ControlFlow> { + fn exec_return(&mut self) -> bool { let result_counts = ValueCountsSmall::from(self.func.ty.results.iter()); self.store.stack.values.truncate_keep_counts(self.cf.locals_base, result_counts); - let Some(cf) = self.store.stack.call_stack.pop() else { return ControlFlow::Break(None) }; + let Some(cf) = self.store.stack.call_stack.pop() else { return true }; if cf.func_addr != self.cf.func_addr { self.func = self.store.state.get_wasm_func(cf.func_addr).clone(); @@ -808,7 +806,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } } self.cf = cf; - ControlFlow::Continue(()) + false } fn exec_global_get(&mut self, global_index: u32) -> Result<()> { self.store.stack.values.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) @@ -928,24 +926,20 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { mem_addr: tinywasm_types::MemAddr, offset: u64, lane: u8, - ) -> ControlFlow> { + ) -> Result<()> { let mut imm = self.store.stack.values.pop::().to_mem_bytes(); let val = self.store.stack.values.pop::() as u64; let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let Some(Ok(addr)) = offset.checked_add(val).map(TryInto::try_into) else { - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: val as usize, - len: LOAD_SIZE, - max: 0, - }))); + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: val as usize, len: LOAD_SIZE, max: 0 })); }; - let val = mem.load_as::(addr).to_cf()?.to_mem_bytes(); + let val = mem.load_as::(addr)?.to_mem_bytes(); let offset = lane as usize * LOAD_SIZE; imm[offset..offset + LOAD_SIZE].copy_from_slice(&val); - self.store.stack.values.push(Value128::from_mem_bytes(imm)).to_cf()?; - ControlFlow::Continue(()) + self.store.stack.values.push(Value128::from_mem_bytes(imm))?; + Ok(()) } #[inline(always)] @@ -954,7 +948,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { mem_addr: tinywasm_types::MemAddr, offset: u64, cast: fn(LOAD) -> TARGET, - ) -> ControlFlow> { + ) -> Result<()> { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(mem_addr)); let base: u64 = if mem.is_64bit() { @@ -964,24 +958,16 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }; let Some(addr) = base.checked_add(offset) else { - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: base as usize, - len: LOAD_SIZE, - max: 0, - }))); + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: base as usize, len: LOAD_SIZE, max: 0 })); }; let Ok(addr) = usize::try_from(addr) else { - return ControlFlow::Break(Some(Error::Trap(Trap::MemoryOutOfBounds { - offset: base as usize, - len: LOAD_SIZE, - max: 0, - }))); + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: base as usize, len: LOAD_SIZE, max: 0 })); }; - let val = mem.load_as::(addr).to_cf()?; - self.store.stack.values.push(cast(val)).to_cf()?; - ControlFlow::Continue(()) + let val = mem.load_as::(addr)?; + self.store.stack.values.push(cast(val))?; + Ok(()) } fn exec_mem_store_lane + Copy, const N: usize>( @@ -989,7 +975,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { mem_addr: tinywasm_types::MemAddr, offset: u64, lane: u8, - ) -> ControlFlow> { + ) -> Result<()> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); let bytes = self.store.stack.values.pop::().to_mem_bytes(); let lane_offset = lane as usize * N; @@ -1002,10 +988,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }; if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return ControlFlow::Break(Some(e)); + return Err(e); } - ControlFlow::Continue(()) + Ok(()) } fn exec_mem_store, const N: usize>( @@ -1013,7 +999,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { mem_addr: tinywasm_types::MemAddr, offset: u64, cast: fn(T) -> U, - ) -> ControlFlow> { + ) -> Result<()> { let val = self.store.stack.values.pop::(); let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(mem_addr)); let val = (cast(val)).to_mem_bytes(); @@ -1024,10 +1010,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }; if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return ControlFlow::Break(Some(e)); + return Err(e); } - ControlFlow::Continue(()) + Ok(()) } fn exec_table_get(&mut self, table_index: u32) -> Result<()> { @@ -1129,16 +1115,16 @@ impl<'store> Executor<'store, false> { #[inline(always)] pub(crate) fn run_to_completion(&mut self) -> Result<()> { loop { - match self.exec_next() { - ControlFlow::Continue(()) => continue, - ControlFlow::Break(res) => break res.map_or(Ok(()), Err), + // for some reason, using a iteration count of 4096 here seems to be a sweet spot for performance + if self.exec::<1024>()?.is_some() { + return Ok(()); } } } #[cfg(feature = "std")] #[inline(always)] - pub(crate) fn run_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result { + pub(crate) fn run_with_time_budget(&mut self, time_budget: core::time::Duration) -> Result { use crate::std::time::Instant; let start = Instant::now(); if time_budget.is_zero() { @@ -1146,12 +1132,8 @@ impl<'store> Executor<'store, false> { } loop { - for _ in 0..TIME_BUDGET_CHECK_INTERVAL { - match self.exec_next() { - ControlFlow::Continue(()) => {} - ControlFlow::Break(None) => return Ok(ExecState::Completed), - ControlFlow::Break(Some(e)) => return Err(e), - } + if self.exec::()?.is_some() { + return Ok(ExecState::Completed); } if start.elapsed() >= time_budget { @@ -1170,15 +1152,11 @@ impl<'store> Executor<'store, true> { } loop { - for _ in 0..FUEL_ACCOUNTING_INTERVAL { - match self.exec_next() { - ControlFlow::Continue(()) => {} - ControlFlow::Break(None) => return Ok(ExecState::Completed), - ControlFlow::Break(Some(e)) => return Err(e), - } + if self.exec::()?.is_some() { + return Ok(ExecState::Completed); } - self.store.execution_fuel = self.store.execution_fuel.saturating_sub(FUEL_ACCOUNTING_INTERVAL); + self.store.execution_fuel = self.store.execution_fuel.saturating_sub(FUEL_ACCOUNTING_INTERVAL as u32); if self.store.execution_fuel == 0 { return Ok(ExecState::Suspended(self.cf)); } diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 933e782b..1af7d2f9 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -38,7 +38,7 @@ impl InterpreterRuntime { pub(crate) fn exec_with_time_budget( store: &mut Store, cf: CallFrame, - time_budget: crate::std::time::Duration, + time_budget: core::time::Duration, ) -> Result { executor::Executor::::new(store, cf)?.run_with_time_budget(time_budget) } diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index cf13260e..5ae81ef8 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -31,21 +31,16 @@ macro_rules! checked_conv_float { }; // Conversion with an intermediate unsigned type and error checking (three types) ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { - $self - .store - .stack - .values - .unary_into::<$from, $to>(|v| { - let (min, max) = float_min_max!($from, $intermediate); - if unlikely(v.is_nan()) { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - if unlikely(v <= min || v >= max) { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - Ok((v as $intermediate as $to).into()) - }) - .to_cf()? + $self.store.stack.values.unary_into::<$from, $to>(|v| { + let (min, max) = float_min_max!($from, $intermediate); + if unlikely(v.is_nan()) { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + if unlikely(v <= min || v >= max) { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + Ok((v as $intermediate as $to).into()) + })? }; } diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 9b0a5308..0e4ba699 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -100,7 +100,8 @@ fn test_linking_invalid_typed_func() -> Result<()> { let mut store = Store::default(); let mut imports = Imports::new(); imports.define("host", "hfn", typed_fn).unwrap(); - module.clone().instantiate(&mut store, Some(imports))?; + let link_failure = module.clone().instantiate(&mut store, Some(imports)); + assert!(link_failure.is_err(), "Expected linking to fail for mismatched typed func, but it succeeded"); } } diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 4574d23c..d51840b6 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -7,7 +7,7 @@ out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" rust_features="+simd128,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" -wasmopt_features="--enable-simd --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int --duplicate-function-elimination" +wasmopt_features="--enable-simd --enable-tail-call --enable-extended-const --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int --duplicate-function-elimination" # ensure out dir exists mkdir -p "$dest_dir" From a15acabfc1e23282d8fba9ec660f593b96709b2a Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 21:30:45 +0200 Subject: [PATCH 30/47] chore: cleanup Signed-off-by: Henry --- crates/parser/src/module.rs | 6 ++ crates/tinywasm/src/func.rs | 8 +- crates/tinywasm/src/interpreter/executor.rs | 58 +++++---------- .../src/interpreter/stack/value_stack.rs | 74 +++++++++++-------- crates/types/src/lib.rs | 2 +- 5 files changed, 70 insertions(+), 78 deletions(-) diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index a4afad0d..27fcb1b0 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -210,6 +210,12 @@ impl ModuleReader { .map(|(func_idx, ((instructions, data, locals), ty_idx))| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); let params = ValueCountsSmall::from(&ty.params); + let locals = ValueCountsSmall { + c32: u16::try_from(locals.c32).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + c64: u16::try_from(locals.c64).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + c128: u16::try_from(locals.c128).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + }; let self_func_addr = imported_func_count + func_idx as u32; let mut instructions = instructions.to_vec(); Self::apply_instruction_rewrites(&mut instructions, self_func_addr); diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 98de9a2e..fd1279fb 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -66,8 +66,8 @@ impl FuncHandle { // Reset stack, push args, allocate locals, create entry frame. store.stack.clear(); store.stack.values.extend_from_wasmvalues(params)?; - let (locals_base, _stack_base, stack_offset) = - store.stack.values.enter_locals(wasm_func.params, wasm_func.locals)?; + let locals_base = store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals)?; + let stack_offset = wasm_func.locals; let callframe = CallFrame::new(self.addr, func_inst.owner, locals_base, stack_offset); // Execute until completion and then collect result values from the stack. @@ -100,8 +100,8 @@ impl FuncHandle { Function::Wasm(wasm_func) => { store.stack.clear(); store.stack.values.extend_from_wasmvalues(params)?; - let (locals_base, _stack_base, stack_offset) = - store.stack.values.enter_locals(wasm_func.params, wasm_func.locals)?; + let locals_base = store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals)?; + let stack_offset = wasm_func.locals; let callframe = CallFrame::new(self.addr, func_inst_owner, locals_base, stack_offset); Ok(FuncExecution { diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 76464cb9..85208bf6 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -49,10 +49,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { #[inline(always)] fn exec(&mut self) -> Result> { - for _ in 0..ITERATIONS { - use tinywasm_types::Instruction::*; - - macro_rules! stack_op { + macro_rules! stack_op { (simd_unary $method:ident) => { stack_op!(unary Value128, |v| v.$method()) }; (simd_binary $method:ident) => { stack_op!(binary Value128, |a, b| a.$method(b)) }; (unary $ty:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary::<$ty>(|$v| Ok($expr))? }; @@ -73,6 +70,9 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { }}; } + for _ in 0..ITERATIONS { + use tinywasm_types::Instruction::*; + let next = match self.func.instructions.0.get(self.cf.instr_ptr as usize) { Some(instr) => instr, None => unreachable!( @@ -164,9 +164,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); let value = self.store.stack.values.local_get::(&self.cf, *value_local).to_mem_bytes(); - if let Err(e) = mem.store((m.offset() + addr) as usize, value.len(), &value) { - return Err(e); - } + mem.store((m.offset() + addr) as usize, value.len(), &value)?; } I32LoadLocalTee(m, addr_local, dst_local) => { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); @@ -677,24 +675,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.stack.values.truncate_keep_counts(self.cf.locals_base, wasm_func.params); } - let (locals_base, _stack_base, stack_offset) = - match self.store.stack.values.enter_locals(wasm_func.params, wasm_func.locals) { - Ok(v) => v, - Err(Error::Trap(Trap::ValueStackOverflow)) if !IS_RETURN_CALL => { - return Err(Trap::CallStackOverflow.into()); - } - Err(err) => return Err(err), - }; - - let new_call_frame = CallFrame::new(func_addr, owner, locals_base, stack_offset); + let res = self.store.stack.values.enter_locals(&wasm_func.params, &wasm_func.locals); + let locals_base = res.map_err(|err| if IS_RETURN_CALL { err } else { Error::Trap(Trap::CallStackOverflow) })?; + let new_call_frame = CallFrame::new(func_addr, owner, locals_base, wasm_func.locals); - if IS_RETURN_CALL { - self.cf = new_call_frame; - } else { + if !IS_RETURN_CALL { self.cf.incr_instr_ptr(); // skip the call instruction self.store.stack.call_stack.push(self.cf)?; - self.cf = new_call_frame; } + self.cf = new_call_frame; if self.cf.module_addr != self.module.idx { self.module = self.store.get_module_instance_raw(self.cf.module_addr).clone(); @@ -730,24 +719,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.store.stack.values.truncate_keep_counts(self.cf.locals_base, params); } - let (locals_base, _stack_base, stack_offset) = match self.store.stack.values.enter_locals(params, locals) { - Ok(v) => v, - Err(Error::Trap(Trap::ValueStackOverflow)) if !IS_RETURN_CALL => { - return Err(Trap::CallStackOverflow.into()); - } - Err(err) => return Err(err), - }; - - let new_call_frame = CallFrame::new(self.cf.func_addr, self.cf.module_addr, locals_base, stack_offset); + let res = self.store.stack.values.enter_locals(¶ms, &locals); + let locals_base = res.map_err(|err| if IS_RETURN_CALL { err } else { Error::Trap(Trap::CallStackOverflow) })?; + let new_call_frame = CallFrame::new(self.cf.func_addr, self.cf.module_addr, locals_base, locals); - if IS_RETURN_CALL { - self.cf = new_call_frame; - } else { + if !IS_RETURN_CALL { self.cf.incr_instr_ptr(); self.store.stack.call_stack.push(self.cf)?; - self.cf = new_call_frame; } - + self.cf = new_call_frame; Ok(()) } @@ -987,9 +967,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { false => self.store.stack.values.pop::() as u32 as u64, }; - if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return Err(e); - } + mem.store((offset + addr) as usize, val.len(), &val)?; Ok(()) } @@ -1009,9 +987,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { false => u64::from(self.store.stack.values.pop::() as u32), }; - if let Err(e) = mem.store((offset + addr) as usize, val.len(), &val) { - return Err(e); - } + mem.store((offset + addr) as usize, val.len(), &val)?; Ok(()) } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 395f0a49..1ec20fe6 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,6 +1,6 @@ use alloc::boxed::Box; use alloc::vec::Vec; -use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCounts, ValueCountsSmall, WasmValue}; +use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCountsSmall, WasmValue}; use crate::{Result, Trap, engine::Config, interpreter::*, unlikely}; @@ -84,20 +84,22 @@ impl Stack { pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { debug_assert!(n <= self.len); - if n >= self.len { + let len = self.len; + if n >= len { return; } - let keep = (self.len - n).min(end_keep); - if keep != 0 { - let src = self.len - keep; - self.data.copy_within(src..self.len, n); + if end_keep == 0 { + self.len = n; + return; } + let keep = (len - n).min(end_keep); + self.data.copy_within((len - keep)..len, n); self.len = n + keep; } - pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result<(u32, u32)> { + pub(crate) fn enter_locals(&mut self, param_count: usize, local_count: usize) -> Result { debug_assert!(param_count <= local_count); let start = self.len - param_count; let end = start + local_count; @@ -107,10 +109,11 @@ impl Stack { } let init_start = start + param_count; - self.data[init_start..end].fill(T::default()); + if init_start != end { + self.data[init_start..end].fill(T::default()); + } self.len = end; - - Ok((start as u32, end as u32)) + Ok(start as u32) } pub(crate) fn select_many(&mut self, count: usize, condition: bool) { @@ -246,33 +249,40 @@ impl ValueStack { val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) } - pub(crate) fn enter_locals( - &mut self, - params: ValueCountsSmall, - locals: ValueCounts, - ) -> Result<(StackBase, StackBase, ValueCountsSmall)> { - let stack_offset = ValueCountsSmall { - c32: u16::try_from(locals.c32).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - c64: u16::try_from(locals.c64).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - c128: u16::try_from(locals.c128).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), + pub(crate) fn enter_locals(&mut self, params: &ValueCountsSmall, locals: &ValueCountsSmall) -> Result { + let locals_base32 = if params.c32 == 0 && locals.c32 == 0 { + self.stack_32.len as u32 + } else { + self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)? + }; + let locals_base64 = if params.c64 == 0 && locals.c64 == 0 { + self.stack_64.len as u32 + } else { + self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize)? + }; + let locals_base128 = if params.c128 == 0 && locals.c128 == 0 { + self.stack_128.len as u32 + } else { + self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize)? + }; + let locals_baseref = if params.cref == 0 && locals.cref == 0 { + self.stack_ref.len as u32 + } else { + self.stack_ref.enter_locals(params.cref as usize, locals.cref as usize)? }; - let (locals_base32, stack_base32) = self.stack_32.enter_locals(params.c32 as usize, locals.c32 as usize)?; - let (locals_base64, stack_base64) = self.stack_64.enter_locals(params.c64 as usize, locals.c64 as usize)?; - let (locals_base128, stack_base128) = - self.stack_128.enter_locals(params.c128 as usize, locals.c128 as usize)?; - let (locals_baseref, stack_baseref) = - self.stack_ref.enter_locals(params.cref as usize, locals.cref as usize)?; - - Ok(( - StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128, sref: locals_baseref }, - StackBase { s32: stack_base32, s64: stack_base64, s128: stack_base128, sref: stack_baseref }, - stack_offset, - )) + Ok(StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128, sref: locals_baseref }) } pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCountsSmall) { + if keep.c32 == 0 && keep.c64 == 0 && keep.c128 == 0 && keep.cref == 0 { + self.stack_32.len = base.s32 as usize; + self.stack_64.len = base.s64 as usize; + self.stack_128.len = base.s128 as usize; + self.stack_ref.len = base.sref as usize; + return; + } + self.stack_32.truncate_keep(base.s32 as usize, keep.c32 as usize); self.stack_64.truncate_keep(base.s64 as usize, keep.c64 as usize); self.stack_128.truncate_keep(base.s128 as usize, keep.c128 as usize); diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 8a815cb1..2a167a4a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -262,7 +262,7 @@ impl<'a, T: IntoIterator> From for ValueCountsSmall { pub struct WasmFunction { pub instructions: ArcSlice, pub data: WasmFunctionData, - pub locals: ValueCounts, + pub locals: ValueCountsSmall, pub params: ValueCountsSmall, pub ty: FuncType, } From 1f741ba37d8217de333488d0a66fa6cc7f8537a7 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 23:40:04 +0200 Subject: [PATCH 31/47] feat: add relaxed simd Signed-off-by: Henry --- README.md | 2 +- crates/parser/src/lib.rs | 2 +- crates/parser/src/visit.rs | 15 ++ crates/tinywasm/src/interpreter/executor.rs | 28 +++- crates/tinywasm/src/interpreter/value128.rs | 128 ++++++++++++++++++ .../tests/generated/wasm-relaxed-simd.csv | 2 +- crates/tinywasm/tests/test-wast.rs | 3 +- crates/tinywasm/tests/testsuite/run.rs | 32 +++-- crates/tinywasm/tests/testsuite/util.rs | 34 ++++- crates/types/src/instructions.rs | 28 ++-- profile.sh | 3 - tinywasm.png | Bin 115910 -> 0 bytes 12 files changed, 234 insertions(+), 43 deletions(-) delete mode 100755 profile.sh delete mode 100644 tinywasm.png diff --git a/README.md b/README.md index f8f4f7f4..cb3a4755 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Untrusted WebAssembly code should not be able to crash the runtime or access mem | [**Fixed-Width SIMD**](https://github.com/WebAssembly/simd/blob/main/proposals/simd/Overview.md) | 🟢 | `next` | | [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | | [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | -| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🚧 | - | +| [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🟢 | `next` | | [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🚧 | - | | [**Custom Descriptors**](https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md) | 🌑 | - | | [**Exception Handling**](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) | 🌑 | - | diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index ff2cacfa..a799f69d 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -79,7 +79,7 @@ impl Parser { exceptions: false, gc: false, memory_control: false, - relaxed_simd: false, + relaxed_simd: true, threads: false, shared_everything_threads: false, legacy_exceptions: false, diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 7eefa1ca..e3de0095 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -363,6 +363,7 @@ macro_rules! impl_visit_operator { (@@saturating_float_to_int $($rest:tt)* ) => {}; (@@bulk_memory $($rest:tt)* ) => {}; (@@simd $($rest:tt)* ) => {}; + (@@relaxed_simd $($rest:tt)* ) => {}; (@@tail_call $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { @@ -830,6 +831,7 @@ macro_rules! impl_visit_simd_operator { }; (@@simd $($rest:tt)* ) => {}; + (@@relaxed_simd $($rest:tt)* ) => {}; (@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => { fn $visit(&mut self $($(,$arg: $argty)*)?) { self.unsupported(stringify!($visit)) @@ -879,6 +881,19 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuild visit_f64x2_convert_low_i32x4_s(F64x2ConvertLowI32x4S), visit_f64x2_convert_low_i32x4_u(F64x2ConvertLowI32x4U), visit_f32x4_demote_f64x2_zero(F32x4DemoteF64x2Zero), visit_f64x2_promote_low_f32x4(F64x2PromoteLowF32x4), + visit_i8x16_relaxed_swizzle(I8x16RelaxedSwizzle), + visit_i32x4_relaxed_trunc_f32x4_s(I32x4RelaxedTruncF32x4S), visit_i32x4_relaxed_trunc_f32x4_u(I32x4RelaxedTruncF32x4U), + visit_i32x4_relaxed_trunc_f64x2_s_zero(I32x4RelaxedTruncF64x2SZero), visit_i32x4_relaxed_trunc_f64x2_u_zero(I32x4RelaxedTruncF64x2UZero), + visit_f32x4_relaxed_madd(F32x4RelaxedMadd), visit_f32x4_relaxed_nmadd(F32x4RelaxedNmadd), + visit_f64x2_relaxed_madd(F64x2RelaxedMadd), visit_f64x2_relaxed_nmadd(F64x2RelaxedNmadd), + visit_i8x16_relaxed_laneselect(I8x16RelaxedLaneselect), visit_i16x8_relaxed_laneselect(I16x8RelaxedLaneselect), + visit_i32x4_relaxed_laneselect(I32x4RelaxedLaneselect), visit_i64x2_relaxed_laneselect(I64x2RelaxedLaneselect), + visit_f32x4_relaxed_min(F32x4RelaxedMin), visit_f32x4_relaxed_max(F32x4RelaxedMax), + visit_f64x2_relaxed_min(F64x2RelaxedMin), visit_f64x2_relaxed_max(F64x2RelaxedMax), + visit_i16x8_relaxed_q15mulr_s(I16x8RelaxedQ15mulrS), + visit_i16x8_relaxed_dot_i8x16_i7x16_s(I16x8RelaxedDotI8x16I7x16S), + visit_i32x4_relaxed_dot_i8x16_i7x16_add_s(I32x4RelaxedDotI8x16I7x16AddS), + visit_i8x16_extract_lane_s(I8x16ExtractLaneS, u8), visit_i8x16_extract_lane_u(I8x16ExtractLaneU, u8), visit_i8x16_replace_lane(I8x16ReplaceLane, u8), visit_i16x8_extract_lane_s(I16x8ExtractLaneS, u8), visit_i16x8_extract_lane_u(I16x8ExtractLaneU, u8), visit_i16x8_replace_lane(I16x8ReplaceLane, u8), visit_i32x4_extract_lane(I32x4ExtractLane, u8), visit_i32x4_replace_lane(I32x4ReplaceLane, u8), diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 85208bf6..17343ee2 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -152,12 +152,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { LocalCopy64(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), LocalCopy128(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), LocalCopyRef(from, to) => self.store.stack.values.local_set(&self.cf, *to, self.store.stack.values.local_get::(&self.cf, *from)), - I32AddLocals(a, b) => self.store.stack.values.push( - self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), - )?, - I64AddLocals(a, b) => self.store.stack.values.push( - self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)), - )?, + I32AddLocals(a, b) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)))?, + I64AddLocals(a, b) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)))?, I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), I32StoreLocalLocal(m, addr_local, value_local) => { @@ -394,6 +390,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { V128Bitselect => stack_op!(ternary Value128, |v1, v2, c| Value128::v128_bitselect(v1, v2, c)), V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), + I8x16RelaxedSwizzle => stack_op!(binary Value128, |a, s| a.i8x16_relaxed_swizzle(s)), V128Load(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| v)?, V128Load8x8S(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_s(v.to_le_bytes()))?, V128Load8x8U(arg) => self.exec_mem_load::(arg.mem_addr(), arg.offset(), |v| Value128::v128_load8x8_u(v.to_le_bytes()))?, @@ -582,6 +579,13 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I8x16Shuffle(idx) => { let idx = self.func.data.v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), + I8x16RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i8x16_relaxed_laneselect(v1, v2, c)), + I16x8RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i16x8_relaxed_laneselect(v1, v2, c)), + I32x4RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i32x4_relaxed_laneselect(v1, v2, c)), + I64x2RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i64x2_relaxed_laneselect(v1, v2, c)), + I16x8RelaxedQ15mulrS => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_q15mulr_s(b)), + I16x8RelaxedDotI8x16I7x16S => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_dot_i8x16_i7x16_s(b)), + I32x4RelaxedDotI8x16I7x16AddS => stack_op!(ternary Value128, |a, b, c| a.i32x4_relaxed_dot_i8x16_i7x16_add_s(b, c)), F32x4Ceil => stack_op!(simd_unary f32x4_ceil), F64x2Ceil => stack_op!(simd_unary f64x2_ceil), F32x4Floor => stack_op!(simd_unary f32x4_floor), @@ -612,6 +616,14 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { F32x4PMax => stack_op!(simd_binary f32x4_pmax), F64x2PMin => stack_op!(simd_binary f64x2_pmin), F64x2PMax => stack_op!(simd_binary f64x2_pmax), + F32x4RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_madd(b, c)), + F32x4RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_nmadd(b, c)), + F64x2RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_madd(b, c)), + F64x2RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_nmadd(b, c)), + F32x4RelaxedMin => stack_op!(binary Value128, |a, b| a.f32x4_relaxed_min(b)), + F32x4RelaxedMax => stack_op!(binary Value128, |a, b| a.f32x4_relaxed_max(b)), + F64x2RelaxedMin => stack_op!(binary Value128, |a, b| a.f64x2_relaxed_min(b)), + F64x2RelaxedMax => stack_op!(binary Value128, |a, b| a.f64x2_relaxed_max(b)), I32x4TruncSatF32x4S => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_s()), I32x4TruncSatF32x4U => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f32x4_u()), F32x4ConvertI32x4S => stack_op!(unary Value128, |v| v.f32x4_convert_i32x4_s()), @@ -622,6 +634,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { F64x2PromoteLowF32x4 => stack_op!(unary Value128, |v| v.f64x2_promote_low_f32x4()), I32x4TruncSatF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_s_zero()), I32x4TruncSatF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_trunc_sat_f64x2_u_zero()), + I32x4RelaxedTruncF32x4S => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f32x4_s()), + I32x4RelaxedTruncF32x4U => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f32x4_u()), + I32x4RelaxedTruncF64x2SZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_s_zero()), + I32x4RelaxedTruncF64x2UZero => stack_op!(unary Value128, |v| v.i32x4_relaxed_trunc_f64x2_u_zero()), }; self.cf.incr_instr_ptr(); diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs index 9ccb7fc9..c585b355 100644 --- a/crates/tinywasm/src/interpreter/value128.rs +++ b/crates/tinywasm/src/interpreter/value128.rs @@ -609,6 +609,11 @@ impl Value128 { Self::from_le_bytes(out) } + #[doc(alias = "i8x16.relaxed_swizzle")] + pub fn i8x16_relaxed_swizzle(self, s: Self) -> Self { + self.i8x16_swizzle(s) + } + #[doc(alias = "i8x16.shuffle")] pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { let mut src = [0u8; 32]; @@ -938,6 +943,65 @@ impl Value128 { Self::from_i32x4(out) } + #[doc(alias = "i8x16.relaxed_laneselect")] + pub fn i8x16_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i16x8.relaxed_laneselect")] + pub fn i16x8_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i32x4.relaxed_laneselect")] + pub fn i32x4_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i64x2.relaxed_laneselect")] + pub fn i64x2_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i16x8.relaxed_q15mulr_s")] + pub fn i16x8_relaxed_q15mulr_s(self, rhs: Self) -> Self { + self.i16x8_q15mulr_sat_s(rhs) + } + + #[doc(alias = "i16x8.relaxed_dot_i8x16_i7x16_s")] + pub fn i16x8_relaxed_dot_i8x16_i7x16_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i16; 8]; + + for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { + let prod0 = (a_pair[0] as i16) * (b_pair[0] as i16); + let prod1 = (a_pair[1] as i16) * (b_pair[1] as i16); + *dst = prod0.wrapping_add(prod1); + } + + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.relaxed_dot_i8x16_i7x16_add_s")] + pub fn i32x4_relaxed_dot_i8x16_i7x16_add_s(self, rhs: Self, acc: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let c = acc.as_i32x4(); + let mut out = [0i32; 4]; + + for (i, dst) in out.iter_mut().enumerate() { + let base = i * 4; + let mut sum = 0i32; + for j in 0..4 { + sum = sum.wrapping_add((a[base + j] as i32).wrapping_mul(b[base + j] as i32)); + } + *dst = sum.wrapping_add(c[i]); + } + + Self::from_i32x4(out) + } + simd_cmp_mask!(i8x16_eq, "i8x16.eq", i8x16_eq, i8, 16, as_i8x16, from_i8x16, ==); simd_cmp_mask!(i16x8_eq, "i16x8.eq", i16x8_eq, i16, 8, as_i16x8, from_i16x8, ==); simd_cmp_mask!(i32x4_eq, "i32x4.eq", i32x4_eq, i32, 4, as_i32x4, from_i32x4, ==); @@ -1058,6 +1122,70 @@ impl Value128 { simd_float_binary!(f32x4_pmax, "f32x4.pmax", zip_f32x4, |a, b| if b > a { b } else { a }); simd_float_binary!(f64x2_pmax, "f64x2.pmax", zip_f64x2, |a, b| if b > a { b } else { a }); + #[doc(alias = "f32x4.relaxed_madd")] + pub fn f32x4_relaxed_madd(self, b: Self, c: Self) -> Self { + self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(x * y)) + .zip_f32x4(c, |xy, z| canonicalize_simd_f32_nan(xy + z)) + } + + #[doc(alias = "f32x4.relaxed_nmadd")] + pub fn f32x4_relaxed_nmadd(self, b: Self, c: Self) -> Self { + self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(-(x * y))) + .zip_f32x4(c, |neg_xy, z| canonicalize_simd_f32_nan(neg_xy + z)) + } + + #[doc(alias = "f64x2.relaxed_madd")] + pub fn f64x2_relaxed_madd(self, b: Self, c: Self) -> Self { + self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(x * y)) + .zip_f64x2(c, |xy, z| canonicalize_simd_f64_nan(xy + z)) + } + + #[doc(alias = "f64x2.relaxed_nmadd")] + pub fn f64x2_relaxed_nmadd(self, b: Self, c: Self) -> Self { + self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(-(x * y))) + .zip_f64x2(c, |neg_xy, z| canonicalize_simd_f64_nan(neg_xy + z)) + } + + #[doc(alias = "f32x4.relaxed_min")] + pub fn f32x4_relaxed_min(self, rhs: Self) -> Self { + self.f32x4_min(rhs) + } + + #[doc(alias = "f64x2.relaxed_min")] + pub fn f64x2_relaxed_min(self, rhs: Self) -> Self { + self.f64x2_min(rhs) + } + + #[doc(alias = "f32x4.relaxed_max")] + pub fn f32x4_relaxed_max(self, rhs: Self) -> Self { + self.f32x4_max(rhs) + } + + #[doc(alias = "f64x2.relaxed_max")] + pub fn f64x2_relaxed_max(self, rhs: Self) -> Self { + self.f64x2_max(rhs) + } + + #[doc(alias = "i32x4.relaxed_trunc_f32x4_s")] + pub fn i32x4_relaxed_trunc_f32x4_s(self) -> Self { + self.i32x4_trunc_sat_f32x4_s() + } + + #[doc(alias = "i32x4.relaxed_trunc_f32x4_u")] + pub fn i32x4_relaxed_trunc_f32x4_u(self) -> Self { + self.i32x4_trunc_sat_f32x4_u() + } + + #[doc(alias = "i32x4.relaxed_trunc_f64x2_s_zero")] + pub fn i32x4_relaxed_trunc_f64x2_s_zero(self) -> Self { + self.i32x4_trunc_sat_f64x2_s_zero() + } + + #[doc(alias = "i32x4.relaxed_trunc_f64x2_u_zero")] + pub fn i32x4_relaxed_trunc_f64x2_u_zero(self) -> Self { + self.i32x4_trunc_sat_f64x2_u_zero() + } + #[doc(alias = "i32x4.trunc_sat_f32x4_s")] pub fn i32x4_trunc_sat_f32x4_s(self) -> Self { let v = self.as_f32x4(); diff --git a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv index a6e6228e..6b89d8a5 100644 --- a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv @@ -1 +1 @@ -0.9.0-alpha.0,0,93,[{"name":"i16x8_relaxed_q15mulr_s.wast","passed":0,"failed":3},{"name":"i32x4_relaxed_trunc.wast","passed":0,"failed":17},{"name":"i8x16_relaxed_swizzle.wast","passed":0,"failed":6},{"name":"relaxed_dot_product.wast","passed":0,"failed":11},{"name":"relaxed_laneselect.wast","passed":0,"failed":12},{"name":"relaxed_madd_nmadd.wast","passed":0,"failed":19},{"name":"relaxed_min_max.wast","passed":0,"failed":25}] +0.9.0-alpha.0,93,0,[{"name":"i16x8_relaxed_q15mulr_s.wast","passed":3,"failed":0},{"name":"i32x4_relaxed_trunc.wast","passed":17,"failed":0},{"name":"i8x16_relaxed_swizzle.wast","passed":6,"failed":0},{"name":"relaxed_dot_product.wast","passed":11,"failed":0},{"name":"relaxed_laneselect.wast","passed":12,"failed":0},{"name":"relaxed_madd_nmadd.wast","passed":19,"failed":0},{"name":"relaxed_min_max.wast","passed":25,"failed":0}] diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs index 40b0e043..ff2b2ed7 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; -use eyre::{Result, bail, eyre}; -use owo_colors::OwoColorize; +use eyre::{Result, bail}; use testsuite::TestSuite; mod testsuite; diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index 6352730d..d13c59b1 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -410,7 +410,7 @@ impl TestSuite { AssertReturn { span, exec, results } => { info!("AssertReturn: {exec:?}"); - let expected = match convert_wastret(results.into_iter()) { + let expected_alternatives = match convert_wastret(results.into_iter()) { Err(err) => { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), @@ -449,14 +449,19 @@ impl TestSuite { continue; } }; - let expected = expected.first().expect("expected global value"); - let module_global = module_global.attach_type(expected.val_type()); + let expected = expected_alternatives + .iter() + .filter_map(|alts| alts.first()) + .find(|exp| module_global.attach_type(exp.val_type()).eq_loose(exp)); - if !module_global.eq_loose(expected) { + if expected.is_none() { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), span.linecol_in(wast_raw), - Err(eyre!("global value did not match: {:?} != {:?}", module_global, expected)), + Err(eyre!( + "global value did not match any expected alternative: {:?}", + module_global + )), ); continue; } @@ -493,20 +498,23 @@ impl TestSuite { e })?; - if outcomes.len() != expected.len() { + if !expected_alternatives.iter().any(|expected| expected.len() == outcomes.len()) { return Err(eyre!( "span: {:?} expected {} results, got {}", span, - expected.len(), + expected_alternatives.first().map_or(0, |v| v.len()), outcomes.len() )); } - outcomes.iter().zip(expected).enumerate().try_for_each(|(i, (outcome, exp))| { - (outcome.eq_loose(&exp)) - .then_some(()) - .ok_or_else(|| eyre!(" result {} did not match: {:?} != {:?}", i, outcome, exp)) - }) + if expected_alternatives.iter().any(|expected| { + expected.len() == outcomes.len() + && outcomes.iter().zip(expected.iter()).all(|(outcome, exp)| outcome.eq_loose(exp)) + }) { + Ok(()) + } else { + Err(eyre!("results did not match any expected alternative")) + } }); let res = res.map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))).and_then(|r| r); diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 4699d699..851fa202 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -87,8 +87,27 @@ pub fn convert_wastargs(args: Vec) -> Result(args: impl Iterator>) -> Result> { - args.map(|a| wastret2tinywasmvalue(a)).collect() +pub fn convert_wastret<'a>( + args: impl Iterator>, +) -> Result>> { + let mut alternatives = vec![Vec::new()]; + + for arg in args { + let choices = wastret2tinywasmvalues(arg)?; + let mut next = Vec::with_capacity(alternatives.len() * choices.len()); + + for prefix in alternatives { + for choice in &choices { + let mut candidate = prefix.clone(); + candidate.push(*choice); + next.push(candidate); + } + } + + alternatives = next; + } + + Ok(alternatives) } fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { @@ -134,11 +153,20 @@ fn wast_i128_to_i128(i: wast::core::V128Pattern) -> i128 { i128::from_le_bytes(res.try_into().unwrap()) } -fn wastret2tinywasmvalue(ret: wast::WastRet) -> Result { +fn wastret2tinywasmvalues(ret: wast::WastRet) -> Result> { let wast::WastRet::Core(ret) = ret else { bail!("unsupported arg type"); }; + match ret { + wast::core::WastRetCore::Either(options) => { + options.into_iter().map(wastretcore2tinywasmvalue).collect::>>() + } + ret => Ok(vec![wastretcore2tinywasmvalue(ret)?]), + } +} + +fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result { use wast::core::WastRetCore::{F32, F64, I32, I64, RefExtern, RefFunc, RefNull, V128}; Ok(match ret { F32(f) => nanpattern2tinywasmvalue(f)?, diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 83d0da8c..59dc49fe 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -237,18 +237,18 @@ pub enum Instruction { F32x4DemoteF64x2Zero, F64x2PromoteLowF32x4, // > Relaxed SIMD - // I8x16RelaxedSwizzle, - // I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, - // I32x4RelaxedTruncF64x2SZero, I32x4RelaxedTruncF64x2UZero, - // F32x4RelaxedMadd, F32x4RelaxedNmadd, - // F64x2RelaxedMadd, F64x2RelaxedNmadd, - // I8x16RelaxedLaneselect, - // I16x8RelaxedLaneselect, - // I32x4RelaxedLaneselect, - // I64x2RelaxedLaneselect, - // F32x4RelaxedMin, F32x4RelaxedMax, - // F64x2RelaxedMin, F64x2RelaxedMax, - // I16x8RelaxedQ15mulrS, - // I16x8RelaxedDotI8x16I7x16S, - // I32x4RelaxedDotI8x16I7x16AddS + I8x16RelaxedSwizzle, + I32x4RelaxedTruncF32x4S, I32x4RelaxedTruncF32x4U, + I32x4RelaxedTruncF64x2SZero, I32x4RelaxedTruncF64x2UZero, + F32x4RelaxedMadd, F32x4RelaxedNmadd, + F64x2RelaxedMadd, F64x2RelaxedNmadd, + I8x16RelaxedLaneselect, + I16x8RelaxedLaneselect, + I32x4RelaxedLaneselect, + I64x2RelaxedLaneselect, + F32x4RelaxedMin, F32x4RelaxedMax, + F64x2RelaxedMin, F64x2RelaxedMax, + I16x8RelaxedQ15mulrS, + I16x8RelaxedDotI8x16I7x16S, + I32x4RelaxedDotI8x16I7x16AddS } diff --git a/profile.sh b/profile.sh deleted file mode 100755 index 91bb9460..00000000 --- a/profile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -cargo build --example wasm-rust --profile profiling -samply record -r 10000 ./target/profiling/examples/wasm-rust $@ diff --git a/tinywasm.png b/tinywasm.png deleted file mode 100644 index 782b9d444c3320760d7bbda765afdb1e94dd8981..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115910 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 jR#>$CF!p5NIl#BVWNcnMOEQ+=1C5 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 I8+s-FA4nxhhX4Qo From ddc997ab59762958da139a333a2fa5f0ce37eace Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 3 Apr 2026 23:56:03 +0200 Subject: [PATCH 32/47] feat: Wide Arithmetic Signed-off-by: Henry --- README.md | 2 +- crates/parser/src/lib.rs | 4 +-- crates/parser/src/visit.rs | 5 +++ crates/tinywasm/Cargo.toml | 5 +++ crates/tinywasm/src/interpreter/executor.rs | 36 +++++++++++++++++++ .../tests/generated/wasm-wide-arithmetic.csv | 1 + .../tests/test-wasm-wide-arithmetic.rs | 13 +++++++ crates/types/src/instructions.rs | 5 ++- examples/rust/build.sh | 6 ++-- 9 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv create mode 100644 crates/tinywasm/tests/test-wasm-wide-arithmetic.rs diff --git a/README.md b/README.md index cb3a4755..3ea59e4a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Untrusted WebAssembly code should not be able to crash the runtime or access mem | [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🟢 | `next` | | [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` | | [**Relaxed SIMD**](https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md) | 🟢 | `next` | -| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🚧 | - | +| [**Wide Arithmetic**](https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md) | 🟢 | `next` | | [**Custom Descriptors**](https://github.com/WebAssembly/custom-descriptors/blob/main/proposals/custom-descriptors/Overview.md) | 🌑 | - | | [**Exception Handling**](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) | 🌑 | - | | [**Function References**](https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md) | 🌑 | - | diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index a799f69d..f7e713b3 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -66,20 +66,20 @@ impl Parser { custom_page_sizes: true, bulk_memory_opt: true, call_indirect_overlong: true, + wide_arithmetic: true, + relaxed_simd: true, compact_imports: false, cm_map: false, custom_descriptors: false, cm_threading: false, extended_const: false, - wide_arithmetic: false, gc_types: true, stack_switching: false, component_model: false, exceptions: false, gc: false, memory_control: false, - relaxed_simd: true, threads: false, shared_everything_threads: false, legacy_exceptions: false, diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index e3de0095..d6bccdbf 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -363,6 +363,7 @@ macro_rules! impl_visit_operator { (@@saturating_float_to_int $($rest:tt)* ) => {}; (@@bulk_memory $($rest:tt)* ) => {}; (@@simd $($rest:tt)* ) => {}; + (@@wide_arithmetic $($rest:tt)* ) => {}; (@@relaxed_simd $($rest:tt)* ) => {}; (@@tail_call $($rest:tt)* ) => {}; @@ -402,6 +403,10 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild visit_memory_init(MemoryInit, u32, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32) } + define_operands! { + visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU) + } + fn visit_global_set(&mut self, global_index: u32) -> Self::Output { match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index b89c907e..274946ab 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -103,6 +103,11 @@ name="test-wasm-simd" harness=false test=false +[[test]] +name="test-wasm-wide-arithmetic" +harness=false +test=false + [[test]] name="test-wast" harness=false diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 17343ee2..2292a70b 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -57,9 +57,25 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { (binary try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| $expr)? }; (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary_into::<$from, $to>(|$v| Ok($expr))? }; (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary_into::<$from, $to>(|$a, $b| Ok($expr))? }; + (binary_into2 $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => {{ + let $b = self.store.stack.values.pop::<$from>(); + let $a = self.store.stack.values.pop::<$from>(); + let out = $expr; + self.store.stack.values.push::<$to>(out.0)?; + self.store.stack.values.push::<$to>(out.1)?; + }}; (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { stack_op!(binary $a, $b => $b, |$lhs, $rhs| $expr) }; (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { self.store.stack.values.binary_mixed::<$a, $b, $res>(|$lhs, $rhs| Ok($expr))? }; (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => { self.store.stack.values.ternary::<$ty>(|$a, $b, $c| Ok($expr))? }; + (quaternary_into2 $from:ty => $to:ty, |$a:ident, $b:ident, $c:ident, $d:ident| $expr:expr) => {{ + let $d = self.store.stack.values.pop::<$from>(); + let $c = self.store.stack.values.pop::<$from>(); + let $b = self.store.stack.values.pop::<$from>(); + let $a = self.store.stack.values.pop::<$from>(); + let out = $expr; + self.store.stack.values.push::<$to>(out.0)?; + self.store.stack.values.push::<$to>(out.1)?; + }}; (local_set_pop $ty:ty, $local_index:expr) => {{ let val = self.store.stack.values.pop::<$ty>(); self.store.stack.values.local_set(&self.cf, *$local_index, val); @@ -266,6 +282,26 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64Rotl => stack_op!(binary i64, |a, b| a.wasm_rotl(b)), I32Rotr => stack_op!(binary i32, |a, b| a.wasm_rotr(b)), I64Rotr => stack_op!(binary i64, |a, b| a.wasm_rotr(b)), + I64Add128 => stack_op!(quaternary_into2 i64 => i64, |a_lo, a_hi, b_lo, b_hi| { + let lo = a_lo.wrapping_add(b_lo); + let carry = u64::from((lo as u64) < (a_lo as u64)); + let hi = a_hi.wrapping_add(b_hi).wrapping_add(carry as i64); + (lo, hi) + }), + I64Sub128 => stack_op!(quaternary_into2 i64 => i64, |a_lo, a_hi, b_lo, b_hi| { + let lo = a_lo.wrapping_sub(b_lo); + let borrow = u64::from((a_lo as u64) < (b_lo as u64)); + let hi = a_hi.wrapping_sub(b_hi).wrapping_sub(borrow as i64); + (lo, hi) + }), + I64MulWideS => stack_op!(binary_into2 i64 => i64, |a, b| { + let product = (a as i128).wrapping_mul(b as i128); + (product as i64, (product >> 64) as i64) + }), + I64MulWideU => stack_op!(binary_into2 i64 => i64, |a, b| { + let product = (a as u64 as u128).wrapping_mul(b as u64 as u128); + (product as u64 as i64, (product >> 64) as u64 as i64) + }), I32Clz => stack_op!(unary i32, |v| v.leading_zeros() as i32), I64Clz => stack_op!(unary i64, |v| i64::from(v.leading_zeros())), I32Ctz => stack_op!(unary i32, |v| v.trailing_zeros() as i32), diff --git a/crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv b/crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv new file mode 100644 index 00000000..10d76858 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-wide-arithmetic.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,109,0,[{"name":"wide-arithmetic.wast","passed":109,"failed":0}] diff --git a/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs b/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs new file mode 100644 index 00000000..2aa6b435 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-wide-arithmetic.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let mut test_suite = TestSuite::new(); + test_suite.run_files(proposal(&Proposal::WideArithmetic))?; + test_suite.save_csv("./tests/generated/wasm-wide-arithmetic.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 59dc49fe..efb7172c 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -176,7 +176,10 @@ pub enum Instruction { DataDrop(DataAddr), ElemDrop(ElemAddr), - // > SIMD Instructions + // > Wide Arithmetic + I64Add128, I64Sub128, I64MulWideS, I64MulWideU, + + // > SIMD V128Load(MemoryArg), V128Load8x8S(MemoryArg), V128Load8x8U(MemoryArg), V128Load16x4S(MemoryArg), V128Load16x4U(MemoryArg), diff --git a/examples/rust/build.sh b/examples/rust/build.sh index d51840b6..36f95689 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -6,8 +6,8 @@ exclude_wat=("tinywasm") out_dir="./target/wasm32-unknown-unknown/wasm" dest_dir="out" -rust_features="+simd128,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" -wasmopt_features="--enable-simd --enable-tail-call --enable-extended-const --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int --duplicate-function-elimination" +rust_features="+simd128,+relaxed-simd,+reference-types,+bulk-memory,+mutable-globals,+multivalue,+sign-ext,+nontrapping-fptoint" +wasmopt_features="--enable-simd --enable-relaxed-simd --enable-tail-call --enable-extended-const --enable-reference-types --enable-bulk-memory --enable-mutable-globals --enable-multivalue --enable-sign-ext --enable-nontrapping-float-to-int --duplicate-function-elimination" # ensure out dir exists mkdir -p "$dest_dir" @@ -25,4 +25,4 @@ for bin in "${bins[@]}"; do if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" fi -done \ No newline at end of file +done From 9e533d24d469574bc565cb216fb17b99438fe743 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 4 Apr 2026 00:03:16 +0200 Subject: [PATCH 33/47] chore: add back latest spec Signed-off-by: Henry --- crates/parser/src/visit.rs | 5 ++--- crates/tinywasm/Cargo.toml | 4 ++++ .../tinywasm/tests/generated/wasm-latest.csv | 2 +- crates/tinywasm/tests/test-wasm-latest.rs | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 crates/tinywasm/tests/test-wasm-latest.rs diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index d6bccdbf..6e87a041 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -400,10 +400,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild visit_ref_func(RefFunc, u32), visit_table_fill(TableFill, u32), visit_table_get(TableGet, u32), visit_table_set(TableSet, u32), visit_table_grow(TableGrow, u32), visit_table_size(TableSize, u32), // Bulk Memory - visit_memory_init(MemoryInit, u32, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32) - } + visit_memory_init(MemoryInit, u32, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), - define_operands! { + // Wide Arithmetic visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU) } diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 274946ab..f11b3ad8 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -63,6 +63,10 @@ harness=false name="test-wasm-3" harness=false +[[test]] +name="test-wasm-latest" +harness=false + [[test]] name="test-wasm-multi-memory" harness=false diff --git a/crates/tinywasm/tests/generated/wasm-latest.csv b/crates/tinywasm/tests/generated/wasm-latest.csv index 27a4e864..93e892b9 100644 --- a/crates/tinywasm/tests/generated/wasm-latest.csv +++ b/crates/tinywasm/tests/generated/wasm-latest.csv @@ -1 +1 @@ -0.9.0-alpha.0,28010,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":128,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":178,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":104,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.9.0-alpha.0,20698,531,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":165,"failed":0},{"name":"annotations.wast","passed":74,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":127,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":119,"failed":0},{"name":"br_on_non_null.wast","passed":1,"failed":11},{"name":"br_on_null.wast","passed":1,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"call_ref.wast","passed":4,"failed":31},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":59,"failed":6},{"name":"elem.wast","passed":137,"failed":14},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":97,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":53,"failed":71},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"id.wast","passed":7,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":198,"failed":20},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"instance.wast","passed":0,"failed":23},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":142,"failed":21},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":98,"failed":0},{"name":"loop.wast","passed":121,"failed":0},{"name":"memory.wast","passed":89,"failed":1},{"name":"memory_grow.wast","passed":106,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":34},{"name":"return.wast","passed":84,"failed":0},{"name":"return_call.wast","passed":47,"failed":0},{"name":"return_call_indirect.wast","passed":79,"failed":0},{"name":"return_call_ref.wast","passed":11,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table.wast","passed":37,"failed":9},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":61,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type-canon.wast","passed":0,"failed":2},{"name":"type-equivalence.wast","passed":12,"failed":20},{"name":"type-rec.wast","passed":10,"failed":17},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":11},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/tinywasm/tests/test-wasm-latest.rs b/crates/tinywasm/tests/test-wasm-latest.rs new file mode 100644 index 00000000..46964e34 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-latest.rs @@ -0,0 +1,18 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{SpecVersion, spec}; + +fn main() -> Result<()> { + if !std::env::args().any(|x| &x == "--enable") { + println!("Skipping wasm-latest tests, use --enable to run"); + return Ok(()); + } + + TestSuite::set_log_level(log::LevelFilter::Off); + + let mut test_suite = TestSuite::new(); + test_suite.run_files(spec(&SpecVersion::Latest))?; + test_suite.save_csv("./tests/generated/wasm-latest.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} From 9dcfeeddd1aa9deebbf52fc7f84a63713c6e6839 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 4 Apr 2026 15:15:45 +0200 Subject: [PATCH 34/47] docs: update changelog, architecture, contributing Signed-off-by: Henry --- ARCHITECTURE.md | 32 ++++++++++++++------------------ CHANGELOG.md | 27 ++++++++++++++++++++++++--- CONTRIBUTING.md | 39 +++++++++++++++------------------------ Cargo.toml | 2 +- README.md | 2 +- 5 files changed, 55 insertions(+), 47 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 7aaa99c2..28900892 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,27 +1,23 @@ # TinyWasm's Architecture TinyWasm follows the general Runtime Structure described in the [WebAssembly Specification](https://webassembly.github.io/spec/core/exec/runtime.html). -Some key differences are: -- **Type Storage**: Types are inferred from usage context rather than stored explicitly, with all values held as `u64`. -- **Stack Design**: Implements a specific stack for values, labels, and frames to simplify the implementation and enable optimizations. -- **Bytecode Format**: Adopts a custom bytecode format to reduce memory usage and improve performance by allowing direct execution without the need for decoding. -- **Global State Access**: Allows cross-module access to the `Store`'s global state, optimizing imports and exports access. Access requires a module instance reference, maintaining implicit ownership through a reference count. -- **Non-thread-safe Store**: Designed for efficiency in single-threaded applications. -- **JIT Compilation Support**: Prepares for JIT compiler integration with function instances designed to accommodate `WasmFunction`, `HostFunction`, or future `JitFunction`. -- **`no_std` Environment Support**: Offers compatibility with `no_std` environments by allowing disabling of `std` feature -- **Call Frame Execution**: Executes call frames in a single loop rather than recursively, using a single stack for all frames, facilitating easier pause, resume, and step-through. +Key runtime layout: -## Bytecode Format +- Values are stored in four fixed-capacity typed stacks: `stack_32` (`i32`/`f32`), `stack_64` (`i64`/`f64`), `stack_128` (`v128`), and `stack_ref` (`funcref`/`externref`). +- Locals are allocated in those value stacks. Each `CallFrame` stores `locals_base`, and local ops index from that base. +- Calls use a separate fixed-capacity `CallStack` of `CallFrame`s. +- Structured control (`block`/`loop`/`if`/`br*`) is lowered during parsing to jump-oriented instructions: `Jump`, `JumpIfZero`, `BranchTable*`, `DropKeep*`, and `Return`. +- The interpreter executes this lowered bytecode in a single iterative loop. -To improve performance and reduce code size, instructions are encoded as enum variants instead of opcodes. -This allows preprocessing the bytecode into a more memory aligned format, which can be loaded directly into memory and executed without decoding later. This can skip the decoding step entirely on resource-constrained devices where memory is limited. See this [blog post](https://wasmer.io/posts/improving-with-zero-copy-deserialization) by Wasmer -for more details which inspired this design. +## Precompiled Modules -Some instructions are split into multiple variants to reduce the size of the enum (e.g. `br_table` and `br_label`). -Additionally, label instructions contain offsets relative to the current instruction to make branching faster and easier to implement. -Also, `End` instructions are split into `End` and `EndBlock`. Others are also combined, especially in cases where the stack can be skipped. +`TinyWasmModule` can be serialized to `.twasm` (`serialize_twasm`) and loaded later (`from_twasm`). +This allows deployments that execute precompiled modules without enabling the parser in the runtime binary. -See [instructions.rs](./crates/types/src/instructions.rs) for the full list of instructions. +See: -This is a area that can still be improved. While being able to load pre-processes bytecode directly into memory is nice, in-place decoding could achieve similar speeds, see [A fast in-place interpreter for WebAssembly](https://arxiv.org/abs/2205.01183). +- [visit.rs](./crates/parser/src/visit.rs) +- [instructions.rs](./crates/types/src/instructions.rs) +- [value_stack.rs](./crates/tinywasm/src/interpreter/stack/value_stack.rs) +- [call_stack.rs](./crates/tinywasm/src/interpreter/stack/call_stack.rs) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7452dbf0..b9b5fcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,19 +12,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for the custom memory page sizes proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14)) - Support for the `tail_call` proposal - Support for the `memory64` proposal -- Groundwork for the `simd` proposal +- Support for the fixed-width `simd` proposal +- Support for the `relaxed_simd` proposal +- Support for the `wide_arithmetic` proposal +- New `Engine` API (`tinywasm::Engine` and `engine::Config`) for runtime configuration +- Resumable function execution with fuel/time-budget APIs (`call_resumable`, `resume_with_fuel`, `resume_with_time_budget`, `ExecProgress`) +- Host-function fuel APIs: `FuncContext::charge_fuel` and `FuncContext::remaining_fuel` +- `engine::FuelPolicy` and `engine::Config::fuel_policy` for fuel accounting behavior - New `canonicalize_nans` feature flag to enable canonicalizing NaN values in the `f32`, `f64`, and `v128` types +### Changed + +- Locals are now stored in the typed value stacks instead of a separate locals structure +- Structured control flow is fully lowered to jump-oriented internal instructions during parsing +- Stack and call-stack limits are configured via `engine::Config` + ### Breaking Changes - New backwards-incompatible version of the twasm format based on `postcard` (thanks [@dragonnn](https://github.com/dragonnn)) - `RefNull` has been removed and replaced with new `FuncRef` and `ExternRef` structs -- Increased MSRV to 1.83.0 -- `tinywasm::Error` is now `non_exhaustive`, `Error::ParseError` has been rename to `Error::Parser` and `Error::Twasm` has been added. +- `Store::new` now takes an `Engine`; use `Store::default()` for default settings +- `Error`, `Trap`, and `LinkingError` are now `#[non_exhaustive]` +- `Trap` variant discriminant values changed (if you cast variants to integers) +- `tinywasm::interpreter` is no longer a public module; `InterpreterRuntime` and `TinyWasmValue` are no longer public API +- `FuncHandle::name` was removed +- Cargo feature `simd` was removed +- Cargo feature `tinywasm-parser` was renamed to`parser` +- Cargo feature `logging` was renamed to `log` +- Increased MSRV to 1.90 +- `Error::ParseError` was renamed to `Error::Parser`, and `Error::Twasm` was added ### Fixed - Fixed archive **no_std** support which was broken in the previous release, and added more tests to ensure it stays working +- `ModuleInstance::exported_memory` and `FuncContext::exported_memory` are now actually immutable ([#41](https://github.com/explodingcamera/tinywasm/pull/41)) - Check returns in untyped host functions ([#27](https://github.com/explodingcamera/tinywasm/pull/27)) (thanks [@WhaleKit](https://github.com/WhaleKit)) ## [0.8.0] - 2024-08-29 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2302bef8..9c713bf8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,12 @@ # Contributing -Thank you for considering contributing to this project! This document outlines the process for contributing to this project. For small changes or bug fixes, feel free to open a pull request directly. For larger changes, please open an issue first to discuss the proposed changes. Also, please ensure that you open up your pull request against the `next` branch and [allow maintainers of the project to edit your code](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). +Thank you for considering a contribution. For small fixes, feel free to open a pull request directly. For larger changes, please open an issue first so we can discuss the approach. Please target the `next` branch and [allow maintainers to edit your PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). -## 1. Clone the Repository +## Code of Conduct -Ensure you clone this repository with the `--recursive` flag to include the submodules: +This project follows the [Contributor Covenant 3.0 Code of Conduct](https://www.contributor-covenant.org/version/3/0/code_of_conduct/). -```bash -git clone --recursive https://github.com/explodingcamera/tinywasm.git -``` - -If you have already cloned the repository, you can initialize the submodules with: - -```bash -git submodule update --init --recursive -``` - -This is required to run the WebAssembly test suite. - -## 2. Set up the Development Environment +## Development This project mostly uses a pretty standard Rust setup. Some common tasks: @@ -32,14 +20,17 @@ $ cargo test # Run only the WebAssembly MVP (1.0) test suite $ cargo test-wasm-1 -# Run only the full WebAssembly test suite (2.0) +# Run only the full WebAssembly 2.0 test suite $ cargo test-wasm-2 -# Run a specific test (run without arguments to see available tests) -$ cargo test --test {test_name} +# Run only the full WebAssembly 3.0 test suite +$ cargo test-wasm-3 # Run a single WAST test file -$ cargo test-wast {path} +$ cargo test-wast ./wasm-testsuite/data/wasm-v1/{file}.wast + +# Run custom wasm tests from crates/tinywasm/tests/wasm-custom +$ cargo test-wasm-custom # Run a specific example (run without arguments to see available examples) # The wasm test files required to run the `wasm-rust` examples are not @@ -51,20 +42,20 @@ $ cargo run --example {example_name} ### Profiling -Either [samply](https://github.com/mstange/samply/) or [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph) are recommended for profiling. +Use [samply](https://github.com/mstange/samply/) for profiling. Example usage: ```bash cargo install --locked samply -cargo samply --example wasm-rust -- selfhosted +samply record -- cargo run --release --example wasm-rust -- selfhosted ``` -# Commits +## Commits This project uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages. For pull requests, the commit messages will be squashed so you don't need to worry about this too much. However, it is still recommended to follow this convention for consistency. -# Branches +## Branches - `main`: The main branch. This branch is used for the latest stable release. - `next`: The next branch. Development happens here. diff --git a/Cargo.toml b/Cargo.toml index 7cc731e5..f7d6ccb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ serde={version="1.0", features=["derive"]} [workspace.package] version="0.9.0-alpha.0" -rust-version="1.93" +rust-version="1.90" edition="2024" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] diff --git a/README.md b/README.md index 3ea59e4a..06d11a63 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Current Status -`tinywasm` passes all WebAssembly MVP and WebAssembly 2.0 tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, support for WebAssembly 3.0 is mostly done. See the [Supported Proposals](#supported-proposals) section for more information. +`tinywasm` passes 100% of WebAssembly MVP and WebAssembly 2.0 tests from the [WebAssembly core testsuite](https://github.com/WebAssembly/testsuite) and is able to run most WebAssembly programs. Additionally, support for WebAssembly 3.0 is on the way. See the [Supported Proposals](#supported-proposals) section for more information. ## Usage From e2001ebe6ce6f1c5f3843feac772adf999dae873 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 4 Apr 2026 20:42:15 +0200 Subject: [PATCH 35/47] chore: add ParserOptions Signed-off-by: Henry --- .gitmodules | 3 --- crates/parser/src/lib.rs | 42 +++++++++++++++++++++++++++----- crates/types/src/instructions.rs | 10 ++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 0fa174a9..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "crates/wasm-testsuite/data"] - path = crates/wasm-testsuite/data - url = https://github.com/WebAssembly/testsuite.git diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index f7e713b3..eabeeab5 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -39,17 +39,47 @@ use wasmparser::{Validator, WasmFeaturesInflated}; pub use tinywasm_types::TinyWasmModule; +/// Parser optimization and lowering options. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct ParserOptions { + // /// Enable control-flow graph cleanup rewrites. + // pub cfg_cleanup: bool, + // /// Enable dead-code pruning. + // pub dce: bool, + // /// Enable return-call rewrites when safe. + // pub tailcall_rewrite: bool, +} + +impl Default for ParserOptions { + fn default() -> Self { + Self {} + } +} + /// A WebAssembly parser -#[derive(Default, Debug)] -pub struct Parser {} +#[derive(Debug, Default)] +pub struct Parser { + options: ParserOptions, +} impl Parser { /// Create a new parser instance pub fn new() -> Self { - Self {} + Self::default() + } + + /// Create a new parser with explicit options. + pub fn with_options(options: ParserOptions) -> Self { + Self { options } + } + + /// Read back parser options. + pub const fn options(&self) -> &ParserOptions { + &self.options } - fn create_validator() -> Validator { + fn create_validator(_options: ParserOptions) -> Validator { let features = WasmFeaturesInflated { bulk_memory: true, floats: true, @@ -98,7 +128,7 @@ impl Parser { /// Parse a [`TinyWasmModule`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); - let mut validator = Self::create_validator(); + let mut validator = Self::create_validator(self.options.clone()); let mut reader = ModuleReader::new(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { @@ -128,7 +158,7 @@ impl Parser { pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { use alloc::format; - let mut validator = Self::create_validator(); + let mut validator = Self::create_validator(self.options.clone()); let mut reader = ModuleReader::new(); let mut buffer = alloc::vec::Vec::new(); let mut parser = wasmparser::Parser::new(0); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index efb7172c..d0137342 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -255,3 +255,13 @@ pub enum Instruction { I16x8RelaxedDotI8x16I7x16S, I32x4RelaxedDotI8x16I7x16AddS } + +#[cfg(test)] +mod tests { + use super::Instruction; + + #[test] + fn instruction_layout_size_is_stable() { + assert_eq!(core::mem::size_of::(), 16); + } +} From 1cf50a22104ed075a08781321486e1cbf19fb7c6 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 4 Apr 2026 21:08:27 +0200 Subject: [PATCH 36/47] fix: intruction enum size regression Signed-off-by: Henry --- Cargo.lock | 24 ++++++++++----------- Cargo.toml | 2 +- crates/parser/src/lib.rs | 8 +------ crates/parser/src/visit.rs | 9 ++++++++ crates/tinywasm/src/func.rs | 2 +- crates/tinywasm/src/interpreter/executor.rs | 12 +++++++---- crates/tinywasm/src/interpreter/mod.rs | 2 +- crates/types/src/instructions.rs | 4 ++-- 8 files changed, 35 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31bf8221..35954667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "shlex", @@ -539,9 +539,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.246.1" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1929aad146499e47362c876fcbcbb0363f730951d93438f511178626e999a8" +checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" dependencies = [ "leb128fmt", "wasmparser", @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.246.1" +version = "0.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d991c35d79bf8336dc1cd632ed4aacf0dc5fac4bc466c670625b037b972bb9c" +checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" dependencies = [ "bitflags", "indexmap", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "wast" -version = "246.0.1" +version = "246.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf2d50bc7478dcca61d00df4dadf922ef46c5924db20a97e6daaf09fe1cb09" +checksum = "fe3fe8e3bf88ad96d031b4181ddbd64634b17cb0d06dfc3de589ef43591a9a62" dependencies = [ "bumpalo", "leb128fmt", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.246.1" +version = "1.246.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723f2473b47f738c12fc11c8e0bb8b27ce7cf9c78cf1a29dadbc2d34a2513292" +checksum = "4bd7fda1199b94fff395c2d19a153f05dbe7807630316fa9673367666fd2ad8c" dependencies = [ "wast", ] diff --git a/Cargo.toml b/Cargo.toml index f7d6ccb7..22e206b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ version="0.9.0-alpha.0" rust-version="1.90" edition="2024" license="MIT OR Apache-2.0" -authors=["Henry Gressmann "] +authors=["Henry Gressmann "] repository="https://github.com/explodingcamera/tinywasm" categories=["wasm", "no-std"] keywords=["tinywasm"] diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index eabeeab5..364209c8 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -41,7 +41,7 @@ pub use tinywasm_types::TinyWasmModule; /// Parser optimization and lowering options. #[non_exhaustive] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct ParserOptions { // /// Enable control-flow graph cleanup rewrites. // pub cfg_cleanup: bool, @@ -51,12 +51,6 @@ pub struct ParserOptions { // pub tailcall_rewrite: bool, } -impl Default for ParserOptions { - fn default() -> Self { - Self {} - } -} - /// A WebAssembly parser #[derive(Debug, Default)] pub struct Parser { diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 6e87a041..7da2d99b 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -431,6 +431,10 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild let addr = self.instructions[len - 2]; let value = self.instructions[len - 1]; if let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = (addr, value) { + let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) else { + self.instructions.push(Instruction::I32Store(memarg)); + return; + }; self.instructions.pop(); self.instructions.pop(); self.instructions.push(Instruction::I32StoreLocalLocal(memarg, addr_local, value_local)); @@ -626,6 +630,11 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild if let (Instruction::LocalGet32(addr_local), Instruction::I32Load(memarg)) = (addr, load) { match self.validator.get_operand_type(0) { Some(Some(wasmparser::ValType::I32)) | Some(Some(wasmparser::ValType::F32)) => { + let (Ok(addr_local), Ok(resolved_idx)) = (u8::try_from(addr_local), u8::try_from(resolved_idx)) + else { + self.instructions.push(Instruction::LocalTee32(resolved_idx)); + return; + }; self.instructions.pop(); self.instructions.pop(); self.instructions.push(Instruction::I32LoadLocalTee(memarg, addr_local, resolved_idx)); diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index fd1279fb..75a094a1 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -13,7 +13,7 @@ pub enum ExecProgress { Suspended, } -#[derive(Clone, Copy)] +#[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ExecutionState { pub(crate) callframe: CallFrame, diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 2292a70b..bd2e31ee 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -174,13 +174,17 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), I32StoreLocalLocal(m, addr_local, value_local) => { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); - let value = self.store.stack.values.local_get::(&self.cf, *value_local).to_mem_bytes(); + let addr_local = u16::from(*addr_local); + let value_local = u16::from(*value_local); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, addr_local)); + let value = self.store.stack.values.local_get::(&self.cf, value_local).to_mem_bytes(); mem.store((m.offset() + addr) as usize, value.len(), &value)?; } I32LoadLocalTee(m, addr_local, dst_local) => { let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, *addr_local)); + let addr_local = u16::from(*addr_local); + let dst_local = u16::from(*dst_local); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, addr_local)); let Some(Ok(addr)) = m.offset().checked_add(addr).map(|a| a.try_into()) else { return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: addr as usize, @@ -189,7 +193,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { })); }; let value = mem.load_as::<4, i32>(addr)?; - self.store.stack.values.local_set(&self.cf, *dst_local, value); + self.store.stack.values.local_set(&self.cf, dst_local, value); self.store.stack.values.push(value)?; } I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 1af7d2f9..17481755 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -11,7 +11,7 @@ use crate::{Result, Store, interpreter::stack::CallFrame}; pub(crate) use value128::*; pub(crate) use values::*; -#[derive(Clone, Copy)] +#[derive(Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) enum ExecState { Completed, diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index d0137342..5e88cfa2 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -56,8 +56,8 @@ pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), I32AddLocals(LocalAddr, LocalAddr), I64AddLocals(LocalAddr, LocalAddr), I32AddConst(i32), I64AddConst(i64), - I32StoreLocalLocal(MemoryArg, LocalAddr, LocalAddr), - I32LoadLocalTee(MemoryArg, LocalAddr, LocalAddr), + I32StoreLocalLocal(MemoryArg, u8, u8), + I32LoadLocalTee(MemoryArg, u8, u8), I64XorRotlConst(i64), I64XorRotlConstTee(i64, LocalAddr), // > Control Instructions (jump-oriented, lowered from structured control during parsing) From e8f3d33c7f2b08806bfe14699a936e518fd3630b Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 13:04:40 +0200 Subject: [PATCH 37/47] chore: improve fused instructions Signed-off-by: Henry --- crates/parser/src/visit.rs | 136 ++++++++++++++++++-- crates/tinywasm/src/interpreter/executor.rs | 2 +- crates/types/src/instructions.rs | 8 +- crates/types/src/lib.rs | 2 +- 4 files changed, 135 insertions(+), 13 deletions(-) diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 7da2d99b..f263dccd 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -388,7 +388,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild define_operands! { // basic instructions - visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -446,6 +446,30 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_drop(&mut self) -> Self::Output { + match self.instructions.last().copied() { + Some(Instruction::LocalTee32(local)) => { + self.instructions.pop(); + self.instructions.push(Instruction::LocalSet32(local)); + return; + } + Some(Instruction::LocalTee64(local)) => { + self.instructions.pop(); + self.instructions.push(Instruction::LocalSet64(local)); + return; + } + Some(Instruction::LocalTee128(local)) => { + self.instructions.pop(); + self.instructions.push(Instruction::LocalSet128(local)); + return; + } + Some(Instruction::LocalTeeRef(local)) => { + self.instructions.pop(); + self.instructions.push(Instruction::LocalSetRef(local)); + return; + } + _ => {} + } + match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::Drop32, @@ -491,6 +515,18 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild return; } + if len >= 2 { + let lhs = self.instructions[len - 2]; + let rhs = self.instructions[len - 1]; + if let (Instruction::I32Const(c), Instruction::LocalGet32(local)) = (lhs, rhs) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::LocalGet32(local)); + self.instructions.push(Instruction::I32AddConst(c)); + return; + } + } + self.instructions.push(Instruction::I32Add); } @@ -513,6 +549,18 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild return; } + if len >= 2 { + let lhs = self.instructions[len - 2]; + let rhs = self.instructions[len - 1]; + if let (Instruction::I64Const(c), Instruction::LocalGet64(local)) = (lhs, rhs) { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.push(Instruction::LocalGet64(local)); + self.instructions.push(Instruction::I64AddConst(c)); + return; + } + } + self.instructions.push(Instruction::I64Add); } @@ -541,14 +589,44 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild }; match self.validator.get_local_type(idx) { - Some(t) => self.instructions.push(match t { - wasmparser::ValType::I32 => Instruction::LocalGet32(resolved_idx), - wasmparser::ValType::F32 => Instruction::LocalGet32(resolved_idx), - wasmparser::ValType::I64 => Instruction::LocalGet64(resolved_idx), - wasmparser::ValType::F64 => Instruction::LocalGet64(resolved_idx), - wasmparser::ValType::V128 => Instruction::LocalGet128(resolved_idx), - wasmparser::ValType::Ref(_) => Instruction::LocalGetRef(resolved_idx), - }), + Some(t) => match t { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => { + if matches!(self.instructions.last(), Some(Instruction::LocalSet32(local)) if *local == resolved_idx) + { + self.instructions.pop(); + self.instructions.push(Instruction::LocalTee32(resolved_idx)); + return; + } + self.instructions.push(Instruction::LocalGet32(resolved_idx)); + } + wasmparser::ValType::I64 | wasmparser::ValType::F64 => { + if matches!(self.instructions.last(), Some(Instruction::LocalSet64(local)) if *local == resolved_idx) + { + self.instructions.pop(); + self.instructions.push(Instruction::LocalTee64(resolved_idx)); + return; + } + self.instructions.push(Instruction::LocalGet64(resolved_idx)); + } + wasmparser::ValType::V128 => { + if matches!(self.instructions.last(), Some(Instruction::LocalSet128(local)) if *local == resolved_idx) + { + self.instructions.pop(); + self.instructions.push(Instruction::LocalTee128(resolved_idx)); + return; + } + self.instructions.push(Instruction::LocalGet128(resolved_idx)); + } + wasmparser::ValType::Ref(_) => { + if matches!(self.instructions.last(), Some(Instruction::LocalSetRef(local)) if *local == resolved_idx) + { + self.instructions.pop(); + self.instructions.push(Instruction::LocalTeeRef(resolved_idx)); + return; + } + self.instructions.push(Instruction::LocalGetRef(resolved_idx)); + } + }, _ => { self.visit_unreachable(); } @@ -572,6 +650,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild { let from = *from; self.instructions.pop(); + + if from == resolved_idx { + // local.set x (local.get x) is a no-op; drop it. + return; + } + // validation will ensure that the last instruction is the correct local.get match self.validator.get_operand_type(0) { Some(Some(t)) => self.instructions.push(match t { @@ -612,6 +696,35 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild return; }; + if let Some(Some(t)) = self.validator.get_operand_type(0) { + match t { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => { + if matches!(self.instructions.last(), Some(Instruction::LocalGet32(local)) if *local == resolved_idx) + { + return; + } + } + wasmparser::ValType::I64 | wasmparser::ValType::F64 => { + if matches!(self.instructions.last(), Some(Instruction::LocalGet64(local)) if *local == resolved_idx) + { + return; + } + } + wasmparser::ValType::V128 => { + if matches!(self.instructions.last(), Some(Instruction::LocalGet128(local)) if *local == resolved_idx) + { + return; + } + } + wasmparser::ValType::Ref(_) => { + if matches!(self.instructions.last(), Some(Instruction::LocalGetRef(local)) if *local == resolved_idx) + { + return; + } + } + } + } + if let Some(Instruction::I64XorRotlConst(c)) = self.instructions.last().copied() { match self.validator.get_operand_type(0) { Some(Some(wasmparser::ValType::I64)) | Some(Some(wasmparser::ValType::F64)) => { @@ -836,6 +949,11 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild wasmparser::ValType::Ref(_) => Instruction::SelectRef, }); } + + fn visit_f32_reinterpret_i32(&mut self) -> Self::Output {} + fn visit_f64_reinterpret_i64(&mut self) -> Self::Output {} + fn visit_i32_reinterpret_f32(&mut self) -> Self::Output {} + fn visit_i64_reinterpret_f64(&mut self) -> Self::Output {} } macro_rules! impl_visit_simd_operator { diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index bd2e31ee..54ab12af 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -100,7 +100,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { #[rustfmt::skip] match next { - Nop | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 | F64ReinterpretI64 => {} + Nop => {} Unreachable => return Err(Trap::Unreachable.into()), Drop32 => self.store.stack.values.drop::(), Drop64 => self.store.stack.values.drop::(), diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 5e88cfa2..bccbdd13 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -139,14 +139,17 @@ pub enum Instruction { // See I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU, I64Eqz, I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU, + // Comparisons F32Eq, F32Ne, F32Lt, F32Gt, F32Le, F32Ge, F64Eq, F64Ne, F64Lt, F64Gt, F64Le, F64Ge, I32Clz, I32Ctz, I32Popcnt, I32Add, I32Sub, I32Mul, I32DivS, I32DivU, I32RemS, I32RemU, I64Clz, I64Ctz, I64Popcnt, I64Add, I64Sub, I64Mul, I64DivS, I64DivU, I64RemS, I64RemU, + // Bitwise I32And, I32Or, I32Xor, I32Shl, I32ShrS, I32ShrU, I32Rotl, I32Rotr, I64And, I64Or, I64Xor, I64Shl, I64ShrS, I64ShrU, I64Rotl, I64Rotr, + // Floating Point F32Abs, F32Neg, F32Ceil, F32Floor, F32Trunc, F32Nearest, F32Sqrt, F32Add, F32Sub, F32Mul, F32Div, F32Min, F32Max, F32Copysign, F64Abs, F64Neg, F64Ceil, F64Floor, F64Trunc, F64Nearest, F64Sqrt, F64Add, F64Sub, F64Mul, F64Div, F64Min, F64Max, F64Copysign, @@ -154,8 +157,9 @@ pub enum Instruction { I64Extend8S, I64Extend16S, I64Extend32S, I64ExtendI32S, I64ExtendI32U, I64TruncF32S, I64TruncF32U, I64TruncF64S, I64TruncF64U, F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64, F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32, - // Reinterpretations (noops at runtime) - I32ReinterpretF32, I64ReinterpretF64, F32ReinterpretI32, F64ReinterpretI64, + + // Reinterpretations are parser no-ops and intentionally omitted. + // Saturating Float-to-Int Conversions I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U, I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 2a167a4a..2f6107a9 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -50,7 +50,7 @@ pub mod archive; #[cfg(not(feature = "archive"))] pub mod archive { - #[cfg_attr(feature = "debug", derive(Debug))] + #[derive(Debug)] pub enum TwasmError {} impl core::fmt::Display for TwasmError { fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { From 786152d4ac0751cb130e7a138c5d47394927965a Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 18:39:48 +0200 Subject: [PATCH 38/47] chore: refactor optimizer/add more superinstructions Signed-off-by: Henry --- crates/parser/src/lib.rs | 25 +- crates/parser/src/module.rs | 20 +- crates/parser/src/optimize.rs | 362 +++++++++ crates/parser/src/visit.rs | 760 ++++++------------ crates/tinywasm/src/interpreter/executor.rs | 96 ++- .../src/interpreter/stack/value_stack.rs | 18 + crates/tinywasm/src/interpreter/values.rs | 9 + crates/types/src/instructions.rs | 7 + examples/dump-bytecode.rs | 53 ++ 9 files changed, 801 insertions(+), 549 deletions(-) create mode 100644 crates/parser/src/optimize.rs create mode 100644 examples/dump-bytecode.rs diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 364209c8..2d468572 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -32,6 +32,7 @@ pub(crate) mod log { mod conversion; mod error; mod module; +mod optimize; mod visit; pub use error::*; use module::ModuleReader; @@ -41,14 +42,18 @@ pub use tinywasm_types::TinyWasmModule; /// Parser optimization and lowering options. #[non_exhaustive] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ParserOptions { - // /// Enable control-flow graph cleanup rewrites. - // pub cfg_cleanup: bool, - // /// Enable dead-code pruning. - // pub dce: bool, - // /// Enable return-call rewrites when safe. - // pub tailcall_rewrite: bool, + /// Enable post-lowering DCE pass. + /// Should be enabled by default, since the parser performs some optimizations that can result in dead code. + /// Disabling this may result in larger modules, but faster parsing time. + pub dce: bool, +} + +impl Default for ParserOptions { + fn default() -> Self { + Self { dce: true } + } } /// A WebAssembly parser @@ -133,7 +138,7 @@ impl Parser { return Err(ParseError::EndNotReached); } - reader.into_module() + reader.into_module(&self.options) } #[cfg(feature = "std")] @@ -173,7 +178,7 @@ impl Parser { reader.process_payload(payload, &mut validator)?; buffer.drain(..consumed); if eof || reader.end_reached { - return reader.into_module(); + return reader.into_module(&self.options); } } }; @@ -185,6 +190,6 @@ impl TryFrom for TinyWasmModule { type Error = ParseError; fn try_from(reader: ModuleReader) -> Result { - reader.into_module() + reader.into_module(&ParserOptions::default()) } } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 27fcb1b0..fdc02ab1 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,7 +1,6 @@ use crate::log::debug; -use crate::{ParseError, Result, conversion}; +use crate::{ParseError, ParserOptions, Result, conversion, optimize}; use alloc::string::ToString; -use alloc::sync::Arc; use alloc::{format, vec::Vec}; use tinywasm_types::{ ArcSlice, Data, Element, Export, FuncType, Global, Import, ImportKind, Instruction, MemoryType, TableType, @@ -9,7 +8,7 @@ use tinywasm_types::{ }; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Arc<[Instruction]>, WasmFunctionData, ValueCounts); +pub(crate) type Code = (Vec, WasmFunctionData, ValueCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -31,16 +30,6 @@ pub(crate) struct ModuleReader { } impl ModuleReader { - fn apply_instruction_rewrites(instructions: &mut [Instruction], self_func_addr: u32) { - for instr in instructions.iter_mut() { - if matches!(instr, Instruction::Call(addr) if *addr == self_func_addr) { - *instr = Instruction::CallSelf; - } else if matches!(instr, Instruction::ReturnCall(addr) if *addr == self_func_addr) { - *instr = Instruction::ReturnCallSelf; - } - } - } - pub(crate) fn new() -> Self { Self::default() } @@ -190,7 +179,7 @@ impl ModuleReader { Ok(()) } - pub(crate) fn into_module(self) -> Result { + pub(crate) fn into_module(self, options: &ParserOptions) -> Result { if !self.end_reached { return Err(ParseError::EndNotReached); } @@ -217,8 +206,7 @@ impl ModuleReader { cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), }; let self_func_addr = imported_func_count + func_idx as u32; - let mut instructions = instructions.to_vec(); - Self::apply_instruction_rewrites(&mut instructions, self_func_addr); + let instructions = optimize::optimize_instructions(instructions, self_func_addr, options); WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } }) diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs new file mode 100644 index 00000000..887ea042 --- /dev/null +++ b/crates/parser/src/optimize.rs @@ -0,0 +1,362 @@ +use crate::ParserOptions; +use alloc::vec::Vec; +use tinywasm_types::Instruction; + +pub(crate) fn optimize_instructions( + mut instructions: Vec, + self_func_addr: u32, + options: &ParserOptions, +) -> Vec { + rewrite(&mut instructions, self_func_addr); + if options.dce { + dce(&mut instructions); + } + instructions +} + +fn rewrite(instructions: &mut [Instruction], self_func_addr: u32) { + for read in 0..instructions.len() { + match instructions[read] { + Instruction::LocalCopy32(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopy64(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopy128(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopyRef(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::Call(addr) if addr == self_func_addr => instructions[read] = Instruction::CallSelf, + Instruction::ReturnCall(addr) if addr == self_func_addr => instructions[read] = Instruction::ReturnCallSelf, + Instruction::I32Add => { + if read > 1 + && let (Instruction::LocalGet32(a), Instruction::LocalGet32(b)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddLocals(a, b); + } + + if read > 0 { + match instructions[read - 1] { + Instruction::I32Const(c) if read > 1 => { + if let Instruction::LocalGet32(local) = instructions[read - 2] { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::LocalGet32(local); + instructions[read] = Instruction::I32AddConst(c); + } else { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddConst(c); + } + } + Instruction::I32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddConst(c); + } + _ => {} + } + } + } + Instruction::I64Add => { + if read > 1 + && let (Instruction::LocalGet64(a), Instruction::LocalGet64(b)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddLocals(a, b); + } + + if read > 0 { + match instructions[read - 1] { + Instruction::I64Const(c) if read > 1 => { + if let Instruction::LocalGet64(local) = instructions[read - 2] { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::LocalGet64(local); + instructions[read] = Instruction::I64AddConst(c); + } else { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddConst(c); + } + } + Instruction::I64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddConst(c); + } + _ => {} + } + } + } + Instruction::I64Rotl => { + if read > 1 + && let (Instruction::I64Xor, Instruction::I64Const(c)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64XorRotlConst(c); + } + } + Instruction::I32Store(memarg) => { + if read > 1 + && let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32StoreLocalLocal(memarg, addr_local, value_local); + } + } + Instruction::I64Store(memarg) => { + if read > 1 + && let (Instruction::LocalGet32(addr_local), Instruction::LocalGet64(value_local)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64StoreLocalLocal(memarg, addr_local, value_local); + } + } + Instruction::MemoryFill(mem) => { + if read > 1 + && let (Instruction::I32Const(val), Instruction::I32Const(size)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::MemoryFillImm(mem, val as u8, size); + } + } + + Instruction::LocalGet32(dst) => { + if read > 0 + && let Instruction::LocalSet32(src) = instructions[read - 1] + && src == dst + { + instructions[read - 1] = Instruction::LocalTee32(src); + instructions[read] = Instruction::Nop; + } + } + Instruction::LocalGet64(dst) + if read > 0 + && let Instruction::LocalSet64(src) = instructions[read - 1] + && src == dst => + { + instructions[read - 1] = Instruction::LocalTee64(src); + instructions[read] = Instruction::Nop; + } + Instruction::LocalGet128(dst) + if read > 0 + && let Instruction::LocalSet128(src) = instructions[read - 1] + && src == dst => + { + instructions[read - 1] = Instruction::LocalTee128(src); + instructions[read] = Instruction::Nop; + } + Instruction::LocalGetRef(dst) + if read > 0 + && let Instruction::LocalSetRef(src) = instructions[read - 1] + && src == dst => + { + instructions[read - 1] = Instruction::LocalTeeRef(src); + instructions[read] = Instruction::Nop; + } + + Instruction::LocalSet32(dst) => { + if read > 0 { + match instructions[read - 1] { + Instruction::LocalGet32(src) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy32(src, dst) }; + } + Instruction::I32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalSetConst32(dst, c); + } + Instruction::F32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + Instruction::LocalSetConst32(dst, i32::from_ne_bytes(c.to_bits().to_ne_bytes())); + } + _ => {} + } + } + + if read > 1 { + match (instructions[read - 2], instructions[read - 1]) { + (Instruction::LocalGet32(src), Instruction::I32AddConst(c)) if src == dst => { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalAddConst32(dst, c); + } + (Instruction::LocalGet32(addr), Instruction::I32Load(memarg)) => { + if let (Ok(addr), Ok(dst)) = (u8::try_from(addr), u8::try_from(dst)) { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32LoadLocalSet(memarg, addr, dst); + } + } + _ => {} + } + } + } + Instruction::LocalSet64(dst) => { + if read > 0 { + match instructions[read - 1] { + Instruction::LocalGet64(src) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy64(src, dst) }; + } + Instruction::I64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalSetConst64(dst, c); + } + Instruction::F64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + Instruction::LocalSetConst64(dst, i64::from_ne_bytes(c.to_bits().to_ne_bytes())); + } + _ => {} + } + } + + if read > 1 + && let (Instruction::LocalGet64(src), Instruction::I64AddConst(c)) = + (instructions[read - 2], instructions[read - 1]) + && src == dst + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalAddConst64(dst, c); + } + } + Instruction::LocalSet128(dst) + if read > 0 + && let Instruction::LocalGet128(src) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = if src == dst { Instruction::Nop } else { Instruction::LocalCopy128(src, dst) }; + } + Instruction::LocalSetRef(dst) + if read > 0 + && let Instruction::LocalGetRef(src) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = if src == dst { Instruction::Nop } else { Instruction::LocalCopyRef(src, dst) }; + } + + Instruction::LocalTee32(dst) => { + if read > 0 + && let Instruction::LocalGet32(src) = instructions[read - 1] + && src == dst + { + instructions[read] = Instruction::Nop; + } + + if read > 1 + && let (Instruction::LocalGet32(addr), Instruction::I32Load(memarg)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr), Ok(dst)) = (u8::try_from(addr), u8::try_from(dst)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32LoadLocalTee(memarg, addr, dst); + } + } + Instruction::LocalTee64(dst) if read > 0 => match instructions[read - 1] { + Instruction::LocalGet64(src) if src == dst => { + instructions[read] = Instruction::Nop; + } + Instruction::I64XorRotlConst(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64XorRotlConstTee(c, dst); + } + _ => {} + }, + Instruction::LocalTee128(dst) + if read > 0 + && let Instruction::LocalGet128(src) = instructions[read - 1] + && src == dst => + { + instructions[read] = Instruction::Nop; + } + Instruction::LocalTeeRef(dst) + if read > 0 + && let Instruction::LocalGetRef(src) = instructions[read - 1] + && src == dst => + { + instructions[read] = Instruction::Nop; + } + + Instruction::Drop32 + if read > 0 + && let Instruction::LocalTee32(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSet32(local); + instructions[read] = Instruction::Nop; + } + Instruction::Drop64 + if read > 0 + && let Instruction::LocalTee64(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSet64(local); + instructions[read] = Instruction::Nop; + } + Instruction::Drop128 + if read > 0 + && let Instruction::LocalTee128(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSet128(local); + instructions[read] = Instruction::Nop; + } + Instruction::DropRef + if read > 0 + && let Instruction::LocalTeeRef(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSetRef(local); + instructions[read] = Instruction::Nop; + } + _ => {} + } + } +} + +fn dce(instructions: &mut Vec) { + let old_len = instructions.len(); + if old_len == 0 { + return; + } + + let mut removed_before = Vec::with_capacity(old_len + 1); + removed_before.push(0u32); + instructions.iter().for_each(|instr| { + let removed = removed_before.last().copied().unwrap_or(0) + u32::from(matches!(instr, Instruction::Nop)); + removed_before.push(removed); + }); + + let removed_total = removed_before[old_len]; + if removed_total == 0 { + return; + } + + let compacted_len = old_len as u32 - removed_total; + instructions.retain_mut(|instr| { + let ip = match instr { + Instruction::Jump(ip) + | Instruction::JumpIfZero(ip) + | Instruction::JumpIfNonZero(ip) + | Instruction::BranchTableTarget(ip) + | Instruction::BranchTable(ip, _) => ip, + _ => return !matches!(instr, Instruction::Nop), + }; + + let old_target = *ip as usize; + if old_target > old_len { + return !matches!(instr, Instruction::Nop); + } + + *ip -= removed_before[old_target]; + debug_assert!(*ip < compacted_len, "remapped jump target points past end of function"); + !matches!(instr, Instruction::Nop) + }); +} diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index f263dccd..09e3fae7 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -61,7 +61,7 @@ pub(crate) fn process_operators_and_validate( validator: FuncValidator, body: FunctionBody<'_>, local_addr_map: Vec, -) -> Result<(alloc::sync::Arc<[Instruction]>, WasmFunctionData, FuncValidatorAllocations)> { +) -> Result<(Vec, WasmFunctionData, FuncValidatorAllocations)> { let mut reader = body.get_operators_reader()?; let remaining = reader.get_binary_reader().bytes_remaining(); let mut builder = FunctionBuilder::new(remaining, validator, local_addr_map); @@ -76,7 +76,7 @@ pub(crate) fn process_operators_and_validate( } Ok(( - alloc::sync::Arc::from(builder.instructions), + builder.instructions, WasmFunctionData { v128_constants: builder.v128_constants.into_boxed_slice() }, builder.validator.into_allocations(), )) @@ -141,217 +141,6 @@ pub(crate) struct FunctionBuilder { errors: Vec, } -impl FunctionBuilder { - pub(crate) fn validator_visitor( - &mut self, - offset: usize, - ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { - self.validator.simd_visitor(offset) - } - - pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { - Self { - validator, - local_addr_map, - instructions: Vec::with_capacity(instr_capacity), - v128_constants: Vec::new(), - ctx_stack: Vec::with_capacity(256), - errors: Vec::new(), - } - } - - fn stack_base_at_frame(&self, depth: usize) -> StackBase { - let Some(frame) = self.validator.get_control_frame(depth) else { return StackBase::default() }; - let mut base = StackBase::default(); - for i in 0..frame.height { - let depth_from_top = self.validator.operand_stack_height() as usize - 1 - i; - if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { - match ty { - wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, - wasmparser::ValType::I64 | wasmparser::ValType::F64 => base.s64 += 1, - wasmparser::ValType::V128 => base.s128 += 1, - wasmparser::ValType::Ref(_) => base.sref += 1, - } - } - } - - base - } - - fn unsupported(&mut self, name: &str) { - self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); - } - - fn is_unreachable(&self) -> bool { - self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) - } - - fn get_ctx_idx(&self, depth: u32) -> Option { - let len = self.ctx_stack.len(); - let idx = len.checked_sub(depth as usize + 1)?; - Some(idx) - } - - fn emit_dropkeep(&mut self, base: StackBase, c32: u16, c64: u16, c128: u16, cref: u16) { - let fits_u8 = base.s32 <= u8::MAX as u16 - && c32 <= u8::MAX as u16 - && base.s64 <= u8::MAX as u16 - && c64 <= u8::MAX as u16 - && base.s128 <= u8::MAX as u16 - && c128 <= u8::MAX as u16 - && base.sref <= u8::MAX as u16 - && cref <= u8::MAX as u16; - - if fits_u8 { - self.instructions.push(Instruction::DropKeepSmall { - base32: base.s32 as u8, - keep32: c32 as u8, - base64: base.s64 as u8, - keep64: c64 as u8, - base128: base.s128 as u8, - keep128: c128 as u8, - base_ref: base.sref as u8, - keep_ref: cref as u8, - }); - } else { - self.instructions.push(Instruction::DropKeep32(base.s32, c32)); - self.instructions.push(Instruction::DropKeep64(base.s64, c64)); - self.instructions.push(Instruction::DropKeep128(base.s128, c128)); - self.instructions.push(Instruction::DropKeepRef(base.sref, cref)); - } - } - - fn patch_jump(&mut self, jump_ip: usize, target: usize) { - if let Instruction::Jump(ip) = &mut self.instructions[jump_ip] { - *ip = target as u32; - } - } - - fn patch_jump_if_zero(&mut self, jump_ip: usize, target: usize) { - if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { - *ip = target as u32; - } - } - - fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { - let (mut c32, mut c64, mut c128, mut cref) = (0, 0, 0, 0); - for ty in label_types { - match ty { - wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, - wasmparser::ValType::I64 | wasmparser::ValType::F64 => c64 += 1, - wasmparser::ValType::V128 => c128 += 1, - wasmparser::ValType::Ref(_) => cref += 1, - } - } - - (c32, c64, c128, cref) - } - - fn emit_dropkeep_to_label(&mut self, label_depth: u32) { - if self.is_unreachable() { - return; - } - - let Some(frame) = self.validator.get_control_frame(label_depth as usize) else { - return; - }; - - let base = self.stack_base_at_frame(label_depth as usize); - let label_types: Vec<_> = self.label_types_for_frame(frame); - let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); - - self.emit_dropkeep(base, c32, c64, c128, cref); - } - - fn label_types_for_frame(&self, frame: &wasmparser::Frame) -> Vec { - let ty = &frame.block_type; - match ty { - wasmparser::BlockType::Empty => Vec::new(), - wasmparser::BlockType::Type(ty) => match frame.kind { - FrameKind::Loop => Vec::new(), - _ => vec![*ty], - }, - wasmparser::BlockType::FuncType(idx) => { - let sub_type = self.validator.resources().sub_type_at(*idx); - let func_ty = match sub_type { - Some(st) => st.composite_type.unwrap_func(), - None => return Vec::new(), - }; - match frame.kind { - FrameKind::Loop => func_ty.params().to_vec(), - _ => func_ty.results().to_vec(), - } - } - } - } - - fn emit_branch_jump_or_return(&mut self, depth: u32) { - if let Some(ctx_idx) = self.get_ctx_idx(depth) { - let jump_ip = self.instructions.len(); - self.instructions.push(Instruction::Jump(0)); - self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); - } else { - self.instructions.push(Instruction::Return); - } - } - - fn emit_br_table_pad(&mut self, depth: u32) -> (usize, usize, bool) { - let pad_start = self.instructions.len(); - let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; - let Some(frame) = frame else { - let ip = self.instructions.len(); - self.instructions.push(Instruction::Return); - return (pad_start, ip, true); - }; - - let base = self.stack_base_at_frame(depth as usize); - let label_types: Vec<_> = self.label_types_for_frame(frame); - let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); - self.emit_dropkeep(base, c32, c64, c128, cref); - - let jump_ip = self.instructions.len(); - self.instructions.push(Instruction::Jump(0)); - (pad_start, jump_ip, false) - } - - fn patch_branch_jump_or_return(&mut self, depth: u32, jump_ip: usize) { - let Some(frame) = self.validator.get_control_frame(depth as usize) else { - self.instructions[jump_ip] = Instruction::Return; - return; - }; - let Some(ctx_idx) = self.get_ctx_idx(depth) else { - self.instructions[jump_ip] = Instruction::Return; - return; - }; - - match frame.kind { - FrameKind::Loop => self.patch_jump(jump_ip, self.ctx_stack[ctx_idx].start_ip), - _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), - } - } - - fn patch_end_jumps(&mut self, ctx: LoweringCtx, end_ip: usize) { - match ctx.kind { - BlockKind::Block | BlockKind::Loop => { - let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip } else { end_ip }; - for jump_ip in ctx.branch_jumps { - self.patch_jump(jump_ip, target); - } - } - BlockKind::If => { - if let Some((&cond_jump_ip, branch_jumps)) = ctx.branch_jumps.split_first() { - if !ctx.has_else { - self.patch_jump_if_zero(cond_jump_ip, end_ip); - } - for &jump_ip in branch_jumps { - self.patch_jump(jump_ip, end_ip); - } - } - } - } - } -} - macro_rules! impl_visit_operator { ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit ($($ann:tt)*));)* @@ -383,12 +172,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } define_mem_operands! { - visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_i64_store(I64Store), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32) + visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32), visit_i32_store(I32Store), visit_i64_store(I64Store) } define_operands! { // basic instructions - visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_add(I32Add), visit_i64_add(I64Add), visit_i64_rotl(I64Rotl), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -400,88 +189,35 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild visit_ref_func(RefFunc, u32), visit_table_fill(TableFill, u32), visit_table_get(TableGet, u32), visit_table_set(TableSet, u32), visit_table_grow(TableGrow, u32), visit_table_size(TableSize, u32), // Bulk Memory - visit_memory_init(MemoryInit, u32, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), + visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), // Wide Arithmetic visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU) } fn visit_global_set(&mut self, global_index: u32) -> Self::Output { - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::GlobalSet32(global_index), wasmparser::ValType::F32 => Instruction::GlobalSet32(global_index), wasmparser::ValType::I64 => Instruction::GlobalSet64(global_index), wasmparser::ValType::F64 => Instruction::GlobalSet64(global_index), wasmparser::ValType::V128 => Instruction::GlobalSet128(global_index), wasmparser::ValType::Ref(_) => Instruction::GlobalSetRef(global_index), - }), - _ => { - { - self.visit_unreachable(); - }; - } + }) } } - fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { - let memarg = MemoryArg::new(memarg.offset, memarg.memory); - let len = self.instructions.len(); - if len >= 2 { - let addr = self.instructions[len - 2]; - let value = self.instructions[len - 1]; - if let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = (addr, value) { - let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) else { - self.instructions.push(Instruction::I32Store(memarg)); - return; - }; - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32StoreLocalLocal(memarg, addr_local, value_local)); - return; - } - } - - self.instructions.push(Instruction::I32Store(memarg)); - } - fn visit_drop(&mut self) -> Self::Output { - match self.instructions.last().copied() { - Some(Instruction::LocalTee32(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSet32(local)); - return; - } - Some(Instruction::LocalTee64(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSet64(local)); - return; - } - Some(Instruction::LocalTee128(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSet128(local)); - return; - } - Some(Instruction::LocalTeeRef(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSetRef(local)); - return; - } - _ => {} - } - - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::Drop32, wasmparser::ValType::F32 => Instruction::Drop32, wasmparser::ValType::I64 => Instruction::Drop64, wasmparser::ValType::F64 => Instruction::Drop64, wasmparser::ValType::V128 => Instruction::Drop128, wasmparser::ValType::Ref(_) => Instruction::DropRef, - }), - _ => { - self.visit_unreachable(); - } + }) } } @@ -496,90 +232,6 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild self.instructions.push(Instruction::Return); } - fn visit_i32_add(&mut self) -> Self::Output { - let len = self.instructions.len(); - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::LocalGet32(a), Instruction::LocalGet32(b)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32AddLocals(a, b)); - return; - } - } - - if let Some(Instruction::I32Const(c)) = self.instructions.last().copied() { - self.instructions.pop(); - self.instructions.push(Instruction::I32AddConst(c)); - return; - } - - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::I32Const(c), Instruction::LocalGet32(local)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::LocalGet32(local)); - self.instructions.push(Instruction::I32AddConst(c)); - return; - } - } - - self.instructions.push(Instruction::I32Add); - } - - fn visit_i64_add(&mut self) -> Self::Output { - let len = self.instructions.len(); - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::LocalGet64(a), Instruction::LocalGet64(b)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I64AddLocals(a, b)); - return; - } - } - - if let Some(Instruction::I64Const(c)) = self.instructions.last().copied() { - self.instructions.pop(); - self.instructions.push(Instruction::I64AddConst(c)); - return; - } - - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::I64Const(c), Instruction::LocalGet64(local)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::LocalGet64(local)); - self.instructions.push(Instruction::I64AddConst(c)); - return; - } - } - - self.instructions.push(Instruction::I64Add); - } - - fn visit_i64_rotl(&mut self) -> Self::Output { - let len = self.instructions.len(); - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::I64Xor, Instruction::I64Const(c)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I64XorRotlConst(c)); - return; - } - } - - self.instructions.push(Instruction::I64Rotl); - } - fn visit_local_get(&mut self, idx: u32) -> Self::Output { let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { self.errors.push(crate::ParseError::UnsupportedOperator( @@ -588,103 +240,41 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild return; }; - match self.validator.get_local_type(idx) { - Some(t) => match t { + if let Some(t) = self.validator.get_local_type(idx) { + match t { wasmparser::ValType::I32 | wasmparser::ValType::F32 => { - if matches!(self.instructions.last(), Some(Instruction::LocalSet32(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTee32(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGet32(resolved_idx)); } wasmparser::ValType::I64 | wasmparser::ValType::F64 => { - if matches!(self.instructions.last(), Some(Instruction::LocalSet64(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTee64(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGet64(resolved_idx)); } wasmparser::ValType::V128 => { - if matches!(self.instructions.last(), Some(Instruction::LocalSet128(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTee128(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGet128(resolved_idx)); } wasmparser::ValType::Ref(_) => { - if matches!(self.instructions.last(), Some(Instruction::LocalSetRef(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTeeRef(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGetRef(resolved_idx)); } - }, - _ => { - self.visit_unreachable(); } } } - fn visit_local_set(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; - - if let Some( - Instruction::LocalGet32(from) - | Instruction::LocalGet64(from) - | Instruction::LocalGet128(from) - | Instruction::LocalGetRef(from), - ) = self.instructions.last() - { - let from = *from; - self.instructions.pop(); - - if from == resolved_idx { - // local.set x (local.get x) is a no-op; drop it. - return; - } - - // validation will ensure that the last instruction is the correct local.get - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { - wasmparser::ValType::I32 => Instruction::LocalCopy32(from, resolved_idx), - wasmparser::ValType::F32 => Instruction::LocalCopy32(from, resolved_idx), - wasmparser::ValType::I64 => Instruction::LocalCopy64(from, resolved_idx), - wasmparser::ValType::F64 => Instruction::LocalCopy64(from, resolved_idx), - wasmparser::ValType::V128 => Instruction::LocalCopy128(from, resolved_idx), - wasmparser::ValType::Ref(_) => Instruction::LocalCopyRef(from, resolved_idx), - }), - _ => { - self.visit_unreachable(); - } - } + fn visit_local_set(&mut self, idx: u32) -> Self::Output { + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); return; - } + }; - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalSet32(resolved_idx), wasmparser::ValType::F32 => Instruction::LocalSet32(resolved_idx), wasmparser::ValType::I64 => Instruction::LocalSet64(resolved_idx), wasmparser::ValType::F64 => Instruction::LocalSet64(resolved_idx), wasmparser::ValType::V128 => Instruction::LocalSet128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalSetRef(resolved_idx), - }), - _ => { - self.visit_unreachable(); - } + }) } } @@ -697,79 +287,14 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild }; if let Some(Some(t)) = self.validator.get_operand_type(0) { - match t { - wasmparser::ValType::I32 | wasmparser::ValType::F32 => { - if matches!(self.instructions.last(), Some(Instruction::LocalGet32(local)) if *local == resolved_idx) - { - return; - } - } - wasmparser::ValType::I64 | wasmparser::ValType::F64 => { - if matches!(self.instructions.last(), Some(Instruction::LocalGet64(local)) if *local == resolved_idx) - { - return; - } - } - wasmparser::ValType::V128 => { - if matches!(self.instructions.last(), Some(Instruction::LocalGet128(local)) if *local == resolved_idx) - { - return; - } - } - wasmparser::ValType::Ref(_) => { - if matches!(self.instructions.last(), Some(Instruction::LocalGetRef(local)) if *local == resolved_idx) - { - return; - } - } - } - } - - if let Some(Instruction::I64XorRotlConst(c)) = self.instructions.last().copied() { - match self.validator.get_operand_type(0) { - Some(Some(wasmparser::ValType::I64)) | Some(Some(wasmparser::ValType::F64)) => { - self.instructions.pop(); - self.instructions.push(Instruction::I64XorRotlConstTee(c, resolved_idx)); - return; - } - _ => {} - } - } - - let len = self.instructions.len(); - if len >= 2 { - let addr = self.instructions[len - 2]; - let load = self.instructions[len - 1]; - if let (Instruction::LocalGet32(addr_local), Instruction::I32Load(memarg)) = (addr, load) { - match self.validator.get_operand_type(0) { - Some(Some(wasmparser::ValType::I32)) | Some(Some(wasmparser::ValType::F32)) => { - let (Ok(addr_local), Ok(resolved_idx)) = (u8::try_from(addr_local), u8::try_from(resolved_idx)) - else { - self.instructions.push(Instruction::LocalTee32(resolved_idx)); - return; - }; - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32LoadLocalTee(memarg, addr_local, resolved_idx)); - return; - } - _ => {} - } - } - } - - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), wasmparser::ValType::F32 => Instruction::LocalTee32(resolved_idx), wasmparser::ValType::I64 => Instruction::LocalTee64(resolved_idx), wasmparser::ValType::F64 => Instruction::LocalTee64(resolved_idx), wasmparser::ValType::V128 => Instruction::LocalTee128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalTeeRef(resolved_idx), - }), - _ => { - self.visit_unreachable(); - } + }) } } @@ -838,7 +363,18 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild fn visit_br_if(&mut self, depth: u32) -> Self::Output { let cond_jump_ip = self.instructions.len(); self.instructions.push(Instruction::JumpIfZero(0)); + + let branch_side_start = self.instructions.len(); self.emit_dropkeep_to_label(depth); + + if self.instructions.len() == branch_side_start + && let Some(ctx_idx) = self.get_ctx_idx(depth) + { + self.instructions[cond_jump_ip] = Instruction::JumpIfNonZero(0); + self.ctx_stack[ctx_idx].branch_jumps.push(cond_jump_ip); + return; + } + self.emit_branch_jump_or_return(depth); self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); } @@ -1043,3 +579,229 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuild self.v128_constants.push(value.i128()); } } + +impl FunctionBuilder { + pub(crate) fn validator_visitor( + &mut self, + offset: usize, + ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { + self.validator.simd_visitor(offset) + } + + pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { + Self { + validator, + local_addr_map, + instructions: Vec::with_capacity(instr_capacity), + v128_constants: Vec::new(), + ctx_stack: Vec::with_capacity(256), + errors: Vec::new(), + } + } + + fn stack_base_at_frame(&self, depth: usize) -> StackBase { + let Some(frame) = self.validator.get_control_frame(depth) else { return StackBase::default() }; + let mut base = StackBase::default(); + for i in 0..frame.height { + let depth_from_top = self.validator.operand_stack_height() as usize - 1 - i; + if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => base.s64 += 1, + wasmparser::ValType::V128 => base.s128 += 1, + wasmparser::ValType::Ref(_) => base.sref += 1, + } + } + } + + base + } + + fn unsupported(&mut self, name: &str) { + self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); + } + + fn is_unreachable(&self) -> bool { + self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) + } + + fn get_ctx_idx(&self, depth: u32) -> Option { + let len = self.ctx_stack.len(); + let idx = len.checked_sub(depth as usize + 1)?; + Some(idx) + } + + fn emit_dropkeep(&mut self, base: StackBase, c32: u16, c64: u16, c128: u16, cref: u16) { + if base.s32 == 0 + && c32 == 0 + && base.s64 == 0 + && c64 == 0 + && base.s128 == 0 + && c128 == 0 + && base.sref == 0 + && cref == 0 + { + return; + } + + let fits_u8 = base.s32 <= u8::MAX as u16 + && c32 <= u8::MAX as u16 + && base.s64 <= u8::MAX as u16 + && c64 <= u8::MAX as u16 + && base.s128 <= u8::MAX as u16 + && c128 <= u8::MAX as u16 + && base.sref <= u8::MAX as u16 + && cref <= u8::MAX as u16; + + if fits_u8 { + self.instructions.push(Instruction::DropKeepSmall { + base32: base.s32 as u8, + keep32: c32 as u8, + base64: base.s64 as u8, + keep64: c64 as u8, + base128: base.s128 as u8, + keep128: c128 as u8, + base_ref: base.sref as u8, + keep_ref: cref as u8, + }); + } else { + self.instructions.push(Instruction::DropKeep32(base.s32, c32)); + self.instructions.push(Instruction::DropKeep64(base.s64, c64)); + self.instructions.push(Instruction::DropKeep128(base.s128, c128)); + self.instructions.push(Instruction::DropKeepRef(base.sref, cref)); + } + } + + fn patch_jump(&mut self, jump_ip: usize, target: usize) { + match &mut self.instructions[jump_ip] { + Instruction::Jump(ip) | Instruction::JumpIfNonZero(ip) => { + *ip = target as u32; + } + _ => {} + } + } + + fn patch_jump_if_zero(&mut self, jump_ip: usize, target: usize) { + if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { + *ip = target as u32; + } + } + + fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { + let (mut c32, mut c64, mut c128, mut cref) = (0, 0, 0, 0); + for ty in label_types { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => c64 += 1, + wasmparser::ValType::V128 => c128 += 1, + wasmparser::ValType::Ref(_) => cref += 1, + } + } + + (c32, c64, c128, cref) + } + + fn emit_dropkeep_to_label(&mut self, label_depth: u32) { + if self.is_unreachable() { + return; + } + + let Some(frame) = self.validator.get_control_frame(label_depth as usize) else { + return; + }; + + let base = self.stack_base_at_frame(label_depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + + self.emit_dropkeep(base, c32, c64, c128, cref); + } + + fn label_types_for_frame(&self, frame: &wasmparser::Frame) -> Vec { + let ty = &frame.block_type; + match ty { + wasmparser::BlockType::Empty => Vec::new(), + wasmparser::BlockType::Type(ty) => match frame.kind { + FrameKind::Loop => Vec::new(), + _ => vec![*ty], + }, + wasmparser::BlockType::FuncType(idx) => { + let sub_type = self.validator.resources().sub_type_at(*idx); + let func_ty = match sub_type { + Some(st) => st.composite_type.unwrap_func(), + None => return Vec::new(), + }; + match frame.kind { + FrameKind::Loop => func_ty.params().to_vec(), + _ => func_ty.results().to_vec(), + } + } + } + } + + fn emit_branch_jump_or_return(&mut self, depth: u32) { + if let Some(ctx_idx) = self.get_ctx_idx(depth) { + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); + } else { + self.instructions.push(Instruction::Return); + } + } + + fn emit_br_table_pad(&mut self, depth: u32) -> (usize, usize, bool) { + let pad_start = self.instructions.len(); + let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; + let Some(frame) = frame else { + let ip = self.instructions.len(); + self.instructions.push(Instruction::Return); + return (pad_start, ip, true); + }; + + let base = self.stack_base_at_frame(depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + self.emit_dropkeep(base, c32, c64, c128, cref); + + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + (pad_start, jump_ip, false) + } + + fn patch_branch_jump_or_return(&mut self, depth: u32, jump_ip: usize) { + let Some(frame) = self.validator.get_control_frame(depth as usize) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + let Some(ctx_idx) = self.get_ctx_idx(depth) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + + match frame.kind { + FrameKind::Loop => self.patch_jump(jump_ip, self.ctx_stack[ctx_idx].start_ip), + _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), + } + } + + fn patch_end_jumps(&mut self, ctx: LoweringCtx, end_ip: usize) { + match ctx.kind { + BlockKind::Block | BlockKind::Loop => { + let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip } else { end_ip }; + for jump_ip in ctx.branch_jumps { + self.patch_jump(jump_ip, target); + } + } + BlockKind::If => { + if let Some((&cond_jump_ip, branch_jumps)) = ctx.branch_jumps.split_first() { + if !ctx.has_else { + self.patch_jump_if_zero(cond_jump_ip, end_ip); + } + for &jump_ip in branch_jumps { + self.patch_jump(jump_ip, end_ip); + } + } + } + } + } +} diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 54ab12af..00910133 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -119,6 +119,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { ReturnCallIndirect(ty, table) => { self.exec_call_indirect::(*ty, *table)?; continue; } Jump(ip) => { self.exec_jump(*ip); continue; } JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { continue; }, + JumpIfNonZero(ip) => if self.exec_jump_if_non_zero(*ip) { continue; }, DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { let b32 = self.cf.stack_base().s32 + *base32 as u32; let k32 = *keep32 as usize; @@ -172,30 +173,14 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64AddLocals(a, b) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)))?, I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), - I32StoreLocalLocal(m, addr_local, value_local) => { - let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); - let addr_local = u16::from(*addr_local); - let value_local = u16::from(*value_local); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, addr_local)); - let value = self.store.stack.values.local_get::(&self.cf, value_local).to_mem_bytes(); - mem.store((m.offset() + addr) as usize, value.len(), &value)?; - } - I32LoadLocalTee(m, addr_local, dst_local) => { - let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); - let addr_local = u16::from(*addr_local); - let dst_local = u16::from(*dst_local); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, addr_local)); - let Some(Ok(addr)) = m.offset().checked_add(addr).map(|a| a.try_into()) else { - return Err(Error::Trap(Trap::MemoryOutOfBounds { - offset: addr as usize, - len: 4, - max: 0, - })); - }; - let value = mem.load_as::<4, i32>(addr)?; - self.store.stack.values.local_set(&self.cf, dst_local, value); - self.store.stack.values.push(value)?; - } + LocalAddConst32(local_index, c) => self.store.stack.values.local_update::(&self.cf, *local_index, |local| *local = local.wrapping_add(*c as u32)), + LocalAddConst64(local_index, c) => self.store.stack.values.local_update::(&self.cf, *local_index, |local| *local = local.wrapping_add(*c as u64)), + LocalSetConst32(local_index, c) => self.store.stack.values.local_set::(&self.cf, *local_index, *c), + LocalSetConst64(local_index, c) => self.store.stack.values.local_set::(&self.cf, *local_index, *c), + I32StoreLocalLocal(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local, |v| v)?, + I64StoreLocalLocal(m, addr_local, value_local) =>self.exec_store_local_local::(*m, *addr_local, *value_local, |v| v)?, + I32LoadLocalTee(m, addr_local, dst_local) => self.exec_i32_load_local_tee(*m, *addr_local, *dst_local)?, + I32LoadLocalSet(m, addr_local, dst_local) => self.exec_i32_load_local_set(*m, *addr_local, *dst_local)?, I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), I64XorRotlConstTee(c, local_index) => { stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)); @@ -323,6 +308,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { // Bulk memory operations MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, MemoryFill(addr) => self.exec_memory_fill(*addr)?, + MemoryFillImm(addr, val, size) => self.exec_memory_fill_imm(*addr, *val, *size)?, MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, DataDrop(data_index) => self.store.state.get_data_mut(self.module.resolve_data_addr(*data_index)).drop(), ElemDrop(elem_index) => self.store.state.get_elem_mut(self.module.resolve_elem_addr(*elem_index)).drop(), @@ -700,6 +686,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { false } + #[inline(always)] + fn exec_jump_if_non_zero(&mut self, ip: u32) -> bool { + if self.store.stack.values.pop::() != 0 { + self.cf.instr_ptr = ip; + return true; + } + false + } + #[inline(always)] fn exec_branch_table(&mut self, default_ip: u32, len: u32) { let idx = self.store.stack.values.pop::(); @@ -844,6 +839,52 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.cf = cf; false } + + #[inline(always)] + fn local_mem_addr(&self, memarg: MemoryArg, addr_local: u8) -> Result { + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, u16::from(addr_local))); + let Some(Ok(addr)) = memarg.offset().checked_add(addr).map(|a| a.try_into()) else { + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: addr as usize, len: N, max: 0 })); + }; + Ok(addr) + } + + #[inline(always)] + fn exec_store_local_local, const N: usize>( + &mut self, + memarg: MemoryArg, + addr_local: u8, + value_local: u8, + cast: fn(T) -> U, + ) -> Result<()> { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(memarg.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, u16::from(addr_local))); + let value = cast(self.store.stack.values.local_get::(&self.cf, u16::from(value_local))).to_mem_bytes(); + mem.store((memarg.offset() + addr) as usize, value.len(), &value)?; + Ok(()) + } + + #[inline(always)] + fn exec_i32_load_local_value(&self, memarg: MemoryArg, addr_local: u8) -> Result { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(memarg.mem_addr())); + mem.load_as::<4, i32>(self.local_mem_addr::<4>(memarg, addr_local)?) + } + + #[inline(always)] + fn exec_i32_load_local_tee(&mut self, memarg: MemoryArg, addr_local: u8, dst_local: u8) -> Result<()> { + let value = self.exec_i32_load_local_value(memarg, addr_local)?; + self.store.stack.values.local_set(&self.cf, u16::from(dst_local), value); + self.store.stack.values.push(value)?; + Ok(()) + } + + #[inline(always)] + fn exec_i32_load_local_set(&mut self, memarg: MemoryArg, addr_local: u8, dst_local: u8) -> Result<()> { + let value = self.exec_i32_load_local_value(memarg, addr_local)?; + self.store.stack.values.local_set(&self.cf, u16::from(dst_local), value); + Ok(()) + } + fn exec_global_get(&mut self, global_index: u32) -> Result<()> { self.store.stack.values.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) } @@ -910,6 +951,13 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); mem.fill(dst as usize, size as usize, val as u8) } + + fn exec_memory_fill_imm(&mut self, addr: u32, val: u8, size: i32) -> Result<()> { + let dst: i32 = self.store.stack.values.pop(); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); + mem.fill(dst as usize, size as usize, val) + } + fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { let size: i32 = self.store.stack.values.pop(); let offset: i32 = self.store.stack.values.pop(); diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 1ec20fe6..87d84520 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -82,6 +82,14 @@ impl Stack { } } + #[inline(always)] + pub(crate) fn get_mut(&mut self, index: usize) -> &mut T { + match self.data.get_mut(index) { + Some(v) => v, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { debug_assert!(n <= self.len); let len = self.len; @@ -294,6 +302,16 @@ impl ValueStack { T::local_get(self, frame, index) } + #[inline] + pub(crate) fn local_update( + &mut self, + frame: &CallFrame, + index: LocalAddr, + func: impl FnOnce(&mut T), + ) { + T::local_update(self, frame, index, func) + } + #[inline] pub(crate) fn local_set(&mut self, frame: &CallFrame, index: LocalAddr, value: T) { T::local_set(self, frame, index, value); diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index a127cf67..48b688c7 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -107,6 +107,7 @@ mod sealed { pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + Default { fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self; + fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, func: impl FnOnce(&mut Self)); fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); fn stack_pop(stack: &mut ValueStack) -> Self; fn stack_peek(stack: &ValueStack) -> Self; @@ -137,6 +138,14 @@ macro_rules! impl_internalvalue { $to_outer(stack.$stack.get(frame.locals_base.$stack_base as usize + index as usize)) } + #[inline(always)] + fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, func: impl FnOnce(&mut Self)) { + let slot = stack.$stack.get_mut(frame.locals_base.$stack_base as usize + index as usize); + let mut value = $to_outer(*slot); + func(&mut value); + *slot = $to_internal(value); + } + #[inline(always)] fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) { stack.$stack.set(frame.locals_base.$stack_base as usize + index as usize, $to_internal(value)); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index bccbdd13..dedebc53 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -56,16 +56,22 @@ pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), I32AddLocals(LocalAddr, LocalAddr), I64AddLocals(LocalAddr, LocalAddr), I32AddConst(i32), I64AddConst(i64), + LocalAddConst32(LocalAddr, i32), LocalAddConst64(LocalAddr, i64), + LocalSetConst32(LocalAddr, i32), LocalSetConst64(LocalAddr, i64), I32StoreLocalLocal(MemoryArg, u8, u8), + I64StoreLocalLocal(MemoryArg, u8, u8), I32LoadLocalTee(MemoryArg, u8, u8), + I32LoadLocalSet(MemoryArg, u8, u8), I64XorRotlConst(i64), I64XorRotlConstTee(i64, LocalAddr), + // > Control Instructions (jump-oriented, lowered from structured control during parsing) // See Unreachable, Nop, Jump(u32), JumpIfZero(u32), + JumpIfNonZero(u32), DropKeepSmall { base32: u8, keep32: u8, base64: u8, keep64: u8, base128: u8, keep128: u8, base_ref: u8, keep_ref: u8 }, DropKeep32(u16, u16), DropKeep64(u16, u16), @@ -177,6 +183,7 @@ pub enum Instruction { MemoryInit(MemAddr, DataAddr), MemoryCopy(MemAddr, MemAddr), MemoryFill(MemAddr), + MemoryFillImm(MemAddr, u8, i32), DataDrop(DataAddr), ElemDrop(ElemAddr), diff --git a/examples/dump-bytecode.rs b/examples/dump-bytecode.rs new file mode 100644 index 00000000..912da41a --- /dev/null +++ b/examples/dump-bytecode.rs @@ -0,0 +1,53 @@ +use eyre::{Result, bail}; +use std::io::Read; +use std::path::Path; +use tinywasm::parser::Parser; +use tinywasm::types::{ExternalKind, ImportKind}; + +fn read_input(path: &str) -> Result> { + if path == "-" { + let mut source = String::new(); + std::io::stdin().read_to_string(&mut source)?; + return Ok(wat::parse_str(source)?); + } + + let bytes = std::fs::read(path)?; + let is_wasm = Path::new(path).extension().and_then(|s| s.to_str()) == Some("wasm"); + if is_wasm { Ok(bytes) } else { Ok(wat::parse_bytes(&bytes)?.into_owned()) } +} + +fn main() -> Result<()> { + let args = std::env::args().collect::>(); + if args.len() != 2 { + bail!("usage: cargo run --example dump-bytecode -- ") + } + + let wasm = read_input(&args[1])?; + let module = Parser::new().parse_module_bytes(&wasm)?; + + let imported_func_count = + module.imports.iter().filter(|import| matches!(import.kind, ImportKind::Function(_))).count() as u32; + + for (func_idx, func) in module.funcs.iter().enumerate() { + let global_idx = imported_func_count + func_idx as u32; + let exports = module + .exports + .iter() + .filter(|export| export.kind == ExternalKind::Func && export.index == global_idx) + .map(|export| export.name.as_ref()) + .collect::>(); + + if exports.is_empty() { + println!("func[{func_idx}] global={global_idx}"); + } else { + println!("func[{func_idx}] global={global_idx} exports={exports:?}"); + } + + for (ip, instr) in func.instructions.iter().enumerate() { + println!(" {ip:04}: {instr:?}"); + } + println!(); + } + + Ok(()) +} From 66c9f7ab06dd67ac6e62321dd33943de7f9f9e57 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 19:02:08 +0200 Subject: [PATCH 39/47] chore: fix stable build, move BranchTableTarget to FunctionData Signed-off-by: Henry --- crates/parser/src/module.rs | 4 +- crates/parser/src/optimize.rs | 138 ++++++++++++-------- crates/parser/src/visit.rs | 54 ++++---- crates/tinywasm/src/interpreter/executor.rs | 12 +- crates/types/src/instructions.rs | 5 +- crates/types/src/lib.rs | 1 + 6 files changed, 114 insertions(+), 100 deletions(-) diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index fdc02ab1..29e556cc 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -196,7 +196,7 @@ impl ModuleReader { .into_iter() .zip(self.code_type_addrs) .enumerate() - .map(|(func_idx, ((instructions, data, locals), ty_idx))| { + .map(|(func_idx, ((instructions, mut data, locals), ty_idx))| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); let params = ValueCountsSmall::from(&ty.params); let locals = ValueCountsSmall { @@ -206,7 +206,7 @@ impl ModuleReader { cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), }; let self_func_addr = imported_func_count + func_idx as u32; - let instructions = optimize::optimize_instructions(instructions, self_func_addr, options); + let instructions = optimize::optimize_instructions(instructions, &mut data, self_func_addr, options); WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } }) diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs index 887ea042..1369d7a8 100644 --- a/crates/parser/src/optimize.rs +++ b/crates/parser/src/optimize.rs @@ -1,15 +1,16 @@ use crate::ParserOptions; use alloc::vec::Vec; -use tinywasm_types::Instruction; +use tinywasm_types::{Instruction, WasmFunctionData}; pub(crate) fn optimize_instructions( mut instructions: Vec, + function_data: &mut WasmFunctionData, self_func_addr: u32, options: &ParserOptions, ) -> Vec { rewrite(&mut instructions, self_func_addr); if options.dce { - dce(&mut instructions); + dce(&mut instructions, function_data); } instructions } @@ -135,29 +136,32 @@ fn rewrite(instructions: &mut [Instruction], self_func_addr: u32) { instructions[read] = Instruction::Nop; } } - Instruction::LocalGet64(dst) + Instruction::LocalGet64(dst) => { if read > 0 && let Instruction::LocalSet64(src) = instructions[read - 1] - && src == dst => - { - instructions[read - 1] = Instruction::LocalTee64(src); - instructions[read] = Instruction::Nop; + && src == dst + { + instructions[read - 1] = Instruction::LocalTee64(src); + instructions[read] = Instruction::Nop; + } } - Instruction::LocalGet128(dst) + Instruction::LocalGet128(dst) => { if read > 0 && let Instruction::LocalSet128(src) = instructions[read - 1] - && src == dst => - { - instructions[read - 1] = Instruction::LocalTee128(src); - instructions[read] = Instruction::Nop; + && src == dst + { + instructions[read - 1] = Instruction::LocalTee128(src); + instructions[read] = Instruction::Nop; + } } - Instruction::LocalGetRef(dst) + Instruction::LocalGetRef(dst) => { if read > 0 && let Instruction::LocalSetRef(src) = instructions[read - 1] - && src == dst => - { - instructions[read - 1] = Instruction::LocalTeeRef(src); - instructions[read] = Instruction::Nop; + && src == dst + { + instructions[read - 1] = Instruction::LocalTeeRef(src); + instructions[read] = Instruction::Nop; + } } Instruction::LocalSet32(dst) => { @@ -230,19 +234,23 @@ fn rewrite(instructions: &mut [Instruction], self_func_addr: u32) { instructions[read] = Instruction::LocalAddConst64(dst, c); } } - Instruction::LocalSet128(dst) + Instruction::LocalSet128(dst) => { if read > 0 - && let Instruction::LocalGet128(src) = instructions[read - 1] => - { - instructions[read - 1] = Instruction::Nop; - instructions[read] = if src == dst { Instruction::Nop } else { Instruction::LocalCopy128(src, dst) }; + && let Instruction::LocalGet128(src) = instructions[read - 1] + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy128(src, dst) }; + } } - Instruction::LocalSetRef(dst) + Instruction::LocalSetRef(dst) => { if read > 0 - && let Instruction::LocalGetRef(src) = instructions[read - 1] => - { - instructions[read - 1] = Instruction::Nop; - instructions[read] = if src == dst { Instruction::Nop } else { Instruction::LocalCopyRef(src, dst) }; + && let Instruction::LocalGetRef(src) = instructions[read - 1] + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopyRef(src, dst) }; + } } Instruction::LocalTee32(dst) => { @@ -273,55 +281,61 @@ fn rewrite(instructions: &mut [Instruction], self_func_addr: u32) { } _ => {} }, - Instruction::LocalTee128(dst) + Instruction::LocalTee128(dst) => { if read > 0 && let Instruction::LocalGet128(src) = instructions[read - 1] - && src == dst => - { - instructions[read] = Instruction::Nop; + && src == dst + { + instructions[read] = Instruction::Nop; + } } - Instruction::LocalTeeRef(dst) + Instruction::LocalTeeRef(dst) => { if read > 0 && let Instruction::LocalGetRef(src) = instructions[read - 1] - && src == dst => - { - instructions[read] = Instruction::Nop; + && src == dst + { + instructions[read] = Instruction::Nop; + } } - Instruction::Drop32 + Instruction::Drop32 => { if read > 0 - && let Instruction::LocalTee32(local) = instructions[read - 1] => - { - instructions[read - 1] = Instruction::LocalSet32(local); - instructions[read] = Instruction::Nop; + && let Instruction::LocalTee32(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSet32(local); + instructions[read] = Instruction::Nop; + } } - Instruction::Drop64 + Instruction::Drop64 => { if read > 0 - && let Instruction::LocalTee64(local) = instructions[read - 1] => - { - instructions[read - 1] = Instruction::LocalSet64(local); - instructions[read] = Instruction::Nop; + && let Instruction::LocalTee64(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSet64(local); + instructions[read] = Instruction::Nop; + } } - Instruction::Drop128 + Instruction::Drop128 => { if read > 0 - && let Instruction::LocalTee128(local) = instructions[read - 1] => - { - instructions[read - 1] = Instruction::LocalSet128(local); - instructions[read] = Instruction::Nop; + && let Instruction::LocalTee128(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSet128(local); + instructions[read] = Instruction::Nop; + } } - Instruction::DropRef + Instruction::DropRef => { if read > 0 - && let Instruction::LocalTeeRef(local) = instructions[read - 1] => - { - instructions[read - 1] = Instruction::LocalSetRef(local); - instructions[read] = Instruction::Nop; + && let Instruction::LocalTeeRef(local) = instructions[read - 1] + { + instructions[read - 1] = Instruction::LocalSetRef(local); + instructions[read] = Instruction::Nop; + } } _ => {} } } } -fn dce(instructions: &mut Vec) { +fn dce(instructions: &mut Vec, function_data: &mut WasmFunctionData) { let old_len = instructions.len(); if old_len == 0 { return; @@ -340,13 +354,21 @@ fn dce(instructions: &mut Vec) { } let compacted_len = old_len as u32 - removed_total; + + function_data.branch_table_targets.iter_mut().for_each(|ip| { + let old_target = *ip as usize; + if old_target <= old_len { + *ip -= removed_before[old_target]; + debug_assert!(*ip < compacted_len, "remapped jump target points past end of function"); + } + }); + instructions.retain_mut(|instr| { let ip = match instr { Instruction::Jump(ip) | Instruction::JumpIfZero(ip) | Instruction::JumpIfNonZero(ip) - | Instruction::BranchTableTarget(ip) - | Instruction::BranchTable(ip, _) => ip, + | Instruction::BranchTable(ip, _, _) => ip, _ => return !matches!(instr, Instruction::Nop), }; diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 09e3fae7..076f7c1a 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -32,6 +32,21 @@ struct LoweringCtx { branch_jumps: Vec, } +#[derive(Default)] +struct FunctionDataBuilder { + v128_constants: Vec, + branch_table_targets: Vec, +} + +impl FunctionDataBuilder { + fn finish(self) -> WasmFunctionData { + WasmFunctionData { + v128_constants: self.v128_constants.into_boxed_slice(), + branch_table_targets: self.branch_table_targets.into_boxed_slice(), + } + } +} + struct ValidateThenVisit<'a, R: WasmModuleResources>(usize, &'a mut FunctionBuilder); macro_rules! validate_then_visit { @@ -75,11 +90,7 @@ pub(crate) fn process_operators_and_validate( return Err(builder.errors.remove(0)); } - Ok(( - builder.instructions, - WasmFunctionData { v128_constants: builder.v128_constants.into_boxed_slice() }, - builder.validator.into_allocations(), - )) + Ok((builder.instructions, builder.data.finish(), builder.validator.into_allocations())) } macro_rules! define_operand { @@ -135,7 +146,7 @@ macro_rules! define_mem_operands_simd_lane { pub(crate) struct FunctionBuilder { validator: FuncValidator, instructions: Vec, - v128_constants: Vec, + data: FunctionDataBuilder, ctx_stack: Vec, local_addr_map: Vec, errors: Vec, @@ -390,14 +401,8 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild let target_depths: Vec = ts; let header_ip = self.instructions.len(); - self.instructions.push(Instruction::BranchTable(0, len)); - - let target_table_ip = self.instructions.len(); - for _ in 0..len { - self.instructions.push(Instruction::BranchTableTarget(0)); - } - let default_target_ip = self.instructions.len(); - self.instructions.push(Instruction::BranchTableTarget(0)); + let branch_table_start = self.data.branch_table_targets.len() as u32; + self.instructions.push(Instruction::BranchTable(0, branch_table_start, len)); let mut seen = alloc::collections::BTreeMap::::new(); struct PadInfo { @@ -418,18 +423,13 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild pads.push(PadInfo { depth, pad_start, jump_or_ret_ip, is_return }); } - for (i, &depth) in target_depths.iter().enumerate() { + for &depth in &target_depths { let pad_idx = seen[&depth]; - if let Instruction::BranchTableTarget(ip) = &mut self.instructions[target_table_ip + i] { - *ip = pads[pad_idx].pad_start as u32; - } + self.data.branch_table_targets.push(pads[pad_idx].pad_start as u32); } let default_pad_idx = seen[&default_depth]; - if let Instruction::BranchTableTarget(ip) = &mut self.instructions[default_target_ip] { - *ip = pads[default_pad_idx].pad_start as u32; - } - if let Instruction::BranchTable(default_ip, _) = &mut self.instructions[header_ip] { + if let Instruction::BranchTable(default_ip, _, _) = &mut self.instructions[header_ip] { *default_ip = pads[default_pad_idx].pad_start as u32; } @@ -570,13 +570,13 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuild } fn visit_i8x16_shuffle(&mut self, lanes: [u8; 16]) -> Self::Output { - self.instructions.push(Instruction::I8x16Shuffle(self.v128_constants.len() as u32)); - self.v128_constants.push(i128::from_le_bytes(lanes)); + self.instructions.push(Instruction::I8x16Shuffle(self.data.v128_constants.len() as u32)); + self.data.v128_constants.push(i128::from_le_bytes(lanes)); } fn visit_v128_const(&mut self, value: wasmparser::V128) -> Self::Output { - self.instructions.push(Instruction::V128Const(self.v128_constants.len() as u32)); - self.v128_constants.push(value.i128()); + self.instructions.push(Instruction::V128Const(self.data.v128_constants.len() as u32)); + self.data.v128_constants.push(value.i128()); } } @@ -593,7 +593,7 @@ impl FunctionBuilder { validator, local_addr_map, instructions: Vec::with_capacity(instr_capacity), - v128_constants: Vec::new(), + data: FunctionDataBuilder::default(), ctx_stack: Vec::with_capacity(256), errors: Vec::new(), } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 00910133..83164212 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -154,8 +154,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let k = *keep as usize; self.store.stack.values.stack_ref.truncate_keep(b as usize, k); } - BranchTable(default_ip, len) => { self.exec_branch_table(*default_ip, *len); continue; } - BranchTableTarget {..} => {}, + BranchTable(default_ip, start, len) => { self.exec_branch_table(*default_ip, *start, *len); continue; } Return => { if self.exec_return() { return Ok(Some(())); } continue; } LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, LocalGet64(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, @@ -696,15 +695,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } #[inline(always)] - fn exec_branch_table(&mut self, default_ip: u32, len: u32) { + fn exec_branch_table(&mut self, default_ip: u32, start: u32, len: u32) { let idx = self.store.stack.values.pop::(); - let start = self.cf.instr_ptr + 1; - let target_ip = if idx >= 0 && (idx as u32) < len { - match self.func.instructions.0.get((start + idx as u32) as usize) { - Some(Instruction::BranchTableTarget(ip)) => *ip, - _ => default_ip, - } + self.func.data.branch_table_targets.get((start + idx as u32) as usize).copied().unwrap_or(default_ip) } else { default_ip }; diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index dedebc53..78b52b8d 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -77,8 +77,7 @@ pub enum Instruction { DropKeep64(u16, u16), DropKeep128(u16, u16), DropKeepRef(u16, u16), - BranchTable(u32, u32), // (default_landing_pad_ip, target_count) - followed by BranchTableTarget entries - BranchTableTarget(u32), // (landing_pad_ip) + BranchTable(u32, u32, u32), // (default_landing_pad_ip, branch_table_start, target_count) Return, Call(FuncAddr), CallSelf, @@ -164,8 +163,6 @@ pub enum Instruction { F32ConvertI32S, F32ConvertI32U, F32ConvertI64S, F32ConvertI64U, F32DemoteF64, F64ConvertI32S, F64ConvertI32U, F64ConvertI64S, F64ConvertI64U, F64PromoteF32, - // Reinterpretations are parser no-ops and intentionally omitted. - // Saturating Float-to-Int Conversions I32TruncSatF32S, I32TruncSatF32U, I32TruncSatF64S, I32TruncSatF64U, I64TruncSatF32S, I64TruncSatF32U, I64TruncSatF64S, I64TruncSatF64U, diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 2f6107a9..a56dc9f1 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -318,6 +318,7 @@ impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ArcSlice { #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunctionData { pub v128_constants: Box<[i128]>, + pub branch_table_targets: Box<[u32]>, } /// A WebAssembly Module Export From 7d4dfd8ec65c78eb8154d89aa77dd71ac65015f6 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 20:49:40 +0200 Subject: [PATCH 40/47] chore: cleanup --- crates/parser/src/conversion.rs | 77 +++++++--------- crates/parser/src/lib.rs | 81 ++++++----------- crates/parser/src/module.rs | 89 +++++++------------ crates/parser/src/visit.rs | 32 ++----- crates/tinywasm/Cargo.toml | 4 - crates/tinywasm/src/imports.rs | 27 +++--- crates/tinywasm/src/instance.rs | 46 +++------- crates/tinywasm/src/interpreter/executor.rs | 10 +-- .../src/interpreter/stack/call_stack.rs | 6 +- .../src/interpreter/stack/value_stack.rs | 8 +- crates/tinywasm/src/module.rs | 9 +- crates/types/src/instructions.rs | 4 +- crates/types/src/lib.rs | 36 ++------ 13 files changed, 142 insertions(+), 287 deletions(-) diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index b62e670f..570d0a18 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -70,38 +70,33 @@ pub(crate) fn convert_module_imports<'a, T: IntoIterator) -> Result { - Ok(Import { - module: import.module.to_string().into_boxed_str(), - name: import.name.to_string().into_boxed_str(), - kind: match import.ty { - wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), - wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { - element_type: convert_reftype(ty.element_type), - size_initial: ty.initial.try_into().map_err(|_| { - crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial)) - })?, - size_max: match ty.maximum { - Some(max) => Some(max.try_into().map_err(|_| { - crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")) - })?), - None => None, - }, - }), - wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), - wasmparser::TypeRef::Global(ty) => { - ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) - } - wasmparser::TypeRef::Tag(ty) => { - return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); - } - _ => { - return Err(crate::ParseError::UnsupportedOperator(format!( - "Unsupported import kind: {:?}", - import.ty - ))); - } - }, - }) + let kind = match import.ty { + wasmparser::TypeRef::Func(ty) => ImportKind::Function(ty), + wasmparser::TypeRef::Table(ty) => ImportKind::Table(TableType { + element_type: convert_reftype(ty.element_type), + size_initial: ty.initial.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size initial is too large: {}", ty.initial)) + })?, + size_max: match ty.maximum { + Some(max) => Some(max.try_into().map_err(|_| { + crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")) + })?), + None => None, + }, + }), + wasmparser::TypeRef::Memory(ty) => ImportKind::Memory(convert_module_memory(ty)), + wasmparser::TypeRef::Global(ty) => { + ImportKind::Global(GlobalType { mutable: ty.mutable, ty: convert_valtype(&ty.content_type) }) + } + wasmparser::TypeRef::Tag(ty) => { + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {ty:?}"))); + } + _ => { + return Err(crate::ParseError::UnsupportedOperator(format!("Unsupported import kind: {:?}", import.ty))); + } + }; + + Ok(Import { module: import.module.into(), name: import.name.into(), kind }) } pub(crate) fn convert_module_memories>>( @@ -130,21 +125,16 @@ pub(crate) fn convert_module_table(table: wasmparser::Table<'_>) -> Result Some( - max.try_into() - .map_err(|_| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {max}")))?, - ), - None => None, - }; - + let size_max = table.ty.maximum.map(|max| max.try_into()).transpose(); + let size_max = + size_max.map_err(|e| crate::ParseError::UnsupportedOperator(format!("Table size max is too large: {e}")))?; Ok(TableType { element_type: convert_reftype(table.ty.element_type), size_initial, size_max }) } pub(crate) fn convert_module_globals( globals: wasmparser::SectionLimited<'_, wasmparser::Global<'_>>, ) -> Result> { - let globals = globals + globals .into_iter() .map(|global| { let global = global?; @@ -152,8 +142,7 @@ pub(crate) fn convert_module_globals( let ops = global.init_expr.get_operators_reader(); Ok(Global { init: process_const_operators(ops)?, ty: GlobalType { mutable: global.ty.mutable, ty } }) }) - .collect::>>()?; - Ok(globals) + .collect::>>() } pub(crate) fn convert_module_export(export: wasmparser::Export<'_>) -> Result { @@ -215,7 +204,6 @@ pub(crate) fn convert_module_code( pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result { let mut types = ty.types(); - if types.len() != 1 { return Err(crate::ParseError::UnsupportedOperator( "Expected exactly one type in the type section".to_string(), @@ -225,7 +213,6 @@ pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result let ty = types.next().unwrap().unwrap_func(); let params = ty.params().iter().map(convert_valtype).collect::>().into_boxed_slice(); let results = ty.results().iter().map(convert_valtype).collect::>().into_boxed_slice(); - Ok(FuncType { params, results }) } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 2d468572..a0c0c7e0 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -36,7 +36,7 @@ mod optimize; mod visit; pub use error::*; use module::ModuleReader; -use wasmparser::{Validator, WasmFeaturesInflated}; +use wasmparser::{Validator, WasmFeatures}; pub use tinywasm_types::TinyWasmModule; @@ -79,56 +79,32 @@ impl Parser { } fn create_validator(_options: ParserOptions) -> Validator { - let features = WasmFeaturesInflated { - bulk_memory: true, - floats: true, - multi_value: true, - mutable_global: true, - reference_types: true, - sign_extension: true, - saturating_float_to_int: true, - function_references: true, - tail_call: true, - multi_memory: true, - simd: true, - memory64: true, - custom_page_sizes: true, - bulk_memory_opt: true, - call_indirect_overlong: true, - wide_arithmetic: true, - relaxed_simd: true, - - compact_imports: false, - cm_map: false, - custom_descriptors: false, - cm_threading: false, - extended_const: false, - gc_types: true, - stack_switching: false, - component_model: false, - exceptions: false, - gc: false, - memory_control: false, - threads: false, - shared_everything_threads: false, - legacy_exceptions: false, - cm_async: false, - cm_async_builtins: false, - cm_async_stackful: false, - cm_nested_names: false, - cm_values: false, - cm_error_context: false, - cm_fixed_length_lists: false, - cm_gc: false, - }; - Validator::new_with_features(features.into()) + let features = WasmFeatures::CALL_INDIRECT_OVERLONG + | WasmFeatures::BULK_MEMORY_OPT + | WasmFeatures::RELAXED_SIMD + | WasmFeatures::GC_TYPES + | WasmFeatures::REFERENCE_TYPES + | WasmFeatures::MUTABLE_GLOBAL + | WasmFeatures::MULTI_VALUE + | WasmFeatures::FLOATS + | WasmFeatures::BULK_MEMORY + | WasmFeatures::SATURATING_FLOAT_TO_INT + | WasmFeatures::SIGN_EXTENSION + | WasmFeatures::FUNCTION_REFERENCES + | WasmFeatures::TAIL_CALL + | WasmFeatures::MULTI_MEMORY + | WasmFeatures::SIMD + | WasmFeatures::MEMORY64 + | WasmFeatures::CUSTOM_PAGE_SIZES + | WasmFeatures::WIDE_ARITHMETIC; + Validator::new_with_features(features) } /// Parse a [`TinyWasmModule`] from bytes pub fn parse_module_bytes(&self, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); let mut validator = Self::create_validator(self.options.clone()); - let mut reader = ModuleReader::new(); + let mut reader = ModuleReader::default(); for payload in wasmparser::Parser::new(0).parse_all(wasm) { reader.process_payload(payload?, &mut validator)?; @@ -144,21 +120,16 @@ 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) - .map_err(|e| ParseError::Other(format!("Error opening file {:?}: {}", path.as_ref(), e)))?; - - let mut reader = crate::std::io::BufReader::new(f); - self.parse_module_stream(&mut reader) + let file = crate::std::fs::File::open(&path) + .map_err(|e| ParseError::Other(alloc::format!("Error opening file {:?}: {}", path.as_ref(), e)))?; + self.parse_module_stream(&mut crate::std::io::BufReader::new(file)) } #[cfg(feature = "std")] /// Parse a [`TinyWasmModule`] from a stream. Requires `std` feature. pub fn parse_module_stream(&self, mut stream: impl std::io::Read) -> Result { - use alloc::format; - let mut validator = Self::create_validator(self.options.clone()); - let mut reader = ModuleReader::new(); + let mut reader = ModuleReader::default(); let mut buffer = alloc::vec::Vec::new(); let mut parser = wasmparser::Parser::new(0); let mut eof = false; @@ -170,7 +141,7 @@ impl Parser { buffer.extend((0..hint).map(|_| 0u8)); let read_bytes = stream .read(&mut buffer[len..]) - .map_err(|e| ParseError::Other(format!("Error reading from stream: {e}")))?; + .map_err(|e| ParseError::Other(alloc::format!("Error reading from stream: {e}")))?; buffer.truncate(len + read_bytes); eof = read_bytes == 0; } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 29e556cc..cb29e21f 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -2,10 +2,7 @@ use crate::log::debug; use crate::{ParseError, ParserOptions, Result, conversion, optimize}; use alloc::string::ToString; use alloc::{format, vec::Vec}; -use tinywasm_types::{ - ArcSlice, Data, Element, Export, FuncType, Global, Import, ImportKind, Instruction, MemoryType, TableType, - TinyWasmModule, ValueCounts, ValueCountsSmall, WasmFunction, WasmFunctionData, -}; +use tinywasm_types::*; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; pub(crate) type Code = (Vec, WasmFunctionData, ValueCounts); @@ -30,23 +27,16 @@ pub(crate) struct ModuleReader { } impl ModuleReader { - pub(crate) fn new() -> Self { - Self::default() - } - pub(crate) fn process_payload(&mut self, payload: Payload<'_>, validator: &mut Validator) -> Result<()> { - use wasmparser::Payload::*; - match payload { - Version { num, encoding, range } => { + Payload::Version { num, encoding, range } => { validator.version(num, encoding, &range)?; self.version = Some(num); - match encoding { - wasmparser::Encoding::Module => {} - wasmparser::Encoding::Component => return Err(ParseError::InvalidEncoding(encoding)), + if let wasmparser::Encoding::Component = encoding { + return Err(ParseError::InvalidEncoding(encoding)); } } - StartSection { func, range } => { + Payload::StartSection { func, range } => { if self.start_func.is_some() { return Err(ParseError::DuplicateSection("Start section".into())); } @@ -55,7 +45,7 @@ impl ModuleReader { validator.start_section(func, &range)?; self.start_func = Some(func); } - TypeSection(reader) => { + Payload::TypeSection(reader) => { if !self.func_types.is_empty() { return Err(ParseError::DuplicateSection("Type section".into())); } @@ -68,7 +58,7 @@ impl ModuleReader { .collect::>>()?; } - GlobalSection(reader) => { + Payload::GlobalSection(reader) => { if !self.globals.is_empty() { return Err(ParseError::DuplicateSection("Global section".into())); } @@ -77,7 +67,7 @@ impl ModuleReader { validator.global_section(&reader)?; self.globals = conversion::convert_module_globals(reader)?; } - TableSection(reader) => { + Payload::TableSection(reader) => { if !self.table_types.is_empty() { return Err(ParseError::DuplicateSection("Table section".into())); } @@ -85,7 +75,7 @@ impl ModuleReader { validator.table_section(&reader)?; self.table_types = conversion::convert_module_tables(reader)?; } - MemorySection(reader) => { + Payload::MemorySection(reader) => { if !self.memory_types.is_empty() { return Err(ParseError::DuplicateSection("Memory section".into())); } @@ -94,12 +84,12 @@ impl ModuleReader { validator.memory_section(&reader)?; self.memory_types = conversion::convert_module_memories(reader)?; } - ElementSection(reader) => { + Payload::ElementSection(reader) => { debug!("Found element section"); validator.element_section(&reader)?; self.elements = conversion::convert_module_elements(reader)?; } - DataSection(reader) => { + Payload::DataSection(reader) => { if !self.data.is_empty() { return Err(ParseError::DuplicateSection("Data section".into())); } @@ -108,14 +98,14 @@ impl ModuleReader { validator.data_section(&reader)?; self.data = conversion::convert_module_data_sections(reader)?; } - DataCountSection { count, range } => { + Payload::DataCountSection { count, range } => { debug!("Found data count section"); if !self.data.is_empty() { return Err(ParseError::DuplicateSection("Data count section".into())); } validator.data_count_section(count, &range)?; } - FunctionSection(reader) => { + Payload::FunctionSection(reader) => { if !self.code_type_addrs.is_empty() { return Err(ParseError::DuplicateSection("Function section".into())); } @@ -124,7 +114,7 @@ impl ModuleReader { validator.function_section(&reader)?; self.code_type_addrs = reader.into_iter().map(|f| Ok(f?)).collect::>>()?; } - CodeSectionStart { count, range, .. } => { + Payload::CodeSectionStart { count, range, .. } => { debug!("Found code section ({count} functions)"); if !self.code.is_empty() { return Err(ParseError::DuplicateSection("Code section".into())); @@ -132,7 +122,7 @@ impl ModuleReader { self.code.reserve(count as usize); validator.code_section_start(&range)?; } - CodeSectionEntry(function) => { + Payload::CodeSectionEntry(function) => { debug!("Found code section entry"); let v = validator.code_section_entry(&function)?; let func_validator = v.into_validator(self.func_validator_allocations.take().unwrap_or_default()); @@ -140,7 +130,7 @@ impl ModuleReader { self.code.push(code); self.func_validator_allocations = Some(allocations); } - ImportSection(reader) => { + Payload::ImportSection(reader) => { if !self.imports.is_empty() { return Err(ParseError::DuplicateSection("Import section".into())); } @@ -149,7 +139,7 @@ impl ModuleReader { validator.import_section(&reader)?; self.imports = conversion::convert_module_imports(reader.into_imports())?; } - ExportSection(reader) => { + Payload::ExportSection(reader) => { if !self.exports.is_empty() { return Err(ParseError::DuplicateSection("Export section".into())); } @@ -159,7 +149,7 @@ impl ModuleReader { self.exports = reader.into_iter().map(|e| conversion::convert_module_export(e?)).collect::>>()?; } - End(offset) => { + Payload::End(offset) => { debug!("Reached end of module"); if self.end_reached { return Err(ParseError::DuplicateSection("End section".into())); @@ -168,14 +158,13 @@ impl ModuleReader { validator.end(offset)?; self.end_reached = true; } - CustomSection(_reader) => { + Payload::CustomSection(_reader) => { debug!("Found custom section"); debug!("Skipping custom section: {:?}", _reader.name()); } - UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), + Payload::UnknownSection { .. } => return Err(ParseError::UnsupportedSection("Unknown section".into())), section => return Err(ParseError::UnsupportedSection(format!("Unsupported section: {section:?}"))), }; - Ok(()) } @@ -188,38 +177,22 @@ impl ModuleReader { return Err(ParseError::Other("Code and code type address count mismatch".to_string())); } - let imported_func_count = - self.imports.iter().filter(|i| matches!(&i.kind, ImportKind::Function(_))).count() as u32; - - let funcs = self - .code - .into_iter() - .zip(self.code_type_addrs) - .enumerate() - .map(|(func_idx, ((instructions, mut data, locals), ty_idx))| { + let imported_func_count = self.imports.iter().filter(|i| matches!(&i.kind, ImportKind::Function(_))).count(); + let funcs = self.code.into_iter().zip(self.code_type_addrs).enumerate().map( + |(func_idx, ((instructions, mut data, locals), ty_idx))| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); - let params = ValueCountsSmall::from(&ty.params); - let locals = ValueCountsSmall { - c32: u16::try_from(locals.c32).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - c64: u16::try_from(locals.c64).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - c128: u16::try_from(locals.c128).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), - }; - let self_func_addr = imported_func_count + func_idx as u32; - let instructions = optimize::optimize_instructions(instructions, &mut data, self_func_addr, options); - + let params = ValueCounts::from(&ty.params); + let self_func = (imported_func_count + func_idx) as u32; + let instructions = optimize::optimize_instructions(instructions, &mut data, self_func, options); WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } - }) - .collect::>(); - - let globals = self.globals; - let table_types = self.table_types; + }, + ); Ok(TinyWasmModule { - funcs: funcs.into(), + funcs: funcs.collect(), func_types: self.func_types.into(), - globals: globals.into(), - table_types: table_types.into(), + globals: self.globals.into(), + table_types: self.table_types.into(), imports: self.imports.into(), start_func: self.start_func, data: self.data.into(), diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 076f7c1a..8313d9f1 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -75,7 +75,7 @@ impl VisitSimdOperator<'_> for ValidateThenVisit<'_, R> pub(crate) fn process_operators_and_validate( validator: FuncValidator, body: FunctionBody<'_>, - local_addr_map: Vec, + local_addr_map: Vec, ) -> Result<(Vec, WasmFunctionData, FuncValidatorAllocations)> { let mut reader = body.get_operators_reader()?; let remaining = reader.get_binary_reader().bytes_remaining(); @@ -148,7 +148,7 @@ pub(crate) struct FunctionBuilder { instructions: Vec, data: FunctionDataBuilder, ctx_stack: Vec, - local_addr_map: Vec, + local_addr_map: Vec, errors: Vec, } @@ -244,13 +244,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_local_get(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; - + let resolved_idx = self.local_addr_map[idx as usize]; if let Some(t) = self.validator.get_local_type(idx) { match t { wasmparser::ValType::I32 | wasmparser::ValType::F32 => { @@ -270,13 +264,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_local_set(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; - + let resolved_idx = self.local_addr_map[idx as usize]; if let Some(Some(t)) = self.validator.get_operand_type(0) { self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalSet32(resolved_idx), @@ -290,13 +278,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_local_tee(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; - + let resolved_idx = self.local_addr_map[idx as usize]; if let Some(Some(t)) = self.validator.get_operand_type(0) { self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), @@ -472,7 +454,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild fn visit_typed_select_multi(&mut self, tys: Vec) -> Self::Output { let (c32, c64, c128, cref) = Self::label_keep_counts(&tys); - self.instructions.push(Instruction::SelectMulti(tinywasm_types::ValueCountsSmall { c32, c64, c128, cref })); + self.instructions.push(Instruction::SelectMulti(tinywasm_types::ValueCounts { c32, c64, c128, cref })); } fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { @@ -588,7 +570,7 @@ impl FunctionBuilder { self.validator.simd_visitor(offset) } - pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { + pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { Self { validator, local_addr_map, diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index f11b3ad8..00a47cfc 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -90,7 +90,6 @@ harness=false [[test]] name="test-wasm-memory64" harness=false -test=false [[test]] name="test-wasm-extended-const" @@ -100,17 +99,14 @@ test=false [[test]] name="test-wasm-relaxed-simd" harness=false -test=false [[test]] name="test-wasm-simd" harness=false -test=false [[test]] name="test-wasm-wide-arithmetic" harness=false -test=false [[test]] name="test-wast" diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index d55536ff..9e8de163 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -159,18 +159,18 @@ impl Extern { ty: &tinywasm_types::FuncType, func: impl Fn(FuncContext<'_>, &[WasmValue]) -> Result> + 'static, ) -> Self { - let _ty = ty.clone(); + let ty_inner = ty.clone(); let inner_func = move |ctx: FuncContext<'_>, args: &[WasmValue]| -> Result> { - let _ty = _ty.clone(); + let ty = ty_inner.clone(); let result = func(ctx, args)?; - if result.len() != _ty.results.len() { - return Err(crate::Error::InvalidHostFnReturn { expected: _ty.clone(), actual: result }); + if result.len() != ty.results.len() { + return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result }); }; - result.iter().zip(_ty.results.iter()).try_for_each(|(val, ty)| { - if val.val_type() != *ty { - return Err(crate::Error::InvalidHostFnReturn { expected: _ty.clone(), actual: result.clone() }); + result.iter().zip(ty.results.iter()).try_for_each(|(val, res_ty)| { + if val.val_type() != *res_ty { + return Err(crate::Error::InvalidHostFnReturn { expected: ty.clone(), actual: result.clone() }); } Ok(()) })?; @@ -344,20 +344,17 @@ impl Imports { fn compare_table_types(import: &Import, expected: &TableType, actual: &TableType) -> Result<()> { Self::compare_types(import, &actual.element_type, &expected.element_type)?; - if actual.size_initial > expected.size_initial { return Err(LinkingError::incompatible_import_type(import).into()); } match (expected.size_max, actual.size_max) { - (None, Some(_)) => return Err(LinkingError::incompatible_import_type(import).into()), + (None, Some(_)) => Err(LinkingError::incompatible_import_type(import).into()), (Some(expected_max), Some(actual_max)) if actual_max < expected_max => { - return Err(LinkingError::incompatible_import_type(import).into()); + Err(LinkingError::incompatible_import_type(import).into()) } - _ => {} + _ => Ok(()), } - - Ok(()) } fn compare_memory_types( @@ -394,9 +391,7 @@ impl Imports { let mut imports = ResolvedImports::new(); for import in &*module.0.imports { - let val = self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))?; - - match val { + match self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))? { // A link to something that needs to be added to the store ResolvedExtern::Extern(ex) => match (ex, &import.kind) { (Extern::Global { ty, val }, ImportKind::Global(import_ty)) => { diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 27824024..b95a94d1 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -14,25 +14,18 @@ use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut #[cfg_attr(feature = "debug", derive(Debug))] pub struct ModuleInstance(pub(crate) Rc); -#[expect(dead_code)] #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct ModuleInstanceInner { - pub(crate) failed_to_instantiate: bool, - pub(crate) store_id: usize, pub(crate) idx: ModuleInstanceAddr, - pub(crate) types: ArcSlice, - pub(crate) func_addrs: Box<[FuncAddr]>, pub(crate) table_addrs: Box<[TableAddr]>, pub(crate) mem_addrs: Box<[MemAddr]>, pub(crate) global_addrs: Box<[GlobalAddr]>, pub(crate) elem_addrs: Box<[ElemAddr]>, pub(crate) data_addrs: Box<[DataAddr]>, - pub(crate) func_start: Option, - pub(crate) imports: ArcSlice, pub(crate) exports: ArcSlice, } @@ -107,7 +100,6 @@ impl ModuleInstanceInner { impl ModuleInstance { /// Get the module instance's address - #[inline] pub fn id(&self) -> ModuleInstanceAddr { self.0.idx } @@ -116,24 +108,18 @@ impl ModuleInstance { /// /// See pub fn instantiate(store: &mut Store, module: Module, imports: Option) -> Result { - // This doesn't completely follow the steps in the spec, but the end result is the same - // Constant expressions are evaluated directly where they are used, so we - // don't need to create a auxiliary frame etc. - let idx = store.next_module_instance_idx(); let mut addrs = imports.unwrap_or_default().link(store, &module, idx)?; addrs.funcs.extend(store.init_funcs(&module.0.funcs, idx)?); addrs.tables.extend(store.init_tables(&module.0.table_types, idx)?); addrs.memories.extend(store.init_memories(&module.0.memory_types, idx)?); - let global_addrs = store.init_globals(addrs.globals, &module.0.globals, &addrs.funcs, idx)?; let (elem_addrs, elem_trapped) = store.init_elements(&addrs.tables, &addrs.funcs, &global_addrs, &module.0.elements, idx)?; let (data_addrs, data_trapped) = store.init_data(&addrs.memories, &module.0.data, idx)?; let instance = ModuleInstanceInner { - failed_to_instantiate: elem_trapped.is_some() || data_trapped.is_some(), store_id: store.id(), idx, types: module.0.func_types.clone(), @@ -144,7 +130,6 @@ impl ModuleInstance { elem_addrs, data_addrs, func_start: module.0.start_func, - imports: module.0.imports.clone(), exports: module.0.exports.clone(), }; @@ -166,7 +151,6 @@ impl ModuleInstance { ExternalKind::Memory => self.0.mem_addrs.get(exports.index as usize)?, ExternalKind::Global => self.0.global_addrs.get(exports.index as usize)?, }; - Some(ExternVal::new(exports.kind, *addr)) } @@ -186,11 +170,11 @@ impl ModuleInstance { } /// Get a typed exported function by name - pub fn exported_func(&self, store: &Store, name: &str) -> Result> - where - P: IntoWasmValueTuple, - R: FromWasmValueTuple, - { + pub fn exported_func( + &self, + store: &Store, + name: &str, + ) -> Result> { let func = self.exported_func_untyped(store, name)?; Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) } @@ -201,7 +185,6 @@ impl ModuleInstance { let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - self.memory(store, mem_addr) } @@ -211,26 +194,23 @@ impl ModuleInstance { let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - self.memory_mut(store, mem_addr) } /// Get a memory by address pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result> { - let mem = store.state.get_mem(self.0.resolve_mem_addr(addr)); - Ok(MemoryRef(mem)) + Ok(MemoryRef(store.state.get_mem(self.0.resolve_mem_addr(addr)))) } /// Get a memory by address (mutable) pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { - let mem = store.state.get_mem_mut(self.0.resolve_mem_addr(addr)); - Ok(MemoryRefMut(mem)) + Ok(MemoryRefMut(store.state.get_mem_mut(self.0.resolve_mem_addr(addr)))) } /// Get the start function of the module /// /// Returns None if the module has no start function - /// If no start function is specified, also checks for a _start function in the exports + /// If no start function is specified, also checks for a `_start` function in the exports /// /// See pub fn start_func(&self, store: &Store) -> Result> { @@ -251,23 +231,19 @@ impl ModuleInstance { }; let func_addr = self.0.resolve_func_addr(func_index); - let func_inst = store.state.get_func(func_addr); - let ty = func_inst.func.ty(); - + let ty = store.state.get_func(func_addr).func.ty(); Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone() })) } /// Invoke the start function of the module /// - /// Returns None if the module has no start function + /// Returns `None` if the module has no start function /// /// See pub fn start(&self, store: &mut Store) -> Result> { let Some(func) = self.start_func(store)? else { return Ok(None); }; - - let _ = func.call(store, &[])?; - Ok(Some(())) + func.call(store, &[]).map(|_| Some(())) } } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 83164212..f739906f 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -818,7 +818,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } fn exec_return(&mut self) -> bool { - let result_counts = ValueCountsSmall::from(self.func.ty.results.iter()); + let result_counts = ValueCounts::from(self.func.ty.results.iter()); self.store.stack.values.truncate_keep_counts(self.cf.locals_base, result_counts); let Some(cf) = self.store.stack.call_stack.pop() else { return true }; @@ -1188,12 +1188,10 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { impl<'store> Executor<'store, false> { #[inline(always)] pub(crate) fn run_to_completion(&mut self) -> Result<()> { - loop { - // for some reason, using a iteration count of 4096 here seems to be a sweet spot for performance - if self.exec::<1024>()?.is_some() { - return Ok(()); - } + if self.exec::<{ usize::MAX }>()?.is_some() { + return Ok(()); } + unreachable!(); } #[cfg(feature = "std")] diff --git a/crates/tinywasm/src/interpreter/stack/call_stack.rs b/crates/tinywasm/src/interpreter/stack/call_stack.rs index a91f9382..dfc71bc6 100644 --- a/crates/tinywasm/src/interpreter/stack/call_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/call_stack.rs @@ -1,7 +1,7 @@ use crate::{Result, Trap, unlikely}; use alloc::vec::Vec; -use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCountsSmall}; +use tinywasm_types::{FuncAddr, ModuleInstanceAddr, ValueCounts}; #[cfg_attr(feature = "debug", derive(Debug))] pub(crate) struct CallStack { @@ -39,7 +39,7 @@ pub(crate) struct CallFrame { pub(crate) module_addr: ModuleInstanceAddr, pub(crate) func_addr: FuncAddr, pub(crate) locals_base: StackBase, - pub(crate) stack_offset: ValueCountsSmall, + pub(crate) stack_offset: ValueCounts, } #[derive(Clone, Copy, Default)] @@ -56,7 +56,7 @@ impl CallFrame { func_addr: FuncAddr, module_addr: ModuleInstanceAddr, locals_base: StackBase, - stack_offset: ValueCountsSmall, + stack_offset: ValueCounts, ) -> Self { Self { instr_ptr: 0, func_addr, module_addr, locals_base, stack_offset } } diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 87d84520..b67ab09b 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -1,6 +1,6 @@ use alloc::boxed::Box; use alloc::vec::Vec; -use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCountsSmall, WasmValue}; +use tinywasm_types::{ExternRef, FuncRef, LocalAddr, ValType, ValueCounts, WasmValue}; use crate::{Result, Trap, engine::Config, interpreter::*, unlikely}; @@ -195,7 +195,7 @@ impl ValueStack { } #[inline] - pub(crate) fn select_multi(&mut self, counts: ValueCountsSmall) { + pub(crate) fn select_multi(&mut self, counts: ValueCounts) { let condition = self.pop::() != 0; self.stack_32.select_many(counts.c32 as usize, condition); self.stack_64.select_many(counts.c64 as usize, condition); @@ -257,7 +257,7 @@ impl ValueStack { val_types.into_iter().map(|val_type| self.pop_wasmvalue(*val_type)) } - pub(crate) fn enter_locals(&mut self, params: &ValueCountsSmall, locals: &ValueCountsSmall) -> Result { + pub(crate) fn enter_locals(&mut self, params: &ValueCounts, locals: &ValueCounts) -> Result { let locals_base32 = if params.c32 == 0 && locals.c32 == 0 { self.stack_32.len as u32 } else { @@ -282,7 +282,7 @@ impl ValueStack { Ok(StackBase { s32: locals_base32, s64: locals_base64, s128: locals_base128, sref: locals_baseref }) } - pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCountsSmall) { + pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCounts) { if keep.c32 == 0 && keep.c64 == 0 && keep.c128 == 0 && keep.cref == 0 { self.stack_32.len = base.s32 as usize; self.stack_64.len = base.s64 as usize; diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index d6c52100..1ce3ff0c 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -24,24 +24,21 @@ impl Module { #[cfg(feature = "parser")] /// Parse a module from bytes. Requires `parser` feature. pub fn parse_bytes(wasm: &[u8]) -> Result { - let parser = tinywasm_parser::Parser::new(); - let data = parser.parse_module_bytes(wasm)?; + let data = tinywasm_parser::Parser::new().parse_module_bytes(wasm)?; Ok(data.into()) } #[cfg(all(feature = "parser", feature = "std"))] /// Parse a module from a file. Requires `parser` and `std` features. pub fn parse_file(path: impl AsRef + Clone) -> Result { - let parser = tinywasm_parser::Parser::new(); - let data = parser.parse_module_file(path)?; + let data = tinywasm_parser::Parser::new().parse_module_file(path)?; Ok(data.into()) } #[cfg(all(feature = "parser", feature = "std"))] /// Parse a module from a stream. Requires `parser` and `std` features. pub fn parse_stream(stream: impl crate::std::io::Read) -> Result { - let parser = tinywasm_parser::Parser::new(); - let data = parser.parse_module_stream(stream)?; + let data = tinywasm_parser::Parser::new().parse_module_stream(stream)?; Ok(data.into()) } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 78b52b8d..0cbcad79 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,4 +1,4 @@ -use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType, ValueCountsSmall}; +use super::{FuncAddr, GlobalAddr, LocalAddr, TableAddr, TypeAddr, ValType, ValueCounts}; use crate::{ConstIdx, DataAddr, ElemAddr, ExternAddr, MemAddr}; /// Represents a memory immediate in a WebAssembly memory instruction. @@ -92,7 +92,7 @@ pub enum Instruction { Drop64, Select64, Drop128, Select128, DropRef, SelectRef, - SelectMulti(ValueCountsSmall), + SelectMulti(ValueCounts), // > Variable Instructions // See diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index a56dc9f1..e9d27294 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -208,16 +208,6 @@ pub struct FuncType { #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct ValueCounts { - pub c32: u32, - pub c64: u32, - pub c128: u32, - pub cref: u32, -} - -#[derive(Default, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "debug", derive(Debug))] -#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] -pub struct ValueCountsSmall { pub c32: u16, pub c64: u16, pub c128: u16, @@ -240,30 +230,14 @@ impl<'a, T: IntoIterator> From for ValueCounts { } } -impl<'a, T: IntoIterator> From for ValueCountsSmall { - #[inline] - fn from(types: T) -> Self { - let mut counts = Self::default(); - for ty in types { - match ty { - ValType::I32 | ValType::F32 => counts.c32 += 1, - ValType::I64 | ValType::F64 => counts.c64 += 1, - ValType::V128 => counts.c128 += 1, - ValType::RefExtern | ValType::RefFunc => counts.cref += 1, - } - } - counts - } -} - #[derive(Clone, PartialEq, Default)] #[cfg_attr(feature = "debug", derive(Debug))] #[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))] pub struct WasmFunction { pub instructions: ArcSlice, pub data: WasmFunctionData, - pub locals: ValueCountsSmall, - pub params: ValueCountsSmall, + pub locals: ValueCounts, + pub params: ValueCounts, pub ty: FuncType, } @@ -298,6 +272,12 @@ impl Deref for ArcSlice { } } +impl FromIterator for ArcSlice { + fn from_iter>(iter: I) -> Self { + Self(Arc::from_iter(iter)) + } +} + #[cfg(feature = "archive")] impl serde::Serialize for ArcSlice { fn serialize(&self, serializer: S) -> Result { From 63e33a27a1cccaa96c85c4d5620d9f8a915ae58b Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 21:52:54 +0200 Subject: [PATCH 41/47] chore: refactor simd code in preparation for x86 intrinsics Signed-off-by: Henry --- crates/tinywasm/Cargo.toml | 5 + crates/tinywasm/src/interpreter/mod.rs | 4 +- .../src/interpreter/simd/instructions.rs | 1324 +++++++++++++++ .../tinywasm/src/interpreter/simd/macros.rs | 347 ++++ crates/tinywasm/src/interpreter/simd/mod.rs | 122 ++ crates/tinywasm/src/interpreter/simd/utils.rs | 113 ++ crates/tinywasm/src/interpreter/value128.rs | 1421 ----------------- crates/tinywasm/src/interpreter/values.rs | 2 +- crates/tinywasm/src/lib.rs | 2 +- 9 files changed, 1915 insertions(+), 1425 deletions(-) create mode 100644 crates/tinywasm/src/interpreter/simd/instructions.rs create mode 100644 crates/tinywasm/src/interpreter/simd/macros.rs create mode 100644 crates/tinywasm/src/interpreter/simd/mod.rs create mode 100644 crates/tinywasm/src/interpreter/simd/utils.rs delete mode 100644 crates/tinywasm/src/interpreter/value128.rs diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 00a47cfc..1850680b 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -51,6 +51,11 @@ canonicalize_nans=[] # derive Debug for runtime/types structs debug=["tinywasm-types/debug"] +# enable x86-specific SIMD intrinsics in Value128 +# note: for x86 backend selection, compile with x86-64-v3 target features +# (for example: `RUSTFLAGS="-C target-cpu=x86-64-v3"`) +simd-x86=[] + [[test]] name="test-wasm-1" harness=false diff --git a/crates/tinywasm/src/interpreter/mod.rs b/crates/tinywasm/src/interpreter/mod.rs index 17481755..ab94d473 100644 --- a/crates/tinywasm/src/interpreter/mod.rs +++ b/crates/tinywasm/src/interpreter/mod.rs @@ -1,14 +1,14 @@ pub(crate) mod executor; pub(crate) mod num_helpers; +pub(crate) mod simd; pub(crate) mod stack; -pub(crate) mod value128; pub(crate) mod values; #[cfg(not(feature = "std"))] mod no_std_floats; use crate::{Result, Store, interpreter::stack::CallFrame}; -pub(crate) use value128::*; +pub(crate) use simd::*; pub(crate) use values::*; #[derive(Clone)] diff --git a/crates/tinywasm/src/interpreter/simd/instructions.rs b/crates/tinywasm/src/interpreter/simd/instructions.rs new file mode 100644 index 00000000..8b661d02 --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/instructions.rs @@ -0,0 +1,1324 @@ +use super::super::num_helpers::TinywasmFloatExt; +use super::Value128; +use super::utils::*; + +#[cfg(not(feature = "std"))] +use super::super::no_std_floats::NoStdFloatExt; +#[cfg(target_arch = "wasm32")] +use core::arch::wasm32 as wasm; +#[cfg(target_arch = "wasm64")] +use core::arch::wasm64 as wasm; + +impl Value128 { + #[doc(alias = "v128.any_true")] + pub fn v128_any_true(self) -> bool { + simd_impl! { + wasm => { wasm::v128_any_true(self.to_wasm_v128()) } + generic => { self.0 != 0 } + } + } + + #[doc(alias = "v128.not")] + pub fn v128_not(self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_not(self.to_wasm_v128())) } + generic => { Self(!self.0) } + } + } + + #[doc(alias = "v128.and")] + pub fn v128_and(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_and(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 & rhs.0) } + } + } + + #[doc(alias = "v128.andnot")] + pub fn v128_andnot(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_andnot(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 & !rhs.0) } + } + } + + #[doc(alias = "v128.or")] + pub fn v128_or(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_or(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 | rhs.0) } + } + } + + #[doc(alias = "v128.xor")] + pub fn v128_xor(self, rhs: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_xor(self.to_wasm_v128(), rhs.to_wasm_v128())) } + generic => { Self(self.0 ^ rhs.0) } + } + } + + #[doc(alias = "v128.bitselect")] + pub fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::v128_bitselect(v1.to_wasm_v128(), v2.to_wasm_v128(), c.to_wasm_v128())) } + generic => { Self((v1.0 & c.0) | (v2.0 & !c.0)) } + } + } + + #[doc(alias = "v128.load8x8_s")] + pub const fn v128_load8x8_s(src: [u8; 8]) -> Self { + Self::from_i16x8([ + src[0] as i8 as i16, + src[1] as i8 as i16, + src[2] as i8 as i16, + src[3] as i8 as i16, + src[4] as i8 as i16, + src[5] as i8 as i16, + src[6] as i8 as i16, + src[7] as i8 as i16, + ]) + } + + #[doc(alias = "v128.load8x8_u")] + pub const fn v128_load8x8_u(src: [u8; 8]) -> Self { + Self::from_u16x8([ + src[0] as u16, + src[1] as u16, + src[2] as u16, + src[3] as u16, + src[4] as u16, + src[5] as u16, + src[6] as u16, + src[7] as u16, + ]) + } + + #[doc(alias = "v128.load16x4_s")] + pub const fn v128_load16x4_s(src: [u8; 8]) -> Self { + Self::from_i32x4([ + i16::from_le_bytes([src[0], src[1]]) as i32, + i16::from_le_bytes([src[2], src[3]]) as i32, + i16::from_le_bytes([src[4], src[5]]) as i32, + i16::from_le_bytes([src[6], src[7]]) as i32, + ]) + } + + #[doc(alias = "v128.load16x4_u")] + pub const fn v128_load16x4_u(src: [u8; 8]) -> Self { + Self::from_u32x4([ + u16::from_le_bytes([src[0], src[1]]) as u32, + u16::from_le_bytes([src[2], src[3]]) as u32, + u16::from_le_bytes([src[4], src[5]]) as u32, + u16::from_le_bytes([src[6], src[7]]) as u32, + ]) + } + + #[doc(alias = "v128.load32x2_s")] + pub const fn v128_load32x2_s(src: [u8; 8]) -> Self { + Self::from_i64x2([ + i32::from_le_bytes([src[0], src[1], src[2], src[3]]) as i64, + i32::from_le_bytes([src[4], src[5], src[6], src[7]]) as i64, + ]) + } + + #[doc(alias = "v128.load32x2_u")] + pub const fn v128_load32x2_u(src: [u8; 8]) -> Self { + Self::from_u64x2([ + u32::from_le_bytes([src[0], src[1], src[2], src[3]]) as u64, + u32::from_le_bytes([src[4], src[5], src[6], src[7]]) as u64, + ]) + } + + #[doc(alias = "i8x16.swizzle")] + pub fn i8x16_swizzle(self, s: Self) -> Self { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); + + let a = self.to_le_bytes(); + let idx = s.to_le_bytes(); + let mut out = [0u8; 16]; + let mut i = 0; + while i < 16 { + let j = idx[i]; + let lane = a[(j & 0x0f) as usize]; + out[i] = if j < 16 { lane } else { 0 }; + i += 1; + } + Self::from_le_bytes(out) + } + + #[doc(alias = "i8x16.relaxed_swizzle")] + pub fn i8x16_relaxed_swizzle(self, s: Self) -> Self { + self.i8x16_swizzle(s) + } + + #[doc(alias = "i8x16.shuffle")] + pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { + let mut src = [0u8; 32]; + src[..16].copy_from_slice(&a.to_le_bytes()); + src[16..].copy_from_slice(&b.to_le_bytes()); + let mut out = [0u8; 16]; + for i in 0..16 { + out[i] = src[(idx[i] & 31) as usize]; + } + Self::from_le_bytes(out) + } + + #[doc(alias = "i8x16.splat")] + pub fn splat_i8(src: i8) -> Self { + Self::from_le_bytes([src as u8; 16]) + } + + #[doc(alias = "i8x16.replace_lane")] + pub fn i8x16_replace_lane(self, lane: u8, value: i8) -> Self { + self.replace_lane_bytes::<1>(lane, [value as u8], 16) + } + + #[doc(alias = "i16x8.replace_lane")] + pub fn i16x8_replace_lane(self, lane: u8, value: i16) -> Self { + self.replace_lane_bytes::<2>(lane, value.to_le_bytes(), 8) + } + + #[doc(alias = "i32x4.replace_lane")] + pub fn i32x4_replace_lane(self, lane: u8, value: i32) -> Self { + self.replace_lane_bytes::<4>(lane, value.to_le_bytes(), 4) + } + + #[doc(alias = "i64x2.replace_lane")] + pub fn i64x2_replace_lane(self, lane: u8, value: i64) -> Self { + self.replace_lane_bytes::<8>(lane, value.to_le_bytes(), 2) + } + + #[doc(alias = "f32x4.replace_lane")] + pub fn f32x4_replace_lane(self, lane: u8, value: f32) -> Self { + self.replace_lane_bytes::<4>(lane, value.to_bits().to_le_bytes(), 4) + } + + #[doc(alias = "f64x2.replace_lane")] + pub fn f64x2_replace_lane(self, lane: u8, value: f64) -> Self { + self.replace_lane_bytes::<8>(lane, value.to_bits().to_le_bytes(), 2) + } + + #[doc(alias = "i8x16.all_true")] + pub fn i8x16_all_true(self) -> bool { + for byte in self.to_le_bytes() { + if byte == 0 { + return false; + } + } + true + } + + #[doc(alias = "i16x8.all_true")] + pub fn i16x8_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(2) { + if u16::from_le_bytes([lane[0], lane[1]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i32x4.all_true")] + pub fn i32x4_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(4) { + if u32::from_le_bytes([lane[0], lane[1], lane[2], lane[3]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i64x2.all_true")] + pub fn i64x2_all_true(self) -> bool { + let bytes = self.to_le_bytes(); + for lane in bytes.chunks_exact(8) { + if u64::from_le_bytes([lane[0], lane[1], lane[2], lane[3], lane[4], lane[5], lane[6], lane[7]]) == 0 { + return false; + } + } + true + } + + #[doc(alias = "i8x16.bitmask")] + pub fn i8x16_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, byte) in bytes.into_iter().enumerate() { + if (byte & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i16x8.bitmask")] + pub fn i16x8_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, lane) in bytes.chunks_exact(2).enumerate() { + if (lane[1] & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i32x4.bitmask")] + pub fn i32x4_bitmask(self) -> u32 { + let bytes = self.to_le_bytes(); + let mut mask = 0u32; + for (i, lane) in bytes.chunks_exact(4).enumerate() { + if (lane[3] & 0x80) != 0 { + mask |= 1u32 << i; + } + } + mask + } + + #[doc(alias = "i64x2.bitmask")] + pub fn i64x2_bitmask(self) -> u32 { + let x = u128::from_le_bytes(self.to_le_bytes()); + (((x >> 63) & 1) as u32) | ((((x >> 127) & 1) as u32) << 1) + } + + #[doc(alias = "i8x16.popcnt")] + pub fn i8x16_popcnt(self) -> Self { + let lanes = self.to_le_bytes(); + let mut out = [0u8; 16]; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane.count_ones() as u8; + } + Self::from_le_bytes(out) + } + + #[doc(alias = "i8x16.shl")] + pub fn i8x16_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i8, 16, as_i8x16, from_i8x16, 7) + } + #[doc(alias = "i16x8.shl")] + pub fn i16x8_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i16, 8, as_i16x8, from_i16x8, 15) + } + #[doc(alias = "i32x4.shl")] + pub fn i32x4_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i32, 4, as_i32x4, from_i32x4, 31) + } + #[doc(alias = "i64x2.shl")] + pub fn i64x2_shl(self, shift: u32) -> Self { + simd_shift_left!(self, shift, i64, 2, as_i64x2, from_i64x2, 63) + } + + #[doc(alias = "i8x16.shr_s")] + pub fn i8x16_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i8, 16, as_i8x16, from_i8x16, 7) + } + #[doc(alias = "i16x8.shr_s")] + pub fn i16x8_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i16, 8, as_i16x8, from_i16x8, 15) + } + #[doc(alias = "i32x4.shr_s")] + pub fn i32x4_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i32, 4, as_i32x4, from_i32x4, 31) + } + #[doc(alias = "i64x2.shr_s")] + pub fn i64x2_shr_s(self, shift: u32) -> Self { + simd_shift_right!(self, shift, i64, 2, as_i64x2, from_i64x2, 63) + } + + #[doc(alias = "i8x16.shr_u")] + pub fn i8x16_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u8, 16, as_u8x16, from_u8x16, 7) + } + #[doc(alias = "i16x8.shr_u")] + pub fn i16x8_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u16, 8, as_u16x8, from_u16x8, 15) + } + #[doc(alias = "i32x4.shr_u")] + pub fn i32x4_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u32, 4, as_u32x4, from_u32x4, 31) + } + #[doc(alias = "i64x2.shr_u")] + pub fn i64x2_shr_u(self, shift: u32) -> Self { + simd_shift_right!(self, shift, u64, 2, as_u64x2, from_u64x2, 63) + } + + #[doc(alias = "i8x16.add")] + pub fn i8x16_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i8x16_add, i8, 16, as_i8x16, from_i8x16, wrapping_add) + } + #[doc(alias = "i16x8.add")] + pub fn i16x8_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i16x8_add, i16, 8, as_i16x8, from_i16x8, wrapping_add) + } + #[doc(alias = "i32x4.add")] + pub fn i32x4_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i32x4_add, i32, 4, as_i32x4, from_i32x4, wrapping_add) + } + #[doc(alias = "i64x2.add")] + pub fn i64x2_add(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i64x2_add, i64, 2, as_i64x2, from_i64x2, wrapping_add) + } + #[doc(alias = "i8x16.sub")] + pub fn i8x16_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i8x16_sub, i8, 16, as_i8x16, from_i8x16, wrapping_sub) + } + #[doc(alias = "i16x8.sub")] + pub fn i16x8_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i16x8_sub, i16, 8, as_i16x8, from_i16x8, wrapping_sub) + } + #[doc(alias = "i32x4.sub")] + pub fn i32x4_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i32x4_sub, i32, 4, as_i32x4, from_i32x4, wrapping_sub) + } + #[doc(alias = "i64x2.sub")] + pub fn i64x2_sub(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i64x2_sub, i64, 2, as_i64x2, from_i64x2, wrapping_sub) + } + #[doc(alias = "i16x8.mul")] + pub fn i16x8_mul(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i16x8_mul, i16, 8, as_i16x8, from_i16x8, wrapping_mul) + } + #[doc(alias = "i32x4.mul")] + pub fn i32x4_mul(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i32x4_mul, i32, 4, as_i32x4, from_i32x4, wrapping_mul) + } + #[doc(alias = "i64x2.mul")] + pub fn i64x2_mul(self, rhs: Self) -> Self { + simd_wrapping_binop!(self, rhs, i64x2_mul, i64, 2, as_i64x2, from_i64x2, wrapping_mul) + } + + #[doc(alias = "i8x16.add_sat_s")] + pub fn i8x16_add_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i8x16_add_sat, i8, 16, as_i8x16, from_i8x16, saturating_add) + } + #[doc(alias = "i16x8.add_sat_s")] + pub fn i16x8_add_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i16x8_add_sat, i16, 8, as_i16x8, from_i16x8, saturating_add) + } + #[doc(alias = "i8x16.add_sat_u")] + pub fn i8x16_add_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u8x16_add_sat, u8, 16, as_u8x16, from_u8x16, saturating_add) + } + #[doc(alias = "i16x8.add_sat_u")] + pub fn i16x8_add_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u16x8_add_sat, u16, 8, as_u16x8, from_u16x8, saturating_add) + } + #[doc(alias = "i8x16.sub_sat_s")] + pub fn i8x16_sub_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i8x16_sub_sat, i8, 16, as_i8x16, from_i8x16, saturating_sub) + } + #[doc(alias = "i16x8.sub_sat_s")] + pub fn i16x8_sub_sat_s(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, i16x8_sub_sat, i16, 8, as_i16x8, from_i16x8, saturating_sub) + } + #[doc(alias = "i8x16.sub_sat_u")] + pub fn i8x16_sub_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u8x16_sub_sat, u8, 16, as_u8x16, from_u8x16, saturating_sub) + } + #[doc(alias = "i16x8.sub_sat_u")] + pub fn i16x8_sub_sat_u(self, rhs: Self) -> Self { + simd_sat_binop!(self, rhs, u16x8_sub_sat, u16, 8, as_u16x8, from_u16x8, saturating_sub) + } + + #[doc(alias = "i8x16.avgr_u")] + pub fn i8x16_avgr_u(self, rhs: Self) -> Self { + simd_avgr_u!(self, rhs, u8x16_avgr, u8, u16, 16, as_u8x16, from_u8x16) + } + #[doc(alias = "i16x8.avgr_u")] + pub fn i16x8_avgr_u(self, rhs: Self) -> Self { + simd_avgr_u!(self, rhs, u16x8_avgr, u16, u32, 8, as_u16x8, from_u16x8) + } + + #[doc(alias = "i8x16.narrow_i16x8_s")] + pub fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0i8; 16]; + let (lo, hi) = out.split_at_mut(8); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i16_to_i8(a_lane); + *dst_hi = saturate_i16_to_i8(b_lane); + } + Self::from_i8x16(out) + } + + #[doc(alias = "i8x16.narrow_i16x8_u")] + pub fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { + let av = a.as_i16x8(); + let bv = b.as_i16x8(); + let mut out = [0u8; 16]; + let (lo, hi) = out.split_at_mut(8); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i16_to_u8(a_lane); + *dst_hi = saturate_i16_to_u8(b_lane); + } + Self::from_u8x16(out) + } + + #[doc(alias = "i16x8.narrow_i32x4_s")] + pub fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0i16; 8]; + let (lo, hi) = out.split_at_mut(4); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i32_to_i16(a_lane); + *dst_hi = saturate_i32_to_i16(b_lane); + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.narrow_i32x4_u")] + pub fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { + let av = a.as_i32x4(); + let bv = b.as_i32x4(); + let mut out = [0u16; 8]; + let (lo, hi) = out.split_at_mut(4); + for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { + *dst_lo = saturate_i32_to_u16(a_lane); + *dst_hi = saturate_i32_to_u16(b_lane); + } + Self::from_u16x8(out) + } + + #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] + pub fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { + let lanes = self.as_i8x16(); + let mut out = [0i16; 8]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as i16 + pair[1] as i16; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] + pub fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { + let lanes = self.as_u8x16(); + let mut out = [0u16; 8]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as u16 + pair[1] as u16; + } + Self::from_u16x8(out) + } + + #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] + pub fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { + let lanes = self.as_i16x8(); + let mut out = [0i32; 4]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as i32 + pair[1] as i32; + } + Self::from_i32x4(out) + } + + #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] + pub fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { + let lanes = self.as_u16x8(); + let mut out = [0u32; 4]; + for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { + *dst = pair[0] as u32 + pair[1] as u32; + } + Self::from_u32x4(out) + } + + #[doc(alias = "i16x8.extend_low_i8x16_s")] + pub fn i16x8_extend_low_i8x16_s(self) -> Self { + simd_extend_cast!(self, as_i8x16, from_i16x8, i16, 8, 0) + } + #[doc(alias = "i16x8.extend_low_i8x16_u")] + pub fn i16x8_extend_low_i8x16_u(self) -> Self { + simd_extend_cast!(self, as_u8x16, from_u16x8, u16, 8, 0) + } + #[doc(alias = "i16x8.extend_high_i8x16_s")] + pub fn i16x8_extend_high_i8x16_s(self) -> Self { + simd_extend_cast!(self, as_i8x16, from_i16x8, i16, 8, 8) + } + #[doc(alias = "i16x8.extend_high_i8x16_u")] + pub fn i16x8_extend_high_i8x16_u(self) -> Self { + simd_extend_cast!(self, as_u8x16, from_u16x8, u16, 8, 8) + } + #[doc(alias = "i32x4.extend_low_i16x8_s")] + pub fn i32x4_extend_low_i16x8_s(self) -> Self { + simd_extend_cast!(self, as_i16x8, from_i32x4, i32, 4, 0) + } + #[doc(alias = "i32x4.extend_low_i16x8_u")] + pub fn i32x4_extend_low_i16x8_u(self) -> Self { + simd_extend_cast!(self, as_u16x8, from_u32x4, u32, 4, 0) + } + #[doc(alias = "i32x4.extend_high_i16x8_s")] + pub fn i32x4_extend_high_i16x8_s(self) -> Self { + simd_extend_cast!(self, as_i16x8, from_i32x4, i32, 4, 4) + } + #[doc(alias = "i32x4.extend_high_i16x8_u")] + pub fn i32x4_extend_high_i16x8_u(self) -> Self { + simd_extend_cast!(self, as_u16x8, from_u32x4, u32, 4, 4) + } + #[doc(alias = "i64x2.extend_low_i32x4_s")] + pub fn i64x2_extend_low_i32x4_s(self) -> Self { + simd_extend_cast!(self, as_i32x4, from_i64x2, i64, 2, 0) + } + #[doc(alias = "i64x2.extend_low_i32x4_u")] + pub fn i64x2_extend_low_i32x4_u(self) -> Self { + simd_extend_cast!(self, as_u32x4, from_u64x2, u64, 2, 0) + } + #[doc(alias = "i64x2.extend_high_i32x4_s")] + pub fn i64x2_extend_high_i32x4_s(self) -> Self { + simd_extend_cast!(self, as_i32x4, from_i64x2, i64, 2, 2) + } + #[doc(alias = "i64x2.extend_high_i32x4_u")] + pub fn i64x2_extend_high_i32x4_u(self) -> Self { + simd_extend_cast!(self, as_u32x4, from_u64x2, u64, 2, 2) + } + + #[doc(alias = "i16x8.extmul_low_i8x16_s")] + pub fn i16x8_extmul_low_i8x16_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i8x16, from_i16x8, i16, 8, 0) + } + #[doc(alias = "i16x8.extmul_low_i8x16_u")] + pub fn i16x8_extmul_low_i8x16_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u8x16, from_u16x8, u16, 8, 0) + } + #[doc(alias = "i16x8.extmul_high_i8x16_s")] + pub fn i16x8_extmul_high_i8x16_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i8x16, from_i16x8, i16, 8, 8) + } + #[doc(alias = "i16x8.extmul_high_i8x16_u")] + pub fn i16x8_extmul_high_i8x16_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u8x16, from_u16x8, u16, 8, 8) + } + #[doc(alias = "i32x4.extmul_low_i16x8_s")] + pub fn i32x4_extmul_low_i16x8_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i16x8, from_i32x4, i32, 4, 0) + } + #[doc(alias = "i32x4.extmul_low_i16x8_u")] + pub fn i32x4_extmul_low_i16x8_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u16x8, from_u32x4, u32, 4, 0) + } + #[doc(alias = "i32x4.extmul_high_i16x8_s")] + pub fn i32x4_extmul_high_i16x8_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i16x8, from_i32x4, i32, 4, 4) + } + #[doc(alias = "i32x4.extmul_high_i16x8_u")] + pub fn i32x4_extmul_high_i16x8_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u16x8, from_u32x4, u32, 4, 4) + } + #[doc(alias = "i64x2.extmul_low_i32x4_s")] + pub fn i64x2_extmul_low_i32x4_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i32x4, from_i64x2, i64, 2, 0) + } + #[doc(alias = "i64x2.extmul_low_i32x4_u")] + pub fn i64x2_extmul_low_i32x4_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u32x4, from_u64x2, u64, 2, 0) + } + #[doc(alias = "i64x2.extmul_high_i32x4_s")] + pub fn i64x2_extmul_high_i32x4_s(self, rhs: Self) -> Self { + simd_extmul_signed!(self, rhs, as_i32x4, from_i64x2, i64, 2, 2) + } + #[doc(alias = "i64x2.extmul_high_i32x4_u")] + pub fn i64x2_extmul_high_i32x4_u(self, rhs: Self) -> Self { + simd_extmul_unsigned!(self, rhs, as_u32x4, from_u64x2, u64, 2, 2) + } + + #[doc(alias = "i16x8.q15mulr_sat_s")] + pub fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i16; 8]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + let r = ((lhs as i32 * rhs as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding + *dst = if r > i16::MAX as i32 { + i16::MAX + } else if r < i16::MIN as i32 { + i16::MIN + } else { + r as i16 + }; + } + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.dot_i16x8_s")] + pub fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { + let a = self.as_i16x8(); + let b = rhs.as_i16x8(); + let mut out = [0i32; 4]; + for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { + *dst = (a_pair[0] as i32) + .wrapping_mul(b_pair[0] as i32) + .wrapping_add((a_pair[1] as i32).wrapping_mul(b_pair[1] as i32)); + } + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.relaxed_laneselect")] + pub fn i8x16_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i16x8.relaxed_laneselect")] + pub fn i16x8_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i32x4.relaxed_laneselect")] + pub fn i32x4_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i64x2.relaxed_laneselect")] + pub fn i64x2_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { + Self::v128_bitselect(v1, v2, c) + } + + #[doc(alias = "i16x8.relaxed_q15mulr_s")] + pub fn i16x8_relaxed_q15mulr_s(self, rhs: Self) -> Self { + self.i16x8_q15mulr_sat_s(rhs) + } + + #[doc(alias = "i16x8.relaxed_dot_i8x16_i7x16_s")] + pub fn i16x8_relaxed_dot_i8x16_i7x16_s(self, rhs: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let mut out = [0i16; 8]; + + for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { + let prod0 = (a_pair[0] as i16) * (b_pair[0] as i16); + let prod1 = (a_pair[1] as i16) * (b_pair[1] as i16); + *dst = prod0.wrapping_add(prod1); + } + + Self::from_i16x8(out) + } + + #[doc(alias = "i32x4.relaxed_dot_i8x16_i7x16_add_s")] + pub fn i32x4_relaxed_dot_i8x16_i7x16_add_s(self, rhs: Self, acc: Self) -> Self { + let a = self.as_i8x16(); + let b = rhs.as_i8x16(); + let c = acc.as_i32x4(); + let mut out = [0i32; 4]; + + for (i, dst) in out.iter_mut().enumerate() { + let base = i * 4; + let mut sum = 0i32; + for j in 0..4 { + sum = sum.wrapping_add((a[base + j] as i32).wrapping_mul(b[base + j] as i32)); + } + *dst = sum.wrapping_add(c[i]); + } + + Self::from_i32x4(out) + } + + #[doc(alias = "i8x16.eq")] + pub fn i8x16_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_eq, i8, 16, as_i8x16, from_i8x16, ==) + } + #[doc(alias = "i16x8.eq")] + pub fn i16x8_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_eq, i16, 8, as_i16x8, from_i16x8, ==) + } + #[doc(alias = "i32x4.eq")] + pub fn i32x4_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_eq, i32, 4, as_i32x4, from_i32x4, ==) + } + #[doc(alias = "i64x2.eq")] + pub fn i64x2_eq(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_eq, i64, 2, as_i64x2, from_i64x2, ==) + } + #[doc(alias = "i8x16.ne")] + pub fn i8x16_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_ne, i8, 16, as_i8x16, from_i8x16, !=) + } + #[doc(alias = "i16x8.ne")] + pub fn i16x8_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_ne, i16, 8, as_i16x8, from_i16x8, !=) + } + #[doc(alias = "i32x4.ne")] + pub fn i32x4_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_ne, i32, 4, as_i32x4, from_i32x4, !=) + } + #[doc(alias = "i64x2.ne")] + pub fn i64x2_ne(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_ne, i64, 2, as_i64x2, from_i64x2, !=) + } + #[doc(alias = "i8x16.lt_s")] + pub fn i8x16_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_lt, i8, 16, as_i8x16, from_i8x16, <) + } + #[doc(alias = "i16x8.lt_s")] + pub fn i16x8_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_lt, i16, 8, as_i16x8, from_i16x8, <) + } + #[doc(alias = "i32x4.lt_s")] + pub fn i32x4_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_lt, i32, 4, as_i32x4, from_i32x4, <) + } + #[doc(alias = "i64x2.lt_s")] + pub fn i64x2_lt_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_lt, i64, 2, as_i64x2, from_i64x2, <) + } + #[doc(alias = "i8x16.lt_u")] + pub fn i8x16_lt_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u8x16_lt, i8, 16, as_u8x16, from_i8x16, <) + } + #[doc(alias = "i16x8.lt_u")] + pub fn i16x8_lt_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u16x8_lt, i16, 8, as_u16x8, from_i16x8, <) + } + #[doc(alias = "i32x4.lt_u")] + pub fn i32x4_lt_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u32x4_lt, i32, 4, as_u32x4, from_i32x4, <) + } + + #[doc(alias = "i8x16.gt_s")] + pub fn i8x16_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_lt_s) + } + #[doc(alias = "i16x8.gt_s")] + pub fn i16x8_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_lt_s) + } + #[doc(alias = "i32x4.gt_s")] + pub fn i32x4_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_lt_s) + } + #[doc(alias = "i64x2.gt_s")] + pub fn i64x2_gt_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i64x2_lt_s) + } + #[doc(alias = "i8x16.gt_u")] + pub fn i8x16_gt_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_lt_u) + } + #[doc(alias = "i16x8.gt_u")] + pub fn i16x8_gt_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_lt_u) + } + #[doc(alias = "i32x4.gt_u")] + pub fn i32x4_gt_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_lt_u) + } + #[doc(alias = "i8x16.le_s")] + pub fn i8x16_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_ge_s) + } + #[doc(alias = "i16x8.le_s")] + pub fn i16x8_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_ge_s) + } + #[doc(alias = "i32x4.le_s")] + pub fn i32x4_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_ge_s) + } + #[doc(alias = "i64x2.le_s")] + pub fn i64x2_le_s(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i64x2_ge_s) + } + #[doc(alias = "i8x16.le_u")] + pub fn i8x16_le_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i8x16_ge_u) + } + #[doc(alias = "i16x8.le_u")] + pub fn i16x8_le_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i16x8_ge_u) + } + #[doc(alias = "i32x4.le_u")] + pub fn i32x4_le_u(self, rhs: Self) -> Self { + simd_cmp_delegate!(self, rhs, i32x4_ge_u) + } + + #[doc(alias = "i8x16.ge_s")] + pub fn i8x16_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i8x16_ge, i8, 16, as_i8x16, from_i8x16, >=) + } + #[doc(alias = "i16x8.ge_s")] + pub fn i16x8_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i16x8_ge, i16, 8, as_i16x8, from_i16x8, >=) + } + #[doc(alias = "i32x4.ge_s")] + pub fn i32x4_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i32x4_ge, i32, 4, as_i32x4, from_i32x4, >=) + } + #[doc(alias = "i64x2.ge_s")] + pub fn i64x2_ge_s(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, i64x2_ge, i64, 2, as_i64x2, from_i64x2, >=) + } + #[doc(alias = "i8x16.ge_u")] + pub fn i8x16_ge_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u8x16_ge, i8, 16, as_u8x16, from_i8x16, >=) + } + #[doc(alias = "i16x8.ge_u")] + pub fn i16x8_ge_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u16x8_ge, i16, 8, as_u16x8, from_i16x8, >=) + } + #[doc(alias = "i32x4.ge_u")] + pub fn i32x4_ge_u(self, rhs: Self) -> Self { + simd_cmp_mask!(self, rhs, u32x4_ge, i32, 4, as_u32x4, from_i32x4, >=) + } + + #[doc(alias = "i8x16.abs")] + pub fn i8x16_abs(self) -> Self { + simd_abs_const!(self, i8, 16, as_i8x16, from_i8x16) + } + #[doc(alias = "i16x8.abs")] + pub fn i16x8_abs(self) -> Self { + simd_abs_const!(self, i16, 8, as_i16x8, from_i16x8) + } + #[doc(alias = "i32x4.abs")] + pub fn i32x4_abs(self) -> Self { + simd_abs_const!(self, i32, 4, as_i32x4, from_i32x4) + } + #[doc(alias = "i64x2.abs")] + pub fn i64x2_abs(self) -> Self { + simd_abs_const!(self, i64, 2, as_i64x2, from_i64x2) + } + + #[doc(alias = "i8x16.neg")] + pub fn i8x16_neg(self) -> Self { + simd_neg!(self, i8x16_neg, i8, 16, as_i8x16, from_i8x16) + } + #[doc(alias = "i16x8.neg")] + pub fn i16x8_neg(self) -> Self { + simd_neg!(self, i16x8_neg, i16, 8, as_i16x8, from_i16x8) + } + #[doc(alias = "i32x4.neg")] + pub fn i32x4_neg(self) -> Self { + simd_neg!(self, i32x4_neg, i32, 4, as_i32x4, from_i32x4) + } + #[doc(alias = "i64x2.neg")] + pub fn i64x2_neg(self) -> Self { + simd_neg!(self, i64x2_neg, i64, 2, as_i64x2, from_i64x2) + } + + #[doc(alias = "i8x16.min_s")] + pub fn i8x16_min_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i8x16_min, i8, 16, as_i8x16, from_i8x16, <) + } + #[doc(alias = "i16x8.min_s")] + pub fn i16x8_min_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i16x8_min, i16, 8, as_i16x8, from_i16x8, <) + } + #[doc(alias = "i32x4.min_s")] + pub fn i32x4_min_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i32x4_min, i32, 4, as_i32x4, from_i32x4, <) + } + #[doc(alias = "i8x16.min_u")] + pub fn i8x16_min_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u8x16_min, u8, 16, as_u8x16, from_u8x16, <) + } + #[doc(alias = "i16x8.min_u")] + pub fn i16x8_min_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u16x8_min, u16, 8, as_u16x8, from_u16x8, <) + } + #[doc(alias = "i32x4.min_u")] + pub fn i32x4_min_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u32x4_min, u32, 4, as_u32x4, from_u32x4, <) + } + #[doc(alias = "i8x16.max_s")] + pub fn i8x16_max_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i8x16_max, i8, 16, as_i8x16, from_i8x16, >) + } + #[doc(alias = "i16x8.max_s")] + pub fn i16x8_max_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i16x8_max, i16, 8, as_i16x8, from_i16x8, >) + } + #[doc(alias = "i32x4.max_s")] + pub fn i32x4_max_s(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, i32x4_max, i32, 4, as_i32x4, from_i32x4, >) + } + #[doc(alias = "i8x16.max_u")] + pub fn i8x16_max_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u8x16_max, u8, 16, as_u8x16, from_u8x16, >) + } + #[doc(alias = "i16x8.max_u")] + pub fn i16x8_max_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u16x8_max, u16, 8, as_u16x8, from_u16x8, >) + } + #[doc(alias = "i32x4.max_u")] + pub fn i32x4_max_u(self, rhs: Self) -> Self { + simd_minmax!(self, rhs, u32x4_max, u32, 4, as_u32x4, from_u32x4, >) + } + + #[doc(alias = "f32x4.eq")] + pub fn f32x4_eq(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, ==) + } + #[doc(alias = "f64x2.eq")] + pub fn f64x2_eq(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, ==) + } + #[doc(alias = "f32x4.ne")] + pub fn f32x4_ne(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, !=) + } + #[doc(alias = "f64x2.ne")] + pub fn f64x2_ne(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, !=) + } + #[doc(alias = "f32x4.lt")] + pub fn f32x4_lt(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, <) + } + #[doc(alias = "f64x2.lt")] + pub fn f64x2_lt(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, <) + } + + #[doc(alias = "f32x4.gt")] + pub fn f32x4_gt(self, rhs: Self) -> Self { + rhs.f32x4_lt(self) + } + + #[doc(alias = "f64x2.gt")] + pub fn f64x2_gt(self, rhs: Self) -> Self { + rhs.f64x2_lt(self) + } + + #[doc(alias = "f32x4.le")] + pub fn f32x4_le(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, <=) + } + #[doc(alias = "f64x2.le")] + pub fn f64x2_le(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, <=) + } + #[doc(alias = "f32x4.ge")] + pub fn f32x4_ge(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i32, 4, as_f32x4, from_i32x4, >=) + } + #[doc(alias = "f64x2.ge")] + pub fn f64x2_ge(self, rhs: Self) -> Self { + simd_cmp_mask_const!(self, rhs, i64, 2, as_f64x2, from_i64x2, >=) + } + + #[doc(alias = "f32x4.ceil")] + pub fn f32x4_ceil(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.ceil())) + } + #[doc(alias = "f64x2.ceil")] + pub fn f64x2_ceil(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.ceil())) + } + #[doc(alias = "f32x4.floor")] + pub fn f32x4_floor(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.floor())) + } + #[doc(alias = "f64x2.floor")] + pub fn f64x2_floor(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.floor())) + } + #[doc(alias = "f32x4.trunc")] + pub fn f32x4_trunc(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.trunc())) + } + #[doc(alias = "f64x2.trunc")] + pub fn f64x2_trunc(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.trunc())) + } + #[doc(alias = "f32x4.nearest")] + pub fn f32x4_nearest(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(TinywasmFloatExt::tw_nearest(x))) + } + #[doc(alias = "f64x2.nearest")] + pub fn f64x2_nearest(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(TinywasmFloatExt::tw_nearest(x))) + } + #[doc(alias = "f32x4.abs")] + pub fn f32x4_abs(self) -> Self { + simd_float_unary!(self, map_f32x4, f32::abs) + } + #[doc(alias = "f64x2.abs")] + pub fn f64x2_abs(self) -> Self { + simd_float_unary!(self, map_f64x2, f64::abs) + } + #[doc(alias = "f32x4.neg")] + pub fn f32x4_neg(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| -x) + } + #[doc(alias = "f64x2.neg")] + pub fn f64x2_neg(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| -x) + } + #[doc(alias = "f32x4.sqrt")] + pub fn f32x4_sqrt(self) -> Self { + simd_float_unary!(self, map_f32x4, |x| canonicalize_simd_f32_nan(x.sqrt())) + } + #[doc(alias = "f64x2.sqrt")] + pub fn f64x2_sqrt(self) -> Self { + simd_float_unary!(self, map_f64x2, |x| canonicalize_simd_f64_nan(x.sqrt())) + } + + #[doc(alias = "f32x4.add")] + pub fn f32x4_add(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a + b)) + } + #[doc(alias = "f64x2.add")] + pub fn f64x2_add(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a + b)) + } + #[doc(alias = "f32x4.sub")] + pub fn f32x4_sub(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a - b)) + } + #[doc(alias = "f64x2.sub")] + pub fn f64x2_sub(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a - b)) + } + #[doc(alias = "f32x4.mul")] + pub fn f32x4_mul(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a * b)) + } + #[doc(alias = "f64x2.mul")] + pub fn f64x2_mul(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a * b)) + } + #[doc(alias = "f32x4.div")] + pub fn f32x4_div(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| canonicalize_simd_f32_nan(a / b)) + } + #[doc(alias = "f64x2.div")] + pub fn f64x2_div(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| canonicalize_simd_f64_nan(a / b)) + } + #[doc(alias = "f32x4.min")] + pub fn f32x4_min(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, TinywasmFloatExt::tw_minimum) + } + #[doc(alias = "f64x2.min")] + pub fn f64x2_min(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, TinywasmFloatExt::tw_minimum) + } + #[doc(alias = "f32x4.max")] + pub fn f32x4_max(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, TinywasmFloatExt::tw_maximum) + } + #[doc(alias = "f64x2.max")] + pub fn f64x2_max(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, TinywasmFloatExt::tw_maximum) + } + #[doc(alias = "f32x4.pmin")] + pub fn f32x4_pmin(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| if b < a { b } else { a }) + } + #[doc(alias = "f64x2.pmin")] + pub fn f64x2_pmin(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| if b < a { b } else { a }) + } + #[doc(alias = "f32x4.pmax")] + pub fn f32x4_pmax(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f32x4, |a, b| if b > a { b } else { a }) + } + #[doc(alias = "f64x2.pmax")] + pub fn f64x2_pmax(self, rhs: Self) -> Self { + simd_float_binary!(self, rhs, zip_f64x2, |a, b| if b > a { b } else { a }) + } + + #[doc(alias = "f32x4.relaxed_madd")] + pub fn f32x4_relaxed_madd(self, b: Self, c: Self) -> Self { + self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(x * y)) + .zip_f32x4(c, |xy, z| canonicalize_simd_f32_nan(xy + z)) + } + + #[doc(alias = "f32x4.relaxed_nmadd")] + pub fn f32x4_relaxed_nmadd(self, b: Self, c: Self) -> Self { + self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(-(x * y))) + .zip_f32x4(c, |neg_xy, z| canonicalize_simd_f32_nan(neg_xy + z)) + } + + #[doc(alias = "f64x2.relaxed_madd")] + pub fn f64x2_relaxed_madd(self, b: Self, c: Self) -> Self { + self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(x * y)) + .zip_f64x2(c, |xy, z| canonicalize_simd_f64_nan(xy + z)) + } + + #[doc(alias = "f64x2.relaxed_nmadd")] + pub fn f64x2_relaxed_nmadd(self, b: Self, c: Self) -> Self { + self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(-(x * y))) + .zip_f64x2(c, |neg_xy, z| canonicalize_simd_f64_nan(neg_xy + z)) + } + + #[doc(alias = "f32x4.relaxed_min")] + pub fn f32x4_relaxed_min(self, rhs: Self) -> Self { + self.f32x4_min(rhs) + } + + #[doc(alias = "f64x2.relaxed_min")] + pub fn f64x2_relaxed_min(self, rhs: Self) -> Self { + self.f64x2_min(rhs) + } + + #[doc(alias = "f32x4.relaxed_max")] + pub fn f32x4_relaxed_max(self, rhs: Self) -> Self { + self.f32x4_max(rhs) + } + + #[doc(alias = "f64x2.relaxed_max")] + pub fn f64x2_relaxed_max(self, rhs: Self) -> Self { + self.f64x2_max(rhs) + } + + #[doc(alias = "i32x4.relaxed_trunc_f32x4_s")] + pub fn i32x4_relaxed_trunc_f32x4_s(self) -> Self { + self.i32x4_trunc_sat_f32x4_s() + } + + #[doc(alias = "i32x4.relaxed_trunc_f32x4_u")] + pub fn i32x4_relaxed_trunc_f32x4_u(self) -> Self { + self.i32x4_trunc_sat_f32x4_u() + } + + #[doc(alias = "i32x4.relaxed_trunc_f64x2_s_zero")] + pub fn i32x4_relaxed_trunc_f64x2_s_zero(self) -> Self { + self.i32x4_trunc_sat_f64x2_s_zero() + } + + #[doc(alias = "i32x4.relaxed_trunc_f64x2_u_zero")] + pub fn i32x4_relaxed_trunc_f64x2_u_zero(self) -> Self { + self.i32x4_trunc_sat_f64x2_u_zero() + } + + #[doc(alias = "i32x4.trunc_sat_f32x4_s")] + pub fn i32x4_trunc_sat_f32x4_s(self) -> Self { + let v = self.as_f32x4(); + Self::from_i32x4([ + trunc_sat_f32_to_i32(v[0]), + trunc_sat_f32_to_i32(v[1]), + trunc_sat_f32_to_i32(v[2]), + trunc_sat_f32_to_i32(v[3]), + ]) + } + + #[doc(alias = "i32x4.trunc_sat_f32x4_u")] + pub fn i32x4_trunc_sat_f32x4_u(self) -> Self { + let v = self.as_f32x4(); + Self::from_u32x4([ + trunc_sat_f32_to_u32(v[0]), + trunc_sat_f32_to_u32(v[1]), + trunc_sat_f32_to_u32(v[2]), + trunc_sat_f32_to_u32(v[3]), + ]) + } + + #[doc(alias = "i32x4.trunc_sat_f64x2_s_zero")] + pub fn i32x4_trunc_sat_f64x2_s_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_i32x4([trunc_sat_f64_to_i32(v[0]), trunc_sat_f64_to_i32(v[1]), 0, 0]) + } + + #[doc(alias = "i32x4.trunc_sat_f64x2_u_zero")] + pub fn i32x4_trunc_sat_f64x2_u_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_u32x4([trunc_sat_f64_to_u32(v[0]), trunc_sat_f64_to_u32(v[1]), 0, 0]) + } + + #[doc(alias = "f32x4.convert_i32x4_s")] + pub fn f32x4_convert_i32x4_s(self) -> Self { + let v = self.as_i32x4(); + Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) + } + + #[doc(alias = "f32x4.convert_i32x4_u")] + pub fn f32x4_convert_i32x4_u(self) -> Self { + let v = self.as_u32x4(); + Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) + } + + #[doc(alias = "f64x2.convert_low_i32x4_s")] + pub fn f64x2_convert_low_i32x4_s(self) -> Self { + let v = self.as_i32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "f64x2.convert_low_i32x4_u")] + pub fn f64x2_convert_low_i32x4_u(self) -> Self { + let v = self.as_u32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "f32x4.demote_f64x2_zero")] + pub fn f32x4_demote_f64x2_zero(self) -> Self { + let v = self.as_f64x2(); + Self::from_f32x4([v[0] as f32, v[1] as f32, 0.0, 0.0]) + } + + #[doc(alias = "f64x2.promote_low_f32x4")] + pub fn f64x2_promote_low_f32x4(self) -> Self { + let v = self.as_f32x4(); + Self::from_f64x2([v[0] as f64, v[1] as f64]) + } + + #[doc(alias = "i16x8.splat")] + pub fn splat_i16(src: i16) -> Self { + Self::from_i16x8([src; 8]) + } + + #[doc(alias = "i32x4.splat")] + pub fn splat_i32(src: i32) -> Self { + Self::from_i32x4([src; 4]) + } + + #[doc(alias = "i64x2.splat")] + pub fn splat_i64(src: i64) -> Self { + Self::from_i64x2([src; 2]) + } + + #[doc(alias = "f32x4.splat")] + pub fn splat_f32(src: f32) -> Self { + Self::splat_i32(src.to_bits() as i32) + } + + #[doc(alias = "f64x2.splat")] + pub fn splat_f64(src: f64) -> Self { + Self::splat_i64(src.to_bits() as i64) + } + + #[doc(alias = "i8x16.extract_lane_s")] + pub fn extract_lane_i8(self, lane: u8) -> i8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] as i8 + } + + #[doc(alias = "i8x16.extract_lane_u")] + pub fn extract_lane_u8(self, lane: u8) -> u8 { + debug_assert!(lane < 16); + let lane = lane as usize; + let bytes = self.to_le_bytes(); + bytes[lane] + } + + #[doc(alias = "i16x8.extract_lane_s")] + pub fn extract_lane_i16(self, lane: u8) -> i16 { + i16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) + } + + #[doc(alias = "i16x8.extract_lane_u")] + pub fn extract_lane_u16(self, lane: u8) -> u16 { + u16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) + } + + #[doc(alias = "i32x4.extract_lane")] + pub fn extract_lane_i32(self, lane: u8) -> i32 { + i32::from_le_bytes(self.extract_lane_bytes::<4>(lane, 4)) + } + + #[doc(alias = "i64x2.extract_lane")] + pub fn extract_lane_i64(self, lane: u8) -> i64 { + i64::from_le_bytes(self.extract_lane_bytes::<8>(lane, 2)) + } + + #[doc(alias = "f32x4.extract_lane")] + pub fn extract_lane_f32(self, lane: u8) -> f32 { + f32::from_bits(self.extract_lane_i32(lane) as u32) + } + + #[doc(alias = "f64x2.extract_lane")] + pub fn extract_lane_f64(self, lane: u8) -> f64 { + f64::from_bits(self.extract_lane_i64(lane) as u64) + } +} diff --git a/crates/tinywasm/src/interpreter/simd/macros.rs b/crates/tinywasm/src/interpreter/simd/macros.rs new file mode 100644 index 00000000..03948fe7 --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/macros.rs @@ -0,0 +1,347 @@ +macro_rules! simd_impl { + ($(wasm => $wasm:block)? $(x86 => $x86:block)? generic => $generic:block) => {{ + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + { + simd_impl!(@pick_wasm $( $wasm )? ; $generic) + } + + #[cfg(all( + not(any(target_arch = "wasm32", target_arch = "wasm64")), + feature = "simd-x86", + target_arch = "x86_64", + target_feature = "sse4.2", + target_feature = "avx", + target_feature = "avx2", + target_feature = "bmi1", + target_feature = "bmi2", + target_feature = "fma", + target_feature = "lzcnt", + target_feature = "movbe", + target_feature = "popcnt" + ))] + { + simd_impl!(@pick_x86 $( $x86 )? ; $generic) + } + + #[cfg(not(any( + any(target_arch = "wasm32", target_arch = "wasm64"), + all( + feature = "simd-x86", + target_arch = "x86_64", + target_feature = "sse4.2", + target_feature = "avx", + target_feature = "avx2", + target_feature = "bmi1", + target_feature = "bmi2", + target_feature = "fma", + target_feature = "lzcnt", + target_feature = "movbe", + target_feature = "popcnt" + ) + )))] + { + $generic + } + }}; + + (@pick_wasm $wasm:block ; $generic:block) => { + $wasm + }; + + (@pick_wasm ; $generic:block) => { + $generic + }; + + (@pick_x86 $x86:block ; $generic:block) => { + $x86 + }; + + (@pick_x86 ; $generic:block) => { + $generic + }; +} + +macro_rules! simd_wrapping_binop_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $lane_count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = lhs.$op(rhs); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_wrapping_binop { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_wrapping_binop_generic!($lhs, $rhs, $lane_ty, $lane_count, $as_lanes, $from_lanes, $op) } + } + }}; +} + +macro_rules! simd_sat_binop_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $lane_count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = lhs.$op(rhs); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_sat_binop { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_sat_binop_generic!($lhs, $rhs, $lane_ty, $lane_count, $as_lanes, $from_lanes, $op) } + } + }}; +} + +macro_rules! simd_shift_left { + ($value:expr, $shift:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => {{ + let lanes = $value.$as_lanes(); + let s = $shift & $mask; + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane.wrapping_shl(s); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_shift_right { + ($value:expr, $shift:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => {{ + let lanes = $value.$as_lanes(); + let s = $shift & $mask; + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(lanes) { + *dst = lane >> s; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_avgr_u_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $wide_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = ((lhs as $wide_ty + rhs as $wide_ty + 1) >> 1) as $lane_ty; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_avgr_u { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $wide_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_avgr_u_generic!($lhs, $rhs, $lane_ty, $wide_ty, $count, $as_lanes, $from_lanes) } + } + }}; +} + +macro_rules! simd_extend_cast { + ($value:expr, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => {{ + let lanes = $value.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + for (dst, src) in out.iter_mut().zip(lanes[$offset..($offset + $dst_count)].iter()) { + *dst = *src as $dst_ty; + } + Self::$dst_from(out) + }}; +} + +macro_rules! simd_extmul_signed { + ($lhs:expr, $rhs:expr, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => {{ + let a = $lhs.$src_as(); + let b = $rhs.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + for ((dst, lhs), rhs) in + out.iter_mut().zip(a[$offset..($offset + $dst_count)].iter()).zip(b[$offset..($offset + $dst_count)].iter()) + { + *dst = (*lhs as $dst_ty).wrapping_mul(*rhs as $dst_ty); + } + Self::$dst_from(out) + }}; +} + +macro_rules! simd_extmul_unsigned { + ($lhs:expr, $rhs:expr, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => {{ + let a = $lhs.$src_as(); + let b = $rhs.$src_as(); + let mut out = [0 as $dst_ty; $dst_count]; + for ((dst, lhs), rhs) in + out.iter_mut().zip(a[$offset..($offset + $dst_count)].iter()).zip(b[$offset..($offset + $dst_count)].iter()) + { + *dst = (*lhs as $dst_ty) * (*rhs as $dst_ty); + } + Self::$dst_from(out) + }}; +} + +macro_rules! simd_cmp_mask_generic { + ($lhs:expr, $rhs:expr, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $out_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { -1 } else { 0 }; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_cmp_mask { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_cmp_mask_generic!($lhs, $rhs, $out_ty, $count, $as_lanes, $from_lanes, $cmp) } + } + }}; +} + +macro_rules! simd_cmp_mask_const { + ($lhs:expr, $rhs:expr, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $out_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { -1 } else { 0 }; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_cmp_delegate { + ($lhs:expr, $rhs:expr, $delegate:ident) => {{ $rhs.$delegate($lhs) }}; +} + +macro_rules! simd_abs_const { + ($value:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + let a = $value.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(a) { + *dst = lane.wrapping_abs(); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_neg_generic { + ($value:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + let a = $value.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for (dst, lane) in out.iter_mut().zip(a) { + *dst = lane.wrapping_neg(); + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_neg { + ($value:expr, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($value.to_wasm_v128())) } + generic => { simd_neg_generic!($value, $lane_ty, $count, $as_lanes, $from_lanes) } + } + }}; +} + +macro_rules! simd_minmax_generic { + ($lhs:expr, $rhs:expr, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + let a = $lhs.$as_lanes(); + let b = $rhs.$as_lanes(); + let mut out = [0 as $lane_ty; $count]; + for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { + *dst = if lhs $cmp rhs { lhs } else { rhs }; + } + Self::$from_lanes(out) + }}; +} + +macro_rules! simd_minmax { + ($lhs:expr, $rhs:expr, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => {{ + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::$wasm_op($lhs.to_wasm_v128(), $rhs.to_wasm_v128())) } + generic => { simd_minmax_generic!($lhs, $rhs, $lane_ty, $count, $as_lanes, $from_lanes, $cmp) } + } + }}; +} + +macro_rules! simd_float_unary { + ($value:expr, $map:ident, $op:expr) => {{ $value.$map($op) }}; +} + +macro_rules! simd_float_binary { + ($lhs:expr, $rhs:expr, $zip:ident, $op:expr) => {{ $lhs.$zip($rhs, $op) }}; +} + +#[rustfmt::skip] +macro_rules! lane_read { + (i8, $bytes:expr, $offset:expr) => { $bytes[$offset] as i8 }; + (u8, $bytes:expr, $offset:expr) => { $bytes[$offset] }; + (i16, $bytes:expr, $offset:expr) => { i16::from_le_bytes([$bytes[$offset], $bytes[$offset + 1]]) }; + (u16, $bytes:expr, $offset:expr) => { u16::from_le_bytes([$bytes[$offset], $bytes[$offset + 1]]) }; + (i32, $bytes:expr, $offset:expr) => { i32::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3]]) }; + (u32, $bytes:expr, $offset:expr) => { u32::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3]]) }; + (i64, $bytes:expr, $offset:expr) => { i64::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3], $bytes[$offset + 4], $bytes[$offset + 5], $bytes[$offset + 6], $bytes[$offset + 7]]) }; + (u64, $bytes:expr, $offset:expr) => { u64::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3], $bytes[$offset + 4], $bytes[$offset + 5], $bytes[$offset + 6], $bytes[$offset + 7]]) }; + (f32, $bytes:expr, $offset:expr) => { f32::from_bits(u32::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3]])) }; + (f64, $bytes:expr, $offset:expr) => { f64::from_bits(u64::from_le_bytes([$bytes[$offset], $bytes[$offset + 1], $bytes[$offset + 2], $bytes[$offset + 3], $bytes[$offset + 4], $bytes[$offset + 5], $bytes[$offset + 6], $bytes[$offset + 7]])) }; +} + +#[rustfmt::skip] +macro_rules! lane_write { + (i8, $value:expr) => { [$value as u8] }; + (u8, $value:expr) => { [$value] }; + (i16, $value:expr) => { $value.to_le_bytes() }; + (u16, $value:expr) => { $value.to_le_bytes() }; + (i32, $value:expr) => { $value.to_le_bytes() }; + (u32, $value:expr) => { $value.to_le_bytes() }; + (i64, $value:expr) => { $value.to_le_bytes() }; + (u64, $value:expr) => { $value.to_le_bytes() }; + (f32, $value:expr) => { $value.to_bits().to_le_bytes() }; + (f64, $value:expr) => { $value.to_bits().to_le_bytes() }; +} + +macro_rules! impl_lane_accessors { + ($( $as_vis:vis $as_name:ident => $from_vis:vis $from_name:ident : $lane_ty:tt, $lane_count:expr, $lane_bytes:expr; )*) => { + $( + #[inline] + $as_vis const fn $as_name(self) -> [$lane_ty; $lane_count] { + let bytes = self.to_le_bytes(); + let mut out = [0 as $lane_ty; $lane_count]; + let mut i = 0; + while i < $lane_count { + out[i] = lane_read!($lane_ty, bytes, i * $lane_bytes); + i += 1; + } + out + } + + #[inline] + $from_vis const fn $from_name(lanes: [$lane_ty; $lane_count]) -> Self { + let mut bytes = [0u8; 16]; + let mut i = 0; + while i < $lane_count { + let lane = lane_write!($lane_ty, lanes[i]); + let mut j = 0; + while j < $lane_bytes { + bytes[i * $lane_bytes + j] = lane[j]; + j += 1; + } + i += 1; + } + Self::from_le_bytes(bytes) + } + )* + }; +} diff --git a/crates/tinywasm/src/interpreter/simd/mod.rs b/crates/tinywasm/src/interpreter/simd/mod.rs new file mode 100644 index 00000000..332c9a63 --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/mod.rs @@ -0,0 +1,122 @@ +#![cfg_attr(feature = "simd-x86", allow(unsafe_code))] + +#[macro_use] +mod macros; +mod instructions; +mod utils; + +#[cfg(target_arch = "wasm32")] +use core::arch::wasm32 as wasm; +#[cfg(target_arch = "wasm64")] +use core::arch::wasm64 as wasm; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +/// A 128-bit SIMD value +pub struct Value128(i128); + +impl From for i128 { + fn from(val: Value128) -> Self { + val.0 + } +} + +impl From for Value128 { + fn from(value: i128) -> Self { + Self(value) + } +} + +#[cfg_attr(any(target_arch = "wasm32", target_arch = "wasm64"), allow(unreachable_code))] +impl Value128 { + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + #[inline(always)] + fn to_wasm_v128(self) -> wasm::v128 { + let b = self.to_le_bytes(); + wasm::u8x16( + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], + ) + } + + #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] + #[inline(always)] + #[rustfmt::skip] + fn from_wasm_v128(value: wasm::v128) -> Self { + Self::from_le_bytes([ wasm::u8x16_extract_lane::<0>(value), wasm::u8x16_extract_lane::<1>(value), wasm::u8x16_extract_lane::<2>(value), wasm::u8x16_extract_lane::<3>(value), wasm::u8x16_extract_lane::<4>(value), wasm::u8x16_extract_lane::<5>(value), wasm::u8x16_extract_lane::<6>(value), wasm::u8x16_extract_lane::<7>(value), wasm::u8x16_extract_lane::<8>(value), wasm::u8x16_extract_lane::<9>(value), wasm::u8x16_extract_lane::<10>(value), wasm::u8x16_extract_lane::<11>(value), wasm::u8x16_extract_lane::<12>(value), wasm::u8x16_extract_lane::<13>(value), wasm::u8x16_extract_lane::<14>(value), wasm::u8x16_extract_lane::<15>(value)]) + } + + #[inline] + pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { + Self(i128::from_le_bytes(bytes)) + } + + #[inline] + pub const fn to_le_bytes(self) -> [u8; 16] { + self.0.to_le_bytes() + } + + impl_lane_accessors! { + as_i8x16 => from_i8x16: i8, 16, 1; + as_u8x16 => from_u8x16: u8, 16, 1; + as_i16x8 => from_i16x8: i16, 8, 2; + as_u16x8 => from_u16x8: u16, 8, 2; + as_i32x4 => pub from_i32x4: i32, 4, 4; + as_u32x4 => from_u32x4: u32, 4, 4; + as_f32x4 => from_f32x4: f32, 4, 4; + as_i64x2 => pub from_i64x2: i64, 2, 8; + as_u64x2 => from_u64x2: u64, 2, 8; + as_f64x2 => from_f64x2: f64, 2, 8; + } + + #[inline] + fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { + let bytes = self.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + for (src, dst) in bytes.chunks_exact(4).zip(out_bytes.chunks_exact_mut(4)) { + let lane = f32::from_bits(u32::from_le_bytes([src[0], src[1], src[2], src[3]])); + dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); + } + Self::from_le_bytes(out_bytes) + } + + #[inline] + fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { + let a_bytes = self.to_le_bytes(); + let b_bytes = rhs.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + + for ((a, b), dst) in a_bytes.chunks_exact(4).zip(b_bytes.chunks_exact(4)).zip(out_bytes.chunks_exact_mut(4)) { + let a_lane = f32::from_bits(u32::from_le_bytes([a[0], a[1], a[2], a[3]])); + let b_lane = f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])); + dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); + } + + Self::from_le_bytes(out_bytes) + } + + #[inline] + fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { + let bytes = self.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + for (src, dst) in bytes.chunks_exact(8).zip(out_bytes.chunks_exact_mut(8)) { + let lane = + f64::from_bits(u64::from_le_bytes([src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7]])); + dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); + } + Self::from_le_bytes(out_bytes) + } + + #[inline] + fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { + let a_bytes = self.to_le_bytes(); + let b_bytes = rhs.to_le_bytes(); + let mut out_bytes = [0u8; 16]; + + for ((a, b), dst) in a_bytes.chunks_exact(8).zip(b_bytes.chunks_exact(8)).zip(out_bytes.chunks_exact_mut(8)) { + let a_lane = f64::from_bits(u64::from_le_bytes([a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]])); + let b_lane = f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])); + dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); + } + + Self::from_le_bytes(out_bytes) + } +} diff --git a/crates/tinywasm/src/interpreter/simd/utils.rs b/crates/tinywasm/src/interpreter/simd/utils.rs new file mode 100644 index 00000000..6c08495e --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/utils.rs @@ -0,0 +1,113 @@ +use super::Value128; + +impl Value128 { + pub(super) fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { + debug_assert!(lane < lane_count); + let bytes = self.to_le_bytes(); + let start = lane as usize * LANE_BYTES; + let mut out = [0u8; LANE_BYTES]; + out.copy_from_slice(&bytes[start..start + LANE_BYTES]); + out + } + + pub(super) fn replace_lane_bytes( + self, + lane: u8, + value: [u8; LANE_BYTES], + lane_count: u8, + ) -> Self { + debug_assert!(lane < lane_count); + let mut bytes = self.to_le_bytes(); + let start = lane as usize * LANE_BYTES; + bytes[start..start + LANE_BYTES].copy_from_slice(&value); + Self::from_le_bytes(bytes) + } +} + +pub(super) const fn canonicalize_simd_f32_nan(x: f32) -> f32 { + #[cfg(feature = "canonicalize_nans")] + if x.is_nan() { + f32::NAN + } else { + x + } + #[cfg(not(feature = "canonicalize_nans"))] + x +} + +pub(super) const fn canonicalize_simd_f64_nan(x: f64) -> f64 { + #[cfg(feature = "canonicalize_nans")] + if x.is_nan() { + f64::NAN + } else { + x + } + #[cfg(not(feature = "canonicalize_nans"))] + x +} + +pub(super) const fn saturate_i16_to_i8(x: i16) -> i8 { + match x { + v if v > i8::MAX as i16 => i8::MAX, + v if v < i8::MIN as i16 => i8::MIN, + v => v as i8, + } +} + +pub(super) const fn saturate_i16_to_u8(x: i16) -> u8 { + match x { + v if v <= 0 => 0, + v if v > u8::MAX as i16 => u8::MAX, + v => v as u8, + } +} + +pub(super) const fn saturate_i32_to_i16(x: i32) -> i16 { + match x { + v if v > i16::MAX as i32 => i16::MAX, + v if v < i16::MIN as i32 => i16::MIN, + v => v as i16, + } +} + +pub(super) const fn saturate_i32_to_u16(x: i32) -> u16 { + match x { + v if v <= 0 => 0, + v if v > u16::MAX as i32 => u16::MAX, + v => v as u16, + } +} + +pub(super) fn trunc_sat_f32_to_i32(v: f32) -> i32 { + match v { + x if x.is_nan() => 0, + x if x <= i32::MIN as f32 - (1 << 8) as f32 => i32::MIN, + x if x >= (i32::MAX as f32 + 1.0) => i32::MAX, + x => x.trunc() as i32, + } +} + +pub(super) fn trunc_sat_f32_to_u32(v: f32) -> u32 { + match v { + x if x.is_nan() || x <= -1.0_f32 => 0, + x if x >= (u32::MAX as f32 + 1.0) => u32::MAX, + x => x.trunc() as u32, + } +} + +pub(super) fn trunc_sat_f64_to_i32(v: f64) -> i32 { + match v { + x if x.is_nan() => 0, + x if x <= i32::MIN as f64 - 1.0_f64 => i32::MIN, + x if x >= (i32::MAX as f64 + 1.0) => i32::MAX, + x => x.trunc() as i32, + } +} + +pub(super) fn trunc_sat_f64_to_u32(v: f64) -> u32 { + match v { + x if x.is_nan() || x <= -1.0_f64 => 0, + x if x >= (u32::MAX as f64 + 1.0) => u32::MAX, + x => x.trunc() as u32, + } +} diff --git a/crates/tinywasm/src/interpreter/value128.rs b/crates/tinywasm/src/interpreter/value128.rs deleted file mode 100644 index c585b355..00000000 --- a/crates/tinywasm/src/interpreter/value128.rs +++ /dev/null @@ -1,1421 +0,0 @@ -use super::num_helpers::TinywasmFloatExt; - -#[cfg(not(feature = "std"))] -use super::no_std_floats::NoStdFloatExt; -#[cfg(target_arch = "wasm32")] -use core::arch::wasm32 as wasm; -#[cfg(target_arch = "wasm64")] -use core::arch::wasm64 as wasm; - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -/// A 128-bit SIMD value -pub struct Value128(i128); - -impl From for i128 { - fn from(val: Value128) -> Self { - val.0 - } -} - -impl From for Value128 { - fn from(value: i128) -> Self { - Self(value) - } -} - -macro_rules! simd_wrapping_binop { - ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); - - let a = self.$as_lanes(); - let b = rhs.$as_lanes(); - let mut out = [0 as $lane_ty; $lane_count]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - *dst = lhs.$op(rhs); - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_sat_binop { - ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $lane_count:expr, $as_lanes:ident, $from_lanes:ident, $op:ident) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); - - let a = self.$as_lanes(); - let b = rhs.$as_lanes(); - let mut out = [0 as $lane_ty; $lane_count]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - *dst = lhs.$op(rhs); - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_shift_left { - ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => { - #[doc(alias = $doc)] - pub fn $name(self, shift: u32) -> Self { - let lanes = self.$as_lanes(); - let s = shift & $mask; - let mut out = [0 as $lane_ty; $count]; - for (dst, lane) in out.iter_mut().zip(lanes) { - *dst = lane.wrapping_shl(s); - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_shift_right { - ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $mask:expr) => { - #[doc(alias = $doc)] - pub fn $name(self, shift: u32) -> Self { - let lanes = self.$as_lanes(); - let s = shift & $mask; - let mut out = [0 as $lane_ty; $count]; - for (dst, lane) in out.iter_mut().zip(lanes) { - *dst = lane >> s; - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_avgr_u { - ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $wide_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); - - let a = self.$as_lanes(); - let b = rhs.$as_lanes(); - let mut out = [0 as $lane_ty; $count]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - *dst = ((lhs as $wide_ty + rhs as $wide_ty + 1) >> 1) as $lane_ty; - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_extend_cast { - ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { - #[doc(alias = $doc)] - pub fn $name(self) -> Self { - let lanes = self.$src_as(); - let mut out = [0 as $dst_ty; $dst_count]; - for (dst, src) in out.iter_mut().zip(lanes[$offset..($offset + $dst_count)].iter()) { - *dst = *src as $dst_ty; - } - Self::$dst_from(out) - } - }; -} - -macro_rules! simd_extmul_signed { - ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - let a = self.$src_as(); - let b = rhs.$src_as(); - let mut out = [0 as $dst_ty; $dst_count]; - for ((dst, lhs), rhs) in out - .iter_mut() - .zip(a[$offset..($offset + $dst_count)].iter()) - .zip(b[$offset..($offset + $dst_count)].iter()) - { - *dst = (*lhs as $dst_ty).wrapping_mul(*rhs as $dst_ty); - } - Self::$dst_from(out) - } - }; -} - -macro_rules! simd_extmul_unsigned { - ($name:ident, $doc:literal, $src_as:ident, $dst_from:ident, $dst_ty:ty, $dst_count:expr, $offset:expr) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - let a = self.$src_as(); - let b = rhs.$src_as(); - let mut out = [0 as $dst_ty; $dst_count]; - for ((dst, lhs), rhs) in out - .iter_mut() - .zip(a[$offset..($offset + $dst_count)].iter()) - .zip(b[$offset..($offset + $dst_count)].iter()) - { - *dst = (*lhs as $dst_ty) * (*rhs as $dst_ty); - } - Self::$dst_from(out) - } - }; -} - -macro_rules! simd_cmp_mask { - ($name:ident, $doc:literal, $wasm_op:ident, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); - - let a = self.$as_lanes(); - let b = rhs.$as_lanes(); - let mut out = [0 as $out_ty; $count]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - *dst = if lhs $cmp rhs { -1 } else { 0 }; - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_cmp_mask_const { - ($name:ident, $doc:literal, $out_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - let a = self.$as_lanes(); - let b = rhs.$as_lanes(); - let mut out = [0 as $out_ty; $count]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - *dst = if lhs $cmp rhs { -1 } else { 0 }; - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_cmp_delegate { - ($name:ident, $doc:literal, $delegate:ident) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - rhs.$delegate(self) - } - }; -} - -macro_rules! simd_abs_const { - ($name:ident, $doc:literal, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { - #[doc(alias = $doc)] - pub fn $name(self) -> Self { - let a = self.$as_lanes(); - let mut out = [0 as $lane_ty; $count]; - for (dst, lane) in out.iter_mut().zip(a) { - *dst = lane.wrapping_abs(); - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_neg { - ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident) => { - #[doc(alias = $doc)] - pub fn $name(self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128())); - - let a = self.$as_lanes(); - let mut out = [0 as $lane_ty; $count]; - for (dst, lane) in out.iter_mut().zip(a) { - *dst = lane.wrapping_neg(); - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_minmax { - ($name:ident, $doc:literal, $wasm_op:ident, $lane_ty:ty, $count:expr, $as_lanes:ident, $from_lanes:ident, $cmp:tt) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::$wasm_op(self.to_wasm_v128(), rhs.to_wasm_v128())); - - let a = self.$as_lanes(); - let b = rhs.$as_lanes(); - let mut out = [0 as $lane_ty; $count]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - *dst = if lhs $cmp rhs { lhs } else { rhs }; - } - Self::$from_lanes(out) - } - }; -} - -macro_rules! simd_float_unary { - ($name:ident, $doc:literal, $map:ident, $op:expr) => { - #[doc(alias = $doc)] - pub fn $name(self) -> Self { - self.$map($op) - } - }; -} - -macro_rules! simd_float_binary { - ($name:ident, $doc:literal, $zip:ident, $op:expr) => { - #[doc(alias = $doc)] - pub fn $name(self, rhs: Self) -> Self { - self.$zip(rhs, $op) - } - }; -} - -#[cfg_attr(any(target_arch = "wasm32", target_arch = "wasm64"), allow(unreachable_code))] -impl Value128 { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - #[inline(always)] - fn to_wasm_v128(self) -> wasm::v128 { - let b = self.to_le_bytes(); - wasm::u8x16( - b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], - ) - } - - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - #[inline(always)] - #[rustfmt::skip] - fn from_wasm_v128(value: wasm::v128) -> Self { - Self::from_le_bytes([ wasm::u8x16_extract_lane::<0>(value), wasm::u8x16_extract_lane::<1>(value), wasm::u8x16_extract_lane::<2>(value), wasm::u8x16_extract_lane::<3>(value), wasm::u8x16_extract_lane::<4>(value), wasm::u8x16_extract_lane::<5>(value), wasm::u8x16_extract_lane::<6>(value), wasm::u8x16_extract_lane::<7>(value), wasm::u8x16_extract_lane::<8>(value), wasm::u8x16_extract_lane::<9>(value), wasm::u8x16_extract_lane::<10>(value), wasm::u8x16_extract_lane::<11>(value), wasm::u8x16_extract_lane::<12>(value), wasm::u8x16_extract_lane::<13>(value), wasm::u8x16_extract_lane::<14>(value), wasm::u8x16_extract_lane::<15>(value)]) - } - - #[inline] - pub const fn from_le_bytes(bytes: [u8; 16]) -> Self { - Self(i128::from_le_bytes(bytes)) - } - - #[inline] - pub const fn to_le_bytes(self) -> [u8; 16] { - self.0.to_le_bytes() - } - - #[inline] - #[rustfmt::skip] - const fn as_i8x16(self) -> [i8; 16] { - let b = self.to_le_bytes(); - [b[0] as i8, b[1] as i8, b[2] as i8, b[3] as i8, b[4] as i8, b[5] as i8, b[6] as i8, b[7] as i8, b[8] as i8, b[9] as i8, b[10] as i8, b[11] as i8, b[12] as i8, b[13] as i8, b[14] as i8, b[15] as i8] - } - - #[inline] - #[rustfmt::skip] - const fn as_u8x16(self) -> [u8; 16] { - self.to_le_bytes() - } - - #[inline] - #[rustfmt::skip] - const fn from_i8x16(x: [i8; 16]) -> Self { - Self::from_le_bytes([x[0] as u8, x[1] as u8, x[2] as u8, x[3] as u8, x[4] as u8, x[5] as u8, x[6] as u8, x[7] as u8, x[8] as u8, x[9] as u8, x[10] as u8, x[11] as u8, x[12] as u8, x[13] as u8, x[14] as u8, x[15] as u8]) - } - - #[inline] - #[rustfmt::skip] - const fn from_u8x16(x: [u8; 16]) -> Self { - Self::from_le_bytes(x) - } - - #[inline] - #[rustfmt::skip] - const fn as_i16x8(self) -> [i16; 8] { - let b = self.to_le_bytes(); - [i16::from_le_bytes([b[0], b[1]]), i16::from_le_bytes([b[2], b[3]]), i16::from_le_bytes([b[4], b[5]]), i16::from_le_bytes([b[6], b[7]]), i16::from_le_bytes([b[8], b[9]]), i16::from_le_bytes([b[10], b[11]]), i16::from_le_bytes([b[12], b[13]]), i16::from_le_bytes([b[14], b[15]])] - } - - #[inline] - #[rustfmt::skip] - const fn as_u16x8(self) -> [u16; 8] { - let b = self.to_le_bytes(); - [u16::from_le_bytes([b[0], b[1]]), u16::from_le_bytes([b[2], b[3]]), u16::from_le_bytes([b[4], b[5]]), u16::from_le_bytes([b[6], b[7]]), u16::from_le_bytes([b[8], b[9]]), u16::from_le_bytes([b[10], b[11]]), u16::from_le_bytes([b[12], b[13]]), u16::from_le_bytes([b[14], b[15]])] - } - - #[inline] - #[rustfmt::skip] - const fn from_i16x8(x: [i16; 8]) -> Self { - Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) - } - - #[inline] - #[rustfmt::skip] - const fn from_u16x8(x: [u16; 8]) -> Self { - Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[4].to_le_bytes()[0], x[4].to_le_bytes()[1], x[5].to_le_bytes()[0], x[5].to_le_bytes()[1], x[6].to_le_bytes()[0], x[6].to_le_bytes()[1], x[7].to_le_bytes()[0], x[7].to_le_bytes()[1]]) - } - - #[inline] - #[rustfmt::skip] - const fn as_i32x4(self) -> [i32; 4] { - let b = self.to_le_bytes(); - [i32::from_le_bytes([b[0], b[1], b[2], b[3]]), i32::from_le_bytes([b[4], b[5], b[6], b[7]]), i32::from_le_bytes([b[8], b[9], b[10], b[11]]), i32::from_le_bytes([b[12], b[13], b[14], b[15]])] - } - - #[inline] - #[rustfmt::skip] - const fn as_u32x4(self) -> [u32; 4] { - let b = self.to_le_bytes(); - [u32::from_le_bytes([b[0], b[1], b[2], b[3]]), u32::from_le_bytes([b[4], b[5], b[6], b[7]]), u32::from_le_bytes([b[8], b[9], b[10], b[11]]), u32::from_le_bytes([b[12], b[13], b[14], b[15]])] - } - - #[inline] - #[rustfmt::skip] - const fn as_f32x4(self) -> [f32; 4] { - let b = self.to_le_bytes(); - [f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])), f32::from_bits(u32::from_le_bytes([b[4], b[5], b[6], b[7]])), f32::from_bits(u32::from_le_bytes([b[8], b[9], b[10], b[11]])), f32::from_bits(u32::from_le_bytes([b[12], b[13], b[14], b[15]]))] - } - - #[inline] - #[rustfmt::skip] - pub const fn from_i32x4(x: [i32; 4]) -> Self { - Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) - } - - #[inline] - #[rustfmt::skip] - const fn from_u32x4(x: [u32; 4]) -> Self { - Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[2].to_le_bytes()[0], x[2].to_le_bytes()[1], x[2].to_le_bytes()[2], x[2].to_le_bytes()[3], x[3].to_le_bytes()[0], x[3].to_le_bytes()[1], x[3].to_le_bytes()[2], x[3].to_le_bytes()[3]]) - } - - #[inline] - #[rustfmt::skip] - const fn from_f32x4(x: [f32; 4]) -> Self { - Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[2].to_bits().to_le_bytes()[0], x[2].to_bits().to_le_bytes()[1], x[2].to_bits().to_le_bytes()[2], x[2].to_bits().to_le_bytes()[3], x[3].to_bits().to_le_bytes()[0], x[3].to_bits().to_le_bytes()[1], x[3].to_bits().to_le_bytes()[2], x[3].to_bits().to_le_bytes()[3]]) - } - - #[inline] - #[rustfmt::skip] - const fn as_i64x2(self) -> [i64; 2] { - let b = self.to_le_bytes(); - [i64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), i64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] - } - - #[inline] - #[rustfmt::skip] - const fn as_u64x2(self) -> [u64; 2] { - let b = self.to_le_bytes(); - [u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]), u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]])] - } - - #[inline] - #[rustfmt::skip] - const fn as_f64x2(self) -> [f64; 2] { - let b = self.to_le_bytes(); - [f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])), f64::from_bits(u64::from_le_bytes([b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]]))] - } - - #[inline] - #[rustfmt::skip] - pub const fn from_i64x2(x: [i64; 2]) -> Self { - Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) - } - - #[inline] - #[rustfmt::skip] - const fn from_u64x2(x: [u64; 2]) -> Self { - Self::from_le_bytes([x[0].to_le_bytes()[0], x[0].to_le_bytes()[1], x[0].to_le_bytes()[2], x[0].to_le_bytes()[3], x[0].to_le_bytes()[4], x[0].to_le_bytes()[5], x[0].to_le_bytes()[6], x[0].to_le_bytes()[7], x[1].to_le_bytes()[0], x[1].to_le_bytes()[1], x[1].to_le_bytes()[2], x[1].to_le_bytes()[3], x[1].to_le_bytes()[4], x[1].to_le_bytes()[5], x[1].to_le_bytes()[6], x[1].to_le_bytes()[7]]) - } - - #[inline] - #[rustfmt::skip] - const fn from_f64x2(x: [f64; 2]) -> Self { - Self::from_le_bytes([x[0].to_bits().to_le_bytes()[0], x[0].to_bits().to_le_bytes()[1], x[0].to_bits().to_le_bytes()[2], x[0].to_bits().to_le_bytes()[3], x[0].to_bits().to_le_bytes()[4], x[0].to_bits().to_le_bytes()[5], x[0].to_bits().to_le_bytes()[6], x[0].to_bits().to_le_bytes()[7], x[1].to_bits().to_le_bytes()[0], x[1].to_bits().to_le_bytes()[1], x[1].to_bits().to_le_bytes()[2], x[1].to_bits().to_le_bytes()[3], x[1].to_bits().to_le_bytes()[4], x[1].to_bits().to_le_bytes()[5], x[1].to_bits().to_le_bytes()[6], x[1].to_bits().to_le_bytes()[7]]) - } - - #[inline] - fn map_f32x4(self, mut op: impl FnMut(f32) -> f32) -> Self { - let bytes = self.to_le_bytes(); - let mut out_bytes = [0u8; 16]; - for (src, dst) in bytes.chunks_exact(4).zip(out_bytes.chunks_exact_mut(4)) { - let lane = f32::from_bits(u32::from_le_bytes([src[0], src[1], src[2], src[3]])); - dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); - } - Self::from_le_bytes(out_bytes) - } - - #[inline] - fn zip_f32x4(self, rhs: Self, mut op: impl FnMut(f32, f32) -> f32) -> Self { - let a_bytes = self.to_le_bytes(); - let b_bytes = rhs.to_le_bytes(); - let mut out_bytes = [0u8; 16]; - - for ((a, b), dst) in a_bytes.chunks_exact(4).zip(b_bytes.chunks_exact(4)).zip(out_bytes.chunks_exact_mut(4)) { - let a_lane = f32::from_bits(u32::from_le_bytes([a[0], a[1], a[2], a[3]])); - let b_lane = f32::from_bits(u32::from_le_bytes([b[0], b[1], b[2], b[3]])); - dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); - } - - Self::from_le_bytes(out_bytes) - } - - #[inline] - fn map_f64x2(self, mut op: impl FnMut(f64) -> f64) -> Self { - let bytes = self.to_le_bytes(); - let mut out_bytes = [0u8; 16]; - for (src, dst) in bytes.chunks_exact(8).zip(out_bytes.chunks_exact_mut(8)) { - let lane = - f64::from_bits(u64::from_le_bytes([src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7]])); - dst.copy_from_slice(&op(lane).to_bits().to_le_bytes()); - } - Self::from_le_bytes(out_bytes) - } - - #[inline] - fn zip_f64x2(self, rhs: Self, mut op: impl FnMut(f64, f64) -> f64) -> Self { - let a_bytes = self.to_le_bytes(); - let b_bytes = rhs.to_le_bytes(); - let mut out_bytes = [0u8; 16]; - - for ((a, b), dst) in a_bytes.chunks_exact(8).zip(b_bytes.chunks_exact(8)).zip(out_bytes.chunks_exact_mut(8)) { - let a_lane = f64::from_bits(u64::from_le_bytes([a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]])); - let b_lane = f64::from_bits(u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])); - dst.copy_from_slice(&op(a_lane, b_lane).to_bits().to_le_bytes()); - } - - Self::from_le_bytes(out_bytes) - } - - #[doc(alias = "v128.any_true")] - pub fn v128_any_true(self) -> bool { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return wasm::v128_any_true(self.to_wasm_v128()); - self.0 != 0 - } - - #[doc(alias = "v128.not")] - pub fn v128_not(self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::v128_not(self.to_wasm_v128())); - Self(!self.0) - } - - #[doc(alias = "v128.and")] - pub fn v128_and(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::v128_and(self.to_wasm_v128(), rhs.to_wasm_v128())); - Self(self.0 & rhs.0) - } - - #[doc(alias = "v128.andnot")] - pub fn v128_andnot(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::v128_andnot(self.to_wasm_v128(), rhs.to_wasm_v128())); - Self(self.0 & !rhs.0) - } - - #[doc(alias = "v128.or")] - pub fn v128_or(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::v128_or(self.to_wasm_v128(), rhs.to_wasm_v128())); - Self(self.0 | rhs.0) - } - - #[doc(alias = "v128.xor")] - pub fn v128_xor(self, rhs: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::v128_xor(self.to_wasm_v128(), rhs.to_wasm_v128())); - Self(self.0 ^ rhs.0) - } - - #[doc(alias = "v128.bitselect")] - pub fn v128_bitselect(v1: Self, v2: Self, c: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::v128_bitselect(v1.to_wasm_v128(), v2.to_wasm_v128(), c.to_wasm_v128())); - Self((v1.0 & c.0) | (v2.0 & !c.0)) - } - - #[doc(alias = "v128.load8x8_s")] - pub const fn v128_load8x8_s(src: [u8; 8]) -> Self { - Self::from_i16x8([ - src[0] as i8 as i16, - src[1] as i8 as i16, - src[2] as i8 as i16, - src[3] as i8 as i16, - src[4] as i8 as i16, - src[5] as i8 as i16, - src[6] as i8 as i16, - src[7] as i8 as i16, - ]) - } - - #[doc(alias = "v128.load8x8_u")] - pub const fn v128_load8x8_u(src: [u8; 8]) -> Self { - Self::from_u16x8([ - src[0] as u16, - src[1] as u16, - src[2] as u16, - src[3] as u16, - src[4] as u16, - src[5] as u16, - src[6] as u16, - src[7] as u16, - ]) - } - - #[doc(alias = "v128.load16x4_s")] - pub const fn v128_load16x4_s(src: [u8; 8]) -> Self { - Self::from_i32x4([ - i16::from_le_bytes([src[0], src[1]]) as i32, - i16::from_le_bytes([src[2], src[3]]) as i32, - i16::from_le_bytes([src[4], src[5]]) as i32, - i16::from_le_bytes([src[6], src[7]]) as i32, - ]) - } - - #[doc(alias = "v128.load16x4_u")] - pub const fn v128_load16x4_u(src: [u8; 8]) -> Self { - Self::from_u32x4([ - u16::from_le_bytes([src[0], src[1]]) as u32, - u16::from_le_bytes([src[2], src[3]]) as u32, - u16::from_le_bytes([src[4], src[5]]) as u32, - u16::from_le_bytes([src[6], src[7]]) as u32, - ]) - } - - #[doc(alias = "v128.load32x2_s")] - pub const fn v128_load32x2_s(src: [u8; 8]) -> Self { - Self::from_i64x2([ - i32::from_le_bytes([src[0], src[1], src[2], src[3]]) as i64, - i32::from_le_bytes([src[4], src[5], src[6], src[7]]) as i64, - ]) - } - - #[doc(alias = "v128.load32x2_u")] - pub const fn v128_load32x2_u(src: [u8; 8]) -> Self { - Self::from_u64x2([ - u32::from_le_bytes([src[0], src[1], src[2], src[3]]) as u64, - u32::from_le_bytes([src[4], src[5], src[6], src[7]]) as u64, - ]) - } - - #[doc(alias = "i8x16.swizzle")] - pub fn i8x16_swizzle(self, s: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); - - let a = self.to_le_bytes(); - let idx = s.to_le_bytes(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - let j = idx[i]; - let lane = a[(j & 0x0f) as usize]; - out[i] = if j < 16 { lane } else { 0 }; - i += 1; - } - Self::from_le_bytes(out) - } - - #[doc(alias = "i8x16.relaxed_swizzle")] - pub fn i8x16_relaxed_swizzle(self, s: Self) -> Self { - self.i8x16_swizzle(s) - } - - #[doc(alias = "i8x16.shuffle")] - pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { - let mut src = [0u8; 32]; - src[..16].copy_from_slice(&a.to_le_bytes()); - src[16..].copy_from_slice(&b.to_le_bytes()); - let mut out = [0u8; 16]; - for i in 0..16 { - out[i] = src[(idx[i] & 31) as usize]; - } - Self::from_le_bytes(out) - } - - pub fn splat_i8(src: i8) -> Self { - Self::from_le_bytes([src as u8; 16]) - } - - #[doc(alias = "i8x16.replace_lane")] - pub fn i8x16_replace_lane(self, lane: u8, value: i8) -> Self { - self.replace_lane_bytes::<1>(lane, [value as u8], 16) - } - - #[doc(alias = "i16x8.replace_lane")] - pub fn i16x8_replace_lane(self, lane: u8, value: i16) -> Self { - self.replace_lane_bytes::<2>(lane, value.to_le_bytes(), 8) - } - - #[doc(alias = "i32x4.replace_lane")] - pub fn i32x4_replace_lane(self, lane: u8, value: i32) -> Self { - self.replace_lane_bytes::<4>(lane, value.to_le_bytes(), 4) - } - - #[doc(alias = "i64x2.replace_lane")] - pub fn i64x2_replace_lane(self, lane: u8, value: i64) -> Self { - self.replace_lane_bytes::<8>(lane, value.to_le_bytes(), 2) - } - - #[doc(alias = "f32x4.replace_lane")] - pub fn f32x4_replace_lane(self, lane: u8, value: f32) -> Self { - self.replace_lane_bytes::<4>(lane, value.to_bits().to_le_bytes(), 4) - } - - #[doc(alias = "f64x2.replace_lane")] - pub fn f64x2_replace_lane(self, lane: u8, value: f64) -> Self { - self.replace_lane_bytes::<8>(lane, value.to_bits().to_le_bytes(), 2) - } - - #[doc(alias = "i8x16.all_true")] - pub fn i8x16_all_true(self) -> bool { - for byte in self.to_le_bytes() { - if byte == 0 { - return false; - } - } - true - } - - #[doc(alias = "i16x8.all_true")] - pub fn i16x8_all_true(self) -> bool { - let bytes = self.to_le_bytes(); - for lane in bytes.chunks_exact(2) { - if u16::from_le_bytes([lane[0], lane[1]]) == 0 { - return false; - } - } - true - } - - #[doc(alias = "i32x4.all_true")] - pub fn i32x4_all_true(self) -> bool { - let bytes = self.to_le_bytes(); - for lane in bytes.chunks_exact(4) { - if u32::from_le_bytes([lane[0], lane[1], lane[2], lane[3]]) == 0 { - return false; - } - } - true - } - - #[doc(alias = "i64x2.all_true")] - pub fn i64x2_all_true(self) -> bool { - let bytes = self.to_le_bytes(); - for lane in bytes.chunks_exact(8) { - if u64::from_le_bytes([lane[0], lane[1], lane[2], lane[3], lane[4], lane[5], lane[6], lane[7]]) == 0 { - return false; - } - } - true - } - - #[doc(alias = "i8x16.bitmask")] - pub fn i8x16_bitmask(self) -> u32 { - let bytes = self.to_le_bytes(); - let mut mask = 0u32; - for (i, byte) in bytes.into_iter().enumerate() { - if (byte & 0x80) != 0 { - mask |= 1u32 << i; - } - } - mask - } - - #[doc(alias = "i16x8.bitmask")] - pub fn i16x8_bitmask(self) -> u32 { - let bytes = self.to_le_bytes(); - let mut mask = 0u32; - for (i, lane) in bytes.chunks_exact(2).enumerate() { - if (lane[1] & 0x80) != 0 { - mask |= 1u32 << i; - } - } - mask - } - - #[doc(alias = "i32x4.bitmask")] - pub fn i32x4_bitmask(self) -> u32 { - let bytes = self.to_le_bytes(); - let mut mask = 0u32; - for (i, lane) in bytes.chunks_exact(4).enumerate() { - if (lane[3] & 0x80) != 0 { - mask |= 1u32 << i; - } - } - mask - } - - #[doc(alias = "i64x2.bitmask")] - pub fn i64x2_bitmask(self) -> u32 { - let x = u128::from_le_bytes(self.to_le_bytes()); - (((x >> 63) & 1) as u32) | ((((x >> 127) & 1) as u32) << 1) - } - - #[doc(alias = "i8x16.popcnt")] - pub fn i8x16_popcnt(self) -> Self { - let lanes = self.to_le_bytes(); - let mut out = [0u8; 16]; - for (dst, lane) in out.iter_mut().zip(lanes) { - *dst = lane.count_ones() as u8; - } - Self::from_le_bytes(out) - } - - simd_shift_left!(i8x16_shl, "i8x16.shl", i8, 16, as_i8x16, from_i8x16, 7); - simd_shift_left!(i16x8_shl, "i16x8.shl", i16, 8, as_i16x8, from_i16x8, 15); - simd_shift_left!(i32x4_shl, "i32x4.shl", i32, 4, as_i32x4, from_i32x4, 31); - simd_shift_left!(i64x2_shl, "i64x2.shl", i64, 2, as_i64x2, from_i64x2, 63); - - simd_shift_right!(i8x16_shr_s, "i8x16.shr_s", i8, 16, as_i8x16, from_i8x16, 7); - simd_shift_right!(i16x8_shr_s, "i16x8.shr_s", i16, 8, as_i16x8, from_i16x8, 15); - simd_shift_right!(i32x4_shr_s, "i32x4.shr_s", i32, 4, as_i32x4, from_i32x4, 31); - simd_shift_right!(i64x2_shr_s, "i64x2.shr_s", i64, 2, as_i64x2, from_i64x2, 63); - - simd_shift_right!(i8x16_shr_u, "i8x16.shr_u", u8, 16, as_u8x16, from_u8x16, 7); - simd_shift_right!(i16x8_shr_u, "i16x8.shr_u", u16, 8, as_u16x8, from_u16x8, 15); - simd_shift_right!(i32x4_shr_u, "i32x4.shr_u", u32, 4, as_u32x4, from_u32x4, 31); - simd_shift_right!(i64x2_shr_u, "i64x2.shr_u", u64, 2, as_u64x2, from_u64x2, 63); - - simd_wrapping_binop!(i8x16_add, "i8x16.add", i8x16_add, i8, 16, as_i8x16, from_i8x16, wrapping_add); - simd_wrapping_binop!(i16x8_add, "i16x8.add", i16x8_add, i16, 8, as_i16x8, from_i16x8, wrapping_add); - simd_wrapping_binop!(i32x4_add, "i32x4.add", i32x4_add, i32, 4, as_i32x4, from_i32x4, wrapping_add); - simd_wrapping_binop!(i64x2_add, "i64x2.add", i64x2_add, i64, 2, as_i64x2, from_i64x2, wrapping_add); - simd_wrapping_binop!(i8x16_sub, "i8x16.sub", i8x16_sub, i8, 16, as_i8x16, from_i8x16, wrapping_sub); - simd_wrapping_binop!(i16x8_sub, "i16x8.sub", i16x8_sub, i16, 8, as_i16x8, from_i16x8, wrapping_sub); - simd_wrapping_binop!(i32x4_sub, "i32x4.sub", i32x4_sub, i32, 4, as_i32x4, from_i32x4, wrapping_sub); - simd_wrapping_binop!(i64x2_sub, "i64x2.sub", i64x2_sub, i64, 2, as_i64x2, from_i64x2, wrapping_sub); - simd_wrapping_binop!(i16x8_mul, "i16x8.mul", i16x8_mul, i16, 8, as_i16x8, from_i16x8, wrapping_mul); - simd_wrapping_binop!(i32x4_mul, "i32x4.mul", i32x4_mul, i32, 4, as_i32x4, from_i32x4, wrapping_mul); - simd_wrapping_binop!(i64x2_mul, "i64x2.mul", i64x2_mul, i64, 2, as_i64x2, from_i64x2, wrapping_mul); - - simd_sat_binop!(i8x16_add_sat_s, "i8x16.add_sat_s", i8x16_add_sat, i8, 16, as_i8x16, from_i8x16, saturating_add); - simd_sat_binop!(i16x8_add_sat_s, "i16x8.add_sat_s", i16x8_add_sat, i16, 8, as_i16x8, from_i16x8, saturating_add); - simd_sat_binop!(i8x16_add_sat_u, "i8x16.add_sat_u", u8x16_add_sat, u8, 16, as_u8x16, from_u8x16, saturating_add); - simd_sat_binop!(i16x8_add_sat_u, "i16x8.add_sat_u", u16x8_add_sat, u16, 8, as_u16x8, from_u16x8, saturating_add); - simd_sat_binop!(i8x16_sub_sat_s, "i8x16.sub_sat_s", i8x16_sub_sat, i8, 16, as_i8x16, from_i8x16, saturating_sub); - simd_sat_binop!(i16x8_sub_sat_s, "i16x8.sub_sat_s", i16x8_sub_sat, i16, 8, as_i16x8, from_i16x8, saturating_sub); - simd_sat_binop!(i8x16_sub_sat_u, "i8x16.sub_sat_u", u8x16_sub_sat, u8, 16, as_u8x16, from_u8x16, saturating_sub); - simd_sat_binop!(i16x8_sub_sat_u, "i16x8.sub_sat_u", u16x8_sub_sat, u16, 8, as_u16x8, from_u16x8, saturating_sub); - - simd_avgr_u!(i8x16_avgr_u, "i8x16.avgr_u", u8x16_avgr, u8, u16, 16, as_u8x16, from_u8x16); - simd_avgr_u!(i16x8_avgr_u, "i16x8.avgr_u", u16x8_avgr, u16, u32, 8, as_u16x8, from_u16x8); - - #[doc(alias = "i8x16.narrow_i16x8_s")] - pub fn i8x16_narrow_i16x8_s(a: Self, b: Self) -> Self { - let av = a.as_i16x8(); - let bv = b.as_i16x8(); - let mut out = [0i8; 16]; - let (lo, hi) = out.split_at_mut(8); - for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { - *dst_lo = saturate_i16_to_i8(a_lane); - *dst_hi = saturate_i16_to_i8(b_lane); - } - Self::from_i8x16(out) - } - - #[doc(alias = "i8x16.narrow_i16x8_u")] - pub fn i8x16_narrow_i16x8_u(a: Self, b: Self) -> Self { - let av = a.as_i16x8(); - let bv = b.as_i16x8(); - let mut out = [0u8; 16]; - let (lo, hi) = out.split_at_mut(8); - for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { - *dst_lo = saturate_i16_to_u8(a_lane); - *dst_hi = saturate_i16_to_u8(b_lane); - } - Self::from_u8x16(out) - } - - #[doc(alias = "i16x8.narrow_i32x4_s")] - pub fn i16x8_narrow_i32x4_s(a: Self, b: Self) -> Self { - let av = a.as_i32x4(); - let bv = b.as_i32x4(); - let mut out = [0i16; 8]; - let (lo, hi) = out.split_at_mut(4); - for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { - *dst_lo = saturate_i32_to_i16(a_lane); - *dst_hi = saturate_i32_to_i16(b_lane); - } - Self::from_i16x8(out) - } - - #[doc(alias = "i16x8.narrow_i32x4_u")] - pub fn i16x8_narrow_i32x4_u(a: Self, b: Self) -> Self { - let av = a.as_i32x4(); - let bv = b.as_i32x4(); - let mut out = [0u16; 8]; - let (lo, hi) = out.split_at_mut(4); - for ((dst_lo, dst_hi), (a_lane, b_lane)) in lo.iter_mut().zip(hi.iter_mut()).zip(av.into_iter().zip(bv)) { - *dst_lo = saturate_i32_to_u16(a_lane); - *dst_hi = saturate_i32_to_u16(b_lane); - } - Self::from_u16x8(out) - } - - #[doc(alias = "i16x8.extadd_pairwise_i8x16_s")] - pub fn i16x8_extadd_pairwise_i8x16_s(self) -> Self { - let lanes = self.as_i8x16(); - let mut out = [0i16; 8]; - for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { - *dst = pair[0] as i16 + pair[1] as i16; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i16x8.extadd_pairwise_i8x16_u")] - pub fn i16x8_extadd_pairwise_i8x16_u(self) -> Self { - let lanes = self.as_u8x16(); - let mut out = [0u16; 8]; - for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { - *dst = pair[0] as u16 + pair[1] as u16; - } - Self::from_u16x8(out) - } - - #[doc(alias = "i32x4.extadd_pairwise_i16x8_s")] - pub fn i32x4_extadd_pairwise_i16x8_s(self) -> Self { - let lanes = self.as_i16x8(); - let mut out = [0i32; 4]; - for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { - *dst = pair[0] as i32 + pair[1] as i32; - } - Self::from_i32x4(out) - } - - #[doc(alias = "i32x4.extadd_pairwise_i16x8_u")] - pub fn i32x4_extadd_pairwise_i16x8_u(self) -> Self { - let lanes = self.as_u16x8(); - let mut out = [0u32; 4]; - for (dst, pair) in out.iter_mut().zip(lanes.chunks_exact(2)) { - *dst = pair[0] as u32 + pair[1] as u32; - } - Self::from_u32x4(out) - } - - simd_extend_cast!(i16x8_extend_low_i8x16_s, "i16x8.extend_low_i8x16_s", as_i8x16, from_i16x8, i16, 8, 0); - simd_extend_cast!(i16x8_extend_low_i8x16_u, "i16x8.extend_low_i8x16_u", as_u8x16, from_u16x8, u16, 8, 0); - simd_extend_cast!(i16x8_extend_high_i8x16_s, "i16x8.extend_high_i8x16_s", as_i8x16, from_i16x8, i16, 8, 8); - simd_extend_cast!(i16x8_extend_high_i8x16_u, "i16x8.extend_high_i8x16_u", as_u8x16, from_u16x8, u16, 8, 8); - simd_extend_cast!(i32x4_extend_low_i16x8_s, "i32x4.extend_low_i16x8_s", as_i16x8, from_i32x4, i32, 4, 0); - simd_extend_cast!(i32x4_extend_low_i16x8_u, "i32x4.extend_low_i16x8_u", as_u16x8, from_u32x4, u32, 4, 0); - simd_extend_cast!(i32x4_extend_high_i16x8_s, "i32x4.extend_high_i16x8_s", as_i16x8, from_i32x4, i32, 4, 4); - simd_extend_cast!(i32x4_extend_high_i16x8_u, "i32x4.extend_high_i16x8_u", as_u16x8, from_u32x4, u32, 4, 4); - simd_extend_cast!(i64x2_extend_low_i32x4_s, "i64x2.extend_low_i32x4_s", as_i32x4, from_i64x2, i64, 2, 0); - simd_extend_cast!(i64x2_extend_low_i32x4_u, "i64x2.extend_low_i32x4_u", as_u32x4, from_u64x2, u64, 2, 0); - simd_extend_cast!(i64x2_extend_high_i32x4_s, "i64x2.extend_high_i32x4_s", as_i32x4, from_i64x2, i64, 2, 2); - simd_extend_cast!(i64x2_extend_high_i32x4_u, "i64x2.extend_high_i32x4_u", as_u32x4, from_u64x2, u64, 2, 2); - - simd_extmul_signed!(i16x8_extmul_low_i8x16_s, "i16x8.extmul_low_i8x16_s", as_i8x16, from_i16x8, i16, 8, 0); - simd_extmul_unsigned!(i16x8_extmul_low_i8x16_u, "i16x8.extmul_low_i8x16_u", as_u8x16, from_u16x8, u16, 8, 0); - simd_extmul_signed!(i16x8_extmul_high_i8x16_s, "i16x8.extmul_high_i8x16_s", as_i8x16, from_i16x8, i16, 8, 8); - simd_extmul_unsigned!(i16x8_extmul_high_i8x16_u, "i16x8.extmul_high_i8x16_u", as_u8x16, from_u16x8, u16, 8, 8); - simd_extmul_signed!(i32x4_extmul_low_i16x8_s, "i32x4.extmul_low_i16x8_s", as_i16x8, from_i32x4, i32, 4, 0); - simd_extmul_unsigned!(i32x4_extmul_low_i16x8_u, "i32x4.extmul_low_i16x8_u", as_u16x8, from_u32x4, u32, 4, 0); - simd_extmul_signed!(i32x4_extmul_high_i16x8_s, "i32x4.extmul_high_i16x8_s", as_i16x8, from_i32x4, i32, 4, 4); - simd_extmul_unsigned!(i32x4_extmul_high_i16x8_u, "i32x4.extmul_high_i16x8_u", as_u16x8, from_u32x4, u32, 4, 4); - simd_extmul_signed!(i64x2_extmul_low_i32x4_s, "i64x2.extmul_low_i32x4_s", as_i32x4, from_i64x2, i64, 2, 0); - simd_extmul_unsigned!(i64x2_extmul_low_i32x4_u, "i64x2.extmul_low_i32x4_u", as_u32x4, from_u64x2, u64, 2, 0); - simd_extmul_signed!(i64x2_extmul_high_i32x4_s, "i64x2.extmul_high_i32x4_s", as_i32x4, from_i64x2, i64, 2, 2); - simd_extmul_unsigned!(i64x2_extmul_high_i32x4_u, "i64x2.extmul_high_i32x4_u", as_u32x4, from_u64x2, u64, 2, 2); - - #[doc(alias = "i16x8.q15mulr_sat_s")] - pub fn i16x8_q15mulr_sat_s(self, rhs: Self) -> Self { - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i16; 8]; - for ((dst, lhs), rhs) in out.iter_mut().zip(a).zip(b) { - let r = ((lhs as i32 * rhs as i32) + (1 << 14)) >> 15; // 2^14: Q15 rounding - *dst = if r > i16::MAX as i32 { - i16::MAX - } else if r < i16::MIN as i32 { - i16::MIN - } else { - r as i16 - }; - } - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.dot_i16x8_s")] - pub fn i32x4_dot_i16x8_s(self, rhs: Self) -> Self { - let a = self.as_i16x8(); - let b = rhs.as_i16x8(); - let mut out = [0i32; 4]; - for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { - *dst = (a_pair[0] as i32) - .wrapping_mul(b_pair[0] as i32) - .wrapping_add((a_pair[1] as i32).wrapping_mul(b_pair[1] as i32)); - } - Self::from_i32x4(out) - } - - #[doc(alias = "i8x16.relaxed_laneselect")] - pub fn i8x16_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { - Self::v128_bitselect(v1, v2, c) - } - - #[doc(alias = "i16x8.relaxed_laneselect")] - pub fn i16x8_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { - Self::v128_bitselect(v1, v2, c) - } - - #[doc(alias = "i32x4.relaxed_laneselect")] - pub fn i32x4_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { - Self::v128_bitselect(v1, v2, c) - } - - #[doc(alias = "i64x2.relaxed_laneselect")] - pub fn i64x2_relaxed_laneselect(v1: Self, v2: Self, c: Self) -> Self { - Self::v128_bitselect(v1, v2, c) - } - - #[doc(alias = "i16x8.relaxed_q15mulr_s")] - pub fn i16x8_relaxed_q15mulr_s(self, rhs: Self) -> Self { - self.i16x8_q15mulr_sat_s(rhs) - } - - #[doc(alias = "i16x8.relaxed_dot_i8x16_i7x16_s")] - pub fn i16x8_relaxed_dot_i8x16_i7x16_s(self, rhs: Self) -> Self { - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let mut out = [0i16; 8]; - - for (dst, (a_pair, b_pair)) in out.iter_mut().zip(a.chunks_exact(2).zip(b.chunks_exact(2))) { - let prod0 = (a_pair[0] as i16) * (b_pair[0] as i16); - let prod1 = (a_pair[1] as i16) * (b_pair[1] as i16); - *dst = prod0.wrapping_add(prod1); - } - - Self::from_i16x8(out) - } - - #[doc(alias = "i32x4.relaxed_dot_i8x16_i7x16_add_s")] - pub fn i32x4_relaxed_dot_i8x16_i7x16_add_s(self, rhs: Self, acc: Self) -> Self { - let a = self.as_i8x16(); - let b = rhs.as_i8x16(); - let c = acc.as_i32x4(); - let mut out = [0i32; 4]; - - for (i, dst) in out.iter_mut().enumerate() { - let base = i * 4; - let mut sum = 0i32; - for j in 0..4 { - sum = sum.wrapping_add((a[base + j] as i32).wrapping_mul(b[base + j] as i32)); - } - *dst = sum.wrapping_add(c[i]); - } - - Self::from_i32x4(out) - } - - simd_cmp_mask!(i8x16_eq, "i8x16.eq", i8x16_eq, i8, 16, as_i8x16, from_i8x16, ==); - simd_cmp_mask!(i16x8_eq, "i16x8.eq", i16x8_eq, i16, 8, as_i16x8, from_i16x8, ==); - simd_cmp_mask!(i32x4_eq, "i32x4.eq", i32x4_eq, i32, 4, as_i32x4, from_i32x4, ==); - simd_cmp_mask!(i64x2_eq, "i64x2.eq", i64x2_eq, i64, 2, as_i64x2, from_i64x2, ==); - simd_cmp_mask!(i8x16_ne, "i8x16.ne", i8x16_ne, i8, 16, as_i8x16, from_i8x16, !=); - simd_cmp_mask!(i16x8_ne, "i16x8.ne", i16x8_ne, i16, 8, as_i16x8, from_i16x8, !=); - simd_cmp_mask!(i32x4_ne, "i32x4.ne", i32x4_ne, i32, 4, as_i32x4, from_i32x4, !=); - simd_cmp_mask!(i64x2_ne, "i64x2.ne", i64x2_ne, i64, 2, as_i64x2, from_i64x2, !=); - simd_cmp_mask!(i8x16_lt_s, "i8x16.lt_s", i8x16_lt, i8, 16, as_i8x16, from_i8x16, <); - simd_cmp_mask!(i16x8_lt_s, "i16x8.lt_s", i16x8_lt, i16, 8, as_i16x8, from_i16x8, <); - simd_cmp_mask!(i32x4_lt_s, "i32x4.lt_s", i32x4_lt, i32, 4, as_i32x4, from_i32x4, <); - simd_cmp_mask!(i64x2_lt_s, "i64x2.lt_s", i64x2_lt, i64, 2, as_i64x2, from_i64x2, <); - simd_cmp_mask!(i8x16_lt_u, "i8x16.lt_u", u8x16_lt, i8, 16, as_u8x16, from_i8x16, <); - simd_cmp_mask!(i16x8_lt_u, "i16x8.lt_u", u16x8_lt, i16, 8, as_u16x8, from_i16x8, <); - simd_cmp_mask!(i32x4_lt_u, "i32x4.lt_u", u32x4_lt, i32, 4, as_u32x4, from_i32x4, <); - - simd_cmp_delegate!(i8x16_gt_s, "i8x16.gt_s", i8x16_lt_s); - simd_cmp_delegate!(i16x8_gt_s, "i16x8.gt_s", i16x8_lt_s); - simd_cmp_delegate!(i32x4_gt_s, "i32x4.gt_s", i32x4_lt_s); - simd_cmp_delegate!(i64x2_gt_s, "i64x2.gt_s", i64x2_lt_s); - simd_cmp_delegate!(i8x16_gt_u, "i8x16.gt_u", i8x16_lt_u); - simd_cmp_delegate!(i16x8_gt_u, "i16x8.gt_u", i16x8_lt_u); - simd_cmp_delegate!(i32x4_gt_u, "i32x4.gt_u", i32x4_lt_u); - simd_cmp_delegate!(i8x16_le_s, "i8x16.le_s", i8x16_ge_s); - simd_cmp_delegate!(i16x8_le_s, "i16x8.le_s", i16x8_ge_s); - simd_cmp_delegate!(i32x4_le_s, "i32x4.le_s", i32x4_ge_s); - simd_cmp_delegate!(i64x2_le_s, "i64x2.le_s", i64x2_ge_s); - simd_cmp_delegate!(i8x16_le_u, "i8x16.le_u", i8x16_ge_u); - simd_cmp_delegate!(i16x8_le_u, "i16x8.le_u", i16x8_ge_u); - simd_cmp_delegate!(i32x4_le_u, "i32x4.le_u", i32x4_ge_u); - - simd_cmp_mask!(i8x16_ge_s, "i8x16.ge_s", i8x16_ge, i8, 16, as_i8x16, from_i8x16, >=); - simd_cmp_mask!(i16x8_ge_s, "i16x8.ge_s", i16x8_ge, i16, 8, as_i16x8, from_i16x8, >=); - simd_cmp_mask!(i32x4_ge_s, "i32x4.ge_s", i32x4_ge, i32, 4, as_i32x4, from_i32x4, >=); - simd_cmp_mask!(i64x2_ge_s, "i64x2.ge_s", i64x2_ge, i64, 2, as_i64x2, from_i64x2, >=); - simd_cmp_mask!(i8x16_ge_u, "i8x16.ge_u", u8x16_ge, i8, 16, as_u8x16, from_i8x16, >=); - simd_cmp_mask!(i16x8_ge_u, "i16x8.ge_u", u16x8_ge, i16, 8, as_u16x8, from_i16x8, >=); - simd_cmp_mask!(i32x4_ge_u, "i32x4.ge_u", u32x4_ge, i32, 4, as_u32x4, from_i32x4, >=); - - simd_abs_const!(i8x16_abs, "i8x16.abs", i8, 16, as_i8x16, from_i8x16); - simd_abs_const!(i16x8_abs, "i16x8.abs", i16, 8, as_i16x8, from_i16x8); - simd_abs_const!(i32x4_abs, "i32x4.abs", i32, 4, as_i32x4, from_i32x4); - simd_abs_const!(i64x2_abs, "i64x2.abs", i64, 2, as_i64x2, from_i64x2); - - simd_neg!(i8x16_neg, "i8x16.neg", i8x16_neg, i8, 16, as_i8x16, from_i8x16); - simd_neg!(i16x8_neg, "i16x8.neg", i16x8_neg, i16, 8, as_i16x8, from_i16x8); - simd_neg!(i32x4_neg, "i32x4.neg", i32x4_neg, i32, 4, as_i32x4, from_i32x4); - simd_neg!(i64x2_neg, "i64x2.neg", i64x2_neg, i64, 2, as_i64x2, from_i64x2); - - simd_minmax!(i8x16_min_s, "i8x16.min_s", i8x16_min, i8, 16, as_i8x16, from_i8x16, <); - simd_minmax!(i16x8_min_s, "i16x8.min_s", i16x8_min, i16, 8, as_i16x8, from_i16x8, <); - simd_minmax!(i32x4_min_s, "i32x4.min_s", i32x4_min, i32, 4, as_i32x4, from_i32x4, <); - simd_minmax!(i8x16_min_u, "i8x16.min_u", u8x16_min, u8, 16, as_u8x16, from_u8x16, <); - simd_minmax!(i16x8_min_u, "i16x8.min_u", u16x8_min, u16, 8, as_u16x8, from_u16x8, <); - simd_minmax!(i32x4_min_u, "i32x4.min_u", u32x4_min, u32, 4, as_u32x4, from_u32x4, <); - simd_minmax!(i8x16_max_s, "i8x16.max_s", i8x16_max, i8, 16, as_i8x16, from_i8x16, >); - simd_minmax!(i16x8_max_s, "i16x8.max_s", i16x8_max, i16, 8, as_i16x8, from_i16x8, >); - simd_minmax!(i32x4_max_s, "i32x4.max_s", i32x4_max, i32, 4, as_i32x4, from_i32x4, >); - simd_minmax!(i8x16_max_u, "i8x16.max_u", u8x16_max, u8, 16, as_u8x16, from_u8x16, >); - simd_minmax!(i16x8_max_u, "i16x8.max_u", u16x8_max, u16, 8, as_u16x8, from_u16x8, >); - simd_minmax!(i32x4_max_u, "i32x4.max_u", u32x4_max, u32, 4, as_u32x4, from_u32x4, >); - - simd_cmp_mask_const!(f32x4_eq, "f32x4.eq", i32, 4, as_f32x4, from_i32x4, ==); - simd_cmp_mask_const!(f64x2_eq, "f64x2.eq", i64, 2, as_f64x2, from_i64x2, ==); - simd_cmp_mask_const!(f32x4_ne, "f32x4.ne", i32, 4, as_f32x4, from_i32x4, !=); - simd_cmp_mask_const!(f64x2_ne, "f64x2.ne", i64, 2, as_f64x2, from_i64x2, !=); - simd_cmp_mask_const!(f32x4_lt, "f32x4.lt", i32, 4, as_f32x4, from_i32x4, <); - simd_cmp_mask_const!(f64x2_lt, "f64x2.lt", i64, 2, as_f64x2, from_i64x2, <); - - #[doc(alias = "f32x4.gt")] - pub fn f32x4_gt(self, rhs: Self) -> Self { - rhs.f32x4_lt(self) - } - - #[doc(alias = "f64x2.gt")] - pub fn f64x2_gt(self, rhs: Self) -> Self { - rhs.f64x2_lt(self) - } - - simd_cmp_mask_const!(f32x4_le, "f32x4.le", i32, 4, as_f32x4, from_i32x4, <=); - simd_cmp_mask_const!(f64x2_le, "f64x2.le", i64, 2, as_f64x2, from_i64x2, <=); - simd_cmp_mask_const!(f32x4_ge, "f32x4.ge", i32, 4, as_f32x4, from_i32x4, >=); - simd_cmp_mask_const!(f64x2_ge, "f64x2.ge", i64, 2, as_f64x2, from_i64x2, >=); - - simd_float_unary!(f32x4_ceil, "f32x4.ceil", map_f32x4, |x| canonicalize_simd_f32_nan(x.ceil())); - simd_float_unary!(f64x2_ceil, "f64x2.ceil", map_f64x2, |x| canonicalize_simd_f64_nan(x.ceil())); - simd_float_unary!(f32x4_floor, "f32x4.floor", map_f32x4, |x| canonicalize_simd_f32_nan(x.floor())); - simd_float_unary!(f64x2_floor, "f64x2.floor", map_f64x2, |x| canonicalize_simd_f64_nan(x.floor())); - simd_float_unary!(f32x4_trunc, "f32x4.trunc", map_f32x4, |x| canonicalize_simd_f32_nan(x.trunc())); - simd_float_unary!(f64x2_trunc, "f64x2.trunc", map_f64x2, |x| canonicalize_simd_f64_nan(x.trunc())); - simd_float_unary!(f32x4_nearest, "f32x4.nearest", map_f32x4, |x| canonicalize_simd_f32_nan( - TinywasmFloatExt::tw_nearest(x) - )); - simd_float_unary!(f64x2_nearest, "f64x2.nearest", map_f64x2, |x| canonicalize_simd_f64_nan( - TinywasmFloatExt::tw_nearest(x) - )); - simd_float_unary!(f32x4_abs, "f32x4.abs", map_f32x4, f32::abs); - simd_float_unary!(f64x2_abs, "f64x2.abs", map_f64x2, f64::abs); - simd_float_unary!(f32x4_neg, "f32x4.neg", map_f32x4, |x| -x); - simd_float_unary!(f64x2_neg, "f64x2.neg", map_f64x2, |x| -x); - simd_float_unary!(f32x4_sqrt, "f32x4.sqrt", map_f32x4, |x| canonicalize_simd_f32_nan(x.sqrt())); - simd_float_unary!(f64x2_sqrt, "f64x2.sqrt", map_f64x2, |x| canonicalize_simd_f64_nan(x.sqrt())); - - simd_float_binary!(f32x4_add, "f32x4.add", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a + b)); - simd_float_binary!(f64x2_add, "f64x2.add", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a + b)); - simd_float_binary!(f32x4_sub, "f32x4.sub", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a - b)); - simd_float_binary!(f64x2_sub, "f64x2.sub", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a - b)); - simd_float_binary!(f32x4_mul, "f32x4.mul", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a * b)); - simd_float_binary!(f64x2_mul, "f64x2.mul", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a * b)); - simd_float_binary!(f32x4_div, "f32x4.div", zip_f32x4, |a, b| canonicalize_simd_f32_nan(a / b)); - simd_float_binary!(f64x2_div, "f64x2.div", zip_f64x2, |a, b| canonicalize_simd_f64_nan(a / b)); - simd_float_binary!(f32x4_min, "f32x4.min", zip_f32x4, TinywasmFloatExt::tw_minimum); - simd_float_binary!(f64x2_min, "f64x2.min", zip_f64x2, TinywasmFloatExt::tw_minimum); - simd_float_binary!(f32x4_max, "f32x4.max", zip_f32x4, TinywasmFloatExt::tw_maximum); - simd_float_binary!(f64x2_max, "f64x2.max", zip_f64x2, TinywasmFloatExt::tw_maximum); - simd_float_binary!(f32x4_pmin, "f32x4.pmin", zip_f32x4, |a, b| if b < a { b } else { a }); - simd_float_binary!(f64x2_pmin, "f64x2.pmin", zip_f64x2, |a, b| if b < a { b } else { a }); - simd_float_binary!(f32x4_pmax, "f32x4.pmax", zip_f32x4, |a, b| if b > a { b } else { a }); - simd_float_binary!(f64x2_pmax, "f64x2.pmax", zip_f64x2, |a, b| if b > a { b } else { a }); - - #[doc(alias = "f32x4.relaxed_madd")] - pub fn f32x4_relaxed_madd(self, b: Self, c: Self) -> Self { - self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(x * y)) - .zip_f32x4(c, |xy, z| canonicalize_simd_f32_nan(xy + z)) - } - - #[doc(alias = "f32x4.relaxed_nmadd")] - pub fn f32x4_relaxed_nmadd(self, b: Self, c: Self) -> Self { - self.zip_f32x4(b, |x, y| canonicalize_simd_f32_nan(-(x * y))) - .zip_f32x4(c, |neg_xy, z| canonicalize_simd_f32_nan(neg_xy + z)) - } - - #[doc(alias = "f64x2.relaxed_madd")] - pub fn f64x2_relaxed_madd(self, b: Self, c: Self) -> Self { - self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(x * y)) - .zip_f64x2(c, |xy, z| canonicalize_simd_f64_nan(xy + z)) - } - - #[doc(alias = "f64x2.relaxed_nmadd")] - pub fn f64x2_relaxed_nmadd(self, b: Self, c: Self) -> Self { - self.zip_f64x2(b, |x, y| canonicalize_simd_f64_nan(-(x * y))) - .zip_f64x2(c, |neg_xy, z| canonicalize_simd_f64_nan(neg_xy + z)) - } - - #[doc(alias = "f32x4.relaxed_min")] - pub fn f32x4_relaxed_min(self, rhs: Self) -> Self { - self.f32x4_min(rhs) - } - - #[doc(alias = "f64x2.relaxed_min")] - pub fn f64x2_relaxed_min(self, rhs: Self) -> Self { - self.f64x2_min(rhs) - } - - #[doc(alias = "f32x4.relaxed_max")] - pub fn f32x4_relaxed_max(self, rhs: Self) -> Self { - self.f32x4_max(rhs) - } - - #[doc(alias = "f64x2.relaxed_max")] - pub fn f64x2_relaxed_max(self, rhs: Self) -> Self { - self.f64x2_max(rhs) - } - - #[doc(alias = "i32x4.relaxed_trunc_f32x4_s")] - pub fn i32x4_relaxed_trunc_f32x4_s(self) -> Self { - self.i32x4_trunc_sat_f32x4_s() - } - - #[doc(alias = "i32x4.relaxed_trunc_f32x4_u")] - pub fn i32x4_relaxed_trunc_f32x4_u(self) -> Self { - self.i32x4_trunc_sat_f32x4_u() - } - - #[doc(alias = "i32x4.relaxed_trunc_f64x2_s_zero")] - pub fn i32x4_relaxed_trunc_f64x2_s_zero(self) -> Self { - self.i32x4_trunc_sat_f64x2_s_zero() - } - - #[doc(alias = "i32x4.relaxed_trunc_f64x2_u_zero")] - pub fn i32x4_relaxed_trunc_f64x2_u_zero(self) -> Self { - self.i32x4_trunc_sat_f64x2_u_zero() - } - - #[doc(alias = "i32x4.trunc_sat_f32x4_s")] - pub fn i32x4_trunc_sat_f32x4_s(self) -> Self { - let v = self.as_f32x4(); - Self::from_i32x4([ - trunc_sat_f32_to_i32(v[0]), - trunc_sat_f32_to_i32(v[1]), - trunc_sat_f32_to_i32(v[2]), - trunc_sat_f32_to_i32(v[3]), - ]) - } - - #[doc(alias = "i32x4.trunc_sat_f32x4_u")] - pub fn i32x4_trunc_sat_f32x4_u(self) -> Self { - let v = self.as_f32x4(); - Self::from_u32x4([ - trunc_sat_f32_to_u32(v[0]), - trunc_sat_f32_to_u32(v[1]), - trunc_sat_f32_to_u32(v[2]), - trunc_sat_f32_to_u32(v[3]), - ]) - } - - #[doc(alias = "i32x4.trunc_sat_f64x2_s_zero")] - pub fn i32x4_trunc_sat_f64x2_s_zero(self) -> Self { - let v = self.as_f64x2(); - Self::from_i32x4([trunc_sat_f64_to_i32(v[0]), trunc_sat_f64_to_i32(v[1]), 0, 0]) - } - - #[doc(alias = "i32x4.trunc_sat_f64x2_u_zero")] - pub fn i32x4_trunc_sat_f64x2_u_zero(self) -> Self { - let v = self.as_f64x2(); - Self::from_u32x4([trunc_sat_f64_to_u32(v[0]), trunc_sat_f64_to_u32(v[1]), 0, 0]) - } - - #[doc(alias = "f32x4.convert_i32x4_s")] - pub fn f32x4_convert_i32x4_s(self) -> Self { - let v = self.as_i32x4(); - Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) - } - - #[doc(alias = "f32x4.convert_i32x4_u")] - pub fn f32x4_convert_i32x4_u(self) -> Self { - let v = self.as_u32x4(); - Self::from_f32x4([v[0] as f32, v[1] as f32, v[2] as f32, v[3] as f32]) - } - - #[doc(alias = "f64x2.convert_low_i32x4_s")] - pub fn f64x2_convert_low_i32x4_s(self) -> Self { - let v = self.as_i32x4(); - Self::from_f64x2([v[0] as f64, v[1] as f64]) - } - - #[doc(alias = "f64x2.convert_low_i32x4_u")] - pub fn f64x2_convert_low_i32x4_u(self) -> Self { - let v = self.as_u32x4(); - Self::from_f64x2([v[0] as f64, v[1] as f64]) - } - - #[doc(alias = "f32x4.demote_f64x2_zero")] - pub fn f32x4_demote_f64x2_zero(self) -> Self { - let v = self.as_f64x2(); - Self::from_f32x4([v[0] as f32, v[1] as f32, 0.0, 0.0]) - } - - #[doc(alias = "f64x2.promote_low_f32x4")] - pub fn f64x2_promote_low_f32x4(self) -> Self { - let v = self.as_f32x4(); - Self::from_f64x2([v[0] as f64, v[1] as f64]) - } - - pub fn splat_i16(src: i16) -> Self { - Self::from_i16x8([src; 8]) - } - - pub fn splat_i32(src: i32) -> Self { - Self::from_i32x4([src; 4]) - } - - pub fn splat_i64(src: i64) -> Self { - Self::from_i64x2([src; 2]) - } - - pub fn splat_f32(src: f32) -> Self { - Self::splat_i32(src.to_bits() as i32) - } - - pub fn splat_f64(src: f64) -> Self { - Self::splat_i64(src.to_bits() as i64) - } - - pub fn extract_lane_i8(self, lane: u8) -> i8 { - debug_assert!(lane < 16); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - bytes[lane] as i8 - } - - pub fn extract_lane_u8(self, lane: u8) -> u8 { - debug_assert!(lane < 16); - let lane = lane as usize; - let bytes = self.to_le_bytes(); - bytes[lane] - } - - pub fn extract_lane_i16(self, lane: u8) -> i16 { - i16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) - } - - pub fn extract_lane_u16(self, lane: u8) -> u16 { - u16::from_le_bytes(self.extract_lane_bytes::<2>(lane, 8)) - } - - pub fn extract_lane_i32(self, lane: u8) -> i32 { - i32::from_le_bytes(self.extract_lane_bytes::<4>(lane, 4)) - } - - pub fn extract_lane_i64(self, lane: u8) -> i64 { - i64::from_le_bytes(self.extract_lane_bytes::<8>(lane, 2)) - } - - pub fn extract_lane_f32(self, lane: u8) -> f32 { - f32::from_bits(self.extract_lane_i32(lane) as u32) - } - - pub fn extract_lane_f64(self, lane: u8) -> f64 { - f64::from_bits(self.extract_lane_i64(lane) as u64) - } - - fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { - debug_assert!(lane < lane_count); - let bytes = self.to_le_bytes(); - let start = lane as usize * LANE_BYTES; - let mut out = [0u8; LANE_BYTES]; - out.copy_from_slice(&bytes[start..start + LANE_BYTES]); - out - } - - fn replace_lane_bytes(self, lane: u8, value: [u8; LANE_BYTES], lane_count: u8) -> Self { - debug_assert!(lane < lane_count); - let mut bytes = self.to_le_bytes(); - let start = lane as usize * LANE_BYTES; - bytes[start..start + LANE_BYTES].copy_from_slice(&value); - Self::from_le_bytes(bytes) - } -} - -const fn canonicalize_simd_f32_nan(x: f32) -> f32 { - #[cfg(feature = "canonicalize_nans")] - if x.is_nan() { - f32::NAN - } else { - x - } - #[cfg(not(feature = "canonicalize_nans"))] - x -} - -const fn canonicalize_simd_f64_nan(x: f64) -> f64 { - #[cfg(feature = "canonicalize_nans")] - if x.is_nan() { - f64::NAN - } else { - x - } - #[cfg(not(feature = "canonicalize_nans"))] - x -} - -const fn saturate_i16_to_i8(x: i16) -> i8 { - match x { - v if v > i8::MAX as i16 => i8::MAX, - v if v < i8::MIN as i16 => i8::MIN, - v => v as i8, - } -} - -const fn saturate_i16_to_u8(x: i16) -> u8 { - match x { - v if v <= 0 => 0, - v if v > u8::MAX as i16 => u8::MAX, - v => v as u8, - } -} - -const fn saturate_i32_to_i16(x: i32) -> i16 { - match x { - v if v > i16::MAX as i32 => i16::MAX, - v if v < i16::MIN as i32 => i16::MIN, - v => v as i16, - } -} - -const fn saturate_i32_to_u16(x: i32) -> u16 { - match x { - v if v <= 0 => 0, - v if v > u16::MAX as i32 => u16::MAX, - v => v as u16, - } -} - -fn trunc_sat_f32_to_i32(v: f32) -> i32 { - match v { - x if x.is_nan() => 0, - x if x <= i32::MIN as f32 - (1 << 8) as f32 => i32::MIN, - x if x >= (i32::MAX as f32 + 1.0) => i32::MAX, - x => x.trunc() as i32, - } -} - -fn trunc_sat_f32_to_u32(v: f32) -> u32 { - match v { - x if x.is_nan() || x <= -1.0_f32 => 0, - x if x >= (u32::MAX as f32 + 1.0) => u32::MAX, - x => x.trunc() as u32, - } -} - -fn trunc_sat_f64_to_i32(v: f64) -> i32 { - match v { - x if x.is_nan() => 0, - x if x <= i32::MIN as f64 - 1.0_f64 => i32::MIN, - x if x >= (i32::MAX as f64 + 1.0) => i32::MAX, - x => x.trunc() as i32, - } -} - -fn trunc_sat_f64_to_u32(v: f64) -> u32 { - match v { - x if x.is_nan() || x <= -1.0_f64 => 0, - x if x >= (u32::MAX as f64 + 1.0) => u32::MAX, - x => x.trunc() as u32, - } -} diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index 48b688c7..ec68035d 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -1,4 +1,4 @@ -use crate::{Result, interpreter::value128::Value128}; +use crate::{Result, interpreter::simd::Value128}; use super::stack::{CallFrame, ValueStack}; use tinywasm_types::LocalAddr; diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index cdcb1a14..36cb5405 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -4,7 +4,7 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, rust_2018_idioms, unreachable_pub)] -#![deny(unsafe_code)] +#![cfg_attr(not(feature = "simd-x86"), deny(unsafe_code))] //! A tiny WebAssembly Runtime written in Rust //! From 479b540436d80771c9024994ab1efd55338f7ae5 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 22:41:18 +0200 Subject: [PATCH 42/47] feat: x86 simd implementation for i8x16_swizzle + i8x16_shuffle Signed-off-by: Henry --- crates/tinywasm/Cargo.toml | 2 +- .../src/interpreter/simd/instructions.rs | 106 ++++++++++++++---- .../tinywasm/src/interpreter/simd/macros.rs | 3 + crates/tinywasm/src/interpreter/simd/mod.rs | 2 + crates/tinywasm/src/interpreter/simd/tests.rs | 60 ++++++++++ crates/tinywasm/src/interpreter/simd/utils.rs | 3 + crates/tinywasm/src/lib.rs | 3 +- 7 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 crates/tinywasm/src/interpreter/simd/tests.rs diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 1850680b..dbd92335 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -51,7 +51,7 @@ canonicalize_nans=[] # derive Debug for runtime/types structs debug=["tinywasm-types/debug"] -# enable x86-specific SIMD intrinsics in Value128 +# enable x86-specific SIMD intrinsics in Value128 (uses unsafe code) # note: for x86 backend selection, compile with x86-64-v3 target features # (for example: `RUSTFLAGS="-C target-cpu=x86-64-v3"`) simd-x86=[] diff --git a/crates/tinywasm/src/interpreter/simd/instructions.rs b/crates/tinywasm/src/interpreter/simd/instructions.rs index 8b661d02..97e2360f 100644 --- a/crates/tinywasm/src/interpreter/simd/instructions.rs +++ b/crates/tinywasm/src/interpreter/simd/instructions.rs @@ -8,6 +8,20 @@ use super::super::no_std_floats::NoStdFloatExt; use core::arch::wasm32 as wasm; #[cfg(target_arch = "wasm64")] use core::arch::wasm64 as wasm; +#[cfg(all( + feature = "simd-x86", + target_arch = "x86_64", + target_feature = "sse4.2", + target_feature = "avx", + target_feature = "avx2", + target_feature = "bmi1", + target_feature = "bmi2", + target_feature = "fma", + target_feature = "lzcnt", + target_feature = "movbe", + target_feature = "popcnt" +))] +use core::arch::x86_64 as x86; impl Value128 { #[doc(alias = "v128.any_true")] @@ -132,20 +146,41 @@ impl Value128 { #[doc(alias = "i8x16.swizzle")] pub fn i8x16_swizzle(self, s: Self) -> Self { - #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] - return Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())); - - let a = self.to_le_bytes(); - let idx = s.to_le_bytes(); - let mut out = [0u8; 16]; - let mut i = 0; - while i < 16 { - let j = idx[i]; - let lane = a[(j & 0x0f) as usize]; - out[i] = if j < 16 { lane } else { 0 }; - i += 1; + simd_impl! { + wasm => { Self::from_wasm_v128(wasm::i8x16_swizzle(self.to_wasm_v128(), s.to_wasm_v128())) } + x86 => { + let a = self.to_le_bytes(); + let idx = s.to_le_bytes(); + let mut mask = [0u8; 16]; + for i in 0..16 { + let j = idx[i]; + mask[i] = if j < 16 { j & 0x0f } else { 0x80 }; + } + + // SAFETY: `a`, `mask`, and `out` are valid 16-byte buffers, and `_mm_loadu/_mm_storeu` support unaligned accesses. + #[allow(unsafe_code)] + let out = unsafe { + let a_vec = x86::_mm_loadu_si128(a.as_ptr().cast::()); + let mask_vec = x86::_mm_loadu_si128(mask.as_ptr().cast::()); + let result = x86::_mm_shuffle_epi8(a_vec, mask_vec); + let mut out = [0u8; 16]; + x86::_mm_storeu_si128(out.as_mut_ptr().cast::(), result); + out + }; + Self::from_le_bytes(out) + } + generic => { + let a = self.to_le_bytes(); + let idx = s.to_le_bytes(); + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i]; + let lane = a[(j & 0x0f) as usize]; + out[i] = if j < 16 { lane } else { 0 }; + } + Self::from_le_bytes(out) + } } - Self::from_le_bytes(out) } #[doc(alias = "i8x16.relaxed_swizzle")] @@ -155,14 +190,45 @@ impl Value128 { #[doc(alias = "i8x16.shuffle")] pub fn i8x16_shuffle(a: Self, b: Self, idx: [u8; 16]) -> Self { - let mut src = [0u8; 32]; - src[..16].copy_from_slice(&a.to_le_bytes()); - src[16..].copy_from_slice(&b.to_le_bytes()); - let mut out = [0u8; 16]; - for i in 0..16 { - out[i] = src[(idx[i] & 31) as usize]; + simd_impl! { + x86 => { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + let mut mask_a = [0u8; 16]; + let mut mask_b = [0u8; 16]; + for i in 0..16 { + let j = idx[i] & 31; + mask_a[i] = if j < 16 { j } else { 0x80 }; + mask_b[i] = if j < 16 { 0x80 } else { j & 0x0f }; + } + + // SAFETY: all inputs are valid 16-byte buffers, and `_mm_loadu/_mm_storeu` support unaligned accesses. + #[allow(unsafe_code)] + let out = unsafe { + let a_vec = x86::_mm_loadu_si128(a_bytes.as_ptr().cast::()); + let b_vec = x86::_mm_loadu_si128(b_bytes.as_ptr().cast::()); + let mask_a_vec = x86::_mm_loadu_si128(mask_a.as_ptr().cast::()); + let mask_b_vec = x86::_mm_loadu_si128(mask_b.as_ptr().cast::()); + let a_part = x86::_mm_shuffle_epi8(a_vec, mask_a_vec); + let b_part = x86::_mm_shuffle_epi8(b_vec, mask_b_vec); + let result = x86::_mm_or_si128(a_part, b_part); + let mut out = [0u8; 16]; + x86::_mm_storeu_si128(out.as_mut_ptr().cast::(), result); + out + }; + Self::from_le_bytes(out) + } + generic => { + let a_bytes = a.to_le_bytes(); + let b_bytes = b.to_le_bytes(); + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i] & 31; + out[i] = if j < 16 { a_bytes[j as usize] } else { b_bytes[(j & 0x0f) as usize] }; + } + Self::from_le_bytes(out) + } } - Self::from_le_bytes(out) } #[doc(alias = "i8x16.splat")] diff --git a/crates/tinywasm/src/interpreter/simd/macros.rs b/crates/tinywasm/src/interpreter/simd/macros.rs index 03948fe7..8cb5a3a9 100644 --- a/crates/tinywasm/src/interpreter/simd/macros.rs +++ b/crates/tinywasm/src/interpreter/simd/macros.rs @@ -1,3 +1,5 @@ +#![allow(unused_macros)] + macro_rules! simd_impl { ($(wasm => $wasm:block)? $(x86 => $x86:block)? generic => $generic:block) => {{ #[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))] @@ -23,6 +25,7 @@ macro_rules! simd_impl { simd_impl!(@pick_x86 $( $x86 )? ; $generic) } + #[allow(unreachable_code)] #[cfg(not(any( any(target_arch = "wasm32", target_arch = "wasm64"), all( diff --git a/crates/tinywasm/src/interpreter/simd/mod.rs b/crates/tinywasm/src/interpreter/simd/mod.rs index 332c9a63..4bf9d93d 100644 --- a/crates/tinywasm/src/interpreter/simd/mod.rs +++ b/crates/tinywasm/src/interpreter/simd/mod.rs @@ -3,6 +3,8 @@ #[macro_use] mod macros; mod instructions; +#[cfg(test)] +mod tests; mod utils; #[cfg(target_arch = "wasm32")] diff --git a/crates/tinywasm/src/interpreter/simd/tests.rs b/crates/tinywasm/src/interpreter/simd/tests.rs new file mode 100644 index 00000000..98a60cfa --- /dev/null +++ b/crates/tinywasm/src/interpreter/simd/tests.rs @@ -0,0 +1,60 @@ +use super::Value128; + +fn ref_swizzle(a: [u8; 16], idx: [u8; 16]) -> [u8; 16] { + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i]; + out[i] = if j < 16 { a[(j & 0x0f) as usize] } else { 0 }; + } + out +} + +fn ref_shuffle(a: [u8; 16], b: [u8; 16], idx: [u8; 16]) -> [u8; 16] { + let mut out = [0u8; 16]; + for i in 0..16 { + let j = idx[i] & 31; + out[i] = if j < 16 { a[j as usize] } else { b[(j & 0x0f) as usize] }; + } + out +} + +#[test] +fn swizzle_matches_reference() { + let a = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; + + for seed in 0u32..512 { + let mut s = [0u8; 16]; + let mut x = seed.wrapping_mul(0x9e37_79b9).wrapping_add(0x7f4a_7c15); + for byte in &mut s { + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *byte = (x & 0xff) as u8; + } + + let got = Value128::from_le_bytes(a).i8x16_swizzle(Value128::from_le_bytes(s)).to_le_bytes(); + let expected = ref_swizzle(a, s); + assert_eq!(got, expected, "seed={seed}"); + } +} + +#[test] +fn shuffle_matches_reference() { + let a = [0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f]; + let b = [0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf]; + + for seed in 0u32..512 { + let mut idx = [0u8; 16]; + let mut x = seed.wrapping_mul(0x85eb_ca6b).wrapping_add(0xc2b2_ae35); + for byte in &mut idx { + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *byte = (x & 0xff) as u8; + } + + let got = Value128::i8x16_shuffle(Value128::from_le_bytes(a), Value128::from_le_bytes(b), idx).to_le_bytes(); + let expected = ref_shuffle(a, b, idx); + assert_eq!(got, expected, "seed={seed}"); + } +} diff --git a/crates/tinywasm/src/interpreter/simd/utils.rs b/crates/tinywasm/src/interpreter/simd/utils.rs index 6c08495e..7f798719 100644 --- a/crates/tinywasm/src/interpreter/simd/utils.rs +++ b/crates/tinywasm/src/interpreter/simd/utils.rs @@ -1,5 +1,8 @@ use super::Value128; +#[cfg(not(feature = "std"))] +use crate::interpreter::no_std_floats::NoStdFloatExt; + impl Value128 { pub(super) fn extract_lane_bytes(self, lane: u8, lane_count: u8) -> [u8; LANE_BYTES] { debug_assert!(lane < lane_count); diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 36cb5405..7afc2f65 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -4,7 +4,8 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables)) ))] #![warn(missing_docs, rust_2018_idioms, unreachable_pub)] -#![cfg_attr(not(feature = "simd-x86"), deny(unsafe_code))] +#![cfg_attr(not(feature = "simd-x86"), forbid(unsafe_code))] +#![cfg_attr(feature = "simd-x86", deny(unsafe_code))] //! A tiny WebAssembly Runtime written in Rust //! From 4b4f7ba271b53e8f3a3be771ca2502db29cbe399 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 23:30:57 +0200 Subject: [PATCH 43/47] chore: refactor stack_op Signed-off-by: Henry --- crates/tinywasm/src/interpreter/executor.rs | 164 +++++++++--------- .../tinywasm/src/interpreter/num_helpers.rs | 23 ++- .../src/interpreter/stack/value_stack.rs | 55 ------ crates/tinywasm/src/interpreter/values.rs | 27 --- 4 files changed, 95 insertions(+), 174 deletions(-) diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index f739906f..794fb3ab 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -50,23 +50,50 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { #[inline(always)] fn exec(&mut self) -> Result> { macro_rules! stack_op { - (simd_unary $method:ident) => { stack_op!(unary Value128, |v| v.$method()) }; - (simd_binary $method:ident) => { stack_op!(binary Value128, |a, b| a.$method(b)) }; - (unary $ty:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary::<$ty>(|$v| Ok($expr))? }; - (binary $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| Ok($expr))? }; - (binary try $ty:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary::<$ty>(|$a, $b| $expr)? }; - (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => { self.store.stack.values.unary_into::<$from, $to>(|$v| Ok($expr))? }; - (binary $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => { self.store.stack.values.binary_into::<$from, $to>(|$a, $b| Ok($expr))? }; - (binary_into2 $from:ty => $to:ty, |$a:ident, $b:ident| $expr:expr) => {{ - let $b = self.store.stack.values.pop::<$from>(); - let $a = self.store.stack.values.pop::<$from>(); + (unary $ty:ty, |$v:ident| $expr:expr) => {{ + let $v = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr)?; + }}; + (binary $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$ty>(); + let $lhs = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr)?; + }}; + (binary try $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$ty>(); + let $lhs = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr?)?; + }}; + (unary $from:ty => $to:ty, |$v:ident| $expr:expr) => {{ + let $v = self.store.stack.values.pop::<$from>(); + self.store.stack.values.push::<$to>($expr)?; + }}; + (binary $from:ty => $to:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$from>(); + let $lhs = self.store.stack.values.pop::<$from>(); + self.store.stack.values.push::<$to>($expr)?; + }}; + (binary_into2 $from:ty => $to:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$from>(); + let $lhs = self.store.stack.values.pop::<$from>(); let out = $expr; self.store.stack.values.push::<$to>(out.0)?; self.store.stack.values.push::<$to>(out.1)?; }}; - (binary $a:ty, $b:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { stack_op!(binary $a, $b => $b, |$lhs, $rhs| $expr) }; - (binary $a:ty, $b:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { self.store.stack.values.binary_mixed::<$a, $b, $res>(|$lhs, $rhs| Ok($expr))? }; - (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => { self.store.stack.values.ternary::<$ty>(|$a, $b, $c| Ok($expr))? }; + (binary $lhs_ty:ty, $rhs_ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => { + stack_op!(binary $lhs_ty, $rhs_ty => $rhs_ty, |$lhs, $rhs| $expr) + }; + (binary $lhs_ty:ty, $rhs_ty:ty => $res:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{ + let $rhs = self.store.stack.values.pop::<$rhs_ty>(); + let $lhs = self.store.stack.values.pop::<$lhs_ty>(); + self.store.stack.values.push::<$res>($expr)?; + }}; + (ternary $ty:ty, |$a:ident, $b:ident, $c:ident| $expr:expr) => {{ + let $c = self.store.stack.values.pop::<$ty>(); + let $b = self.store.stack.values.pop::<$ty>(); + let $a = self.store.stack.values.pop::<$ty>(); + self.store.stack.values.push::<$ty>($expr)?; + }}; (quaternary_into2 $from:ty => $to:ty, |$a:ident, $b:ident, $c:ident, $d:ident| $expr:expr) => {{ let $d = self.store.stack.values.pop::<$from>(); let $c = self.store.stack.values.pop::<$from>(); @@ -121,39 +148,16 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { continue; }, JumpIfNonZero(ip) => if self.exec_jump_if_non_zero(*ip) { continue; }, DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { - let b32 = self.cf.stack_base().s32 + *base32 as u32; - let k32 = *keep32 as usize; - self.store.stack.values.stack_32.truncate_keep(b32 as usize, k32); - let b64 = self.cf.stack_base().s64 + *base64 as u32; - let k64 = *keep64 as usize; - self.store.stack.values.stack_64.truncate_keep(b64 as usize, k64); - let b128 = self.cf.stack_base().s128 + *base128 as u32; - let k128 = *keep128 as usize; - self.store.stack.values.stack_128.truncate_keep(b128 as usize, k128); - let bref = self.cf.stack_base().sref + *base_ref as u32; - let kref = *keep_ref as usize; - self.store.stack.values.stack_ref.truncate_keep(bref as usize, kref); - } - DropKeep32(base, keep) => { - let b = self.cf.stack_base().s32 + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_32.truncate_keep(b as usize, k); - } - DropKeep64(base, keep) => { - let b = self.cf.stack_base().s64 + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_64.truncate_keep(b as usize, k); - } - DropKeep128(base, keep) => { - let b = self.cf.stack_base().s128 + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_128.truncate_keep(b as usize, k); - } - DropKeepRef(base, keep) => { - let b = self.cf.stack_base().sref + *base as u32; - let k = *keep as usize; - self.store.stack.values.stack_ref.truncate_keep(b as usize, k); + let stack_base = self.cf.stack_base(); + self.store.stack.values.stack_32.truncate_keep((stack_base.s32 + *base32 as u32) as usize, *keep32 as usize); + self.store.stack.values.stack_64.truncate_keep((stack_base.s64 + *base64 as u32) as usize, *keep64 as usize); + self.store.stack.values.stack_128.truncate_keep((stack_base.s128 + *base128 as u32) as usize, *keep128 as usize); + self.store.stack.values.stack_ref.truncate_keep((stack_base.sref + *base_ref as u32) as usize, *keep_ref as usize); } + DropKeep32(base, keep) => self.store.stack.values.stack_32.truncate_keep((self.cf.stack_base().s32 + *base as u32) as usize, *keep as usize), + DropKeep64(base, keep) => self.store.stack.values.stack_64.truncate_keep((self.cf.stack_base().s64 + *base as u32) as usize, *keep as usize), + DropKeep128(base, keep) => self.store.stack.values.stack_128.truncate_keep((self.cf.stack_base().s128 + *base as u32) as usize, *keep as usize), + DropKeepRef(base, keep) => self.store.stack.values.stack_ref.truncate_keep((self.cf.stack_base().sref + *base as u32) as usize, *keep as usize), BranchTable(default_ip, start, len) => { self.exec_branch_table(*default_ip, *start, *len); continue; } Return => { if self.exec_return() { return Ok(Some(())); } continue; } LocalGet32(local_index) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *local_index))?, @@ -412,7 +416,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { V128AndNot => stack_op!(binary Value128, |a, b| a.v128_andnot(b)), V128Or => stack_op!(binary Value128, |a, b| a.v128_or(b)), V128Xor => stack_op!(binary Value128, |a, b| a.v128_xor(b)), - V128Bitselect => stack_op!(ternary Value128, |v1, v2, c| Value128::v128_bitselect(v1, v2, c)), + V128Bitselect => stack_op!(ternary Value128, |a, b, c| Value128::v128_bitselect(a, b, c)), V128AnyTrue => stack_op!(unary Value128 => i32, |v| v.v128_any_true() as i32), I8x16Swizzle => stack_op!(binary Value128, |a, s| a.i8x16_swizzle(s)), I8x16RelaxedSwizzle => stack_op!(binary Value128, |a, s| a.i8x16_relaxed_swizzle(s)), @@ -604,43 +608,43 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I8x16Shuffle(idx) => { let idx = self.func.data.v128_constants[*idx as usize].to_le_bytes(); stack_op!(binary Value128, |a, b| Value128::i8x16_shuffle(a, b, idx)) } I16x8Q15MulrSatS => stack_op!(binary Value128, |a, b| a.i16x8_q15mulr_sat_s(b)), I32x4DotI16x8S => stack_op!(binary Value128, |a, b| a.i32x4_dot_i16x8_s(b)), - I8x16RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i8x16_relaxed_laneselect(v1, v2, c)), - I16x8RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i16x8_relaxed_laneselect(v1, v2, c)), - I32x4RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i32x4_relaxed_laneselect(v1, v2, c)), - I64x2RelaxedLaneselect => stack_op!(ternary Value128, |v1, v2, c| Value128::i64x2_relaxed_laneselect(v1, v2, c)), + I8x16RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i8x16_relaxed_laneselect(a, b, c)), + I16x8RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i16x8_relaxed_laneselect(a, b, c)), + I32x4RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i32x4_relaxed_laneselect(a, b, c)), + I64x2RelaxedLaneselect => stack_op!(ternary Value128, |a, b, c| Value128::i64x2_relaxed_laneselect(a, b, c)), I16x8RelaxedQ15mulrS => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_q15mulr_s(b)), I16x8RelaxedDotI8x16I7x16S => stack_op!(binary Value128, |a, b| a.i16x8_relaxed_dot_i8x16_i7x16_s(b)), I32x4RelaxedDotI8x16I7x16AddS => stack_op!(ternary Value128, |a, b, c| a.i32x4_relaxed_dot_i8x16_i7x16_add_s(b, c)), - F32x4Ceil => stack_op!(simd_unary f32x4_ceil), - F64x2Ceil => stack_op!(simd_unary f64x2_ceil), - F32x4Floor => stack_op!(simd_unary f32x4_floor), - F64x2Floor => stack_op!(simd_unary f64x2_floor), - F32x4Trunc => stack_op!(simd_unary f32x4_trunc), - F64x2Trunc => stack_op!(simd_unary f64x2_trunc), - F32x4Nearest => stack_op!(simd_unary f32x4_nearest), - F64x2Nearest => stack_op!(simd_unary f64x2_nearest), - F32x4Abs => stack_op!(simd_unary f32x4_abs), - F64x2Abs => stack_op!(simd_unary f64x2_abs), - F32x4Neg => stack_op!(simd_unary f32x4_neg), - F64x2Neg => stack_op!(simd_unary f64x2_neg), - F32x4Sqrt => stack_op!(simd_unary f32x4_sqrt), - F64x2Sqrt => stack_op!(simd_unary f64x2_sqrt), - F32x4Add => stack_op!(simd_binary f32x4_add), - F64x2Add => stack_op!(simd_binary f64x2_add), - F32x4Sub => stack_op!(simd_binary f32x4_sub), - F64x2Sub => stack_op!(simd_binary f64x2_sub), - F32x4Mul => stack_op!(simd_binary f32x4_mul), - F64x2Mul => stack_op!(simd_binary f64x2_mul), - F32x4Div => stack_op!(simd_binary f32x4_div), - F64x2Div => stack_op!(simd_binary f64x2_div), - F32x4Min => stack_op!(simd_binary f32x4_min), - F64x2Min => stack_op!(simd_binary f64x2_min), - F32x4Max => stack_op!(simd_binary f32x4_max), - F64x2Max => stack_op!(simd_binary f64x2_max), - F32x4PMin => stack_op!(simd_binary f32x4_pmin), - F32x4PMax => stack_op!(simd_binary f32x4_pmax), - F64x2PMin => stack_op!(simd_binary f64x2_pmin), - F64x2PMax => stack_op!(simd_binary f64x2_pmax), + F32x4Ceil => stack_op!(unary Value128, |v| v.f32x4_ceil()), + F64x2Ceil => stack_op!(unary Value128, |v| v.f64x2_ceil()), + F32x4Floor => stack_op!(unary Value128, |v| v.f32x4_floor()), + F64x2Floor => stack_op!(unary Value128, |v| v.f64x2_floor()), + F32x4Trunc => stack_op!(unary Value128, |v| v.f32x4_trunc()), + F64x2Trunc => stack_op!(unary Value128, |v| v.f64x2_trunc()), + F32x4Nearest => stack_op!(unary Value128, |v| v.f32x4_nearest()), + F64x2Nearest => stack_op!(unary Value128, |v| v.f64x2_nearest()), + F32x4Abs => stack_op!(unary Value128, |v| v.f32x4_abs()), + F64x2Abs => stack_op!(unary Value128, |v| v.f64x2_abs()), + F32x4Neg => stack_op!(unary Value128, |v| v.f32x4_neg()), + F64x2Neg => stack_op!(unary Value128, |v| v.f64x2_neg()), + F32x4Sqrt => stack_op!(unary Value128, |v| v.f32x4_sqrt()), + F64x2Sqrt => stack_op!(unary Value128, |v| v.f64x2_sqrt()), + F32x4Add => stack_op!(binary Value128, |a, b| a.f32x4_add(b)), + F64x2Add => stack_op!(binary Value128, |a, b| a.f64x2_add(b)), + F32x4Sub => stack_op!(binary Value128, |a, b| a.f32x4_sub(b)), + F64x2Sub => stack_op!(binary Value128, |a, b| a.f64x2_sub(b)), + F32x4Mul => stack_op!(binary Value128, |a, b| a.f32x4_mul(b)), + F64x2Mul => stack_op!(binary Value128, |a, b| a.f64x2_mul(b)), + F32x4Div => stack_op!(binary Value128, |a, b| a.f32x4_div(b)), + F64x2Div => stack_op!(binary Value128, |a, b| a.f64x2_div(b)), + F32x4Min => stack_op!(binary Value128, |a, b| a.f32x4_min(b)), + F64x2Min => stack_op!(binary Value128, |a, b| a.f64x2_min(b)), + F32x4Max => stack_op!(binary Value128, |a, b| a.f32x4_max(b)), + F64x2Max => stack_op!(binary Value128, |a, b| a.f64x2_max(b)), + F32x4PMin => stack_op!(binary Value128, |a, b| a.f32x4_pmin(b)), + F32x4PMax => stack_op!(binary Value128, |a, b| a.f32x4_pmax(b)), + F64x2PMin => stack_op!(binary Value128, |a, b| a.f64x2_pmin(b)), + F64x2PMax => stack_op!(binary Value128, |a, b| a.f64x2_pmax(b)), F32x4RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_madd(b, c)), F32x4RelaxedNmadd => stack_op!(ternary Value128, |a, b, c| a.f32x4_relaxed_nmadd(b, c)), F64x2RelaxedMadd => stack_op!(ternary Value128, |a, b, c| a.f64x2_relaxed_madd(b, c)), diff --git a/crates/tinywasm/src/interpreter/num_helpers.rs b/crates/tinywasm/src/interpreter/num_helpers.rs index 5ae81ef8..20882b34 100644 --- a/crates/tinywasm/src/interpreter/num_helpers.rs +++ b/crates/tinywasm/src/interpreter/num_helpers.rs @@ -30,18 +30,17 @@ macro_rules! checked_conv_float { checked_conv_float!($from, $to, $to, $self) }; // Conversion with an intermediate unsigned type and error checking (three types) - ($from:tt, $intermediate:tt, $to:tt, $self:expr) => { - $self.store.stack.values.unary_into::<$from, $to>(|v| { - let (min, max) = float_min_max!($from, $intermediate); - if unlikely(v.is_nan()) { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - if unlikely(v <= min || v >= max) { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - Ok((v as $intermediate as $to).into()) - })? - }; + ($from:tt, $intermediate:tt, $to:tt, $self:expr) => {{ + let v = $self.store.stack.values.pop::<$from>(); + let (min, max) = float_min_max!($from, $intermediate); + if unlikely(v.is_nan()) { + return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); + } + if unlikely(v <= min || v >= max) { + return Err(Error::Trap(crate::Trap::IntegerOverflow)); + } + $self.store.stack.values.push::<$to>((v as $intermediate as $to).into())?; + }}; } pub(crate) use checked_conv_float; diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index b67ab09b..2b13b670 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -58,14 +58,6 @@ impl Stack { &self.data[self.len - 1] } - #[inline(always)] - pub(crate) fn last_mut(&mut self) -> &mut T { - if self.len == 0 { - unreachable!("ValueStack underflow, this is a bug"); - } - &mut self.data[self.len - 1] - } - #[inline(always)] pub(crate) fn get(&self, index: usize) -> T { match self.data.get(index) { @@ -203,53 +195,6 @@ impl ValueStack { self.stack_ref.select_many(counts.cref as usize, condition); } - #[inline(always)] - pub(crate) fn unary(&mut self, func: impl FnOnce(T) -> Result) -> Result<()> { - T::stack_apply1(self, func) - } - - #[inline(always)] - pub(crate) fn unary_into( - &mut self, - func: impl FnOnce(IN) -> Result, - ) -> Result<()> { - let v = IN::stack_pop(self); - OUT::stack_push(self, func(v)?)?; - Ok(()) - } - - #[inline(always)] - pub(crate) fn binary(&mut self, func: impl FnOnce(T, T) -> Result) -> Result<()> { - T::stack_apply2(self, func) - } - - #[inline(always)] - pub(crate) fn binary_into( - &mut self, - func: impl FnOnce(IN, IN) -> Result, - ) -> Result<()> { - let rhs = IN::stack_pop(self); - let lhs = IN::stack_pop(self); - OUT::stack_push(self, func(lhs, rhs)?)?; - Ok(()) - } - - #[inline(always)] - pub(crate) fn binary_mixed( - &mut self, - func: impl FnOnce(A, B) -> Result, - ) -> Result<()> { - let rhs = B::stack_pop(self); - let lhs = A::stack_pop(self); - OUT::stack_push(self, func(lhs, rhs)?)?; - Ok(()) - } - - #[inline(always)] - pub(crate) fn ternary(&mut self, func: impl FnOnce(T, T, T) -> Result) -> Result<()> { - T::stack_apply3(self, func) - } - pub(crate) fn pop_types<'a>( &'a mut self, val_types: impl IntoIterator, diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index ec68035d..54ba00f5 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -111,9 +111,6 @@ pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + De fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); fn stack_pop(stack: &mut ValueStack) -> Self; fn stack_peek(stack: &ValueStack) -> Self; - fn stack_apply1(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()>; - fn stack_apply2(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()>; - fn stack_apply3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()>; } macro_rules! impl_internalvalue { @@ -160,30 +157,6 @@ macro_rules! impl_internalvalue { fn stack_peek(stack: &ValueStack) -> Self { $to_outer(*stack.$stack.last()) } - - #[inline(always)] - fn stack_apply1(stack: &mut ValueStack, func: impl FnOnce(Self) -> Result) -> Result<()> { - let top = stack.$stack.last_mut(); - *top = $to_internal(func($to_outer(*top))?); - Ok(()) - } - - #[inline(always)] - fn stack_apply2(stack: &mut ValueStack, func: impl FnOnce(Self, Self) -> Result) -> Result<()> { - let v2 = stack.$stack.pop(); - let v1 = stack.$stack.last_mut(); - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2))?); - Ok(()) - } - - #[inline(always)] - fn stack_apply3(stack: &mut ValueStack, func: impl FnOnce(Self, Self, Self) -> Result) -> Result<()> { - let v3 = stack.$stack.pop(); - let v2 = stack.$stack.pop(); - let v1 = stack.$stack.last_mut(); - *v1 = $to_internal(func($to_outer(*v1), $to_outer(v2), $to_outer(v3))?); - Ok(()) - } } )* }; From 4cf65f48e9b17f9a8ca5099d720d3107cb5574b9 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 6 Apr 2026 19:29:47 +0200 Subject: [PATCH 44/47] chore: use FromIterator for ValueCounts Signed-off-by: Henry --- crates/parser/src/module.rs | 2 +- crates/tinywasm/src/imports.rs | 3 ++- crates/tinywasm/src/interpreter/executor.rs | 12 ++++-------- .../src/interpreter/stack/value_stack.rs | 2 +- crates/types/src/lib.rs | 18 ++++++++++++------ 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index cb29e21f..b53dfd67 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -181,7 +181,7 @@ impl ModuleReader { let funcs = self.code.into_iter().zip(self.code_type_addrs).enumerate().map( |(func_idx, ((instructions, mut data, locals), ty_idx))| { let ty = self.func_types.get(ty_idx as usize).expect("No func type for func, this is a bug").clone(); - let params = ValueCounts::from(&ty.params); + let params = ValueCounts::from_iter(&ty.params); let self_func = (imported_func_count + func_idx) as u32; let instructions = optimize::optimize_instructions(instructions, &mut data, self_func, options); WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 9e8de163..3bf35e6d 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -193,7 +193,8 @@ impl Extern { Ok(result.into_wasm_value_tuple()) }; - let ty = tinywasm_types::FuncType { params: P::val_types(), results: R::val_types() }; + let results = R::val_types(); + let ty = tinywasm_types::FuncType { params: P::val_types(), results }; Self::Function(Function::Host(Rc::new(HostFunction { func: Box::new(inner_func), ty }))) } diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 794fb3ab..12f28f4a 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -148,11 +148,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { continue; }, JumpIfNonZero(ip) => if self.exec_jump_if_non_zero(*ip) { continue; }, DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { - let stack_base = self.cf.stack_base(); - self.store.stack.values.stack_32.truncate_keep((stack_base.s32 + *base32 as u32) as usize, *keep32 as usize); - self.store.stack.values.stack_64.truncate_keep((stack_base.s64 + *base64 as u32) as usize, *keep64 as usize); - self.store.stack.values.stack_128.truncate_keep((stack_base.s128 + *base128 as u32) as usize, *keep128 as usize); - self.store.stack.values.stack_ref.truncate_keep((stack_base.sref + *base_ref as u32) as usize, *keep_ref as usize); + let mut base = self.cf.stack_base(); base.s32 += *base32 as u32; base.s64 += *base64 as u32; base.s128 += *base128 as u32; base.sref += *base_ref as u32; + self.store.stack.values.truncate_keep_counts(base, ValueCounts { c32: *keep32 as u16, c64: *keep64 as u16, c128: *keep128 as u16, cref: *keep_ref as u16 }); } DropKeep32(base, keep) => self.store.stack.values.stack_32.truncate_keep((self.cf.stack_base().s32 + *base as u32) as usize, *keep as usize), DropKeep64(base, keep) => self.store.stack.values.stack_64.truncate_keep((self.cf.stack_base().s64 + *base as u32) as usize, *keep as usize), @@ -822,9 +819,8 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { } fn exec_return(&mut self) -> bool { - let result_counts = ValueCounts::from(self.func.ty.results.iter()); - self.store.stack.values.truncate_keep_counts(self.cf.locals_base, result_counts); - + let results = ValueCounts::from_iter(&self.func.ty.results); + self.store.stack.values.truncate_keep_counts(self.cf.locals_base, results); let Some(cf) = self.store.stack.call_stack.pop() else { return true }; if cf.func_addr != self.cf.func_addr { diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 2b13b670..12039290 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -228,7 +228,7 @@ impl ValueStack { } pub(crate) fn truncate_keep_counts(&mut self, base: StackBase, keep: ValueCounts) { - if keep.c32 == 0 && keep.c64 == 0 && keep.c128 == 0 && keep.cref == 0 { + if keep.is_empty() { self.stack_32.len = base.s32 as usize; self.stack_64.len = base.s64 as usize; self.stack_128.len = base.s128 as usize; diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index e9d27294..29a29e5a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -214,19 +214,25 @@ pub struct ValueCounts { pub cref: u16, } -impl<'a, T: IntoIterator> From for ValueCounts { +impl ValueCounts { #[inline] - fn from(types: T) -> Self { - let mut counts = Self::default(); - for ty in types { + pub fn is_empty(&self) -> bool { + self.c32 == 0 && self.c64 == 0 && self.c128 == 0 && self.cref == 0 + } +} + +impl<'a> FromIterator<&'a ValType> for ValueCounts { + #[inline] + fn from_iter>(iter: I) -> Self { + iter.into_iter().fold(Self::default(), |mut counts, ty| { match ty { ValType::I32 | ValType::F32 => counts.c32 += 1, ValType::I64 | ValType::F64 => counts.c64 += 1, ValType::V128 => counts.c128 += 1, ValType::RefExtern | ValType::RefFunc => counts.cref += 1, } - } - counts + counts + }) } } From 46982ca7379584fce8c611736d800d2e091cf399 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 10 Apr 2026 21:09:20 +0200 Subject: [PATCH 45/47] chore: update testsuite Signed-off-by: Henry --- Cargo.lock | 16 +++--- Cargo.toml | 4 +- crates/tinywasm/Cargo.toml | 51 +++++++++++++++---- crates/tinywasm/tests/generated/wasm-2.csv | 2 +- .../generated/wasm-function-references.csv | 1 + crates/tinywasm/tests/generated/wasm-gc.csv | 1 + .../tests/generated/wasm-multi-memory.csv | 2 +- ...m-nontrapping-float-to-int-conversions.csv | 1 + .../tests/generated/wasm-reference-types.csv | 1 + .../tests/generated/wasm-relaxed-simd.csv | 2 +- .../generated/wasm-sign-extension-ops.csv | 1 + crates/tinywasm/tests/generated/wasm-simd.csv | 2 +- .../tinywasm/tests/generated/wasm-threads.csv | 1 + crates/tinywasm/tests/test-wasm-3.rs | 5 -- .../tests/test-wasm-function-references.rs | 13 +++++ crates/tinywasm/tests/test-wasm-gc.rs | 13 +++++ crates/tinywasm/tests/test-wasm-latest.rs | 5 -- ...sm-nontrapping-float-to-int-conversions.rs | 14 +++++ .../tests/test-wasm-reference-types.rs | 13 +++++ .../tests/test-wasm-sign-extension-op.rs | 13 +++++ crates/tinywasm/tests/test-wasm-threads.rs | 13 +++++ 21 files changed, 140 insertions(+), 34 deletions(-) create mode 100644 crates/tinywasm/tests/generated/wasm-function-references.csv create mode 100644 crates/tinywasm/tests/generated/wasm-gc.csv create mode 100644 crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv create mode 100644 crates/tinywasm/tests/generated/wasm-reference-types.csv create mode 100644 crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv create mode 100644 crates/tinywasm/tests/generated/wasm-threads.csv create mode 100644 crates/tinywasm/tests/test-wasm-function-references.rs create mode 100644 crates/tinywasm/tests/test-wasm-gc.rs create mode 100644 crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs create mode 100644 crates/tinywasm/tests/test-wasm-reference-types.rs create mode 100644 crates/tinywasm/tests/test-wasm-sign-extension-op.rs create mode 100644 crates/tinywasm/tests/test-wasm-threads.rs diff --git a/Cargo.lock b/Cargo.lock index 35954667..cac58c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", "shlex", @@ -295,9 +295,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hermit-abi" @@ -338,9 +338,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "wasm-testsuite" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fbb8e8f3d0776ab4a9c9fe5b9252d5ea9d4536e6d8c9e2a666a6e16aa1279f" +checksum = "dc638d7adcaae61bdd173f053d018555843c5deca669b5caaeda9ae37402a602" dependencies = [ "include_dir", "wast", diff --git a/Cargo.toml b/Cargo.toml index 22e206b5..ffd4d895 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ eyre="0.6" log="0.4" pretty_env_logger="0.5" criterion={version="0.8", default-features=false, features=["cargo_bench_support", "rayon"]} -wasm-testsuite={version="0.6"} -indexmap="2.13" +wasm-testsuite={version="0.7"} +indexmap="2.14" owo-colors={version="4.3"} serde_json={version="1.0"} serde={version="1.0", features=["derive"]} diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index dbd92335..21455698 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -64,14 +64,6 @@ harness=false name="test-wasm-2" harness=false -[[test]] -name="test-wasm-3" -harness=false - -[[test]] -name="test-wasm-latest" -harness=false - [[test]] name="test-wasm-multi-memory" harness=false @@ -114,9 +106,12 @@ name="test-wasm-wide-arithmetic" harness=false [[test]] -name="test-wast" +name="test-wasm-sign-extension-op" +harness=false + +[[test]] +name="test-wasm-nontrapping-float-to-int-conversions" harness=false -test=false [[bench]] name="argon2id" @@ -133,3 +128,39 @@ harness=false [[bench]] name="tinywasm_modes" harness=false + + +[[test]] +name="test-wasm-3" +harness=false +test=false + +[[test]] +name="test-wasm-latest" +harness=false +test=false + +[[test]] +name="test-wasm-threads" +harness=false +test=false + +[[test]] +name="test-wast" +harness=false +test=false + +[[test]] +name="test-wasm-function-references" +harness=false +test=false + +[[test]] +name="test-wasm-gc" +harness=false +test=false + +[[test]] +name="test-wasm-reference-types" +harness=false +test=false diff --git a/crates/tinywasm/tests/generated/wasm-2.csv b/crates/tinywasm/tests/generated/wasm-2.csv index 8e228b45..267ea738 100644 --- a/crates/tinywasm/tests/generated/wasm-2.csv +++ b/crates/tinywasm/tests/generated/wasm-2.csv @@ -10,4 +10,4 @@ 0.6.1,20278,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.7.0,20278,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":186,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] 0.8.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":128,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":170,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":178,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":104,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.9.0-alpha.0,27822,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":79,"failed":0},{"name":"binary.wast","passed":160,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"comments.wast","passed":4,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":58,"failed":0},{"name":"elem.wast","passed":74,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":900,"failed":0},{"name":"float_literals.wast","passed":161,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":441,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":239,"failed":0},{"name":"imports.wast","passed":183,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":79,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":147,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":50,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":2,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":6,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.9.0-alpha.0,28008,0,[{"name":"address.wast","passed":260,"failed":0},{"name":"align.wast","passed":162,"failed":0},{"name":"binary-leb128.wast","passed":89,"failed":0},{"name":"binary.wast","passed":128,"failed":0},{"name":"block.wast","passed":223,"failed":0},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":174,"failed":0},{"name":"bulk.wast","passed":117,"failed":0},{"name":"call.wast","passed":91,"failed":0},{"name":"call_indirect.wast","passed":172,"failed":0},{"name":"comments.wast","passed":8,"failed":0},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":619,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"elem.wast","passed":98,"failed":0},{"name":"endianness.wast","passed":69,"failed":0},{"name":"exports.wast","passed":96,"failed":0},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":8,"failed":0},{"name":"float_exprs.wast","passed":927,"failed":0},{"name":"float_literals.wast","passed":179,"failed":0},{"name":"float_memory.wast","passed":90,"failed":0},{"name":"float_misc.wast","passed":471,"failed":0},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":172,"failed":0},{"name":"func_ptrs.wast","passed":36,"failed":0},{"name":"global.wast","passed":110,"failed":0},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"imports.wast","passed":178,"failed":0},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":29,"failed":0},{"name":"left-to-right.wast","passed":96,"failed":0},{"name":"linking.wast","passed":132,"failed":0},{"name":"load.wast","passed":97,"failed":0},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":97,"failed":0},{"name":"loop.wast","passed":120,"failed":0},{"name":"memory.wast","passed":88,"failed":0},{"name":"memory_copy.wast","passed":4450,"failed":0},{"name":"memory_fill.wast","passed":100,"failed":0},{"name":"memory_grow.wast","passed":104,"failed":0},{"name":"memory_init.wast","passed":240,"failed":0},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":42,"failed":0},{"name":"memory_trap.wast","passed":182,"failed":0},{"name":"names.wast","passed":486,"failed":0},{"name":"nop.wast","passed":88,"failed":0},{"name":"obsolete-keywords.wast","passed":11,"failed":0},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":148,"failed":0},{"name":"skip-stack-guard-page.wast","passed":11,"failed":0},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":20,"failed":0},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1728,"failed":0},{"name":"table_fill.wast","passed":45,"failed":0},{"name":"table_get.wast","passed":16,"failed":0},{"name":"table_grow.wast","passed":58,"failed":0},{"name":"table_init.wast","passed":780,"failed":0},{"name":"table_set.wast","passed":26,"failed":0},{"name":"table_size.wast","passed":39,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unreached-valid.wast","passed":7,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-function-references.csv b/crates/tinywasm/tests/generated/wasm-function-references.csv new file mode 100644 index 00000000..0f67ba14 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-function-references.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,1536,331,[{"name":"binary.wast","passed":128,"failed":0},{"name":"br_on_non_null.wast","passed":0,"failed":9},{"name":"br_on_null.wast","passed":0,"failed":9},{"name":"br_table.wast","passed":24,"failed":162},{"name":"call_ref.wast","passed":3,"failed":31},{"name":"data.wast","passed":59,"failed":0},{"name":"elem.wast","passed":138,"failed":0},{"name":"func.wast","passed":175,"failed":0},{"name":"global.wast","passed":108,"failed":0},{"name":"if.wast","passed":241,"failed":0},{"name":"linking.wast","passed":146,"failed":21},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_init.wast","passed":10,"failed":0},{"name":"ref.wast","passed":12,"failed":1},{"name":"ref_as_non_null.wast","passed":1,"failed":6},{"name":"ref_is_null.wast","passed":2,"failed":20},{"name":"ref_null.wast","passed":0,"failed":4},{"name":"return_call.wast","passed":45,"failed":0},{"name":"return_call_indirect.wast","passed":76,"failed":0},{"name":"return_call_ref.wast","passed":10,"failed":40},{"name":"select.wast","passed":155,"failed":2},{"name":"table-sub.wast","passed":2,"failed":1},{"name":"table.wast","passed":35,"failed":8},{"name":"type-equivalence.wast","passed":7,"failed":7},{"name":"unreached-invalid.wast","passed":121,"failed":0},{"name":"unreached-valid.wast","passed":2,"failed":10}] diff --git a/crates/tinywasm/tests/generated/wasm-gc.csv b/crates/tinywasm/tests/generated/wasm-gc.csv new file mode 100644 index 00000000..f09637b7 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-gc.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,80,703,[{"name":"array.wast","passed":6,"failed":48},{"name":"array_copy.wast","passed":4,"failed":31},{"name":"array_fill.wast","passed":3,"failed":27},{"name":"array_init_data.wast","passed":2,"failed":44},{"name":"array_init_elem.wast","passed":3,"failed":33},{"name":"array_new_data.wast","passed":0,"failed":28},{"name":"array_new_elem.wast","passed":0,"failed":24},{"name":"binary-gc.wast","passed":1,"failed":0},{"name":"br_on_cast.wast","passed":6,"failed":31},{"name":"br_on_cast_fail.wast","passed":6,"failed":31},{"name":"extern.wast","passed":0,"failed":18},{"name":"i31.wast","passed":2,"failed":71},{"name":"ref_cast.wast","passed":0,"failed":45},{"name":"ref_eq.wast","passed":6,"failed":83},{"name":"ref_test.wast","passed":0,"failed":71},{"name":"struct.wast","passed":5,"failed":25},{"name":"type-subtyping.wast","passed":36,"failed":93}] diff --git a/crates/tinywasm/tests/generated/wasm-multi-memory.csv b/crates/tinywasm/tests/generated/wasm-multi-memory.csv index c81e9e5d..9036cebf 100644 --- a/crates/tinywasm/tests/generated/wasm-multi-memory.csv +++ b/crates/tinywasm/tests/generated/wasm-multi-memory.csv @@ -1,2 +1,2 @@ 0.8.0,1872,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"multi-memory/simd_memory-multi.wast (skipped)","passed":0,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] -0.9.0-alpha.0,1872,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align.wast","passed":160,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary.wast","passed":126,"failed":0},{"name":"binary0.wast","passed":6,"failed":0},{"name":"data.wast","passed":61,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports.wast","passed":175,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load.wast","passed":118,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory.wast","passed":86,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":157,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size.wast","passed":49,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store.wast","passed":111,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] +0.9.0-alpha.0,912,0,[{"name":"address0.wast","passed":92,"failed":0},{"name":"address1.wast","passed":127,"failed":0},{"name":"align0.wast","passed":5,"failed":0},{"name":"binary0.wast","passed":7,"failed":0},{"name":"data0.wast","passed":7,"failed":0},{"name":"data1.wast","passed":14,"failed":0},{"name":"data_drop0.wast","passed":11,"failed":0},{"name":"exports0.wast","passed":8,"failed":0},{"name":"float_exprs0.wast","passed":14,"failed":0},{"name":"float_exprs1.wast","passed":3,"failed":0},{"name":"float_memory0.wast","passed":30,"failed":0},{"name":"imports0.wast","passed":8,"failed":0},{"name":"imports1.wast","passed":5,"failed":0},{"name":"imports2.wast","passed":20,"failed":0},{"name":"imports3.wast","passed":10,"failed":0},{"name":"imports4.wast","passed":16,"failed":0},{"name":"linking0.wast","passed":6,"failed":0},{"name":"linking1.wast","passed":14,"failed":0},{"name":"linking2.wast","passed":11,"failed":0},{"name":"linking3.wast","passed":14,"failed":0},{"name":"load0.wast","passed":3,"failed":0},{"name":"load1.wast","passed":18,"failed":0},{"name":"load2.wast","passed":38,"failed":0},{"name":"memory-multi.wast","passed":6,"failed":0},{"name":"memory_copy0.wast","passed":29,"failed":0},{"name":"memory_copy1.wast","passed":14,"failed":0},{"name":"memory_fill0.wast","passed":16,"failed":0},{"name":"memory_grow.wast","passed":51,"failed":0},{"name":"memory_init0.wast","passed":13,"failed":0},{"name":"memory_size0.wast","passed":8,"failed":0},{"name":"memory_size1.wast","passed":15,"failed":0},{"name":"memory_size2.wast","passed":21,"failed":0},{"name":"memory_size3.wast","passed":2,"failed":0},{"name":"memory_size_import.wast","passed":7,"failed":0},{"name":"memory_trap0.wast","passed":14,"failed":0},{"name":"memory_trap1.wast","passed":168,"failed":0},{"name":"start0.wast","passed":9,"failed":0},{"name":"store0.wast","passed":5,"failed":0},{"name":"store1.wast","passed":13,"failed":0},{"name":"store2.wast","passed":25,"failed":0},{"name":"traps0.wast","passed":15,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv b/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv new file mode 100644 index 00000000..3dff47cb --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-nontrapping-float-to-int-conversions.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,615,0,[{"name":"conversions.wast","passed":615,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-reference-types.csv b/crates/tinywasm/tests/generated/wasm-reference-types.csv new file mode 100644 index 00000000..c81aecca --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-reference-types.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,8971,161,[{"name":"binary-leb128.wast","passed":77,"failed":0},{"name":"binary.wast","passed":134,"failed":8},{"name":"br_table.wast","passed":172,"failed":0},{"name":"bulk.wast","passed":111,"failed":6},{"name":"call_indirect.wast","passed":169,"failed":0},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":31,"failed":14},{"name":"elem.wast","passed":46,"failed":14},{"name":"exports.wast","passed":84,"failed":0},{"name":"global.wast","passed":86,"failed":0},{"name":"imports.wast","passed":158,"failed":0},{"name":"linking.wast","passed":108,"failed":24},{"name":"memory_copy.wast","passed":4432,"failed":18},{"name":"memory_fill.wast","passed":96,"failed":4},{"name":"memory_grow.wast","passed":96,"failed":0},{"name":"memory_init.wast","passed":226,"failed":14},{"name":"ref_func.wast","passed":17,"failed":0},{"name":"ref_is_null.wast","passed":16,"failed":0},{"name":"ref_null.wast","passed":3,"failed":0},{"name":"select.wast","passed":141,"failed":0},{"name":"table-sub.wast","passed":2,"failed":0},{"name":"table.wast","passed":19,"failed":0},{"name":"table_copy.wast","passed":1703,"failed":25},{"name":"table_fill.wast","passed":42,"failed":3},{"name":"table_get.wast","passed":12,"failed":4},{"name":"table_grow.wast","passed":50,"failed":0},{"name":"table_init.wast","passed":762,"failed":18},{"name":"table_set.wast","passed":18,"failed":8},{"name":"table_size.wast","passed":39,"failed":0},{"name":"unreached-invalid.wast","passed":110,"failed":1}] diff --git a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv index 6b89d8a5..5520022a 100644 --- a/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-relaxed-simd.csv @@ -1 +1 @@ -0.9.0-alpha.0,93,0,[{"name":"i16x8_relaxed_q15mulr_s.wast","passed":3,"failed":0},{"name":"i32x4_relaxed_trunc.wast","passed":17,"failed":0},{"name":"i8x16_relaxed_swizzle.wast","passed":6,"failed":0},{"name":"relaxed_dot_product.wast","passed":11,"failed":0},{"name":"relaxed_laneselect.wast","passed":12,"failed":0},{"name":"relaxed_madd_nmadd.wast","passed":19,"failed":0},{"name":"relaxed_min_max.wast","passed":25,"failed":0}] +0.9.0-alpha.0,77,0,[{"name":"i16x8_relaxed_q15mulr_s.wast","passed":3,"failed":0},{"name":"i32x4_relaxed_trunc.wast","passed":1,"failed":0},{"name":"i8x16_relaxed_swizzle.wast","passed":6,"failed":0},{"name":"relaxed_dot_product.wast","passed":11,"failed":0},{"name":"relaxed_laneselect.wast","passed":12,"failed":0},{"name":"relaxed_madd_nmadd.wast","passed":19,"failed":0},{"name":"relaxed_min_max.wast","passed":25,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv b/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv new file mode 100644 index 00000000..e883c5e7 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-sign-extension-ops.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,872,0,[{"name":"i32.wast","passed":458,"failed":0},{"name":"i64.wast","passed":414,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-simd.csv b/crates/tinywasm/tests/generated/wasm-simd.csv index b05959cb..0ed87b6b 100644 --- a/crates/tinywasm/tests/generated/wasm-simd.csv +++ b/crates/tinywasm/tests/generated/wasm-simd.csv @@ -1,2 +1,2 @@ 0.8.0,1300,24679,[{"name":"simd_address.wast","passed":4,"failed":45},{"name":"simd_align.wast","passed":46,"failed":54},{"name":"simd_bit_shift.wast","passed":39,"failed":213},{"name":"simd_bitwise.wast","passed":28,"failed":141},{"name":"simd_boolean.wast","passed":16,"failed":261},{"name":"simd_const.wast","passed":301,"failed":456},{"name":"simd_conversions.wast","passed":48,"failed":234},{"name":"simd_f32x4.wast","passed":16,"failed":774},{"name":"simd_f32x4_arith.wast","passed":16,"failed":1806},{"name":"simd_f32x4_cmp.wast","passed":24,"failed":2583},{"name":"simd_f32x4_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f32x4_rounding.wast","passed":24,"failed":177},{"name":"simd_f64x2.wast","passed":8,"failed":795},{"name":"simd_f64x2_arith.wast","passed":16,"failed":1809},{"name":"simd_f64x2_cmp.wast","passed":24,"failed":2661},{"name":"simd_f64x2_pmin_pmax.wast","passed":14,"failed":3873},{"name":"simd_f64x2_rounding.wast","passed":24,"failed":177},{"name":"simd_i16x8_arith.wast","passed":11,"failed":183},{"name":"simd_i16x8_arith2.wast","passed":19,"failed":153},{"name":"simd_i16x8_cmp.wast","passed":30,"failed":435},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":4,"failed":17},{"name":"simd_i16x8_extmul_i8x16.wast","passed":12,"failed":105},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":3,"failed":27},{"name":"simd_i16x8_sat_arith.wast","passed":16,"failed":206},{"name":"simd_i32x4_arith.wast","passed":11,"failed":183},{"name":"simd_i32x4_arith2.wast","passed":26,"failed":123},{"name":"simd_i32x4_cmp.wast","passed":40,"failed":435},{"name":"simd_i32x4_dot_i16x8.wast","passed":3,"failed":27},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":4,"failed":17},{"name":"simd_i32x4_extmul_i16x8.wast","passed":12,"failed":105},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":4,"failed":103},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":4,"failed":103},{"name":"simd_i64x2_arith.wast","passed":11,"failed":189},{"name":"simd_i64x2_arith2.wast","passed":2,"failed":23},{"name":"simd_i64x2_cmp.wast","passed":10,"failed":103},{"name":"simd_i64x2_extmul_i32x4.wast","passed":12,"failed":105},{"name":"simd_i8x16_arith.wast","passed":8,"failed":123},{"name":"simd_i8x16_arith2.wast","passed":25,"failed":186},{"name":"simd_i8x16_cmp.wast","passed":30,"failed":415},{"name":"simd_i8x16_sat_arith.wast","passed":24,"failed":190},{"name":"simd_int_to_int_extend.wast","passed":24,"failed":229},{"name":"simd_lane.wast","passed":189,"failed":286},{"name":"simd_linking.wast","passed":0,"failed":3},{"name":"simd_load.wast","passed":8,"failed":31},{"name":"simd_load16_lane.wast","passed":3,"failed":33},{"name":"simd_load32_lane.wast","passed":3,"failed":21},{"name":"simd_load64_lane.wast","passed":3,"failed":13},{"name":"simd_load8_lane.wast","passed":3,"failed":49},{"name":"simd_load_extend.wast","passed":18,"failed":86},{"name":"simd_load_splat.wast","passed":12,"failed":114},{"name":"simd_load_zero.wast","passed":10,"failed":29},{"name":"simd_splat.wast","passed":23,"failed":162},{"name":"simd_store.wast","passed":9,"failed":19},{"name":"simd_store16_lane.wast","passed":3,"failed":33},{"name":"simd_store32_lane.wast","passed":3,"failed":21},{"name":"simd_store64_lane.wast","passed":3,"failed":13},{"name":"simd_store8_lane.wast","passed":3,"failed":49}] -0.9.0-alpha.0,25989,0,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":757,"failed":0},{"name":"simd_conversions.wast","passed":282,"failed":0},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":201,"failed":0},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1825,"failed":0},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":201,"failed":0},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":172,"failed":0},{"name":"simd_i16x8_cmp.wast","passed":465,"failed":0},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":21,"failed":0},{"name":"simd_i16x8_extmul_i8x16.wast","passed":117,"failed":0},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":475,"failed":0},{"name":"simd_i32x4_dot_i16x8.wast","passed":32,"failed":0},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":21,"failed":0},{"name":"simd_i32x4_extmul_i16x8.wast","passed":117,"failed":0},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":107,"failed":0},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":107,"failed":0},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":117,"failed":0},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":211,"failed":0},{"name":"simd_i8x16_cmp.wast","passed":445,"failed":0},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":253,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":39,"failed":0},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":104,"failed":0},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":185,"failed":0},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] +0.9.0-alpha.0,25990,0,[{"name":"simd_address.wast","passed":49,"failed":0},{"name":"simd_align.wast","passed":100,"failed":0},{"name":"simd_bit_shift.wast","passed":252,"failed":0},{"name":"simd_bitwise.wast","passed":169,"failed":0},{"name":"simd_boolean.wast","passed":277,"failed":0},{"name":"simd_const.wast","passed":758,"failed":0},{"name":"simd_conversions.wast","passed":282,"failed":0},{"name":"simd_f32x4.wast","passed":790,"failed":0},{"name":"simd_f32x4_arith.wast","passed":1822,"failed":0},{"name":"simd_f32x4_cmp.wast","passed":2607,"failed":0},{"name":"simd_f32x4_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f32x4_rounding.wast","passed":201,"failed":0},{"name":"simd_f64x2.wast","passed":803,"failed":0},{"name":"simd_f64x2_arith.wast","passed":1825,"failed":0},{"name":"simd_f64x2_cmp.wast","passed":2685,"failed":0},{"name":"simd_f64x2_pmin_pmax.wast","passed":3887,"failed":0},{"name":"simd_f64x2_rounding.wast","passed":201,"failed":0},{"name":"simd_i16x8_arith.wast","passed":194,"failed":0},{"name":"simd_i16x8_arith2.wast","passed":172,"failed":0},{"name":"simd_i16x8_cmp.wast","passed":465,"failed":0},{"name":"simd_i16x8_extadd_pairwise_i8x16.wast","passed":21,"failed":0},{"name":"simd_i16x8_extmul_i8x16.wast","passed":117,"failed":0},{"name":"simd_i16x8_q15mulr_sat_s.wast","passed":30,"failed":0},{"name":"simd_i16x8_sat_arith.wast","passed":222,"failed":0},{"name":"simd_i32x4_arith.wast","passed":194,"failed":0},{"name":"simd_i32x4_arith2.wast","passed":149,"failed":0},{"name":"simd_i32x4_cmp.wast","passed":475,"failed":0},{"name":"simd_i32x4_dot_i16x8.wast","passed":32,"failed":0},{"name":"simd_i32x4_extadd_pairwise_i16x8.wast","passed":21,"failed":0},{"name":"simd_i32x4_extmul_i16x8.wast","passed":117,"failed":0},{"name":"simd_i32x4_trunc_sat_f32x4.wast","passed":107,"failed":0},{"name":"simd_i32x4_trunc_sat_f64x2.wast","passed":107,"failed":0},{"name":"simd_i64x2_arith.wast","passed":200,"failed":0},{"name":"simd_i64x2_arith2.wast","passed":25,"failed":0},{"name":"simd_i64x2_cmp.wast","passed":113,"failed":0},{"name":"simd_i64x2_extmul_i32x4.wast","passed":117,"failed":0},{"name":"simd_i8x16_arith.wast","passed":131,"failed":0},{"name":"simd_i8x16_arith2.wast","passed":211,"failed":0},{"name":"simd_i8x16_cmp.wast","passed":445,"failed":0},{"name":"simd_i8x16_sat_arith.wast","passed":214,"failed":0},{"name":"simd_int_to_int_extend.wast","passed":253,"failed":0},{"name":"simd_lane.wast","passed":475,"failed":0},{"name":"simd_linking.wast","passed":3,"failed":0},{"name":"simd_load.wast","passed":39,"failed":0},{"name":"simd_load16_lane.wast","passed":36,"failed":0},{"name":"simd_load32_lane.wast","passed":24,"failed":0},{"name":"simd_load64_lane.wast","passed":16,"failed":0},{"name":"simd_load8_lane.wast","passed":52,"failed":0},{"name":"simd_load_extend.wast","passed":104,"failed":0},{"name":"simd_load_splat.wast","passed":126,"failed":0},{"name":"simd_load_zero.wast","passed":39,"failed":0},{"name":"simd_memory-multi.wast","passed":1,"failed":0},{"name":"simd_select.wast","passed":7,"failed":0},{"name":"simd_splat.wast","passed":185,"failed":0},{"name":"simd_store.wast","passed":28,"failed":0},{"name":"simd_store16_lane.wast","passed":36,"failed":0},{"name":"simd_store32_lane.wast","passed":24,"failed":0},{"name":"simd_store64_lane.wast","passed":16,"failed":0},{"name":"simd_store8_lane.wast","passed":52,"failed":0}] diff --git a/crates/tinywasm/tests/generated/wasm-threads.csv b/crates/tinywasm/tests/generated/wasm-threads.csv new file mode 100644 index 00000000..e43e34a4 --- /dev/null +++ b/crates/tinywasm/tests/generated/wasm-threads.csv @@ -0,0 +1 @@ +0.9.0-alpha.0,357,262,[{"name":"atomic.wast","passed":48,"failed":249},{"name":"exports.wast","passed":82,"failed":6},{"name":"imports.wast","passed":147,"failed":5},{"name":"memory.wast","passed":80,"failed":2}] diff --git a/crates/tinywasm/tests/test-wasm-3.rs b/crates/tinywasm/tests/test-wasm-3.rs index 3e63dc5b..11ebbc84 100644 --- a/crates/tinywasm/tests/test-wasm-3.rs +++ b/crates/tinywasm/tests/test-wasm-3.rs @@ -4,11 +4,6 @@ use testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; fn main() -> Result<()> { - if !std::env::args().any(|x| &x == "--enable") { - println!("Skipping wasm-3 tests, use --enable to run"); - return Ok(()); - } - TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-function-references.rs b/crates/tinywasm/tests/test-wasm-function-references.rs new file mode 100644 index 00000000..d91ff82b --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-function-references.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + + let mut test_suite = TestSuite::new(); + test_suite.run_files(proposal(&Proposal::FunctionReferences))?; + test_suite.save_csv("./tests/generated/wasm-function-references.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-gc.rs b/crates/tinywasm/tests/test-wasm-gc.rs new file mode 100644 index 00000000..a80d347e --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-gc.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::GC))?; + test_suite.save_csv("./tests/generated/wasm-gc.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-latest.rs b/crates/tinywasm/tests/test-wasm-latest.rs index 46964e34..af83c2f3 100644 --- a/crates/tinywasm/tests/test-wasm-latest.rs +++ b/crates/tinywasm/tests/test-wasm-latest.rs @@ -4,11 +4,6 @@ use testsuite::TestSuite; use wasm_testsuite::data::{SpecVersion, spec}; fn main() -> Result<()> { - if !std::env::args().any(|x| &x == "--enable") { - println!("Skipping wasm-latest tests, use --enable to run"); - return Ok(()); - } - TestSuite::set_log_level(log::LevelFilter::Off); let mut test_suite = TestSuite::new(); diff --git a/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs b/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs new file mode 100644 index 00000000..0475fac7 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-nontrapping-float-to-int-conversions.rs @@ -0,0 +1,14 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::NontrappingFloatToIntConversions))?; + test_suite + .save_csv("./tests/generated/wasm-nontrapping-float-to-int-conversions.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-reference-types.rs b/crates/tinywasm/tests/test-wasm-reference-types.rs new file mode 100644 index 00000000..b6f11553 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-reference-types.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::ReferenceTypes))?; + test_suite.save_csv("./tests/generated/wasm-reference-types.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-sign-extension-op.rs b/crates/tinywasm/tests/test-wasm-sign-extension-op.rs new file mode 100644 index 00000000..c0ef1436 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-sign-extension-op.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::SignExtensionOps))?; + test_suite.save_csv("./tests/generated/wasm-sign-extension-ops.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} diff --git a/crates/tinywasm/tests/test-wasm-threads.rs b/crates/tinywasm/tests/test-wasm-threads.rs new file mode 100644 index 00000000..2fd5a9b6 --- /dev/null +++ b/crates/tinywasm/tests/test-wasm-threads.rs @@ -0,0 +1,13 @@ +mod testsuite; +use eyre::Result; +use testsuite::TestSuite; +use wasm_testsuite::data::{Proposal, proposal}; + +fn main() -> Result<()> { + TestSuite::set_log_level(log::LevelFilter::Off); + let mut test_suite = TestSuite::new(); + + test_suite.run_files(proposal(&Proposal::Threads))?; + test_suite.save_csv("./tests/generated/wasm-threads.csv", env!("CARGO_PKG_VERSION"))?; + test_suite.report_status() +} From 87499c635504e808960e0c6ffffe2b43ca4d6206 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 10 Apr 2026 22:32:06 +0200 Subject: [PATCH 46/47] ci: update binaryen Signed-off-by: Henry --- .github/workflows/test.yaml | 13 +++++++++++-- examples/rust/build.sh | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 02d47ceb..0e0c3c59 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,8 +23,17 @@ jobs: components: rust-src rustflags: "" target: wasm32-unknown-unknown - - name: Install Binaryen and WABT - run: sudo apt-get install -y binaryen wabt + - name: Install Binaryen + run: | + BINARYEN_VERSION=version_129 + BINARYEN_DIR="binaryen-${BINARYEN_VERSION}" + BINARYEN_ARCHIVE="${BINARYEN_DIR}-x86_64-linux.tar.gz" + curl -L "https://github.com/WebAssembly/binaryen/releases/download/${BINARYEN_VERSION}/${BINARYEN_ARCHIVE}" -o "/tmp/${BINARYEN_ARCHIVE}" + echo "50b9fa62b9abea752da92ec57e0c555fee578760cd237c40107957715d2976ba /tmp/${BINARYEN_ARCHIVE}" | sha256sum -c - + tar -xzf "/tmp/${BINARYEN_ARCHIVE}" -C /tmp + sudo install "/tmp/${BINARYEN_DIR}/bin/wasm-opt" /usr/local/bin/wasm-opt + - name: Install WABT + run: sudo apt-get install -y wabt - name: Build wasm run: ./examples/rust/build.sh - name: Save artifacts diff --git a/examples/rust/build.sh b/examples/rust/build.sh index 36f95689..db4cb439 100755 --- a/examples/rust/build.sh +++ b/examples/rust/build.sh @@ -20,7 +20,7 @@ for bin in "${bins[@]}"; do RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none -C target-feature=$rust_features -C panic=abort" cargo build -Z build-std=std,panic_abort -Z build-std-features="optimize_for_size" --target wasm32-unknown-unknown --package rust-wasm-examples --profile=wasm --bin "$bin" cp "$out_dir/$bin.wasm" "$dest_dir/" - wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 -Oz $wasmopt_features + wasm-opt "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.opt.wasm" -O3 $wasmopt_features if [[ ! " ${exclude_wat[@]} " =~ " $bin " ]]; then wasm2wat "$dest_dir/$bin.wasm" -o "$dest_dir/$bin.wat" From dcd8b152466ab023762e4b9b6fc50c99da0cae50 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 12 Apr 2026 13:34:31 +0200 Subject: [PATCH 47/47] feat: rework public api, add missing export apis Signed-off-by: Henry --- CHANGELOG.md | 23 +- crates/cli/src/bin.rs | 2 +- crates/parser/src/visit.rs | 8 +- crates/tinywasm/Cargo.toml | 7 + crates/tinywasm/benches/argon2id.rs | 15 +- crates/tinywasm/benches/fibonacci.rs | 17 +- crates/tinywasm/benches/tinywasm.rs | 5 +- crates/tinywasm/benches/tinywasm_modes.rs | 2 +- crates/tinywasm/src/func.rs | 9 + crates/tinywasm/src/imports.rs | 93 +++-- crates/tinywasm/src/instance.rs | 364 ++++++++++++++++-- crates/tinywasm/src/interpreter/executor.rs | 36 +- crates/tinywasm/src/lib.rs | 12 +- crates/tinywasm/src/module.rs | 133 ++++++- crates/tinywasm/src/reference.rs | 183 ++++++++- crates/tinywasm/src/store/mod.rs | 23 +- crates/tinywasm/src/store/table.rs | 6 +- .../tests/host_func_signature_check.rs | 2 +- crates/tinywasm/tests/import_linking.rs | 57 +++ crates/tinywasm/tests/imported_table_init.rs | 35 ++ crates/tinywasm/tests/internal_refs.rs | 101 +++++ crates/tinywasm/tests/memory_ref_api.rs | 25 ++ crates/tinywasm/tests/module_descriptors.rs | 91 +++++ crates/tinywasm/tests/resume_execution.rs | 12 +- crates/tinywasm/tests/store_ownership.rs | 43 +++ crates/tinywasm/tests/testsuite/run.rs | 62 +-- crates/tinywasm/tests/testsuite/util.rs | 8 +- crates/tinywasm/tests/typed_lookup.rs | 48 +++ crates/types/src/instructions.rs | 4 +- examples/archive.rs | 2 +- examples/funcref_callbacks.rs | 10 +- examples/linking.rs | 4 +- examples/resumable.rs | 2 +- examples/rust/src/tinywasm.rs | 2 +- examples/rust/src/tinywasm_no_std.rs | 2 +- examples/simple.rs | 2 +- examples/simple2.rs | 2 +- examples/wasm-rust.rs | 26 +- 38 files changed, 1292 insertions(+), 186 deletions(-) create mode 100644 crates/tinywasm/tests/import_linking.rs create mode 100644 crates/tinywasm/tests/imported_table_init.rs create mode 100644 crates/tinywasm/tests/internal_refs.rs create mode 100644 crates/tinywasm/tests/memory_ref_api.rs create mode 100644 crates/tinywasm/tests/module_descriptors.rs create mode 100644 crates/tinywasm/tests/store_ownership.rs create mode 100644 crates/tinywasm/tests/typed_lookup.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b5fcc6..8a6b138f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Support for the custom memory page sizes proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14)) +- Support for the `custom_page_sizes` proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14)) - Support for the `tail_call` proposal - Support for the `memory64` proposal -- Support for the fixed-width `simd` proposal +- Support for the `simd` proposal - Support for the `relaxed_simd` proposal - Support for the `wide_arithmetic` proposal - New `Engine` API (`tinywasm::Engine` and `engine::Config`) for runtime configuration @@ -20,12 +20,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Host-function fuel APIs: `FuncContext::charge_fuel` and `FuncContext::remaining_fuel` - `engine::FuelPolicy` and `engine::Config::fuel_policy` for fuel accounting behavior - New `canonicalize_nans` feature flag to enable canonicalizing NaN values in the `f32`, `f64`, and `v128` types +- Public API rework for runtime object access: + - export lookups: `func`, `func_typed`, `memory`, `memory_mut` + - table/global access: `table`, `table_mut`, `global`, `global_mut`, `global_get`, `global_set` + - generic export access: `extern_item`, `extern_item_mut` + - export iteration: `ModuleInstance::exports` + - module descriptors: `Module::imports`, `Module::exports` + - raw memory data access: `MemoryRef::data`/`data_size`, `MemoryRefMut::data`/`data_mut`/`data_size` ### Changed - Locals are now stored in the typed value stacks instead of a separate locals structure - Structured control flow is fully lowered to jump-oriented internal instructions during parsing -- Stack and call-stack limits are configured via `engine::Config` +- Stack and call-stack limits can now be configured via `engine::Config` +- Module-internal by-index inspection APIs are now gated behind the `guest_debug` feature ### Breaking Changes @@ -41,12 +49,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cargo feature `logging` was renamed to `log` - Increased MSRV to 1.90 - `Error::ParseError` was renamed to `Error::Parser`, and `Error::Twasm` was added +- `ModuleInstance` export lookup APIs were renamed: + - `exported_func_untyped` -> `func` + - `exported_func` -> `func_typed` + - `exported_memory` -> `memory` + - `exported_memory_mut` -> `memory_mut` +- `Imports::link_module` now takes a `ModuleInstance` instead of a raw module instance id +- `func_typed` now validates the exact wasm signature at lookup time and fails immediately on mismatches ### Fixed - Fixed archive **no_std** support which was broken in the previous release, and added more tests to ensure it stays working - `ModuleInstance::exported_memory` and `FuncContext::exported_memory` are now actually immutable ([#41](https://github.com/explodingcamera/tinywasm/pull/41)) - Check returns in untyped host functions ([#27](https://github.com/explodingcamera/tinywasm/pull/27)) (thanks [@WhaleKit](https://github.com/WhaleKit)) +- `MemoryRefMut::copy_within(src, dst, len)` now follows its documented argument order +- Imported tables created with `Extern::table(ty, init)` now honor the provided init value ## [0.8.0] - 2024-08-29 diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 235498c6..814edd46 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -109,7 +109,7 @@ fn run(module: Module, func: Option, args: &[WasmValue]) -> Result<()> { let instance = module.instantiate(&mut store, None)?; if let Some(func) = func { - let func = instance.exported_func_untyped(&store, &func)?; + let func = instance.func(&store, &func)?; let res = func.call(&mut store, args)?; info!("{res:?}"); } diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index 8313d9f1..94cb4eaf 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -200,7 +200,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild visit_ref_func(RefFunc, u32), visit_table_fill(TableFill, u32), visit_table_get(TableGet, u32), visit_table_set(TableSet, u32), visit_table_grow(TableGrow, u32), visit_table_size(TableSize, u32), // Bulk Memory - visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), + visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), // Wide Arithmetic visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU) @@ -440,7 +440,11 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { - self.instructions.push(Instruction::TableCopy { from: src_table, to: dst_table }); + self.instructions.push(Instruction::TableCopy { dst_table, src_table }); + } + + fn visit_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Self::Output { + self.instructions.push(Instruction::MemoryCopy { dst_mem, src_mem }); } // Reference Types diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 21455698..62b592ba 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -15,6 +15,10 @@ readme="../../README.md" name="tinywasm" path="src/lib.rs" +[package.metadata.docs.rs] +features=["std", "parser", "archive", "log", "canonicalize_nans", "debug", "guest_debug"] +rustdoc-args=["--cfg", "docsrs"] + [dependencies] log={workspace=true, optional=true} tinywasm-parser={version="0.9.0-alpha.0", path="../parser", default-features=false, optional=true} @@ -51,6 +55,9 @@ canonicalize_nans=[] # derive Debug for runtime/types structs debug=["tinywasm-types/debug"] +# expose module-internal by-index inspection APIs +guest_debug=[] + # enable x86-specific SIMD intrinsics in Value128 (uses unsafe code) # note: for x86 backend selection, compile with x86-64-v3 target features # (for example: `RUSTFLAGS="-C target-cpu=x86-64-v3"`) diff --git a/crates/tinywasm/benches/argon2id.rs b/crates/tinywasm/benches/argon2id.rs index 74c51e3b..5d1719f2 100644 --- a/crates/tinywasm/benches/argon2id.rs +++ b/crates/tinywasm/benches/argon2id.rs @@ -24,7 +24,7 @@ fn argon2id_from_twasm(twasm: &[u8]) -> Result { fn argon2id_run(module: TinyWasmModule) -> Result<()> { let mut store = Store::default(); let instance = ModuleInstance::instantiate(&mut store, module.into(), None)?; - let argon2 = instance.exported_func::<(i32, i32, i32), i32>(&store, "argon2id")?; + let argon2 = instance.func_typed::<(i32, i32, i32), i32>(&store, "argon2id")?; argon2.call(&mut store, (1000, 2, 1))?; Ok(()) } @@ -32,11 +32,14 @@ fn argon2id_run(module: TinyWasmModule) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let module = argon2id_parse().expect("argon2id_parse"); let twasm = argon2id_to_twasm(&module).expect("argon2id_to_twasm"); - - c.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); - c.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); - c.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); - c.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone()))); + let mut group = c.benchmark_group("argon2id"); + + group.measurement_time(std::time::Duration::from_secs(2)); + group.bench_function("argon2id_parse", |b| b.iter(argon2id_parse)); + group.bench_function("argon2id_to_twasm", |b| b.iter(|| argon2id_to_twasm(&module))); + group.bench_function("argon2id_from_twasm", |b| b.iter(|| argon2id_from_twasm(&twasm))); + group.measurement_time(std::time::Duration::from_secs(10)); + group.bench_function("argon2id", |b| b.iter(|| argon2id_run(module.clone()))); } criterion_group!(benches, criterion_benchmark); diff --git a/crates/tinywasm/benches/fibonacci.rs b/crates/tinywasm/benches/fibonacci.rs index 75d17a43..2865594a 100644 --- a/crates/tinywasm/benches/fibonacci.rs +++ b/crates/tinywasm/benches/fibonacci.rs @@ -23,7 +23,7 @@ fn fibonacci_from_twasm(twasm: &[u8]) -> Result { fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> { let mut store = Store::default(); let instance = ModuleInstance::instantiate(&mut store, module.into(), None)?; - let argon2 = instance.exported_func::( + let argon2 = instance.func_typed::( &store, match recursive { true => "fibonacci_recursive", @@ -37,12 +37,15 @@ fn fibonacci_run(module: TinyWasmModule, recursive: bool, n: i32) -> Result<()> fn criterion_benchmark(c: &mut Criterion) { let module = fibonacci_parse().expect("fibonacci_parse"); let twasm = fibonacci_to_twasm(&module).expect("fibonacci_to_twasm"); - - c.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); - c.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); - c.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); - c.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60))); - c.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26))); + let mut group = c.benchmark_group("fibonacci"); + + group.measurement_time(std::time::Duration::from_secs(2)); + group.bench_function("fibonacci_parse", |b| b.iter(fibonacci_parse)); + group.bench_function("fibonacci_to_twasm", |b| b.iter(|| fibonacci_to_twasm(&module))); + group.bench_function("fibonacci_from_twasm", |b| b.iter(|| fibonacci_from_twasm(&twasm))); + group.measurement_time(std::time::Duration::from_secs(10)); + group.bench_function("fibonacci_iterative_60", |b| b.iter(|| fibonacci_run(module.clone(), false, 60))); + group.bench_function("fibonacci_recursive_26", |b| b.iter(|| fibonacci_run(module.clone(), true, 26))); } criterion_group!(benches, criterion_benchmark); diff --git a/crates/tinywasm/benches/tinywasm.rs b/crates/tinywasm/benches/tinywasm.rs index 73096a50..7ab95878 100644 --- a/crates/tinywasm/benches/tinywasm.rs +++ b/crates/tinywasm/benches/tinywasm.rs @@ -26,7 +26,7 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> { let mut imports = Imports::default(); imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(()))).expect("define"); let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports)).expect("instantiate"); - let hello = instance.exported_func::<(), ()>(&store, "hello").expect("exported_func"); + let hello = instance.func_typed::<(), ()>(&store, "hello").expect("func_typed"); hello.call(&mut store, ()).expect("call"); Ok(()) } @@ -35,11 +35,12 @@ fn criterion_benchmark(c: &mut Criterion) { let module = tinywasm_parse().expect("tinywasm_parse"); let twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm"); let mut group = c.benchmark_group("tinywasm"); - group.measurement_time(std::time::Duration::from_secs(10)); + group.measurement_time(std::time::Duration::from_secs(2)); group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse)); group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module))); group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm))); + group.measurement_time(std::time::Duration::from_secs(10)); group.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone()))); } diff --git a/crates/tinywasm/benches/tinywasm_modes.rs b/crates/tinywasm/benches/tinywasm_modes.rs index edb472dc..54f80332 100644 --- a/crates/tinywasm/benches/tinywasm_modes.rs +++ b/crates/tinywasm/benches/tinywasm_modes.rs @@ -24,7 +24,7 @@ fn setup_typed_func(module: TinyWasmModule, engine: Option) -> Result<(S imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?; let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports))?; - let func = instance.exported_func::<(), ()>(&store, "hello")?; + let func = instance.func_typed::<(), ()>(&store, "hello")?; Ok((store, func)) } diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 75a094a1..51ac9b05 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -22,6 +22,7 @@ pub(crate) struct ExecutionState { /// A function handle #[cfg_attr(feature = "debug", derive(Debug))] pub struct FuncHandle { + pub(crate) store_id: usize, pub(crate) module_addr: ModuleInstanceAddr, pub(crate) addr: u32, pub(crate) ty: FuncType, @@ -53,6 +54,10 @@ impl FuncHandle { /// See #[inline] pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { + if self.store_id != store.id() { + return Err(Error::InvalidStore); + } + validate_call_params(&self.ty, params)?; let func_inst = store.state.get_func(self.addr); @@ -86,6 +91,10 @@ impl FuncHandle { store: &'store mut Store, params: &[WasmValue], ) -> Result> { + if self.store_id != store.id() { + return Err(Error::InvalidStore); + } + validate_call_params(&self.ty, params)?; let func_inst = store.state.get_func(self.addr); diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs index 3bf35e6d..e5fda8c2 100644 --- a/crates/tinywasm/src/imports.rs +++ b/crates/tinywasm/src/imports.rs @@ -6,7 +6,8 @@ use alloc::vec::Vec; use core::fmt::Debug; use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; -use crate::{LinkingError, MemoryRef, MemoryRefMut, Result, log}; +use crate::instance::{ExternItemRef, ExternItemRefMut}; +use crate::{GlobalRef, GlobalRefMut, LinkingError, MemoryRef, MemoryRefMut, Result, TableRef, TableRefMut, log}; use tinywasm_types::*; /// The internal representation of a function @@ -74,14 +75,54 @@ impl FuncContext<'_> { }) } - /// Get a reference to an exported memory - pub fn exported_memory(&self, name: &str) -> Result> { - self.module().exported_memory(self.store, name) + /// Get a reference to a memory export. + pub fn memory(&self, name: &str) -> Result> { + self.module().memory(self.store, name) } - /// Get a mutable reference to an exported memory - pub fn exported_memory_mut(&mut self, name: &str) -> Result> { - self.module().exported_memory_mut(self.store, name) + /// Get a mutable reference to a memory export. + pub fn memory_mut(&mut self, name: &str) -> Result> { + self.module().memory_mut(self.store, name) + } + + /// Get any exported extern value by name. + pub fn extern_item(&self, name: &str) -> Result> { + self.module().extern_item(self.store, name) + } + + /// Get any exported extern value by name with mutable access when applicable. + pub fn extern_item_mut(&mut self, name: &str) -> Result> { + self.module().extern_item_mut(self.store, name) + } + + /// Get a reference to a table export. + pub fn table(&self, name: &str) -> Result> { + self.module().table(self.store, name) + } + + /// Get a mutable reference to a table export. + pub fn table_mut(&mut self, name: &str) -> Result> { + self.module().table_mut(self.store, name) + } + + /// Get the value of a global export. + pub fn global_get(&self, name: &str) -> Result { + self.module().global_get(self.store, name) + } + + /// Get a reference to a global export. + pub fn global(&self, name: &str) -> Result> { + self.module().global(self.store, name) + } + + /// Get a mutable reference to a global export. + pub fn global_mut(&mut self, name: &str) -> Result> { + self.module().global_mut(self.store, name) + } + + /// Set the value of a mutable global export. + pub fn global_set(&mut self, name: &str, value: WasmValue) -> Result<()> { + self.module().global_set(self.store, name, value) } /// Charge additional fuel from the currently running resumable invocation. @@ -230,8 +271,12 @@ impl From<&Import> for ExternName { /// ```rust /// # use log; /// # fn main() -> tinywasm::Result<()> { -/// use tinywasm::{Imports, Extern}; +/// use tinywasm::{Extern, Imports, Module, Store}; /// use tinywasm::types::{ValType, TableType, MemoryType, MemoryArch, WasmValue}; +/// # let wasm = wat::parse_str("(module)").expect("valid wat"); +/// # let module = Module::parse_bytes(&wasm)?; +/// # let mut store = Store::default(); +/// # let my_other_instance = module.instantiate(&mut store, None)?; /// let mut imports = Imports::new(); /// /// // function args can be either a single @@ -249,18 +294,16 @@ impl From<&Import> for ExternName { /// .define("my_module", "table", Extern::table(table_type, table_init))? /// .define("my_module", "memory", Extern::memory(MemoryType::new(MemoryArch::I32, 1, Some(2), None)))? /// .define("my_module", "global_i32", Extern::global(WasmValue::I32(666), false))? -/// .link_module("my_other_module", 0)?; +/// .link_module("my_other_module", my_other_instance)?; /// # Ok(()) /// # } /// ``` -/// -/// Note that module instance addresses for [`Imports::link_module`] can be obtained from [`crate::ModuleInstance::id`]. /// Now, the imports object can be passed to [`crate::ModuleInstance::instantiate`]. #[derive(Default, Clone)] #[cfg_attr(feature = "debug", derive(Debug))] pub struct Imports { values: BTreeMap, - modules: BTreeMap, + modules: BTreeMap, } pub(crate) enum ResolvedExtern { @@ -297,8 +340,8 @@ impl Imports { /// Link a module /// /// This will automatically link all imported values on instantiation - pub fn link_module(&mut self, name: &str, addr: ModuleInstanceAddr) -> Result<&mut Self> { - self.modules.insert(name.to_string(), addr); + pub fn link_module(&mut self, name: &str, instance: crate::ModuleInstance) -> Result<&mut Self> { + self.modules.insert(name.to_string(), instance); Ok(self) } @@ -312,17 +355,20 @@ impl Imports { &mut self, store: &mut crate::Store, import: &Import, - ) -> Option> { + ) -> Result>> { let name = ExternName::from(import); if let Some(v) = self.values.get(&name) { - return Some(ResolvedExtern::Extern(v.clone())); + return Ok(Some(ResolvedExtern::Extern(v.clone()))); } - if let Some(addr) = self.modules.get(&name.module) { - let instance = store.get_module_instance(*addr)?; - return Some(ResolvedExtern::Store(instance.export_addr(&import.name)?)); + if let Some(instance) = self.modules.get(&name.module) { + if instance.0.store_id != store.id() { + return Err(crate::Error::InvalidStore); + } + + return Ok(instance.export_addr(&import.name).map(ResolvedExtern::Store)); } - None + Ok(None) } #[cfg(not(feature = "debug"))] @@ -392,16 +438,17 @@ impl Imports { let mut imports = ResolvedImports::new(); for import in &*module.0.imports { - match self.take(store, import).ok_or_else(|| LinkingError::unknown_import(import))? { + match self.take(store, import)?.ok_or_else(|| LinkingError::unknown_import(import))? { // A link to something that needs to be added to the store ResolvedExtern::Extern(ex) => match (ex, &import.kind) { (Extern::Global { ty, val }, ImportKind::Global(import_ty)) => { Self::compare_types(import, &ty, import_ty)?; imports.globals.push(store.add_global(ty, val.into(), idx)?); } - (Extern::Table { ty, .. }, ImportKind::Table(import_ty)) => { + (Extern::Table { ty, init }, ImportKind::Table(import_ty)) => { Self::compare_table_types(import, &ty, import_ty)?; - imports.tables.push(store.add_table(ty, idx)?); + Self::compare_types(import, &ty.element_type, &init.val_type())?; + imports.tables.push(store.add_table(ty, init, idx)?); } (Extern::Memory { ty }, ImportKind::Memory(import_ty)) => { Self::compare_memory_types(import, &ty, import_ty, None)?; diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index b95a94d1..a23213bf 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -2,8 +2,35 @@ use alloc::boxed::Box; use alloc::{format, rc::Rc}; use tinywasm_types::*; -use crate::func::{FromWasmValueTuple, IntoWasmValueTuple}; -use crate::{Error, FuncHandle, FuncHandleTyped, Imports, MemoryRef, MemoryRefMut, Module, Result, Store}; +use crate::func::{FromWasmValueTuple, IntoWasmValueTuple, ValTypesFromTuple}; +use crate::{ + Error, FuncHandle, FuncHandleTyped, GlobalRef, GlobalRefMut, Imports, MemoryRef, MemoryRefMut, Module, Result, + Store, TableRef, TableRefMut, +}; + +/// A typed borrowed view over an exported extern value. +pub enum ExternItemRef<'a> { + /// Exported function handle. + Func(FuncHandle), + /// Exported memory reference. + Memory(MemoryRef<'a>), + /// Exported table reference. + Table(TableRef<'a>), + /// Exported global reference. + Global(GlobalRef<'a>), +} + +/// A typed mutable borrowed view over an exported extern value. +pub enum ExternItemRefMut<'a> { + /// Exported function handle. + Func(FuncHandle), + /// Exported mutable memory reference. + Memory(MemoryRefMut<'a>), + /// Exported mutable table reference. + Table(TableRefMut<'a>), + /// Exported mutable global reference. + Global(GlobalRefMut<'a>), +} /// An instantiated WebAssembly module /// @@ -99,6 +126,14 @@ impl ModuleInstanceInner { } impl ModuleInstance { + #[inline] + fn validate_store(&self, store: &Store) -> Result<()> { + if self.0.store_id != store.id() { + return Err(Error::InvalidStore); + } + Ok(()) + } + /// Get the module instance's address pub fn id(&self) -> ModuleInstanceAddr { self.0.idx @@ -154,57 +189,330 @@ impl ModuleInstance { Some(ExternVal::new(exports.kind, *addr)) } - /// Get an exported function by name - pub fn exported_func_untyped(&self, store: &Store, name: &str) -> Result { - if self.0.store_id != store.id() { - return Err(Error::InvalidStore); + /// Returns an iterator over all exported extern values for this instance. + pub fn exports<'a>(&'a self, store: &'a Store) -> Result)> + 'a> { + self.validate_store(store)?; + + Ok(self.0.exports.iter().map(move |export| { + let name = export.name.as_ref(); + let item = match export.kind { + ExternalKind::Func => { + let idx = export.index as usize; + let func_addr = *self + .0 + .func_addrs + .get(idx) + .unwrap_or_else(|| unreachable!("invalid function export index: {}", export.index)); + let ty = store.state.get_func(func_addr).func.ty(); + ExternItemRef::Func(FuncHandle { + store_id: self.0.store_id, + module_addr: self.id(), + addr: func_addr, + ty: ty.clone(), + }) + } + ExternalKind::Table => { + let idx = export.index as usize; + let table_addr = *self + .0 + .table_addrs + .get(idx) + .unwrap_or_else(|| unreachable!("invalid table export index: {}", export.index)); + ExternItemRef::Table(TableRef(store.state.get_table(table_addr))) + } + ExternalKind::Memory => { + let idx = export.index as usize; + let mem_addr = *self + .0 + .mem_addrs + .get(idx) + .unwrap_or_else(|| unreachable!("invalid memory export index: {}", export.index)); + ExternItemRef::Memory(MemoryRef(store.state.get_mem(mem_addr))) + } + ExternalKind::Global => { + let idx = export.index as usize; + let global_addr = *self + .0 + .global_addrs + .get(idx) + .unwrap_or_else(|| unreachable!("invalid global export index: {}", export.index)); + ExternItemRef::Global(GlobalRef(store.state.get_global(global_addr))) + } + }; + + (name, item) + })) + } + + #[inline] + fn require_export(&self, name: &str) -> Result { + self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}"))) + } + + #[inline] + #[cfg(feature = "guest_debug")] + fn index_addr(slice: &[T], idx: u32, kind: &str) -> Result { + slice.get(idx as usize).copied().ok_or_else(|| Error::Other(format!("{kind} index out of bounds: {idx}"))) + } + + /// Get any exported extern value by name. + pub fn extern_item<'a>(&self, store: &'a Store, name: &str) -> Result> { + self.validate_store(store)?; + match self.require_export(name)? { + ExternVal::Func(_) => self.func(store, name).map(ExternItemRef::Func), + ExternVal::Memory(mem_addr) => Ok(ExternItemRef::Memory(MemoryRef(store.state.get_mem(mem_addr)))), + ExternVal::Table(table_addr) => Ok(ExternItemRef::Table(TableRef(store.state.get_table(table_addr)))), + ExternVal::Global(global_addr) => Ok(ExternItemRef::Global(GlobalRef(store.state.get_global(global_addr)))), } + } - let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + /// Get any exported extern value by name with mutable access when applicable. + pub fn extern_item_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + self.validate_store(store)?; + match self.require_export(name)? { + ExternVal::Func(_) => self.func(store, name).map(ExternItemRefMut::Func), + ExternVal::Memory(mem_addr) => { + Ok(ExternItemRefMut::Memory(MemoryRefMut(store.state.get_mem_mut(mem_addr)))) + } + ExternVal::Table(table_addr) => { + Ok(ExternItemRefMut::Table(TableRefMut(store.state.get_table_mut(table_addr)))) + } + ExternVal::Global(global_addr) => { + Ok(ExternItemRefMut::Global(GlobalRefMut(store.state.get_global_mut(global_addr)))) + } + } + } + + /// Get a function export by name. + pub fn func(&self, store: &Store, name: &str) -> Result { + self.validate_store(store)?; + + let export = self.require_export(name)?; let ExternVal::Func(func_addr) = export else { return Err(Error::Other(format!("Export is not a function: {name}"))); }; let ty = store.state.get_func(func_addr).func.ty(); - Ok(FuncHandle { addr: func_addr, module_addr: self.id(), ty: ty.clone() }) + Ok(FuncHandle { store_id: self.0.store_id, addr: func_addr, module_addr: self.id(), ty: ty.clone() }) + } + + /// Get a function by its module-local index. + /// + /// This exposes an internal module-owned function directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// introspection. Calling private functions can change behavior in ways the + /// module author did not expose as part of the public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn func_by_index(&self, store: &Store, func_index: FuncAddr) -> Result { + self.validate_store(store)?; + let func_addr = Self::index_addr(&self.0.func_addrs, func_index, "function")?; + + let ty = store.state.get_func(func_addr).func.ty(); + Ok(FuncHandle { store_id: self.0.store_id, addr: func_addr, module_addr: self.id(), ty: ty.clone() }) } - /// Get a typed exported function by name - pub fn exported_func( + /// Get a typed function export by name. + pub fn func_typed( &self, store: &Store, name: &str, ) -> Result> { - let func = self.exported_func_untyped(store, name)?; + let func = self.func(store, name)?; + Self::validate_typed_func::(&func, name)?; Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) } - /// Get an exported memory by name - pub fn exported_memory<'a>(&self, store: &'a Store, name: &str) -> Result> { - let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + /// Get a typed function by its module-local index. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn func_typed_by_index( + &self, + store: &Store, + func_index: FuncAddr, + ) -> Result> { + let func = self.func_by_index(store, func_index)?; + Self::validate_typed_func::(&func, &format!("function index {func_index}"))?; + Ok(FuncHandleTyped { func, marker: core::marker::PhantomData }) + } + + fn validate_typed_func( + func: &FuncHandle, + func_name: &str, + ) -> Result<()> { + let expected = FuncType { params: P::val_types(), results: R::val_types() }; + if func.ty != expected { + return Err(Error::Other(format!( + "function type mismatch for {func_name}: expected {expected:?}, actual {:?}", + func.ty + ))); + } + + Ok(()) + } + + /// Get a memory export by name. + pub fn memory<'a>(&self, store: &'a Store, name: &str) -> Result> { + self.validate_store(store)?; + + let export = self.require_export(name)?; let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - self.memory(store, mem_addr) + Ok(MemoryRef(store.state.get_mem(mem_addr))) } - /// Get an exported memory by name (mutable) - pub fn exported_memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { - let export = self.export_addr(name).ok_or_else(|| Error::Other(format!("Export not found: {name}")))?; + /// Get a mutable memory export by name. + pub fn memory_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + self.validate_store(store)?; + + let export = self.require_export(name)?; let ExternVal::Memory(mem_addr) = export else { return Err(Error::Other(format!("Export is not a memory: {name}"))); }; - self.memory_mut(store, mem_addr) + Ok(MemoryRefMut(store.state.get_mem_mut(mem_addr))) + } + + /// Get a memory by its module-local index. + /// + /// This exposes an internal module-owned memory directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// inspection. Mutating a private memory can change module behavior in ways + /// that are not part of the module's public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn memory_by_index<'a>(&self, store: &'a Store, memory_index: MemAddr) -> Result> { + self.validate_store(store)?; + let mem_addr = Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?; + Ok(MemoryRef(store.state.get_mem(mem_addr))) + } + + /// Get a mutable memory by its module-local index. + /// + /// This exposes an internal module-owned memory directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// inspection. Mutating a private memory can change module behavior in ways + /// that are not part of the module's public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn memory_mut_by_index<'a>(&self, store: &'a mut Store, memory_index: MemAddr) -> Result> { + self.validate_store(store)?; + let mem_addr = Self::index_addr(&self.0.mem_addrs, memory_index, "memory")?; + Ok(MemoryRefMut(store.state.get_mem_mut(mem_addr))) + } + + /// Get a table export by name. + pub fn table<'a>(&self, store: &'a Store, name: &str) -> Result> { + self.validate_store(store)?; + + let export = self.require_export(name)?; + let ExternVal::Table(table_addr) = export else { + return Err(Error::Other(format!("Export is not a table: {name}"))); + }; + Ok(TableRef(store.state.get_table(table_addr))) + } + + /// Get a mutable table export by name. + pub fn table_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + self.validate_store(store)?; + + let export = self.require_export(name)?; + let ExternVal::Table(table_addr) = export else { + return Err(Error::Other(format!("Export is not a table: {name}"))); + }; + Ok(TableRefMut(store.state.get_table_mut(table_addr))) + } + + /// Get a table by its module-local index. + /// + /// This exposes an internal module-owned table directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// inspection. Mutating a private table can change module behavior in ways + /// that are not part of the module's public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn table_by_index<'a>(&self, store: &'a Store, table_index: TableAddr) -> Result> { + self.validate_store(store)?; + let table_addr = Self::index_addr(&self.0.table_addrs, table_index, "table")?; + Ok(TableRef(store.state.get_table(table_addr))) + } + + /// Get a mutable table by its module-local index. + /// + /// This exposes an internal module-owned table directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// inspection. Mutating a private table can change module behavior in ways + /// that are not part of the module's public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn table_mut_by_index<'a>(&self, store: &'a mut Store, table_index: TableAddr) -> Result> { + self.validate_store(store)?; + let table_addr = Self::index_addr(&self.0.table_addrs, table_index, "table")?; + Ok(TableRefMut(store.state.get_table_mut(table_addr))) + } + + /// Get the value of a global export by name. + pub fn global_get(&self, store: &Store, name: &str) -> Result { + self.global(store, name).map(|global| global.get()) + } + + /// Get a reference to a global export by name. + pub fn global<'a>(&self, store: &'a Store, name: &str) -> Result> { + self.validate_store(store)?; + + let export = self.require_export(name)?; + let ExternVal::Global(global_addr) = export else { + return Err(Error::Other(format!("Export is not a global: {name}"))); + }; + + Ok(GlobalRef(store.state.get_global(global_addr))) + } + + /// Get a mutable reference to a global export by name. + pub fn global_mut<'a>(&self, store: &'a mut Store, name: &str) -> Result> { + self.validate_store(store)?; + + let export = self.require_export(name)?; + let ExternVal::Global(global_addr) = export else { + return Err(Error::Other(format!("Export is not a global: {name}"))); + }; + + Ok(GlobalRefMut(store.state.get_global_mut(global_addr))) } - /// Get a memory by address - pub fn memory<'a>(&self, store: &'a Store, addr: MemAddr) -> Result> { - Ok(MemoryRef(store.state.get_mem(self.0.resolve_mem_addr(addr)))) + /// Set the value of a mutable global export by name. + pub fn global_set(&self, store: &mut Store, name: &str, value: WasmValue) -> Result<()> { + self.global_mut(store, name)?.set(value) } - /// Get a memory by address (mutable) - pub fn memory_mut<'a>(&self, store: &'a mut Store, addr: MemAddr) -> Result> { - Ok(MemoryRefMut(store.state.get_mem_mut(self.0.resolve_mem_addr(addr)))) + /// Get a reference to a global by its module-local index. + /// + /// This exposes an internal module-owned global directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// inspection. Mutating a private global can change module behavior in ways + /// that are not part of the module's public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn global_by_index<'a>(&self, store: &'a Store, global_index: GlobalAddr) -> Result> { + self.validate_store(store)?; + let global_addr = Self::index_addr(&self.0.global_addrs, global_index, "global")?; + + Ok(GlobalRef(store.state.get_global(global_addr))) + } + + /// Get a mutable reference to a global by its module-local index. + /// + /// This exposes an internal module-owned global directly and bypasses the + /// normal export boundary. It is mainly intended for tooling and + /// inspection. Mutating a private global can change module behavior in ways + /// that are not part of the module's public API. + #[cfg_attr(docsrs, doc(cfg(feature = "guest_debug")))] + #[cfg(feature = "guest_debug")] + pub fn global_mut_by_index<'a>(&self, store: &'a mut Store, global_index: GlobalAddr) -> Result> { + self.validate_store(store)?; + let global_addr = Self::index_addr(&self.0.global_addrs, global_index, "global")?; + + Ok(GlobalRefMut(store.state.get_global_mut(global_addr))) } /// Get the start function of the module @@ -214,9 +522,7 @@ impl ModuleInstance { /// /// See pub fn start_func(&self, store: &Store) -> Result> { - if self.0.store_id != store.id() { - return Err(Error::InvalidStore); - } + self.validate_store(store)?; let func_index = match self.0.func_start { Some(func_index) => func_index, @@ -232,7 +538,7 @@ impl ModuleInstance { let func_addr = self.0.resolve_func_addr(func_index); let ty = store.state.get_func(func_addr).func.ty(); - Ok(Some(FuncHandle { module_addr: self.id(), addr: func_addr, ty: ty.clone() })) + Ok(Some(FuncHandle { store_id: self.0.store_id, module_addr: self.id(), addr: func_addr, ty: ty.clone() })) } /// Invoke the start function of the module diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 12f28f4a..9218edea 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -306,7 +306,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { MemoryGrow(addr) => self.exec_memory_grow(*addr)?, // Bulk memory operations - MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, + MemoryCopy { dst_mem, src_mem } => self.exec_memory_copy(*dst_mem, *src_mem)?, MemoryFill(addr) => self.exec_memory_fill(*addr)?, MemoryFillImm(addr, val, size) => self.exec_memory_fill_imm(*addr, *val, *size)?, MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, @@ -320,7 +320,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { TableInit(elem_idx, table_idx) => self.exec_table_init(*elem_idx, *table_idx)?, TableGrow(table_idx) => self.exec_table_grow(*table_idx)?, TableFill(table_idx) => self.exec_table_fill(*table_idx)?, - TableCopy { from, to } => self.exec_table_copy(*from, *to)?, + TableCopy { dst_table, src_table } => self.exec_table_copy(*dst_table, *src_table)?, // Core memory load/store operations I32Store(m) => self.exec_mem_store::(m.mem_addr(), m.offset(), |v| v)?, @@ -919,21 +919,23 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { Ok(()) } - fn exec_memory_copy(&mut self, from: u32, to: u32) -> Result<()> { + fn exec_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Result<()> { let size: i32 = self.store.stack.values.pop(); let src: i32 = self.store.stack.values.pop(); let dst: i32 = self.store.stack.values.pop(); - if from == to { - let mem_from = self.store.state.get_mem_mut(self.module.resolve_mem_addr(from)); + if dst_mem == src_mem { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(dst_mem)); // copy within the same memory - mem_from.copy_within(dst as usize, src as usize, size as usize)?; + mem.copy_within(dst as usize, src as usize, size as usize)?; } else { // copy between two memories - let (mem_from, mem_to) = - self.store.state.get_mems_mut(self.module.resolve_mem_addr(from), self.module.resolve_mem_addr(to))?; + let (dst_memory, src_memory) = self + .store + .state + .get_mems_mut(self.module.resolve_mem_addr(dst_mem), self.module.resolve_mem_addr(src_mem))?; - mem_from.copy_from_slice(dst as usize, mem_to.load(src as usize, size as usize)?)?; + dst_memory.copy_from_slice(dst as usize, src_memory.load(src as usize, size as usize)?)?; } Ok(()) } @@ -977,25 +979,25 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let Some(data) = &data.data else { return Err(Trap::MemoryOutOfBounds { offset: 0, len: 0, max: 0 }.into()) }; mem.store(dst as usize, size as usize, &data[offset as usize..((offset + size) as usize)]) } - fn exec_table_copy(&mut self, from: u32, to: u32) -> Result<()> { + fn exec_table_copy(&mut self, dst_table: u32, src_table: u32) -> Result<()> { let size: i32 = self.store.stack.values.pop(); let src: i32 = self.store.stack.values.pop(); let dst: i32 = self.store.stack.values.pop(); - if from == to { - // copy within the same memory - self.store.state.get_table_mut(self.module.resolve_table_addr(from)).copy_within( + if dst_table == src_table { + // copy within the same table + self.store.state.get_table_mut(self.module.resolve_table_addr(dst_table)).copy_within( dst as usize, src as usize, size as usize, ) } else { - // copy between two memories - let (table_from, table_to) = self + // copy between two tables + let (dst_table_ref, src_table_ref) = self .store .state - .get_tables_mut(self.module.resolve_table_addr(from), self.module.resolve_table_addr(to))?; - table_to.copy_from_slice(dst as usize, table_from.load(src as usize, size as usize)?) + .get_tables_mut(self.module.resolve_table_addr(dst_table), self.module.resolve_table_addr(src_table))?; + dst_table_ref.copy_from_slice(dst as usize, src_table_ref.load(src as usize, size as usize)?) } } diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index 7afc2f65..6847680a 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -22,10 +22,14 @@ //! Enables the `tinywasm-parser` crate. This is enabled by default. //!- **`archive`**\ //! Enables pre-parsing of archives. This is enabled by default. +//!- **`guest_debug`**\ +//! Enables module-internal by-index inspection APIs (`*_by_index`). //! //! 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. + +#![cfg_attr(docsrs, feature(doc_cfg))] //! //! ## Getting Started //! The easiest way to get started is to use the [`Module::parse_bytes`] function to load a @@ -51,9 +55,9 @@ //! let instance = module.instantiate(&mut store, None)?; //! //! // Get a typed handle to the exported "add" function -//! // Alternatively, you can use `instance.get_func` to get an untyped handle +//! // Alternatively, you can use `instance.func` to get an untyped handle //! // that takes and returns [`WasmValue`]s -//! let func = instance.exported_func::<(i32, i32), i32>(&mut store, "add")?; +//! let func = instance.func_typed::<(i32, i32), i32>(&mut store, "add")?; //! let res = func.call(&mut store, (1, 2))?; //! //! assert_eq!(res, 3); @@ -94,8 +98,8 @@ mod error; pub use error::*; pub use func::{ExecProgress, FuncExecution, FuncExecutionTyped, FuncHandle, FuncHandleTyped}; pub use imports::*; -pub use instance::ModuleInstance; -pub use module::Module; +pub use instance::{ExternItemRef, ExternItemRefMut, ModuleInstance}; +pub use module::{ExportType, ImportType, Module, ModuleExport, ModuleImport}; pub use reference::*; pub use store::*; diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 1ce3ff0c..df8b0ac5 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -1,5 +1,73 @@ use crate::{Imports, ModuleInstance, Result, Store}; -use tinywasm_types::TinyWasmModule; +use tinywasm_types::{ExternalKind, FuncType, TinyWasmModule}; + +fn imported_func_type(module: &TinyWasmModule, function_index: usize) -> Option<&FuncType> { + let mut seen = 0usize; + for import in module.imports.iter() { + if let tinywasm_types::ImportKind::Function(type_idx) = import.kind { + if seen == function_index { + return module.func_types.get(type_idx as usize); + } + seen += 1; + } + } + None +} + +fn imported_global_type(module: &TinyWasmModule, global_index: usize) -> Option<&tinywasm_types::GlobalType> { + let mut seen = 0usize; + for import in module.imports.iter() { + if let tinywasm_types::ImportKind::Global(global_ty) = &import.kind { + if seen == global_index { + return Some(global_ty); + } + seen += 1; + } + } + None +} + +/// A module import descriptor. +pub struct ModuleImport<'a> { + /// Importing module name. + pub module: &'a str, + /// Import name. + pub name: &'a str, + /// Import type. + pub ty: ImportType<'a>, +} + +/// A module export descriptor. +pub struct ModuleExport<'a> { + /// Export name. + pub name: &'a str, + /// Export type. + pub ty: ExportType<'a>, +} + +/// Imported entity type. +pub enum ImportType<'a> { + /// Imported function type. + Func(&'a FuncType), + /// Imported table type. + Table(&'a tinywasm_types::TableType), + /// Imported memory type. + Memory(&'a tinywasm_types::MemoryType), + /// Imported global type. + Global(&'a tinywasm_types::GlobalType), +} + +/// Exported entity type. +pub enum ExportType<'a> { + /// Exported function type. + Func(&'a FuncType), + /// Exported table type. + Table(&'a tinywasm_types::TableType), + /// Exported memory type. + Memory(&'a tinywasm_types::MemoryType), + /// Exported global type. + Global(&'a tinywasm_types::GlobalType), +} /// A WebAssembly Module /// @@ -54,4 +122,67 @@ impl Module { let _ = instance.start(store)?; Ok(instance) } + + /// Returns an iterator over the module's import descriptors. + /// + /// The returned data mirrors the module's import section and preserves order. + pub fn imports(&self) -> impl Iterator> { + self.0.imports.iter().filter_map(|import| { + let ty = match &import.kind { + tinywasm_types::ImportKind::Function(type_idx) => { + Some(ImportType::Func(self.0.func_types.get(*type_idx as usize)?)) + } + tinywasm_types::ImportKind::Table(table_ty) => Some(ImportType::Table(table_ty)), + tinywasm_types::ImportKind::Memory(memory_ty) => Some(ImportType::Memory(memory_ty)), + tinywasm_types::ImportKind::Global(global_ty) => Some(ImportType::Global(global_ty)), + }?; + + Some(ModuleImport { module: import.module.as_ref(), name: import.name.as_ref(), ty }) + }) + } + + /// Returns an iterator over the module's export descriptors. + /// + /// The returned data mirrors the module's export section and preserves order. + pub fn exports(&self) -> impl Iterator> { + self.0.exports.iter().filter_map(|export| { + let ty = match export.kind { + ExternalKind::Func => { + let idx = export.index as usize; + let imported_funcs = self + .0 + .imports + .iter() + .filter(|import| matches!(import.kind, tinywasm_types::ImportKind::Function(_))) + .count(); + + if idx < imported_funcs { + ExportType::Func(imported_func_type(&self.0, idx)?) + } else { + let local_idx = idx - imported_funcs; + ExportType::Func(&self.0.funcs.get(local_idx)?.ty) + } + } + ExternalKind::Table => ExportType::Table(self.0.table_types.get(export.index as usize)?), + ExternalKind::Memory => ExportType::Memory(self.0.memory_types.get(export.index as usize)?), + ExternalKind::Global => { + let idx = export.index as usize; + let imported_globals = self + .0 + .imports + .iter() + .filter(|import| matches!(import.kind, tinywasm_types::ImportKind::Global(_))) + .count(); + if idx < imported_globals { + ExportType::Global(imported_global_type(&self.0, idx)?) + } else { + let local_idx = idx - imported_globals; + ExportType::Global(&self.0.globals.get(local_idx)?.ty) + } + } + }; + + Some(ModuleExport { name: export.name.as_ref(), ty }) + }) + } } diff --git a/crates/tinywasm/src/reference.rs b/crates/tinywasm/src/reference.rs index 7901e572..4deac5f1 100644 --- a/crates/tinywasm/src/reference.rs +++ b/crates/tinywasm/src/reference.rs @@ -1,9 +1,11 @@ use core::ffi::CStr; use alloc::string::{String, ToString}; -use alloc::{ffi::CString, format, vec::Vec}; +use alloc::{ffi::CString, format}; -use crate::{MemoryInstance, Result}; +use crate::store::{GlobalInstance, TableElement, TableInstance}; +use crate::{Error, MemoryInstance, Result}; +use tinywasm_types::{ExternRef, FuncRef, GlobalType, TableAddr, TableType, ValType, WasmValue}; // This module essentially contains the public APIs to interact with the data stored in the store @@ -11,10 +13,42 @@ use crate::{MemoryInstance, Result}; #[cfg_attr(feature = "debug", derive(Debug))] pub struct MemoryRef<'a>(pub(crate) &'a MemoryInstance); -/// A borrowed reference to a memory instance +/// A mutable reference to a memory instance. #[cfg_attr(feature = "debug", derive(Debug))] pub struct MemoryRefMut<'a>(pub(crate) &'a mut MemoryInstance); +/// A reference to a table instance. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct TableRef<'a>(pub(crate) &'a TableInstance); + +/// A mutable reference to a table instance. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct TableRefMut<'a>(pub(crate) &'a mut TableInstance); + +/// A reference to a global instance. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct GlobalRef<'a>(pub(crate) &'a GlobalInstance); + +/// A mutable reference to a global instance. +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct GlobalRefMut<'a>(pub(crate) &'a mut GlobalInstance); + +fn table_element_to_value(element_type: ValType, element: TableElement) -> WasmValue { + match element_type { + ValType::RefFunc => WasmValue::RefFunc(FuncRef::new(element.addr())), + ValType::RefExtern => WasmValue::RefExtern(ExternRef::new(element.addr())), + _ => unreachable!("table element type must be a reference type"), + } +} + +fn table_value_to_element(element_type: ValType, value: WasmValue) -> Result { + match (element_type, value) { + (ValType::RefFunc, WasmValue::RefFunc(func_ref)) => Ok(TableElement::from(func_ref.addr())), + (ValType::RefExtern, WasmValue::RefExtern(extern_ref)) => Ok(TableElement::from(extern_ref.addr())), + _ => Err(Error::Other("invalid table value type".to_string())), + } +} + impl MemoryRefLoad for MemoryRef<'_> { /// Load a slice of memory fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { @@ -30,28 +64,43 @@ impl MemoryRefLoad for MemoryRefMut<'_> { } impl MemoryRef<'_> { + /// Returns the full raw memory data. + pub fn data(&self) -> &[u8] { + &self.0.data + } + + /// Returns the raw memory byte length. + pub fn data_size(&self) -> usize { + self.0.data.len() + } + /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { self.0.load(offset, len) } - - /// Load a slice of memory as a vector - pub fn load_vec(&self, offset: usize, len: usize) -> Result> { - self.load(offset, len).map(<[u8]>::to_vec) - } } impl MemoryRefMut<'_> { + /// Returns the full raw memory data. + pub fn data(&self) -> &[u8] { + &self.0.data + } + + /// Returns the full raw mutable memory data. + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.0.data + } + + /// Returns the raw memory byte length. + pub fn data_size(&self) -> usize { + self.0.data.len() + } + /// Load a slice of memory pub fn load(&self, offset: usize, len: usize) -> Result<&[u8]> { self.0.load(offset, len) } - /// Load a slice of memory as a vector - pub fn load_vec(&self, offset: usize, len: usize) -> Result> { - self.load(offset, len).map(<[u8]>::to_vec) - } - /// Grow the memory by the given number of pages pub fn grow(&mut self, delta_pages: i64) -> Option { self.0.grow(delta_pages) @@ -64,7 +113,7 @@ impl MemoryRefMut<'_> { /// Copy a slice of memory to another place in memory pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> { - self.0.copy_within(src, dst, len) + self.0.copy_within(dst, src, len) } /// Fill a slice of memory with a value @@ -78,12 +127,112 @@ impl MemoryRefMut<'_> { } } +impl TableRef<'_> { + /// Get the type of the table. + pub fn ty(&self) -> TableType { + self.0.kind.clone() + } + + /// Get the current number of elements in the table. + pub fn size(&self) -> usize { + self.0.size() as usize + } + + /// Get a table element as a wasm reference value. + pub fn get(&self, index: TableAddr) -> Result { + self.0.get_wasm_val(index) + } + + /// Load a range of table elements and iterate over wasm reference values. + pub fn load(&self, offset: usize, len: usize) -> Result + '_> { + let element_type = self.0.kind.element_type; + let elements = self.0.load(offset, len)?; + Ok(elements.iter().copied().map(move |element| table_element_to_value(element_type, element))) + } +} + +impl TableRefMut<'_> { + /// Get the type of the table. + pub fn ty(&self) -> TableType { + self.0.kind.clone() + } + + /// Get the current number of elements in the table. + pub fn size(&self) -> usize { + self.0.size() as usize + } + + /// Get a table element as a wasm reference value. + pub fn get(&self, index: TableAddr) -> Result { + self.0.get_wasm_val(index) + } + + /// Load a range of table elements and iterate over wasm reference values. + pub fn load(&self, offset: usize, len: usize) -> Result + '_> { + let element_type = self.0.kind.element_type; + let elements = self.0.load(offset, len)?; + Ok(elements.iter().copied().map(move |element| table_element_to_value(element_type, element))) + } + + /// Set a table element. + pub fn set(&mut self, index: TableAddr, value: WasmValue) -> Result<()> { + let value = table_value_to_element(self.0.kind.element_type, value)?; + self.0.set(index, value) + } + + /// Copy elements within the same table. + pub fn copy_within(&mut self, src: usize, dst: usize, len: usize) -> Result<()> { + self.0.copy_within(dst, src, len) + } + + /// Grow the table and return the previous size. + pub fn grow(&mut self, delta: i32, init: WasmValue) -> Result { + let old_size = self.size(); + let init = table_value_to_element(self.0.kind.element_type, init)?; + self.0.grow(delta, init)?; + Ok(old_size) + } +} + +impl GlobalRef<'_> { + /// Get the type of the global. + pub fn ty(&self) -> GlobalType { + self.0.ty + } + + /// Get the current value of the global. + pub fn get(&self) -> WasmValue { + self.0.value.get().attach_type(self.0.ty.ty) + } +} + +impl GlobalRefMut<'_> { + /// Get the type of the global. + pub fn ty(&self) -> GlobalType { + self.0.ty + } + + /// Get the current value of the global. + pub fn get(&self) -> WasmValue { + self.0.value.get().attach_type(self.0.ty.ty) + } + + /// Set the current value of the global. + pub fn set(&mut self, value: WasmValue) -> Result<()> { + if !self.0.ty.mutable { + return Err(Error::Other("global is immutable".to_string())); + } + if value.val_type() != self.0.ty.ty { + return Err(Error::Other("invalid global value type".to_string())); + } + self.0.value.set(value.into()); + Ok(()) + } +} + #[doc(hidden)] pub trait MemoryRefLoad { fn load(&self, offset: usize, len: usize) -> Result<&[u8]>; - fn load_vec(&self, offset: usize, len: usize) -> Result> { - self.load(offset, len).map(<[u8]>::to_vec) - } } /// Convenience methods for loading strings from memory diff --git a/crates/tinywasm/src/store/mod.rs b/crates/tinywasm/src/store/mod.rs index aae8b351..648efaa3 100644 --- a/crates/tinywasm/src/store/mod.rs +++ b/crates/tinywasm/src/store/mod.rs @@ -207,6 +207,14 @@ impl State { } } + /// Get the global at the actual index in the store + pub(crate) fn get_global_mut(&mut self, addr: GlobalAddr) -> &mut GlobalInstance { + match self.globals.get_mut(addr as usize) { + Some(global) => global, + None => unreachable!("global {addr} not found. This should be unreachable"), + } + } + /// Get the global at the actual index in the store pub(crate) fn get_global_val(&self, addr: MemAddr) -> TinyWasmValue { match self.globals.get(addr as usize) { @@ -444,8 +452,19 @@ impl Store { Ok(self.state.globals.len() as Addr - 1) } - pub(crate) fn add_table(&mut self, table: TableType, _idx: ModuleInstanceAddr) -> Result { - self.state.tables.push(TableInstance::new(table)); + pub(crate) fn add_table( + &mut self, + table: TableType, + init: WasmValue, + _idx: ModuleInstanceAddr, + ) -> Result { + let init = match (table.element_type, init) { + (ValType::RefFunc, WasmValue::RefFunc(func_ref)) => TableElement::from(func_ref.addr()), + (ValType::RefExtern, WasmValue::RefExtern(extern_ref)) => TableElement::from(extern_ref.addr()), + _ => return Err(Error::Other("invalid table init value".to_string())), + }; + + self.state.tables.push(TableInstance::new_with_init(table, init)); Ok(self.state.tables.len() as TableAddr - 1) } diff --git a/crates/tinywasm/src/store/table.rs b/crates/tinywasm/src/store/table.rs index 7bbda838..86f9f0d5 100644 --- a/crates/tinywasm/src/store/table.rs +++ b/crates/tinywasm/src/store/table.rs @@ -15,7 +15,11 @@ pub(crate) struct TableInstance { impl TableInstance { pub(crate) fn new(kind: TableType) -> Self { - Self { elements: vec![TableElement::Uninitialized; kind.size_initial as usize], kind } + Self::new_with_init(kind, TableElement::Uninitialized) + } + + pub(crate) fn new_with_init(kind: TableType, init: TableElement) -> Self { + Self { elements: vec![init; kind.size_initial as usize], kind } } #[inline(never)] diff --git a/crates/tinywasm/tests/host_func_signature_check.rs b/crates/tinywasm/tests/host_func_signature_check.rs index 0e4ba699..4590e77e 100644 --- a/crates/tinywasm/tests/host_func_signature_check.rs +++ b/crates/tinywasm/tests/host_func_signature_check.rs @@ -43,7 +43,7 @@ fn test_return_invalid_type() -> Result<()> { .unwrap(); let instance = module.clone().instantiate(&mut store, Some(imports)).unwrap(); - let caller = instance.exported_func_untyped(&store, "call_hfn").unwrap(); + let caller = instance.func(&store, "call_hfn").unwrap(); // Return-type mismatch is only observable at call time. let should_succeed = returned_values.iter().map(WasmValue::val_type).eq(func_ty.results.iter().copied()); let call_res = caller.call(&mut store, &args); diff --git a/crates/tinywasm/tests/import_linking.rs b/crates/tinywasm/tests/import_linking.rs new file mode 100644 index 00000000..85c41ed3 --- /dev/null +++ b/crates/tinywasm/tests/import_linking.rs @@ -0,0 +1,57 @@ +use eyre::Result; +use tinywasm::{Error, Imports, Module, Store}; + +const WASM_ADD: &str = r#" + (module + (func $add (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + (export "add" (func $add))) +"#; + +const WASM_IMPORT: &str = r#" + (module + (import "adder" "add" (func $add (param i32 i32) (result i32))) + (func (export "main") (result i32) + i32.const 1 + i32.const 2 + call $add)) +"#; + +fn parse_modules() -> Result<(Module, Module)> { + let add = Module::parse_bytes(&wat::parse_str(WASM_ADD)?)?; + let import = Module::parse_bytes(&wat::parse_str(WASM_IMPORT)?)?; + Ok((add, import)) +} + +#[test] +fn link_module_links_same_store_instance() -> Result<()> { + let (add_module, import_module) = parse_modules()?; + let mut store = Store::default(); + + let add_instance = add_module.instantiate(&mut store, None)?; + let mut imports = Imports::new(); + imports.link_module("adder", add_instance)?; + + let instance = import_module.instantiate(&mut store, Some(imports))?; + let main = instance.func_typed::<(), i32>(&store, "main")?; + assert_eq!(main.call(&mut store, ())?, 3); + Ok(()) +} + +#[test] +fn link_module_rejects_cross_store_instance() -> Result<()> { + let (add_module, import_module) = parse_modules()?; + + let mut source_store = Store::default(); + let add_instance = add_module.instantiate(&mut source_store, None)?; + + let mut target_store = Store::default(); + let mut imports = Imports::new(); + imports.link_module("adder", add_instance)?; + + let err = import_module.instantiate(&mut target_store, Some(imports)).unwrap_err(); + assert!(matches!(err, Error::InvalidStore)); + Ok(()) +} diff --git a/crates/tinywasm/tests/imported_table_init.rs b/crates/tinywasm/tests/imported_table_init.rs new file mode 100644 index 00000000..c040394d --- /dev/null +++ b/crates/tinywasm/tests/imported_table_init.rs @@ -0,0 +1,35 @@ +use eyre::Result; +use tinywasm::types::{FuncRef, TableType, ValType, WasmValue}; +use tinywasm::{Extern, Imports, Module, Store}; + +#[test] +fn imported_table_uses_provided_init_value() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (import "host" "table" (table 3 funcref)) + (func (export "slot_is_null") (param i32) (result i32) + local.get 0 + table.get 0 + ref.is_null) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let mut imports = Imports::new(); + imports.define( + "host", + "table", + Extern::table(TableType::new(ValType::RefFunc, 3, None), WasmValue::RefFunc(FuncRef::new(Some(0)))), + )?; + + let instance = module.instantiate(&mut store, Some(imports))?; + let slot_is_null = instance.func_typed::(&store, "slot_is_null")?; + + assert_eq!(slot_is_null.call(&mut store, 0)?, 0); + assert_eq!(slot_is_null.call(&mut store, 1)?, 0); + + Ok(()) +} diff --git a/crates/tinywasm/tests/internal_refs.rs b/crates/tinywasm/tests/internal_refs.rs new file mode 100644 index 00000000..bd5561ff --- /dev/null +++ b/crates/tinywasm/tests/internal_refs.rs @@ -0,0 +1,101 @@ +use eyre::Result; +use tinywasm::types::{FuncRef, WasmValue}; +use tinywasm::{ExternItemRef, ExternItemRefMut, Module, Store}; + +#[test] +#[cfg(feature = "guest_debug")] +fn private_items_are_accessible_by_index() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (func (result i32) + i32.const 7) + (memory 1) + (global (mut i32) (i32.const 11)) + (table 2 funcref) + (elem (i32.const 0) func 0) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + + let func = instance.func_by_index(&store, 0)?; + assert_eq!(func.call(&mut store, &[])?, vec![WasmValue::I32(7)]); + + instance.memory_mut_by_index(&mut store, 0)?.store(0, 4, &[1, 2, 3, 4])?; + assert_eq!(instance.memory_by_index(&store, 0)?.load(0, 4)?, &[1, 2, 3, 4]); + + assert_eq!(instance.table_by_index(&store, 0)?.size(), 2); + assert_eq!(instance.table_by_index(&store, 0)?.get(0)?, WasmValue::RefFunc(FuncRef::new(Some(0)))); + assert!(matches!(instance.table_by_index(&store, 0)?.get(1)?, WasmValue::RefFunc(func_ref) if func_ref.is_null())); + + assert_eq!(instance.global_by_index(&store, 0)?.get(), WasmValue::I32(11)); + instance.global_mut_by_index(&mut store, 0)?.set(WasmValue::I32(23))?; + assert_eq!(instance.global_by_index(&store, 0)?.get(), WasmValue::I32(23)); + + Ok(()) +} + +#[test] +fn exported_tables_and_globals_have_handle_and_helper_apis() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (global (export "g") (mut i32) (i32.const 3)) + (table (export "t") 1 funcref) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + + assert_eq!(instance.global_get(&store, "g")?, WasmValue::I32(3)); + assert_eq!(instance.global(&store, "g")?.get(), WasmValue::I32(3)); + instance.global_set(&mut store, "g", WasmValue::I32(9))?; + assert_eq!(instance.global_mut(&mut store, "g")?.get(), WasmValue::I32(9)); + + let table = instance.table(&store, "t")?; + assert_eq!(table.size(), 1); + assert!(matches!(table.get(0)?, WasmValue::RefFunc(func_ref) if func_ref.is_null())); + + let old_size = instance.table_mut(&mut store, "t")?.grow(1, WasmValue::RefFunc(FuncRef::null()))?; + assert_eq!(old_size, 1); + assert_eq!(instance.table(&store, "t")?.size(), 2); + + Ok(()) +} + +#[test] +fn extern_item_lookup_returns_expected_kinds() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (func (export "f") (result i32) i32.const 1) + (memory (export "m") 1) + (table (export "t") 1 funcref) + (global (export "g") (mut i32) (i32.const 5)) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + + assert!(matches!(instance.extern_item(&store, "f")?, ExternItemRef::Func(_))); + assert!(matches!(instance.extern_item(&store, "m")?, ExternItemRef::Memory(_))); + assert!(matches!(instance.extern_item(&store, "t")?, ExternItemRef::Table(_))); + assert!(matches!(instance.extern_item(&store, "g")?, ExternItemRef::Global(_))); + + assert!(matches!(instance.extern_item_mut(&mut store, "f")?, ExternItemRefMut::Func(_))); + assert!(matches!(instance.extern_item_mut(&mut store, "m")?, ExternItemRefMut::Memory(_))); + assert!(matches!(instance.extern_item_mut(&mut store, "t")?, ExternItemRefMut::Table(_))); + assert!(matches!(instance.extern_item_mut(&mut store, "g")?, ExternItemRefMut::Global(_))); + + Ok(()) +} diff --git a/crates/tinywasm/tests/memory_ref_api.rs b/crates/tinywasm/tests/memory_ref_api.rs new file mode 100644 index 00000000..9e57b6bb --- /dev/null +++ b/crates/tinywasm/tests/memory_ref_api.rs @@ -0,0 +1,25 @@ +use eyre::Result; +use tinywasm::{Module, Store}; + +#[test] +fn memory_ref_mut_copy_within_uses_src_then_dst_order() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (memory (export "memory") 1) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = Store::default(); + let instance = module.instantiate(&mut store, None)?; + + let mut memory = instance.memory_mut(&mut store, "memory")?; + memory.store(0, 4, &[1, 2, 3, 4])?; + memory.copy_within(0, 4, 4)?; + + assert_eq!(memory.load(0, 8)?, &[1, 2, 3, 4, 1, 2, 3, 4]); + + Ok(()) +} diff --git a/crates/tinywasm/tests/module_descriptors.rs b/crates/tinywasm/tests/module_descriptors.rs new file mode 100644 index 00000000..99935995 --- /dev/null +++ b/crates/tinywasm/tests/module_descriptors.rs @@ -0,0 +1,91 @@ +use eyre::Result; +use tinywasm::types::ValType; +use tinywasm::{ExportType, ImportType, Module}; + +#[test] +fn module_descriptors_resolve_imported_and_local_export_types() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (type $t0 (func (param i32) (result i32))) + (import "host" "ifunc" (func $ifunc (type $t0))) + (import "host" "iglobal" (global $iglobal (mut i32))) + + (func $lfunc (param i64) (result i64) + local.get 0) + (global $lglobal i64 (i64.const 9)) + + (export "ifunc_export" (func $ifunc)) + (export "iglobal_export" (global $iglobal)) + (export "lfunc_export" (func $lfunc)) + (export "lglobal_export" (global $lglobal)) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + + let imports: Vec<_> = module.imports().collect(); + assert_eq!(imports.len(), 2); + + let ifunc_import = imports.iter().find(|import| import.name == "ifunc").expect("ifunc import not found"); + match ifunc_import.ty { + ImportType::Func(ty) => { + assert_eq!(ty.params.as_ref(), &[ValType::I32]); + assert_eq!(ty.results.as_ref(), &[ValType::I32]); + } + _ => panic!("ifunc import should be a function type"), + } + + let iglobal_import = imports.iter().find(|import| import.name == "iglobal").expect("iglobal import not found"); + match iglobal_import.ty { + ImportType::Global(ty) => { + assert!(ty.mutable); + assert_eq!(ty.ty, ValType::I32); + } + _ => panic!("iglobal import should be a global type"), + } + + let exports: Vec<_> = module.exports().collect(); + assert_eq!(exports.len(), 4); + + let ifunc_export = exports.iter().find(|export| export.name == "ifunc_export").expect("ifunc export not found"); + match ifunc_export.ty { + ExportType::Func(ty) => { + assert_eq!(ty.params.as_ref(), &[ValType::I32]); + assert_eq!(ty.results.as_ref(), &[ValType::I32]); + } + _ => panic!("ifunc export should resolve to imported function type"), + } + + let iglobal_export = + exports.iter().find(|export| export.name == "iglobal_export").expect("iglobal export not found"); + match iglobal_export.ty { + ExportType::Global(ty) => { + assert!(ty.mutable); + assert_eq!(ty.ty, ValType::I32); + } + _ => panic!("iglobal export should resolve to imported global type"), + } + + let lfunc_export = exports.iter().find(|export| export.name == "lfunc_export").expect("lfunc export not found"); + match lfunc_export.ty { + ExportType::Func(ty) => { + assert_eq!(ty.params.as_ref(), &[ValType::I64]); + assert_eq!(ty.results.as_ref(), &[ValType::I64]); + } + _ => panic!("lfunc export should resolve to local function type"), + } + + let lglobal_export = + exports.iter().find(|export| export.name == "lglobal_export").expect("lglobal export not found"); + match lglobal_export.ty { + ExportType::Global(ty) => { + assert!(!ty.mutable); + assert_eq!(ty.ty, ValType::I64); + } + _ => panic!("lglobal export should resolve to local global type"), + } + + Ok(()) +} diff --git a/crates/tinywasm/tests/resume_execution.rs b/crates/tinywasm/tests/resume_execution.rs index 93b1c27f..5a58f811 100644 --- a/crates/tinywasm/tests/resume_execution.rs +++ b/crates/tinywasm/tests/resume_execution.rs @@ -14,12 +14,12 @@ fn typed_resume_matches_non_budgeted_call() -> Result<()> { let mut store_full = tinywasm::Store::default(); let instance_full = module.clone().instantiate(&mut store_full, None)?; - let func_full = instance_full.exported_func::(&store_full, "fibonacci_recursive")?; + let func_full = instance_full.func_typed::(&store_full, "fibonacci_recursive")?; let expected = func_full.call(&mut store_full, 20)?; let mut store_budgeted = tinywasm::Store::default(); let instance_budgeted = module.instantiate(&mut store_budgeted, None)?; - let func_budgeted = instance_budgeted.exported_func::(&store_budgeted, "fibonacci_recursive")?; + let func_budgeted = instance_budgeted.func_typed::(&store_budgeted, "fibonacci_recursive")?; let mut exec = func_budgeted.call_resumable(&mut store_budgeted, 20)?; let mut saw_suspended = false; @@ -41,7 +41,7 @@ fn untyped_resume_supports_zero_fuel() -> Result<()> { let module = Module::parse_bytes(ADD_WASM)?; let mut store = tinywasm::Store::default(); let instance = module.instantiate(&mut store, None)?; - let func = instance.exported_func_untyped(&store, "add")?; + let func = instance.func(&store, "add")?; let mut exec = func.call_resumable(&mut store, &[WasmValue::I32(20), WasmValue::I32(22)])?; assert!(matches!(exec.resume_with_fuel(0)?, ExecProgress::Suspended)); @@ -62,12 +62,12 @@ fn weighted_call_fuel_requires_more_rounds() -> Result<()> { let mut per_instr_store = tinywasm::Store::default(); let instance_per_instr = module.clone().instantiate(&mut per_instr_store, None)?; - let func_per_instr = instance_per_instr.exported_func::(&per_instr_store, "fibonacci_recursive")?; + let func_per_instr = instance_per_instr.func_typed::(&per_instr_store, "fibonacci_recursive")?; let mut weighted_store = tinywasm::Store::new(tinywasm::Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted))); let instance_weighted = module.instantiate(&mut weighted_store, None)?; - let func_weighted = instance_weighted.exported_func::(&weighted_store, "fibonacci_recursive")?; + let func_weighted = instance_weighted.func_typed::(&weighted_store, "fibonacci_recursive")?; let fuel = 64; let n = 20; @@ -104,7 +104,7 @@ fn time_budget_zero_suspends_then_completes() -> Result<()> { let module = Module::parse_bytes(ADD_WASM)?; let mut store = tinywasm::Store::default(); let instance = module.instantiate(&mut store, None)?; - let func = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + let func = instance.func_typed::<(i32, i32), i32>(&store, "add")?; let mut exec = func.call_resumable(&mut store, (20, 22))?; assert!(matches!(exec.resume_with_time_budget(Duration::ZERO)?, ExecProgress::Suspended)); diff --git a/crates/tinywasm/tests/store_ownership.rs b/crates/tinywasm/tests/store_ownership.rs new file mode 100644 index 00000000..a9cbad4f --- /dev/null +++ b/crates/tinywasm/tests/store_ownership.rs @@ -0,0 +1,43 @@ +use eyre::Result; +use tinywasm::{Error, Module, Store}; + +const MODULE_WAT: &str = r#" + (module + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + (memory (export "memory") 1) + ) +"#; + +#[test] +fn func_handle_rejects_wrong_store() -> Result<()> { + let wasm = wat::parse_str(MODULE_WAT)?; + let module = Module::parse_bytes(&wasm)?; + + let mut owner_store = Store::default(); + let instance = module.instantiate(&mut owner_store, None)?; + let func = instance.func(&owner_store, "add")?; + + let mut other_store = Store::default(); + let err = func.call(&mut other_store, &[1.into(), 2.into()]).unwrap_err(); + assert!(matches!(err, Error::InvalidStore)); + + Ok(()) +} + +#[test] +fn memory_access_rejects_wrong_store() -> Result<()> { + let wasm = wat::parse_str(MODULE_WAT)?; + let module = Module::parse_bytes(&wasm)?; + + let mut owner_store = Store::default(); + let instance = module.instantiate(&mut owner_store, None)?; + + let other_store = Store::default(); + let err = instance.memory(&other_store, "memory").unwrap_err(); + assert!(matches!(err, Error::InvalidStore)); + + Ok(()) +} diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index d13c59b1..9987cd89 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -13,57 +13,61 @@ use wasm_testsuite::wast::{Wast, lexer::Lexer, parser::ParseBuffer}; #[derive(Default)] struct ModuleRegistry { - modules: HashMap, + modules: HashMap, - named_modules: HashMap, - last_module: Option, + named_modules: HashMap, + last_module: Option, } impl ModuleRegistry { - fn modules(&self) -> &HashMap { + fn modules(&self) -> &HashMap { &self.modules } - fn update_last_module(&mut self, addr: ModuleInstanceAddr, name: Option) { - self.last_module = Some(addr); + fn update_last_module(&mut self, module: ModuleInstance, name: Option) { + self.last_module = Some(module.clone()); if let Some(name) = name { - self.named_modules.insert(name, addr); + self.named_modules.insert(name, module); } } - fn register(&mut self, name: String, addr: ModuleInstanceAddr) { + fn register(&mut self, name: String, module: ModuleInstance) { log::debug!("registering module: {name}"); - self.modules.insert(name.clone(), addr); + self.modules.insert(name.clone(), module.clone()); - self.last_module = Some(addr); - self.named_modules.insert(name, addr); + self.last_module = Some(module.clone()); + self.named_modules.insert(name, module); } - fn get_idx(&self, module_id: Option>) -> Option<&ModuleInstanceAddr> { + fn get_idx(&self, module_id: Option>) -> Option { match module_id { Some(module) => { log::debug!("getting module: {}", module.name()); - if let Some(addr) = self.modules.get(module.name()) { - return Some(addr); + if let Some(module) = self.modules.get(module.name()) { + return Some(module.id()); } - if let Some(addr) = self.named_modules.get(module.name()) { - return Some(addr); + if let Some(module) = self.named_modules.get(module.name()) { + return Some(module.id()); } None } - None => self.last_module.as_ref(), + None => self.last_module.as_ref().map(ModuleInstance::id), } } - fn get(&self, module_id: Option>, store: &tinywasm::Store) -> Option { - let addr = self.get_idx(module_id)?; - store.get_module_instance(*addr) + fn get(&self, module_id: Option>) -> Option { + match module_id { + Some(module_id) => { + self.modules.get(module_id.name()).or_else(|| self.named_modules.get(module_id.name())).cloned() + } + None => self.last_module.clone(), + } } - fn last(&self, store: &tinywasm::Store) -> Option { - store.get_module_instance(*self.last_module.as_ref()?) + fn last(&self) -> Option { + self.last_module.clone() } } @@ -83,7 +87,7 @@ impl TestSuite { Ok(()) } - fn imports(modules: &HashMap) -> Result { + fn imports(modules: &HashMap) -> Result { let mut imports = Imports::new(); let table = @@ -143,9 +147,9 @@ impl TestSuite { .define("spectest", "print_i32_f32", print_i32_f32)? .define("spectest", "print_f64_f64", print_f64_f64)?; - for (name, addr) in modules { + for (name, module) in modules { log::debug!("registering module: {name}"); - imports.link_module(name, *addr)?; + imports.link_module(name, module.clone())?; } Ok(imports) @@ -186,7 +190,7 @@ impl TestSuite { match directive { Register { span, name, .. } => { - let Some(last) = module_registry.last(&store) else { + let Some(last) = module_registry.last() else { test_group.add_result( &format!("Register({i})"), span.linecol_in(wast_raw), @@ -194,7 +198,7 @@ impl TestSuite { ); continue; }; - module_registry.register(name.to_string(), last.id()); + module_registry.register(name.to_string(), last); test_group.add_result(&format!("Register({i})"), span.linecol_in(wast_raw), Ok(())); } @@ -214,7 +218,7 @@ impl TestSuite { match &result { Err(err) => debug!("failed to parse module: {err:?}"), - Ok((name, module)) => module_registry.update_last_module(module.id(), name.clone()), + Ok((name, module)) => module_registry.update_last_module(module.clone(), name.clone()), }; test_group.add_result(&format!("Wat({i})"), span.linecol_in(wast_raw), result.map(|_| ())); @@ -425,7 +429,7 @@ impl TestSuite { let invoke = match match exec { wast::WastExecute::Wat(_) => Err(eyre!("wat not supported")), wast::WastExecute::Get { module: module_id, global, .. } => { - let module = module_registry.get(module_id, &store); + let module = module_registry.get(module_id); let Some(module) = module else { test_group.add_result( &format!("AssertReturn(unsupported-{i})"), diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 851fa202..000bb1ad 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -13,7 +13,7 @@ pub fn try_downcast_panic(panic: Box) -> String { } pub fn exec_fn_instance( - instance: Option<&ModuleInstanceAddr>, + instance: Option, store: &mut tinywasm::Store, name: &str, args: &[tinywasm_types::WasmValue], @@ -22,11 +22,11 @@ pub fn exec_fn_instance( return Err(tinywasm::Error::Other("no instance found".to_string())); }; - let Some(instance) = store.get_module_instance(*instance) else { + let Some(instance) = store.get_module_instance(instance) else { return Err(tinywasm::Error::Other("no instance found".to_string())); }; - let func = instance.exported_func_untyped(store, name)?; + let func = instance.func(store, name)?; func.call(store, args) } @@ -43,7 +43,7 @@ pub fn exec_fn( let mut store = tinywasm::Store::default(); let module = tinywasm::Module::from(module); let instance = module.instantiate(&mut store, imports)?; - instance.exported_func_untyped(&store, name)?.call(&mut store, args) + instance.func(&store, name)?.call(&mut store, args) } pub fn catch_unwind_silent(f: impl FnOnce() -> R) -> std::thread::Result { diff --git a/crates/tinywasm/tests/typed_lookup.rs b/crates/tinywasm/tests/typed_lookup.rs new file mode 100644 index 00000000..7bfc26da --- /dev/null +++ b/crates/tinywasm/tests/typed_lookup.rs @@ -0,0 +1,48 @@ +use eyre::Result; +use tinywasm::Module; + +#[test] +fn func_typed_rejects_wrong_param_or_result_types() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = tinywasm::Store::default(); + let instance = module.instantiate(&mut store, None)?; + + assert!(instance.func_typed::<(i32, i32), i32>(&store, "add").is_ok()); + assert!(instance.func_typed::(&store, "add").is_err()); + assert!(instance.func_typed::<(i32, i32), ()>(&store, "add").is_err()); + + Ok(()) +} + +#[test] +fn func_typed_rejects_partial_multi_value_results() -> Result<()> { + let wasm = wat::parse_str( + r#" + (module + (func (export "pair") (result i32 i32) + i32.const 1 + i32.const 2) + ) + "#, + )?; + + let module = Module::parse_bytes(&wasm)?; + let mut store = tinywasm::Store::default(); + let instance = module.instantiate(&mut store, None)?; + + assert!(instance.func_typed::<(), (i32, i32)>(&store, "pair").is_ok()); + assert!(instance.func_typed::<(), i32>(&store, "pair").is_err()); + + Ok(()) +} diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 0cbcad79..8b128110 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -171,14 +171,14 @@ pub enum Instruction { TableInit(ElemAddr, TableAddr), TableGet(TableAddr), TableSet(TableAddr), - TableCopy { from: TableAddr, to: TableAddr }, + TableCopy { dst_table: TableAddr, src_table: TableAddr }, TableGrow(TableAddr), TableSize(TableAddr), TableFill(TableAddr), // > Bulk Memory Instructions MemoryInit(MemAddr, DataAddr), - MemoryCopy(MemAddr, MemAddr), + MemoryCopy { dst_mem: MemAddr, src_mem: MemAddr }, MemoryFill(MemAddr), MemoryFillImm(MemAddr, u8, i32), DataDrop(DataAddr), diff --git a/examples/archive.rs b/examples/archive.rs index 7a1021b5..7025fba2 100644 --- a/examples/archive.rs +++ b/examples/archive.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { let module: Module = TinyWasmModule::from_twasm(&twasm)?.into(); let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + let add = instance.func_typed::<(i32, i32), i32>(&store, "add")?; assert_eq!(add.call(&mut store, (1, 2))?, 3); diff --git a/examples/funcref_callbacks.rs b/examples/funcref_callbacks.rs index a460df83..6e1d3ef4 100644 --- a/examples/funcref_callbacks.rs +++ b/examples/funcref_callbacks.rs @@ -56,8 +56,7 @@ fn run_passed_funcref_example() -> Result<()> { "call_this", Extern::typed_func(|mut ctx: FuncContext<'_>, func_ref: FuncRef| -> tinywasm::Result<()> { // Host cannot call a funcref directly, so it routes through Wasm. - let call_by_ref = - ctx.module().exported_func::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; + let call_by_ref = ctx.module().func_typed::<(FuncRef, i32, i32), i32>(ctx.store(), "call_binop_by_ref")?; let _result = call_by_ref.call(ctx.store_mut(), (func_ref, LHS, RHS))?; Ok(()) }), @@ -70,7 +69,7 @@ fn run_passed_funcref_example() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let caller = instance.exported_func::<(), ()>(&store, "tell_host_to_call")?; + let caller = instance.func_typed::<(), ()>(&store, "tell_host_to_call")?; caller.call(&mut store, ())?; @@ -123,12 +122,11 @@ fn run_returned_funcref_example() -> Result<()> { let instance = module.instantiate(&mut store, Some(imports))?; let (add_ref, sub_ref, mul_ref) = { - let get_funcrefs = - instance.exported_func::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; + let get_funcrefs = instance.func_typed::<(), (FuncRef, FuncRef, FuncRef)>(&store, "what_should_host_call")?; get_funcrefs.call(&mut store, ())? }; - let call_by_ref = instance.exported_func::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; + let call_by_ref = instance.func_typed::<(FuncRef, i32, i32), i32>(&store, "call_binop_by_ref")?; for func_ref in [add_ref, sub_ref, mul_ref] { let _result = call_by_ref.call(&mut store, (func_ref, LHS, RHS))?; diff --git a/examples/linking.rs b/examples/linking.rs index f94773a1..ce9748b1 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -37,13 +37,13 @@ fn main() -> Result<()> { // Link the `adder` namespace to the `add` module's instance. let mut imports = tinywasm::Imports::new(); - imports.link_module("adder", add_instance.id())?; + imports.link_module("adder", add_instance)?; // Instantiate the `import` module with the linked imports. let import_instance = import_module.instantiate(&mut store, Some(imports))?; // Call the `main` function, which uses the imported `add` function. - let main = import_instance.exported_func::<(), i32>(&store, "main")?; + let main = import_instance.func_typed::<(), i32>(&store, "main")?; assert_eq!(main.call(&mut store, ())?, 3); Ok(()) diff --git a/examples/resumable.rs b/examples/resumable.rs index 6838e20f..f3ebc89a 100644 --- a/examples/resumable.rs +++ b/examples/resumable.rs @@ -27,7 +27,7 @@ fn main() -> Result<()> { let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let count_down = instance.exported_func::(&store, "count_down")?; + let count_down = instance.func_typed::(&store, "count_down")?; let mut execution = count_down.call_resumable(&mut store, 10_000)?; let fuel_per_round = 128; diff --git a/examples/rust/src/tinywasm.rs b/examples/rust/src/tinywasm.rs index 11d76378..e06cf81b 100644 --- a/examples/rust/src/tinywasm.rs +++ b/examples/rust/src/tinywasm.rs @@ -26,7 +26,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + let add_and_print = instance.func_typed::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/rust/src/tinywasm_no_std.rs b/examples/rust/src/tinywasm_no_std.rs index 6a31ff0c..9a032dd9 100644 --- a/examples/rust/src/tinywasm_no_std.rs +++ b/examples/rust/src/tinywasm_no_std.rs @@ -43,7 +43,7 @@ fn run() -> tinywasm::Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + let add_and_print = instance.func_typed::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) } diff --git a/examples/simple.rs b/examples/simple.rs index e0842dda..83719921 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -15,7 +15,7 @@ fn main() -> Result<()> { let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let add = instance.exported_func::<(i32, i32), i32>(&store, "add")?; + let add = instance.func_typed::<(i32, i32), i32>(&store, "add")?; assert_eq!(add.call(&mut store, (1, 2))?, 3); Ok(()) diff --git a/examples/simple2.rs b/examples/simple2.rs index a3124f86..66e85b30 100644 --- a/examples/simple2.rs +++ b/examples/simple2.rs @@ -14,7 +14,7 @@ fn main() -> Result<()> { let module = Module::parse_bytes(&wasm)?; let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let add = instance.exported_func::<(i32, i64), (i32, i64)>(&store, "return")?; + let add = instance.func_typed::<(i32, i64), (i32, i64)>(&store, "return")?; assert_eq!(add.call(&mut store, (1, 2))?, (1, 2)); Ok(()) diff --git a/examples/wasm-rust.rs b/examples/wasm-rust.rs index d376130a..0ffa499d 100644 --- a/examples/wasm-rust.rs +++ b/examples/wasm-rust.rs @@ -79,7 +79,7 @@ fn tinywasm() -> Result<()> { imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _x: i32| Ok(())))?; let instance = module.instantiate(&mut store, Some(black_box(imports)))?; - let hello = instance.exported_func::<(), ()>(&store, "hello")?; + let hello = instance.func_typed::<(), ()>(&store, "hello")?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; @@ -94,7 +94,7 @@ fn tinywasm_no_std() -> Result<()> { imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _x: i32| Ok(())))?; let instance = module.instantiate(&mut store, Some(black_box(imports)))?; - let hello = instance.exported_func::<(), ()>(&store, "hello")?; + let hello = instance.func_typed::<(), ()>(&store, "hello")?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; hello.call(&mut store, black_box(()))?; @@ -109,22 +109,20 @@ fn hello() -> Result<()> { imports.define( "env", "print_utf8", - Extern::typed_func(|ctx: FuncContext<'_>, args: (i64, i32)| { - let mem = ctx.exported_memory("memory")?; - let ptr = args.0 as usize; - let len = args.1 as usize; - let string = mem.load_string(ptr, len)?; + Extern::typed_func(|ctx: FuncContext<'_>, (ptr, len): (i64, i32)| { + let mem = ctx.memory("memory")?; + let string = mem.load_string(ptr as usize, len as usize)?; println!("{string}"); Ok(()) }), )?; let instance = module.instantiate(&mut store, Some(imports))?; - let arg_ptr = instance.exported_func::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; + let arg_ptr = instance.func_typed::<(), i32>(&store, "arg_ptr")?.call(&mut store, ())?; let arg = b"world"; - instance.exported_memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; - let hello = instance.exported_func::(&store, "hello")?; + instance.memory_mut(&mut store, "memory")?.store(arg_ptr as usize, arg.len(), arg)?; + let hello = instance.func_typed::(&store, "hello")?; hello.call(&mut store, arg.len() as i32)?; Ok(()) @@ -145,7 +143,7 @@ fn host_fn() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let host_fn = instance.exported_func::<(), i32>(&store, "foo")?; + let host_fn = instance.func_typed::<(), i32>(&store, "foo")?; assert_eq!(host_fn.call(&mut store, ())?, 3); Ok(()) } @@ -165,7 +163,7 @@ fn printi32() -> Result<()> { )?; let instance = module.instantiate(&mut store, Some(imports))?; - let add_and_print = instance.exported_func::<(i32, i32), ()>(&store, "add_and_print")?; + let add_and_print = instance.func_typed::<(i32, i32), ()>(&store, "add_and_print")?; add_and_print.call(&mut store, (1, 2))?; Ok(()) @@ -176,7 +174,7 @@ fn fibonacci() -> Result<()> { let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let fibonacci = instance.exported_func::(&store, "fibonacci_recursive")?; + let fibonacci = instance.func_typed::(&store, "fibonacci_recursive")?; let n = 26; let result = fibonacci.call(&mut store, n)?; println!("fibonacci({n}) = {result}"); @@ -189,7 +187,7 @@ fn argon2id() -> Result<()> { let mut store = Store::default(); let instance = module.instantiate(&mut store, None)?; - let argon2id = instance.exported_func::<(i32, i32, i32), i32>(&store, "argon2id")?; + let argon2id = instance.func_typed::<(i32, i32, i32), i32>(&store, "argon2id")?; argon2id.call(&mut store, (1000, 2, 1))?; Ok(())