From 35acaee318f277dc4b3ece7f4306d6d058ecb520 Mon Sep 17 00:00:00 2001 From: Stanislav Stepanov Date: Mon, 6 Oct 2025 21:54:47 +0700 Subject: [PATCH 01/14] feat(segment-caching): simple BitVecInlined wrapper for BitVec to support stack values; perf graphs (flamgraph) to measure perfomance; generic impl for reusable pools (for ValueStack and CallStack); improved hashmaps perf by changing hash builder to a faster one; rewrote RwasmStore.(tables|global_variables) onto Vec for perfomance; unix-speciific memory pool optimization (#53) * feat(segment-caching): simple BitVecInlined wrapper for BitVec * feat(segment-caching): fix std error * feat(segment-caching): fix bug, extend unit-test * feat(segment-caching): implemented stack based BitVec (BitVecInlined) replacement and integrated into RwasmStore; measured perfomance of fibbonachi app execution on rwasm with perf and visualized with flamegraph; implemented benchmarks for BitVecInlined and fibonnachi app execution using * feat(segment-caching): renamings for clarity * feat(segment-caching): added load perfomance graph * feat(segment-caching): fix std problem * feat(segment-caching): updated flamegraphs * feat(rwasm): implemented&integrated reusable pool for ValueStack * feat(rwasm): implemented generic reusable pool and integrated for ValueStack and CallStack * feat(rwasm): cleanup * feat(rwasm): cleanup * feat(rwasm): move reusable pools from RwasmStore to ExecutionEngineInner * feat(rwasm): fix no-std issues + cleanup * feat(rwasm): optimise perfomance of ValueStackPtr by making it repr(transparent) * feat(rwasm): improve hashmap perf by replacing hash build with fnv::FnvBuildHasher; do not create elements with capacity in TableEntity for perfomance * feat(rwasm): rewrote RwasmStore.tables onto Vec instead of hashmap for perfomance * feat(rwasm): fix no-std issue * feat(rwasm): rewrote RwasmStore.global_variables onto Vec instead of hashmap for perfomance * feat(rwasm): cleanup * feat(rwasm): optimised work with store.global_variables and store.tables for perfomance; added benches for Hashmap and Vec to compare perf * feat(rwasm): fix bench * feat(rwasm): perf fix * feat(rwasm): cleanup * feat(rwasm): added make test * feat(rwasm): uncommented bitvec_inlined; cleanup * feat(rwasm): fixes * feat(rwasm): cover bitvec_inlined with feature * feat(rwasm): fixed bug for RwasmStore.global_variables processing * feat(rwasm): fixes to benches * feat(rwasm): cleanup * feat(rwasm): cleanup * chore: add evm benchmarks * feat(rwasm): removed 1 flame graph * chore: add benchamrks for fib32/fib64 * feat(rwasm): added perf graph for bench_evm * chore: fix running fib32 bench * feat(rwasm): added perf graph for fib64 * feat(rwasm): added perf for fib64 * feat(rwasm): perf graph for fib64 * feat(rwasm): fix perf graph * feat(rwasm): inline for some new alu methods; fix bench collission * fix: fix running tests, optimized reusable stacks, put tests related functionality from value stack under flag * feat(rwasm): fix bench group name * chore: tiny fixes * fix: add fib256 benches, fix missing virtual stack alloc for benches, fix project compilation in tracer mode, fix typo with global memory init with max possible capacity, fixed module serialization, add evm machine executor, add trace extractor for fib32, fib64, fib64 tests * feat(rwasm): global memory implementation using unix low level optimisations * feat(rwasm): renamings * feat(rwasm): fix feature name * feat(rwasm): file renaming * feat(rwasm): added fib32 test as regular test for trials --------- Co-authored-by: Dmitry Savonin --- Cargo.lock | 330 ++- Cargo.toml | 17 +- Makefile | 4 + benchmarks/.gitignore | 2 +- benchmarks/Cargo.toml | 29 +- benchmarks/Makefile | 4 +- benchmarks/benches/bench.rs | 98 - benchmarks/benches/bitvec.rs | 109 + benchmarks/benches/fib256.rs | 139 ++ benchmarks/benches/fib32.rs | 144 ++ benchmarks/benches/fib64.rs | 144 ++ benchmarks/lib.rs | 63 +- e2e/src/lib.rs | 11 + perf/.gitignore | 7 + perf/Cargo.toml | 56 + perf/Makefile | 51 + perf/evm_fibonacci_perf.rs | 40 + perf/fibonacci_perf32.rs | 39 + perf/fibonacci_perf32_flame.svg | 7 + perf/fibonacci_perf64.rs | 39 + perf/fibonacci_perf64_flame.svg | 2602 ++++++++++++++++++++++++ perf/load_perf.rs | 46 + snippets/Makefile | 2 +- src/compiler/parser.rs | 3 +- src/compiler/segment_builder.rs | 4 +- src/compiler/translator.rs | 2 +- src/module.rs | 26 +- src/types/bitvec_inlined.rs | 266 +++ src/types/branch_offset.rs | 2 - src/types/import_linker.rs | 6 +- src/types/mod.rs | 1 + src/vm/call_stack.rs | 4 +- src/vm/context.rs | 7 +- src/vm/engine.rs | 88 +- src/vm/executor.rs | 19 +- src/vm/executor/alu.rs | 2 + src/vm/executor/control_flow.rs | 4 +- src/vm/executor/fpu.rs | 8 +- src/vm/executor/memory.rs | 43 +- src/vm/executor/system.rs | 22 +- src/vm/executor/table.rs | 38 +- src/vm/memory.rs | 91 +- src/vm/memory_unix.rs | 301 +++ src/vm/mod.rs | 3 + src/vm/reusable_pool.rs | 57 + src/vm/store.rs | 53 +- src/vm/table_entity.rs | 6 +- src/vm/tracer/mem_index.rs | 4 +- src/vm/tracer/mod.rs | 8 +- src/vm/value_stack.rs | 46 +- tests/basic.rs | 4 +- tests/wasmtime.rs | 4 +- trace-extractor/Cargo.toml | 11 + trace-extractor/evm-machine/Cargo.toml | 13 + trace-extractor/evm-machine/Makefile | 5 + trace-extractor/evm-machine/lib.rs | 38 + trace-extractor/src/main.rs | 68 + wasm/Makefile | 2 +- 58 files changed, 4934 insertions(+), 308 deletions(-) delete mode 100644 benchmarks/benches/bench.rs create mode 100644 benchmarks/benches/bitvec.rs create mode 100644 benchmarks/benches/fib256.rs create mode 100644 benchmarks/benches/fib32.rs create mode 100644 benchmarks/benches/fib64.rs create mode 100644 perf/.gitignore create mode 100644 perf/Cargo.toml create mode 100644 perf/Makefile create mode 100644 perf/evm_fibonacci_perf.rs create mode 100644 perf/fibonacci_perf32.rs create mode 100644 perf/fibonacci_perf32_flame.svg create mode 100644 perf/fibonacci_perf64.rs create mode 100644 perf/fibonacci_perf64_flame.svg create mode 100644 perf/load_perf.rs create mode 100644 src/types/bitvec_inlined.rs create mode 100644 src/vm/memory_unix.rs create mode 100644 src/vm/reusable_pool.rs create mode 100644 trace-extractor/Cargo.toml create mode 100644 trace-extractor/evm-machine/Cargo.toml create mode 100644 trace-extractor/evm-machine/Makefile create mode 100644 trace-extractor/evm-machine/lib.rs create mode 100644 trace-extractor/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 27973d9bc..5a397f1f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,30 @@ dependencies = [ "gimli", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -46,6 +64,17 @@ dependencies = [ "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -78,6 +107,36 @@ dependencies = [ "virtue", ] +[[package]] +name = "bindgen" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "cfg-if 0.1.10", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex 0.1.1", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -134,15 +193,56 @@ checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", - "shlex", + "shlex 1.3.0", ] +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width 0.1.14", + "vec_map", +] + [[package]] name = "cobs" version = "0.2.3" @@ -155,7 +255,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -219,7 +319,7 @@ dependencies = [ "log", "pulley-interpreter", "regalloc2", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "smallvec", "target-lexicon", @@ -297,7 +397,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -375,7 +475,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -432,7 +532,20 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", ] [[package]] @@ -457,6 +570,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -573,7 +692,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ - "bitflags", + "bitflags 2.9.0", "debugid", "fxhash", "serde", @@ -596,7 +715,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] @@ -607,7 +726,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", @@ -624,6 +743,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hashbrown" version = "0.15.2" @@ -643,12 +768,30 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hex-literal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -727,6 +870,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -739,6 +894,16 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + [[package]] name = "libm" version = "0.2.15" @@ -751,7 +916,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", ] @@ -813,6 +978,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" +[[package]] +name = "nom" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -863,6 +1038,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -941,6 +1122,12 @@ dependencies = [ "syn", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.36" @@ -1043,16 +1230,51 @@ dependencies = [ "bumpalo", "hashbrown", "log", - "rustc-hash", + "rustc-hash 2.1.1", "smallvec", ] +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + [[package]] name = "rustc-demangle" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -1071,7 +1293,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1084,7 +1306,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.9.4", @@ -1107,15 +1329,18 @@ dependencies = [ "bytes", "directories", "downcast-rs", + "fnv", "futures", "hashbrown", "hex-literal", + "libc", "libm", "num-derive", "num-traits", "paste", "rand", "serde", + "setjmp", "smallvec", "spin 0.10.0", "tiny-keccak", @@ -1187,17 +1412,34 @@ dependencies = [ "serde", ] +[[package]] +name = "setjmp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bce8e042e9b4349ccf7ce5caeb5c9b7ee6007ff543b494733ae8ed4ea083c73" +dependencies = [ + "bindgen", + "clang-sys", + "libc", +] + [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", ] +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + [[package]] name = "shlex" version = "1.3.0" @@ -1240,6 +1482,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "syn" version = "2.0.103" @@ -1272,6 +1520,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width 0.1.14", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1385,6 +1642,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.1" @@ -1413,6 +1676,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -1446,7 +1715,7 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -1564,7 +1833,7 @@ version = "0.228.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -1573,7 +1842,7 @@ version = "0.233.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51cb03afce7964bbfce46602d6cb358726f36430b6ba084ac6020d8ce5bc102" dependencies = [ - "bitflags", + "bitflags 2.9.0", "hashbrown", "indexmap", "semver", @@ -1586,7 +1855,7 @@ version = "0.234.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be22e5a8f600afce671dd53c8d2dd26b4b7aa810fd18ae27dfc49737f3e02fc5" dependencies = [ - "bitflags", + "bitflags 2.9.0", "indexmap", "semver", ] @@ -1619,10 +1888,10 @@ dependencies = [ "addr2line", "anyhow", "async-trait", - "bitflags", + "bitflags 2.9.0", "bumpalo", "cc", - "cfg-if", + "cfg-if 1.0.0", "encoding_rs", "fxprof-processed-profile", "gimli", @@ -1671,7 +1940,7 @@ name = "wasmtime-asm-macros" version = "34.0.1" source = "git+https://github.com/fluentlabs-xyz/wasmtime?branch=devel#f0c5b017d2b7d59c84999123338da7634ab0cf1f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1718,7 +1987,7 @@ version = "34.0.1" source = "git+https://github.com/fluentlabs-xyz/wasmtime?branch=devel#f0c5b017d2b7d59c84999123338da7634ab0cf1f" dependencies = [ "anyhow", - "cfg-if", + "cfg-if 1.0.0", "cranelift-codegen", "cranelift-control", "cranelift-entity", @@ -1771,7 +2040,7 @@ source = "git+https://github.com/fluentlabs-xyz/wasmtime?branch=devel#f0c5b017d2 dependencies = [ "anyhow", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "rustix 1.0.7", "wasmtime-asm-macros", @@ -1796,7 +2065,7 @@ version = "34.0.1" source = "git+https://github.com/fluentlabs-xyz/wasmtime?branch=devel#f0c5b017d2b7d59c84999123338da7634ab0cf1f" dependencies = [ "anyhow", - "cfg-if", + "cfg-if 1.0.0", "libc", "windows-sys", ] @@ -1860,7 +2129,7 @@ dependencies = [ "bumpalo", "leb128fmt", "memchr", - "unicode-width", + "unicode-width 0.2.1", "wasm-encoder 0.234.0", ] @@ -1873,6 +2142,15 @@ dependencies = [ "wast", ] +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2011,7 +2289,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ca1b6155f..2b0b8ed1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ libm = "0.2.1" smallvec = "1.15.0" num-derive = "0.4.2" spin = "0.10.0" +fnv = { version = "1.0.7", default-features = false } # tracing serde = { version = "1.0.219", features = ["derive"], optional = true } @@ -37,6 +38,8 @@ futures = { version = "0.3.31", optional = true } # wasmi wasmi = { version = "0.47.0", default-features = false } +libc = { version = "0.2.172", default-features = false, features = ["align", "extra_traits", "const-extern-fn"] } +setjmp = { version = "0.1.4", default-features = false, features = [], optional = true } [dev-dependencies] rand = "0.9.1" @@ -51,14 +54,24 @@ std = [ "num-traits/std", "bitvec/std", "wasmtime?/std", + # "unix-memory", ] more-max-pages = [] serde = [ "dep:serde", "serde/derive" ] tracing = ["serde"] -debug-print = [] +test-build = [] +debug-print = ["std"] fpu = [] wasmtime = ["dep:wasmtime", "dep:anyhow", "dep:futures"] cache-compiled-artifacts = ["wasmtime", "dep:directories"] -pooling-allocator = [] \ No newline at end of file +pooling-allocator = [] +unix-memory = [ + "dep:setjmp" +] + +[[test]] +name = "integration" +path = "tests/snippets.rs" +required-features = ["test-build"] diff --git a/Makefile b/Makefile index 8d544c518..740aefa04 100644 --- a/Makefile +++ b/Makefile @@ -31,4 +31,8 @@ clean: # Delete all Cargo.lock files except the root find . -name Cargo.lock ! -path './Cargo.lock' -type f -exec rm -f {} + +.PHONY: test +test: + cargo test + all: test-specific-cases \ No newline at end of file diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore index c7cb67382..17cd2545e 100644 --- a/benchmarks/.gitignore +++ b/benchmarks/.gitignore @@ -1,4 +1,4 @@ target Cargo.lock lib.wat -lib.wasm \ No newline at end of file +lib.wasm diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 17b3dc758..2427ff88b 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -17,12 +17,31 @@ debug-assertions = false rpath = false codegen-units = 1 -[dependencies] -criterion = { version = "0.7.0", default-features = false, features = [] } - [dev-dependencies] +criterion = { version = "0.7.0", default-features = false, features = [] } rwasm = { path = "..", features = ["std", "wasmtime"] } +rand = { version = "0.9.2" } +wat = "1.230.0" +bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } +revm-interpreter = "25.0.3" +revm-bytecode = "6.2.2" +hex-literal = "1.0.0" + +[[bench]] +name = "bitvec" +harness = false [[bench]] -name = "bench" -harness = false \ No newline at end of file +name = "fib32" +harness = false + +[[bench]] +name = "fib64" +harness = false + +[[bench]] +name = "fib256" +harness = false + +[dependencies] +alloy-primitives = { version = "1.4.0", default-features = false } diff --git a/benchmarks/Makefile b/benchmarks/Makefile index 5e88d7d6e..6e8384642 100644 --- a/benchmarks/Makefile +++ b/benchmarks/Makefile @@ -1,5 +1,5 @@ .PHONY: build build: - RUSTFLAGS="-C link-arg=-zstack-size=0" cargo b --target=wasm32-unknown-unknown --release --no-default-features + RUSTFLAGS="-C link-arg=-zstack-size=1024" cargo b --target-dir=./target --target=wasm32-unknown-unknown --release --no-default-features cp ./target/wasm32-unknown-unknown/release/fib.wasm ./lib.wasm - wasm2wat ./lib.wasm > ./lib.wat || true \ No newline at end of file + wasm2wat ./lib.wasm > ./lib.wat || true diff --git a/benchmarks/benches/bench.rs b/benchmarks/benches/bench.rs deleted file mode 100644 index 8f9e7aea3..000000000 --- a/benchmarks/benches/bench.rs +++ /dev/null @@ -1,98 +0,0 @@ -use criterion::{criterion_main, Bencher, Criterion}; -use rwasm::{ - always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, - CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, -}; -use std::{sync::Arc, time::Duration}; - -const FIB_VALUE: i32 = 43; - -fn bench_comparisons(c: &mut Criterion) { - let mut group = c.benchmark_group("Comparisons"); - - // bench_native - { - pub fn fib(n: i32) -> i32 { - let (mut a, mut b) = (0, 1); - for _ in 0..n { - let t = a; - a = b; - b = t + b; - } - a - } - group.bench_function("bench_native", |b| { - b.iter(|| { - core::hint::black_box(fib(core::hint::black_box(FIB_VALUE))); - }); - }); - }; - - fn bench_strategy(b: &mut Bencher, strategy: Strategy) { - b.iter(|| { - let mut store = strategy.create_store( - Arc::new(ImportLinker::default()), - (), - always_failing_syscall_handler, - FuelConfig::default(), - ); - let mut result = [Value::I32(0)]; - strategy - .execute(&mut store, "main", &[Value::I32(FIB_VALUE)], &mut result) - .unwrap(); - core::hint::black_box(result); - }); - } - - { - let wasm_binary = include_bytes!("../lib.wasm"); - let config = CompilationConfig::default().with_consume_fuel(false); - let module = compile_wasmtime_module(config, wasm_binary).unwrap(); - group.bench_function("bench_strategy_wasmtime", |b| { - let strategy = Strategy::Wasmtime { - module: module.clone(), - }; - bench_strategy(b, strategy); - }); - } - - { - let wasm_binary = include_bytes!("../lib.wasm"); - let config = CompilationConfig::default().with_consume_fuel(false); - let module = compile_wasmi_module(config, wasm_binary).unwrap(); - group.bench_function("bench_strategy_wasmi", |b| { - let strategy = Strategy::Wasmi { - module: module.clone(), - }; - bench_strategy(b, strategy); - }); - } - - { - let wasm_binary = include_bytes!("../lib.wasm"); - let config = CompilationConfig::default() - .with_entrypoint_name("main".into()) - .with_allow_malformed_entrypoint_func_type(true) - .with_consume_fuel(false); - let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); - group.bench_function("bench_strategy_rwasm", |b| { - let strategy = Strategy::Rwasm { - module: module.clone(), - engine: ExecutionEngine::acquire_shared(), - }; - bench_strategy(b, strategy); - }); - } - - group.finish(); -} - -pub fn benches() { - let mut criterion: Criterion<_> = Criterion::default() - .configure_from_args() - .warm_up_time(Duration::from_secs(1)) - .measurement_time(Duration::from_secs(1)) - .sample_size(1000); - bench_comparisons(&mut criterion); -} -criterion_main!(benches); diff --git a/benchmarks/benches/bitvec.rs b/benchmarks/benches/bitvec.rs new file mode 100644 index 000000000..31b611648 --- /dev/null +++ b/benchmarks/benches/bitvec.rs @@ -0,0 +1,109 @@ +use bitvec::{order::Lsb0, vec::BitVec}; +use criterion::{criterion_main, Criterion}; +use rwasm::{ + bitvec_inlined::{BitVecInlined, USIZE_BITS}, + CompilationConfig, ExecutionEngine, RwasmModule, RwasmStore, Value, +}; +use std::time::Duration; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons"); + + const BITVEC_STORE_COUNT: usize = 1; + const BITVEC_INLINED_STORE_COUNT: usize = BITVEC_STORE_COUNT; + const BITVEC_INLINED_STORE_COUNT_HALF: usize = BITVEC_STORE_COUNT / 2; + let bitvec_bits = USIZE_BITS * BITVEC_STORE_COUNT; + let random_sets_count = 1000; + let random_idxs_values = + core::iter::repeat_with(|| (rand::random_range(..bitvec_bits), rand::random::())) + .take(random_sets_count) + .collect::>(); + + // bitvec + { + group.bench_function("bitvec", |b| { + b.iter(|| { + for i in 0..random_sets_count { + let mut bv = BitVec::::repeat(true, bitvec_bits); + let (idx, value) = random_idxs_values[i]; + bv.set(idx, value); + core::hint::black_box(bv); + } + }); + }); + }; + + // bitvec_inlined + { + group.bench_function("bitvec_inlined", |b| { + b.iter(|| { + for i in 0..random_sets_count { + let mut bv = + BitVecInlined::<{ BITVEC_INLINED_STORE_COUNT }>::repeat(true, bitvec_bits); + let (idx, value) = random_idxs_values[i]; + bv.set(idx, value); + core::hint::black_box(bv); + } + }); + }); + }; + + // bitvec_inlined (half store) + { + let mut bv = + BitVecInlined::<{ BITVEC_INLINED_STORE_COUNT_HALF }>::repeat(true, bitvec_bits); + group.bench_function("bitvec_inlined (half of inline store)", |b| { + b.iter(|| { + for i in 0..random_sets_count { + let (idx, value) = random_idxs_values[i]; + bv.set(idx, value); + } + }); + }); + }; + + { + let wasm_binary = wat::parse_str( + r#" + (module + (memory 1) + (data (i32.const 0) "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab") + (func (export "64_good1") (param $i i32) (result i64) + (i64.load offset=0 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + ) + "#, + ) + .unwrap(); + let config = CompilationConfig::default() + .with_entrypoint_name("64_good1".into()) + .with_allow_malformed_entrypoint_func_type(true); + let (rwasm_module, _) = RwasmModule::compile(config, &wasm_binary).unwrap(); + println!("{}", rwasm_module); + let mut store = RwasmStore::<()>::default(); + let engine = ExecutionEngine::new(); + let mut result = [Value::I64(0); 1]; + group.bench_function("bitvec_inlined (through ExecutionEngine)", |b| { + b.iter(|| { + for _ in 0..random_sets_count { + engine + .execute(&mut store, &rwasm_module, &[Value::I32(0)], &mut result) + .unwrap(); + assert_eq!(result[0].i64().unwrap(), 0x6867666564636261); + } + }); + }); + }; + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/fib256.rs b/benchmarks/benches/fib256.rs new file mode 100644 index 000000000..549af922e --- /dev/null +++ b/benchmarks/benches/fib256.rs @@ -0,0 +1,139 @@ +use alloy_primitives::U256; +use criterion::{criterion_main, Bencher, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, + CallInput, InputsImpl, Interpreter, SharedMemory, +}; +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, +}; +use std::{sync::Arc, time::Duration}; + +const FIB_VALUE: i64 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons Fib256"); + + // bench_native + { + pub fn fib256(n: u64) -> U256 { + let (mut a, mut b) = (U256::ZERO, U256::ONE); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + group.bench_function("bench_native", |b| { + b.iter(|| { + core::hint::black_box(fib256(core::hint::black_box(FIB_VALUE as u64))); + }); + }); + }; + + // bench_evm + { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063e78692bb1461002d575b5f5ffd5b610047600480360381019061004291906100fd565b61005d565b6040516100549190610137565b60405180910390f35b5f5f5f90505f600190505f600290505b8467ffffffffffffffff168167ffffffffffffffff16116100b1575f8284610095919061017d565b90508293508092505080806100a9906101b8565b91505061006d565b508092505050919050565b5f5ffd5b5f67ffffffffffffffff82169050919050565b6100dc816100c0565b81146100e6575f5ffd5b50565b5f813590506100f7816100d3565b92915050565b5f60208284031215610112576101116100bc565b5b5f61011f848285016100e9565b91505092915050565b610131816100c0565b82525050565b5f60208201905061014a5f830184610128565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610187826100c0565b9150610192836100c0565b9250828201905067ffffffffffffffff8111156101b2576101b1610150565b5b92915050565b5f6101c2826100c0565b915067ffffffffffffffff82036101dc576101db610150565b5b60018201905091905056fea2646970667358221220b9932107a06e2c6f884433417401d45c3d48c85efc8e1d3110c6fba210eb5abc64736f6c634300081e0033"); + group.bench_function("bench_evm", |b| { + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + b.iter(|| { + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes(hex!("e78692bb000000000000000000000000000000000000000000000000000000000000002b").into()), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + core::hint::black_box(result); + }); + }); + }; + + fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + b.iter(|| { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = []; + strategy + .execute( + &mut store, + "fib256", + &[Value::I32(0), Value::I64(FIB_VALUE)], + &mut result, + ) + .unwrap(); + core::hint::black_box(result); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmtime", |b| { + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmi", |b| { + let strategy = Strategy::Wasmi { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + group.bench_function("bench_rwasm", |b| { + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + bench_strategy(b, strategy); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/fib32.rs b/benchmarks/benches/fib32.rs new file mode 100644 index 000000000..d26c784bc --- /dev/null +++ b/benchmarks/benches/fib32.rs @@ -0,0 +1,144 @@ +use criterion::{criterion_main, Bencher, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, + CallInput, InputsImpl, Interpreter, SharedMemory, +}; +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, +}; +use std::{sync::Arc, time::Duration}; + +const FIB_VALUE: i32 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons fib32"); + + // bench_native + { + pub fn fib32(n: u32) -> u32 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + group.bench_function("bench_native", |b| { + b.iter(|| { + core::hint::black_box(fib32(core::hint::black_box(FIB_VALUE as u32))); + }); + }); + }; + + // bench_evm + { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063f9b7c7e51461002d575b5f5ffd5b610047600480360381019061004291906100f1565b61005d565b604051610054919061012b565b60405180910390f35b5f5f5f90505f600190505f600290505b8463ffffffff168163ffffffff16116100a9575f828461008d9190610171565b90508293508092505080806100a1906101a8565b91505061006d565b508092505050919050565b5f5ffd5b5f63ffffffff82169050919050565b6100d0816100b8565b81146100da575f5ffd5b50565b5f813590506100eb816100c7565b92915050565b5f60208284031215610106576101056100b4565b5b5f610113848285016100dd565b91505092915050565b610125816100b8565b82525050565b5f60208201905061013e5f83018461011c565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61017b826100b8565b9150610186836100b8565b9250828201905063ffffffff8111156101a2576101a1610144565b5b92915050565b5f6101b2826100b8565b915063ffffffff82036101c8576101c7610144565b5b60018201905091905056fea26469706673582212206f34ca4baf4d7f4a2ab9c7060b71c1f28bca433c9959aabaa5c1ac6323863d2364736f6c634300081e0033"); + group.bench_function("bench_evm", |b| { + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + b.iter(|| { + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes(hex!("f9b7c7e5000000000000000000000000000000000000000000000000000000000000002b").into()), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + // match &result { + // InterpreterAction::NewFrame(_) => unreachable!(), + // InterpreterAction::Return(result) => { + // if !result.is_ok() { + // println!("{:?}", result); + // } + // assert!(result.is_ok()); + // assert_eq!(result.output.len(), 32); + // assert_eq!(result.output.as_ref(), hex!("0000000000000000000000000000000000000000000000000000000019d699a5")); + // } + // } + core::hint::black_box(result); + }); + }); + }; + + fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + b.iter(|| { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I32(0)]; + strategy + .execute(&mut store, "fib32", &[Value::I32(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmtime", |b| { + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmi", |b| { + let strategy = Strategy::Wasmi { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib32".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + group.bench_function("bench_rwasm", |b| { + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + bench_strategy(b, strategy); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/fib64.rs b/benchmarks/benches/fib64.rs new file mode 100644 index 000000000..8802adbd3 --- /dev/null +++ b/benchmarks/benches/fib64.rs @@ -0,0 +1,144 @@ +use criterion::{criterion_main, Bencher, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, + CallInput, InputsImpl, Interpreter, SharedMemory, +}; +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, +}; +use std::{sync::Arc, time::Duration}; + +const FIB_VALUE: i64 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons fib64"); + + // bench_native + { + pub fn fib64(n: u64) -> u64 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + group.bench_function("bench_native", |b| { + b.iter(|| { + core::hint::black_box(fib64(core::hint::black_box(FIB_VALUE as u64))); + }); + }); + }; + + // bench_evm + { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063e78692bb1461002d575b5f5ffd5b610047600480360381019061004291906100fd565b61005d565b6040516100549190610137565b60405180910390f35b5f5f5f90505f600190505f600290505b8467ffffffffffffffff168167ffffffffffffffff16116100b1575f8284610095919061017d565b90508293508092505080806100a9906101b8565b91505061006d565b508092505050919050565b5f5ffd5b5f67ffffffffffffffff82169050919050565b6100dc816100c0565b81146100e6575f5ffd5b50565b5f813590506100f7816100d3565b92915050565b5f60208284031215610112576101116100bc565b5b5f61011f848285016100e9565b91505092915050565b610131816100c0565b82525050565b5f60208201905061014a5f830184610128565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610187826100c0565b9150610192836100c0565b9250828201905067ffffffffffffffff8111156101b2576101b1610150565b5b92915050565b5f6101c2826100c0565b915067ffffffffffffffff82036101dc576101db610150565b5b60018201905091905056fea2646970667358221220b9932107a06e2c6f884433417401d45c3d48c85efc8e1d3110c6fba210eb5abc64736f6c634300081e0033"); + group.bench_function("bench_evm", |b| { + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + b.iter(|| { + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes(hex!("e78692bb000000000000000000000000000000000000000000000000000000000000002b").into()), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + // match &result { + // InterpreterAction::NewFrame(_) => unreachable!(), + // InterpreterAction::Return(result) => { + // if !result.is_ok() { + // println!("{:?}", result); + // } + // assert!(result.is_ok()); + // assert_eq!(result.output.len(), 32); + // assert_eq!(result.output.as_ref(), hex!("00000000000000000000000000000000000000000000000027f80ddaa1ba7878")); + // } + // } + core::hint::black_box(result); + }); + }); + }; + + fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + b.iter(|| { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I64(0)]; + strategy + .execute(&mut store, "fib64", &[Value::I64(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmtime", |b| { + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmi", |b| { + let strategy = Strategy::Wasmi { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + group.bench_function("bench_rwasm", |b| { + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + bench_strategy(b, strategy); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/lib.rs b/benchmarks/lib.rs index 9e395eaf9..df509cbdd 100644 --- a/benchmarks/lib.rs +++ b/benchmarks/lib.rs @@ -1,6 +1,7 @@ -#[cfg(target_arch = "wasm32")] +use alloy_primitives::U256; + #[no_mangle] -pub fn main(n: i32) -> i32 { +pub fn fib32(n: u32) -> u32 { let (mut a, mut b) = (0, 1); for _ in 0..n { let temp = a; @@ -9,3 +10,61 @@ pub fn main(n: i32) -> i32 { } a } + +#[no_mangle] +pub fn fib64(n: u64) -> u64 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a +} + +#[no_mangle] +pub fn fib256(n: u64) -> U256 { + let (mut a, mut b) = (U256::ZERO, U256::ONE); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a +} + +#[cfg(test)] +mod tests { + use rwasm::{ + always_failing_syscall_handler, CompilationConfig, ExecutionEngine, FuelConfig, + ImportLinker, RwasmModule, Strategy, Value, + }; + use std::sync::Arc; + + const FIB_VALUE: i32 = 41; + + #[test] + fn fib32_test() { + let wasm_binary = include_bytes!("./lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib32".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I32(0)]; + strategy + .execute(&mut store, "fib32", &[Value::I32(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result); + } +} diff --git a/e2e/src/lib.rs b/e2e/src/lib.rs index 3d47aa42b..63c8d8f3e 100644 --- a/e2e/src/lib.rs +++ b/e2e/src/lib.rs @@ -49,6 +49,17 @@ macro_rules! define_spec_tests { }; } +#[cfg(test)] +mod tests { + use crate::run; + use std::fmt; + + #[test] + fn specific_test() { + run::run_wasm_spec_test(&fmt::format(format_args!("{}/{}", "testsuite", "global"))); + } +} + define_spec_tests! { let runner = run::run_wasm_spec_test; diff --git a/perf/.gitignore b/perf/.gitignore new file mode 100644 index 000000000..994a3970e --- /dev/null +++ b/perf/.gitignore @@ -0,0 +1,7 @@ +target +Cargo.lock +lib.wat +lib.wasm +/perf.data* +/out.folded +/out.perf diff --git a/perf/Cargo.toml b/perf/Cargo.toml new file mode 100644 index 000000000..7c1dd78fe --- /dev/null +++ b/perf/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "perf" +version = "0.1.0" +edition = "2021" + +[build] +rustflags = ["-C", "force-frame-pointers=yes"] + +[[bin]] +name = "fibonacci_perf32" +path = "fibonacci_perf32.rs" + +[[bin]] +name = "fibonacci_perf64" +path = "fibonacci_perf64.rs" + +[[bin]] +name = "load_perf" +path = "load_perf.rs" + +[[bin]] +name = "evm_fibonacci_perf" +path = "evm_fibonacci_perf.rs" + +[profile.release] +panic = "abort" +lto = true +opt-level = 3 +strip = false +debug = true +debug-assertions = true +rpath = true +codegen-units = 1 + +[profile.dev] +panic = "abort" +debug = true +opt-level = 0 +debug-assertions = true +strip = false +codegen-units = 256 +rpath = true + +[dependencies] +rwasm = { path = "..", features = [ + "std", + "wasmtime", +] } +wat = "1.230.0" +revm-interpreter = "25.0.3" +revm-bytecode = "6.2.2" +hex-literal = "1.0.0" + +[dev-dependencies] + + diff --git a/perf/Makefile b/perf/Makefile new file mode 100644 index 000000000..a8390bbb6 --- /dev/null +++ b/perf/Makefile @@ -0,0 +1,51 @@ +all: all_sequentially + +.PHONY: build_wasm +build_wasm: + cd ../benchmarks && $(MAKE) + +PROFILE=debug# release | debug + +.PHONY: build +build: + #cargo build --profile dev + @if [ "$(PROFILE)" = "debug" ]; then\ + cargo build --profile dev; \ + else \ + cargo build --profile release; \ + fi + +.PHONY: fibonacci_perf32_flame +fibonacci_perf32_flame: + perf.sh target/$(PROFILE)/fibonacci_perf32 + mv flame.svg fibonacci_perf32_flame.svg + +.PHONY: fibonacci_perf64_flame +fibonacci_perf64_flame: + perf.sh target/$(PROFILE)/fibonacci_perf64 + mv flame.svg fibonacci_perf64_flame.svg + +.PHONY: evm_fibonacci_perf_flame +evm_fibonacci_perf_flame: + perf.sh target/$(PROFILE)/evm_fibonacci_perf + mv flame.svg evm_fibonacci_perf_flame.svg + +.PHONY: load_perf_flame +load_perf_flame: + perf.sh target/$(PROFILE)/load_perf main + mv flame.svg load_perf_flame.svg + +.PHONY: all_sequentially +all_sequentially: + $(MAKE) build_wasm + $(MAKE) build + #$(MAKE) evm_fibonacci_perf_flame + $(MAKE) fibonacci_perf32_flame + #$(MAKE) fibonacci_perf64_flame + #$(MAKE) load_perf_flame + +.PHONY: clean +clean: + #cargo clean + rm -rf out.* + rm -rf perf.data* diff --git a/perf/evm_fibonacci_perf.rs b/perf/evm_fibonacci_perf.rs new file mode 100644 index 000000000..f7d522c86 --- /dev/null +++ b/perf/evm_fibonacci_perf.rs @@ -0,0 +1,40 @@ +#![no_main] + +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::host::DummyHost; +use revm_interpreter::interpreter::{EthInterpreter, ExtBytecode}; +use revm_interpreter::interpreter_types::{Jumps, LoopControl, StackTr}; +use revm_interpreter::{instruction_table, CallInput, InputsImpl, Interpreter, SharedMemory}; + +#[no_mangle] +pub fn main() { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063f9b7c7e51461002d575b5f5ffd5b610047600480360381019061004291906100f1565b61005d565b604051610054919061012b565b60405180910390f35b5f5f5f90505f600190505f600290505b8463ffffffff168163ffffffff16116100a9575f828461008d9190610171565b90508293508092505080806100a1906101a8565b91505061006d565b508092505050919050565b5f5ffd5b5f63ffffffff82169050919050565b6100d0816100b8565b81146100da575f5ffd5b50565b5f813590506100eb816100c7565b92915050565b5f60208284031215610106576101056100b4565b5b5f610113848285016100dd565b91505092915050565b610125816100b8565b82525050565b5f60208201905061013e5f83018461011c565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61017b826100b8565b9150610186836100b8565b9250828201905063ffffffff8111156101a2576101a1610144565b5b92915050565b5f6101b2826100b8565b915063ffffffff82036101c8576101c7610144565b5b60018201905091905056fea26469706673582212206f34ca4baf4d7f4a2ab9c7060b71c1f28bca433c9959aabaa5c1ac6323863d2364736f6c634300081e0033"); + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes( + hex!("f9b7c7e5000000000000000000000000000000000000000000000000000000000000002b") + .into(), + ), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + for _ in 0..1000 { + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + interpreter.bytecode.absolute_jump(0); + interpreter.stack.clear(); + interpreter.bytecode.reset_action(); + // println!("result {:?}", result); + core::hint::black_box(result); + } +} diff --git a/perf/fibonacci_perf32.rs b/perf/fibonacci_perf32.rs new file mode 100644 index 000000000..f713b0268 --- /dev/null +++ b/perf/fibonacci_perf32.rs @@ -0,0 +1,39 @@ +#![no_main] + +use rwasm::{ + always_failing_syscall_handler, CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, + RwasmModule, Strategy, Value, +}; +use std::sync::Arc; + +#[no_mangle] +pub fn main() { + const FIB_VALUE: i32 = 43; + #[inline(never)] + fn bench_strategy(strategy: &Strategy) { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I32(0)]; + strategy + .execute(&mut store, "fib32", &[Value::I32(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result.clone()); + } + let wasm_binary = include_bytes!("../benchmarks/lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib32".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + for _ in 0..1000 { + bench_strategy(&strategy); + } +} diff --git a/perf/fibonacci_perf32_flame.svg b/perf/fibonacci_perf32_flame.svg new file mode 100644 index 000000000..66ab67ce9 --- /dev/null +++ b/perf/fibonacci_perf32_flame.svg @@ -0,0 +1,7 @@ + + + + + +ERROR: No valid input provided to flamegraph.pl. + diff --git a/perf/fibonacci_perf64.rs b/perf/fibonacci_perf64.rs new file mode 100644 index 000000000..dfd40fd86 --- /dev/null +++ b/perf/fibonacci_perf64.rs @@ -0,0 +1,39 @@ +#![no_main] + +use rwasm::{ + always_failing_syscall_handler, CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, + RwasmModule, Strategy, Value, +}; +use std::sync::Arc; + +#[no_mangle] +pub fn main() { + const FIB_VALUE: i64 = 90; + #[inline(never)] + fn bench_strategy(strategy: &Strategy) { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I64(0)]; + strategy + .execute(&mut store, "fib64", &[Value::I64(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result.clone()); + } + let wasm_binary = include_bytes!("../benchmarks/lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + for _ in 0..1 { + bench_strategy(&strategy); + } +} diff --git a/perf/fibonacci_perf64_flame.svg b/perf/fibonacci_perf64_flame.svg new file mode 100644 index 000000000..f21d205cc --- /dev/null +++ b/perf/fibonacci_perf64_flame.svg @@ -0,0 +1,2602 @@ + + + + + + + + + + + + + + +Flame Graph + +Reset Zoom +Search + + +desc_read (1 samples, 0.16%) + + + +smp_call_function_single_async (1 samples, 0.16%) + + + +irqentry_exit (1 samples, 0.16%) + + + +wasmparser_nostd::validator::operators::OperatorValidatorTemp<R>::push_operand (13 samples, 2.14%) +w.. + + +__handle_mm_fault (37 samples, 6.09%) +__handle.. + + +pte_offset_map_nolock (2 samples, 0.33%) + + + +__rcu_read_unlock (4 samples, 0.66%) + + + +rwasm::compiler::func_builder::FuncBuilder::translate_operators (104 samples, 17.11%) +rwasm::compiler::func_buil.. + + +perf_duration_warn (1 samples, 0.16%) + + + +alloc_pages_mpol (53 samples, 8.72%) +alloc_pages_.. + + +<wasmparser_nostd::validator::operators::OperatorValidatorTemp<R> as core::ops::deref::DerefMut>::deref_mut (1 samples, 0.16%) + + + +xas_descend (3 samples, 0.49%) + + + +printk_sprint (28 samples, 4.61%) +print.. + + +__update_load_avg_cfs_rq (1 samples, 0.16%) + + + +<hashbrown::scopeguard::ScopeGuard<T,F> as core::ops::drop::Drop>::drop (3 samples, 0.49%) + + + +irqentry_exit_to_user_mode (15 samples, 2.47%) +ir.. + + +_find_next_bit (3 samples, 0.49%) + + + +do_execveat_common.isra.0 (104 samples, 17.11%) +do_execveat_common.isra.0 + + +core::iter::traits::iterator::Iterator::take (1 samples, 0.16%) + + + +xas_load (1 samples, 0.16%) + + + +xas_load (5 samples, 0.82%) + + + +__rb_insert_augmented (2 samples, 0.33%) + + + +note_gp_changes (1 samples, 0.16%) + + + +__irqentry_text_end (1 samples, 0.16%) + + + +swake_up_one_online (22 samples, 3.62%) +swak.. + + +smp_call_function_single_async (3 samples, 0.49%) + + + +select_idle_sibling (2 samples, 0.33%) + + + +core::option::Option<T>::unwrap_or_else (33 samples, 5.43%) +core::o.. + + +rwasm::compiler::func_builder::FuncBuilder::translate (104 samples, 17.11%) +rwasm::compiler::func_buil.. + + +_raw_spin_unlock_irqrestore (1 samples, 0.16%) + + + +prepare_task_switch (6 samples, 0.99%) + + + +<hashbrown::control::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (3 samples, 0.49%) + + + +rwasm::vm::engine::ExecutionEngineInner::execute (88 samples, 14.47%) +rwasm::vm::engine::Exe.. + + +__schedule (59 samples, 9.70%) +__schedule + + +_$LT$rwasm..compiler..func_builder..FuncBuilder$u20$as$u20$wasmparser_nostd..readers..core..operators..VisitOperator$GT$::visit_block::_$u7b$$u7b$closure$u7d$$u7d$::hc85237fb4dec80c8 (40 samples, 6.58%) +_$LT$rwa.. + + +trigger_load_balance (1 samples, 0.16%) + + + +enqueue_hrtimer (4 samples, 0.66%) + + + +kick_ilb (4 samples, 0.66%) + + + +core::ops::function::FnOnce::call_once (88 samples, 14.47%) +core::ops::function::F.. + + +__calc_delta.constprop.0 (1 samples, 0.16%) + + + +wasmparser_nostd::binary_reader::BinaryReader::read_u8 (2 samples, 0.33%) + + + +timerqueue_add (2 samples, 0.33%) + + + +hrtimer_update_next_event (3 samples, 0.49%) + + + +sysvec_irq_work (17 samples, 2.80%) +sy.. + + +hashbrown::control::group::sse2::Group::match_full (61 samples, 10.03%) +hashbrown::con.. + + +irq_exit_rcu (41 samples, 6.74%) +irq_exit_.. + + +irq_exit_rcu (17 samples, 2.80%) +ir.. + + +core::intrinsics::copy_nonoverlapping::precondition_check (3 samples, 0.49%) + + + +core::option::Option<T>::is_none (1 samples, 0.16%) + + + +core::slice::<impl [T]>::get (2 samples, 0.33%) + + + +scheduler_tick (1 samples, 0.16%) + + + +tick_sched_handle (24 samples, 3.95%) +tick.. + + +rwasm::types::untyped_value::UntypedValue::i32_add (88 samples, 14.47%) +rwasm::types::untyped_.. + + +psi_task_switch (27 samples, 4.44%) +psi_t.. + + +next_uptodate_folio (11 samples, 1.81%) +n.. + + +<I as core::iter::traits::collect::IntoIterator>::into_iter (1 samples, 0.16%) + + + +rwasm::compiler::translator::InstructionTranslator::bump_fuel_consumption (4 samples, 0.66%) + + + +core::core_arch::x86::sse2::_mm_loadu_si128 (3 samples, 0.49%) + + + +<rwasm::compiler::control_flow::ControlFrame as core::convert::From<rwasm::compiler::control_flow::BlockControlFrame>>::from (1 samples, 0.16%) + + + +__mod_memcg_lruvec_state (6 samples, 0.99%) + + + +vprintk_emit (94 samples, 15.46%) +vprintk_emit + + +<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (2 samples, 0.33%) + + + +put_dec (1 samples, 0.16%) + + + +irq_work_single (97 samples, 15.95%) +irq_work_single + + +hashbrown::control::group::sse2::Group::load_aligned (23 samples, 3.78%) +hash.. + + +nohz_balance_exit_idle (1 samples, 0.16%) + + + +note_gp_changes (1 samples, 0.16%) + + + +nohz_balancer_kick (5 samples, 0.82%) + + + +error_entry (1 samples, 0.16%) + + + +core::slice::<impl [T]>::last (1 samples, 0.16%) + + + +sysvec_irq_work (100 samples, 16.45%) +sysvec_irq_work + + +available_idle_cpu (1 samples, 0.16%) + + + +<alloc::vec::Vec<T,A> as core::ops::deref::Deref>::deref (2 samples, 0.33%) + + + +fibonacci_perf6 (504 samples, 82.89%) +fibonacci_perf6 + + +native_sched_clock (1 samples, 0.16%) + + + +rcu_start_this_gp (1 samples, 0.16%) + + + +__alloc_pages (52 samples, 8.55%) +__alloc_pages + + +core::num::<impl u32>::checked_sub (1 samples, 0.16%) + + + +vprintk_store (86 samples, 14.14%) +vprintk_store + + +clockevents_program_event (2 samples, 0.33%) + + + +rb_insert_color (1 samples, 0.16%) + + + +rwasm::compiler::translator::InstructionTranslator::get_expressed_depth (9 samples, 1.48%) + + + +native_apic_msr_eoi (1 samples, 0.16%) + + + +handle_pte_fault (92 samples, 15.13%) +handle_pte_fault + + +core::iter::traits::iterator::Iterator::map (1 samples, 0.16%) + + + +__lruvec_stat_mod_folio (5 samples, 0.82%) + + + +scheduler_tick (7 samples, 1.15%) + + + +hashbrown::map::HashMap<K,V,S,A>::find_or_find_insert_slot (104 samples, 17.11%) +hashbrown::map::HashMap<K,.. + + +<core::option::Option<T> as core::convert::From<T>>::from (1 samples, 0.16%) + + + +<T as core::convert::Into<U>>::into (2 samples, 0.33%) + + + +rcu_report_qs_rdp (1 samples, 0.16%) + + + +__rcu_read_unlock (5 samples, 0.82%) + + + +scheduler_tick (17 samples, 2.80%) +sc.. + + +clockevents_program_event (3 samples, 0.49%) + + + +irq_work_run_list (98 samples, 16.12%) +irq_work_run_list + + +<usize as core::slice::index::SliceIndex<[T]>>::get (1 samples, 0.16%) + + + +rwasm::compiler::parser::ModuleParser::process_code_entry (104 samples, 17.11%) +rwasm::compiler::parser::M.. + + +prb_final_commit (1 samples, 0.16%) + + + +__smp_call_single_queue (3 samples, 0.49%) + + + +<alloc::vec::Vec<T,A> as core::ops::deref::Deref>::deref (1 samples, 0.16%) + + + +__irq_exit_rcu (19 samples, 3.12%) +__i.. + + +__hrtimer_run_queues (29 samples, 4.77%) +__hrt.. + + +__rcu_read_unlock (3 samples, 0.49%) + + + +core::result::Result<T,E>::unwrap_or_else (1 samples, 0.16%) + + + +search_binary_handler (104 samples, 17.11%) +search_binary_handler + + +xas_move_index (1 samples, 0.16%) + + + +rwasm::vm::executor::RwasmExecutor<T>::run (88 samples, 14.47%) +rwasm::vm::executor::R.. + + +<core::option::Option<T> as core::ops::try_trait::Try>::branch (1 samples, 0.16%) + + + +__lruvec_stat_mod_folio (5 samples, 0.82%) + + + +[unknown] (104 samples, 17.11%) +[unknown] + + +next_uptodate_folio (11 samples, 1.81%) +n.. + + +wake_affine (1 samples, 0.16%) + + + +put_prev_task_fair (1 samples, 0.16%) + + + +vma_interval_tree_insert (3 samples, 0.49%) + + + +prb_reserve (7 samples, 1.15%) + + + +prb_final_commit (7 samples, 1.15%) + + + +generic_exec_single (3 samples, 0.49%) + + + +alloc::vec::Vec<T,A>::push (9 samples, 1.48%) + + + +tick_sched_handle (7 samples, 1.15%) + + + +rwasm::compiler::translator::InstructionTranslator::relative_local_depth (4 samples, 0.66%) + + + +__perf_event_task_sched_in (4 samples, 0.66%) + + + +lapic_next_deadline (1 samples, 0.16%) + + + +sysvec_apic_timer_interrupt (79 samples, 12.99%) +sysvec_apic_timer_i.. + + +__note_gp_changes (12 samples, 1.97%) +_.. + + +__irq_exit_rcu (41 samples, 6.74%) +__irq_exi.. + + +load_elf_binary (104 samples, 17.11%) +load_elf_binary + + +alloc::vec::Vec<T,A>::as_slice (1 samples, 0.16%) + + + +wasmparser_nostd::validator::operators::Locals::get (4 samples, 0.66%) + + + +__count_memcg_events (2 samples, 0.33%) + + + +core::ub_checks::maybe_is_nonoverlapping::runtime (1 samples, 0.16%) + + + +__x64_sys_execve (104 samples, 17.11%) +__x64_sys_execve + + +hashbrown::raw::RawTable<T,A>::reserve_rehash (93 samples, 15.30%) +hashbrown::raw::RawTabl.. + + +<hashbrown::scopeguard::ScopeGuard<T,F> as core::ops::deref::DerefMut>::deref_mut (1 samples, 0.16%) + + + +<usize as core::slice::index::SliceIndex<[T]>>::index (1 samples, 0.16%) + + + +nohz_balancer_kick (1 samples, 0.16%) + + + +rcu_segcblist_accelerate (4 samples, 0.66%) + + + +hrtimer_forward (1 samples, 0.16%) + + + +do_syscall_64 (104 samples, 17.11%) +do_syscall_64 + + +irqentry_exit (1 samples, 0.16%) + + + +rwasm::compiler::func_builder::FuncBuilder::validate_then_translate (42 samples, 6.91%) +rwasm::co.. + + +<usize as core::slice::index::SliceIndex<[T]>>::get (1 samples, 0.16%) + + + +_$LT$rwasm..compiler..func_builder..FuncBuilder$u20$as$u20$wasmparser_nostd..readers..core..operators..VisitOperator$GT$::visit_local_get::_$u7b$$u7b$closure$u7d$$u7d$::hf8ebd179c11d2b84 (23 samples, 3.78%) +_$LT.. + + +asm_exc_page_fault (104 samples, 17.11%) +asm_exc_page_fault + + +wasmparser_nostd::binary_reader::BinaryReader::read_u8 (2 samples, 0.33%) + + + +percpu_counter_add_batch (1 samples, 0.16%) + + + +alloc::raw_vec::RawVecInner<A>::grow_amortized (7 samples, 1.15%) + + + +psi_group_change (21 samples, 3.45%) +psi.. + + +__sysvec_irq_work (98 samples, 16.12%) +__sysvec_irq_work + + +ctx_resched (4 samples, 0.66%) + + + +rwasm::compiler::translator::InstructionTranslator::consume_fuel_instr (3 samples, 0.49%) + + + +filemap_map_pages (22 samples, 3.62%) +file.. + + +handle_softirqs (15 samples, 2.47%) +ha.. + + +search_binary_handler (104 samples, 17.11%) +search_binary_handler + + +tick_nohz_highres_handler (9 samples, 1.48%) + + + +<rwasm::compiler::func_builder::FuncBuilder as wasmparser_nostd::readers::core::operators::VisitOperator>::visit_block (42 samples, 6.91%) +<rwasm::c.. + + +update_curr (5 samples, 0.82%) + + + +rcu_core_si (15 samples, 2.47%) +rc.. + + +hrtimer_update_next_event (3 samples, 0.49%) + + + +wasmparser_nostd::binary_reader::BinaryReader::visit_operator (100 samples, 16.45%) +wasmparser_nostd::binary_.. + + +put_prev_task_fair (14 samples, 2.30%) +p.. + + +rcu_core_si (1 samples, 0.16%) + + + +rcu_accelerate_cbs (5 samples, 0.82%) + + + +tick_nohz_highres_handler (5 samples, 0.82%) + + + +<rwasm::compiler::translator::InstructionTranslator as wasmparser_nostd::readers::core::operators::VisitOperator>::visit_local_get (23 samples, 3.78%) +<rwa.. + + +__memcg_kmem_charge_page (2 samples, 0.33%) + + + +do_user_addr_fault (104 samples, 17.11%) +do_user_addr_fault + + +tick_program_event (4 samples, 0.66%) + + + +irqentry_exit (1 samples, 0.16%) + + + +tick_sched_handle (3 samples, 0.49%) + + + +core::iter::traits::iterator::Iterator::sum (2 samples, 0.33%) + + + +perf_ctx_enable (4 samples, 0.66%) + + + +begin_new_exec (104 samples, 17.11%) +begin_new_exec + + +raw_spin_rq_lock_nested (2 samples, 0.33%) + + + +do_fault (25 samples, 4.11%) +do_f.. + + +__irqentry_text_end (1 samples, 0.16%) + + + +cgroup_rstat_updated (1 samples, 0.16%) + + + +hashbrown::raw::RawTableInner::prepare_resize::_$u7b$$u7b$closure$u7d$$u7d$::hf1e3bee4a1fd8022 (1 samples, 0.16%) + + + +call_function_single_prep_ipi (1 samples, 0.16%) + + + +__hrtimer_next_event_base (3 samples, 0.49%) + + + +__hrtimer_run_queues (16 samples, 2.63%) +__.. + + +vsnprintf (38 samples, 6.25%) +vsnprintf + + +_raw_spin_lock (1 samples, 0.16%) + + + +__mod_memcg_lruvec_state (4 samples, 0.66%) + + + +desc_update_last_finalized (7 samples, 1.15%) + + + +main (296 samples, 48.68%) +main + + +lapic_next_deadline (1 samples, 0.16%) + + + +handle_softirqs (40 samples, 6.58%) +handle_s.. + + +put_dec_trunc8 (1 samples, 0.16%) + + + +folio_add_file_rmap_ptes (8 samples, 1.32%) + + + +rwasm::compiler::parser::ModuleParser::parse (208 samples, 34.21%) +rwasm::compiler::parser::ModuleParser::parse + + +_prb_read_valid (6 samples, 0.99%) + + + +_$LT$rwasm..compiler..translator..InstructionTranslator$u20$as$u20$wasmparser_nostd..readers..core..operators..VisitOperator$GT$::visit_local_get::_$u7b$$u7b$closure$u7d$$u7d$::hebd24373617a51c1 (21 samples, 3.45%) +_$L.. + + +copy_data (1 samples, 0.16%) + + + +___ratelimit (2 samples, 0.33%) + + + +malloc (1 samples, 0.16%) + + + +<rwasm::compiler::translator::InstructionTranslator as wasmparser_nostd::readers::core::operators::VisitOperator>::visit_block (40 samples, 6.58%) +<rwasm::.. + + +rwasm::vm::executor::RwasmExecutor<T>::run_the_loop (88 samples, 14.47%) +rwasm::vm::executor::R.. + + +handle_softirqs (18 samples, 2.96%) +ha.. + + +<u32 as core::iter::traits::accum::Sum>::sum (1 samples, 0.16%) + + + +desc_read (2 samples, 0.33%) + + + +core::slice::<impl [T]>::get (1 samples, 0.16%) + + + +console_emit_next_record (4 samples, 0.66%) + + + +<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.16%) + + + +rcu_start_this_gp (2 samples, 0.33%) + + + +filemap_map_pmd (1 samples, 0.16%) + + + +do_syscall_64 (104 samples, 17.11%) +do_syscall_64 + + +read_tsc (1 samples, 0.16%) + + + +filemap_map_pmd (1 samples, 0.16%) + + + +note_gp_changes (1 samples, 0.16%) + + + +lock_vma_under_rcu (10 samples, 1.64%) + + + +idle_cpu (1 samples, 0.16%) + + + +prepend_path (20 samples, 3.29%) +pre.. + + +_int_malloc (1 samples, 0.16%) + + + +sched_clock (1 samples, 0.16%) + + + +__update_load_avg_se (2 samples, 0.33%) + + + +timerqueue_add (3 samples, 0.49%) + + + +__rcu_read_lock (1 samples, 0.16%) + + + +filemap_map_pages (4 samples, 0.66%) + + + +all (608 samples, 100%) + + + +rseq_update_cpu_node_id (1 samples, 0.16%) + + + +rwasm::compiler::labels::LabelRegistry::new_label (4 samples, 0.66%) + + + +do_nocb_deferred_wakeup.isra.0 (1 samples, 0.16%) + + + +tick_program_event (3 samples, 0.49%) + + + +alloc::alloc::alloc (3 samples, 0.49%) + + + +perf_duration_warn (97 samples, 15.95%) +perf_duration_warn + + +__hrtimer_next_event_base (2 samples, 0.33%) + + + +tick_sched_handle (18 samples, 2.96%) +ti.. + + +printk_get_next_message (4 samples, 0.66%) + + + +rwasm::vm::value_stack::ValueStackPtr::eval_top2 (88 samples, 14.47%) +rwasm::vm::value_stack.. + + +x64_sys_call (104 samples, 17.11%) +x64_sys_call + + +nohz_balance_exit_idle (2 samples, 0.33%) + + + +clear_page_erms (49 samples, 8.06%) +clear_page_.. + + +console_unlock (1 samples, 0.16%) + + + +irqentry_exit (2 samples, 0.33%) + + + +__libc_start_main@@GLIBC_2.34 (296 samples, 48.68%) +__libc_start_main@@GLIBC_2.34 + + +_raw_spin_lock (1 samples, 0.16%) + + + +tick_nohz_highres_handler (18 samples, 2.96%) +ti.. + + +wasmparser_nostd::readers::core::operators::OperatorsReader::visit_operator (101 samples, 16.61%) +wasmparser_nostd::readers.. + + +desc_read (1 samples, 0.16%) + + + +wasmparser_nostd::validator::operators::OperatorValidator::with_resources (1 samples, 0.16%) + + + +vprintk (95 samples, 15.62%) +vprintk + + +rwasm::compiler::translator::InstructionTranslator::translate_if_reachable (22 samples, 3.62%) +rwas.. + + +bprm_execve (104 samples, 17.11%) +bprm_execve + + +__hrtimer_next_event_base (1 samples, 0.16%) + + + +rcu_gp_kthread_wake (22 samples, 3.62%) +rcu_.. + + +_raw_spin_unlock (2 samples, 0.33%) + + + +set_pte_range (5 samples, 0.82%) + + + +mas_walk (10 samples, 1.64%) + + + +_raw_spin_unlock_irqrestore (1 samples, 0.16%) + + + +console_unlock (5 samples, 0.82%) + + + +enqueue_hrtimer (7 samples, 1.15%) + + + +wasmparser_nostd::validator::operators::OperatorValidatorTemp<R>::local (6 samples, 0.99%) + + + +rcu_segcblist_advance (1 samples, 0.16%) + + + +x86_pmu_enable (3 samples, 0.49%) + + + +data_alloc (3 samples, 0.49%) + + + +rwasm::compiler::parser::ModuleParser::process_exports (104 samples, 17.11%) +rwasm::compiler::parser::M.. + + +hashbrown::raw::RawTable<T,A>::find_or_find_insert_slot (104 samples, 17.11%) +hashbrown::raw::RawTable<T.. + + +clockevents_program_event (2 samples, 0.33%) + + + +ktime_get (1 samples, 0.16%) + + + +copy_from_kernel_nofault (1 samples, 0.16%) + + + +nohz_balance_exit_idle (1 samples, 0.16%) + + + +finish_task_switch.isra.0 (6 samples, 0.99%) + + + +rb_insert_color (2 samples, 0.33%) + + + +rwasm::compiler::parser::ModuleParser::process_payload (104 samples, 17.11%) +rwasm::compiler::parser::M.. + + +exec_binprm (104 samples, 17.11%) +exec_binprm + + +do_mmap (104 samples, 17.11%) +do_mmap + + +kick_ilb (1 samples, 0.16%) + + + +rwasm::compiler::control_flow::ControlFlowStack::push_frame (3 samples, 0.49%) + + + +__rcu_read_unlock (5 samples, 0.82%) + + + +nohz_balancer_kick (17 samples, 2.80%) +no.. + + +try_to_wake_up (19 samples, 3.12%) +try.. + + +do_execveat_common.isra.0 (104 samples, 17.11%) +do_execveat_common.isra.0 + + +__count_memcg_events (4 samples, 0.66%) + + + +d_path (20 samples, 3.29%) +d_p.. + + +irqentry_enter (1 samples, 0.16%) + + + +__irq_exit_rcu (17 samples, 2.80%) +__.. + + +irq_work_run (98 samples, 16.12%) +irq_work_run + + +console_emit_next_record (1 samples, 0.16%) + + + +xas_start (1 samples, 0.16%) + + + +exc_nmi (1 samples, 0.16%) + + + +dl_main (104 samples, 17.11%) +dl_main + + +__alloc_pages (1 samples, 0.16%) + + + +irqentry_exit_to_user_mode (64 samples, 10.53%) +irqentry_exit_t.. + + +asm_sysvec_irq_work (19 samples, 3.12%) +asm.. + + +bprm_execve.part.0 (104 samples, 17.11%) +bprm_execve.part.0 + + +rcu_core_si (1 samples, 0.16%) + + + +format_decode (16 samples, 2.63%) +fo.. + + +rwasm::strategy::Strategy::execute (88 samples, 14.47%) +rwasm::strategy::Strat.. + + +mas_wr_node_store (80 samples, 13.16%) +mas_wr_node_store + + +vprintk_default (95 samples, 15.62%) +vprintk_default + + +update_process_times (7 samples, 1.15%) + + + +__sysvec_apic_timer_interrupt (38 samples, 6.25%) +__sysvec.. + + +rcu_accelerate_cbs (7 samples, 1.15%) + + + +format_decode (12 samples, 1.97%) +f.. + + +rcu_segcblist_accelerate (1 samples, 0.16%) + + + +__rcu_read_unlock (2 samples, 0.33%) + + + +_raw_spin_unlock (1 samples, 0.16%) + + + +alloc::vec::Vec<T,A>::as_slice (1 samples, 0.16%) + + + +rwasm::module::RwasmModule::compile (208 samples, 34.21%) +rwasm::module::RwasmModule::compile + + +trigger_load_balance (6 samples, 0.99%) + + + +load_elf_binary (104 samples, 17.11%) +load_elf_binary + + +__schedule (6 samples, 0.99%) + + + +printk_sprint (1 samples, 0.16%) + + + +[unknown] (104 samples, 17.11%) +[unknown] + + +hrtimer_forward (1 samples, 0.16%) + + + +idle_cpu (1 samples, 0.16%) + + + +update_load_avg (5 samples, 0.82%) + + + +count_memcg_events.constprop.0 (4 samples, 0.66%) + + + +try_charge_memcg (2 samples, 0.33%) + + + +__rseq_handle_notify_resume (2 samples, 0.33%) + + + +core::iter::traits::iterator::Iterator::rev (1 samples, 0.16%) + + + +do_read_fault (88 samples, 14.47%) +do_read_fault + + +copy_from_kernel_nofault_allowed (6 samples, 0.99%) + + + +idle_cpu (1 samples, 0.16%) + + + +_printk (95 samples, 15.62%) +_printk + + +nohz_balance_exit_idle (1 samples, 0.16%) + + + +lapic_next_deadline (1 samples, 0.16%) + + + +rcu_core_si (1 samples, 0.16%) + + + +rcu_report_qs_rdp (7 samples, 1.15%) + + + +hrtimer_update_next_event (3 samples, 0.49%) + + + +timerqueue_add (4 samples, 0.66%) + + + +__x64_sys_execve (104 samples, 17.11%) +__x64_sys_execve + + +__sysvec_apic_timer_interrupt (15 samples, 2.47%) +__.. + + +do_fault (92 samples, 15.13%) +do_fault + + +asm_sysvec_apic_timer_interrupt (49 samples, 8.06%) +asm_sysvec_.. + + +exec_binprm (104 samples, 17.11%) +exec_binprm + + +rcu_core_si (12 samples, 1.97%) +r.. + + +trigger_load_balance (18 samples, 2.96%) +tr.. + + +<core::option::Option<T> as core::ops::try_trait::FromResidual<core::option::Option<core::convert::Infallible>>>::from_residual (1 samples, 0.16%) + + + +exc_page_fault (104 samples, 17.11%) +exc_page_fault + + +get_next_lpos (1 samples, 0.16%) + + + +cgroup_rstat_updated (1 samples, 0.16%) + + + +prb_read (3 samples, 0.49%) + + + +timerqueue_add (2 samples, 0.33%) + + + +handle_mm_fault (39 samples, 6.41%) +handle_m.. + + +asm_sysvec_apic_timer_interrupt (80 samples, 13.16%) +asm_sysvec_apic_tim.. + + +vscnprintf (26 samples, 4.28%) +vscnp.. + + +__rcu_read_unlock (3 samples, 0.49%) + + + +__libc_start_call_main (296 samples, 48.68%) +__libc_start_call_main + + +enqueue_hrtimer (4 samples, 0.66%) + + + +read_tsc (1 samples, 0.16%) + + + +xas_descend (4 samples, 0.66%) + + + +asm_exc_page_fault (60 samples, 9.87%) +asm_exc_page_f.. + + +_raw_spin_unlock_irqrestore (1 samples, 0.16%) + + + +__mmap_region (104 samples, 17.11%) +__mmap_region + + +do_nocb_deferred_wakeup.isra.0 (1 samples, 0.16%) + + + +alloc::vec::Vec<T,A>::len (1 samples, 0.16%) + + + +alloc_pages (54 samples, 8.88%) +alloc_pages + + +enqueue_hrtimer (3 samples, 0.49%) + + + +update_process_times (24 samples, 3.95%) +upda.. + + +x64_sys_call (104 samples, 17.11%) +x64_sys_call + + +x86_pmu_disable (1 samples, 0.16%) + + + +xas_find (6 samples, 0.99%) + + + +core::num::nonzero::NonZero<T>::new (1 samples, 0.16%) + + + +__rcu_read_unlock (1 samples, 0.16%) + + + +asm_sysvec_apic_timer_interrupt (88 samples, 14.47%) +asm_sysvec_apic_timer_.. + + +_dl_sysdep_start (104 samples, 17.11%) +_dl_sysdep_start + + +balance_fair (1 samples, 0.16%) + + + +rwasm::compiler::control_flow::ControlFrame::consume_fuel_instr (1 samples, 0.16%) + + + +number (6 samples, 0.99%) + + + +<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.16%) + + + +<rwasm::compiler::func_builder::FuncBuilder as wasmparser_nostd::readers::core::operators::VisitOperator>::visit_local_get (50 samples, 8.22%) +<rwasm::com.. + + +perf_pmu_nop_void (1 samples, 0.16%) + + + +__lruvec_stat_mod_folio (6 samples, 0.99%) + + + +entry_SYSCALL_64_after_hwframe (104 samples, 17.11%) +entry_SYSCALL_64_after_hwf.. + + +schedule (61 samples, 10.03%) +schedule + + +sched_use_asym_prio (2 samples, 0.33%) + + + +get_page_from_freelist (50 samples, 8.22%) +get_page_fr.. + + +rcu_segcblist_accelerate (1 samples, 0.16%) + + + +sysvec_apic_timer_interrupt (49 samples, 8.06%) +sysvec_apic.. + + +sysvec_apic_timer_interrupt (88 samples, 14.47%) +sysvec_apic_timer_inte.. + + +_RNvCs691rhTbG0Ee_7___rustc12___rust_alloc (2 samples, 0.33%) + + + +select_task_rq (14 samples, 2.30%) +s.. + + +console_flush_all (4 samples, 0.66%) + + + +fibonacci_perf64::main::bench_strategy (88 samples, 14.47%) +fibonacci_perf64::main.. + + +mas_wr_modify (80 samples, 13.16%) +mas_wr_modify + + +read_tsc (1 samples, 0.16%) + + + +lruvec_stat_mod_folio.constprop.0 (10 samples, 1.64%) + + + +rb_insert_color (1 samples, 0.16%) + + + +hashbrown::control::bitmask::BitMask::lowest_set_bit (1 samples, 0.16%) + + + +note_gp_changes (32 samples, 5.26%) +note_g.. + + +tick_program_event (4 samples, 0.66%) + + + +hashbrown::raw::RawTableInner::find_or_find_insert_slot_inner (8 samples, 1.32%) + + + +x2apic_send_IPI (1 samples, 0.16%) + + + +core::slice::<impl [T]>::get (2 samples, 0.33%) + + + +scheduler_tick (24 samples, 3.95%) +sche.. + + +perf_event_mmap (21 samples, 3.45%) +per.. + + +<alloc::vec::Vec<T,A> as core::ops::deref::Deref>::deref (2 samples, 0.33%) + + + +set_pte_range (11 samples, 1.81%) +s.. + + +rcu_gp_kthread_wake (1 samples, 0.16%) + + + +rcu_core (14 samples, 2.30%) +r.. + + +prb_read (3 samples, 0.49%) + + + +alloc::vec::Vec<T,A>::len (1 samples, 0.16%) + + + +consume_stock (1 samples, 0.16%) + + + +<wasmparser_nostd::validator::operators::OperatorValidatorTemp<T> as wasmparser_nostd::readers::core::operators::VisitOperator>::visit_local_get (21 samples, 3.45%) +<wa.. + + +lruvec_stat_mod_folio.constprop.0 (1 samples, 0.16%) + + + +sysvec_reschedule_ipi (2 samples, 0.33%) + + + +call_function_single_prep_ipi (1 samples, 0.16%) + + + +vsnprintf (26 samples, 4.28%) +vsnpr.. + + +__hrtimer_run_queues (23 samples, 3.78%) +__hr.. + + +__sysvec_apic_timer_interrupt (23 samples, 3.78%) +__sy.. + + +count_memcg_events.constprop.0 (2 samples, 0.33%) + + + +record_times (2 samples, 0.33%) + + + +__sysvec_apic_timer_interrupt (29 samples, 4.77%) +__sys.. + + +ttwu_queue_wakelist (3 samples, 0.49%) + + + +prepend (18 samples, 2.96%) +pr.. + + +perf-exec (104 samples, 17.11%) +perf-exec + + +<alloc::alloc::Global as core::alloc::Allocator>::allocate (3 samples, 0.49%) + + + +_dl_relocate_object (104 samples, 17.11%) +_dl_relocate_object + + +hrtimer_interrupt (29 samples, 4.77%) +hrtim.. + + +wasmparser_nostd::validator::func::FuncValidator<T>::visitor (2 samples, 0.33%) + + + +__calc_delta.constprop.0 (2 samples, 0.33%) + + + +hrtimer_interrupt (23 samples, 3.78%) +hrti.. + + +rwasm::compiler::translator::InstructionTranslator::frame_stack_height (33 samples, 5.43%) +rwasm::.. + + +note_gp_changes (12 samples, 1.97%) +n.. + + +pte_alloc_one (64 samples, 10.53%) +pte_alloc_one + + +tick_program_event (2 samples, 0.33%) + + + +fpregs_assert_state_consistent (1 samples, 0.16%) + + + +pick_next_task_stop (2 samples, 0.33%) + + + +native_queued_spin_lock_slowpath (1 samples, 0.16%) + + + +pick_next_task_stop (1 samples, 0.16%) + + + +__rcu_read_unlock (5 samples, 0.82%) + + + +__irqentry_text_end (1 samples, 0.16%) + + + +_start (400 samples, 65.79%) +_start + + +mas_wr_store_entry.isra.0 (80 samples, 13.16%) +mas_wr_store_entry... + + +sched_clock_noinstr (1 samples, 0.16%) + + + +alloc::raw_vec::RawVec<T,A>::grow_one (7 samples, 1.15%) + + + +hashbrown::raw::RawTable<T,A>::reserve (95 samples, 15.62%) +hashbrown::raw::RawTable.. + + +exc_page_fault (60 samples, 9.87%) +exc_page_fault + + +__mod_memcg_lruvec_state (3 samples, 0.49%) + + + +perf_event_task_tick (4 samples, 0.66%) + + + +core::ptr::drop_in_place$LT$hashbrown..scopeguard..ScopeGuard$LT$hashbrown..raw..RawTableInner$C$hashbrown..raw..RawTableInner..prepare_resize$LT$allocator_api2..stable..alloc..global..Global$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$$GT$::hb7b94959ffe4f32a (4 samples, 0.66%) + + + +rwasm::types::untyped_value::UntypedValue::execute_binary (88 samples, 14.47%) +rwasm::types::untyped_.. + + +core::cmp::Ord::max (1 samples, 0.16%) + + + +handle_pte_fault (27 samples, 4.44%) +handl.. + + +alloc::alloc::Global::alloc_impl (3 samples, 0.49%) + + + +bprm_execve.part.0 (104 samples, 17.11%) +bprm_execve.part.0 + + +rcu_start_this_gp (2 samples, 0.33%) + + + +_$LT$rwasm..compiler..func_builder..FuncBuilder$u20$as$u20$wasmparser_nostd..readers..core..operators..VisitOperator$GT$::visit_local_get::_$u7b$$u7b$closure$u7d$$u7d$::h2bf6eb9d55d09b2a (24 samples, 3.95%) +_$LT.. + + +select_task_rq_fair (9 samples, 1.48%) + + + +generic_exec_single (1 samples, 0.16%) + + + +entry_SYSCALL_64_after_hwframe (104 samples, 17.11%) +entry_SYSCALL_64_after_hwf.. + + +copy_from_kernel_nofault (16 samples, 2.63%) +co.. + + +hashbrown::control::group::sse2::Group::load (3 samples, 0.49%) + + + +__hrtimer_next_event_base (2 samples, 0.33%) + + + +rwasm::compiler::control_flow::ControlFlowStack::last (2 samples, 0.33%) + + + +__perf_event_task_sched_out (4 samples, 0.66%) + + + +format_decode (1 samples, 0.16%) + + + +sysvec_apic_timer_interrupt (32 samples, 5.26%) +sysvec.. + + +run_posix_cpu_timers (1 samples, 0.16%) + + + +hrtimer_update_next_event (4 samples, 0.66%) + + + +<wasmparser_nostd::validator::operators::WasmProposalValidator<T> as wasmparser_nostd::readers::core::operators::VisitOperator>::visit_local_get (22 samples, 3.62%) +<was.. + + +irq_exit_rcu (19 samples, 3.12%) +irq.. + + +update_process_times (17 samples, 2.80%) +up.. + + +blkcg_maybe_throttle_current (1 samples, 0.16%) + + + +irqentry_exit (1 samples, 0.16%) + + + +<T as core::convert::Into<U>>::into (88 samples, 14.47%) +<T as core::convert::I.. + + +select_task_rq_fair (1 samples, 0.16%) + + + +rcu_core_si (37 samples, 6.09%) +rcu_core.. + + +core::slice::iter::Iter<T>::new (1 samples, 0.16%) + + + +handle_mm_fault (102 samples, 16.78%) +handle_mm_fault + + +data_push_tail (2 samples, 0.33%) + + + +number (9 samples, 1.48%) + + + +irqentry_exit (65 samples, 10.69%) +irqentry_exit + + +printk_parse_prefix (1 samples, 0.16%) + + + +rcu_core (36 samples, 5.92%) +rcu_core + + +desc_read_finalized_seq (1 samples, 0.16%) + + + +file_path (21 samples, 3.45%) +fil.. + + +<alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index (2 samples, 0.33%) + + + +<T as core::convert::Into<U>>::into (1 samples, 0.16%) + + + +tick_nohz_highres_handler (24 samples, 3.95%) +tick.. + + +xas_load (6 samples, 0.99%) + + + +perf_event_task_tick (2 samples, 0.33%) + + + +do_read_fault (22 samples, 3.62%) +do_r.. + + +clockevents_program_event (1 samples, 0.16%) + + + +hrtimer_interrupt (15 samples, 2.47%) +hr.. + + +core::core_arch::x86::sse2::_mm_load_si128 (20 samples, 3.29%) +cor.. + + +native_queued_spin_lock_slowpath (1 samples, 0.16%) + + + +rcu_note_context_switch (1 samples, 0.16%) + + + +schedule (7 samples, 1.15%) + + + +desc_make_final (1 samples, 0.16%) + + + +<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.16%) + + + +trigger_load_balance (1 samples, 0.16%) + + + +rcu_report_qs_rnp (1 samples, 0.16%) + + + +nohz_balancer_kick (1 samples, 0.16%) + + + +hrtimer_interrupt (38 samples, 6.25%) +hrtimer_.. + + +_dl_start (104 samples, 17.11%) +_dl_start + + +kick_ilb (3 samples, 0.49%) + + + +folio_add_file_rmap_ptes (5 samples, 0.82%) + + + +cpuacct_charge (1 samples, 0.16%) + + + +mas_store_prealloc (80 samples, 13.16%) +mas_store_prealloc + + +lapic_next_deadline (1 samples, 0.16%) + + + +desc_read_finalized_seq (3 samples, 0.49%) + + + +alloc::vec::Vec<T,A>::push (2 samples, 0.33%) + + + +switch_fpu_return (2 samples, 0.33%) + + + +alloc::raw_vec::finish_grow (4 samples, 0.66%) + + + +get_data (1 samples, 0.16%) + + + +alloc::vec::Vec<T,A>::as_slice (1 samples, 0.16%) + + + +prb_read_valid (3 samples, 0.49%) + + + +__smp_call_single_queue (1 samples, 0.16%) + + + +_prb_read_valid (3 samples, 0.49%) + + + +space_used (1 samples, 0.16%) + + + +__memmove (1 samples, 0.16%) + + + +sched_clock_cpu (1 samples, 0.16%) + + + +__mod_lruvec_state (6 samples, 0.99%) + + + +vsnprintf (1 samples, 0.16%) + + + +rcu_core (9 samples, 1.48%) + + + +hrtimer_forward (1 samples, 0.16%) + + + +put_prev_entity (13 samples, 2.14%) +p.. + + +asm_sysvec_irq_work (100 samples, 16.45%) +asm_sysvec_irq_work + + +<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.16%) + + + +rwasm::compiler::func_builder::FuncBuilder::validate_then_translate (49 samples, 8.06%) +rwasm::comp.. + + +__mod_lruvec_state (3 samples, 0.49%) + + + +vm_mmap (104 samples, 17.11%) +vm_mmap + + +nohz_balancer_kick (6 samples, 0.99%) + + + +__note_gp_changes (9 samples, 1.48%) + + + +filemap_map_pages (21 samples, 3.45%) +fil.. + + +read_tsc (1 samples, 0.16%) + + + +trigger_load_balance (7 samples, 1.15%) + + + +__handle_mm_fault (98 samples, 16.12%) +__handle_mm_fault + + +perf_event_exec (104 samples, 17.11%) +perf_event_exec + + +update_rq_clock (1 samples, 0.16%) + + + +asm_exc_nmi (1 samples, 0.16%) + + + +mmap_region (104 samples, 17.11%) +mmap_region + + +rcu_segcblist_accelerate (5 samples, 0.82%) + + + +wasmparser_nostd::binary_reader::BinaryReader::read_var_u32 (3 samples, 0.49%) + + + +filemap_map_pages (3 samples, 0.49%) + + + +xas_find (7 samples, 1.15%) + + + +put_prev_task_balance (16 samples, 2.63%) +pu.. + + +rcu_accelerate_cbs (4 samples, 0.66%) + + + +housekeeping_cpumask (1 samples, 0.16%) + + + +update_min_vruntime (2 samples, 0.33%) + + + +core::core_arch::x86::sse2::_mm_movemask_epi8 (61 samples, 10.03%) +core::core_arc.. + + +profile_tick (1 samples, 0.16%) + + + +elf_load (104 samples, 17.11%) +elf_load + + +__hrtimer_run_queues (9 samples, 1.48%) + + + +asm_sysvec_reschedule_ipi (2 samples, 0.33%) + + + +alloc::vec::Vec<T,A>::push (1 samples, 0.16%) + + + +perf_event_mmap_event (21 samples, 3.45%) +per.. + + +do_user_addr_fault (57 samples, 9.38%) +do_user_addr_.. + + +core::slice::<impl [T]>::iter (2 samples, 0.33%) + + + +update_process_times (3 samples, 0.49%) + + + +core::result::Result<T,E>::is_err (1 samples, 0.16%) + + + +alloc::raw_vec::RawVecInner<A>::current_memory (1 samples, 0.16%) + + + +__mod_lruvec_state (4 samples, 0.66%) + + + +pick_next_task (20 samples, 3.29%) +pic.. + + +<T as core::convert::TryInto<U>>::try_into (1 samples, 0.16%) + + + +perf_event_enable_on_exec (104 samples, 17.11%) +perf_event_enable_on_exec + + +vm_mmap_pgoff (104 samples, 17.11%) +vm_mmap_pgoff + + +core::option::Option<T>::unwrap_or_else (1 samples, 0.16%) + + + +irqentry_exit_to_user_mode (2 samples, 0.33%) + + + +perf_adjust_freq_unthr_context (4 samples, 0.66%) + + + +rb_insert_color (1 samples, 0.16%) + + + +<wasmparser_nostd::validator::operators::OperatorValidatorTemp<R> as core::ops::deref::Deref>::deref (1 samples, 0.16%) + + + +irqentry_exit (16 samples, 2.63%) +ir.. + + +alloc::vec::Vec<T,A>::len (1 samples, 0.16%) + + + +hashbrown::control::group::sse2::Group::match_empty_or_deleted (61 samples, 10.03%) +hashbrown::con.. + + +bprm_execve (104 samples, 17.11%) +bprm_execve + + +perf_event_context_sched_out (2 samples, 0.33%) + + + +prepend_copy (17 samples, 2.80%) +pr.. + + +asm_sysvec_apic_timer_interrupt (32 samples, 5.26%) +asm_sy.. + + +__hrtimer_next_event_base (3 samples, 0.49%) + + + +swake_up_one (21 samples, 3.45%) +swa.. + + +<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.16%) + + + +hashbrown::map::HashMap<K,V,S,A>::insert (104 samples, 17.11%) +hashbrown::map::HashMap<K,.. + + +__memcg_kmem_charge_page (1 samples, 0.16%) + + + diff --git a/perf/load_perf.rs b/perf/load_perf.rs new file mode 100644 index 000000000..7b5ba0cf3 --- /dev/null +++ b/perf/load_perf.rs @@ -0,0 +1,46 @@ +#![no_main] + +use rwasm::{CompilationConfig, ExecutionEngine, RwasmModule, RwasmStore, Value}; + +#[no_mangle] +pub fn main() { + let wasm_binary = wat::parse_str( + r#" + (module + (memory 1) + (data (i32.const 0) "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab") + (func (export "64_good1") (param $i i32) (result i64) + (i64.load offset=0 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + ) + "#, + ) + .unwrap(); + let config = CompilationConfig::default() + .with_entrypoint_name("64_good1".into()) + .with_allow_malformed_entrypoint_func_type(true); + let (rwasm_module, _) = RwasmModule::compile(config, &wasm_binary).unwrap(); + println!("{}", rwasm_module); + let mut store = RwasmStore::<()>::default(); + let engine = ExecutionEngine::new(); + let mut result = [Value::I64(0); 1]; + fn bench_execute( + engine: &ExecutionEngine, + store: &mut RwasmStore<()>, + rwasm_module: &RwasmModule, + result: &mut [Value; 1], + ) { + engine + .execute(store, rwasm_module, &[Value::I32(0)], result) + .unwrap(); + assert_eq!(result[0].i64().unwrap(), 0x6867666564636261); + } + for _ in 0..1000 { + core::hint::black_box(bench_execute( + &engine, + &mut store, + &rwasm_module, + &mut result, + )); + } +} diff --git a/snippets/Makefile b/snippets/Makefile index 4f3c9014f..878eb5d8e 100644 --- a/snippets/Makefile +++ b/snippets/Makefile @@ -1,5 +1,5 @@ .PHONY: lib.wasm lib.wasm: Cargo.toml lib.rs - cargo +nightly-2025-09-20 b --release --target=wasm32-unknown-unknown --no-default-features + cargo +nightly-2025-09-20 b --release --target-dir=./target --target=wasm32-unknown-unknown --no-default-features cp ./target/wasm32-unknown-unknown/release/snippets.wasm lib.wasm wasm2wat lib.wasm > lib.wat || true \ No newline at end of file diff --git a/src/compiler/parser.rs b/src/compiler/parser.rs index 6cb04ed2d..c5d8ce7d1 100644 --- a/src/compiler/parser.rs +++ b/src/compiler/parser.rs @@ -240,7 +240,8 @@ impl ModuleParser { } pub fn emit_snippets(&mut self) { - let mut emitted_snippets: HashMap = HashMap::new(); + let mut emitted_snippets: HashMap = + Default::default(); let snippet_calls = self.allocations.translation.snippet_calls.clone(); for snippet_call in snippet_calls { diff --git a/src/compiler/segment_builder.rs b/src/compiler/segment_builder.rs index 3d2dbe52f..b68cdad3d 100644 --- a/src/compiler/segment_builder.rs +++ b/src/compiler/segment_builder.rs @@ -10,9 +10,9 @@ use wasmparser::{TableType, ValType}; #[derive(Debug)] pub struct SegmentBuilder { pub(crate) global_memory_section: Vec, - pub(crate) memory_sections: HashMap, + pub(crate) memory_sections: HashMap, pub(crate) global_element_section: Vec, - pub(crate) element_sections: HashMap, + pub(crate) element_sections: HashMap, pub(crate) total_allocated_pages: u32, pub(crate) entrypoint_bytecode: InstructionSet, } diff --git a/src/compiler/translator.rs b/src/compiler/translator.rs index a4d7ba205..4f046432a 100644 --- a/src/compiler/translator.rs +++ b/src/compiler/translator.rs @@ -63,7 +63,7 @@ pub struct FuncTranslatorAllocations { pub(crate) tables: Vec, pub(crate) memories: Vec, pub(crate) globals: Vec, - pub(crate) exported_funcs: HashMap, FuncIdx>, + pub(crate) exported_funcs: HashMap, FuncIdx, fnv::FnvBuildHasher>, pub(crate) start_func: Option, pub(crate) func_offsets: Vec, pub(crate) constructor_params: ConstructorParams, diff --git a/src/module.rs b/src/module.rs index e9a414fcc..36a00197f 100644 --- a/src/module.rs +++ b/src/module.rs @@ -20,12 +20,34 @@ pub use view::*; /// reference) information needed for execution within the rWasm virtual machine. /// /// It's compiled from Wasm -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Default, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct RwasmModule { inner: Arc, } +#[cfg(feature = "serde")] +impl serde::Serialize for RwasmModule { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for RwasmModule { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let result = RwasmModuleInner::deserialize(deserializer)?; + Ok(Self { + inner: Arc::new(result), + }) + } +} + fn _check() { fn assert_send_sync() {} assert_send_sync::(); @@ -105,8 +127,8 @@ impl Deref for RwasmModule { } } -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Default, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RwasmModuleInner { /// The main instruction set (bytecode) for this module that includes an entrypoint /// and all required functions. diff --git a/src/types/bitvec_inlined.rs b/src/types/bitvec_inlined.rs new file mode 100644 index 000000000..29221cd24 --- /dev/null +++ b/src/types/bitvec_inlined.rs @@ -0,0 +1,266 @@ +use bitvec::index::BitIdx; +use bitvec::order::Lsb0; +use bitvec::store::BitStore; +use bitvec::vec::BitVec; +use core::cmp::min; +use core::ops::Range; + +pub const USIZE_BITS: usize = 0_usize.count_zeros() as usize; + +pub type ElemType = usize; + +pub struct BitVecInlined { + pub inline_data: [ElemType; INLINE], + pub inline_data_bit_len: usize, + pub bit_vec: BitVec, +} + +impl BitVecInlined { + pub const INLINE_DATA_BIT_LEN_MAX: usize = INLINE * USIZE_BITS; + + pub fn new(bit_vec: BitVec) -> Self { + let static_vec = [ElemType::MIN; INLINE]; + let static_len = if bit_vec.len() > INLINE { + INLINE + } else { + bit_vec.len() + }; + let bit_vec = bit_vec[static_len..].try_into().unwrap(); + Self { + inline_data: static_vec, + inline_data_bit_len: static_len, + bit_vec, + } + } + + pub fn new_empty() -> Self { + Self { + inline_data: [usize::MIN; INLINE], + inline_data_bit_len: 0, + bit_vec: BitVec::<_, _>::EMPTY, + } + } + + pub fn get_inline_count(&self) -> usize { + INLINE + } +} + +impl BitVecInlined { + pub const EMPTY: Self = Self { + inline_data: [ElemType::MIN; INLINE], + inline_data_bit_len: 0, + bit_vec: BitVec::<_, _>::EMPTY, + }; + + #[inline] + fn fill_range(data: &mut [ElemType; INLINE], range: Range, value: bool) { + // println!("BV.fill_range(range={:?},value={})", range, value); + let mut idx = range.start; + while idx < range.end { + let (item_idx, item_shift_idx_base) = Self::relative_indexes(idx); + let bits_to_set_count = Self::INLINE_DATA_BIT_LEN_MAX - item_shift_idx_base; + if idx + bits_to_set_count <= range.end { + if value { + let mask = ElemType::MAX.unbounded_shr(item_shift_idx_base as u32); + data[item_idx] |= mask; + } else { + let mask = ElemType::MAX.unbounded_shl(bits_to_set_count as u32); + data[item_idx] &= mask; + } + idx += bits_to_set_count; + continue; + } + let item = &mut data[item_idx]; + for i in item_shift_idx_base..USIZE_BITS { + Self::set_bit(item, i, value); + } + + idx += USIZE_BITS - item_shift_idx_base; + } + } + + #[inline] + pub fn repeat(bit: bool, len: usize) -> Self { + // println!("BV.repeat(bit={},len={})", bit, len); + let mut inline_data = [ElemType::MIN; INLINE]; + let inline_data_bit_len = min(len, Self::INLINE_DATA_BIT_LEN_MAX); + Self::fill_range(&mut inline_data, 0..inline_data_bit_len, bit); + let bit_vec = if len <= Self::INLINE_DATA_BIT_LEN_MAX { + BitVec::<_, _>::EMPTY + } else { + BitVec::repeat(bit, len - Self::INLINE_DATA_BIT_LEN_MAX) + }; + + Self { + inline_data, + inline_data_bit_len, + bit_vec, + } + } + + #[inline] + pub fn len(&self) -> usize { + self.inline_data_bit_len + self.bit_vec.len() + } + + pub fn fill(&mut self, value: bool) { + // println!("BV.fill(value={})", value); + let fill = if value { ElemType::MAX } else { ElemType::MIN }; + self.inline_data.fill(fill); + if !self.bit_vec.is_empty() { + self.bit_vec.fill(value); + } + } + + #[inline] + fn item_index(index: usize) -> usize { + let item_index = index / USIZE_BITS; + item_index + } + + #[inline] + fn relative_indexes(index: usize) -> (usize, usize) { + let item_index = Self::item_index(index); + let item_shift_index = index - item_index * USIZE_BITS; + (item_index, item_shift_index) + } + + #[inline] + pub fn get(&self, index: usize) -> Option { + // println!("BV.get(index={})", index); + if index < Self::INLINE_DATA_BIT_LEN_MAX { + if index >= self.inline_data_bit_len { + return None; + } + let (item_index, item_shift_index) = Self::relative_indexes(index); + + let item = self.inline_data[item_index]; + // TODO replace with manual calculation for performance? + return Some(item.get_bit::(BitIdx::new(item_shift_index as u8).unwrap())); + }; + self.bit_vec + .get(index - Self::INLINE_DATA_BIT_LEN_MAX) + .as_deref() + .copied() + } + + #[inline] + pub fn resize(&mut self, new_len: usize, value: bool) { + // println!("BV.resize(new_len={},value={})", new_len, value); + if self.inline_data_bit_len < new_len { + let new_inline_data_bit_len = min(new_len, Self::INLINE_DATA_BIT_LEN_MAX); + Self::fill_range( + &mut self.inline_data, + self.inline_data_bit_len..new_inline_data_bit_len, + value, + ); + + self.inline_data_bit_len = new_inline_data_bit_len; + } + if new_len > Self::INLINE_DATA_BIT_LEN_MAX { + let dynamic_len = new_len - Self::INLINE_DATA_BIT_LEN_MAX; + self.bit_vec.resize(dynamic_len, value) + } + } + + #[inline] + pub fn set(&mut self, index: usize, value: bool) { + // println!("BV.set(index={},value={})", index, value); + self.replace(index, value); + } + + #[inline] + pub fn set_bit(val: &mut usize, index: usize, value: bool) { + let mask = 1usize.unbounded_shl(index as u32); + if value { + *val |= mask; + } else { + *val &= !mask; + } + } + + #[inline] + pub fn replace(&mut self, index: usize, value: bool) -> bool { + // println!("replace(index={},value={})", index, value); + if index >= Self::INLINE_DATA_BIT_LEN_MAX { + return self + .bit_vec + .replace(index - Self::INLINE_DATA_BIT_LEN_MAX, value); + } + self.assert_valid_idx(index); + let (item_index, item_shift_index) = Self::relative_indexes(index); + let old_value = self.get(item_index).unwrap(); + let item = &mut self.inline_data[item_index]; + Self::set_bit(item, item_shift_index, value); + + old_value + } + + #[inline] + pub fn assert_valid_idx(&self, idx: usize) { + if idx >= self.len() { + panic!("index out of bounds") + } + } +} + +#[cfg(test)] +mod tests { + use crate::bitvec_inlined::USIZE_BITS; + use crate::types::bitvec_inlined::BitVecInlined; + + #[test] + fn tt() { + let v = 1usize; + let r = 2usize; + assert_eq!(v.unbounded_shl(1), r); + } + + #[test] + fn bit_vec_inlined() { + let mut len = 65; + let mut idx = 1; + assert!(idx < len); + let mut value = true; + let mut bv = BitVecInlined::<1>::repeat(value, len); + assert_eq!(bv.get(idx), Some(value)); + idx = 64; + value = false; + bv.set(idx, value); + assert_eq!(bv.get(idx), Some(value)); + assert_eq!(bv.get(len), None); + len += 1; + value = true; + bv.resize(len, value); + assert_eq!(bv.get(len - 1), Some(value)); + } + + #[test] + fn bit_vec_inlined_filling() { + const LEN_BASE: usize = 65; + let mut len = LEN_BASE; + let mut idx = 1; + assert!(idx < len); + let mut value = true; + let mut bv = BitVecInlined::<{ (LEN_BASE + USIZE_BITS) / USIZE_BITS }>::repeat(value, len); + assert_eq!(bv.get(idx), Some(value)); + idx = 64; + value = false; + bv.set(idx, value); + assert_eq!(bv.get(idx), Some(value)); + assert_eq!(bv.get(len), None); + len += 1; + value = true; + bv.resize(len, value); + assert_eq!(bv.get(len - 1), Some(value)); + len += USIZE_BITS; + value = false; + bv.resize(len, value); + assert_eq!(bv.get(len - 1), Some(value)); + assert_eq!(bv.get(63), Some(true)); + assert_eq!(bv.get(64), Some(false)); + assert_eq!(bv.get(65), Some(true)); + assert_eq!(bv.get(66), Some(false)); + } +} diff --git a/src/types/branch_offset.rs b/src/types/branch_offset.rs index 8415cf791..0484805e3 100644 --- a/src/types/branch_offset.rs +++ b/src/types/branch_offset.rs @@ -1,6 +1,4 @@ use bincode::{Decode, Encode}; -#[cfg(feature = "tracing")] -use serde::{Deserialize, Serialize}; /// A signed offset for branch instructions. /// diff --git a/src/types/import_linker.rs b/src/types/import_linker.rs index d18d189cb..f289089c6 100644 --- a/src/types/import_linker.rs +++ b/src/types/import_linker.rs @@ -1,14 +1,14 @@ +use crate::intrinsic::Intrinsic; use crate::{ImportName, InstructionSet}; use alloc::vec::Vec; use hashbrown::HashMap; use wasmparser::{FuncType, ValType}; -use crate::intrinsic::Intrinsic; #[derive(Debug, Default, Clone)] pub struct ImportLinker { entities: Vec, - name_to_entity: HashMap, - idx_to_entity: HashMap, + name_to_entity: HashMap, + idx_to_entity: HashMap, } #[derive(Debug, Clone)] diff --git a/src/types/mod.rs b/src/types/mod.rs index 1371611b8..e8249a781 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,3 +1,4 @@ +pub mod bitvec_inlined; mod branch_offset; mod constructor_params; mod error; diff --git a/src/vm/call_stack.rs b/src/vm/call_stack.rs index 226db5b63..cfcc5f2fc 100644 --- a/src/vm/call_stack.rs +++ b/src/vm/call_stack.rs @@ -1,5 +1,5 @@ use crate::InstructionPtr; -use smallvec::SmallVec; +use alloc::vec::Vec; #[derive(Default, Clone)] /// A lightweight call stack used by the interpreter to track return addresses. @@ -7,7 +7,7 @@ use smallvec::SmallVec; /// The capacity grows on demand but is typically small due to Wasm's structured control flow. pub struct CallStack { /// Return address stack backing storage; holds instruction pointers for nested calls. - buf: SmallVec<[InstructionPtr; 16]>, + buf: Vec, } impl CallStack { diff --git a/src/vm/context.rs b/src/vm/context.rs index 4ddbf2e92..33137bcda 100644 --- a/src/vm/context.rs +++ b/src/vm/context.rs @@ -25,15 +25,14 @@ impl<'a, T: 'static + Send + Sync> RwasmCaller<'a, T> { impl<'a, T: 'static + Send + Sync> Store for RwasmCaller<'a, T> { fn memory_read(&mut self, offset: usize, buffer: &mut [u8]) -> Result<(), TrapCode> { - self.store.global_memory.read(offset, buffer)?; + self.store.get_global_memory().read(offset, buffer)?; Ok(()) } fn memory_write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), TrapCode> { - self.store.global_memory.write(offset, buffer)?; + self.store.get_global_memory().write(offset, buffer)?; #[cfg(feature = "tracing")] - self.vm - .store + self.store .tracer .memory_change(offset as u32, buffer.len() as u32, buffer); Ok(()) diff --git a/src/vm/engine.rs b/src/vm/engine.rs index 9d559dba8..a9fa838d4 100644 --- a/src/vm/engine.rs +++ b/src/vm/engine.rs @@ -1,7 +1,10 @@ -use crate::{CallStack, RwasmExecutor, RwasmModule, RwasmStore, TrapCode, Value, ValueStack}; -use alloc::sync::Arc; +use crate::{ + vm::reusable_pool::{ItemConfig, ReusablePool, ReusablePoolConfig}, + CallStack, RwasmExecutor, RwasmModule, RwasmStore, TrapCode, Value, ValueStack, + N_DEFAULT_STACK_SIZE, N_MAX_STACK_SIZE, +}; +use alloc::{sync::Arc, vec::Vec}; use core::mem::take; -use smallvec::SmallVec; use spin::Mutex; /// Represents the core execution engine for managing the execution of a program, @@ -40,10 +43,53 @@ impl ExecutionEngine { } } -#[derive(Default)] +const ESTIMATED_CALL_DEPTH: usize = 1024; +const REUSABLE_POOL_KEEP: usize = 128; + +#[derive(Clone)] +pub struct ReusableStackConfig { + initial_len: usize, + maximum_len: usize, +} + +impl ReusableStackConfig { + pub fn new(initial_len: usize, maximum_len: usize) -> Self { + Self { + initial_len, + maximum_len, + } + } +} + +impl ItemConfig<(ValueStack, CallStack)> for ReusableStackConfig { + fn create_item(&self) -> (ValueStack, CallStack) { + ( + ValueStack::new(self.initial_len, self.maximum_len), + CallStack::default(), + ) + } + + fn reset_for_reuse(item: &mut (ValueStack, CallStack)) { + item.0.reset(); + item.1.reset(); + } +} + struct ExecutionEngineInner { - value_stack: SmallVec<[ValueStack; 8]>, - call_stack: SmallVec<[CallStack; 8]>, + acquired_stacks: Vec<(ValueStack, CallStack)>, + reusable_stacks: ReusablePool<(ValueStack, CallStack), ReusableStackConfig>, +} + +impl Default for ExecutionEngineInner { + fn default() -> Self { + Self { + acquired_stacks: Vec::with_capacity(ESTIMATED_CALL_DEPTH), + reusable_stacks: ReusablePool::new(ReusablePoolConfig::new( + REUSABLE_POOL_KEEP, + ReusableStackConfig::new(N_DEFAULT_STACK_SIZE, N_MAX_STACK_SIZE), + )), + } + } } impl ExecutionEngineInner { @@ -55,22 +101,19 @@ impl ExecutionEngineInner { params: &[Value], result: &mut [Value], ) -> Result<(), TrapCode> { - self.value_stack.push(ValueStack::default()); - self.call_stack.push(CallStack::default()); - let mut executor = RwasmExecutor::entrypoint( - &module, - self.value_stack.last_mut().unwrap(), - self.call_stack.last_mut().unwrap(), - store, - ); + let (value_stack, call_stack) = self.reusable_stacks.reuse_or_new(); + self.acquired_stacks.push((value_stack, call_stack)); + let (value_stack_ref, call_stack_ref) = self.acquired_stacks.last_mut().unwrap(); + let mut executor = + RwasmExecutor::entrypoint(&module, value_stack_ref, call_stack_ref, store); match executor.run(params, result) { Err(TrapCode::InterruptionCalled) => { store.resumable_context = Some((executor.ip, executor.sp)); Err(TrapCode::InterruptionCalled) } res => { - self.value_stack.pop().unwrap(); - self.call_stack.pop().unwrap(); + let stacks = self.acquired_stacks.pop().unwrap(); + self.reusable_stacks.recycle(stacks); res } } @@ -84,23 +127,20 @@ impl ExecutionEngineInner { params: &[Value], result: &mut [Value], ) -> Result<(), TrapCode> { - let (value_stack, call_stack) = ( - self.value_stack.last_mut().unwrap(), - self.call_stack.last_mut().unwrap(), - ); + let (value_stack_ref, call_stack_ref) = self.acquired_stacks.last_mut().unwrap(); let (ip, sp) = take(&mut store.resumable_context).unwrap_or_else(|| { unreachable!("resume calling without a remaining call stack"); }); - let mut executor = RwasmExecutor::new(&module, value_stack, sp, call_stack, ip, store); + let mut executor = + RwasmExecutor::new(&module, value_stack_ref, sp, call_stack_ref, ip, store); match executor.run(params, result) { Err(TrapCode::InterruptionCalled) => { store.resumable_context = Some((executor.ip, executor.sp)); Err(TrapCode::InterruptionCalled) } res => { - // TODO: Recycle stack - self.value_stack.pop().unwrap(); - self.call_stack.pop().unwrap(); + let value_stack = self.acquired_stacks.pop().unwrap(); + self.reusable_stacks.recycle(value_stack); res } } diff --git a/src/vm/executor.rs b/src/vm/executor.rs index 6711f2b31..9c61aa067 100644 --- a/src/vm/executor.rs +++ b/src/vm/executor.rs @@ -234,6 +234,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[cfg(feature = "debug-print")] self.debug_print(&instr); exec_opcode!(self, instr, break Ok(())); + #[cfg(feature = "test-build")] self.value_stack.check_max_stack_height(self.sp); }; // trap halts the execution, we need to clear the stack @@ -284,12 +285,6 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[cfg(feature = "tracing")] pub fn step(&mut self) -> Result { - if !self - .ip - .is_valid((self.module.code_section.instr.last().unwrap()) as *const Opcode as u64) - { - return Err(TrapCode::UnreachableCodeReached); - }; let instr = self.ip.get(); self.trace_instr_pre(&instr); let mut wrapper = |instr: Opcode| -> Result { @@ -305,7 +300,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { fn trace_instr_pre(&mut self, instr: &Opcode) { self.store.tracer.state.next_cycle(); let pc = self.program_counter(); - let memory_size: u32 = self.store.global_memory.current_pages().into(); + let memory_size: u32 = self.store.get_global_memory().current_pages().into(); let consumed_fuel = self.store.fuel_consumed(); self.store.tracer.pre_opcode_state(pc, self.sp, *instr); } @@ -313,11 +308,10 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[cfg(feature = "tracing")] fn trace_instr_post(&mut self, instr: &Opcode, trap_code: Option) { // TODO(wangyao): "track trap codes" - let sp = self.sp.to_relative_address(); - let pc = self.program_counter(); - let stack = self.value_stack.dump_stack(self.sp); + self.value_stack.sync_stack_ptr(self.sp); + let stack = self.value_stack.dump_stack(); self.store.tracer.post_opcode_state(pc, sp, stack); } @@ -365,7 +359,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { ) -> Result, ) -> Result<(), TrapCode> { self.sp.try_eval_top(|address| { - let memory = self.store.global_memory.data(); + let memory = self.store.get_global_memory().data(); let value = load_extend(memory, address, offset)?; Ok(value) })?; @@ -386,10 +380,11 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[allow(unused_variables)] len: u32, ) -> Result<(), TrapCode> { let (address, value) = self.sp.pop2(); - let memory = self.store.global_memory.data_mut(); + let memory = self.store.get_global_memory().data_mut(); store_wrap(memory, address, offset, value)?; #[cfg(feature = "tracing")] { + let memory = memory.to_vec(); let base_address = offset + u32::from(address); self.store.tracer.memory_change( base_address, diff --git a/src/vm/executor/alu.rs b/src/vm/executor/alu.rs index 54a672204..6f18ec085 100644 --- a/src/vm/executor/alu.rs +++ b/src/vm/executor/alu.rs @@ -65,6 +65,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { fn visit_i32_rotr(i32_rotr); } + #[inline(always)] pub(crate) fn visit_i32_mul64(&mut self) { let (lhs, rhs) = self.sp.pop2(); let res = lhs.as_i64().wrapping_mul(rhs.as_i64()); @@ -72,6 +73,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { self.ip.add(1); } + #[inline(always)] pub(crate) fn visit_i32_add64(&mut self) { let (lhs, rhs) = self.sp.pop2(); let res = lhs.as_i64().wrapping_add(rhs.as_i64()); diff --git a/src/vm/executor/control_flow.rs b/src/vm/executor/control_flow.rs index b825f2f67..9c996baf6 100644 --- a/src/vm/executor/control_flow.rs +++ b/src/vm/executor/control_flow.rs @@ -89,7 +89,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let instr_ref: u32 = self .store .tables - .get(&table) + .get(table as usize) .expect("rwasm: unresolved table index") .get_untyped(func_index) .ok_or(TrapCode::TableOutOfBounds)? @@ -144,7 +144,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let instr_ref = self .store .tables - .get(&table) + .get(table as usize) .expect("rwasm: unresolved table index") .get_untyped(func_index) .map(|v| v.as_u32()) diff --git a/src/vm/executor/fpu.rs b/src/vm/executor/fpu.rs index 1d66f5d6f..7a823b26a 100644 --- a/src/vm/executor/fpu.rs +++ b/src/vm/executor/fpu.rs @@ -63,7 +63,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[inline(always)] pub(crate) fn visit_f32_load(&mut self, address_offset: AddressOffset) -> Result<(), TrapCode> { self.sp.try_eval_top(|address| { - let memory = self.store.global_memory.data(); + let memory = self.store.get_global_memory().data(); let value = UntypedValue::f32_load(memory, address, address_offset)?; Ok(value) })?; @@ -74,7 +74,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[inline(always)] pub(crate) fn visit_f64_load(&mut self, address_offset: AddressOffset) -> Result<(), TrapCode> { let address = self.sp.pop_i32(); - let memory = self.store.global_memory.data(); + let memory = self.store.get_global_memory().data(); let value = UntypedValue::load_typed::(memory, address as u32, address_offset)?; self.sp.push_f64(value); self.ip.add(1); @@ -87,7 +87,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { address_offset: AddressOffset, ) -> Result<(), TrapCode> { let (address, value) = self.sp.pop2(); - let memory = self.store.global_memory.data_mut(); + let memory = self.store.get_global_memory().data_mut(); UntypedValue::f32_store(memory, address, address_offset, value)?; #[cfg(feature = "tracing")] { @@ -109,7 +109,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { ) -> Result<(), TrapCode> { let value = self.sp.pop_f64(); let address = self.sp.pop_i32(); - let memory = self.store.global_memory.data_mut(); + let memory = self.store.get_global_memory().data_mut(); UntypedValue::store_typed(memory, address as u32, address_offset, value)?; self.ip.add(1); Ok(()) diff --git a/src/vm/executor/memory.rs b/src/vm/executor/memory.rs index 8fe6f65f2..78e27ebed 100644 --- a/src/vm/executor/memory.rs +++ b/src/vm/executor/memory.rs @@ -40,7 +40,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[inline(always)] pub(crate) fn visit_memory_size(&mut self) { - let result: u32 = self.store.global_memory.current_pages().into(); + let result: u32 = self.store.get_global_memory().current_pages().into(); self.sp.push_as(result); self.ip.add(1); } @@ -58,7 +58,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { }; let new_pages = self .store - .global_memory + .get_global_memory() .grow(delta) .map(u32::from) .unwrap_or(u32::MAX); @@ -75,16 +75,19 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let byte = u8::from(val); let memory = self .store - .global_memory + .get_global_memory() .data_mut() .get_mut(offset..) .and_then(|memory| memory.get_mut(..n)) .ok_or(TrapCode::MemoryOutOfBounds)?; memory.fill(byte); #[cfg(feature = "tracing")] - self.store - .tracer - .memory_change(offset as u32, n as u32, memory); + { + let memory = memory.to_vec(); + self.store + .tracer + .memory_change(offset as u32, n as u32, &memory); + } self.ip.add(1); Ok(()) } @@ -96,7 +99,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let src_offset = i32::from(s) as usize; let dst_offset = i32::from(d) as usize; // these accesses just perform the bound checks required by the Wasm spec. - let data = self.store.global_memory.data_mut(); + let data = self.store.get_global_memory().data_mut(); data.get(src_offset..) .and_then(|memory| memory.get(..n)) .ok_or(TrapCode::MemoryOutOfBounds)?; @@ -105,11 +108,14 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { .ok_or(TrapCode::MemoryOutOfBounds)?; data.copy_within(src_offset..src_offset.wrapping_add(n), dst_offset); #[cfg(feature = "tracing")] - self.store.tracer.memory_change( - dst_offset as u32, - n as u32, - &data[dst_offset..(dst_offset + n)], - ); + { + let data = data.to_vec(); + self.store.tracer.memory_change( + dst_offset as u32, + n as u32, + &data[dst_offset..(dst_offset + n)], + ); + } self.ip.add(1); Ok(()) } @@ -123,8 +129,6 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { .store .empty_data_segments .get(data_segment_idx as usize) - .as_deref() - .copied() .unwrap_or(false); let (d, s, n) = self.sp.pop3(); let n = i32::from(n) as usize; @@ -132,7 +136,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let dst_offset = i32::from(d) as usize; let memory = self .store - .global_memory + .get_global_memory() .data_mut() .get_mut(dst_offset..) .and_then(|memory| memory.get_mut(..n)) @@ -147,9 +151,12 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { .ok_or(TrapCode::MemoryOutOfBounds)?; memory.copy_from_slice(data); #[cfg(feature = "tracing")] - self.store - .tracer - .global_memory(dst_offset as u32, n as u32, memory); + { + let memory = memory.to_vec(); + self.store + .tracer + .global_memory(dst_offset as u32, n as u32, &memory); + } self.ip.add(1); Ok(()) } diff --git a/src/vm/executor/system.rs b/src/vm/executor/system.rs index 6e3211549..adb395e2e 100644 --- a/src/vm/executor/system.rs +++ b/src/vm/executor/system.rs @@ -1,4 +1,7 @@ -use crate::{BlockFuel, GlobalIdx, MaxStackHeight, RwasmExecutor, SignatureIdx, Store, TrapCode}; +use crate::{ + BlockFuel, GlobalIdx, MaxStackHeight, RwasmExecutor, SignatureIdx, Store, TrapCode, + UntypedValue, +}; impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[inline(always)] @@ -48,7 +51,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let global_value = self .store .global_variables - .get(&global_idx) + .get(global_idx as usize) .copied() .unwrap_or_default(); self.sp.push(global_value); @@ -57,8 +60,19 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { #[inline(always)] pub(crate) fn visit_global_set(&mut self, global_idx: GlobalIdx) { - let new_value = self.sp.pop(); - self.store.global_variables.insert(global_idx, new_value); + let new_value: UntypedValue = self.sp.pop(); + let expected_cap = global_idx as usize + 1; + let len = self.store.global_variables.len(); + if expected_cap > len { + self.store.global_variables.reserve(expected_cap - len); + let default_elements_count = global_idx as usize - len; + self.store + .global_variables + .extend(core::iter::repeat(UntypedValue::default()).take(default_elements_count)); + self.store.global_variables.push(new_value); + } else { + self.store.global_variables[global_idx as usize] = new_value; + }; self.ip.add(1); } } diff --git a/src/vm/executor/table.rs b/src/vm/executor/table.rs index b95ab420b..514ecd0fd 100644 --- a/src/vm/executor/table.rs +++ b/src/vm/executor/table.rs @@ -6,7 +6,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let table_size = self .store .tables - .get(&table_idx) + .get(table_idx as usize) .expect("rwasm: unresolved table segment") .size(); self.sp.push_as(table_size); @@ -17,11 +17,21 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { pub(crate) fn visit_table_grow(&mut self, table_idx: TableIdx) -> Result<(), TrapCode> { let (init, delta) = self.sp.pop2(); let delta: u32 = delta.into(); - let table = self - .store - .tables - .entry(table_idx) - .or_insert_with(TableEntity::new); + let expected_capacity = table_idx as usize + 1; + if self.store.tables.capacity() < expected_capacity { + self.store + .tables + .reserve(expected_capacity - self.store.tables.capacity()); + } + let table = if self.store.tables.len() > table_idx as usize { + &mut self.store.tables[table_idx as usize] + } else { + self.store.tables.extend( + core::iter::repeat_with(|| TableEntity::new()) + .take(expected_capacity - self.store.tables.len()), + ); + self.store.tables.last_mut().unwrap() + }; let result = table.grow_untyped(delta, init); self.sp.push_as(result); #[cfg(feature = "tracing")] @@ -37,7 +47,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let (i, val, n) = self.sp.pop3(); self.store .tables - .get_mut(&table_idx) + .get_mut(table_idx as usize) .expect("rwasm: missing table") .fill_untyped(i.into(), val, n.into())?; self.ip.add(1); @@ -50,7 +60,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let value = self .store .tables - .get_mut(&table_idx) + .get_mut(table_idx as usize) .expect("rwasm: missing table") .get_untyped(index.into()) .ok_or(TrapCode::TableOutOfBounds)?; @@ -64,7 +74,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let (index, value) = self.sp.pop2(); self.store .tables - .get_mut(&table_idx) + .get_mut(table_idx as usize) .expect("rwasm: missing table") .set_untyped(index.into(), value) .map_err(|_| TrapCode::TableOutOfBounds)?; @@ -91,14 +101,14 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let [src, dst] = self .store .tables - .get_many_mut([&src_table_idx, &dst_table_idx]) - .map(|v| v.expect("rwasm: unresolved table segment")); + .get_disjoint_mut([src_table_idx as usize, dst_table_idx as usize]) + .expect("rwasm: unresolved table segment"); TableEntity::copy(dst, dst_index, src, src_index, len)?; } else { let src = self .store .tables - .get_mut(&src_table_idx) + .get_mut(src_table_idx as usize) .expect("rwasm: unresolved table segment"); src.copy_within(dst_index, src_index, len)?; } @@ -131,8 +141,6 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { .store .empty_elem_segments .get(element_segment_idx as usize) - .as_deref() - .copied() .unwrap_or(false); let mut module_elements_section = &self.module.elem_section[..]; @@ -142,7 +150,7 @@ impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { let table = self .store .tables - .get_mut(&table_idx) + .get_mut(table_idx as usize) .expect("rwasm: missing table"); table.init_untyped(dst_index, module_elements_section, src_index, len)?; diff --git a/src/vm/memory.rs b/src/vm/memory.rs index 9fc1d481f..f70d77fcf 100644 --- a/src/vm/memory.rs +++ b/src/vm/memory.rs @@ -1,4 +1,7 @@ use crate::types::{Pages, TrapCode, N_MAX_MEMORY_PAGES}; +#[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] +use crate::vm::memory_unix::rwmem::RwMemory; +#[cfg(not(all(feature = "unix-memory", unix, not(target_arch = "wasm32"))))] use bytes::BytesMut; /// Shared linear memory backing store for a running module. @@ -6,7 +9,11 @@ use bytes::BytesMut; /// The buffer is pre-reserved and grown in page-sized steps. pub struct GlobalMemory { /// Underlying byte buffer for the linear memory. + #[cfg(not(all(feature = "unix-memory", unix, not(target_arch = "wasm32"))))] pub shared_memory: BytesMut, + /// + #[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] + pub shared_memory_unix: RwMemory, /// Current logical size of the linear memory in pages. pub current_pages: Pages, } @@ -15,20 +22,36 @@ const MEMORY_MAX_PAGES: Pages = Pages::new_unchecked(N_MAX_MEMORY_PAGES * 2); impl GlobalMemory { pub fn new(initial_pages: Pages) -> Self { - let initial_len = initial_pages - .to_bytes() - .expect("rwasm: not supported target pointer width"); - let maximum_len = MEMORY_MAX_PAGES - .to_bytes() - .expect("rwasm: not supported target pointer width"); - if initial_len > maximum_len { - unreachable!("rwasm: initial memory size is greater than the maximum"); + #[cfg(not(all(feature = "unix-memory", unix, not(target_arch = "wasm32"))))] + { + let initial_len = initial_pages + .to_bytes() + .expect("rwasm: not supported target pointer width"); + let maximum_len = MEMORY_MAX_PAGES + .to_bytes() + .expect("rwasm: not supported target pointer width"); + debug_assert!( + initial_len <= maximum_len, + "rwasm: initial memory size is greater than the maximum" + ); + unsafe { core::hint::assert_unchecked(initial_len <= maximum_len) }; + let shared_memory = BytesMut::zeroed(initial_len); + Self { + shared_memory, + current_pages: initial_pages, + } } - let mut shared_memory = BytesMut::with_capacity(maximum_len); - shared_memory.resize(initial_len, 0); - Self { - shared_memory, - current_pages: initial_pages, + #[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] + { + let shared_memory_unix = crate::vm::memory_unix::rwmem::Memory::new( + initial_pages.into_inner(), + MEMORY_MAX_PAGES.into_inner(), + ) + .unwrap(); + Self { + shared_memory_unix, + current_pages: initial_pages, + } } } @@ -59,20 +82,50 @@ impl GlobalMemory { let new_size = desired_pages .to_bytes() .expect("rwasm: not supported target pointer width"); - assert!(new_size >= self.shared_memory.len()); - self.shared_memory.resize(new_size, 0); - self.current_pages = desired_pages; - Some(current_pages) + #[cfg(not(all(feature = "unix-memory", unix, not(target_arch = "wasm32"))))] + { + assert!(new_size >= self.shared_memory.len()); + self.shared_memory.resize(new_size, 0); + self.current_pages = desired_pages; + Some(current_pages) + } + #[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] + { + assert!(new_size >= self.shared_memory_unix.committed_len()); + if self + .shared_memory_unix + .grow(additional.into_inner()) + .is_err() + { + return None; + }; + self.current_pages = desired_pages; + Some(current_pages) + } } /// Returns a shared slice to the bytes underlying to the byte buffer. pub fn data(&self) -> &[u8] { - self.shared_memory.as_ref() + #[cfg(not(all(feature = "unix-memory", unix, not(target_arch = "wasm32"))))] + { + self.shared_memory.as_ref() + } + #[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] + { + self.shared_memory_unix.as_slice() + } } /// Returns an exclusive slice to the bytes underlying to the byte buffer. pub fn data_mut(&mut self) -> &mut [u8] { - self.shared_memory.as_mut() + #[cfg(not(all(feature = "unix-memory", unix, not(target_arch = "wasm32"))))] + { + self.shared_memory.as_mut() + } + #[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] + { + self.shared_memory_unix.as_slice_mut() + } } /// Reads `n` bytes from `memory[offset..offset+n]` into `buffer` diff --git a/src/vm/memory_unix.rs b/src/vm/memory_unix.rs new file mode 100644 index 000000000..8724530ae --- /dev/null +++ b/src/vm/memory_unix.rs @@ -0,0 +1,301 @@ +#[cfg(all(feature = "unix-memory", unix))] +pub mod rwmem { + use core::ptr::NonNull; + use core::{ + mem, ptr, + sync::atomic::{AtomicUsize, Ordering}, + }; + use libc::{ + c_void, madvise, mmap, mprotect, munmap, sigaction, sigaltstack, sigemptyset, siginfo_t, + stack_t, MADV_DONTNEED, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE, PROT_READ, + PROT_WRITE, SA_ONSTACK, SA_SIGINFO, SIGSEGV, + }; + + pub const WASM_PAGE: usize = 64 * 1024; + pub type Pages = u32; // 32-bit pointers / sizes in pages + + #[inline] + fn ceil_to_pages(len: usize) -> usize { + (len + WASM_PAGE - 1) / WASM_PAGE * WASM_PAGE + } + + /// Linear memory reservation with front/back guards. + pub struct GuardedHeap { + base: NonNull, // points to start of HEAP (after front guard) + reserved_len: usize, // total HEAP bytes reserved (without guards) + committed_len: AtomicUsize, // bytes currently RW (multiple of page) + front_guard: usize, // guard size before heap + back_guard: usize, // guard size after heap + } + + impl GuardedHeap { + /// Reserve `[GUARD | HEAP | GUARD]` and commit `initial_pages`. + /// `max_pages` caps growth; both guards are at least one page. + pub unsafe fn new(initial_pages: Pages, max_pages: Pages) -> Result { + let guard = WASM_PAGE; // 64 KiB guard is enough to turn OOB into SIGSEGV fast + let heap_res = (max_pages as usize) * WASM_PAGE; + let map_len = guard + heap_res + guard; + + let addr = mmap( + ptr::null_mut(), + map_len, + PROT_NONE, + MAP_PRIVATE | MAP_ANON, + -1, + 0, + ); + if addr == MAP_FAILED { + return Err("mmap reserve failed"); + } + + // Commit the initial part of HEAP as RW + let init_len = ceil_to_pages((initial_pages as usize) * WASM_PAGE); + if init_len > 0 { + let heap_ptr = (addr as usize + guard) as *mut c_void; + if mprotect(heap_ptr, init_len, PROT_READ | PROT_WRITE) != 0 { + let _ = munmap(addr, map_len); + return Err("mprotect initial commit failed"); + } + // Ensure zero pages on first touch (they already are zero, but this keeps the story) + let _ = madvise(heap_ptr, init_len, MADV_DONTNEED); + } + + Ok(Self { + base: NonNull::new_unchecked((addr as usize + guard) as *mut u8), + reserved_len: heap_res, + committed_len: AtomicUsize::new(init_len), + front_guard: guard, + back_guard: guard, + }) + } + + /// Pointer to start of linear memory. Your JIT/interpreter can do `base.add(u32_offset)`. + #[inline] + pub fn base(&self) -> *mut u8 { + self.base.as_ptr() + } + + /// Bytes currently committed RW. + #[inline] + pub fn committed_len(&self) -> usize { + self.committed_len.load(Ordering::Relaxed) + } + + /// Max bytes we can grow to (reserved). + #[inline] + pub fn reserved_len(&self) -> usize { + self.reserved_len + } + + /// Grow by `delta_pages`. Newly committed pages are logically zero via DONTNEED. + pub unsafe fn grow(&self, delta_pages: Pages) -> Result<(), &'static str> { + if delta_pages == 0 { + return Ok(()); + } + let add = (delta_pages as usize) * WASM_PAGE; + + let old = self.committed_len.load(Ordering::Relaxed); + let new = old.checked_add(add).ok_or("overflow")?; + if new > self.reserved_len { + return Err("exceeds reserved"); + } + + let start = self.base.as_ptr().add(old) as *mut c_void; + if mprotect(start, add, PROT_READ | PROT_WRITE) != 0 { + return Err("mprotect grow failed"); + } + // Make the kernel hand zero pages lazily on next touch + let _ = madvise(start, add, MADV_DONTNEED); + + self.committed_len.store(new, Ordering::Release); + Ok(()) + } + + /// Zero-and-forget: turn the committed range back into “fresh zero” without unmapping. + pub unsafe fn recycle(&self) { + let len = self.committed_len(); + if len == 0 { + return; + } + let ptr = self.base.as_ptr() as *mut c_void; + // Keep writable; just tell kernel we don't need contents. + let _ = madvise(ptr, len, MADV_DONTNEED); + } + } + + impl Drop for GuardedHeap { + fn drop(&mut self) { + unsafe { + let map_base = (self.base.as_ptr() as usize - self.front_guard) as *mut c_void; + let map_len = self.front_guard + self.reserved_len + self.back_guard; + let _ = munmap(map_base, map_len); + } + } + } + + // ===== Trap trampoline (SIGSEGV -> Result::Err) ===== + + // Per-thread jump buffer. We only need an address to jump back to. + // We use `libc::sigsetjmp/siglongjmp` because unwinding across a signal is UB. + #[repr(C)] + struct JmpBuf { + buf: [libc::c_int; 27], + } // typical glibc size; we never touch fields + + thread_local! { + static TLS_JMP: Jmp = Jmp::new(); + } + + struct Jmp { + buf: JmpBuf, + } + impl Jmp { + const fn new() -> Self { + Self { + buf: JmpBuf { buf: [0; 27] }, + } + } + } + + static mut SEGV_INSTALLED: bool = false; + + extern "C" fn segv_handler(_sig: libc::c_int, _si: *mut siginfo_t, _ctx: *mut c_void) { + // Jump back to the last `run_with_memory_trap` call on this thread. + TLS_JMP.with(|j| unsafe { setjmp::siglongjmp(j.buf.buf.as_ptr() as *mut _, 1) }); + } + + /// Call `f()` with a SIGSEGV→Err trampoline. + /// Return `Ok` if no fault; `Err` if any memory fault happened inside. + pub fn run_with_memory_trap(f: F) -> Result + where + F: FnOnce() -> T, + { + unsafe { + // One-time global install of SIGSEGV handler + altstack (so we can handle guard faults reliably). + if !SEGV_INSTALLED { + // alt stack (32 KiB is plenty) + const ALT: usize = 32 * 1024; + static mut ALTSTACK: *mut u8 = ptr::null_mut(); + if ALTSTACK.is_null() { + ALTSTACK = mmap( + ptr::null_mut(), + ALT, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0, + ) as *mut u8; + } + let ss = stack_t { + ss_sp: ALTSTACK as *mut c_void, + ss_flags: 0, + ss_size: ALT, + }; + if sigaltstack(&ss, ptr::null_mut()) != 0 { + return Err(()); + } + + let mut sa: sigaction = mem::zeroed(); + sa.sa_sigaction = segv_handler as usize; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&mut sa.sa_mask); + if libc::sigaction(SIGSEGV, &sa, ptr::null_mut()) != 0 { + return Err(()); + } + SEGV_INSTALLED = true; + } + } + + // Establish jump point. + let jumped = + TLS_JMP.with(|j| unsafe { setjmp::sigsetjmp(j.buf.buf.as_ptr() as *mut _, 1) }); + if jumped != 0 { + // We got here via siglongjmp from the handler => memory fault + return Err(()); + } + // Normal execution + let out = f(); + Ok(out) + } + + // Public facade you’ll likely call from your runtime: + pub struct RwMemory { + pub heap: GuardedHeap, + } + + impl RwMemory { + pub fn new(initial_pages: Pages, max_pages: Pages) -> Result { + Ok(Self { + heap: unsafe { GuardedHeap::new(initial_pages, max_pages)? }, + }) + } + #[inline] + pub fn base(&self) -> *mut u8 { + self.heap.base() + } + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.base(), self.committed_len()) } + } + #[inline] + pub fn as_slice_mut(&self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.base(), self.committed_len()) } + } + #[inline] + pub fn committed_len(&self) -> usize { + self.heap.committed_len() + } + #[inline] + pub fn reserved_len(&self) -> usize { + self.heap.reserved_len() + } + #[inline] + pub fn grow(&self, delta_pages: Pages) -> Result<(), &'static str> { + unsafe { self.heap.grow(delta_pages) } + } + #[inline] + pub unsafe fn recycle(&self) { + self.heap.recycle() + } + } + + pub use run_with_memory_trap as with_trap; + pub use GuardedHeap as Heap; + pub use RwMemory as Memory; +} + +#[cfg(test)] +mod tests { + use crate::vm::memory_unix::rwmem::{RwMemory, WASM_PAGE}; + + #[test] + fn test_rw_memory() { + let initial_pages = 1; + let current_pages = initial_pages; + let delta_pages = 1; + let max_pages = 1024; + let mem = RwMemory::new(initial_pages, max_pages).unwrap(); + let mut value_idx = 0; + let mut value_byte = 33; + let mut value_slice = &[3, 2, 1, 2, 3]; + mem.as_slice_mut()[value_idx] = value_byte; + assert_eq!(mem.as_slice()[value_idx], value_byte); + mem.as_slice_mut()[value_idx..value_idx + value_slice.len()].copy_from_slice(value_slice); + assert_eq!( + &mem.as_slice()[value_idx..value_idx + value_slice.len()], + value_slice + ); + mem.grow(delta_pages).unwrap(); + // current_pages += delta_pages; + value_idx = WASM_PAGE; + value_byte = 22; + value_slice = &[3, 2, 1, 2, 3]; + mem.as_slice_mut()[value_idx] = value_byte; + assert_eq!(mem.as_slice_mut()[value_idx], value_byte); + mem.as_slice_mut()[value_idx..value_idx + value_slice.len()].copy_from_slice(value_slice); + assert_eq!( + &mem.as_slice()[value_idx..value_idx + value_slice.len()], + value_slice + ); + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index fec7f1268..584768461 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -5,6 +5,9 @@ mod executor; mod handler; mod instr_ptr; mod memory; +#[cfg(all(feature = "unix-memory", unix, not(target_arch = "wasm32")))] +mod memory_unix; +mod reusable_pool; mod store; mod table_entity; #[cfg(feature = "tracing")] diff --git a/src/vm/reusable_pool.rs b/src/vm/reusable_pool.rs new file mode 100644 index 000000000..916f456df --- /dev/null +++ b/src/vm/reusable_pool.rs @@ -0,0 +1,57 @@ +use alloc::vec::Vec; +use core::marker::PhantomData; + +pub trait ItemConfig: Clone + Sized { + fn create_item(&self) -> ITEM; + fn reset_for_reuse(item: &mut ITEM); +} + +#[derive(Clone)] +pub struct ReusablePoolConfig> { + pub keep: usize, + pub item_config: CONFIG, + pub _phantom: PhantomData, +} + +impl> ReusablePoolConfig { + pub fn new(keep: usize, item_config: CONFIG) -> Self { + Self { + keep, + item_config, + _phantom: PhantomData::default(), + } + } +} + +#[derive(Clone)] +pub struct ReusablePool> { + items: Vec, + item_config: CONFIG, + keep: usize, +} + +impl> ReusablePool { + pub fn new(config: ReusablePoolConfig) -> Self { + Self { + items: Vec::new(), + item_config: config.item_config, + keep: config.keep, + } + } + + #[inline] + pub fn reuse_or_new(&mut self) -> ITEM { + match self.items.pop() { + Some(item) => item, + None => self.item_config.create_item(), + } + } + + #[inline] + pub fn recycle(&mut self, mut item: ITEM) { + if self.items.len() < self.keep { + CONFIG::reset_for_reuse(&mut item); + self.items.push(item); + } + } +} diff --git a/src/vm/store.rs b/src/vm/store.rs index 3b1c06faf..5ac6e13c2 100644 --- a/src/vm/store.rs +++ b/src/vm/store.rs @@ -1,10 +1,8 @@ use crate::{ - FuelConfig, GlobalIdx, GlobalMemory, ImportLinker, InstructionPtr, Pages, SignatureIdx, Store, - SyscallHandler, TableEntity, TableIdx, TrapCode, UntypedValue, ValueStackPtr, + bitvec_inlined::BitVecInlined as BV, FuelConfig, GlobalMemory, ImportLinker, InstructionPtr, + Pages, SignatureIdx, Store, SyscallHandler, TableEntity, TrapCode, UntypedValue, ValueStackPtr, }; -use alloc::sync::Arc; -use bitvec::{order::Lsb0, vec::BitVec}; -use hashbrown::HashMap; +use alloc::{sync::Arc, vec::Vec}; /// Host-side store that holds memory, tables, globals and host context for an rwasm instance. /// It also tracks fuel for metering and provides access to imported functions and syscalls. @@ -13,19 +11,19 @@ pub struct RwasmStore { /// Total amount of fuel consumed by the currently running instance. pub(crate) consumed_fuel: u64, /// The linear memory shared by the running module and the host. - pub(crate) global_memory: GlobalMemory, + pub(crate) global_memory: Option, /// User-defined context available to host functions and syscalls. pub(crate) context: T, /// The last used signature index used for validating indirect calls. pub(crate) last_signature: Option, /// Runtime-managed tables (may differ from compile-time layout due to mutations). - pub(crate) tables: HashMap, + pub(crate) tables: Vec, /// Runtime values of mutable and immutable globals. - pub(crate) global_variables: HashMap, + pub(crate) global_variables: Vec, /// Bitset tracking which data segments have been consumed/emptied. - pub(crate) empty_data_segments: BitVec, + pub(crate) empty_data_segments: BV<2>, /// Bitset tracking which element segments have been consumed/emptied. - pub(crate) empty_elem_segments: BitVec, + pub(crate) empty_elem_segments: BV<2>, /// Dispatcher for system calls made by the guest. pub(crate) syscall_handler: SyscallHandler, /// Linker that resolves imports to host functions/globals. @@ -39,7 +37,6 @@ pub struct RwasmStore { pub tracer: crate::Tracer, } -#[cfg(feature = "std")] impl Default for RwasmStore { fn default() -> Self { Self::new( @@ -53,12 +50,12 @@ impl Default for RwasmStore { impl Store for RwasmStore { fn memory_read(&mut self, offset: usize, buffer: &mut [u8]) -> Result<(), TrapCode> { - self.global_memory.read(offset, buffer)?; + self.get_global_memory().read(offset, buffer)?; Ok(()) } fn memory_write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), TrapCode> { - self.global_memory.write(offset, buffer)?; + self.get_global_memory().write(offset, buffer)?; #[cfg(feature = "tracing")] self.tracer .memory_change(offset as u32, buffer.len() as u32, buffer); @@ -96,10 +93,9 @@ impl RwasmStore { syscall_handler: SyscallHandler, fuel_config: FuelConfig, ) -> Self { - let global_memory = GlobalMemory::new(Pages::default()); Self { consumed_fuel: 0, - global_memory, + global_memory: None, context, #[cfg(feature = "tracing")] tracer: crate::Tracer::default(), @@ -107,34 +103,29 @@ impl RwasmStore { tables: Default::default(), last_signature: None, syscall_handler, - empty_data_segments: BitVec::EMPTY, - empty_elem_segments: BitVec::EMPTY, + empty_data_segments: BV::EMPTY, + empty_elem_segments: BV::EMPTY, import_linker, resumable_context: None, fuel_config, } } + pub fn get_global_memory(&mut self) -> &mut GlobalMemory { + if self.global_memory.is_none() { + self.global_memory = Some(GlobalMemory::new(Pages::default())) + } + self.global_memory.as_mut().unwrap() + } + /// Resets the state of the current execution context. pub fn reset(&mut self, keep_flags: bool) { // reset consumed fuel to 0 self.consumed_fuel = 0; // we might want to keep data/elem flags between calls, it's required for e2e tests if !keep_flags { - // we don't do any assumptions regarding how data segments are used, - // maybe there is a way to optimize reuse of bitset. - if self.empty_data_segments.len() <= size_of::() { - self.empty_data_segments.fill(false); - } else { - self.empty_data_segments = BitVec::::EMPTY; - } - // we don't do any assumptions regarding how tables are used inside the applications, - // so keep it always empty, probably there is an optimization here. - if self.empty_elem_segments.len() <= size_of::() { - self.empty_elem_segments.fill(false); - } else { - self.empty_elem_segments = BitVec::::EMPTY; - } + self.empty_data_segments = BV::EMPTY; + self.empty_elem_segments = BV::EMPTY; } // in case of a trap, we might have this flag remains active self.last_signature = None; diff --git a/src/vm/table_entity.rs b/src/vm/table_entity.rs index baea21196..db2f1be6b 100644 --- a/src/vm/table_entity.rs +++ b/src/vm/table_entity.rs @@ -1,6 +1,6 @@ use crate::{ types::{TrapCode, UntypedValue}, - N_MAX_TABLE_SIZE, + N_MAX_STACK_SIZE, }; use alloc::vec::Vec; @@ -17,7 +17,7 @@ impl TableEntity { /// /// If `init` does not match the [`TableType`] element type. pub fn new() -> Self { - let elements = Vec::with_capacity(N_MAX_TABLE_SIZE as usize); + let elements = Vec::new(); Self { elements } } @@ -44,7 +44,7 @@ impl TableEntity { let Some(desired) = current.checked_add(delta) else { return u32::MAX; }; - if desired as usize > self.elements.capacity() { + if desired as usize > N_MAX_STACK_SIZE { return u32::MAX; } self.elements.resize(desired as usize, init.to_bits()); diff --git a/src/vm/tracer/mem_index.rs b/src/vm/tracer/mem_index.rs index 4b15db772..c03f21458 100644 --- a/src/vm/tracer/mem_index.rs +++ b/src/vm/tracer/mem_index.rs @@ -1,5 +1,5 @@ use crate::{ - N_DEFAULT_STACK_SIZE, N_MAX_DATA_SEGMENTS_BITS, N_MAX_RECURSION_DEPTH, N_MAX_TABLES, + N_MAX_DATA_SEGMENTS_BITS, N_MAX_RECURSION_DEPTH, N_MAX_STACK_SIZE, N_MAX_TABLES, N_MAX_TABLE_SIZE, }; @@ -15,7 +15,7 @@ pub const UNIT: u32 = 4; // size_of() / size_of() /// The stack starts with and invalid position, and every element in the stack has an index less /// than SP_START. -pub const SP_START: u32 = N_DEFAULT_STACK_SIZE as u32 * UNIT + UNIT; +pub const SP_START: u32 = N_MAX_STACK_SIZE as u32 * UNIT + UNIT; /// This is the index when the stack reaches the max length. So every valid index for the stack is /// >0. Making the index of a stack element strictly larger than 0 makes circuit checking this bound diff --git a/src/vm/tracer/mod.rs b/src/vm/tracer/mod.rs index 15f663453..f555caf96 100644 --- a/src/vm/tracer/mod.rs +++ b/src/vm/tracer/mod.rs @@ -81,8 +81,8 @@ pub struct Tracer { pub fns_meta: Vec, pub global_variables: Vec, pub nested_calls: u32, - pub memory_records: HashMap, - pub local_memory_event: HashMap, + pub memory_records: HashMap, + pub local_memory_event: HashMap, pub state: VMState, pub ip_max: u64, } @@ -131,7 +131,6 @@ impl Tracer { call_id: 0, memory_access, }; - println!("opcode _state{:?},", opcode_state); self.logs.push(opcode_state); } @@ -188,6 +187,7 @@ impl Tracer { pub fn record_mr(&mut self, ins: Opcode, sp: u32) -> MemoryAccessRecord { let length = opcode_stack_read(ins); let mut memory_access = MemoryAccessRecord::default(); + #[cfg(feature = "std")] println!( "op:{},length:{},memory_record{:?}", ins, length, self.memory_records @@ -195,6 +195,7 @@ impl Tracer { for idx in length..0 { let addr = sp - idx; + #[cfg(feature = "std")] println!("length in loop{},addr:{}", length, addr); let record = self.memory_records.entry(addr).or_insert(MemoryRecord { value: 0, @@ -269,6 +270,7 @@ impl Tracer { let op_state = self.logs.last_mut().unwrap(); op_state.memory_access.c = Some(MemoryRecordEnum::Write(write_record)); + #[cfg(feature = "std")] println!("op_state:memoeryaccess:{:?}", op_state.memory_access); } } diff --git a/src/vm/value_stack.rs b/src/vm/value_stack.rs index 34d0530d6..bb9cfe5d6 100644 --- a/src/vm/value_stack.rs +++ b/src/vm/value_stack.rs @@ -2,9 +2,8 @@ use crate::{ types::{TrapCode, UntypedValue}, ExternRef, FuncRef, I64ValueSplit, Value, F32, F64, N_DEFAULT_STACK_SIZE, N_MAX_STACK_SIZE, }; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use core::fmt::Debug; -use smallvec::{smallvec, SmallVec}; use wasmparser::ValType; /// The value stack used to execute Wasm bytecode. @@ -16,7 +15,7 @@ use wasmparser::ValType; #[derive(Clone)] pub struct ValueStack { /// All currently live stack entries. - entries: SmallVec<[UntypedValue; N_DEFAULT_STACK_SIZE]>, + entries: Vec, /// Index of the first free place in the stack. stack_ptr: usize, /// The maximum value stack height. @@ -27,6 +26,7 @@ pub struct ValueStack { /// will cause a stack overflow trap. maximum_len: usize, /// The maximum stack height + #[cfg(feature = "test-build")] max_stack_height: usize, } @@ -74,13 +74,15 @@ impl ValueStack { /// proper stack with an inexpensive fake one. pub fn empty() -> Self { Self { - entries: SmallVec::new(), + entries: Vec::new(), stack_ptr: 0, maximum_len: 0, + #[cfg(feature = "test-build")] max_stack_height: 0, } } + #[cfg(feature = "test-build")] pub fn max_stack_height(&self) -> usize { self.max_stack_height } @@ -134,11 +136,13 @@ impl ValueStack { let offset = new_sp.offset_from(self.base_ptr()); debug_assert!(offset >= 0, "stack underflow: {}", offset); self.stack_ptr = offset as usize; + #[cfg(feature = "test-build")] if self.stack_ptr > self.max_stack_height { self.max_stack_height = self.stack_ptr; } } + #[cfg(feature = "test-build")] pub(crate) fn check_max_stack_height(&mut self, sp: ValueStackPtr) { let offset = sp.offset_from(self.base_ptr()); debug_assert!(offset >= 0, "stack underflow: {}", offset); @@ -167,11 +171,12 @@ impl ValueStack { initial_len <= maximum_len, "the initial value stack length is greater than the maximum value stack length", ); - let entries = smallvec![UntypedValue::default(); initial_len]; + let entries = vec![UntypedValue::default(); initial_len]; Self { entries, stack_ptr: 0, maximum_len, + #[cfg(feature = "test-build")] max_stack_height: 0, } } @@ -218,6 +223,7 @@ impl ValueStack { pub fn push(&mut self, entry: UntypedValue) { *self.get_release_unchecked_mut(self.stack_ptr) = entry; self.stack_ptr += 1; + #[cfg(feature = "test-build")] if self.stack_ptr > self.max_stack_height { self.max_stack_height = self.stack_ptr; } @@ -282,6 +288,7 @@ impl ValueStack { .unwrap_or_else(|| panic!("did not reserve enough value stack space")); cells.fill(UntypedValue::default()); self.stack_ptr += additional; + #[cfg(feature = "test-build")] if self.stack_ptr > self.max_stack_height { self.max_stack_height = self.stack_ptr; } @@ -319,7 +326,10 @@ impl ValueStack { /// function execution happens. pub fn reset(&mut self) { self.stack_ptr = 0; - self.max_stack_height = 0; + #[cfg(feature = "test-build")] + { + self.max_stack_height = 0; + } } } @@ -329,9 +339,11 @@ impl ValueStack { /// /// [`ValueStack`]: super::ValueStack #[derive(Debug, Copy, Clone)] +#[cfg_attr(not(feature = "tracing"), repr(transparent))] pub struct ValueStackPtr { - src: *mut UntypedValue, ptr: *mut UntypedValue, + #[cfg(feature = "tracing")] + src: *mut UntypedValue, } unsafe impl Send for ValueStackPtr {} @@ -339,13 +351,21 @@ unsafe impl Send for ValueStackPtr {} impl From<*mut UntypedValue> for ValueStackPtr { #[inline] fn from(ptr: *mut UntypedValue) -> Self { - Self { src: ptr, ptr } + Self { + #[cfg(feature = "tracing")] + src: ptr, + ptr, + } } } impl ValueStackPtr { pub fn new(ptr: *mut UntypedValue) -> ValueStackPtr { - Self { ptr, src: ptr } + Self { + ptr, + #[cfg(feature = "tracing")] + src: ptr, + } } /// Calculates the distance between two [`ValueStackPtr] in units of [`UntypedValue`]. @@ -394,7 +414,7 @@ impl ValueStackPtr { /// /// The amount of `delta` is in the number of bytes per [`UntypedValue`]. #[must_use] - #[inline] + #[inline(always)] pub fn into_sub(mut self, delta: usize) -> Self { self.dec_by(delta); self @@ -445,16 +465,18 @@ impl ValueStackPtr { // Wasm validation and `rwasm` codegen to never run out // of valid bounds using this method. self.ptr = unsafe { self.ptr.add(delta) }; + #[cfg(feature = "tracing")] debug_assert!(self.ptr >= self.src, "stack underflow: {}", delta); } /// Decreases the [`ValueStackPtr`] of `self` by one. - #[inline] + #[inline(always)] fn dec_by(&mut self, delta: usize) { // SAFETY: Within Wasm bytecode execution we are guaranteed by // Wasm validation and `rwasm` codegen to never run out // of valid bounds using this method. self.ptr = unsafe { self.ptr.sub(delta) }; + #[cfg(feature = "tracing")] debug_assert!(self.ptr >= self.src, "stack underflow"); } @@ -535,7 +557,7 @@ impl ValueStackPtr { /// the executed WebAssembly bytecode for correctness. /// /// [`ValueStack`]: super::ValueStack - #[inline] + #[inline(always)] pub fn pop(&mut self) -> UntypedValue { self.dec_by(1); self.get() diff --git a/tests/basic.rs b/tests/basic.rs index 4c75ef038..3bc9d9b79 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -6,13 +6,13 @@ use rwasm::{ fn test_fib() { let wasm_binary = include_bytes!("../benchmarks/lib.wasm"); let config = CompilationConfig::default() - .with_entrypoint_name("main".into()) + .with_entrypoint_name("fib32".into()) .with_consume_fuel(false); for_each_strategy( |strategy| { let mut store = strategy.empty_store(); let mut result = [Value::I32(0); 1]; - strategy.execute(&mut store, "main", &[Value::I32(43)], &mut result)?; + strategy.execute(&mut store, "fib32", &[Value::I32(43)], &mut result)?; assert_eq!(result[0].i32().unwrap(), 433494437); Ok(()) }, diff --git a/tests/wasmtime.rs b/tests/wasmtime.rs index 759a55ddf..587b7e3af 100644 --- a/tests/wasmtime.rs +++ b/tests/wasmtime.rs @@ -48,7 +48,7 @@ fn test_fib_bench() { ); let mut result = [Value::I32(0)]; strategy - .execute(&mut store, "main", &[Value::I32(43)], &mut result) + .execute(&mut store, "fib32", &[Value::I32(43)], &mut result) .unwrap(); core::hint::black_box(result); } @@ -69,7 +69,7 @@ fn test_instance_reuse() { for _ in 0..32_165 { let mut store = Store::new(module.engine(), ()); let instance = instance_pre.instantiate(store.as_context_mut()).unwrap(); - let entrypoint = instance.get_func(store.as_context_mut(), "main").unwrap(); + let entrypoint = instance.get_func(store.as_context_mut(), "fib32").unwrap(); let mut result = [Val::I32(0)]; entrypoint .call(store.as_context_mut(), &[Val::I32(43)], &mut result) diff --git a/trace-extractor/Cargo.toml b/trace-extractor/Cargo.toml new file mode 100644 index 000000000..cebe63219 --- /dev/null +++ b/trace-extractor/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "trace-extractor" +version = "0.1.0" +edition = "2024" + +[dependencies] +rwasm = { path = "..", default-features = false, features = ["tracing"] } + +[features] +default = ["std"] +std = [] \ No newline at end of file diff --git a/trace-extractor/evm-machine/Cargo.toml b/trace-extractor/evm-machine/Cargo.toml new file mode 100644 index 000000000..4597d16e6 --- /dev/null +++ b/trace-extractor/evm-machine/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "evm-machine" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] +path = "lib.rs" + +[dependencies] +revm-interpreter = { version = "25.0.3", default-features = false } +revm-bytecode = { version = "6.2.2", default-features = false } +hex-literal = { version = "1.0.0", default-features = false } \ No newline at end of file diff --git a/trace-extractor/evm-machine/Makefile b/trace-extractor/evm-machine/Makefile new file mode 100644 index 000000000..863d16523 --- /dev/null +++ b/trace-extractor/evm-machine/Makefile @@ -0,0 +1,5 @@ +.PHONY: build +build: + cargo b --target-dir=./target --target=wasm32-unknown-unknown --release --no-default-features + cp ./target/wasm32-unknown-unknown/release/evm_machine.wasm ./lib.wasm + wasm2wat ./lib.wasm > ./lib.wat || true diff --git a/trace-extractor/evm-machine/lib.rs b/trace-extractor/evm-machine/lib.rs new file mode 100644 index 000000000..2f94524e6 --- /dev/null +++ b/trace-extractor/evm-machine/lib.rs @@ -0,0 +1,38 @@ +#![no_main] + +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + CallInput, InputsImpl, Interpreter, SharedMemory, + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, +}; + +#[unsafe(no_mangle)] +pub fn main() { + let evm_bytecode = hex!( + "608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063f9b7c7e51461002d575b5f5ffd5b610047600480360381019061004291906100f1565b61005d565b604051610054919061012b565b60405180910390f35b5f5f5f90505f600190505f600290505b8463ffffffff168163ffffffff16116100a9575f828461008d9190610171565b90508293508092505080806100a1906101a8565b91505061006d565b508092505050919050565b5f5ffd5b5f63ffffffff82169050919050565b6100d0816100b8565b81146100da575f5ffd5b50565b5f813590506100eb816100c7565b92915050565b5f60208284031215610106576101056100b4565b5b5f610113848285016100dd565b91505092915050565b610125816100b8565b82525050565b5f60208201905061013e5f83018461011c565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61017b826100b8565b9150610186836100b8565b9250828201905063ffffffff8111156101a2576101a1610144565b5b92915050565b5f6101b2826100b8565b915063ffffffff82036101c8576101c7610144565b5b60018201905091905056fea26469706673582212206f34ca4baf4d7f4a2ab9c7060b71c1f28bca433c9959aabaa5c1ac6323863d2364736f6c634300081e0033" + ); + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes( + hex!("f9b7c7e5000000000000000000000000000000000000000000000000000000000000002b") + .into(), + ), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + core::hint::black_box(result); +} diff --git a/trace-extractor/src/main.rs b/trace-extractor/src/main.rs new file mode 100644 index 000000000..0a0056e8e --- /dev/null +++ b/trace-extractor/src/main.rs @@ -0,0 +1,68 @@ +use rwasm::{CompilationConfig, ExecutionEngine, RwasmModule, RwasmStore, Value}; + +fn trace_steps( + wasm_bytecode: &[u8], + entrypoint: &'static str, + input: &[Value], + output: &mut [Value], +) -> usize { + let config = CompilationConfig::default() + .with_entrypoint_name(entrypoint.into()) + .with_consume_fuel(false); + let (rwasm_module, _) = RwasmModule::compile(config, wasm_bytecode).unwrap(); + let engine = ExecutionEngine::new(); + let mut store = RwasmStore::<()>::default(); + engine + .execute(&mut store, &rwasm_module, input, output) + .unwrap(); + store.tracer.logs.len() +} + +fn main() { + // trace evm machine fib32 + let evm_steps = trace_steps( + include_bytes!("../evm-machine/lib.wasm"), + "main", + &[], + &mut [], + ); + println!("evm (fib32) trace steps: {}", evm_steps); + // trace rwasm fib32 + let mut result = [Value::I32(0)]; + let rwasm_fib32_steps = trace_steps( + include_bytes!("../../benchmarks/lib.wasm"), + "fib32", + &[Value::I32(43)], + &mut result, + ); + println!( + "rwasm (fib32) trace steps: {} ({}x)", + rwasm_fib32_steps, + evm_steps / rwasm_fib32_steps + ); + // trace rwasm fib64 + let mut result = [Value::I64(0)]; + let rwasm_fib64_steps = trace_steps( + include_bytes!("../../benchmarks/lib.wasm"), + "fib64", + &[Value::I64(90)], + &mut result, + ); + println!( + "rwasm (fib64) trace steps: {} ({}x)", + rwasm_fib64_steps, + evm_steps / rwasm_fib64_steps + ); + // trace rwasm fib256 + let rwasm_fib256_steps = trace_steps( + include_bytes!("../../benchmarks/lib.wasm"), + "fib256", + &[Value::I32(0), Value::I64(90)], + &mut [], + ); + println!( + "rwasm (fib256) trace steps: {} ({}x)", + rwasm_fib256_steps, + evm_steps / rwasm_fib256_steps + ); +} diff --git a/wasm/Makefile b/wasm/Makefile index 81176da59..0ca90893d 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -1,5 +1,5 @@ .PHONY: build build: - cargo b --release --target=wasm32-unknown-unknown --no-default-features + cargo b --release --target-dir=./target --target=wasm32-unknown-unknown --no-default-features cp ./target/wasm32-unknown-unknown/release/wasm.wasm ./lib.wasm wasm2wat ./lib.wasm > ./lib.wat || true \ No newline at end of file From 2139ca9bd951d4b62905aa95399694fc75f5cf42 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin <3367605+dmitry123@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:53:06 +0300 Subject: [PATCH 02/14] chore: remove legacy folder (#59) --- legacy/Cargo.toml | 41 - legacy/README.md | 197 -- legacy/src/arena/component_vec.rs | 224 -- legacy/src/arena/dedup.rs | 174 -- legacy/src/arena/guarded.rs | 34 - legacy/src/arena/mod.rs | 335 -- legacy/src/arena/tests.rs | 161 - legacy/src/core/host_error.rs | 60 - legacy/src/core/import_linker.rs | 53 - legacy/src/core/mod.rs | 48 - legacy/src/core/nan_preserving_float.rs | 270 -- legacy/src/core/rwasm.rs | 19 - legacy/src/core/trap.rs | 330 -- legacy/src/core/units.rs | 320 -- legacy/src/core/untyped.rs | 1655 ---------- legacy/src/core/value.rs | 923 ------ legacy/src/engine/bytecode/instr_meta.rs | 43 - legacy/src/engine/bytecode/mod.rs | 650 ---- legacy/src/engine/bytecode/stack_height.rs | 360 --- legacy/src/engine/bytecode/tests.rs | 18 - legacy/src/engine/bytecode/utils.rs | 429 --- legacy/src/engine/cache.rs | 376 --- legacy/src/engine/code_map.rs | 389 --- legacy/src/engine/config.rs | 553 ---- legacy/src/engine/const_pool.rs | 115 - legacy/src/engine/executor.rs | 1852 ----------- legacy/src/engine/func_args.rs | 138 - .../src/engine/func_builder/control_frame.rs | 425 --- .../src/engine/func_builder/control_stack.rs | 78 - legacy/src/engine/func_builder/error.rs | 108 - .../src/engine/func_builder/inst_builder.rs | 336 -- legacy/src/engine/func_builder/labels.rs | 212 -- .../engine/func_builder/locals_registry.rs | 54 - legacy/src/engine/func_builder/mod.rs | 352 --- legacy/src/engine/func_builder/translator.rs | 2743 ----------------- legacy/src/engine/func_builder/value_stack.rs | 87 - legacy/src/engine/func_types.rs | 139 - legacy/src/engine/mod.rs | 935 ------ legacy/src/engine/resumable.rs | 287 -- legacy/src/engine/stack/frames.rs | 111 - legacy/src/engine/stack/mod.rs | 236 -- legacy/src/engine/stack/values/mod.rs | 297 -- legacy/src/engine/stack/values/sp.rs | 364 --- legacy/src/engine/stack/values/tests.rs | 94 - legacy/src/engine/tests.rs | 1401 --------- legacy/src/engine/tracer.rs | 202 -- legacy/src/engine/traits.rs | 84 - legacy/src/error.rs | 109 - legacy/src/externref.rs | 213 -- legacy/src/foreach_tuple.rs | 25 - legacy/src/func/caller.rs | 159 - legacy/src/func/error.rs | 38 - legacy/src/func/func_type.rs | 327 -- legacy/src/func/funcref.rs | 112 - legacy/src/func/into_func.rs | 363 --- legacy/src/func/mod.rs | 497 --- legacy/src/func/typed_func.rs | 198 -- legacy/src/global.rs | 275 -- legacy/src/instance/builder.rs | 198 -- legacy/src/instance/exports.rs | 274 -- legacy/src/instance/mod.rs | 284 -- legacy/src/lib.rs | 151 - legacy/src/limits.rs | 302 -- legacy/src/linker.rs | 839 ----- legacy/src/memory/buffer.rs | 52 - legacy/src/memory/data.rs | 104 - legacy/src/memory/error.rs | 50 - legacy/src/memory/mod.rs | 471 --- legacy/src/memory/tests.rs | 16 - legacy/src/module/builder.rs | 594 ---- legacy/src/module/compile/block_type.rs | 118 - legacy/src/module/compile/mod.rs | 104 - legacy/src/module/custom_section.rs | 166 - legacy/src/module/data.rs | 87 - legacy/src/module/element.rs | 147 - legacy/src/module/error.rs | 55 - legacy/src/module/export.rs | 198 -- legacy/src/module/global.rs | 55 - legacy/src/module/import.rs | 134 - legacy/src/module/init_expr.rs | 410 --- legacy/src/module/instantiate/error.rs | 110 - legacy/src/module/instantiate/mod.rs | 370 --- legacy/src/module/instantiate/pre.rs | 81 - legacy/src/module/instantiate/tests.rs | 123 - legacy/src/module/mod.rs | 469 --- legacy/src/module/parser.rs | 580 ---- legacy/src/module/read.rs | 69 - legacy/src/module/utils.rs | 121 - legacy/src/reftype.rs | 38 - legacy/src/rwasm/binary_format/drop_keep.rs | 25 - legacy/src/rwasm/binary_format/instruction.rs | 572 ---- .../rwasm/binary_format/instruction_set.rs | 52 - legacy/src/rwasm/binary_format/mod.rs | 42 - legacy/src/rwasm/binary_format/module.rs | 136 - legacy/src/rwasm/binary_format/number.rs | 32 - .../src/rwasm/binary_format/reader_writer.rs | 269 -- legacy/src/rwasm/binary_format/utils.rs | 77 - legacy/src/rwasm/drop_keep.rs | 127 - legacy/src/rwasm/instruction_set.rs | 346 --- legacy/src/rwasm/mod.rs | 16 - legacy/src/rwasm/reduced_module.rs | 206 -- legacy/src/rwasm/segment_builder.rs | 126 - legacy/src/rwasm/tests.rs | 392 --- legacy/src/rwasm/translator.rs | 491 --- legacy/src/rwasm/types.rs | 46 - legacy/src/store.rs | 1149 ------- legacy/src/table/element.rs | 146 - legacy/src/table/error.rs | 79 - legacy/src/table/mod.rs | 772 ----- legacy/src/table/tests.rs | 19 - legacy/src/value.rs | 198 -- 111 files changed, 32716 deletions(-) delete mode 100644 legacy/Cargo.toml delete mode 100644 legacy/README.md delete mode 100644 legacy/src/arena/component_vec.rs delete mode 100644 legacy/src/arena/dedup.rs delete mode 100644 legacy/src/arena/guarded.rs delete mode 100644 legacy/src/arena/mod.rs delete mode 100644 legacy/src/arena/tests.rs delete mode 100644 legacy/src/core/host_error.rs delete mode 100644 legacy/src/core/import_linker.rs delete mode 100644 legacy/src/core/mod.rs delete mode 100644 legacy/src/core/nan_preserving_float.rs delete mode 100644 legacy/src/core/rwasm.rs delete mode 100644 legacy/src/core/trap.rs delete mode 100644 legacy/src/core/units.rs delete mode 100644 legacy/src/core/untyped.rs delete mode 100644 legacy/src/core/value.rs delete mode 100644 legacy/src/engine/bytecode/instr_meta.rs delete mode 100644 legacy/src/engine/bytecode/mod.rs delete mode 100644 legacy/src/engine/bytecode/stack_height.rs delete mode 100644 legacy/src/engine/bytecode/tests.rs delete mode 100644 legacy/src/engine/bytecode/utils.rs delete mode 100644 legacy/src/engine/cache.rs delete mode 100644 legacy/src/engine/code_map.rs delete mode 100644 legacy/src/engine/config.rs delete mode 100644 legacy/src/engine/const_pool.rs delete mode 100644 legacy/src/engine/executor.rs delete mode 100644 legacy/src/engine/func_args.rs delete mode 100644 legacy/src/engine/func_builder/control_frame.rs delete mode 100644 legacy/src/engine/func_builder/control_stack.rs delete mode 100644 legacy/src/engine/func_builder/error.rs delete mode 100644 legacy/src/engine/func_builder/inst_builder.rs delete mode 100644 legacy/src/engine/func_builder/labels.rs delete mode 100644 legacy/src/engine/func_builder/locals_registry.rs delete mode 100644 legacy/src/engine/func_builder/mod.rs delete mode 100644 legacy/src/engine/func_builder/translator.rs delete mode 100644 legacy/src/engine/func_builder/value_stack.rs delete mode 100644 legacy/src/engine/func_types.rs delete mode 100644 legacy/src/engine/mod.rs delete mode 100644 legacy/src/engine/resumable.rs delete mode 100644 legacy/src/engine/stack/frames.rs delete mode 100644 legacy/src/engine/stack/mod.rs delete mode 100644 legacy/src/engine/stack/values/mod.rs delete mode 100644 legacy/src/engine/stack/values/sp.rs delete mode 100644 legacy/src/engine/stack/values/tests.rs delete mode 100644 legacy/src/engine/tests.rs delete mode 100644 legacy/src/engine/tracer.rs delete mode 100644 legacy/src/engine/traits.rs delete mode 100644 legacy/src/error.rs delete mode 100644 legacy/src/externref.rs delete mode 100644 legacy/src/foreach_tuple.rs delete mode 100644 legacy/src/func/caller.rs delete mode 100644 legacy/src/func/error.rs delete mode 100644 legacy/src/func/func_type.rs delete mode 100644 legacy/src/func/funcref.rs delete mode 100644 legacy/src/func/into_func.rs delete mode 100644 legacy/src/func/mod.rs delete mode 100644 legacy/src/func/typed_func.rs delete mode 100644 legacy/src/global.rs delete mode 100644 legacy/src/instance/builder.rs delete mode 100644 legacy/src/instance/exports.rs delete mode 100644 legacy/src/instance/mod.rs delete mode 100644 legacy/src/lib.rs delete mode 100644 legacy/src/limits.rs delete mode 100644 legacy/src/linker.rs delete mode 100644 legacy/src/memory/buffer.rs delete mode 100644 legacy/src/memory/data.rs delete mode 100644 legacy/src/memory/error.rs delete mode 100644 legacy/src/memory/mod.rs delete mode 100644 legacy/src/memory/tests.rs delete mode 100644 legacy/src/module/builder.rs delete mode 100644 legacy/src/module/compile/block_type.rs delete mode 100644 legacy/src/module/compile/mod.rs delete mode 100644 legacy/src/module/custom_section.rs delete mode 100644 legacy/src/module/data.rs delete mode 100644 legacy/src/module/element.rs delete mode 100644 legacy/src/module/error.rs delete mode 100644 legacy/src/module/export.rs delete mode 100644 legacy/src/module/global.rs delete mode 100644 legacy/src/module/import.rs delete mode 100644 legacy/src/module/init_expr.rs delete mode 100644 legacy/src/module/instantiate/error.rs delete mode 100644 legacy/src/module/instantiate/mod.rs delete mode 100644 legacy/src/module/instantiate/pre.rs delete mode 100644 legacy/src/module/instantiate/tests.rs delete mode 100644 legacy/src/module/mod.rs delete mode 100644 legacy/src/module/parser.rs delete mode 100644 legacy/src/module/read.rs delete mode 100644 legacy/src/module/utils.rs delete mode 100644 legacy/src/reftype.rs delete mode 100644 legacy/src/rwasm/binary_format/drop_keep.rs delete mode 100644 legacy/src/rwasm/binary_format/instruction.rs delete mode 100644 legacy/src/rwasm/binary_format/instruction_set.rs delete mode 100644 legacy/src/rwasm/binary_format/mod.rs delete mode 100644 legacy/src/rwasm/binary_format/module.rs delete mode 100644 legacy/src/rwasm/binary_format/number.rs delete mode 100644 legacy/src/rwasm/binary_format/reader_writer.rs delete mode 100644 legacy/src/rwasm/binary_format/utils.rs delete mode 100644 legacy/src/rwasm/drop_keep.rs delete mode 100644 legacy/src/rwasm/instruction_set.rs delete mode 100644 legacy/src/rwasm/mod.rs delete mode 100644 legacy/src/rwasm/reduced_module.rs delete mode 100644 legacy/src/rwasm/segment_builder.rs delete mode 100644 legacy/src/rwasm/tests.rs delete mode 100644 legacy/src/rwasm/translator.rs delete mode 100644 legacy/src/rwasm/types.rs delete mode 100644 legacy/src/store.rs delete mode 100644 legacy/src/table/element.rs delete mode 100644 legacy/src/table/error.rs delete mode 100644 legacy/src/table/mod.rs delete mode 100644 legacy/src/table/tests.rs delete mode 100644 legacy/src/value.rs diff --git a/legacy/Cargo.toml b/legacy/Cargo.toml deleted file mode 100644 index 0a227b984..000000000 --- a/legacy/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "rwasm-legacy" -version = "0.30.0" -documentation = "" -description = "rwasm" -edition = "2021" - -[dependencies] -wasmparser = { version = "0.100.1", package = "wasmparser-nostd", default-features = false } -spin = { version = "0.9", default-features = false, features = [ - "mutex", - "spin_mutex", - "rwlock", -] } -smallvec = { version = "1.10.0", features = ["union"] } -libm = "0.2.1" -num-traits = { version = "0.2", default-features = false } -downcast-rs = { version = "1.2.0", default-features = false } -paste = "1" -byteorder = { version = "1.5.0", default-features = false } -hashbrown = { version = "0.15.2", features = ["alloc"] } - -# strum is used only with test cfg -strum = { version = "0.27.1", optional = true } -strum_macros = { version = "0.27.1", optional = true } - -[dev-dependencies] -hex-literal = "0.4.1" -wat = "1" -assert_matches = "1.5" -wast = "52.0" -anyhow = "1.0" -criterion = { version = "0.4", default-features = false } -rand = "0.8.2" - -[features] -default = ["std"] -# Use `no-default-features` for a `no_std` build. -std = ["num-traits/std", "downcast-rs/std", "byteorder/std", "dep:strum", "dep:strum_macros"] -print-trace = ["std"] -e2e = [] diff --git a/legacy/README.md b/legacy/README.md deleted file mode 100644 index dd12886e5..000000000 --- a/legacy/README.md +++ /dev/null @@ -1,197 +0,0 @@ - -| Continuous Integration | Test Coverage | Documentation | Crates.io | -|:----------------------:|:--------------------:|:----------------:|:--------------------:| -| [![ci][1]][2] | [![codecov][3]][4] | [![docs][5]][6] | [![crates][7]][8] | - -[1]: https://github.com/paritytech/wasmi/workflows/Rust%20-%20Continuous%20Integration/badge.svg?branch=master -[2]: https://github.com/paritytech/wasmi/actions?query=workflow%3A%22Rust+-+Continuous+Integration%22+branch%3Amaster -[3]: https://codecov.io/gh/paritytech/wasmi/branch/master/graph/badge.svg -[4]: https://codecov.io/gh/paritytech/wasmi/branch/master -[5]: https://docs.rs/wasmi/badge.svg -[6]: https://docs.rs/wasmi -[7]: https://img.shields.io/crates/v/wasmi.svg -[8]: https://crates.io/crates/wasmi - -[license-mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[license-apache-badge]: https://img.shields.io/badge/license-APACHE-orange.svg - -# `wasmi`- WebAssembly (Wasm) Interpreter - -`wasmi` is an efficient WebAssembly interpreter with low-overhead and support -for embedded environment such as WebAssembly itself. - -At Parity we are using `wasmi` in [Substrate](https://github.com/paritytech/substrate) -as the execution engine for our WebAssembly based smart contracts. -Furthermore we run `wasmi` within the Substrate runtime which is a WebAssembly -environment itself and driven via [Wasmtime] at the time of this writing. -As such `wasmi`'s implementation requires a high degree of correctness and -Wasm specification conformance. - -Since `wasmi` is relatively lightweight compared to other Wasm virtual machines -such as Wasmtime it is also a decent option for initial prototyping. - -[Wasmtime]: https://github.com/bytecodealliance/wasmtime - -## Distinct Features - -The following list states some of the distinct features of `wasmi`. - -- Focus on simple, correct and deterministic WebAssembly execution. -- Can itself run inside of WebAssembly. -- Low-overhead and cross-platform WebAssembly runtime. -- Loosely mirrors the [Wasmtime API](https://docs.rs/wasmtime/). -- Resumable function calls. -- Built-in support for fuel metering. -- 100% official WebAssembly spec testsuite compliance. - -## WebAssembly Proposals - -The new `wasmi` engine supports a variety of WebAssembly proposals and will support even more of them in the future. - -| WebAssembly Proposal | Status | Comment | -|:--|:--:|:--| -| [`mutable-global`] | ✅ | Since version `0.14.0`. | -| [`saturating-float-to-int`] | ✅ | Since version `0.14.0`. | -| [`sign-extension`] | ✅ | Since version `0.14.0`. | -| [`multi-value`] | ✅ | Since version `0.14.0`. | -| [`bulk-memory`] | ✅ | Since version `0.24.0`. [(#628)] | -| [`reference-types`] | ✅ | Since version `0.24.0`. [(#635)] | -| [`simd`] | ❌ | Unlikely to be supported. | -| [`tail-calls`] | ✅ | Since version `0.28.0`. [(#683)] | -| [`extended-const`] | ✅ | Since version `0.29.0`. [(#707)] | -| | | -| [WASI] | 🟡 | Experimental support via the [`wasmi_wasi` crate] or the `wasmi` CLI application. | - -[`mutable-global`]: https://github.com/WebAssembly/mutable-global -[`saturating-float-to-int`]: https://github.com/WebAssembly/nontrapping-float-to-int-conversions -[`sign-extension`]: https://github.com/WebAssembly/sign-extension-ops -[`multi-value`]: https://github.com/WebAssembly/multi-value -[`reference-types`]: https://github.com/WebAssembly/reference-types -[`bulk-memory`]: https://github.com/WebAssembly/bulk-memory-operations -[`simd` ]: https://github.com/webassembly/simd -[`tail-calls`]: https://github.com/WebAssembly/tail-call -[`extended-const`]: https://github.com/WebAssembly/extended-const - -[WASI]: https://github.com/WebAssembly/WASI -[`wasmi_wasi` crate]: ./crates/wasi - -[(#363)]: https://github.com/paritytech/wasmi/issues/363 -[(#364)]: https://github.com/paritytech/wasmi/issues/364 -[(#496)]: https://github.com/paritytech/wasmi/issues/496 -[(#628)]: https://github.com/paritytech/wasmi/pull/628 -[(#635)]: https://github.com/paritytech/wasmi/pull/635 -[(#638)]: https://github.com/paritytech/wasmi/pull/638 -[(#683)]: https://github.com/paritytech/wasmi/pull/683 -[(#707)]: https://github.com/paritytech/wasmi/pull/707 - -## Usage - -### As CLI Application - -Install the newest `wasmi` CLI version via: -```console -cargo install wasmi_cli -``` -Then run arbitrary `wasm32-unknown-unknown` Wasm blobs via: -```console -wasmi_cli []* -``` - -### As Rust Library - -Any Rust crate can depend on the [`wasmi` crate](https://crates.io/crates/wasmi) -in order to integrate a WebAssembly intepreter into their stack. - -Refer to the [`wasmi` crate docs](https://docs.rs/wasmi) to learn how to use the `wasmi` crate as library. - -## Development - -### Building - -Clone `wasmi` from our official repository and then build using the standard `cargo` procedure: - -```console -git clone https://github.com/paritytech/wasmi.git -cd wasmi -cargo build -``` - -### Testing - -In order to test `wasmi` you need to initialize and update the Git submodules using: - -```console -git submodule update --init --recursive -``` - -Alternatively you can provide `--recursive` flag to `git clone` command while cloning the repository: - -```console -git clone https://github.com/paritytech/wasmi.git --recursive -``` - -After Git submodules have been initialized and updated you can test using: - -```console -cargo test --workspace -``` - -### Benchmarks - -In order to benchmark `wasmi` use the following command: - -```console -cargo bench -``` - -You can filter which set of benchmarks to run: -- `cargo bench translate` - - Only runs benchmarks concerned with WebAssembly module translation. - -- `cargo bench instantiate` - - Only runs benchmarks concerned with WebAssembly module instantiation. - -- `cargo bench execute` - - Only runs benchmarks concerned with executing WebAssembly functions. - -## Supported Platforms - -Supported platforms are primarily Linux, MacOS, Windows and WebAssembly. -Other platforms might be working but are not guaranteed to be so by the `wasmi` maintainers. - -Use the following command in order to produce a WebAssembly build: - -```console -cargo build --no-default-features --target wasm32-unknown-unknown -``` - -## Production Builds - -In order to reap the most performance out of `wasmi` we highly recommended -to compile the `wasmi` crate using the following Cargo `profile`: - -```toml -[profile.release] -lto = "fat" -codegen-units = 1 -``` - -When compiling for the WebAssembly target we highly recommend to post-optimize -`wasmi` using [Binaryen]'s `wasm-opt` tool since our experiments displayed a -80-100% performance improvements when executed under Wasmtime and also -slightly smaller Wasm binaries. - -[Binaryen]: https://github.com/WebAssembly/binaryen - -## License - -`wasmi` is primarily distributed under the terms of both the MIT -license and the APACHE license (Version 2.0), at your choice. - -See `LICENSE-APACHE` and `LICENSE-MIT` for details. - -## Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in `wasmi` by you, as defined in the APACHE 2.0 license, shall be -dual licensed as above, without any additional terms or conditions. diff --git a/legacy/src/arena/component_vec.rs b/legacy/src/arena/component_vec.rs deleted file mode 100644 index cbd8b0407..000000000 --- a/legacy/src/arena/component_vec.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::arena::ArenaIndex; -use alloc::vec::Vec; -use core::{ - fmt::{self, Debug}, - marker::PhantomData, - ops::{Index, IndexMut}, -}; - -/// Stores components for entities backed by a [`Vec`]. -pub struct ComponentVec { - components: Vec>, - marker: PhantomData Idx>, -} - -/// [`ComponentVec`] does not store `Idx` therefore it is `Send` without its bound. -unsafe impl Send for ComponentVec where T: Send {} - -/// [`ComponentVec`] does not store `Idx` therefore it is `Sync` without its bound. -unsafe impl Sync for ComponentVec where T: Send {} - -impl Debug for ComponentVec -where - T: Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ComponentVec") - .field("components", &DebugComponents(&self.components)) - .finish() - } -} - -struct DebugComponents<'a, T>(&'a [Option]); - -impl<'a, T> Debug for DebugComponents<'a, T> -where - T: Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut map = f.debug_map(); - let components = self - .0 - .iter() - .enumerate() - .filter_map(|(n, component)| component.as_ref().map(|c| (n, c))); - for (idx, component) in components { - map.entry(&idx, component); - } - map.finish() - } -} - -impl Default for ComponentVec { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for ComponentVec -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.components.eq(&other.components) - } -} - -impl Eq for ComponentVec where T: Eq {} - -impl ComponentVec { - /// Creates a new empty [`ComponentVec`]. - pub fn new() -> Self { - Self { - components: Vec::new(), - marker: PhantomData, - } - } - - /// Clears all components from the [`ComponentVec`]. - pub fn clear(&mut self) { - self.components.clear(); - } -} - -impl ComponentVec -where - Idx: ArenaIndex, -{ - /// Sets the `component` for the entity at `index`. - /// - /// Returns the old component of the same entity if any. - pub fn set(&mut self, index: Idx, component: T) -> Option { - let index = index.into_usize(); - if index >= self.components.len() { - // The underlying vector does not have enough capacity - // and is required to be enlarged. - self.components.resize_with(index + 1, || None); - } - self.components[index].replace(component) - } - - /// Unsets the component for the entity at `index` and returns it if any. - pub fn unset(&mut self, index: Idx) -> Option { - self.components - .get_mut(index.into_usize()) - .and_then(Option::take) - } - - /// Returns a shared reference to the component at the `index` if any. - /// - /// Returns `None` if no component is stored under the `index`. - #[inline] - pub fn get(&self, index: Idx) -> Option<&T> { - self.components - .get(index.into_usize()) - .and_then(Option::as_ref) - } - - /// Returns an exclusive reference to the component at the `index` if any. - /// - /// Returns `None` if no component is stored under the `index`. - #[inline] - pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { - self.components - .get_mut(index.into_usize()) - .and_then(Option::as_mut) - } -} - -impl Index for ComponentVec -where - Idx: ArenaIndex, -{ - type Output = T; - - #[inline] - fn index(&self, index: Idx) -> &Self::Output { - self.get(index) - .unwrap_or_else(|| panic!("missing component at index: {}", index.into_usize())) - } -} - -impl IndexMut for ComponentVec -where - Idx: ArenaIndex, -{ - #[inline] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - self.get_mut(index) - .unwrap_or_else(|| panic!("missing component at index: {}", index.into_usize())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Add `n` components and perform checks along the way. - fn add_components(vec: &mut ComponentVec, n: usize) { - for i in 0..n { - let mut str = format!("{i}"); - assert!(vec.get(i).is_none()); - assert!(vec.get_mut(i).is_none()); - assert!(vec.set(i, str.clone()).is_none()); - assert_eq!(vec.get(i), Some(&str)); - assert_eq!(vec.get_mut(i), Some(&mut str)); - assert_eq!(&vec[i], &str); - assert_eq!(&mut vec[i], &mut str); - } - } - - #[test] - fn it_works() { - let mut vec = >::new(); - let n = 10; - add_components(&mut vec, n); - // Remove components in reverse order for fun. - // Check if components have been removed properly. - for i in (0..n).rev() { - let str = format!("{i}"); - assert_eq!(vec.unset(i), Some(str)); - assert!(vec.get(i).is_none()); - assert!(vec.get_mut(i).is_none()); - } - } - - #[test] - fn clear_works() { - let mut vec = >::new(); - let n = 10; - add_components(&mut vec, n); - // Clear component vec and check if components have been removed properly. - vec.clear(); - for i in 0..n { - assert!(vec.get(i).is_none()); - assert!(vec.get_mut(i).is_none()); - } - } - - #[test] - fn debug_works() { - let mut vec = >::new(); - add_components(&mut vec, 4); - { - let debug_str = format!("{vec:?}"); - let expected_str = "\ - ComponentVec { components: {0: \"0\", 1: \"1\", 2: \"2\", 3: \"3\"} }\ - "; - assert_eq!(debug_str, expected_str); - } - { - let debug_str = format!("{vec:#?}"); - let expected_str = "\ - ComponentVec {\n \ - components: {\n \ - 0: \"0\",\n \ - 1: \"1\",\n \ - 2: \"2\",\n \ - 3: \"3\",\n \ - },\n}\ - "; - assert_eq!(debug_str, expected_str); - } - } -} diff --git a/legacy/src/arena/dedup.rs b/legacy/src/arena/dedup.rs deleted file mode 100644 index 3585a90ef..000000000 --- a/legacy/src/arena/dedup.rs +++ /dev/null @@ -1,174 +0,0 @@ -use super::{Arena, ArenaIndex, Iter, IterMut}; -use alloc::collections::BTreeMap; -use core::ops::{Index, IndexMut}; - -/// A deduplicating arena allocator with a given index and entity type. -/// -/// For performance reasons the arena cannot deallocate single entities. -#[derive(Debug)] -pub struct DedupArena { - entity2idx: BTreeMap, - entities: Arena, -} - -impl Default for DedupArena { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for DedupArena -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.entities.eq(&other.entities) - } -} - -impl Eq for DedupArena where T: Eq {} - -impl DedupArena { - /// Creates a new empty deduplicating entity arena. - pub fn new() -> Self { - Self { - entity2idx: BTreeMap::new(), - entities: Arena::new(), - } - } - - /// Returns the allocated number of entities. - #[inline] - pub fn len(&self) -> usize { - self.entities.len() - } - - /// Returns `true` if the [`Arena`] has not yet allocated entities. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Clears all entities from the arena. - pub fn clear(&mut self) { - self.entity2idx.clear(); - self.entities.clear(); - } - - /// Returns an iterator over the shared reference of the [`Arena`] entities. - pub fn iter(&self) -> Iter { - self.entities.iter() - } - - /// Returns an iterator over the exclusive reference of the [`Arena`] entities. - pub fn iter_mut(&mut self) -> IterMut { - self.entities.iter_mut() - } -} - -impl DedupArena -where - Idx: ArenaIndex, - T: Ord + Clone, -{ - /// Returns the next entity index. - fn next_index(&self) -> Idx { - self.entities.next_index() - } - - /// Allocates a new entity and returns its index. - /// - /// # Note - /// - /// Only allocates if the entity does not already exist in the [`DedupArena`]. - pub fn alloc(&mut self, entity: T) -> Idx { - match self.entity2idx.get(&entity) { - Some(index) => *index, - None => { - let index = self.next_index(); - self.entity2idx.insert(entity.clone(), index); - self.entities.alloc(entity); - index - } - } - } - - /// Returns a shared reference to the entity at the given index if any. - #[inline] - pub fn get(&self, index: Idx) -> Option<&T> { - self.entities.get(index) - } - - /// Returns an exclusive reference to the entity at the given index if any. - #[inline] - pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { - self.entities.get_mut(index) - } -} - -impl FromIterator for DedupArena -where - Idx: ArenaIndex, - T: Clone + Ord, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let entities = Arena::from_iter(iter); - let entity2idx = entities - .iter() - .map(|(idx, entity)| (entity.clone(), idx)) - .collect::>(); - Self { - entity2idx, - entities, - } - } -} - -impl<'a, Idx, T> IntoIterator for &'a DedupArena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a T); - type IntoIter = Iter<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, Idx, T> IntoIterator for &'a mut DedupArena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a mut T); - type IntoIter = IterMut<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -impl Index for DedupArena -where - Idx: ArenaIndex, -{ - type Output = T; - - #[inline] - fn index(&self, index: Idx) -> &Self::Output { - &self.entities[index] - } -} - -impl IndexMut for DedupArena -where - Idx: ArenaIndex, -{ - #[inline] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - &mut self.entities[index] - } -} diff --git a/legacy/src/arena/guarded.rs b/legacy/src/arena/guarded.rs deleted file mode 100644 index e7905a41c..000000000 --- a/legacy/src/arena/guarded.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::arena::ArenaIndex; - -/// A guarded entity. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct GuardedEntity { - guard_idx: GuardIdx, - entity_idx: EntityIdx, -} - -impl GuardedEntity { - /// Creates a new [`GuardedEntity`]. - pub fn new(guard_idx: GuardIdx, entity_idx: EntityIdx) -> Self { - Self { - guard_idx, - entity_idx, - } - } -} - -impl GuardedEntity -where - GuardIdx: ArenaIndex, - EntityIdx: ArenaIndex, -{ - /// Returns the entity index of the [`GuardedEntity`]. - /// - /// Return `None` if the `guard_index` does not match. - pub fn entity_index(&self, guard_index: GuardIdx) -> Option { - if self.guard_idx.into_usize() != guard_index.into_usize() { - return None; - } - Some(self.entity_idx) - } -} diff --git a/legacy/src/arena/mod.rs b/legacy/src/arena/mod.rs deleted file mode 100644 index f4a939dd8..000000000 --- a/legacy/src/arena/mod.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Fast arena allocators for different usage purposes. -//! -//! They cannot deallocate single allocated entities for extra efficiency. -//! These allocators mainly serve as the backbone for an efficient Wasm store -//! implementation. - -#![warn( - clippy::cast_lossless, - clippy::missing_errors_doc, - clippy::used_underscore_binding, - clippy::redundant_closure_for_method_calls, - clippy::type_repetition_in_bounds, - clippy::inconsistent_struct_constructor, - clippy::default_trait_access, - clippy::map_unwrap_or, - clippy::items_after_statements -)] -#[cfg(not(feature = "std"))] -extern crate alloc; -#[cfg(feature = "std")] -extern crate std as alloc; - -mod component_vec; -mod dedup; -mod guarded; - -#[cfg(test)] -mod tests; - -pub use self::{component_vec::ComponentVec, dedup::DedupArena, guarded::GuardedEntity}; -use alloc::vec::Vec; -use core::{ - iter::{DoubleEndedIterator, Enumerate, ExactSizeIterator}, - marker::PhantomData, - ops::{Index, IndexMut}, - slice, -}; - -/// Types that can be used as indices for arenas. -pub trait ArenaIndex: Copy { - /// Converts the [`ArenaIndex`] into the underlying `usize` value. - fn into_usize(self) -> usize; - /// Converts the `usize` value into the associated [`ArenaIndex`]. - fn from_usize(value: usize) -> Self; -} - -/// An arena allocator with a given index and entity type. -/// -/// For performance reasons the arena cannot deallocate single entities. -#[derive(Debug)] -pub struct Arena { - entities: Vec, - marker: PhantomData, -} - -/// `Arena` does not store `Idx` therefore it is `Send` without its bound. -unsafe impl Send for Arena where T: Send {} - -/// `Arena` does not store `Idx` therefore it is `Sync` without its bound. -unsafe impl Sync for Arena where T: Send {} - -impl Default for Arena { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for Arena -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.entities.eq(&other.entities) - } -} - -impl Eq for Arena where T: Eq {} - -impl Arena { - /// Creates a new empty entity arena. - pub fn new() -> Self { - Self { - entities: Vec::new(), - marker: PhantomData, - } - } - - /// Returns the allocated number of entities. - #[inline] - pub fn len(&self) -> usize { - self.entities.len() - } - - /// Returns `true` if the arena has not yet allocated entities. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Clears all entities from the arena. - pub fn clear(&mut self) { - self.entities.clear(); - } - - /// Returns an iterator over the shared reference of the arena entities. - pub fn iter(&self) -> Iter { - Iter { - iter: self.entities.iter().enumerate(), - marker: PhantomData, - } - } - - /// Returns an iterator over the exclusive reference of the arena entities. - pub fn iter_mut(&mut self) -> IterMut { - IterMut { - iter: self.entities.iter_mut().enumerate(), - marker: PhantomData, - } - } -} - -impl Arena -where - Idx: ArenaIndex, -{ - /// Returns the next entity index. - fn next_index(&self) -> Idx { - Idx::from_usize(self.entities.len()) - } - - /// Allocates a new entity and returns its index. - #[inline] - pub fn alloc(&mut self, entity: T) -> Idx { - let index = self.next_index(); - self.entities.push(entity); - index - } - - /// Returns a shared reference to the entity at the given index if any. - #[inline] - pub fn get(&self, index: Idx) -> Option<&T> { - self.entities.get(index.into_usize()) - } - - /// Returns an exclusive reference to the entity at the given index if any. - #[inline] - pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { - self.entities.get_mut(index.into_usize()) - } - - /// Returns an exclusive reference to the pair of entities at the given indices if any. - /// - /// Returns `None` if `fst` and `snd` refer to the same entity. - /// Returns `None` if either `fst` or `snd` is invalid for this [`Arena`]. - #[inline] - pub fn get_pair_mut(&mut self, fst: Idx, snd: Idx) -> Option<(&mut T, &mut T)> { - let fst_index = fst.into_usize(); - let snd_index = snd.into_usize(); - if fst_index == snd_index { - return None; - } - if fst_index > snd_index { - let (fst, snd) = self.get_pair_mut(snd, fst)?; - return Some((snd, fst)); - } - // At this point we know that fst_index < snd_index. - let (fst_set, snd_set) = self.entities.split_at_mut(snd_index); - let fst = fst_set.get_mut(fst_index)?; - let snd = snd_set.get_mut(0)?; - Some((fst, snd)) - } -} - -impl FromIterator for Arena { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self { - entities: Vec::from_iter(iter), - marker: PhantomData, - } - } -} - -impl<'a, Idx, T> IntoIterator for &'a Arena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a T); - type IntoIter = Iter<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, Idx, T> IntoIterator for &'a mut Arena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a mut T); - type IntoIter = IterMut<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -/// An iterator over shared references of arena entities and their indices. -#[derive(Debug)] -pub struct Iter<'a, Idx, T> { - iter: Enumerate>, - marker: PhantomData Idx>, -} - -impl<'a, Idx, T> Iterator for Iter<'a, Idx, T> -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a T); - - #[inline] - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, Idx, T> DoubleEndedIterator for Iter<'a, Idx, T> -where - Idx: ArenaIndex, -{ - #[inline] - fn next_back(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } -} - -impl<'a, Idx, T> ExactSizeIterator for Iter<'a, Idx, T> -where - Idx: ArenaIndex, -{ - fn len(&self) -> usize { - self.iter.len() - } -} - -/// An iterator over exclusive references of arena entities and their indices. -#[derive(Debug)] -pub struct IterMut<'a, Idx, T> { - iter: Enumerate>, - marker: PhantomData Idx>, -} - -impl<'a, Idx, T> Iterator for IterMut<'a, Idx, T> -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a mut T); - - #[inline] - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, Idx, T> DoubleEndedIterator for IterMut<'a, Idx, T> -where - Idx: ArenaIndex, -{ - #[inline] - fn next_back(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } -} - -impl<'a, Idx, T> ExactSizeIterator for IterMut<'a, Idx, T> -where - Idx: ArenaIndex, -{ - #[inline] - fn len(&self) -> usize { - self.iter.len() - } -} - -impl Arena { - /// Panics with an index out of bounds message. - fn index_out_of_bounds(len: usize, index: usize) -> ! { - panic!("index out of bounds: the len is {len} but the index is {index}") - } -} - -impl Index for Arena -where - Idx: ArenaIndex, -{ - type Output = T; - - #[inline] - fn index(&self, index: Idx) -> &Self::Output { - self.get(index) - .unwrap_or_else(|| Self::index_out_of_bounds(self.len(), index.into_usize())) - } -} - -impl IndexMut for Arena -where - Idx: ArenaIndex, -{ - #[inline] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - let len = self.len(); - self.get_mut(index) - .unwrap_or_else(|| Self::index_out_of_bounds(len, index.into_usize())) - } -} diff --git a/legacy/src/arena/tests.rs b/legacy/src/arena/tests.rs deleted file mode 100644 index e7ce1ffef..000000000 --- a/legacy/src/arena/tests.rs +++ /dev/null @@ -1,161 +0,0 @@ -use super::*; - -impl ArenaIndex for usize { - fn into_usize(self) -> usize { - self - } - - fn from_usize(value: usize) -> Self { - value - } -} - -const TEST_ENTITIES: &[&str] = &["a", "b", "c", "d"]; - -mod arena { - use super::*; - - fn alloc_arena(entities: &[&'static str]) -> Arena { - let mut arena = >::new(); - // Check that the given arena is actually empty. - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - // Fill arena and check invariants while doing so. - for idx in 0..entities.len() { - assert!(arena.get(idx).is_none()); - } - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.alloc(str), n); - } - // Check state of filled arena. - assert_eq!(arena.len(), entities.len()); - assert!(!arena.is_empty()); - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.get(n), Some(str)); - assert_eq!(&arena[n], str); - } - assert_eq!(arena.get(arena.len()), None); - // Return filled arena. - arena - } - - #[test] - fn alloc_works() { - alloc_arena(TEST_ENTITIES); - } - - #[test] - fn clear_works() { - let mut arena = alloc_arena(TEST_ENTITIES); - // Clear the arena and check if all elements are removed. - arena.clear(); - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - for idx in 0..arena.len() { - assert_eq!(arena.get(idx), None); - } - assert_eq!(arena.get(arena.len()), None); - } - - #[test] - fn iter_works() { - let arena = alloc_arena(TEST_ENTITIES); - assert!(arena.iter().eq(TEST_ENTITIES.iter().enumerate())); - } - - #[test] - fn from_iter_works() { - let expected = alloc_arena(TEST_ENTITIES); - let actual = TEST_ENTITIES.iter().copied().collect::>(); - assert_eq!(actual, expected); - } - - #[test] - fn duplicates_work() { - let mut arena = alloc_arena(TEST_ENTITIES); - // Re-inserting the same entities into the filled arena will - // result in new and unique indices since the standard arena - // type does not deduplicate its entities. - let previous_len = arena.len(); - for (idx, str) in TEST_ENTITIES.iter().enumerate() { - let offset = previous_len + idx; - assert_eq!(arena.alloc(str), offset); - assert_eq!(arena.get(offset), Some(str)); - } - // Assert that the arena actually did increase in size since - // there is no deduplication of equal entities. - assert_eq!(arena.len(), previous_len + TEST_ENTITIES.len()); - } -} - -mod dedup_arena { - use super::*; - - fn alloc_dedup_arena(entities: &[&'static str]) -> DedupArena { - let mut arena = >::new(); - // Check that the given arena is actually empty. - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - // Fill arena and check invariants while doing so. - for idx in 0..entities.len() { - assert!(arena.get(idx).is_none()); - } - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.alloc(str), n); - } - // Check state of filled arena. - assert_eq!(arena.len(), entities.len()); - assert!(!arena.is_empty()); - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.get(n), Some(str)); - assert_eq!(&arena[n], str); - } - assert_eq!(arena.get(arena.len()), None); - // Return filled arena. - arena - } - - #[test] - fn alloc_works() { - alloc_dedup_arena(TEST_ENTITIES); - } - - #[test] - fn clear_works() { - let mut arena = alloc_dedup_arena(TEST_ENTITIES); - // Clear the arena and check if all elements are removed. - arena.clear(); - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - for idx in 0..arena.len() { - assert_eq!(arena.get(idx), None); - } - assert_eq!(arena.get(arena.len()), None); - } - - #[test] - fn iter_works() { - let arena = alloc_dedup_arena(TEST_ENTITIES); - assert!(arena.iter().eq(TEST_ENTITIES.iter().enumerate())); - } - - #[test] - fn from_iter_works() { - let expected = alloc_dedup_arena(TEST_ENTITIES); - let actual = TEST_ENTITIES.iter().copied().collect::>(); - assert_eq!(actual, expected); - } - - #[test] - fn duplicates_work() { - let mut arena = alloc_dedup_arena(TEST_ENTITIES); - // Re-inserting the same entities into the filled arena will - // yield back the same indices as their already allocated entities. - for (idx, str) in TEST_ENTITIES.iter().enumerate() { - assert_eq!(arena.alloc(str), idx); - assert_eq!(arena.get(idx), Some(str)); - } - // Assert that the deduplicating arena did not increase in size. - assert_eq!(arena.len(), TEST_ENTITIES.len()); - } -} diff --git a/legacy/src/core/host_error.rs b/legacy/src/core/host_error.rs deleted file mode 100644 index e173871f6..000000000 --- a/legacy/src/core/host_error.rs +++ /dev/null @@ -1,60 +0,0 @@ -use core::fmt::{Debug, Display}; -use downcast_rs::{impl_downcast, DowncastSync}; - -/// Trait that allows the host to return custom error. -/// -/// It should be useful for representing custom traps, -/// troubles at instantiation time or other host specific conditions. -/// -/// Types that implement this trait can automatically be converted to `wasmi::Error` and -/// `wasmi::Trap` and will be represented as a boxed `HostError`. You can then use the various -/// methods on `wasmi::Error` to get your custom error type back -/// -/// # Examples -/// -/// ```rust -/// use std::fmt; -/// use crate::rwasm_legacy::core::{Trap, HostError}; -/// -/// #[derive(Debug, Copy, Clone)] -/// struct MyError { -/// code: u32, -/// } -/// -/// impl fmt::Display for MyError { -/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -/// write!(f, "MyError, code={}", self.code) -/// } -/// } -/// -/// impl HostError for MyError { } -/// -/// fn failable_fn() -> Result<(), Trap> { -/// let my_error = MyError { code: 42 }; -/// // Note how you can just convert your errors to `wasmi::Error` -/// Err(my_error.into()) -/// } -/// -/// // Get a reference to the concrete error -/// match failable_fn() { -/// Err(trap) => { -/// let my_error: &MyError = trap.downcast_ref().unwrap(); -/// assert_eq!(my_error.code, 42); -/// } -/// _ => panic!(), -/// } -/// -/// // get the concrete error itself -/// match failable_fn() { -/// Err(err) => { -/// let my_error = match err.downcast_ref::() { -/// Some(host_error) => host_error.clone(), -/// None => panic!("expected host error `MyError` but found: {}", err), -/// }; -/// assert_eq!(my_error.code, 42); -/// } -/// _ => panic!(), -/// } -/// ``` -pub trait HostError: 'static + Display + Debug + DowncastSync {} -impl_downcast!(HostError); diff --git a/legacy/src/core/import_linker.rs b/legacy/src/core/import_linker.rs deleted file mode 100644 index b92e01738..000000000 --- a/legacy/src/core/import_linker.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{core::ValueType, module::ImportName}; -use hashbrown::HashMap; -use crate::engine::bytecode::Instruction; - -#[derive(Debug, Default, Clone)] -pub struct ImportLinker { - func_by_name: HashMap, -} - -#[derive(Debug, Clone)] -pub struct ImportLinkerEntity { - pub func_idx: u32, - pub fuel_procedure: &'static [Instruction], - pub params: &'static [ValueType], - pub result: &'static [ValueType], -} - -impl From for ImportLinker -where - I: IntoIterator, -{ - fn from(iter: I) -> Self { - Self { - func_by_name: HashMap::from_iter(iter), - } - } -} - -impl ImportLinker { - pub fn insert_function( - &mut self, - import_name: ImportName, - func_idx: u32, - fuel_procedure: &'static [Instruction], - params: &'static [ValueType], - result: &'static [ValueType], - ) { - let last_value = self.func_by_name.insert( - import_name, - ImportLinkerEntity { - func_idx, - fuel_procedure, - params, - result, - }, - ); - assert!(last_value.is_none(), "rwasm: import linker name collision"); - } - - pub fn resolve_by_import_name(&self, import_name: &ImportName) -> Option<&ImportLinkerEntity> { - self.func_by_name.get(import_name) - } -} diff --git a/legacy/src/core/mod.rs b/legacy/src/core/mod.rs deleted file mode 100644 index c18ce4015..000000000 --- a/legacy/src/core/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -#![warn( - clippy::cast_lossless, - clippy::missing_errors_doc, - clippy::used_underscore_binding, - clippy::redundant_closure_for_method_calls, - clippy::type_repetition_in_bounds, - clippy::inconsistent_struct_constructor, - clippy::default_trait_access, - clippy::map_unwrap_or, - clippy::items_after_statements -)] - -mod host_error; -mod import_linker; -mod nan_preserving_float; -mod rwasm; -mod trap; -mod units; -mod untyped; -mod value; - -#[cfg(not(feature = "std"))] -extern crate alloc; - -#[cfg(feature = "std")] -extern crate std as alloc; - -use self::value::{ - ArithmeticOps, - ExtendInto, - Float, - Integer, - LittleEndianConvert, - SignExtendFrom, - TruncateSaturateInto, - TryTruncateInto, - WrapInto, -}; -pub use self::{ - host_error::HostError, - import_linker::*, - nan_preserving_float::{F32, F64}, - rwasm::*, - trap::{Trap, TrapCode}, - units::{Bytes, Pages}, - untyped::{DecodeUntypedSlice, EncodeUntypedSlice, UntypedError, UntypedValue}, - value::ValueType, -}; diff --git a/legacy/src/core/nan_preserving_float.rs b/legacy/src/core/nan_preserving_float.rs deleted file mode 100644 index bb455c062..000000000 --- a/legacy/src/core/nan_preserving_float.rs +++ /dev/null @@ -1,270 +0,0 @@ -macro_rules! impl_binop { - ($for:ty, $is:ty, $op:ident, $func_name:ident) => { - impl> ::core::ops::$op for $for { - type Output = Self; - - #[inline] - fn $func_name(self, other: T) -> Self { - Self( - ::core::ops::$op::$func_name( - <$is>::from_bits(self.0), - <$is>::from_bits(other.into().0), - ) - .to_bits(), - ) - } - } - }; -} - -macro_rules! float { - ( - $( #[$docs:meta] )* - struct $for:ident($rep:ty as $is:ty); - ) => { - float!( - $(#[$docs])* - struct $for($rep as $is, #bits = 1 << (::core::mem::size_of::<$is>() * 8 - 1)); - ); - }; - ( - $( #[$docs:meta] )* - struct $for:ident($rep:ty as $is:ty, #bits = $sign_bit:expr); - ) => { - $(#[$docs])* - #[derive(Copy, Clone)] - pub struct $for($rep); - - impl_binop!($for, $is, Add, add); - impl_binop!($for, $is, Sub, sub); - impl_binop!($for, $is, Mul, mul); - impl_binop!($for, $is, Div, div); - impl_binop!($for, $is, Rem, rem); - - impl $for { - /// Creates a float from its underlying bits. - #[inline] - pub fn from_bits(other: $rep) -> Self { - Self(other) - } - - /// Returns the underlying bits of the float. - #[inline] - pub fn to_bits(self) -> $rep { - self.0 - } - - /// Creates a float from the respective primitive float type. - #[inline] - pub fn from_float(float: $is) -> Self { - Self(float.to_bits()) - } - - /// Returns the respective primitive float type. - #[inline] - pub fn to_float(self) -> $is { - <$is>::from_bits(self.0) - } - - /// Returns `true` if the float is not a number (NaN). - #[inline] - pub fn is_nan(self) -> ::core::primitive::bool { - self.to_float().is_nan() - } - - /// Returns the absolute value of the float. - #[must_use] - #[inline] - pub fn abs(self) -> Self { - Self(self.0 & !$sign_bit) - } - - /// Returns the fractional part of the float. - #[must_use] - #[inline] - pub fn fract(self) -> Self { - Self::from_float( - ::num_traits::float::FloatCore::fract(self.to_float()) - ) - } - - /// Returns the minimum float between `self` and `other`. - #[must_use] - #[inline] - pub fn min(self, other: Self) -> Self { - Self::from(self.to_float().min(other.to_float())) - } - - /// Returns the maximum float between `self` and `other`. - #[must_use] - #[inline] - pub fn max(self, other: Self) -> Self { - Self::from(self.to_float().max(other.to_float())) - } - } - - impl ::core::convert::From<$is> for $for { - #[inline] - fn from(float: $is) -> $for { - Self::from_float(float) - } - } - - impl ::core::convert::From<$for> for $is { - #[inline] - fn from(float: $for) -> $is { - float.to_float() - } - } - - impl ::core::ops::Neg for $for { - type Output = Self; - - #[inline] - fn neg(self) -> Self { - Self(self.0 ^ $sign_bit) - } - } - - impl + ::core::marker::Copy> ::core::cmp::PartialEq for $for { - #[inline] - fn eq(&self, other: &T) -> ::core::primitive::bool { - <$is as ::core::convert::From>::from(*self) - .eq(&<$is as ::core::convert::From>::from((*other).into())) - } - } - - impl + ::core::marker::Copy> ::core::cmp::PartialOrd for $for { - #[inline] - fn partial_cmp(&self, other: &T) -> ::core::option::Option<::core::cmp::Ordering> { - <$is as ::core::convert::From>::from(*self) - .partial_cmp(&<$is as ::core::convert::From>::from((*other).into())) - } - } - - impl ::core::fmt::Debug for $for { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - <$is as ::core::fmt::Debug>::fmt( - &<$is as ::core::convert::From>::from(*self), - f, - ) - } - } - - impl ::core::fmt::Display for $for { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - <$is as ::core::fmt::Display>::fmt( - &<$is as ::core::convert::From>::from(*self), - f, - ) - } - } - }; -} - -float! { - /// A NaN preserving `f32` type. - struct F32(u32 as f32); -} - -float! { - /// A NaN preserving `f64` type. - struct F64(u64 as f64); -} - -impl From for F32 { - #[inline] - fn from(other: u32) -> Self { - Self::from_bits(other) - } -} - -impl From for u32 { - #[inline] - fn from(other: F32) -> Self { - other.to_bits() - } -} - -impl From for F64 { - #[inline] - fn from(other: u64) -> Self { - Self::from_bits(other) - } -} - -impl From for u64 { - #[inline] - fn from(other: F64) -> Self { - other.to_bits() - } -} - -#[cfg(test)] -mod tests { - extern crate rand; - - use self::rand::Rng; - use super::{F32, F64}; - use core::{ - fmt::Debug, - iter, - ops::{Add, Div, Mul, Neg, Sub}, - }; - - fn test_ops(iter: I) - where - T: Add - + Div - + Mul - + Sub - + Neg - + Copy - + Debug - + PartialEq, - F: Into - + Add - + Div - + Mul - + Sub - + Neg - + Copy - + Debug, - I: IntoIterator, - { - for (a, b) in iter { - assert_eq!((a + b).into(), a.into() + b.into()); - assert_eq!((a - b).into(), a.into() - b.into()); - assert_eq!((a * b).into(), a.into() * b.into()); - assert_eq!((a / b).into(), a.into() / b.into()); - assert_eq!((-a).into(), -a.into()); - assert_eq!((-b).into(), -b.into()); - } - } - - #[test] - fn test_ops_f32() { - let mut rng = rand::thread_rng(); - let iter = iter::repeat(()).map(|_| rng.gen()); - - test_ops::(iter.take(1000)); - } - - #[test] - fn test_ops_f64() { - let mut rng = rand::thread_rng(); - let iter = iter::repeat(()).map(|_| rng.gen()); - - test_ops::(iter.take(1000)); - } - - #[test] - fn test_neg_nan_f32() { - assert_eq!((-F32(0xff80_3210)).0, 0x7f80_3210); - } - - #[test] - fn test_neg_nan_f64() { - assert_eq!((-F64(0xff80_3210_0000_0000)).0, 0x7f80_3210_0000_0000); - } -} diff --git a/legacy/src/core/rwasm.rs b/legacy/src/core/rwasm.rs deleted file mode 100644 index 257a88693..000000000 --- a/legacy/src/core/rwasm.rs +++ /dev/null @@ -1,19 +0,0 @@ -/// This constant is driven by WebAssembly standard, default -/// memory page size is 64kB -pub const N_BYTES_PER_MEMORY_PAGE: u32 = 65536; - -/// We have a hard limit for max possible memory used -/// that is equal to ~64mB -pub const N_MAX_MEMORY_PAGES: u32 = 1024; -/// To optimize proving process we have to limit max -/// number of pages, tables, etc. We found 1024 is enough. -pub const N_MAX_TABLES: usize = 1024; -pub const N_MAX_TABLE_ELEMENTS: u32 = 1024; - -pub const N_MAX_STACK_HEIGHT: usize = 4096; -pub const N_MAX_RECURSION_DEPTH: usize = 1024; - -/// Max possible amount of data segments -pub const N_MAX_DATA_SEGMENTS: usize = 1024; -pub const N_MAX_ELEM_SEGMENTS: usize = 1024; -pub const N_MAX_GLOBALS: usize = 1024; diff --git a/legacy/src/core/trap.rs b/legacy/src/core/trap.rs deleted file mode 100644 index b82748693..000000000 --- a/legacy/src/core/trap.rs +++ /dev/null @@ -1,330 +0,0 @@ -use crate::core::HostError; -use alloc::{boxed::Box, string::String}; -use core::fmt::{self, Display}; -#[cfg(feature = "std")] -use std::error::Error as StdError; - -/// Error type which can be returned by Wasm code or by the host environment. -/// -/// Under some conditions, Wasm execution may produce a [`Trap`], -/// which immediately aborts execution. -/// Traps cannot be handled by WebAssembly code, but are reported to the -/// host embedder. -#[derive(Debug)] -pub struct Trap { - /// The cloneable reason of a [`Trap`]. - reason: Box, -} - -#[test] -fn trap_size() { - assert_eq!( - core::mem::size_of::(), - core::mem::size_of::<*const ()>() - ); -} - -/// The reason of a [`Trap`]. -#[derive(Debug)] -enum TrapReason { - /// Traps during Wasm execution. - InstructionTrap(TrapCode), - /// An `i32` exit status code. - /// - /// # Note - /// - /// This is useful for some WASI functions. - I32Exit(i32), - /// An error decribed by a display message. - Message(Box), - /// Traps and errors during host execution. - Host(Box), -} - -impl TrapReason { - /// Returns the classic `i32` exit program code of a `Trap` if any. - /// - /// Otherwise returns `None`. - pub fn i32_exit_status(&self) -> Option { - if let Self::I32Exit(status) = self { - return Some(*status); - } - None - } - - /// Returns a shared reference to the [`HostError`] if any. - #[inline] - pub fn as_host(&self) -> Option<&dyn HostError> { - if let Self::Host(host_error) = self { - return Some(&**host_error); - } - None - } - - /// Returns an exclusive reference to the [`HostError`] if any. - #[inline] - pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> { - if let Self::Host(host_error) = self { - return Some(&mut **host_error); - } - None - } - - /// Consumes `self` to return the [`HostError`] if any. - #[inline] - pub fn into_host(self) -> Option> { - if let Self::Host(host_error) = self { - return Some(host_error); - } - None - } - - /// Returns the [`TrapCode`] traps originating from Wasm execution. - #[inline] - pub fn trap_code(&self) -> Option { - if let Self::InstructionTrap(trap_code) = self { - return Some(*trap_code); - } - None - } -} - -impl Trap { - /// Create a new [`Trap`] from the [`TrapReason`]. - fn with_reason(reason: TrapReason) -> Self { - Self { - reason: Box::new(reason), - } - } - - /// Creates a new [`Trap`] described by a `message`. - #[cold] // traps are exceptional, this helps move handling off the main path - pub fn new(message: T) -> Self - where - T: Into, - { - Self::with_reason(TrapReason::Message(message.into().into_boxed_str())) - } - - /// Downcasts the [`Trap`] into the `T: HostError` if possible. - /// - /// Returns `None` otherwise. - #[inline] - pub fn downcast_ref(&self) -> Option<&T> - where - T: HostError, - { - self.reason - .as_host() - .and_then(<(dyn HostError + 'static)>::downcast_ref) - } - - /// Downcasts the [`Trap`] into the `T: HostError` if possible. - /// - /// Returns `None` otherwise. - #[inline] - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: HostError, - { - self.reason - .as_host_mut() - .and_then(<(dyn HostError + 'static)>::downcast_mut) - } - - /// Consumes `self` to downcast the [`Trap`] into the `T: HostError` if possible. - /// - /// Returns `None` otherwise. - #[inline] - pub fn downcast(self) -> Option - where - T: HostError, - { - self.reason - .into_host() - .and_then(|error| error.downcast().ok()) - .map(|boxed| *boxed) - } - - /// Creates a new `Trap` representing an explicit program exit with a classic `i32` - /// exit status value. - #[cold] // see Trap::new - pub fn i32_exit(status: i32) -> Self { - Self::with_reason(TrapReason::I32Exit(status)) - } - - /// Returns the classic `i32` exit program code of a `Trap` if any. - /// - /// Otherwise returns `None`. - #[inline] - pub fn i32_exit_status(&self) -> Option { - self.reason.i32_exit_status() - } - - /// Returns the [`TrapCode`] traps originating from Wasm execution. - #[inline] - pub fn trap_code(&self) -> Option { - self.reason.trap_code() - } -} - -impl From for Trap { - #[cold] // see Trap::new - fn from(error: TrapCode) -> Self { - Self::with_reason(TrapReason::InstructionTrap(error)) - } -} - -impl From for Trap -where - E: HostError, -{ - #[inline] - #[cold] // see Trap::new - fn from(host_error: E) -> Self { - Self::with_reason(TrapReason::Host(Box::new(host_error))) - } -} - -impl Display for TrapReason { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InstructionTrap(trap_code) => Display::fmt(trap_code, f), - Self::I32Exit(status) => write!(f, "Exited with i32 exit status {status}"), - Self::Message(message) => write!(f, "{message}"), - Self::Host(host_error) => Display::fmt(host_error, f), - } - } -} - -impl Display for Trap { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self.reason, f) - } -} - -#[cfg(feature = "std")] -impl StdError for Trap { - fn description(&self) -> &str { - self.trap_code().map_or("", |code| code.trap_message()) - } -} - -/// Error type which can be thrown by wasm code or by host environment. -/// -/// See [`Trap`] for details. -/// -/// [`Trap`]: struct.Trap.html -#[derive(Debug, Copy, Clone)] -pub enum TrapCode { - /// Wasm code executed `unreachable` opcode. - /// - /// This indicates that unreachable Wasm code was actually reached. - /// This opcode have a similar purpose as `ud2` in x86. - UnreachableCodeReached, - - /// Attempt to load or store at the address which - /// lies outside of bounds of the memory. - /// - /// Since addresses are interpreted as unsigned integers, out of bounds access - /// can't happen with negative addresses (i.e. they will always wrap). - MemoryOutOfBounds, - - /// Attempt to access table element at index which - /// lies outside of bounds. - /// - /// This typically can happen when `call_indirect` is executed - /// with index that lies out of bounds. - /// - /// Since indexes are interpreted as unsigned integers, out of bounds access - /// can't happen with negative indexes (i.e. they will always wrap). - TableOutOfBounds, - - /// Indicates that a `call_indirect` instruction called a function at - /// an uninitialized (i.e. `null`) table index. - IndirectCallToNull, - - /// Attempt to divide by zero. - /// - /// This trap typically can happen if `div` or `rem` is executed with - /// zero as divider. - IntegerDivisionByZero, - - /// An integer arithmetic operation caused an overflow. - /// - /// This can happen when trying to do signed division (or get the remainder) - /// -2N-1 over -1. This is because the result +2N-1 - /// isn't representable as a N-bit signed integer. - IntegerOverflow, - - /// Attempted to make an invalid conversion to an integer type. - /// - /// This can for example happen when trying to truncate NaNs, - /// infinity, or value for which the result is out of range into an integer. - BadConversionToInteger, - - /// Stack overflow. - /// - /// This is likely caused by some infinite or very deep recursion. - /// Extensive inlining might also be the cause of stack overflow. - StackOverflow, - - /// Attempt to invoke a function with mismatching signature. - /// - /// This can happen with indirect calls as they always - /// specify the expected signature of function. If an indirect call is executed - /// with an index that points to a function with signature different of what is - /// expected by this indirect call, this trap is raised. - BadSignature, - - /// This trap is raised when a WebAssembly execution ran out of fuel. - /// - /// The `wasmi` execution engine can be configured to instrument its - /// internal bytecode so that fuel is consumed for each executed instruction. - /// This is useful to deterministically halt or yield a WebAssembly execution. - OutOfFuel, - - /// This trap is raised when a growth operation was attempted and an - /// installed `wasmi::ResourceLimiter` returned `Err(...)` from the - /// associated `table_growing` or `memory_growing` method, indicating a - /// desire on the part of the embedder to trap the interpreter rather than - /// merely fail the growth operation. - GrowthOperationLimited, - - /// This error happens when we can't resolve function by its offset, usually - /// it should never happen. Maybe it's better to think how to replace this - /// error with panic. - UnresolvedFunction, -} - -impl TrapCode { - /// Returns the trap message as specified by the WebAssembly specification. - /// - /// # Note - /// - /// This API is primarily useful for the Wasm spec testsuite but might have - /// other uses since it avoid heap memory allocation in certain cases. - pub fn trap_message(&self) -> &'static str { - match self { - Self::UnreachableCodeReached => "wasm `unreachable` instruction executed", - Self::MemoryOutOfBounds => "out of bounds memory access", - Self::TableOutOfBounds => "undefined element: out of bounds table access", - Self::IndirectCallToNull => "uninitialized element 2", /* TODO: fixme, remove the */ - // trailing " 2" again - Self::IntegerDivisionByZero => "integer divide by zero", - Self::IntegerOverflow => "integer overflow", - Self::BadConversionToInteger => "invalid conversion to integer", - Self::StackOverflow => "call stack exhausted", - Self::BadSignature => "indirect call type mismatch", - Self::OutOfFuel => "all fuel consumed by WebAssembly", - Self::GrowthOperationLimited => "growth operation limited", - Self::UnresolvedFunction => "unresolved function by offset", - } - } -} - -impl Display for TrapCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.trap_message()) - } -} diff --git a/legacy/src/core/units.rs b/legacy/src/core/units.rs deleted file mode 100644 index a087fd07c..000000000 --- a/legacy/src/core/units.rs +++ /dev/null @@ -1,320 +0,0 @@ -/// An amount of linear memory pages. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct Pages(u32); - -impl Pages { - /// The maximum amount of pages on the `wasm32` target. - /// - /// # Note - /// - /// This is the maximum since WebAssembly is a 32-bit platform - /// and a page is 2^16 bytes in size. Therefore there can be at - /// most 2^16 pages of a single linear memory so that all bytes - /// are still accessible. - pub const fn max() -> Self { - Self(65536) // 2^16 - } - - pub fn into_inner(self) -> u32 { - self.0 - } -} - -impl From for Pages { - /// Creates an `amount` of [`Pages`]. - /// - /// # Note - /// - /// This is infallible since `u16` cannot represent invalid amounts - /// of [`Pages`]. However, `u16` can also not represent [`Pages::max()`]. - /// - /// [`Pages::max()`]: struct.Pages.html#method.max - fn from(amount: u16) -> Self { - Self(u32::from(amount)) - } -} - -impl Pages { - /// Creates a new amount of [`Pages`] if the amount is within bounds. - /// - /// Returns `None` if the given `amount` of [`Pages`] exceeds [`Pages::max()`]. - /// - /// [`Pages::max()`]: struct.Pages.html#method.max - pub fn new(amount: u32) -> Option { - if amount > u32::from(Self::max()) { - return None; - } - Some(Self(amount)) - } - - /// Adds the given amount of pages to `self`. - /// - /// Returns `Some` if the result is within bounds and `None` otherwise. - pub fn checked_add(self, rhs: T) -> Option - where - T: Into, - { - let lhs: u32 = self.into(); - let rhs: u32 = rhs.into(); - lhs.checked_add(rhs).and_then(Self::new) - } - - /// Substracts the given amount of pages from `self`. - /// - /// Returns `None` if the subtraction underflows or the result is out of bounds. - pub fn checked_sub(self, rhs: T) -> Option - where - T: Into, - { - let lhs: u32 = self.into(); - let rhs: u32 = rhs.into(); - lhs.checked_sub(rhs).and_then(Self::new) - } - - /// Returns the amount of bytes required for the amount of [`Pages`]. - /// - /// Returns `None` if the amount of pages represented by `self` cannot - /// be represented as bytes on the executing platform. - pub fn to_bytes(self) -> Option { - Bytes::new(self).map(Into::into) - } -} - -impl From for u32 { - fn from(pages: Pages) -> Self { - pages.0 - } -} - -/// An amount of bytes of a linear memory. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct Bytes(usize); - -impl Bytes { - /// A 16-bit platform cannot represent the size of a single Wasm page. - const fn max16() -> u64 { - i16::MAX as u64 + 1 - } - - /// A 32-bit platform can represent at most i32::MAX + 1 Wasm pages. - const fn max32() -> u64 { - i32::MAX as u64 + 1 - } - - /// A 64-bit platform can represent all possible u32::MAX + 1 Wasm pages. - const fn max64() -> u64 { - u32::MAX as u64 + 1 - } - - /// The bytes per WebAssembly linear memory page. - /// - /// # Note - /// - /// As mandated by the WebAssembly specification every linear memory page - /// has exactly 2^16 (65536) bytes. - pub const fn per_page() -> Self { - Self(65536) // 2^16 - } - - /// Creates [`Bytes`] from the given amount of [`Pages`] if possible. - /// - /// Returns `None` if the amount of bytes is out of bounds. This may - /// happen for example when trying to allocate bytes for more than - /// `i16::MAX + 1` pages on a 32-bit platform since that amount would - /// not be representable by a pointer sized `usize`. - fn new(pages: Pages) -> Option { - if cfg!(target_pointer_width = "16") { - Self::new16(pages) - } else if cfg!(target_pointer_width = "32") { - Self::new32(pages) - } else if cfg!(target_pointer_width = "64") { - Self::new64(pages) - } else { - None - } - } - - /// Creates [`Bytes`] from the given amount of [`Pages`] as if - /// on a 16-bit platform if possible. - /// - /// Returns `None` otherwise. - /// - /// # Note - /// - /// This API exists in isolation for cross-platform testing purposes. - fn new16(pages: Pages) -> Option { - Self::new_impl(pages, Bytes::max16()) - } - - /// Creates [`Bytes`] from the given amount of [`Pages`] as if - /// on a 32-bit platform if possible. - /// - /// Returns `None` otherwise. - /// - /// # Note - /// - /// This API exists in isolation for cross-platform testing purposes. - fn new32(pages: Pages) -> Option { - Self::new_impl(pages, Bytes::max32()) - } - - /// Creates [`Bytes`] from the given amount of [`Pages`] as if - /// on a 64-bit platform if possible. - /// - /// Returns `None` otherwise. - /// - /// # Note - /// - /// This API exists in isolation for cross-platform testing purposes. - fn new64(pages: Pages) -> Option { - Self::new_impl(pages, Bytes::max64()) - } - - /// Actual underlying implementation of [`Bytes::new`]. - fn new_impl(pages: Pages, max: u64) -> Option { - let pages = u64::from(u32::from(pages)); - let bytes_per_page = usize::from(Self::per_page()) as u64; - let bytes = pages - .checked_mul(bytes_per_page) - .filter(|&amount| amount <= max)?; - Some(Self(bytes as usize)) - } -} - -impl From for usize { - #[inline] - fn from(bytes: Bytes) -> Self { - bytes.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn pages(amount: u32) -> Pages { - Pages::new(amount).unwrap() - } - - fn bytes(amount: usize) -> Bytes { - Bytes(amount) - } - - #[test] - fn pages_max() { - assert_eq!(Pages::max(), pages(u32::from(u16::MAX) + 1)); - } - - #[test] - fn pages_new() { - assert_eq!(Pages::new(0), Some(Pages(0))); - assert_eq!(Pages::new(1), Some(Pages(1))); - assert_eq!(Pages::new(1000), Some(Pages(1000))); - assert_eq!( - Pages::new(u32::from(u16::MAX)), - Some(Pages(u32::from(u16::MAX))) - ); - assert_eq!(Pages::new(u32::from(u16::MAX) + 1), Some(Pages::max())); - assert_eq!(Pages::new(u32::from(u16::MAX) + 2), None); - assert_eq!(Pages::new(u32::MAX), None); - } - - #[test] - fn pages_checked_add() { - let max_pages = u32::from(Pages::max()); - - assert_eq!(pages(0).checked_add(0u32), Some(pages(0))); - assert_eq!(pages(0).checked_add(1u32), Some(pages(1))); - assert_eq!(pages(1).checked_add(0u32), Some(pages(1))); - - assert_eq!(pages(0).checked_add(max_pages), Some(Pages::max())); - assert_eq!(pages(0).checked_add(Pages::max()), Some(Pages::max())); - assert_eq!(pages(1).checked_add(max_pages), None); - assert_eq!(pages(1).checked_add(Pages::max()), None); - - assert_eq!(Pages::max().checked_add(0u32), Some(Pages::max())); - assert_eq!(Pages::max().checked_add(1u32), None); - assert_eq!(pages(0).checked_add(u32::MAX), None); - - for i in 0..100 { - for j in 0..100 { - assert_eq!(pages(i).checked_add(pages(j)), Some(pages(i + j))); - } - } - } - - #[test] - fn pages_checked_sub() { - let max_pages = u32::from(Pages::max()); - - assert_eq!(pages(0).checked_sub(0u32), Some(pages(0))); - assert_eq!(pages(0).checked_sub(1u32), None); - assert_eq!(pages(1).checked_sub(0u32), Some(pages(1))); - assert_eq!(pages(1).checked_sub(1u32), Some(pages(0))); - - assert_eq!(Pages::max().checked_sub(Pages::max()), Some(pages(0))); - assert_eq!(Pages::max().checked_sub(u32::MAX), None); - assert_eq!(Pages::max().checked_sub(1u32), Some(pages(max_pages - 1))); - - for i in 0..100 { - for j in 0..100 { - assert_eq!(pages(i).checked_sub(pages(j)), i.checked_sub(j).map(pages)); - } - } - } - - #[test] - fn pages_to_bytes() { - assert_eq!(pages(0).to_bytes(), Some(0)); - if cfg!(target_pointer_width = "16") { - assert_eq!(pages(1).to_bytes(), None); - } - if cfg!(target_pointer_width = "32") || cfg!(target_pointer_width = "64") { - let bytes_per_page = usize::from(Bytes::per_page()); - for n in 1..10 { - assert_eq!(pages(n as u32).to_bytes(), Some(n * bytes_per_page)); - } - } - } - - #[test] - fn bytes_new16() { - assert_eq!(Bytes::new16(pages(0)), Some(bytes(0))); - assert_eq!(Bytes::new16(pages(1)), None); - assert!(Bytes::new16(Pages::max()).is_none()); - } - - #[test] - fn bytes_new32() { - assert_eq!(Bytes::new32(pages(0)), Some(bytes(0))); - assert_eq!(Bytes::new32(pages(1)), Some(Bytes::per_page())); - let bytes_per_page = usize::from(Bytes::per_page()); - for n in 2..10 { - assert_eq!( - Bytes::new32(pages(n as u32)), - Some(bytes(n * bytes_per_page)) - ); - } - assert!(Bytes::new32(pages(i16::MAX as u32 + 1)).is_some()); - assert!(Bytes::new32(pages(i16::MAX as u32 + 2)).is_none()); - assert!(Bytes::new32(Pages::max()).is_none()); - } - - #[test] - fn bytes_new64() { - assert_eq!(Bytes::new64(pages(0)), Some(bytes(0))); - assert_eq!(Bytes::new64(pages(1)), Some(Bytes::per_page())); - let bytes_per_page = usize::from(Bytes::per_page()); - for n in 2..10 { - assert_eq!( - Bytes::new64(pages(n as u32)), - Some(bytes(n * bytes_per_page)) - ); - } - assert!(Bytes::new64(Pages(u32::from(u16::MAX) + 1)).is_some()); - assert!(Bytes::new64(Pages(u32::from(u16::MAX) + 2)).is_none()); - assert!(Bytes::new64(Pages::max()).is_some()); - } -} diff --git a/legacy/src/core/untyped.rs b/legacy/src/core/untyped.rs deleted file mode 100644 index 26cb701d2..000000000 --- a/legacy/src/core/untyped.rs +++ /dev/null @@ -1,1655 +0,0 @@ -use crate::{ - core::{ - value::{LoadInto, StoreFrom}, - ArithmeticOps, - ExtendInto, - Float, - Integer, - LittleEndianConvert, - SignExtendFrom, - TrapCode, - TruncateSaturateInto, - TryTruncateInto, - ValueType, - WrapInto, - F32, - F64, - }, - value::split_i64_to_i32, -}; -use alloc::vec::Vec; -use core::{ - fmt::{self, Display, Formatter}, - ops::{Neg, Shl, Shr}, -}; -use paste::paste; - -/// An untyped value. -/// -/// Provides a dense and simple interface to all functional Wasm operations. -#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct UntypedValue { - /// This inner value is required to have enough bits to represent - /// all fundamental WebAssembly types `i32`, `i64`, `f32` and `f64`. - bits: u64, -} - -impl Display for UntypedValue { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let name = format!("{:?}", self.bits); - write!(f, "{}", name) - } -} - -impl UntypedValue { - pub const fn from_bits(bits: u64) -> Self { - Self { bits } - } - /// Returns the underlying bits of the [`UntypedValue`]. - pub const fn to_bits(self) -> u64 { - self.bits - } -} - -macro_rules! impl_from_untyped_for_int { - ( $( $int:ty ),* $(,)? ) => { - $( - impl From for $int { - fn from(untyped: UntypedValue) -> Self { - untyped.to_bits() as _ - } - } - )* - }; -} -impl_from_untyped_for_int!(i8, i16, i32, i64, u8, u16, u32, u64); - -macro_rules! impl_from_untyped_for_float { - ( $( $float:ty ),* $(,)? ) => { - $( - impl From for $float { - fn from(untyped: UntypedValue) -> Self { - Self::from_bits(untyped.to_bits() as _) - } - } - )* - }; -} -impl_from_untyped_for_float!(f32, f64, F32, F64); - -impl From for bool { - fn from(untyped: UntypedValue) -> Self { - untyped.to_bits() != 0 - } -} - -macro_rules! impl_from_unsigned_prim { - ( $( $prim:ty ),* $(,)? ) => { - $( - impl From<$prim> for UntypedValue { - fn from(value: $prim) -> Self { - Self { bits: value as _ } - } - } - )* - }; -} -#[rustfmt::skip] -impl_from_unsigned_prim!( - bool, u8, u16, u32, u64, usize, -); - -macro_rules! impl_from_signed_prim { - ( $( $prim:ty as $base:ty ),* $(,)? ) => { - $( - impl From<$prim> for UntypedValue { - fn from(value: $prim) -> Self { - Self { bits: value as $base as _ } - } - } - )* - }; -} -#[rustfmt::skip] -impl_from_signed_prim!( - i8 as u8, - i16 as u16, - i32 as u32, - i64 as u64, -); - -macro_rules! impl_from_float { - ( $( $float:ty ),* $(,)? ) => { - $( - impl From<$float> for UntypedValue { - fn from(value: $float) -> Self { - Self { - bits: value.to_bits() as _, - } - } - } - )* - }; -} -impl_from_float!(f32, f64, F32, F64); - -macro_rules! op { - ( $operator:tt ) => {{ - |lhs, rhs| lhs $operator rhs - }}; -} - -/// Calculates the effective address of a linear memory access. -/// -/// # Errors -/// -/// If the resulting effective address overflows. -fn effective_address(address: u32, offset: u32) -> Result { - offset - .checked_add(address) - .map(|address| address as usize) - .ok_or(TrapCode::MemoryOutOfBounds) -} - -impl UntypedValue { - /// Executes a generic `T.loadN_[s|u]` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - fn load_extend(memory: &[u8], address: Self, offset: u32) -> Result - where - T: Into, - U: LittleEndianConvert + ExtendInto, - { - let raw_address = u32::from(address); - let address = effective_address(raw_address, offset)?; - let mut buffer = <::Bytes as Default>::default(); - buffer.load_into(memory, address)?; - let value: Self = ::from_le_bytes(buffer) - .extend_into() - .into(); - Ok(value) - } - - /// Executes a generic `T.load` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - fn load(memory: &[u8], address: Self, offset: u32) -> Result - where - T: LittleEndianConvert + ExtendInto + Into, - { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i32.load` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i32_load(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load::(memory, address, offset) - } - - /// Executes the `i64.load` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load::(memory, address, offset) - } - - /// Executes the `f32.load` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn f32_load(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load::(memory, address, offset) - } - - /// Executes the `f64.load` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn f64_load(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load::(memory, address, offset) - } - - /// Executes the `i32.load8_s` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i32_load8_s(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i32.load8_u` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i32_load8_u(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i32.load16_s` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i32_load16_s(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i32.load16_u` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i32_load16_u(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i64.load8_s` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load8_s(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i64.load8_u` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load8_u(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i64.load16_s` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load16_s(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i64.load16_u` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load16_u(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i64.load32_s` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load32_s(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes the `i64.load32_u` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` loads out of bounds from `memory`. - pub fn i64_load32_u(memory: &[u8], address: Self, offset: u32) -> Result { - Self::load_extend::(memory, address, offset) - } - - /// Executes a generic `T.store[N]` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - fn store_wrap( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> - where - T: From + WrapInto, - U: LittleEndianConvert, - { - let raw_address = u32::from(address); - let address = effective_address(raw_address, offset)?; - let wrapped = T::from(value).wrap_into(); - let buffer = ::into_le_bytes(wrapped); - buffer.store_from(memory, address)?; - Ok(()) - } - - /// Executes a generic `T.store` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - fn store(memory: &mut [u8], address: Self, offset: u32, value: Self) -> Result<(), TrapCode> - where - T: From + WrapInto + LittleEndianConvert, - { - Self::store_wrap::(memory, address, offset, value) - } - - /// Executes the `i32.store` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i32_store( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store::(memory, address, offset, value) - } - - /// Executes the `i64.store` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i64_store( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store::(memory, address, offset, value) - } - - /// Executes the `f32.store` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn f32_store( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store::(memory, address, offset, value) - } - - /// Executes the `f64.store` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn f64_store( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store::(memory, address, offset, value) - } - - /// Executes the `i32.store8` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i32_store8( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store_wrap::(memory, address, offset, value) - } - - /// Executes the `i32.store16` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i32_store16( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store_wrap::(memory, address, offset, value) - } - - /// Executes the `i64.store8` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i64_store8( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store_wrap::(memory, address, offset, value) - } - - /// Executes the `i64.store16` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i64_store16( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store_wrap::(memory, address, offset, value) - } - - /// Executes the `i64.store32` Wasm operation. - /// - /// # Errors - /// - /// - If `address + offset` overflows. - /// - If `address + offset` stores out of bounds from `memory`. - pub fn i64_store32( - memory: &mut [u8], - address: Self, - offset: u32, - value: Self, - ) -> Result<(), TrapCode> { - Self::store_wrap::(memory, address, offset, value) - } - - /// Execute an infallible generic operation on `T` that returns an `R`. - fn execute_unary(self, op: fn(T) -> R) -> Self - where - T: From, - R: Into, - { - op(T::from(self)).into() - } - - /// Execute an infallible generic operation on `T` that returns an `R`. - fn try_execute_unary(self, op: fn(T) -> Result) -> Result - where - T: From, - R: Into, - { - op(T::from(self)).map(Into::into) - } - - /// Execute an infallible generic operation on `T` that returns an `R`. - fn execute_binary(self, rhs: Self, op: fn(T, T) -> R) -> Self - where - T: From, - R: Into, - { - op(T::from(self), T::from(rhs)).into() - } - - /// Execute a fallible generic operation on `T` that returns an `R`. - fn try_execute_binary( - self, - rhs: Self, - op: fn(T, T) -> Result, - ) -> Result - where - T: From, - R: Into, - { - op(T::from(self), T::from(rhs)).map(Into::into) - } - - /// Execute `i32.add` Wasm operation. - pub fn i32_add(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::add) - } - - /// Execute `i64.add` Wasm operation. - pub fn i64_add(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::add) - } - - /// Execute `i32.sub` Wasm operation. - pub fn i32_sub(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::sub) - } - - /// Execute `i64.sub` Wasm operation. - pub fn i64_sub(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::sub) - } - - /// Execute `i32.mul` Wasm operation. - pub fn i32_mul(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::mul) - } - - /// Execute `i64.mul` Wasm operation. - pub fn i64_mul(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::mul) - } - - /// Execute `i32.div_s` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i32_div_s(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::div) - } - - /// Execute `i64.div_s` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i64_div_s(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::div) - } - - /// Execute `i32.div_u` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i32_div_u(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::div) - } - - /// Execute `i64.div_u` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i64_div_u(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::div) - } - - /// Execute `i32.rem_s` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i32_rem_s(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::rem) - } - - /// Execute `i64.rem_s` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i64_rem_s(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::rem) - } - - /// Execute `i32.rem_u` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i32_rem_u(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::rem) - } - - /// Execute `i64.rem_u` Wasm operation. - /// - /// # Errors - /// - /// - If `rhs` is equal to zero. - /// - If the operation result overflows. - pub fn i64_rem_u(self, rhs: Self) -> Result { - self.try_execute_binary(rhs, >::rem) - } - - /// Execute `i32.and` Wasm operation. - pub fn i32_and(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(&)) - } - - /// Execute `i64.and` Wasm operation. - pub fn i64_and(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(&)) - } - - /// Execute `i32.or` Wasm operation. - pub fn i32_or(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(|)) - } - - /// Execute `i64.or` Wasm operation. - pub fn i64_or(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(|)) - } - - /// Execute `i32.xor` Wasm operation. - pub fn i32_xor(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(^)) - } - - /// Execute `i64.xor` Wasm operation. - pub fn i64_xor(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(^)) - } - - /// Execute `i32.shl` Wasm operation. - pub fn i32_shl(self, rhs: Self) -> Self { - self.execute_binary::(rhs, |lhs, rhs| lhs.shl(rhs & 0x1F)) - } - - /// Execute `i64.shl` Wasm operation. - pub fn i64_shl(self, rhs: Self) -> Self { - self.execute_binary::(rhs, |lhs, rhs| lhs.shl(rhs & 0x3F)) - } - - /// Execute `i32.shr_s` Wasm operation. - pub fn i32_shr_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x1F)) - } - - /// Execute `i64.shr_s` Wasm operation. - pub fn i64_shr_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x3F)) - } - - /// Execute `i32.shr_u` Wasm operation. - pub fn i32_shr_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x1F)) - } - - /// Execute `i64.shr_u` Wasm operation. - pub fn i64_shr_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x3F)) - } - - /// Execute `i32.clz` Wasm operation. - pub fn i32_clz(self) -> Self { - self.execute_unary(>::leading_zeros) - } - - /// Execute `i64.clz` Wasm operation. - pub fn i64_clz(self) -> Self { - self.execute_unary(>::leading_zeros) - } - - /// Execute `i32.ctz` Wasm operation. - pub fn i32_ctz(self) -> Self { - self.execute_unary(>::trailing_zeros) - } - - /// Execute `i64.ctz` Wasm operation. - pub fn i64_ctz(self) -> Self { - self.execute_unary(>::trailing_zeros) - } - - /// Execute `i32.popcnt` Wasm operation. - pub fn i32_popcnt(self) -> Self { - self.execute_unary(>::count_ones) - } - - /// Execute `i64.popcnt` Wasm operation. - pub fn i64_popcnt(self) -> Self { - self.execute_unary(>::count_ones) - } - - /// Execute `i32.rotl` Wasm operation. - pub fn i32_rotl(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::rotl) - } - - /// Execute `i64.rotl` Wasm operation. - pub fn i64_rotl(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::rotl) - } - - /// Execute `i32.rotr` Wasm operation. - pub fn i32_rotr(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::rotr) - } - - /// Execute `i64.rotr` Wasm operation. - pub fn i64_rotr(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::rotr) - } - - /// Execute `i32.eqz` Wasm operation. - pub fn i32_eqz(self) -> Self { - self.execute_unary::(|value| value == 0) - } - - /// Execute `i64.eqz` Wasm operation. - pub fn i64_eqz(self) -> Self { - self.execute_unary::(|value| value == 0) - } - - /// Execute `i32.eq` Wasm operation. - pub fn i32_eq(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(==)) - } - - /// Execute `i64.eq` Wasm operation. - pub fn i64_eq(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(==)) - } - - /// Execute `f32.eq` Wasm operation. - pub fn f32_eq(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(==)) - } - - /// Execute `f64.eq` Wasm operation. - pub fn f64_eq(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(==)) - } - - /// Execute `i32.ne` Wasm operation. - pub fn i32_ne(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(!=)) - } - - /// Execute `i64.ne` Wasm operation. - pub fn i64_ne(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(!=)) - } - - /// Execute `f32.ne` Wasm operation. - pub fn f32_ne(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(!=)) - } - - /// Execute `f64.ne` Wasm operation. - pub fn f64_ne(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(!=)) - } - - /// Execute `i32.lt_s` Wasm operation. - pub fn i32_lt_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<)) - } - - /// Execute `i64.lt_s` Wasm operation. - pub fn i64_lt_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<)) - } - - /// Execute `i32.lt_u` Wasm operation. - pub fn i32_lt_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<)) - } - - /// Execute `i64.lt_u` Wasm operation. - pub fn i64_lt_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<)) - } - - /// Execute `f32.lt` Wasm operation. - pub fn f32_lt(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<)) - } - - /// Execute `f64.lt` Wasm operation. - pub fn f64_lt(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<)) - } - - /// Execute `i32.le_s` Wasm operation. - pub fn i32_le_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<=)) - } - - /// Execute `i64.le_s` Wasm operation. - pub fn i64_le_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<=)) - } - - /// Execute `i32.le_u` Wasm operation. - pub fn i32_le_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<=)) - } - - /// Execute `i64.le_u` Wasm operation. - pub fn i64_le_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<=)) - } - - /// Execute `f32.le` Wasm operation. - pub fn f32_le(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<=)) - } - - /// Execute `f64.le` Wasm operation. - pub fn f64_le(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(<=)) - } - - /// Execute `i32.gt_s` Wasm operation. - pub fn i32_gt_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>)) - } - - /// Execute `i64.gt_s` Wasm operation. - pub fn i64_gt_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>)) - } - - /// Execute `i32.gt_u` Wasm operation. - pub fn i32_gt_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>)) - } - - /// Execute `i64.gt_u` Wasm operation. - pub fn i64_gt_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>)) - } - - /// Execute `f32.gt` Wasm operation. - pub fn f32_gt(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>)) - } - - /// Execute `f64.gt` Wasm operation. - pub fn f64_gt(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>)) - } - - /// Execute `i32.ge_s` Wasm operation. - pub fn i32_ge_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>=)) - } - - /// Execute `i64.ge_s` Wasm operation. - pub fn i64_ge_s(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>=)) - } - - /// Execute `i32.ge_u` Wasm operation. - pub fn i32_ge_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>=)) - } - - /// Execute `i64.ge_u` Wasm operation. - pub fn i64_ge_u(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>=)) - } - - /// Execute `f32.ge` Wasm operation. - pub fn f32_ge(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>=)) - } - - /// Execute `f64.ge` Wasm operation. - pub fn f64_ge(self, rhs: Self) -> Self { - self.execute_binary::(rhs, op!(>=)) - } - - /// Execute `f32.abs` Wasm operation. - pub fn f32_abs(self) -> Self { - self.execute_unary(>::abs) - } - - /// Execute `f32.neg` Wasm operation. - pub fn f32_neg(self) -> Self { - self.execute_unary(::neg) - } - - /// Execute `f32.ceil` Wasm operation. - pub fn f32_ceil(self) -> Self { - self.execute_unary(>::ceil) - } - - /// Execute `f32.floor` Wasm operation. - pub fn f32_floor(self) -> Self { - self.execute_unary(>::floor) - } - - /// Execute `f32.trunc` Wasm operation. - pub fn f32_trunc(self) -> Self { - self.execute_unary(>::trunc) - } - - /// Execute `f32.nearest` Wasm operation. - pub fn f32_nearest(self) -> Self { - self.execute_unary(>::nearest) - } - - /// Execute `f32.sqrt` Wasm operation. - pub fn f32_sqrt(self) -> Self { - self.execute_unary(>::sqrt) - } - - /// Execute `f32.min` Wasm operation. - pub fn f32_min(self, other: Self) -> Self { - self.execute_binary(other, >::min) - } - - /// Execute `f32.max` Wasm operation. - pub fn f32_max(self, other: Self) -> Self { - self.execute_binary(other, >::max) - } - - /// Execute `f32.copysign` Wasm operation. - pub fn f32_copysign(self, other: Self) -> Self { - self.execute_binary(other, >::copysign) - } - - /// Execute `f64.abs` Wasm operation. - pub fn f64_abs(self) -> Self { - self.execute_unary(>::abs) - } - - /// Execute `f64.neg` Wasm operation. - pub fn f64_neg(self) -> Self { - self.execute_unary(::neg) - } - - /// Execute `f64.ceil` Wasm operation. - pub fn f64_ceil(self) -> Self { - self.execute_unary(>::ceil) - } - - /// Execute `f64.floor` Wasm operation. - pub fn f64_floor(self) -> Self { - self.execute_unary(>::floor) - } - - /// Execute `f64.trunc` Wasm operation. - pub fn f64_trunc(self) -> Self { - self.execute_unary(>::trunc) - } - - /// Execute `f64.nearest` Wasm operation. - pub fn f64_nearest(self) -> Self { - self.execute_unary(>::nearest) - } - - /// Execute `f64.sqrt` Wasm operation. - pub fn f64_sqrt(self) -> Self { - self.execute_unary(>::sqrt) - } - - /// Execute `f32.add` Wasm operation. - pub fn f32_add(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::add) - } - - /// Execute `f64.add` Wasm operation. - pub fn f64_add(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::add) - } - - /// Execute `f32.sub` Wasm operation. - pub fn f32_sub(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::sub) - } - - /// Execute `f64.sub` Wasm operation. - pub fn f64_sub(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::sub) - } - - /// Execute `f32.mul` Wasm operation. - pub fn f32_mul(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::mul) - } - - /// Execute `f64.mul` Wasm operation. - pub fn f64_mul(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::mul) - } - - /// Execute `f32.div` Wasm operation. - pub fn f32_div(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::div) - } - - /// Execute `f64.div` Wasm operation. - pub fn f64_div(self, rhs: Self) -> Self { - self.execute_binary(rhs, >::div) - } - - /// Execute `f64.min` Wasm operation. - pub fn f64_min(self, other: Self) -> Self { - self.execute_binary(other, >::min) - } - - /// Execute `f64.max` Wasm operation. - pub fn f64_max(self, other: Self) -> Self { - self.execute_binary(other, >::max) - } - - /// Execute `f64.copysign` Wasm operation. - pub fn f64_copysign(self, other: Self) -> Self { - self.execute_binary(other, >::copysign) - } - - /// Execute `i32.wrap_i64` Wasm operation. - pub fn i32_wrap_i64(self) -> Self { - self.execute_unary(>::wrap_into) - } - - /// Execute `i32.trunc_f32_s` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i32_trunc_f32_s(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i32.trunc_f32_u` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i32_trunc_f32_u(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i32.trunc_f64_s` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i32_trunc_f64_s(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i32.trunc_f64_u` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i32_trunc_f64_u(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i64.extend_i32_s` Wasm operation. - pub fn i64_extend_i32_s(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `i64.extend_i32_u` Wasm operation. - pub fn i64_extend_i32_u(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `i64.trunc_f32_s` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i64_trunc_f32_s(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i64.trunc_f32_u` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i64_trunc_f32_u(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i64.trunc_f64_s` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i64_trunc_f64_s(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `i64.trunc_f64_u` Wasm operation. - /// - /// # Errors - /// - /// - If `self` is NaN (not a number). - /// - If `self` is positive or negative infinity. - /// - If the integer value of `self` is out of bounds of the target type. - /// - /// Read more about the failure cases in the [WebAssembly specification]. - /// - /// [WebAssembly specification]: - /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s - pub fn i64_trunc_f64_u(self) -> Result { - self.try_execute_unary(>::try_truncate_into) - } - - /// Execute `f32.convert_i32_s` Wasm operation. - pub fn f32_convert_i32_s(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `f32.convert_i32_u` Wasm operation. - pub fn f32_convert_i32_u(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `f32.convert_i64_s` Wasm operation. - pub fn f32_convert_i64_s(self) -> Self { - self.execute_unary(>::wrap_into) - } - - /// Execute `f32.convert_i64_u` Wasm operation. - pub fn f32_convert_i64_u(self) -> Self { - self.execute_unary(>::wrap_into) - } - - /// Execute `f32.demote_f64` Wasm operation. - pub fn f32_demote_f64(self) -> Self { - self.execute_unary(>::wrap_into) - } - - /// Execute `f64.convert_i32_s` Wasm operation. - pub fn f64_convert_i32_s(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `f64.convert_i32_u` Wasm operation. - pub fn f64_convert_i32_u(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `f64.convert_i64_s` Wasm operation. - pub fn f64_convert_i64_s(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `f64.convert_i64_u` Wasm operation. - pub fn f64_convert_i64_u(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `f64.promote_f32` Wasm operation. - pub fn f64_promote_f32(self) -> Self { - self.execute_unary(>::extend_into) - } - - /// Execute `i32.extend8_s` Wasm operation. - pub fn i32_extend8_s(self) -> Self { - self.execute_unary(>::sign_extend_from) - } - - /// Execute `i32.extend16_s` Wasm operation. - pub fn i32_extend16_s(self) -> Self { - self.execute_unary(>::sign_extend_from) - } - - /// Execute `i64.extend8_s` Wasm operation. - pub fn i64_extend8_s(self) -> Self { - self.execute_unary(>::sign_extend_from) - } - - /// Execute `i64.extend16_s` Wasm operation. - pub fn i64_extend16_s(self) -> Self { - self.execute_unary(>::sign_extend_from) - } - - /// Execute `i64.extend32_s` Wasm operation. - pub fn i64_extend32_s(self) -> Self { - self.execute_unary(>::sign_extend_from) - } - - /// Execute `i32.trunc_sat_f32_s` Wasm operation. - pub fn i32_trunc_sat_f32_s(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i32.trunc_sat_f32_u` Wasm operation. - pub fn i32_trunc_sat_f32_u(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i32.trunc_sat_f64_s` Wasm operation. - pub fn i32_trunc_sat_f64_s(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i32.trunc_sat_f64_u` Wasm operation. - pub fn i32_trunc_sat_f64_u(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i64.trunc_sat_f32_s` Wasm operation. - pub fn i64_trunc_sat_f32_s(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i64.trunc_sat_f32_u` Wasm operation. - pub fn i64_trunc_sat_f32_u(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i64.trunc_sat_f64_s` Wasm operation. - pub fn i64_trunc_sat_f64_s(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } - - /// Execute `i64.trunc_sat_f64_u` Wasm operation. - pub fn i64_trunc_sat_f64_u(self) -> Self { - self.execute_unary(>::truncate_saturate_into) - } -} - -/// Macro to help implement generic trait implementations for tuple types. -macro_rules! for_each_tuple { - ($mac:ident) => { - $mac!( 0 ); - $mac!( 1 T1); - $mac!( 2 T1 T2); - $mac!( 3 T1 T2 T3); - $mac!( 4 T1 T2 T3 T4); - $mac!( 5 T1 T2 T3 T4 T5); - $mac!( 6 T1 T2 T3 T4 T5 T6); - $mac!( 7 T1 T2 T3 T4 T5 T6 T7); - $mac!( 8 T1 T2 T3 T4 T5 T6 T7 T8); - $mac!( 9 T1 T2 T3 T4 T5 T6 T7 T8 T9); - $mac!(10 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10); - $mac!(11 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11); - $mac!(12 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12); - $mac!(13 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13); - $mac!(14 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14); - $mac!(15 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15); - $mac!(16 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16); - } -} - -/// An error that may occur upon encoding or decoding slices of [`UntypedValue`]. -#[derive(Debug, Copy, Clone)] -pub enum UntypedError { - /// The [`UntypedValue`] slice length did not match `Self`. - InvalidLen, -} - -impl UntypedError { - /// Creates a new `InvalidLen` [`UntypedError`]. - #[cold] - pub fn invalid_len() -> Self { - Self::InvalidLen - } -} - -impl Display for UntypedError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - UntypedError::InvalidLen => { - write!(f, "mismatched length of the untyped slice",) - } - } - } -} - -impl UntypedValue { - /// Decodes the slice of [`UntypedValue`] as a value of type `T`. - /// - /// # Note - /// - /// `T` can either be a single type or a tuple of types depending - /// on the length of the `slice`. - /// - /// # Errors - /// - /// If the tuple length of `T` and the length of `slice` does not match. - pub fn decode_slice(slice: &[Self]) -> Result - where - T: DecodeUntypedSlice, - { - ::decode_untyped_slice(slice) - } - - pub fn decode_slice_i32( - slice: &[Self], - origin_params: &[ValueType], - ) -> Result - where - T: DecodeUntypedSlice, - { - ::decode_untyped_slice_i32(slice, origin_params) - } - - /// Encodes the slice of [`UntypedValue`] from the given value of type `T`. - /// - /// # Note - /// - /// `T` can either be a single type or a tuple of types depending - /// on the length of the `slice`. - /// - /// # Errors - /// - /// If the tuple length of `T` and the length of `slice` does not match. - pub fn encode_slice(slice: &mut [Self], input: T) -> Result<(), UntypedError> - where - T: EncodeUntypedSlice, - { - ::encode_untyped_slice(input, slice) - } - - pub fn encode_slice_i32( - slice: &mut [Self], - input: T, - origin_results: Vec, - ) -> Result<(), UntypedError> - where - T: EncodeUntypedSlice, - { - ::encode_untyped_slice_i32(input, slice, origin_results) - } -} - -/// Tuple types that allow to decode a slice of [`UntypedValue`]. -pub trait DecodeUntypedSlice: Sized { - /// Decodes the slice of [`UntypedValue`] as a value of type `Self`. - /// - /// # Note - /// - /// `Self` can either be a single type or a tuple of types depending - /// on the length of the `slice`. - /// - /// # Errors - /// - /// If the tuple length of `Self` and the length of `slice` does not match. - fn decode_untyped_slice(params: &[UntypedValue]) -> Result; - - fn decode_untyped_slice_i32( - params: &[UntypedValue], - origin_params: &[ValueType], - ) -> Result; -} - -impl DecodeUntypedSlice for T1 -where - T1: From, -{ - #[inline] - fn decode_untyped_slice(results: &[UntypedValue]) -> Result { - <(T1,) as DecodeUntypedSlice>::decode_untyped_slice(results).map(|t| t.0) - } - - #[inline] - fn decode_untyped_slice_i32( - results: &[UntypedValue], - origin_params: &[ValueType], - ) -> Result { - <(T1,) as DecodeUntypedSlice>::decode_untyped_slice_i32(results, origin_params).map(|t| t.0) - } -} - -macro_rules! impl_decode_untyped_slice { - ( $n:literal $( $tuple:ident )* ) => { - impl<$($tuple),*> DecodeUntypedSlice for ($($tuple,)*) - where - $( - $tuple: From - ),* - { - #[allow(non_snake_case)] - #[inline] - fn decode_untyped_slice(results: &[UntypedValue]) -> Result { - match results { - &[ $($tuple),* ] => Ok(( - $( - <$tuple as From>::from($tuple), - )* - )), - _ => Err(UntypedError::invalid_len()), - } - } - - #[allow(non_snake_case)] - #[inline] - #[allow(unused_variables, unused_mut, unused_assignments)] - fn decode_untyped_slice_i32(results: &[UntypedValue], origin_params: &[ValueType]) -> Result { - let mut i = 0; - match origin_params { - &[ $($tuple),* ] => Ok(( - $( - { - if $tuple == ValueType::I64 { - if i + 1 >= results.len() { - return Err(UntypedError::invalid_len()); - } - let high = results[i].as_u64(); - let low = results[i + 1].as_u64(); - i += 2; - - <$tuple as From>::from(UntypedValue::from((high << 32) | low)) - } else { - if i >= results.len() { - return Err(UntypedError::invalid_len()); - } - let value = results[i].clone(); - i += 1; - - <$tuple as From>::from(value) - } - }, - )* - - )), - _ => Err(UntypedError::invalid_len()), - } - } - } - }; -} -for_each_tuple!(impl_decode_untyped_slice); - -/// Tuple types that allow to encode a slice of [`UntypedValue`]. -pub trait EncodeUntypedSlice { - /// Encodes the slice of [`UntypedValue`] from the given value of type `Self`. - /// - /// # Note - /// - /// `Self` can either be a single type or a tuple of types depending - /// on the length of the `slice`. - /// - /// # Errors - /// - /// If the tuple length of `Self` and the length of `slice` does not match. - fn encode_untyped_slice(self, results: &mut [UntypedValue]) -> Result<(), UntypedError>; - - fn encode_untyped_slice_i32( - self, - results: &mut [UntypedValue], - origin_results: Vec, - ) -> Result<(), UntypedError>; -} - -impl EncodeUntypedSlice for T1 -where - T1: Into, -{ - #[inline] - fn encode_untyped_slice(self, results: &mut [UntypedValue]) -> Result<(), UntypedError> { - <(T1,) as EncodeUntypedSlice>::encode_untyped_slice((self,), results) - } - - #[inline] - fn encode_untyped_slice_i32( - self, - results: &mut [UntypedValue], - origin_results: Vec, - ) -> Result<(), UntypedError> { - <(T1,) as EncodeUntypedSlice>::encode_untyped_slice_i32((self,), results, origin_results) - } -} - -macro_rules! impl_encode_untyped_slice { - ( $n:literal $( $tuple:ident )* ) => { - paste! { - impl<$($tuple),*> EncodeUntypedSlice for ($($tuple,)*) - where - $( - $tuple: Into - ),* - { - #[allow(non_snake_case)] - #[inline] - fn encode_untyped_slice(self, results: &mut [UntypedValue]) -> Result<(), UntypedError> { - match results { - [ $( [< _results_ $tuple >] ,)* ] => { - let ( $( [< _self_ $tuple >] ,)* ) = self; - $( - *[< _results_ $tuple >] = <$tuple as Into>::into([< _self_ $tuple >]); - )* - Ok(()) - } - _ => Err(UntypedError::invalid_len()) - } - } - - #[allow(non_snake_case)] - #[inline] - #[allow(unused_variables, unused_mut, unused_assignments)] - fn encode_untyped_slice_i32(self, results: &mut [UntypedValue], origin_results: Vec) -> Result<(), UntypedError> { - let mut i = 0; - match origin_results.as_slice() { - [ $( [< _origin_results_ $tuple >] ,)* ] => { - let ( $( [< _self_ $tuple >] ,)* ) = self; - $( - let untyped = <$tuple as Into>::into([< _self_ $tuple >]); - if [< _origin_results_ $tuple >] == &ValueType::I64 { - let [low, high] = split_i64_to_i32(untyped.as_u64() as i64); - results[i] = UntypedValue::from(high); - i += 1; - results[i] = UntypedValue::from(low); - i += 1; - } else { - results[i] = untyped; - i += 1; - } - )* - if i != results.len() { - Err(UntypedError::invalid_len()) - } else { - Ok(()) - } - - } - _ => Err(UntypedError::invalid_len()) - } - } - } - } - }; -} -for_each_tuple!(impl_encode_untyped_slice); - -impl UntypedValue { - pub fn as_u16(self) -> u16 { - u16::from(self) - } - - pub fn as_u32(self) -> u32 { - u32::from(self) - } - - pub fn as_i32(self) -> i32 { - i32::from(self) - } - - pub fn as_u64(self) -> u64 { - u64::from(self) - } - - pub fn as_usize(self) -> usize { - self.as_u64() as usize - } -} diff --git a/legacy/src/core/value.rs b/legacy/src/core/value.rs deleted file mode 100644 index 88ce8c3d6..000000000 --- a/legacy/src/core/value.rs +++ /dev/null @@ -1,923 +0,0 @@ -use crate::core::{ - nan_preserving_float::{F32, F64}, - TrapCode, -}; -use core::{f32, i32, i64, u32, u64}; -use wasmparser::ValType; - -/// Type of a value. -/// -/// See [`Value`] for details. -/// -/// [`Value`]: enum.Value.html -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ValueType { - /// 32-bit signed or unsigned integer. - I32, - /// 64-bit signed or unsigned integer. - I64, - /// 32-bit IEEE 754-2008 floating point number. - F32, - /// 64-bit IEEE 754-2008 floating point number. - F64, - /// A nullable function reference. - FuncRef, - /// A nullable external reference. - ExternRef, -} - -impl ValueType { - /// Returns `true` if [`ValueType`] is a Wasm numeric type. - /// - /// This is `true` for [`ValueType::I32`], [`ValueType::I64`], - /// [`ValueType::F32`] and [`ValueType::F64`]. - pub fn is_num(&self) -> bool { - matches!(self, Self::I32 | Self::I64 | Self::F32 | Self::F64) - } - - /// Returns `true` if [`ValueType`] is a Wasm reference type. - /// - /// This is `true` for [`ValueType::FuncRef`] and [`ValueType::ExternRef`]. - pub fn is_ref(&self) -> bool { - matches!(self, Self::ExternRef | Self::FuncRef) - } -} - -impl From for ValueType { - fn from(value: ValType) -> Self { - match value { - ValType::I32 => ValueType::I32, - ValType::I64 => ValueType::I64, - ValType::F32 => ValueType::F32, - ValType::F64 => ValueType::F64, - ValType::FuncRef => ValueType::FuncRef, - ValType::ExternRef => ValueType::ExternRef, - _ => unreachable!("not supported local type ({:?})", value), - } - } -} - -/// Convert one type to another by wrapping. -pub trait WrapInto { - /// Convert one type to another by wrapping. - fn wrap_into(self) -> T; -} - -/// Convert one type to another by rounding to the nearest integer towards zero. -/// -/// # Errors -/// -/// Traps when the input float cannot be represented by the target integer or -/// when the input float is NaN. -pub trait TryTruncateInto { - /// Convert one type to another by rounding to the nearest integer towards zero. - /// - /// # Errors - /// - /// - If the input float value is NaN (not a number). - /// - If the input float value cannot be represented using the truncated integer type. - fn try_truncate_into(self) -> Result; -} - -/// Convert one type to another by rounding to the nearest integer towards zero. -/// -/// # Note -/// -/// This has saturating semantics for when the integer cannot represent the float. -/// -/// Returns -/// -/// - `0` when the input is NaN. -/// - `int::MIN` when the input is -INF. -/// - `int::MAX` when the input is +INF. -pub trait TruncateSaturateInto { - /// Convert one type to another by rounding to the nearest integer towards zero. - fn truncate_saturate_into(self) -> T; -} - -/// Convert one type to another by extending with leading zeroes. -pub trait ExtendInto { - /// Convert one type to another by extending with leading zeroes. - fn extend_into(self) -> T; -} - -/// Sign-extends `Self` integer type from `T` integer type. -pub trait SignExtendFrom { - /// Convert one type to another by extending with leading zeroes. - fn sign_extend_from(self) -> Self; -} - -/// Reinterprets the bits of a value of one type as another type. -pub trait TransmuteInto { - /// Reinterprets the bits of a value of one type as another type. - fn transmute_into(self) -> T; -} - -/// Allows to efficiently load bytes from `memory` into a buffer. -pub trait LoadInto { - /// Loads bytes from `memory` into `self`. - /// - /// # Errors - /// - /// Traps if the `memory` access is out of bounds. - fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode>; -} - -impl LoadInto for [u8; N] { - #[inline] - fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode> { - let slice: &Self = memory - .get(address..) - .and_then(|slice| slice.get(..N)) - .and_then(|slice| slice.try_into().ok()) - .ok_or(TrapCode::MemoryOutOfBounds)?; - *self = *slice; - Ok(()) - } -} - -/// Allows to efficiently write bytes from a buffer into `memory`. -pub trait StoreFrom { - /// Writes bytes from `self` to `memory`. - /// - /// # Errors - /// - /// Traps if the `memory` access is out of bounds. - fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode>; -} - -impl StoreFrom for [u8; N] { - #[inline] - fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode> { - let slice: &mut Self = memory - .get_mut(address..) - .and_then(|slice| slice.get_mut(..N)) - .and_then(|slice| slice.try_into().ok()) - .ok_or(TrapCode::MemoryOutOfBounds)?; - *slice = *self; - Ok(()) - } -} - -/// Types that can be converted from and to little endian bytes. -pub trait LittleEndianConvert { - /// The little endian bytes representation. - type Bytes: Default + LoadInto + StoreFrom; - - /// Converts `self` into little endian bytes. - fn into_le_bytes(self) -> Self::Bytes; - - /// Converts little endian bytes into `Self`. - fn from_le_bytes(bytes: Self::Bytes) -> Self; -} - -macro_rules! impl_little_endian_convert_primitive { - ( $($primitive:ty),* $(,)? ) => { - $( - impl LittleEndianConvert for $primitive { - type Bytes = [::core::primitive::u8; ::core::mem::size_of::<$primitive>()]; - - #[inline] - fn into_le_bytes(self) -> Self::Bytes { - <$primitive>::to_le_bytes(self) - } - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - <$primitive>::from_le_bytes(bytes) - } - } - )* - }; -} -impl_little_endian_convert_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); - -macro_rules! impl_little_endian_convert_float { - ( $( struct $float_ty:ident($uint_ty:ty); )* $(,)? ) => { - $( - impl LittleEndianConvert for $float_ty { - type Bytes = <$uint_ty as LittleEndianConvert>::Bytes; - - #[inline] - fn into_le_bytes(self) -> Self::Bytes { - <$uint_ty>::into_le_bytes(self.to_bits()) - } - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - Self::from_bits(<$uint_ty>::from_le_bytes(bytes)) - } - } - )* - }; -} -impl_little_endian_convert_float!( - struct F32(u32); - struct F64(u64); -); - -/// Arithmetic operations. -pub trait ArithmeticOps: Copy { - /// Add two values. - fn add(self, other: T) -> T; - /// Subtract two values. - fn sub(self, other: T) -> T; - /// Multiply two values. - fn mul(self, other: T) -> T; -} - -/// Integer value. -pub trait Integer: ArithmeticOps { - /// Counts leading zeros in the bitwise representation of the value. - fn leading_zeros(self) -> T; - /// Counts trailing zeros in the bitwise representation of the value. - fn trailing_zeros(self) -> T; - /// Counts 1-bits in the bitwise representation of the value. - fn count_ones(self) -> T; - /// Get left bit rotation result. - fn rotl(self, other: T) -> T; - /// Get right bit rotation result. - fn rotr(self, other: T) -> T; - /// Divide two values. - /// - /// # Errors - /// - /// If `other` is equal to zero. - fn div(self, other: T) -> Result; - /// Get division remainder. - /// - /// # Errors - /// - /// If `other` is equal to zero. - fn rem(self, other: T) -> Result; -} - -/// Float-point value. -pub trait Float: ArithmeticOps { - /// Get absolute value. - fn abs(self) -> T; - /// Returns the largest integer less than or equal to a number. - fn floor(self) -> T; - /// Returns the smallest integer greater than or equal to a number. - fn ceil(self) -> T; - /// Returns the integer part of a number. - fn trunc(self) -> T; - /// Returns the nearest integer to a number. Round half-way cases away from 0.0. - fn round(self) -> T; - /// Returns the nearest integer to a number. Ties are round to even number. - fn nearest(self) -> T; - /// Takes the square root of a number. - fn sqrt(self) -> T; - /// Returns `true` if the sign of the number is positive. - fn is_sign_positive(self) -> bool; - /// Returns `true` if the sign of the number is negative. - fn is_sign_negative(self) -> bool; - /// Returns the division of the two numbers. - fn div(self, other: T) -> T; - /// Returns the minimum of the two numbers. - fn min(self, other: T) -> T; - /// Returns the maximum of the two numbers. - fn max(self, other: T) -> T; - /// Sets sign of this value to the sign of other value. - fn copysign(self, other: T) -> T; -} - -macro_rules! impl_wrap_into { - ($from:ident, $into:ident) => { - impl WrapInto<$into> for $from { - #[inline] - fn wrap_into(self) -> $into { - self as $into - } - } - }; - ($from:ident, $intermediate:ident, $into:ident) => { - impl WrapInto<$into> for $from { - #[inline] - fn wrap_into(self) -> $into { - $into::from(self as $intermediate) - } - } - }; -} - -impl_wrap_into!(i32, i8); -impl_wrap_into!(i32, i16); -impl_wrap_into!(i64, i8); -impl_wrap_into!(i64, i16); -impl_wrap_into!(i64, i32); -impl_wrap_into!(i64, f32, F32); -impl_wrap_into!(u64, f32, F32); - -// Casting to self -impl_wrap_into!(i32, i32); -impl_wrap_into!(i64, i64); -impl_wrap_into!(F32, F32); -impl_wrap_into!(F64, F64); - -impl WrapInto for F64 { - #[inline] - fn wrap_into(self) -> F32 { - (f64::from(self) as f32).into() - } -} - -macro_rules! impl_try_truncate_into { - (@primitive $from: ident, $into: ident, $to_primitive:path, $rmin:literal, $rmax:literal) => { - impl TryTruncateInto<$into, TrapCode> for $from { - #[inline] - fn try_truncate_into(self) -> Result<$into, TrapCode> { - if self.is_nan() { - return Err(TrapCode::BadConversionToInteger); - } - if self <= $rmin || self >= $rmax { - return Err(TrapCode::IntegerOverflow); - } - Ok(self as _) - } - } - - impl TruncateSaturateInto<$into> for $from { - #[inline] - fn truncate_saturate_into(self) -> $into { - if self.is_nan() { - return <$into as Default>::default(); - } - if self.is_infinite() && self.is_sign_positive() { - return <$into>::MAX; - } - if self.is_infinite() && self.is_sign_negative() { - return <$into>::MIN; - } - self as _ - } - } - }; - (@wrapped $from:ident, $intermediate:ident, $into:ident) => { - impl TryTruncateInto<$into, TrapCode> for $from { - #[inline] - fn try_truncate_into(self) -> Result<$into, TrapCode> { - $intermediate::from(self).try_truncate_into() - } - } - - impl TruncateSaturateInto<$into> for $from { - #[inline] - fn truncate_saturate_into(self) -> $into { - $intermediate::from(self).truncate_saturate_into() - } - } - }; -} - -impl_try_truncate_into!(@primitive f32, i32, num_traits::cast::ToPrimitive::to_i32, -2147483904.0_f32, 2147483648.0_f32); -impl_try_truncate_into!(@primitive f32, u32, num_traits::cast::ToPrimitive::to_u32, -1.0_f32, 4294967296.0_f32); -impl_try_truncate_into!(@primitive f64, i32, num_traits::cast::ToPrimitive::to_i32, -2147483649.0_f64, 2147483648.0_f64); -impl_try_truncate_into!(@primitive f64, u32, num_traits::cast::ToPrimitive::to_u32, -1.0_f64, 4294967296.0_f64); -impl_try_truncate_into!(@primitive f32, i64, num_traits::cast::ToPrimitive::to_i64, -9223373136366403584.0_f32, 9223372036854775808.0_f32); -impl_try_truncate_into!(@primitive f32, u64, num_traits::cast::ToPrimitive::to_u64, -1.0_f32, 18446744073709551616.0_f32); -impl_try_truncate_into!(@primitive f64, i64, num_traits::cast::ToPrimitive::to_i64, -9223372036854777856.0_f64, 9223372036854775808.0_f64); -impl_try_truncate_into!(@primitive f64, u64, num_traits::cast::ToPrimitive::to_u64, -1.0_f64, 18446744073709551616.0_f64); -impl_try_truncate_into!(@wrapped F32, f32, i32); -impl_try_truncate_into!(@wrapped F32, f32, i64); -impl_try_truncate_into!(@wrapped F64, f64, i32); -impl_try_truncate_into!(@wrapped F64, f64, i64); -impl_try_truncate_into!(@wrapped F32, f32, u32); -impl_try_truncate_into!(@wrapped F32, f32, u64); -impl_try_truncate_into!(@wrapped F64, f64, u32); -impl_try_truncate_into!(@wrapped F64, f64, u64); - -macro_rules! impl_extend_into { - ($from:ident, $into:ident) => { - impl ExtendInto<$into> for $from { - #[inline] - fn extend_into(self) -> $into { - self as $into - } - } - }; - ($from:ident, $intermediate:ident, $into:ident) => { - impl ExtendInto<$into> for $from { - #[inline] - fn extend_into(self) -> $into { - $into::from(self as $intermediate) - } - } - }; -} - -impl_extend_into!(i8, i32); -impl_extend_into!(u8, i32); -impl_extend_into!(i16, i32); -impl_extend_into!(u16, i32); -impl_extend_into!(i8, i64); -impl_extend_into!(u8, i64); -impl_extend_into!(i16, i64); -impl_extend_into!(u16, i64); -impl_extend_into!(i32, i64); -impl_extend_into!(u32, i64); -impl_extend_into!(u32, u64); - -impl_extend_into!(i32, f32, F32); -impl_extend_into!(i32, f64, F64); -impl_extend_into!(u32, f32, F32); -impl_extend_into!(u32, f64, F64); -impl_extend_into!(i64, f64, F64); -impl_extend_into!(u64, f64, F64); -impl_extend_into!(f32, f64, F64); - -// Casting to self -impl_extend_into!(i32, i32); -impl_extend_into!(i64, i64); -impl_extend_into!(F32, F32); -impl_extend_into!(F64, F64); - -impl ExtendInto for F32 { - #[inline] - fn extend_into(self) -> F64 { - F64::from(f64::from(f32::from(self))) - } -} - -macro_rules! impl_sign_extend_from { - ( $( impl SignExtendFrom<$from_type:ty> for $for_type:ty; )* ) => { - $( - impl SignExtendFrom<$from_type> for $for_type { - #[inline] - fn sign_extend_from(self) -> Self { - (self as $from_type) as Self - } - } - )* - }; -} -impl_sign_extend_from! { - impl SignExtendFrom for i32; - impl SignExtendFrom for i32; - impl SignExtendFrom for i64; - impl SignExtendFrom for i64; - impl SignExtendFrom for i64; -} - -macro_rules! impl_transmute_into_self { - ($type: ident) => { - impl TransmuteInto<$type> for $type { - #[inline] - fn transmute_into(self) -> $type { - self - } - } - }; -} - -impl_transmute_into_self!(i32); -impl_transmute_into_self!(i64); -impl_transmute_into_self!(f32); -impl_transmute_into_self!(f64); -impl_transmute_into_self!(F32); -impl_transmute_into_self!(F64); - -macro_rules! impl_transmute_into_as { - ($from: ident, $into: ident) => { - impl TransmuteInto<$into> for $from { - #[inline] - fn transmute_into(self) -> $into { - self as $into - } - } - }; -} - -impl_transmute_into_as!(i8, u8); -impl_transmute_into_as!(i32, u32); -impl_transmute_into_as!(i64, u64); - -macro_rules! impl_transmute_into_npf { - ($npf:ident, $float:ident, $signed:ident, $unsigned:ident) => { - impl TransmuteInto<$float> for $npf { - #[inline] - fn transmute_into(self) -> $float { - self.into() - } - } - - impl TransmuteInto<$npf> for $float { - #[inline] - fn transmute_into(self) -> $npf { - self.into() - } - } - - impl TransmuteInto<$signed> for $npf { - #[inline] - fn transmute_into(self) -> $signed { - self.to_bits() as _ - } - } - - impl TransmuteInto<$unsigned> for $npf { - #[inline] - fn transmute_into(self) -> $unsigned { - self.to_bits() - } - } - - impl TransmuteInto<$npf> for $signed { - #[inline] - fn transmute_into(self) -> $npf { - $npf::from_bits(self as _) - } - } - - impl TransmuteInto<$npf> for $unsigned { - #[inline] - fn transmute_into(self) -> $npf { - $npf::from_bits(self) - } - } - }; -} - -impl_transmute_into_npf!(F32, f32, i32, u32); -impl_transmute_into_npf!(F64, f64, i64, u64); - -impl TransmuteInto for f32 { - #[inline] - fn transmute_into(self) -> i32 { - self.to_bits() as i32 - } -} - -impl TransmuteInto for f64 { - #[inline] - fn transmute_into(self) -> i64 { - self.to_bits() as i64 - } -} - -impl TransmuteInto for i32 { - #[inline] - fn transmute_into(self) -> f32 { - f32::from_bits(self as u32) - } -} - -impl TransmuteInto for i64 { - #[inline] - fn transmute_into(self) -> f64 { - f64::from_bits(self as u64) - } -} - -impl TransmuteInto for u32 { - #[inline] - fn transmute_into(self) -> i32 { - self as _ - } -} - -impl TransmuteInto for u64 { - #[inline] - fn transmute_into(self) -> i64 { - self as _ - } -} - -macro_rules! impl_integer_arithmetic_ops { - ($type: ident) => { - impl ArithmeticOps<$type> for $type { - #[inline] - fn add(self, other: $type) -> $type { - self.wrapping_add(other) - } - #[inline] - fn sub(self, other: $type) -> $type { - self.wrapping_sub(other) - } - #[inline] - fn mul(self, other: $type) -> $type { - self.wrapping_mul(other) - } - } - }; -} - -impl_integer_arithmetic_ops!(i32); -impl_integer_arithmetic_ops!(u32); -impl_integer_arithmetic_ops!(i64); -impl_integer_arithmetic_ops!(u64); - -macro_rules! impl_float_arithmetic_ops { - ($type:ty) => { - impl ArithmeticOps for $type { - #[inline] - fn add(self, other: Self) -> Self { - self + other - } - #[inline] - fn sub(self, other: Self) -> Self { - self - other - } - #[inline] - fn mul(self, other: Self) -> Self { - self * other - } - } - }; -} - -impl_float_arithmetic_ops!(f32); -impl_float_arithmetic_ops!(f64); -impl_float_arithmetic_ops!(F32); -impl_float_arithmetic_ops!(F64); - -macro_rules! impl_integer { - ($type:ty) => { - impl Integer for $type { - #[inline] - fn leading_zeros(self) -> Self { - self.leading_zeros() as _ - } - #[inline] - fn trailing_zeros(self) -> Self { - self.trailing_zeros() as _ - } - #[inline] - fn count_ones(self) -> Self { - self.count_ones() as _ - } - #[inline] - fn rotl(self, other: Self) -> Self { - self.rotate_left(other as u32) - } - #[inline] - fn rotr(self, other: Self) -> Self { - self.rotate_right(other as u32) - } - #[inline] - fn div(self, other: Self) -> Result { - if other == 0 { - return Err(TrapCode::IntegerDivisionByZero); - } - match self.overflowing_div(other) { - (result, false) => Ok(result), - _ => Err(TrapCode::IntegerOverflow), - } - } - #[inline] - fn rem(self, other: Self) -> Result { - if other == 0 { - return Err(TrapCode::IntegerDivisionByZero); - } - Ok(self.wrapping_rem(other)) - } - } - }; -} - -impl_integer!(i32); -impl_integer!(u32); -impl_integer!(i64); -impl_integer!(u64); - -#[cfg(feature = "std")] -mod fmath { - pub use f32; - pub use f64; -} - -#[cfg(not(feature = "std"))] -mod fmath { - pub use super::libm_adapters::{f32, f64}; -} - -// We cannot call the math functions directly, because they are not all available in `core`. -// In no-std cases we instead rely on `libm`. -// These wrappers handle that delegation. -macro_rules! impl_float { - ($type:ident, $fXX:ident, $iXX:ident) => { - // In this particular instance we want to directly compare floating point numbers. - impl Float for $type { - #[inline] - fn abs(self) -> Self { - fmath::$fXX::abs(<$fXX>::from(self)).into() - } - #[inline] - fn floor(self) -> Self { - fmath::$fXX::floor(<$fXX>::from(self)).into() - } - #[inline] - fn ceil(self) -> Self { - fmath::$fXX::ceil(<$fXX>::from(self)).into() - } - #[inline] - fn trunc(self) -> Self { - fmath::$fXX::trunc(<$fXX>::from(self)).into() - } - #[inline] - fn round(self) -> Self { - fmath::$fXX::round(<$fXX>::from(self)).into() - } - #[inline] - fn nearest(self) -> Self { - let round = self.round(); - if fmath::$fXX::fract(<$fXX>::from(self)).abs() != 0.5 { - return round; - } - let rem = ::core::ops::Rem::rem(round, 2.0); - if rem == 1.0 { - self.floor() - } else if rem == -1.0 { - self.ceil() - } else { - round - } - } - #[inline] - fn sqrt(self) -> Self { - fmath::$fXX::sqrt(<$fXX>::from(self)).into() - } - #[inline] - fn is_sign_positive(self) -> bool { - <$fXX>::is_sign_positive(<$fXX>::from(self)).into() - } - #[inline] - fn is_sign_negative(self) -> bool { - <$fXX>::is_sign_negative(<$fXX>::from(self)).into() - } - #[inline] - fn div(self, other: Self) -> Self { - self / other - } - #[inline] - fn min(self, other: Self) -> Self { - // The implementation strictly adheres to the mandated behavior for the Wasm - // specification. Note: In other contexts this API is also known as: - // `nan_min`. - match (self.is_nan(), other.is_nan()) { - (true, false) => self, - (false, true) => other, - _ => { - // Case: Both values are NaN; OR both values are non-NaN. - if other.is_sign_negative() { - return other.min(self); - } - self.min(other) - } - } - } - #[inline] - fn max(self, other: Self) -> Self { - // The implementation strictly adheres to the mandated behavior for the Wasm - // specification. Note: In other contexts this API is also known as: - // `nan_max`. - match (self.is_nan(), other.is_nan()) { - (true, false) => self, - (false, true) => other, - _ => { - // Case: Both values are NaN; OR both values are non-NaN. - if other.is_sign_positive() { - return other.max(self); - } - self.max(other) - } - } - } - #[inline] - fn copysign(self, other: Self) -> Self { - use core::mem::size_of; - let sign_mask: $iXX = 1 << ((size_of::<$iXX>() << 3) - 1); - let self_int: $iXX = self.transmute_into(); - let other_int: $iXX = other.transmute_into(); - let is_self_sign_set = (self_int & sign_mask) != 0; - let is_other_sign_set = (other_int & sign_mask) != 0; - if is_self_sign_set == is_other_sign_set { - self - } else if is_other_sign_set { - (self_int | sign_mask).transmute_into() - } else { - (self_int & !sign_mask).transmute_into() - } - } - } - }; -} - -#[test] -fn wasm_float_min_regression_works() { - assert_eq!( - Float::min(F32::from(-0.0), F32::from(0.0)).to_bits(), - 0x8000_0000, - ); - assert_eq!( - Float::min(F32::from(0.0), F32::from(-0.0)).to_bits(), - 0x8000_0000, - ); -} - -#[test] -fn wasm_float_max_regression_works() { - assert_eq!( - Float::max(F32::from(-0.0), F32::from(0.0)).to_bits(), - 0x0000_0000, - ); - assert_eq!( - Float::max(F32::from(0.0), F32::from(-0.0)).to_bits(), - 0x0000_0000, - ); -} - -impl_float!(f32, f32, i32); -impl_float!(f64, f64, i64); -impl_float!(F32, f32, i32); -impl_float!(F64, f64, i64); - -#[test] -fn copysign_regression_works() { - // This test has been directly extracted from a WebAssembly Specification assertion. - use Float as _; - assert!(F32::from_bits(0xFFC00000).is_nan()); - assert_eq!( - F32::from_bits(0xFFC00000) - .copysign(F32::from_bits(0x0000_0000)) - .to_bits(), - F32::from_bits(0x7FC00000).to_bits() - ) -} - -#[cfg(not(feature = "std"))] -mod libm_adapters { - pub mod f32 { - #[inline] - pub fn abs(v: f32) -> f32 { - libm::fabsf(v) - } - - #[inline] - pub fn floor(v: f32) -> f32 { - libm::floorf(v) - } - - #[inline] - pub fn ceil(v: f32) -> f32 { - libm::ceilf(v) - } - - #[inline] - pub fn trunc(v: f32) -> f32 { - libm::truncf(v) - } - - #[inline] - pub fn round(v: f32) -> f32 { - libm::roundf(v) - } - - #[inline] - pub fn fract(v: f32) -> f32 { - v - trunc(v) - } - - #[inline] - pub fn sqrt(v: f32) -> f32 { - libm::sqrtf(v) - } - } - - pub mod f64 { - #[inline] - pub fn abs(v: f64) -> f64 { - libm::fabs(v) - } - - #[inline] - pub fn floor(v: f64) -> f64 { - libm::floor(v) - } - - #[inline] - pub fn ceil(v: f64) -> f64 { - libm::ceil(v) - } - - #[inline] - pub fn trunc(v: f64) -> f64 { - libm::trunc(v) - } - - #[inline] - pub fn round(v: f64) -> f64 { - libm::round(v) - } - - #[inline] - pub fn fract(v: f64) -> f64 { - v - trunc(v) - } - - #[inline] - pub fn sqrt(v: f64) -> f64 { - libm::sqrt(v) - } - } -} diff --git a/legacy/src/engine/bytecode/instr_meta.rs b/legacy/src/engine/bytecode/instr_meta.rs deleted file mode 100644 index b6cb81806..000000000 --- a/legacy/src/engine/bytecode/instr_meta.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::engine::bytecode::Instruction; - -type InstrByteLen = usize; -type CommitByteLen = usize; -type IsSigned = bool; - -impl Instruction { - pub const MAX_BYTE_LEN: usize = 8; - pub fn store_instr_meta(instr: &Instruction) -> InstrByteLen { - match instr { - Instruction::I32Store(_) => 4, - Instruction::I32Store8(_) => 1, - Instruction::I32Store16(_) => 2, - Instruction::I64Store(_) => 8, - Instruction::I64Store8(_) => 1, - Instruction::I64Store16(_) => 2, - Instruction::I64Store32(_) => 4, - Instruction::F32Store(_) => 4, - Instruction::F64Store(_) => 8, - _ => unreachable!("unsupported opcode {:?}", instr), - } - } - - pub fn load_instr_meta(instr: &Instruction) -> (InstrByteLen, CommitByteLen, IsSigned) { - match instr { - Instruction::I32Load(_) => (4, 4, false), - Instruction::I64Load(_) => (8, 8, false), - Instruction::F32Load(_) => (4, 4, false), - Instruction::F64Load(_) => (8, 8, false), - Instruction::I32Load8S(_) => (4, 1, true), - Instruction::I32Load8U(_) => (4, 1, false), - Instruction::I32Load16S(_) => (4, 2, true), - Instruction::I32Load16U(_) => (4, 2, false), - Instruction::I64Load8S(_) => (8, 1, true), - Instruction::I64Load8U(_) => (8, 1, false), - Instruction::I64Load16S(_) => (8, 2, true), - Instruction::I64Load16U(_) => (8, 2, false), - Instruction::I64Load32S(_) => (8, 4, true), - Instruction::I64Load32U(_) => (8, 4, false), - _ => unreachable!("unsupported opcode {:?}", instr), - } - } -} diff --git a/legacy/src/engine/bytecode/mod.rs b/legacy/src/engine/bytecode/mod.rs deleted file mode 100644 index b4dda3ff6..000000000 --- a/legacy/src/engine/bytecode/mod.rs +++ /dev/null @@ -1,650 +0,0 @@ -//! The instruction architecture of the `wasmi` interpreter. - -mod utils; - -mod instr_meta; -mod stack_height; -#[cfg(test)] -mod tests; - -pub use self::utils::{ - AddressOffset, - BlockFuel, - BranchOffset, - BranchTableTargets, - DataSegmentIdx, - DropKeep, - DropKeepError, - ElementSegmentIdx, - F64Const32, - FuncIdx, - GlobalIdx, - LocalDepth, - SignatureIdx, - TableIdx, -}; -use super::{const_pool::ConstRef, CompiledFunc, TranslationError}; -use crate::core::{UntypedValue, F32}; -#[cfg(feature = "std")] -use core::{ - fmt, - fmt::{Debug, Formatter}, -}; - -/// The internal `wasmi` bytecode that is stored for Wasm functions. -/// -/// # Note -/// -/// This representation slightly differs from WebAssembly instructions. -/// -/// For example the `BrTable` instruction is unrolled into separate instructions -/// each representing either the `BrTable` head or one of its branching targets. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(strum_macros::EnumIter))] -pub enum Instruction { - LocalGet(LocalDepth), - LocalSet(LocalDepth), - LocalTee(LocalDepth), - /// An unconditional branch. - Br(BranchOffset), - /// Branches if the top-most stack value is equal to zero. - BrIfEqz(BranchOffset), - /// Branches if the top-most stack value is _not_ equal to zero. - BrIfNez(BranchOffset), - /// An unconditional branch. - /// - /// This operation also adjust the underlying value stack if necessary. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by a [`Instruction::Return`] - /// which stores information about the [`DropKeep`] behavior of the - /// [`Instruction::Br`]. The [`Instruction::Return`] will never be executed - /// and only acts as parameter storage for this instruction. - BrAdjust(BranchOffset), - /// Branches if the top-most stack value is _not_ equal to zero. - /// - /// This operation also adjust the underlying value stack if necessary. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by a [`Instruction::Return`] - /// which stores information about the [`DropKeep`] behavior of the - /// [`Instruction::BrIfNez`]. The [`Instruction::Return`] will never be executed - /// and only acts as parameter storage for this instruction. - BrAdjustIfNez(BranchOffset), - /// Branch table with a set number of branching targets. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by exactly as many unconditional - /// branch instructions as determined by [`BranchTableTargets`]. Branch - /// instructions that may follow are [`Instruction::Br] and [`Instruction::Return`]. - BrTable(BranchTableTargets), - Unreachable, - ConsumeFuel(BlockFuel), - Return(DropKeep), - ReturnIfNez(DropKeep), - /// Tail calls an internal (compiled) function. - /// - /// # Note - /// - /// This instruction can be used for calls to functions that are engine internal - /// (or compiled) and acts as an optimization for those common cases. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::Return`] that - /// encodes the [`DropKeep`] parameter. Note that the [`Instruction::Return`] - /// only acts as a storage for the parameter of the [`Instruction::ReturnCall`] - /// and will never be executed by itself. - ReturnCallInternal(CompiledFunc), - /// Tail calling `func`. - /// - /// # Note - /// - /// Since [`Instruction::ReturnCallInternal`] should be used for all functions internal - /// (or compiled) to the engine this instruction should mainly be used for tail calling - /// imported functions. However, it is a general form that can technically be used - /// for both. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::Return`] that - /// encodes the [`DropKeep`] parameter. Note that the [`Instruction::Return`] - /// only acts as a storage for the parameter of the [`Instruction::ReturnCall`] - /// and will never be executed by itself. - ReturnCall(FuncIdx), - /// Tail calling a function indirectly. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::Return`] that - /// encodes the [`DropKeep`] parameter as well as an [`Instruction::TableGet`] - /// that encodes the [`TableIdx`] parameter. Note that both, [`Instruction::Return`] - /// and [`Instruction::TableGet`] only act as a storage for parameters to the - /// [`Instruction::ReturnCallIndirect`] and will never be executed by themselves. - ReturnCallIndirect(SignatureIdx), - /// Calls an internal (compiled) function. - /// - /// # Note - /// - /// This instruction can be used for calls to functions that are engine internal - /// (or compiled) and acts as an optimization for those common cases. - CallInternal(CompiledFunc), - /// Calls the function. - /// - /// # Note - /// - /// Since [`Instruction::CallInternal`] should be used for all functions internal - /// (or compiled) to the engine this instruction should mainly be used for calling - /// imported functions. However, it is a general form that can technically be used - /// for both. - Call(FuncIdx), - /// Calling a function indirectly. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::TableGet`] - /// that encodes the [`TableIdx`] parameter. Note that the [`Instruction::TableGet`] - /// only acts as a storage for the parameter of the [`Instruction::CallIndirect`] - /// and will never be executed by itself. - CallIndirect(SignatureIdx), - SignatureCheck(SignatureIdx), - StackAlloc { - max_stack_height: u32, - }, - Drop, - Select, - GlobalGet(GlobalIdx), - GlobalSet(GlobalIdx), - I32Load(AddressOffset), - I64Load(AddressOffset), - F32Load(AddressOffset), - F64Load(AddressOffset), - I32Load8S(AddressOffset), - I32Load8U(AddressOffset), - I32Load16S(AddressOffset), - I32Load16U(AddressOffset), - I64Load8S(AddressOffset), - I64Load8U(AddressOffset), - I64Load16S(AddressOffset), - I64Load16U(AddressOffset), - I64Load32S(AddressOffset), - I64Load32U(AddressOffset), - I32Store(AddressOffset), - I64Store(AddressOffset), - F32Store(AddressOffset), - F64Store(AddressOffset), - I32Store8(AddressOffset), - I32Store16(AddressOffset), - I64Store8(AddressOffset), - I64Store16(AddressOffset), - I64Store32(AddressOffset), - MemorySize, - MemoryGrow, - MemoryFill, - MemoryCopy, - MemoryInit(DataSegmentIdx), - DataDrop(DataSegmentIdx), - TableSize(TableIdx), - TableGrow(TableIdx), - TableFill(TableIdx), - TableGet(TableIdx), - TableSet(TableIdx), - /// Copies elements from one table to another. - /// - /// # Note - /// - /// It is also possible to copy elements within the same table. - /// - /// # Encoding - /// - /// The [`TableIdx`] referred to by the [`Instruction::TableCopy`] - /// represents the `dst` (destination) table. The [`Instruction::TableCopy`] - /// must be followed by an [`Instruction::TableGet`] which stores a - /// [`TableIdx`] that refers to the `src` (source) table. - TableCopy(TableIdx), - /// Initializes a table given an [`ElementSegmentIdx`]. - /// - /// # Encoding - /// - /// The [`Instruction::TableInit`] must be followed by an - /// [`Instruction::TableGet`] which stores a [`TableIdx`] - /// that refers to the table to be initialized. - TableInit(ElementSegmentIdx), - ElemDrop(ElementSegmentIdx), - RefFunc(FuncIdx), - /// A 32/64-bit constant value. - I32Const(UntypedValue), - I64Const(UntypedValue), - /// A 64-bit float value losslessly encoded as 32-bit float. - /// - /// Upon execution the 32-bit float is promoted to the 64-bit float. - /// - /// # Note - /// - /// This is a space-optimized variant of [`Instruction::ConstRef`] but can - /// only used for certain float values that fit into a 32-bit float value. - F32Const(UntypedValue), - F64Const(UntypedValue), - /// Pushes a constant value onto the stack. - /// - /// The constant value is referred to indirectly by the [`ConstRef`]. - ConstRef(ConstRef), - I32Eqz, - I32Eq, - I32Ne, - I32LtS, - I32LtU, - I32GtS, - I32GtU, - I32LeS, - I32LeU, - I32GeS, - I32GeU, - I64Eqz, - I64Eq, - I64Ne, - I64LtS, - I64LtU, - I64GtS, - I64GtU, - I64LeS, - I64LeU, - I64GeS, - I64GeU, - F32Eq, - F32Ne, - F32Lt, - F32Gt, - F32Le, - F32Ge, - F64Eq, - F64Ne, - F64Lt, - F64Gt, - F64Le, - F64Ge, - I32Clz, - I32Ctz, - I32Popcnt, - I32Add, - I32Sub, - I32Mul, - I32DivS, - I32DivU, - I32RemS, - I32RemU, - I32And, - I32Or, - I32Xor, - I32Shl, - I32ShrS, - I32ShrU, - I32Rotl, - I32Rotr, - I64Clz, - I64Ctz, - I64Popcnt, - I64Add, - I64Sub, - I64Mul, - I64DivS, - I64DivU, - I64RemS, - I64RemU, - I64And, - I64Or, - I64Xor, - I64Shl, - I64ShrS, - I64ShrU, - I64Rotl, - I64Rotr, - F32Abs, - F32Neg, - F32Ceil, - F32Floor, - F32Trunc, - F32Nearest, - F32Sqrt, - F32Add, - F32Sub, - F32Mul, - F32Div, - F32Min, - F32Max, - F32Copysign, - F64Abs, - F64Neg, - F64Ceil, - F64Floor, - F64Trunc, - F64Nearest, - F64Sqrt, - F64Add, - F64Sub, - F64Mul, - F64Div, - F64Min, - F64Max, - F64Copysign, - I32WrapI64, - I32TruncF32S, - I32TruncF32U, - I32TruncF64S, - I32TruncF64U, - I64ExtendI32S, - I64ExtendI32U, - I64TruncF32S, - I64TruncF32U, - I64TruncF64S, - I64TruncF64U, - F32ConvertI32S, - F32ConvertI32U, - F32ConvertI64S, - F32ConvertI64U, - F32DemoteF64, - F64ConvertI32S, - F64ConvertI32U, - F64ConvertI64S, - F64ConvertI64U, - F64PromoteF32, - I32Extend8S, - I32Extend16S, - I64Extend8S, - I64Extend16S, - I64Extend32S, - I32TruncSatF32S, - I32TruncSatF32U, - I32TruncSatF64S, - I32TruncSatF64U, - I64TruncSatF32S, - I64TruncSatF32U, - I64TruncSatF64S, - I64TruncSatF64U, -} - -#[cfg(feature = "std")] -impl fmt::Display for Instruction { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let name = format!("{:?}", self); - let name: Vec<_> = name.split('(').collect(); - write!(f, "{}", name[0]) - } -} - -impl Instruction { - /// Creates an [`Instruction::Const32`] from the given `i32` constant value. - pub fn i32_const(value: i32) -> Self { - Self::I32Const(UntypedValue::from(i64::from(value))) - } - - /// Creates an [`Instruction::Const32`] from the given `f32` constant value. - pub fn f32_const(value: F32) -> Self { - Self::F32Const(UntypedValue::from(value)) - } - - /// Creates a new `local.get` instruction from the given local depth. - /// - /// # Errors - /// - /// If the `local_depth` is out of bounds as local depth index. - pub fn local_get(local_depth: u32) -> Result { - Ok(Self::LocalGet(LocalDepth::from(local_depth))) - } - - /// Creates a new `local.set` instruction from the given local depth. - /// - /// # Errors - /// - /// If the `local_depth` is out of bounds as local depth index. - pub fn local_set(local_depth: u32) -> Result { - Ok(Self::LocalSet(LocalDepth::from(local_depth))) - } - - /// Creates a new `local.tee` instruction from the given local depth. - /// - /// # Errors - /// - /// If the `local_depth` is out of bounds as local depth index. - pub fn local_tee(local_depth: u32) -> Result { - Ok(Self::LocalTee(LocalDepth::from(local_depth))) - } - - /// Convenience method to create a new `ConsumeFuel` instruction. - pub fn consume_fuel(amount: u64) -> Result { - let block_fuel = BlockFuel::try_from(amount)?; - Ok(Self::ConsumeFuel(block_fuel)) - } - - pub fn is_supported(&self) -> bool { - match self { - Instruction::LocalGet(_) - | Instruction::LocalSet(_) - | Instruction::LocalTee(_) - | Instruction::Br(_) - | Instruction::BrIfEqz(_) - | Instruction::BrIfNez(_) - | Instruction::Unreachable - | Instruction::ConsumeFuel(_) - | Instruction::Return(_) - | Instruction::ReturnIfNez(_) - | Instruction::Call(_) - | Instruction::Drop - | Instruction::Select - | Instruction::GlobalGet(_) - | Instruction::GlobalSet(_) - | Instruction::I32Load(_) - | Instruction::I64Load(_) - | Instruction::F32Load(_) - | Instruction::F64Load(_) - | Instruction::I32Load8S(_) - | Instruction::I32Load8U(_) - | Instruction::I32Load16S(_) - | Instruction::I32Load16U(_) - | Instruction::I64Load8S(_) - | Instruction::I64Load8U(_) - | Instruction::I64Load16S(_) - | Instruction::I64Load16U(_) - | Instruction::I64Load32S(_) - | Instruction::I64Load32U(_) - | Instruction::I32Store(_) - | Instruction::I64Store(_) - | Instruction::F32Store(_) - | Instruction::F64Store(_) - | Instruction::I32Store8(_) - | Instruction::I32Store16(_) - | Instruction::I64Store8(_) - | Instruction::I64Store16(_) - | Instruction::I64Store32(_) - | Instruction::MemorySize - | Instruction::MemoryGrow - | Instruction::MemoryFill - | Instruction::MemoryCopy - | Instruction::MemoryInit(_) - | Instruction::DataDrop(_) - | Instruction::TableSize(_) - | Instruction::TableGrow(_) - | Instruction::TableFill(_) - | Instruction::TableGet(_) - | Instruction::TableSet(_) - | Instruction::TableCopy(_) - | Instruction::TableInit(_) - | Instruction::ElemDrop(_) - | Instruction::RefFunc(_) - | Instruction::I32Const(_) - | Instruction::I64Const(_) - | Instruction::I32Eqz - | Instruction::I32Eq - | Instruction::I32Ne - | Instruction::I32LtS - | Instruction::I32LtU - | Instruction::I32GtS - | Instruction::I32GtU - | Instruction::I32LeS - | Instruction::I32LeU - | Instruction::I32GeS - | Instruction::I32GeU - | Instruction::I64Eqz - | Instruction::I64Eq - | Instruction::I64Ne - | Instruction::I64LtS - | Instruction::I64LtU - | Instruction::I64GtS - | Instruction::I64GtU - | Instruction::I64LeS - | Instruction::I64LeU - | Instruction::I64GeS - | Instruction::I64GeU - | Instruction::F32Eq - | Instruction::F32Ne - | Instruction::F32Lt - | Instruction::F32Gt - | Instruction::F32Le - | Instruction::F32Ge - | Instruction::F64Eq - | Instruction::F64Ne - | Instruction::F64Lt - | Instruction::F64Gt - | Instruction::F64Le - | Instruction::F64Ge - | Instruction::I32Clz - | Instruction::I32Ctz - | Instruction::I32Popcnt - | Instruction::I32Add - | Instruction::I32Sub - | Instruction::I32Mul - | Instruction::I32DivS - | Instruction::I32DivU - | Instruction::I32RemS - | Instruction::I32RemU - | Instruction::I32And - | Instruction::I32Or - | Instruction::I32Xor - | Instruction::I32Shl - | Instruction::I32ShrS - | Instruction::I32ShrU - | Instruction::I32Rotl - | Instruction::I32Rotr - | Instruction::I64Clz - | Instruction::I64Ctz - | Instruction::I64Popcnt - | Instruction::I64Add - | Instruction::I64Sub - | Instruction::I64Mul - | Instruction::I64DivS - | Instruction::I64DivU - | Instruction::I64RemS - | Instruction::I64RemU - | Instruction::I64And - | Instruction::I64Or - | Instruction::I64Xor - | Instruction::I64Shl - | Instruction::I64ShrS - | Instruction::I64ShrU - | Instruction::I64Rotl - | Instruction::I64Rotr - | Instruction::F32Abs - | Instruction::F32Neg - | Instruction::F32Ceil - | Instruction::F32Floor - | Instruction::F32Trunc - | Instruction::F32Nearest - | Instruction::F32Sqrt - | Instruction::F32Add - | Instruction::F32Sub - | Instruction::F32Mul - | Instruction::F32Div - | Instruction::F32Min - | Instruction::F32Max - | Instruction::F32Copysign - | Instruction::F64Abs - | Instruction::F64Neg - | Instruction::F64Ceil - | Instruction::F64Floor - | Instruction::F64Trunc - | Instruction::F64Nearest - | Instruction::F64Sqrt - | Instruction::F64Add - | Instruction::F64Sub - | Instruction::F64Mul - | Instruction::F64Div - | Instruction::F64Min - | Instruction::F64Max - | Instruction::F64Copysign - | Instruction::I32WrapI64 - | Instruction::I32TruncF32S - | Instruction::I32TruncF32U - | Instruction::I32TruncF64S - | Instruction::I32TruncF64U - | Instruction::I64ExtendI32S - | Instruction::I64ExtendI32U - | Instruction::I64TruncF32S - | Instruction::I64TruncF32U - | Instruction::I64TruncF64S - | Instruction::I64TruncF64U - | Instruction::F32ConvertI32S - | Instruction::F32ConvertI32U - | Instruction::F32ConvertI64S - | Instruction::F32ConvertI64U - | Instruction::F32DemoteF64 - | Instruction::F64ConvertI32S - | Instruction::F64ConvertI32U - | Instruction::F64ConvertI64S - | Instruction::F64ConvertI64U - | Instruction::F64PromoteF32 - | Instruction::I32Extend8S - | Instruction::I32Extend16S - | Instruction::I64Extend8S - | Instruction::I64Extend16S - | Instruction::I64Extend32S - | Instruction::I32TruncSatF32S - | Instruction::I32TruncSatF32U - | Instruction::I32TruncSatF64S - | Instruction::I32TruncSatF64U - | Instruction::I64TruncSatF32S - | Instruction::I64TruncSatF32U - | Instruction::I64TruncSatF64S - | Instruction::I64TruncSatF64U => true, - _ => false, - } - } - - /// Increases the fuel consumption of the [`ConsumeFuel`] instruction by `delta`. - /// - /// # Panics - /// - /// - If `self` is not a [`ConsumeFuel`] instruction. - /// - If the new fuel consumption overflows the internal `u64` value. - /// - /// [`ConsumeFuel`]: Instruction::ConsumeFuel - pub fn bump_fuel_consumption(&mut self, delta: u64) -> Result<(), TranslationError> { - match self { - Self::ConsumeFuel(block_fuel) => block_fuel.bump_by(delta), - instr => panic!("expected Instruction::ConsumeFuel but found: {:?}", instr), - } - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct InstrMeta(usize, u16, pub(crate) usize); - -impl InstrMeta { - pub fn new(pos: usize, code: u16, index: usize) -> Self { - Self(pos, code, index) - } - - pub fn offset(&self) -> usize { - self.0 - } - - pub fn opcode(&self) -> u16 { - self.1 - } - - pub fn index(&self) -> usize { - self.2 - } -} diff --git a/legacy/src/engine/bytecode/stack_height.rs b/legacy/src/engine/bytecode/stack_height.rs deleted file mode 100644 index cad031a13..000000000 --- a/legacy/src/engine/bytecode/stack_height.rs +++ /dev/null @@ -1,360 +0,0 @@ -use crate::engine::bytecode::Instruction; -use alloc::vec::Vec; - -#[derive(Debug, Copy, Clone)] -pub enum RwOp { - StackWrite(u32), - StackRead(u32), - GlobalWrite(u32), - GlobalRead(u32), - MemoryWrite { - offset: u32, - length: u32, - signed: bool, - }, - MemoryRead { - offset: u32, - length: u32, - signed: bool, - }, - MemorySizeWrite, - MemorySizeRead, - TableSizeRead(u32), - TableSizeWrite(u32), - TableElemRead(u32), - TableElemWrite(u32), - DataWrite(u32), - DataRead(u32), -} - -impl Instruction { - pub fn get_rw_count(&self) -> usize { - let mut rw_count = 0; - for rw_op in self.get_rw_ops() { - match rw_op { - RwOp::MemoryWrite { length, .. } => rw_count += length as usize, - RwOp::MemoryRead { length, .. } => rw_count += length as usize, - _ => rw_count += 1, - } - } - rw_count - } - - pub fn get_rw_ops(&self) -> Vec { - let mut stack_ops = Vec::new(); - match *self { - Instruction::LocalGet(local_depth) => { - stack_ops.push(RwOp::StackRead(local_depth.to_usize() as u32)); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::LocalSet(local_depth) => { - stack_ops.push(RwOp::StackRead(0)); - // local depth can't be zero otherwise this op is useless - if local_depth.to_usize() > 0 { - stack_ops.push(RwOp::StackWrite(local_depth.to_usize() as u32 - 1)); - } else { - stack_ops.push(RwOp::StackWrite(0)); - } - } - Instruction::LocalTee(local_depth) => { - stack_ops.push(RwOp::StackRead(0)); - // local depth can't be zero otherwise this op is useless - if local_depth.to_usize() > 0 { - stack_ops.push(RwOp::StackWrite(local_depth.to_usize() as u32 - 1)); - } else { - stack_ops.push(RwOp::StackWrite(0)); - } - } - Instruction::Br(_) => {} - Instruction::BrIfEqz(_) | Instruction::BrIfNez(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::BrAdjust(_) => {} - Instruction::BrAdjustIfNez(_) | Instruction::BrTable(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::Unreachable | Instruction::ConsumeFuel(_) | Instruction::Return(_) => {} - Instruction::ReturnIfNez(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::ReturnCallInternal(_) | Instruction::ReturnCall(_) => {} - Instruction::ReturnCallIndirect(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::CallInternal(_) => {} - Instruction::Call(_) => {} - Instruction::CallIndirect(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::Drop => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::Select => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::GlobalGet(val) => { - stack_ops.push(RwOp::GlobalRead(val.to_u32())); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::GlobalSet(val) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::GlobalWrite(val.to_u32())); - } - Instruction::I32Load(val) - | Instruction::I64Load(val) - | Instruction::F32Load(val) - | Instruction::F64Load(val) - | Instruction::I32Load8S(val) - | Instruction::I32Load8U(val) - | Instruction::I32Load16S(val) - | Instruction::I32Load16U(val) - | Instruction::I64Load8S(val) - | Instruction::I64Load8U(val) - | Instruction::I64Load16S(val) - | Instruction::I64Load16U(val) - | Instruction::I64Load32S(val) - | Instruction::I64Load32U(val) => { - let (_, commit_byte_len, signed) = Self::load_instr_meta(self); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::MemoryRead { - offset: val.into_inner(), - length: commit_byte_len as u32, - signed, - }); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::I32Store(val) - | Instruction::I64Store(val) - | Instruction::F32Store(val) - | Instruction::F64Store(val) - | Instruction::I32Store8(val) - | Instruction::I32Store16(val) - | Instruction::I64Store8(val) - | Instruction::I64Store16(val) - | Instruction::I64Store32(val) => { - let length = Self::store_instr_meta(self); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::MemoryWrite { - offset: val.into_inner(), - length: length as u32, - signed: false, - }); - } - Instruction::MemorySize => { - stack_ops.push(RwOp::MemorySizeRead); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::MemoryGrow => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - stack_ops.push(RwOp::MemorySizeWrite); - } - Instruction::MemoryFill | Instruction::MemoryCopy => { - // unreachable!("not implemented here") - } - Instruction::MemoryInit(_) => {} - Instruction::DataDrop(_) => {} - - Instruction::TableSize(table_idx) => { - stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::TableGrow(table_idx) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::TableSizeWrite(table_idx.to_u32())); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::TableFill(table_idx) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); - } - Instruction::TableGet(_) => { - panic!("custom function is used"); - } - Instruction::TableSet(table_idx) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::TableElemWrite(table_idx.to_u32())); - stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); - } - Instruction::TableCopy(_) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::TableInit(_) => {} - - Instruction::ElemDrop(_) => {} - Instruction::RefFunc(_) => { - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::ConstRef(_) => stack_ops.push(RwOp::StackWrite(0)), - - Instruction::I32Eqz - | Instruction::I32Eq - | Instruction::I64Eqz - | Instruction::I64Eq - | Instruction::I32Ne - | Instruction::I64Ne => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::I32LtS - | Instruction::I32LtU - | Instruction::I32GtS - | Instruction::I32GtU - | Instruction::I32LeS - | Instruction::I32LeU - | Instruction::I32GeS - | Instruction::I32GeU - | Instruction::I64LtS - | Instruction::I64LtU - | Instruction::I64GtS - | Instruction::I64GtU - | Instruction::I64LeS - | Instruction::I64LeU - | Instruction::I64GeS - | Instruction::I64GeU - | Instruction::F32Eq - | Instruction::F32Lt - | Instruction::F32Gt - | Instruction::F32Le - | Instruction::F32Ge - | Instruction::F32Ne - | Instruction::F64Eq - | Instruction::F64Ne - | Instruction::F64Lt - | Instruction::F64Gt - | Instruction::F64Le - | Instruction::F64Ge => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::I32Clz - | Instruction::I64Clz - | Instruction::I32Ctz - | Instruction::I64Ctz - | Instruction::I32Popcnt - | Instruction::I64Popcnt => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::I32Add - | Instruction::I32Sub - | Instruction::I32Mul - | Instruction::I32DivS - | Instruction::I32DivU - | Instruction::I32RemS - | Instruction::I32RemU - | Instruction::I32And - | Instruction::I32Or - | Instruction::I32Xor - | Instruction::I32Shl - | Instruction::I32ShrS - | Instruction::I32ShrU - | Instruction::I32Rotl - | Instruction::I32Rotr - | Instruction::I64Add - | Instruction::I64Sub - | Instruction::I64Mul - | Instruction::I64DivS - | Instruction::I64DivU - | Instruction::I64RemS - | Instruction::I64RemU - | Instruction::I64And - | Instruction::I64Or - | Instruction::I64Xor - | Instruction::I64Shl - | Instruction::I64ShrS - | Instruction::I64ShrU - | Instruction::I64Rotl - | Instruction::I64Rotr - | Instruction::F32Add - | Instruction::F32Sub - | Instruction::F32Mul - | Instruction::F32Div - | Instruction::F32Min - | Instruction::F32Max - | Instruction::F32Copysign - | Instruction::F64Add - | Instruction::F64Sub - | Instruction::F64Mul - | Instruction::F64Div - | Instruction::F64Min - | Instruction::F64Max - | Instruction::F64Copysign => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::I32WrapI64 - | Instruction::I32TruncF32S - | Instruction::I32TruncF32U - | Instruction::I32TruncF64S - | Instruction::I32TruncF64U - | Instruction::I64ExtendI32S - | Instruction::I64ExtendI32U - | Instruction::I64TruncF32S - | Instruction::I64TruncF32U - | Instruction::I64TruncF64S - | Instruction::I64TruncF64U - | Instruction::F32ConvertI32S - | Instruction::F32ConvertI32U - | Instruction::F32ConvertI64S - | Instruction::F32ConvertI64U - | Instruction::F32DemoteF64 - | Instruction::F64ConvertI32S - | Instruction::F64ConvertI32U - | Instruction::F64ConvertI64S - | Instruction::F64ConvertI64U - | Instruction::F64PromoteF32 - | Instruction::I32Extend8S - | Instruction::I32Extend16S - | Instruction::I64Extend8S - | Instruction::I64Extend16S - | Instruction::I64Extend32S - | Instruction::I32TruncSatF32S - | Instruction::I32TruncSatF32U - | Instruction::I32TruncSatF64S - | Instruction::I32TruncSatF64U - | Instruction::I64TruncSatF32S - | Instruction::I64TruncSatF32U - | Instruction::I64TruncSatF64S - | Instruction::I64TruncSatF64U => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::F32Sqrt => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - _ => unreachable!("not supported rws for opcode: {:?}", self), - } - stack_ops - } - - pub fn get_stack_diff(&self) -> i32 { - let mut stack_diff = 0; - for rw_op in self.get_rw_ops() { - match rw_op { - RwOp::StackWrite(_) => stack_diff += 1, - RwOp::StackRead(_) => stack_diff -= 1, - _ => {} - } - } - stack_diff - } -} diff --git a/legacy/src/engine/bytecode/tests.rs b/legacy/src/engine/bytecode/tests.rs deleted file mode 100644 index 0a3a77328..000000000 --- a/legacy/src/engine/bytecode/tests.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; -use core::mem::size_of; - -#[test] -fn size_of_instruction() { - assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); -} diff --git a/legacy/src/engine/bytecode/utils.rs b/legacy/src/engine/bytecode/utils.rs deleted file mode 100644 index e488417a7..000000000 --- a/legacy/src/engine/bytecode/utils.rs +++ /dev/null @@ -1,429 +0,0 @@ -use crate::engine::{func_builder::TranslationErrorInner, Instr, TranslationError}; -use core::fmt::{self, Display}; - -/// A 32-bit encoded `f64` value. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -pub struct F64Const32(u32); - -impl F64Const32 { - /// Creates an [`Instruction::F64Const32`] from the given `f64` value if possible. - /// - /// [`Instruction::F64Const32`]: [`super::Instruction::F64Const32`] - pub fn new(value: f64) -> Option { - let demoted = value as f32; - if f64::from(demoted).to_bits() != value.to_bits() { - return None; - } - Some(Self(demoted.to_bits())) - } - - /// Returns the 32-bit encoded `f64` value. - pub fn to_f64(self) -> f64 { - f64::from(f32::from_bits(self.0)) - } -} - -/// A function index. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct FuncIdx(u32); - -impl From for FuncIdx { - fn from(index: u16) -> Self { - Self(index as u32) - } -} -impl From for FuncIdx { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl FuncIdx { - pub const fn from_u32(value: u32) -> Self { - Self(value) - } - /// Returns the index value as `u32`. - pub const fn to_u32(self) -> u32 { - self.0 - } -} - -/// A table index. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct TableIdx(u32); - -impl From for TableIdx { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl TableIdx { - /// Returns the index value as `u32`. - pub fn to_u32(self) -> u32 { - self.0 - } -} - -/// An index of a unique function signature. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct SignatureIdx(u32); - -impl From for SignatureIdx { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl SignatureIdx { - /// Returns the index value as `u32`. - pub fn to_u32(self) -> u32 { - self.0 - } -} - -/// A local variable depth access index. -/// -/// # Note -/// -/// The depth refers to the relative position of a local -/// variable on the value stack with respect to the height -/// of the value stack at the time of access. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct LocalDepth(u32); - -impl From for LocalDepth { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl LocalDepth { - pub const fn from_u32(value: u32) -> Self { - Self(value) - } - /// Returns the depth as `usize` index. - pub const fn to_usize(self) -> usize { - self.0 as usize - } -} - -/// A global variable index. -/// -/// # Note -/// -/// Refers to a global variable of a [`Store`]. -/// -/// [`Store`]: [`crate::Store`] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct GlobalIdx(u32); - -impl From for GlobalIdx { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl GlobalIdx { - /// Returns the index value as `u32`. - pub fn to_u32(self) -> u32 { - self.0 - } -} - -/// A data segment index. -/// -/// # Note -/// -/// Refers to a data segment of a [`Store`]. -/// -/// [`Store`]: [`crate::Store`] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct DataSegmentIdx(u32); - -impl From for DataSegmentIdx { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl DataSegmentIdx { - /// Returns the index value as `u32`. - pub fn to_u32(self) -> u32 { - self.0 - } -} - -/// An element segment index. -/// -/// # Note -/// -/// Refers to a data segment of a [`Store`]. -/// -/// [`Store`]: [`crate::Store`] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct ElementSegmentIdx(u32); - -impl From for ElementSegmentIdx { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl ElementSegmentIdx { - /// Returns the index value as `u32`. - pub fn to_u32(self) -> u32 { - self.0 - } -} - -/// The number of branches of an [`Instruction::BrTable`]. -/// -/// [`Instruction::BrTable`]: [`super::Instruction::BrTable`] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct BranchTableTargets(u32); - -impl TryFrom for BranchTableTargets { - type Error = TranslationError; - - fn try_from(index: usize) -> Result { - match u32::try_from(index) { - Ok(index) => Ok(Self(index)), - Err(_) => Err(TranslationError::new( - TranslationErrorInner::BranchTableTargetsOutOfBounds, - )), - } - } -} - -impl From for BranchTableTargets { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl BranchTableTargets { - /// Returns the index value as `usize`. - pub fn to_usize(self) -> usize { - self.0 as usize - } -} - -/// The accumulated fuel to execute a block via [`Instruction::ConsumeFuel`]. -/// -/// [`Instruction::ConsumeFuel`]: [`super::Instruction::ConsumeFuel`] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct BlockFuel(u32); - -impl TryFrom for BlockFuel { - type Error = TranslationError; - - fn try_from(index: u64) -> Result { - match u32::try_from(index) { - Ok(index) => Ok(Self(index)), - Err(_) => Err(TranslationError::new( - TranslationErrorInner::BlockFuelOutOfBounds, - )), - } - } -} - -impl From for BlockFuel { - fn from(value: u32) -> Self { - BlockFuel(value) - } -} - -impl BlockFuel { - /// Bump the fuel by `amount` if possible. - /// - /// # Errors - /// - /// If the new fuel amount after this operation is out of bounds. - pub fn bump_by(&mut self, amount: u64) -> Result<(), TranslationError> { - let new_amount = self - .to_u64() - .checked_add(amount) - .ok_or(TranslationErrorInner::BlockFuelOutOfBounds) - .map_err(TranslationError::new)?; - self.0 = u32::try_from(new_amount) - .map_err(|_| TranslationErrorInner::BlockFuelOutOfBounds) - .map_err(TranslationError::new)?; - Ok(()) - } - - /// Returns the index value as `u64`. - pub fn to_u64(self) -> u64 { - u64::from(self.0) - } -} - -/// A linear memory access offset. -/// -/// # Note -/// -/// Used to calculate the effective address of a linear memory access. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct AddressOffset(u32); - -impl From for AddressOffset { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl AddressOffset { - /// Returns the inner `u32` index. - pub fn into_inner(self) -> u32 { - self.0 - } -} - -/// A signed offset for branch instructions. -/// -/// This defines how much the instruction pointer is offset -/// upon taking the respective branch. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -pub struct BranchOffset(i32); - -impl From for BranchOffset { - fn from(index: i32) -> Self { - Self(index) - } -} - -impl BranchOffset { - /// Creates an uninitalized [`BranchOffset`]. - pub fn uninit() -> Self { - Self(0) - } - - /// Creates an initialized [`BranchOffset`] from `src` to `dst`. - /// - /// # Errors - /// - /// If the resulting [`BranchOffset`] is out of bounds. - /// - /// # Panics - /// - /// If the resulting [`BranchOffset`] is uninitialized, aka equal to 0. - pub fn from_src_to_dst(src: Instr, dst: Instr) -> Result { - fn make_err() -> TranslationError { - TranslationError::new(TranslationErrorInner::BranchOffsetOutOfBounds) - } - let src = i64::from(src.into_u32()); - let dst = i64::from(dst.into_u32()); - let offset = dst.checked_sub(src).ok_or_else(make_err)?; - let offset = i32::try_from(offset).map_err(|_| make_err())?; - Ok(Self(offset)) - } - - /// Returns `true` if the [`BranchOffset`] has been initialized. - pub fn is_init(self) -> bool { - self.to_i32() != 0 - } - - /// Initializes the [`BranchOffset`] with a proper value. - /// - /// # Panics - /// - /// - If the [`BranchOffset`] have already been initialized. - /// - If the given [`BranchOffset`] is not properly initialized. - pub fn init(&mut self, valid_offset: BranchOffset) { - assert!(valid_offset.is_init()); - assert!(!self.is_init()); - *self = valid_offset; - } - - /// Returns the `i32` representation of the [`BranchOffset`]. - pub fn to_i32(self) -> i32 { - self.0 - } -} - -/// Defines how many stack values are going to be dropped and kept after branching. -#[derive(Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -pub struct DropKeep { - drop: u16, - keep: u16, -} - -impl fmt::Debug for DropKeep { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("DropKeep") - .field("drop", &self.drop()) - .field("keep", &self.keep()) - .finish() - } -} - -/// An error that may occur upon operating on [`DropKeep`]. -#[derive(Debug, Copy, Clone)] -pub enum DropKeepError { - /// The amount of kept elements exceeds the engine's limits. - KeepOutOfBounds, - /// The amount of dropped elements exceeds the engine's limits. - DropOutOfBounds, -} - -impl Display for DropKeepError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DropKeepError::KeepOutOfBounds => { - write!(f, "amount of kept elements exceeds engine limits") - } - DropKeepError::DropOutOfBounds => { - write!(f, "amount of dropped elements exceeds engine limits") - } - } - } -} - -impl DropKeep { - pub fn none() -> Self { - Self { drop: 0, keep: 0 } - } - - /// Returns the amount of stack values to keep. - pub fn keep(self) -> u16 { - self.keep - } - - pub fn add_keep(&mut self, delta: u16) { - self.keep += delta; - } - - /// Returns the amount of stack values to drop. - pub fn drop(self) -> u16 { - self.drop - } - - /// Returns `true` if the [`DropKeep`] does nothing. - pub fn is_noop(self) -> bool { - self.drop == 0 - } - - /// Creates a new [`DropKeep`] with the given amounts to drop and keep. - /// - /// # Errors - /// - /// - If `keep` is larger than `drop`. - /// - If `keep` is out of bounds. (max 4095) - /// - If `drop` is out of bounds. (delta to keep max 4095) - pub fn new(drop: usize, keep: usize) -> Result { - let keep = u16::try_from(keep).map_err(|_| DropKeepError::KeepOutOfBounds)?; - let drop = u16::try_from(drop).map_err(|_| DropKeepError::KeepOutOfBounds)?; - // Now we can cast `drop` and `keep` to `u16` values safely. - Ok(Self { drop, keep }) - } -} diff --git a/legacy/src/engine/cache.rs b/legacy/src/engine/cache.rs deleted file mode 100644 index 757d0e58f..000000000 --- a/legacy/src/engine/cache.rs +++ /dev/null @@ -1,376 +0,0 @@ -use super::bytecode::{DataSegmentIdx, ElementSegmentIdx, FuncIdx, GlobalIdx, TableIdx}; -use crate::{ - core::UntypedValue, - instance::InstanceEntity, - memory::DataSegment, - module::DEFAULT_MEMORY_INDEX, - table::TableEntity, - ElementSegment, - ElementSegmentEntity, - Func, - Instance, - Memory, - StoreInner, - Table, -}; -use core::ptr::NonNull; - -/// A cache for frequently used entities of an [`Instance`]. -#[derive(Debug)] -#[repr(C)] -pub struct InstanceCache { - /// The bytes of a default linear memory of the currently used [`Instance`]. - default_memory_bytes: Option>, - /// The last accessed global variable value of the currently used [`Instance`]. - last_global: Option<(GlobalIdx, NonNull)>, - /// The current instance in use. - instance: Instance, - /// The default linear memory of the currently used [`Instance`]. - default_memory: Option, - /// The last accessed table of the currently used [`Instance`]. - last_table: Option<(TableIdx, Table)>, - /// The last accessed function of the currently used [`Instance`]. - last_func: Option<(FuncIdx, Func)>, -} - -impl From<&'_ Instance> for InstanceCache { - fn from(instance: &Instance) -> Self { - Self { - instance: *instance, - default_memory: None, - last_table: None, - last_func: None, - last_global: None, - default_memory_bytes: None, - } - } -} - -impl InstanceCache { - /// Resolves the instances. - #[inline] - pub fn instance(&self) -> &Instance { - &self.instance - } - - /// Updates the cached [`Instance`]. - #[cold] - #[inline] - fn set_instance(&mut self, instance: &Instance) { - self.instance = *instance; - self.default_memory = None; - self.last_table = None; - self.last_func = None; - self.last_global = None; - self.default_memory_bytes = None; - } - - /// Updates the currently used instance resetting all cached entities. - #[inline] - pub fn update_instance(&mut self, instance: &Instance) { - if instance == self.instance() { - return; - } - self.set_instance(instance); - } - - /// Loads the [`DataSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`DataSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_data_segment(&mut self, ctx: &StoreInner, index: u32) -> DataSegment { - let instance = self.instance(); - ctx.resolve_instance(instance) - .get_data_segment(index) - .unwrap_or_else(|| { - unreachable!("missing data segment ({index:?}) for instance: {instance:?}",) - }) - } - - /// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_element_segment( - &mut self, - ctx: &StoreInner, - index: ElementSegmentIdx, - ) -> ElementSegment { - let instance = self.instance(); - ctx.resolve_instance(instance) - .get_element_segment(index.to_u32()) - .unwrap_or_else(|| { - unreachable!("missing element segment ({index:?}) for instance: {instance:?}",) - }) - } - - /// Loads the [`DataSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`DataSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_default_memory_and_data_segment<'a>( - &mut self, - ctx: &'a mut StoreInner, - segment: DataSegmentIdx, - ) -> (&'a mut [u8], &'a [u8]) { - let seg = self.get_data_segment(ctx, segment.to_u32()); - let mem = self.default_memory(ctx); - let (memory, segment) = ctx.resolve_memory_mut_and_data_segment(mem, &seg); - (memory.data_mut(), segment.bytes()) - } - - /// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_table_and_element_segment<'a>( - &mut self, - ctx: &'a mut StoreInner, - table: TableIdx, - segment: ElementSegmentIdx, - ) -> ( - &'a InstanceEntity, - &'a mut TableEntity, - &'a ElementSegmentEntity, - ) { - let tab = self.get_table(ctx, table); - let seg = self.get_element_segment(ctx, segment); - let inst = self.instance(); - ctx.resolve_instance_table_element(inst, &tab, &seg) - } - - /// Loads the default [`Memory`] of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default linear memory. - #[cold] - #[inline] - fn load_default_memory(&mut self, ctx: &StoreInner) -> &Memory { - let instance = self.instance(); - let default_memory = ctx - .resolve_instance(instance) - .get_memory(DEFAULT_MEMORY_INDEX) - .unwrap_or_else(|| { - unreachable!("missing default linear memory for instance: {instance:?}") - }); - self.default_memory.insert(default_memory) - } - - /// Returns the default [`Memory`] of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default linear memory. - #[inline] - pub fn default_memory(&mut self, ctx: &StoreInner) -> &Memory { - match self.default_memory { - Some(ref default_memory) => default_memory, - None => self.load_default_memory(ctx), - } - } - - /// Returns a cached default linear memory. - /// - /// # Note - /// - /// This avoids one indirection compared to using the `default_memory`. - #[inline] - pub fn default_memory_bytes<'ctx>(&mut self, ctx: &'ctx mut StoreInner) -> &'ctx mut [u8] { - let bytes = match self.default_memory_bytes { - Some(ref mut cached) => cached, - None => self.load_default_memory_bytes(ctx), - }; - unsafe { bytes.as_mut() } - } - - /// Loads and populates the cached default memory instance. - /// - /// Returns an exclusive reference to the cached default memory. - #[cold] - #[inline] - fn load_default_memory_bytes(&mut self, ctx: &mut StoreInner) -> &mut NonNull<[u8]> { - let memory = *self.default_memory(ctx); - self.default_memory_bytes - .insert(ctx.resolve_memory_mut(&memory).data().into()) - } - - /// Clears the cached default memory instance. - /// - /// # Note - /// - /// - This is important when operations such as `memory.grow` have occured that might have - /// invalidated the cached memory. - /// - It is equally important to reset cached default memory bytes when calling a host function - /// since it might call `memory.grow`. - #[inline] - pub fn reset_default_memory_bytes(&mut self) { - self.default_memory_bytes = None; - self.last_global = None; - } - - /// Clears the cached default memory instance and global variable. - /// - /// # Note - /// - /// - This is required for host function calls for reasons explained in - /// [`InstanceCache::reset_default_memory_bytes`]. - /// - Furthermore a called host function could introduce new global variables to the [`Store`] - /// and thus might invalidate cached global variables. So we need to reset them as well. - /// - /// [`Store`]: crate::Store - #[inline] - pub fn reset(&mut self) { - self.reset_default_memory_bytes(); - self.last_global = None; - } - - /// Returns the [`Table`] at the `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default table. - #[inline] - pub fn get_table(&mut self, ctx: &StoreInner, index: TableIdx) -> Table { - match self.last_table { - Some((table_index, table)) if index == table_index => table, - _ => self.load_table_at(ctx, index), - } - } - - /// Loads the [`Table`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have the table. - #[cold] - #[inline] - fn load_table_at(&mut self, ctx: &StoreInner, index: TableIdx) -> Table { - let table = ctx - .resolve_instance(self.instance()) - .get_table(index.to_u32()) - .unwrap_or_else(|| { - unreachable!( - "missing table at index {index:?} for instance: {:?}", - self.instance - ) - }); - self.last_table = Some((index, table)); - table - } - - /// Loads the [`Func`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have the function. - #[cold] - #[inline] - fn load_func_at(&mut self, ctx: &StoreInner, index: FuncIdx) -> Func { - let func = ctx - .resolve_instance(self.instance()) - .get_func(index.to_u32()) - .unwrap_or_else(|| { - unreachable!( - "missing func at index {index:?} for instance: {:?}", - self.instance - ) - }); - self.last_func = Some((index, func)); - func - } - - /// Loads the [`Func`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline] - pub fn get_func(&mut self, ctx: &StoreInner, func_idx: FuncIdx) -> Func { - match self.last_func { - Some((index, func)) if index == func_idx => func, - _ => self.load_func_at(ctx, func_idx), - } - } - - /// Loads the pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default table. - #[cold] - #[inline] - fn load_global_at(&mut self, ctx: &mut StoreInner, index: GlobalIdx) -> NonNull { - let global = ctx - .resolve_instance(self.instance()) - .get_global(index.to_u32()) - .as_ref() - .map(|global| ctx.resolve_global_mut(global).get_untyped_ptr()) - .unwrap_or_else(|| { - unreachable!( - "missing global variable at index {index:?} for instance: {:?}", - self.instance - ) - }); - self.last_global = Some((index, global)); - global - } - - /// Returns a pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline(always)] - fn get_global_mut<'ctx>( - &mut self, - ctx: &'ctx mut StoreInner, - global_index: GlobalIdx, - ) -> &'ctx mut UntypedValue { - let mut ptr = match self.last_global { - Some((index, global)) if index == global_index => global, - _ => self.load_global_at(ctx, global_index), - }; - // SAFETY: This deref is safe since we only hold this pointer - // as long as we are sure that nothing else can manipulate - // the global in a way that would invalidate the pointer. - unsafe { ptr.as_mut() } - } - - /// Returns a pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline(always)] - pub fn get_global(&mut self, ctx: &mut StoreInner, global_index: GlobalIdx) -> UntypedValue { - *self.get_global_mut(ctx, global_index) - } - - /// Returns a pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline(always)] - pub fn set_global( - &mut self, - ctx: &mut StoreInner, - global_index: GlobalIdx, - new_value: UntypedValue, - ) { - *self.get_global_mut(ctx, global_index) = new_value; - } -} diff --git a/legacy/src/engine/code_map.rs b/legacy/src/engine/code_map.rs deleted file mode 100644 index 0deeaaf51..000000000 --- a/legacy/src/engine/code_map.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! Datastructure to efficiently store function bodies and their instructions. - -use super::Instruction; -use crate::{arena::ArenaIndex, engine::bytecode::InstrMeta}; -use alloc::vec::Vec; -use hashbrown::HashMap; - -/// A reference to a compiled function stored in the [`CodeMap`] of an [`Engine`](crate::Engine). -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -pub struct CompiledFunc(u32); - -impl ArenaIndex for CompiledFunc { - fn into_usize(self) -> usize { - self.0 as usize - } - - fn from_usize(index: usize) -> Self { - let index = u32::try_from(index) - .unwrap_or_else(|_| panic!("out of bounds compiled func index: {index}")); - CompiledFunc(index) - } -} - -impl From for CompiledFunc { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl CompiledFunc { - pub fn to_u32(&self) -> u32 { - self.0 - } -} - -/// A reference to the instructions of a compiled Wasm function. -#[derive(Debug, Copy, Clone)] -pub struct InstructionsRef { - /// The start index in the instructions array. - index: usize, -} - -impl InstructionsRef { - /// Creates a new valid [`InstructionsRef`] for the given `index`. - /// - /// # Note - /// - /// The `index` denotes the index of the first instruction in the sequence - /// of instructions denoted by [`InstructionsRef`]. - /// - /// # Panics - /// - /// If `index` is 0 since the zero index is reserved for uninitialized [`InstructionsRef`]. - fn new(index: usize) -> Self { - assert_ne!(index, 0, "must initialize with a proper non-zero index"); - Self { index } - } - - /// Creates a new uninitialized [`InstructionsRef`]. - pub fn uninit() -> Self { - Self { index: 0 } - } - - /// Returns `true` if the [`InstructionsRef`] refers to an uninitialized sequence of - /// instructions. - fn is_uninit(self) -> bool { - self.index == 0 - } - - /// Returns the `usize` value of the underlying index. - fn to_usize(self) -> usize { - self.index - } -} - -/// Meta information about a compiled function. -#[derive(Debug, Copy, Clone)] -pub struct FuncHeader { - /// A reference to the instructions of the function. - iref: InstructionsRef, - /// The number of local variables of the function. - len_locals: usize, - /// The maximum stack height usage of the function during execution. - max_stack_height: usize, -} - -impl FuncHeader { - /// Create a new initialized [`FuncHeader`]. - pub fn new(iref: InstructionsRef, len_locals: usize, local_stack_height: usize) -> Self { - let max_stack_height = local_stack_height - .checked_add(len_locals) - .unwrap_or_else(|| panic!("invalid maximum stack height for function")); - Self { - iref, - len_locals, - max_stack_height, - } - } - - /// Create a new uninitialized [`FuncHeader`]. - pub fn uninit() -> Self { - Self { - iref: InstructionsRef::uninit(), - len_locals: 0, - max_stack_height: 0, - } - } - - /// Returns `true` if the [`FuncHeader`] is uninitialized. - pub fn is_uninit(&self) -> bool { - self.iref.is_uninit() - } - - /// Returns a reference to the instructions of the function. - pub fn iref(&self) -> InstructionsRef { - self.iref - } - - /// Returns the amount of local variable of the function. - pub fn len_locals(&self) -> usize { - self.len_locals - } - - /// Returns the amount of stack values required by the function. - /// - /// # Note - /// - /// This amount includes the amount of local variables but does - /// _not_ include the amount of input parameters to the function. - pub fn max_stack_height(&self) -> usize { - self.max_stack_height - } -} - -/// Datastructure to efficiently store Wasm function bodies. -#[derive(Debug)] -pub struct CodeMap { - /// The headers of all compiled functions. - headers: Vec, - index_by_offset: HashMap, - /// The instructions of all allocated function bodies. - /// - /// By storing all `wasmi` bytecode instructions in a single - /// allocation we avoid an indirection when calling a function - /// compared to a solution that stores instructions of different - /// function bodies in different allocations. - /// - /// Also, this improves efficiency of deallocating the [`CodeMap`] - /// and generally improves data locality. - instrs: Vec, - metas: Vec, -} - -impl Default for CodeMap { - fn default() -> Self { - Self { - headers: Vec::new(), - index_by_offset: Default::default(), - // The first instruction always is a simple trapping instruction - // so that we safely can use `InstructionsRef(0)` as an uninitialized - // index value for compiled functions that have yet to be - // initialized with their actual function bodies. - instrs: vec![Instruction::Unreachable], - metas: vec![InstrMeta::default()], - } - } -} - -impl CodeMap { - /// Allocates a new uninitialized [`CompiledFunc`] to the [`CodeMap`]. - /// - /// # Note - /// - /// The uninitialized [`CompiledFunc`] must be initialized using - /// [`CodeMap::init_func`] before it is executed. - pub fn alloc_func(&mut self) -> CompiledFunc { - let header_index = self.headers.len(); - self.headers.push(FuncHeader::uninit()); - CompiledFunc::from_usize(header_index) - } - - /// Initializes the [`CompiledFunc`]. - /// - /// # Panics - /// - /// - If `func` is an invalid [`CompiledFunc`] reference for this [`CodeMap`]. - /// - If `func` refers to an already initialized [`CompiledFunc`]. - pub fn init_func( - &mut self, - func: CompiledFunc, - len_locals: usize, - local_stack_height: usize, - instrs: I, - metas: M, - ) where - I: IntoIterator, - M: IntoIterator, - { - assert!( - self.header(func).is_uninit(), - "func {func:?} is already initialized" - ); - let start = self.instrs.len(); - self.instrs.extend(instrs); - self.metas.extend(metas); - let iref = InstructionsRef::new(start); - self.headers[func.into_usize()] = FuncHeader::new(iref, len_locals, local_stack_height); - } - - pub fn mark_func( - &mut self, - func: CompiledFunc, - len_locals: usize, - local_stack_height: usize, - start: usize, - ) { - // first byte is reserved for unreachable - let start = start + 1; - assert!( - self.header(func).is_uninit(), - "func {func:?} is already initialized" - ); - let iref = InstructionsRef::new(start); - assert!( - start < self.instrs.len(), - "instruction overflow ({} > {})", - start, - self.instrs.len() - ); - self.headers[func.into_usize()] = FuncHeader::new(iref, len_locals, local_stack_height); - assert!( - !self.index_by_offset.contains_key(&start), - "function with such offset already exists" - ); - self.index_by_offset.insert(start - 1, func); - } - - pub fn resolve_function_by_offset(&self, offset: usize) -> Option { - self.index_by_offset.get(&offset).copied() - } - - /// Returns an [`InstructionPtr`] to the instruction at [`InstructionsRef`]. - #[inline] - pub fn instr_ptr(&self, iref: InstructionsRef) -> InstructionPtr { - InstructionPtr::new( - self.instrs[iref.to_usize()..].as_ptr(), - self.metas[iref.to_usize()..].as_ptr(), - ) - } - - /// Returns an [`InstructionPtr`] to the instruction at [`InstructionsRef`]. - #[inline] - pub fn instr_ptr_with_end(&self, func_body: CompiledFunc) -> (InstructionPtr, InstructionPtr) { - let header = self.header(func_body); - let start = header.iref.to_usize(); - let end = self.instr_end(func_body); - let start_ptr = InstructionPtr::new( - self.instrs[start..end].as_ptr(), - self.metas[start..end].as_ptr(), - ); - let mut end_ptr = start_ptr; - end_ptr.add(end - start); - (start_ptr, end_ptr) - } - - /// Returns the [`FuncHeader`] of the [`CompiledFunc`]. - pub fn header(&self, func_body: CompiledFunc) -> &FuncHeader { - &self.headers[func_body.into_usize()] - } - - /// Resolves the instruction at `index` of the compiled [`CompiledFunc`]. - pub fn get_instr(&self, func_body: CompiledFunc, index: usize) -> Option<&Instruction> { - let header = self.header(func_body); - let start = header.iref.to_usize(); - let end = self.instr_end(func_body); - let instrs = &self.instrs[start..end]; - instrs.get(index) - } - - pub fn instr_vec(&self, func_body: CompiledFunc) -> Vec { - let header = self.header(func_body); - let start = header.iref.index; - let end = self.instr_end(func_body); - self.instrs[start..end].to_vec() - } - - pub fn num_locals(&self, func_body: CompiledFunc) -> u32 { - let header = self.header(func_body); - header.len_locals as u32 - } - - /// Returns the `end` index of the instructions of [`CompiledFunc`]. - /// - /// This is important to synthesize how many instructions there are in - /// the function referred to by [`CompiledFunc`]. - pub fn instr_end(&self, func_body: CompiledFunc) -> usize { - self.headers - .get(func_body.into_usize() + 1) - .map(|header| header.iref.to_usize()) - .unwrap_or(self.instrs.len()) - } -} - -/// The instruction pointer to the instruction of a function on the call stack. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct InstructionPtr { - /// The pointer to the instruction. - pub(crate) ptr: *const Instruction, - pub(crate) src: *const Instruction, - /// The pointer to metas - pub(crate) meta: *const InstrMeta, -} - -/// It is safe to send an [`InstructionPtr`] to another thread. -/// -/// The access to the pointed-to [`Instruction`] is read-only and -/// [`Instruction`] itself is [`Send`]. -/// -/// However, it is not safe to share an [`InstructionPtr`] between threads -/// due to their [`InstructionPtr::offset`] method which relinks the -/// internal pointer and is not synchronized. -unsafe impl Send for InstructionPtr {} - -impl InstructionPtr { - /// Creates a new [`InstructionPtr`] for `instr`. - #[inline] - pub fn new(ptr: *const Instruction, meta: *const InstrMeta) -> Self { - Self { - ptr, - src: ptr, - meta, - } - } - - #[inline(always)] - pub fn pc(&self) -> u32 { - let size = core::mem::size_of::() as u32; - let diff = self.ptr as u32 - self.src as u32; - diff / size - } - - /// Offset the [`InstructionPtr`] by the given value. - /// - /// # Safety - /// - /// The caller is responsible for calling this method only with valid - /// offset values so that the [`InstructionPtr`] never points out of valid - /// bounds of the instructions of the same compiled Wasm function. - #[inline(always)] - pub fn offset(&mut self, by: isize) { - // SAFETY: Within Wasm bytecode execution we are guaranteed by - // Wasm validation and `wasmi` codegen to never run out - // of valid bounds using this method. - self.ptr = unsafe { self.ptr.offset(by) }; - self.meta = unsafe { self.meta.offset(by) }; - } - - #[inline(always)] - pub fn add(&mut self, delta: usize) { - // SAFETY: Within Wasm bytecode execution we are guaranteed by - // Wasm validation and `wasmi` codegen to never run out - // of valid bounds using this method. - self.ptr = unsafe { self.ptr.add(delta) }; - self.meta = unsafe { self.meta.add(delta) }; - } - - /// Returns a shared reference to the currently pointed at [`Instruction`]. - /// - /// # Safety - /// - /// The caller is responsible for calling this method only when it is - /// guaranteed that the [`InstructionPtr`] is validly pointing inside - /// the boundaries of its associated compiled Wasm function. - #[inline(always)] - pub fn get(&self) -> &Instruction { - // SAFETY: Within Wasm bytecode execution we are guaranteed by - // Wasm validation and `wasmi` codegen to never run out - // of valid bounds using this method. - unsafe { &*self.ptr } - } - - #[inline(always)] - pub fn meta(&self) -> &InstrMeta { - // SAFETY: Within Wasm bytecode execution we are guaranteed by - // Wasm validation and `wasmi` codegen to never run out - // of valid bounds using this method. - unsafe { &*self.meta } - } -} diff --git a/legacy/src/engine/config.rs b/legacy/src/engine/config.rs deleted file mode 100644 index a1b3e9e22..000000000 --- a/legacy/src/engine/config.rs +++ /dev/null @@ -1,553 +0,0 @@ -use super::{stack::StackLimits, DropKeep}; -use crate::{ - core::{ImportLinker, UntypedValue}, - engine::bytecode::Instruction, -}; -use alloc::{ - boxed::Box, - string::{String, ToString}, -}; -use core::{mem::size_of, num::NonZeroU64}; -use wasmparser::WasmFeatures; - -/// The default number of stacks kept in the cache at most. -const DEFAULT_CACHED_STACKS: usize = 2; - -#[derive(Debug, Clone)] -pub struct StateRouterConfig { - /// List of states to be router based on the state - pub states: Box<[(String, u32)]>, - /// Instruction that describes how we determine an input state - pub opcode: Instruction, -} - -#[derive(Debug, Clone)] -pub struct RwasmConfig { - /// State router is used to choose one of the function based on the index provided. - /// P.S: this flag doesn't work if you have WASM's start entry point - pub state_router: Option, - /// Entrypoint that stores bytecode for module init - /// P.S: this flag doesn't work if you have WASM's start entry point - pub entrypoint_name: Option, - /// Import linker that stores mapping from function to special identifiers that is used - /// to remember unique external calls ids. We need this to simplify a proving process to - /// forward external calls to corresponding circuits. - pub import_linker: Option, - /// Do we need to wrap input functions to convert them from ExternRef to FuncRef (we need it to - /// simplify tables sometimes)? Its needed only for rWASM mode where we replace all external - /// calls with import linker mapping. - pub wrap_import_functions: bool, - /// An option for translating a drop keeps into SetLocal/GetLocal opcodes, - /// right now under a flag because the function is unstable - pub translate_drop_keep: bool, - /// An option to disable malformed entrypoint func type check. We need this check for e2e tests - /// where we manage stack manually. - pub allow_malformed_entrypoint_func_type: bool, - /// Should fuel-charging instructions be injected before each builtin call - pub builtins_consume_fuel: bool, -} - -impl Default for RwasmConfig { - fn default() -> Self { - Self { - state_router: None, - entrypoint_name: Some("main".to_string()), - import_linker: None, - wrap_import_functions: false, - translate_drop_keep: false, - allow_malformed_entrypoint_func_type: false, - builtins_consume_fuel: true, - } - } -} - -impl RwasmConfig { - pub fn with_state_router(mut self, state_router_config: StateRouterConfig) -> Self { - self.state_router = Some(state_router_config); - self - } - - pub fn with_entrypoint_name(mut self, entrypoint_name: String) -> Self { - self.entrypoint_name = Some(entrypoint_name); - self - } - - pub fn with_import_linker(mut self, linker: ImportLinker) -> Self { - self.import_linker = Some(linker); - self - } - - pub fn with_wrap_import_functions(mut self, wrap_import_functions: bool) -> Self { - self.wrap_import_functions = wrap_import_functions; - self - } - - pub fn with_translate_drop_keep(mut self, translate_drop_keep: bool) -> Self { - self.translate_drop_keep = translate_drop_keep; - self - } - - pub fn with_allow_malformed_entrypoint_func_type(mut self) -> Self { - self.allow_malformed_entrypoint_func_type = true; - self - } - - pub fn with_builtins_consume_fuel(mut self, builtins_consume_fuel: bool) -> Self { - self.builtins_consume_fuel = builtins_consume_fuel; - self - } -} - -/// Configuration for an [`Engine`]. -/// -/// [`Engine`]: [`crate::Engine`] -#[derive(Debug, Clone)] -pub struct Config { - /// The limits set on the value stack and call stack. - stack_limits: StackLimits, - /// The amount of Wasm stacks to keep in cache at most. - cached_stacks: usize, - /// Is `true` if the `mutable-global` Wasm proposal is enabled. - mutable_global: bool, - /// Is `true` if the `sign-extension` Wasm proposal is enabled. - sign_extension: bool, - /// Is `true` if the `saturating-float-to-int` Wasm proposal is enabled. - saturating_float_to_int: bool, - /// Is `true` if the [`multi-value`] Wasm proposal is enabled. - multi_value: bool, - /// Is `true` if the [`bulk-memory`] Wasm proposal is enabled. - bulk_memory: bool, - /// Is `true` if the [`reference-types`] Wasm proposal is enabled. - reference_types: bool, - /// Is `true` if the [`tail-call`] Wasm proposal is enabled. - tail_call: bool, - /// Is `true` if the [`extended-const`] Wasm proposal is enabled. - extended_const: bool, - /// Is `true` if Wasm instructions on `f32` and `f64` types are allowed. - floats: bool, - /// Is `true` if `wasmi` executions shall consume fuel. - consume_fuel: bool, - /// The fuel consumption mode of the `wasmi` [`Engine`](crate::Engine). - fuel_consumption_mode: FuelConsumptionMode, - /// The configured fuel costs of all `wasmi` bytecode instructions. - fuel_costs: FuelCosts, - /// Translate into rWASM compatible binary - rwasm_config: Option, -} - -/// The fuel consumption mode of the `wasmi` [`Engine`]. -/// -/// This mode affects when fuel is charged for Wasm bulk-operations. -/// Affected Wasm instructions are: -/// -/// - `memory.{grow, copy, fill}` -/// - `data.init` -/// - `table.{grow, copy, fill}` -/// - `element.init` -/// -/// The default fuel consumption mode is [`FuelConsumptionMode::Lazy`]. -/// -/// [`Engine`]: crate::Engine -#[derive(Debug, Default, Copy, Clone)] -pub enum FuelConsumptionMode { - /// Fuel consumption for bulk-operations is lazy. - /// - /// Lazy fuel consumption means that fuel for bulk-operations - /// is checked before executing the instruction but only consumed - /// if the executed instruction suceeded. The reason for this is - /// that bulk-operations fail fast and therefore do not cost - /// a lot of compute power in case of failure. - /// - /// # Note - /// - /// Lazy fuel consumption makes sense as default mode since the - /// affected bulk-operations usually are very costly if they are - /// successful. Therefore users generally want to avoid having to - /// using more fuel than what was actually used, especially if there - /// is an underlying cost model associated to the used fuel. - #[default] - Lazy, - /// Fuel consumption for bulk-operations is eager. - /// - /// Eager fuel consumption means that fuel for bulk-operations - /// is always consumed before executing the instruction independent - /// of it suceeding or failing. - /// - /// # Note - /// - /// A use case for when a user might prefer eager fuel consumption - /// is when the fuel **required** to perform an execution should be identical - /// to the actual fuel **consumed** by an execution. Otherwise it can be confusing - /// that the execution consumed `x` gas while it needs `x + gas_for_bulk_op` to - /// not run out of fuel. - Eager, -} - -/// Type storing all kinds of fuel costs of instructions. -#[derive(Debug, Copy, Clone)] -pub struct FuelCosts { - /// The base fuel costs for all instructions. - pub base: u64, - /// The fuel cost for instruction operating on Wasm entities. - /// - /// # Note - /// - /// A Wasm entitiy is one of `func`, `global`, `memory` or `table`. - /// Those instructions are usually a bit more costly since they need - /// multiplie indirect accesses through the Wasm instance and store. - pub entity: u64, - /// The fuel cost offset for `memory.load` instructions. - pub load: u64, - /// The fuel cost offset for `memory.store` instructions. - pub store: u64, - /// The fuel cost offset for `call` and `call_indirect` instructions. - pub call: u64, - /// Determines how many moved stack values consume one fuel upon a branch or return - /// instruction. - /// - /// # Note - /// - /// If this is zero then processing [`DropKeep`] costs nothing. - pub branch_kept_per_fuel: u64, - /// Determines how many function locals consume one fuel per function call. - /// - /// # Note - /// - /// - This is also applied to all function parameters since they are translated to local - /// variable slots. - /// - If this is zero then processing function locals costs nothing. - pub func_locals_per_fuel: u64, - /// How many memory bytes can be processed per fuel in a `bulk-memory` instruction. - /// - /// # Note - /// - /// If this is zero then processing memory bytes costs nothing. - pub memory_bytes_per_fuel: u64, - /// How many table elements can be processed per fuel in a `bulk-table` instruction. - /// - /// # Note - /// - /// If this is zero then processing table elements costs nothing. - pub table_elements_per_fuel: u64, -} - -impl FuelCosts { - /// Returns the fuel consumption of the amount of items with costs per items. - fn costs_per(len_items: u64, items_per_fuel: u64) -> u64 { - NonZeroU64::new(items_per_fuel) - .map(|items_per_fuel| len_items / items_per_fuel) - .unwrap_or(0) - } - - /// Returns the fuel consumption for branches and returns using the given [`DropKeep`]. - pub fn fuel_for_drop_keep(&self, drop_keep: DropKeep) -> u64 { - if drop_keep.drop() == 0 { - return 0; - } - Self::costs_per(u64::from(drop_keep.keep()), self.branch_kept_per_fuel) - } - - /// Returns the fuel consumption for calling a function with the amount of local variables. - /// - /// # Note - /// - /// Function parameters are also treated as local variables. - pub fn fuel_for_locals(&self, locals: u64) -> u64 { - Self::costs_per(locals, self.func_locals_per_fuel) - } - - /// Returns the fuel consumption for processing the amount of memory bytes. - pub fn fuel_for_bytes(&self, bytes: u64) -> u64 { - Self::costs_per(bytes, self.memory_bytes_per_fuel) - } - - /// Returns the fuel consumption for processing the amount of table elements. - pub fn fuel_for_elements(&self, elements: u64) -> u64 { - Self::costs_per(elements, self.table_elements_per_fuel) - } -} - -impl Default for FuelCosts { - fn default() -> Self { - let memory_bytes_per_fuel = 64; - let bytes_per_register = size_of::() as u64; - let registers_per_fuel = memory_bytes_per_fuel / bytes_per_register; - Self { - base: 1, - entity: 1, - load: 1, - store: 1, - call: 1, - func_locals_per_fuel: registers_per_fuel, - branch_kept_per_fuel: registers_per_fuel, - memory_bytes_per_fuel, - table_elements_per_fuel: registers_per_fuel, - } - } -} - -impl Default for Config { - fn default() -> Self { - Self { - stack_limits: StackLimits::default(), - cached_stacks: DEFAULT_CACHED_STACKS, - mutable_global: true, - sign_extension: true, - saturating_float_to_int: true, - multi_value: true, - bulk_memory: true, - reference_types: true, - tail_call: false, - extended_const: false, - floats: true, - consume_fuel: false, - fuel_costs: FuelCosts::default(), - fuel_consumption_mode: FuelConsumptionMode::default(), - rwasm_config: None, - } - } -} - -impl Config { - /// Sets the [`StackLimits`] for the [`Config`]. - pub fn set_stack_limits(&mut self, stack_limits: StackLimits) -> &mut Self { - self.stack_limits = stack_limits; - self - } - - /// Returns the [`StackLimits`] of the [`Config`]. - pub(super) fn stack_limits(&self) -> StackLimits { - self.stack_limits - } - - /// Sets the maximum amount of cached stacks for reuse for the [`Config`]. - /// - /// # Note - /// - /// Defaults to 2. - pub fn set_cached_stacks(&mut self, amount: usize) -> &mut Self { - self.cached_stacks = amount; - self - } - - /// Returns the maximum amount of cached stacks for reuse of the [`Config`]. - pub(super) fn cached_stacks(&self) -> usize { - self.cached_stacks - } - - /// Enable or disable the [`mutable-global`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Enabled by default. - /// - /// [`mutable-global`]: https://github.com/WebAssembly/mutable-global - pub fn wasm_mutable_global(&mut self, enable: bool) -> &mut Self { - self.mutable_global = enable; - self - } - - /// Enable or disable the [`sign-extension`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Enabled by default. - /// - /// [`sign-extension`]: https://github.com/WebAssembly/sign-extension-ops - pub fn wasm_sign_extension(&mut self, enable: bool) -> &mut Self { - self.sign_extension = enable; - self - } - - /// Enable or disable the [`saturating-float-to-int`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Enabled by default. - /// - /// [`saturating-float-to-int`]: - /// https://github.com/WebAssembly/nontrapping-float-to-int-conversions - pub fn wasm_saturating_float_to_int(&mut self, enable: bool) -> &mut Self { - self.saturating_float_to_int = enable; - self - } - - /// Enable or disable the [`multi-value`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Enabled by default. - /// - /// [`multi-value`]: https://github.com/WebAssembly/multi-value - pub fn wasm_multi_value(&mut self, enable: bool) -> &mut Self { - self.multi_value = enable; - self - } - - /// Enable or disable the [`bulk-memory`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Enabled by default. - /// - /// [`bulk-memory`]: https://github.com/WebAssembly/bulk-memory-operations - pub fn wasm_bulk_memory(&mut self, enable: bool) -> &mut Self { - self.bulk_memory = enable; - self - } - - /// Enable or disable the [`reference-types`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Enabled by default. - /// - /// [`reference-types`]: https://github.com/WebAssembly/reference-types - pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self { - self.reference_types = enable; - self - } - - /// Enable or disable the [`tail-call`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Disabled by default. - /// - /// [`tail-call`]: https://github.com/WebAssembly/tail-calls - pub fn wasm_tail_call(&mut self, enable: bool) -> &mut Self { - self.tail_call = enable; - self - } - - /// Enable or disable the [`extended-const`] Wasm proposal for the [`Config`]. - /// - /// # Note - /// - /// Disabled by default. - /// - /// [`tail-call`]: https://github.com/WebAssembly/extended-const - pub fn wasm_extended_const(&mut self, enable: bool) -> &mut Self { - self.extended_const = enable; - self - } - - /// Enable or disable Wasm floating point (`f32` and `f64`) instructions and types. - /// - /// Enabled by default. - pub fn floats(&mut self, enable: bool) -> &mut Self { - self.floats = enable; - self - } - - /// Configures whether `wasmi` will consume fuel during execution to either halt execution as - /// desired. - /// - /// # Note - /// - /// This configuration can be used to make `wasmi` instrument its internal bytecode - /// so that it consumes fuel as it executes. Once an execution runs out of fuel - /// a [`TrapCode::OutOfFuel`](crate::core::TrapCode::OutOfFuel) trap is raised. - /// This way users can deterministically halt or yield the execution of WebAssembly code. - /// - /// - Use [`Store::add_fuel`](crate::Store::add_fuel) to pour some fuel into the [`Store`] - /// before executing some code as the [`Store`] start with no fuel. - /// - Use [`Caller::consume_fuel`](crate::Caller::consume_fuel) to charge costs for executed - /// host functions. - /// - /// Disabled by default. - /// - /// [`Store`]: crate::Store - /// [`Engine`]: crate::Engine - pub fn consume_fuel(&mut self, enable: bool) -> &mut Self { - self.consume_fuel = enable; - self - } - - pub fn builtins_consume_fuel(&mut self, builtins_consume_fuel: bool) -> &mut Self { - if self.rwasm_config.is_some() { - self.rwasm_config.as_mut().unwrap().builtins_consume_fuel = builtins_consume_fuel; - } - self - } - - /// Returns `true` if the [`Config`] enables fuel consumption by the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - pub fn get_consume_fuel(&self) -> bool { - self.consume_fuel - } - - pub fn get_builtins_consume_fuel(&self) -> bool { - self.rwasm_config - .as_ref() - .map(|rwasm_config| rwasm_config.builtins_consume_fuel) - .unwrap_or(false) - } - - /// Returns the configured [`FuelCosts`]. - pub(crate) fn fuel_costs(&self) -> &FuelCosts { - &self.fuel_costs - } - - /// Configures the [`FuelConsumptionMode`] for the [`Engine`]. - /// - /// # Note - /// - /// This has no effect if fuel metering is disabled for the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - pub fn fuel_consumption_mode(&mut self, mode: FuelConsumptionMode) -> &mut Self { - self.fuel_consumption_mode = mode; - self - } - - /// Returns the [`FuelConsumptionMode`] for the [`Engine`]. - /// - /// Returns `None` if fuel metering is disabled for the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - pub fn get_fuel_consumption_mode(&self) -> Option { - self.get_consume_fuel() - .then_some(self.fuel_consumption_mode) - } - - pub fn rwasm_config(&mut self, rwasm_config: RwasmConfig) -> &mut Self { - self.rwasm_config = Some(rwasm_config); - self - } - - pub fn get_rwasm_config(&self) -> Option<&RwasmConfig> { - self.rwasm_config.as_ref() - } - - pub fn get_rwasm_wrap_import_funcs(&self) -> bool { - self.rwasm_config - .as_ref() - .map(|rwasm_config| rwasm_config.wrap_import_functions) - .unwrap_or_default() - } - - /// Returns the [`WasmFeatures`] represented by the [`Config`]. - pub(crate) fn wasm_features(&self) -> WasmFeatures { - WasmFeatures { - multi_value: self.multi_value, - mutable_global: self.mutable_global, - saturating_float_to_int: self.saturating_float_to_int, - sign_extension: self.sign_extension, - bulk_memory: self.bulk_memory, - reference_types: self.reference_types, - tail_call: self.tail_call, - extended_const: self.extended_const, - floats: self.floats, - component_model: false, - simd: false, - relaxed_simd: false, - threads: false, - multi_memory: false, - exceptions: false, - memory64: false, - memory_control: false, - } - } -} diff --git a/legacy/src/engine/const_pool.rs b/legacy/src/engine/const_pool.rs deleted file mode 100644 index fb895dfc1..000000000 --- a/legacy/src/engine/const_pool.rs +++ /dev/null @@ -1,115 +0,0 @@ -use super::{func_builder::TranslationErrorInner, TranslationError}; -use crate::core::UntypedValue; -use alloc::{ - collections::{btree_map, BTreeMap}, - vec::Vec, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] -pub struct ConstRef(u32); - -impl TryFrom for ConstRef { - type Error = TranslationError; - - fn try_from(index: usize) -> Result { - match u32::try_from(index) { - Ok(index) => Ok(Self(index)), - Err(_) => Err(TranslationError::new( - TranslationErrorInner::ConstRefOutOfBounds, - )), - } - } -} - -impl From for ConstRef { - fn from(value: u32) -> Self { - ConstRef(value) - } -} - -impl ConstRef { - /// Returns the index of the [`ConstRef`] as `usize` value. - pub fn to_usize(self) -> usize { - self.0 as usize - } -} - -/// A pool of deduplicated reusable constant values. -/// -/// - Those constant values are identified by their associated [`ConstRef`]. This type exists so -/// that the `wasmi` bytecode can extract large constant values to this pool instead of storing -/// their values inline. -/// - All constant values are also deduplicated so that no duplicates are stored in a single -/// [`ConstPool`]. This also means that deciding if two [`ConstRef`] values refer to the equal -/// constant values can be efficiently done by comparing the [`ConstRef`] indices without -/// resolving to their underlying constant values. -#[derive(Debug, Default)] -pub struct ConstPool { - /// Mapping from constant [`UntypedValue`] values to [`ConstRef`] indices. - const2idx: BTreeMap, - /// Mapping from [`ConstRef`] indices to constant [`UntypedValue`] values. - idx2const: Vec, -} - -impl ConstPool { - /// Allocates a new constant `value` on the [`ConstPool`] and returns its identifier. - /// - /// # Note - /// - /// If the constant `value` already exists in this [`ConstPool`] no new value is - /// allocated and the identifier of the existing constant `value` returned instead. - /// - /// # Errors - /// - /// If too many constant values have been allocated for this [`ConstPool`]. - pub fn alloc(&mut self, value: UntypedValue) -> Result { - match self.const2idx.entry(value) { - btree_map::Entry::Occupied(entry) => Ok(*entry.get()), - btree_map::Entry::Vacant(entry) => { - let idx = self.idx2const.len(); - let cref = ConstRef::try_from(idx)?; - entry.insert(cref); - self.idx2const.push(value); - Ok(cref) - } - } - } - - /// Returns the [`UntypedValue`] for the given [`ConstRef`] if existing. - /// - /// Returns `None` is the [`ConstPool`] does not store a value for the [`ConstRef`]. - /// - /// # Note - /// - /// This API is mainly used and useful in testing code. - #[allow(dead_code)] - pub fn get(&self, cref: ConstRef) -> Option { - self.idx2const.get(cref.to_usize()).copied() - } - - /// Returns the read-only [`ConstPoolView`] of this [`ConstPool`]. - pub fn view(&self) -> ConstPoolView { - ConstPoolView { - idx2const: &self.idx2const, - } - } -} - -/// A read-only view of a [`ConstPool`]. -/// -/// This allows for a more efficient access to the underlying constant -/// [`UntypedValue`] values given their associated [`ConstRef`] indices. -#[derive(Debug)] -pub struct ConstPoolView<'a> { - /// Mapping from [`ConstRef`] indices to constant [`UntypedValue`] values. - idx2const: &'a [UntypedValue], -} - -impl ConstPoolView<'_> { - /// Returns the [`UntypedValue`] for the given [`ConstRef`] if existing. - /// - /// Returns `None` is the [`ConstPool`] does not store a value for the [`ConstRef`]. - pub fn get(&self, cref: ConstRef) -> Option { - self.idx2const.get(cref.to_usize()).copied() - } -} diff --git a/legacy/src/engine/executor.rs b/legacy/src/engine/executor.rs deleted file mode 100644 index 4e7e9c6a9..000000000 --- a/legacy/src/engine/executor.rs +++ /dev/null @@ -1,1852 +0,0 @@ -use super::{bytecode::BranchOffset, const_pool::ConstRef, CompiledFunc, ConstPoolView}; -use crate::{ - arena::ArenaIndex, - core::{Pages, TrapCode, UntypedValue}, - engine::{ - bytecode::{ - AddressOffset, - BlockFuel, - BranchTableTargets, - DataSegmentIdx, - ElementSegmentIdx, - FuncIdx, - GlobalIdx, - Instruction, - LocalDepth, - SignatureIdx, - TableIdx, - }, - cache::InstanceCache, - code_map::{CodeMap, InstructionPtr}, - config::FuelCosts, - stack::{CallStack, ValueStackPtr}, - tracer::Tracer, - DropKeep, - FuncFrame, - ValueStack, - }, - func::FuncEntity, - module::DEFAULT_MEMORY_INDEX, - store::ResourceLimiterRef, - table::{ElementSegmentEntity, TableEntity}, - FuelConsumptionMode, - Func, - FuncRef, - Instance, - StoreInner, - Table, -}; -use alloc::string::String; -use core::cmp::{self}; - -/// The outcome of a Wasm execution. -/// -/// # Note -/// -/// A Wasm execution includes everything but host calls. -/// In other words: Everything in between host calls is a Wasm execution. -#[derive(Debug, Copy, Clone)] -pub enum WasmOutcome { - /// The Wasm execution has ended and returns to the host side. - Return, - /// The Wasm execution calls a host function. - Call { host_func: Func, instance: Instance }, -} - -/// The outcome of a Wasm execution. -/// -/// # Note -/// -/// A Wasm execution includes everything but host calls. -/// In other words: Everything in between host calls is a Wasm execution. -#[derive(Debug, Copy, Clone)] -pub enum CallOutcome { - /// The Wasm execution continues in Wasm. - Continue, - /// The Wasm execution calls a host function. - Call { host_func: Func, instance: Instance }, -} - -/// The kind of a function call. -#[derive(Debug, Copy, Clone)] -pub enum CallKind { - /// A nested function call. - Nested, - /// A tailing function call. - Tail, -} - -/// The outcome of a Wasm return statement. -#[derive(Debug, Copy, Clone)] -pub enum ReturnOutcome { - /// The call returns to a nested Wasm caller. - Wasm, - /// The call returns back to the host. - Host, -} - -/// Executes the given function `frame`. -/// -/// # Note -/// -/// This executes Wasm instructions until either the execution calls -/// into a host function or the Wasm execution has come to an end. -/// -/// # Errors -/// -/// If the Wasm execution traps. -#[inline(never)] -pub fn execute_wasm<'ctx, 'engine>( - ctx: &'ctx mut StoreInner, - cache: &'engine mut InstanceCache, - value_stack: &'engine mut ValueStack, - call_stack: &'engine mut CallStack, - code_map: &'engine CodeMap, - const_pool: ConstPoolView<'engine>, - resource_limiter: &'ctx mut ResourceLimiterRef<'ctx>, - tracer: Option<&'engine mut Tracer>, -) -> Result { - Executor::new( - ctx, - cache, - value_stack, - call_stack, - code_map, - const_pool, - tracer, - ) - .execute(resource_limiter) -} - -/// The function signature of Wasm load operations. -type WasmLoadOp = - fn(memory: &[u8], address: UntypedValue, offset: u32) -> Result; - -/// The function signature of Wasm store operations. -type WasmStoreOp = fn( - memory: &mut [u8], - address: UntypedValue, - offset: u32, - value: UntypedValue, -) -> Result<(), TrapCode>; - -/// An error that can occur upon `memory.grow` or `table.grow`. -#[derive(Copy, Clone)] -pub enum EntityGrowError { - /// Usually a [`TrapCode::OutOfFuel`] trap. - TrapCode(TrapCode), - /// Encountered when `memory.grow` or `table.grow` fails. - InvalidGrow, -} - -impl From for EntityGrowError { - fn from(trap_code: TrapCode) -> Self { - Self::TrapCode(trap_code) - } -} - -/// The WebAssembly specification demands to return this value -/// if the `memory.grow` or `table.grow` operations fail. -const INVALID_GROWTH_ERRCODE: u32 = u32::MAX; - -/// An execution context for executing a `wasmi` function frame. -#[derive(Debug)] -struct Executor<'ctx, 'engine> { - /// Stores the value stack of live values on the Wasm stack. - sp: ValueStackPtr, - /// The pointer to the currently executed instruction. - ip: InstructionPtr, - /// Stores frequently used instance related data. - cache: &'engine mut InstanceCache, - /// A mutable [`StoreInner`] context. - /// - /// [`StoreInner`]: [`crate::StoreInner`] - ctx: &'ctx mut StoreInner, - /// The value stack. - /// - /// # Note - /// - /// This reference is mainly used to synchronize back state - /// after manipulations to the value stack via `sp`. - value_stack: &'engine mut ValueStack, - /// The call stack. - /// - /// # Note - /// - /// This is used to store the stack of nested function calls. - call_stack: &'engine mut CallStack, - /// The Wasm function code map. - /// - /// # Note - /// - /// This is used to lookup Wasm function information. - code_map: &'engine CodeMap, - /// A read-only view to a pool of constant values. - const_pool: ConstPoolView<'engine>, - /// A tracer that stores execution info - tracer: Option<&'engine mut Tracer>, - /// Store an information about last signature used by IndirectCall - last_signature: Option, -} - -macro_rules! forward_call { - ($expr:expr) => {{ - if let CallOutcome::Call { - host_func, - instance, - } = $expr? - { - return Ok(WasmOutcome::Call { - host_func, - instance, - }); - } - }}; -} - -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - /// Creates a new [`Executor`] for executing a `wasmi` function frame. - #[inline(always)] - pub fn new( - ctx: &'ctx mut StoreInner, - cache: &'engine mut InstanceCache, - value_stack: &'engine mut ValueStack, - call_stack: &'engine mut CallStack, - code_map: &'engine CodeMap, - const_pool: ConstPoolView<'engine>, - tracer: Option<&'engine mut Tracer>, - ) -> Self { - let frame = call_stack.pop().expect("must have frame on the call stack"); - let sp = value_stack.stack_ptr(); - let ip = frame.ip(); - Self { - sp, - ip, - cache, - ctx, - value_stack, - call_stack, - code_map, - const_pool, - tracer, - last_signature: None, - } - } - - /// Executes the function frame until it returns or traps. - #[inline(always)] - fn execute( - mut self, - resource_limiter: &'ctx mut ResourceLimiterRef<'ctx>, - ) -> Result { - use Instruction as Instr; - loop { - let instr = *self.ip.get(); - let meta = *self.ip.meta(); - - // TODO: Need to add recursive check while call function - // TODO: Create more optimized check for stack overflowed - if self.value_stack.has_stack_overflowed(self.sp) { - return Err(TrapCode::StackOverflow.into()); - } - - #[cfg(feature = "print-trace")] - { - let stack = self.value_stack.dump_stack(self.sp); - println!( - "{}:\t {:?} \tstack({}):{:?}", - self.ip.pc(), - instr, - stack.len(), - stack - .iter() - .rev() - .take(10) - .map(|v| v.as_usize()) - .collect::>() - ); - } - - // handle pre-instruction state - if let Some(tracer) = self.tracer.as_mut() { - let has_default_memory = { - let instance = self.cache.instance(); - self.ctx - .resolve_instance(instance) - .get_memory(DEFAULT_MEMORY_INDEX) - .is_some() - }; - let memory_size: u32 = if has_default_memory { - self.ctx - .resolve_memory(self.cache.default_memory(self.ctx)) - .current_pages() - .into() - } else { - 0 - }; - let consumed_fuel = self.ctx.fuel().fuel_consumed(); - let stack = self.value_stack.dump_stack(self.sp); - tracer.pre_opcode_state( - self.ip.pc(), - instr, - stack, - &meta, - memory_size, - consumed_fuel, - ); - } - - match instr { - Instr::LocalGet(local_depth) => self.visit_local_get(local_depth), - Instr::LocalSet(local_depth) => self.visit_local_set(local_depth), - Instr::LocalTee(local_depth) => self.visit_local_tee(local_depth), - Instr::Br(offset) => self.visit_br(offset), - Instr::BrIfEqz(offset) => self.visit_br_if_eqz(offset), - Instr::BrIfNez(offset) => self.visit_br_if_nez(offset), - Instr::BrAdjust(offset) => self.visit_br_adjust(offset), - Instr::BrAdjustIfNez(offset) => self.visit_br_adjust_if_nez(offset), - Instr::BrTable(targets) => self.visit_br_table(targets), - Instr::Unreachable => self.visit_unreachable()?, - Instr::ConsumeFuel(block_fuel) => self.visit_consume_fuel(block_fuel)?, - Instr::Return(drop_keep) => { - if let ReturnOutcome::Host = self.visit_ret(drop_keep) { - return Ok(WasmOutcome::Return); - } - } - Instr::ReturnIfNez(drop_keep) => { - if let ReturnOutcome::Host = self.visit_return_if_nez(drop_keep) { - return Ok(WasmOutcome::Return); - } - } - Instr::ReturnCallInternal(compiled_func) => { - self.visit_return_call_internal(compiled_func)? - } - Instr::ReturnCall(func) => { - forward_call!(self.visit_return_call(func)) - } - Instr::ReturnCallIndirect(func_type) => { - forward_call!(self.visit_return_call_indirect(func_type)) - } - Instr::CallInternal(compiled_func) => self.visit_call_internal(compiled_func)?, - Instr::Call(func) => forward_call!(self.visit_call(func)), - Instr::CallIndirect(func_type) => { - forward_call!(self.visit_call_indirect(func_type)) - } - Instr::SignatureCheck(func_type) => self.visit_signature_check(func_type)?, - Instr::Drop => self.visit_drop(), - Instr::Select => self.visit_select(), - Instr::GlobalGet(global_idx) => self.visit_global_get(global_idx), - Instr::GlobalSet(global_idx) => self.visit_global_set(global_idx), - Instr::I32Load(offset) => self.visit_i32_load(offset)?, - Instr::I64Load(offset) => self.visit_i64_load(offset)?, - Instr::F32Load(offset) => self.visit_f32_load(offset)?, - Instr::F64Load(offset) => self.visit_f64_load(offset)?, - Instr::I32Load8S(offset) => self.visit_i32_load_i8_s(offset)?, - Instr::I32Load8U(offset) => self.visit_i32_load_i8_u(offset)?, - Instr::I32Load16S(offset) => self.visit_i32_load_i16_s(offset)?, - Instr::I32Load16U(offset) => self.visit_i32_load_i16_u(offset)?, - Instr::I64Load8S(offset) => self.visit_i64_load_i8_s(offset)?, - Instr::I64Load8U(offset) => self.visit_i64_load_i8_u(offset)?, - Instr::I64Load16S(offset) => self.visit_i64_load_i16_s(offset)?, - Instr::I64Load16U(offset) => self.visit_i64_load_i16_u(offset)?, - Instr::I64Load32S(offset) => self.visit_i64_load_i32_s(offset)?, - Instr::I64Load32U(offset) => self.visit_i64_load_i32_u(offset)?, - Instr::I32Store(offset) => self.visit_i32_store(offset)?, - Instr::I64Store(offset) => self.visit_i64_store(offset)?, - Instr::F32Store(offset) => self.visit_f32_store(offset)?, - Instr::F64Store(offset) => self.visit_f64_store(offset)?, - Instr::I32Store8(offset) => self.visit_i32_store_8(offset)?, - Instr::I32Store16(offset) => self.visit_i32_store_16(offset)?, - Instr::I64Store8(offset) => self.visit_i64_store_8(offset)?, - Instr::I64Store16(offset) => self.visit_i64_store_16(offset)?, - Instr::I64Store32(offset) => self.visit_i64_store_32(offset)?, - Instr::MemorySize => self.visit_memory_size(), - Instr::MemoryGrow => self.visit_memory_grow(&mut *resource_limiter)?, - Instr::MemoryFill => self.visit_memory_fill()?, - Instr::MemoryCopy => self.visit_memory_copy()?, - Instr::MemoryInit(segment) => self.visit_memory_init(segment)?, - Instr::DataDrop(segment) => self.visit_data_drop(segment), - Instr::TableSize(table) => self.visit_table_size(table), - Instr::TableGrow(table) => self.visit_table_grow(table, &mut *resource_limiter)?, - Instr::TableFill(table) => self.visit_table_fill(table)?, - Instr::TableGet(table) => self.visit_table_get(table)?, - Instr::TableSet(table) => self.visit_table_set(table)?, - Instr::TableCopy(dst) => self.visit_table_copy(dst)?, - Instr::TableInit(elem) => self.visit_table_init(elem)?, - Instr::ElemDrop(segment) => self.visit_element_drop(segment), - Instr::RefFunc(func_index) => self.visit_ref_func(func_index)?, - Instr::I32Const(value) => self.visit_i32_const(value), - Instr::I64Const(value) => self.visit_i64_const(value), - Instr::F32Const(value) => self.visit_f32_const(value), - Instr::F64Const(value) => self.visit_f64_const(value), - Instr::ConstRef(cref) => self.visit_const(cref), - Instr::I32Eqz => self.visit_i32_eqz(), - Instr::I32Eq => self.visit_i32_eq(), - Instr::I32Ne => self.visit_i32_ne(), - Instr::I32LtS => self.visit_i32_lt_s(), - Instr::I32LtU => self.visit_i32_lt_u(), - Instr::I32GtS => self.visit_i32_gt_s(), - Instr::I32GtU => self.visit_i32_gt_u(), - Instr::I32LeS => self.visit_i32_le_s(), - Instr::I32LeU => self.visit_i32_le_u(), - Instr::I32GeS => self.visit_i32_ge_s(), - Instr::I32GeU => self.visit_i32_ge_u(), - Instr::I64Eqz => self.visit_i64_eqz(), - Instr::I64Eq => self.visit_i64_eq(), - Instr::I64Ne => self.visit_i64_ne(), - Instr::I64LtS => self.visit_i64_lt_s(), - Instr::I64LtU => self.visit_i64_lt_u(), - Instr::I64GtS => self.visit_i64_gt_s(), - Instr::I64GtU => self.visit_i64_gt_u(), - Instr::I64LeS => self.visit_i64_le_s(), - Instr::I64LeU => self.visit_i64_le_u(), - Instr::I64GeS => self.visit_i64_ge_s(), - Instr::I64GeU => self.visit_i64_ge_u(), - Instr::F32Eq => self.visit_f32_eq(), - Instr::F32Ne => self.visit_f32_ne(), - Instr::F32Lt => self.visit_f32_lt(), - Instr::F32Gt => self.visit_f32_gt(), - Instr::F32Le => self.visit_f32_le(), - Instr::F32Ge => self.visit_f32_ge(), - Instr::F64Eq => self.visit_f64_eq(), - Instr::F64Ne => self.visit_f64_ne(), - Instr::F64Lt => self.visit_f64_lt(), - Instr::F64Gt => self.visit_f64_gt(), - Instr::F64Le => self.visit_f64_le(), - Instr::F64Ge => self.visit_f64_ge(), - Instr::I32Clz => self.visit_i32_clz(), - Instr::I32Ctz => self.visit_i32_ctz(), - Instr::I32Popcnt => self.visit_i32_popcnt(), - Instr::I32Add => self.visit_i32_add(), - Instr::I32Sub => self.visit_i32_sub(), - Instr::I32Mul => self.visit_i32_mul(), - Instr::I32DivS => self.visit_i32_div_s()?, - Instr::I32DivU => self.visit_i32_div_u()?, - Instr::I32RemS => self.visit_i32_rem_s()?, - Instr::I32RemU => self.visit_i32_rem_u()?, - Instr::I32And => self.visit_i32_and(), - Instr::I32Or => self.visit_i32_or(), - Instr::I32Xor => self.visit_i32_xor(), - Instr::I32Shl => self.visit_i32_shl(), - Instr::I32ShrS => self.visit_i32_shr_s(), - Instr::I32ShrU => self.visit_i32_shr_u(), - Instr::I32Rotl => self.visit_i32_rotl(), - Instr::I32Rotr => self.visit_i32_rotr(), - Instr::I64Clz => self.visit_i64_clz(), - Instr::I64Ctz => self.visit_i64_ctz(), - Instr::I64Popcnt => self.visit_i64_popcnt(), - Instr::I64Add => self.visit_i64_add(), - Instr::I64Sub => self.visit_i64_sub(), - Instr::I64Mul => self.visit_i64_mul(), - Instr::I64DivS => self.visit_i64_div_s()?, - Instr::I64DivU => self.visit_i64_div_u()?, - Instr::I64RemS => self.visit_i64_rem_s()?, - Instr::I64RemU => self.visit_i64_rem_u()?, - Instr::I64And => self.visit_i64_and(), - Instr::I64Or => self.visit_i64_or(), - Instr::I64Xor => self.visit_i64_xor(), - Instr::I64Shl => self.visit_i64_shl(), - Instr::I64ShrS => self.visit_i64_shr_s(), - Instr::I64ShrU => self.visit_i64_shr_u(), - Instr::I64Rotl => self.visit_i64_rotl(), - Instr::I64Rotr => self.visit_i64_rotr(), - Instr::F32Abs => self.visit_f32_abs(), - Instr::F32Neg => self.visit_f32_neg(), - Instr::F32Ceil => self.visit_f32_ceil(), - Instr::F32Floor => self.visit_f32_floor(), - Instr::F32Trunc => self.visit_f32_trunc(), - Instr::F32Nearest => self.visit_f32_nearest(), - Instr::F32Sqrt => self.visit_f32_sqrt(), - Instr::F32Add => self.visit_f32_add(), - Instr::F32Sub => self.visit_f32_sub(), - Instr::F32Mul => self.visit_f32_mul(), - Instr::F32Div => self.visit_f32_div(), - Instr::F32Min => self.visit_f32_min(), - Instr::F32Max => self.visit_f32_max(), - Instr::F32Copysign => self.visit_f32_copysign(), - Instr::F64Abs => self.visit_f64_abs(), - Instr::F64Neg => self.visit_f64_neg(), - Instr::F64Ceil => self.visit_f64_ceil(), - Instr::F64Floor => self.visit_f64_floor(), - Instr::F64Trunc => self.visit_f64_trunc(), - Instr::F64Nearest => self.visit_f64_nearest(), - Instr::F64Sqrt => self.visit_f64_sqrt(), - Instr::F64Add => self.visit_f64_add(), - Instr::F64Sub => self.visit_f64_sub(), - Instr::F64Mul => self.visit_f64_mul(), - Instr::F64Div => self.visit_f64_div(), - Instr::F64Min => self.visit_f64_min(), - Instr::F64Max => self.visit_f64_max(), - Instr::F64Copysign => self.visit_f64_copysign(), - Instr::I32WrapI64 => self.visit_i32_wrap_i64(), - Instr::I32TruncF32S => self.visit_i32_trunc_f32_s()?, - Instr::I32TruncF32U => self.visit_i32_trunc_f32_u()?, - Instr::I32TruncF64S => self.visit_i32_trunc_f64_s()?, - Instr::I32TruncF64U => self.visit_i32_trunc_f64_u()?, - Instr::I64ExtendI32S => self.visit_i64_extend_i32_s(), - Instr::I64ExtendI32U => self.visit_i64_extend_i32_u(), - Instr::I64TruncF32S => self.visit_i64_trunc_f32_s()?, - Instr::I64TruncF32U => self.visit_i64_trunc_f32_u()?, - Instr::I64TruncF64S => self.visit_i64_trunc_f64_s()?, - Instr::I64TruncF64U => self.visit_i64_trunc_f64_u()?, - Instr::F32ConvertI32S => self.visit_f32_convert_i32_s(), - Instr::F32ConvertI32U => self.visit_f32_convert_i32_u(), - Instr::F32ConvertI64S => self.visit_f32_convert_i64_s(), - Instr::F32ConvertI64U => self.visit_f32_convert_i64_u(), - Instr::F32DemoteF64 => self.visit_f32_demote_f64(), - Instr::F64ConvertI32S => self.visit_f64_convert_i32_s(), - Instr::F64ConvertI32U => self.visit_f64_convert_i32_u(), - Instr::F64ConvertI64S => self.visit_f64_convert_i64_s(), - Instr::F64ConvertI64U => self.visit_f64_convert_i64_u(), - Instr::F64PromoteF32 => self.visit_f64_promote_f32(), - Instr::I32TruncSatF32S => self.visit_i32_trunc_sat_f32_s(), - Instr::I32TruncSatF32U => self.visit_i32_trunc_sat_f32_u(), - Instr::I32TruncSatF64S => self.visit_i32_trunc_sat_f64_s(), - Instr::I32TruncSatF64U => self.visit_i32_trunc_sat_f64_u(), - Instr::I64TruncSatF32S => self.visit_i64_trunc_sat_f32_s(), - Instr::I64TruncSatF32U => self.visit_i64_trunc_sat_f32_u(), - Instr::I64TruncSatF64S => self.visit_i64_trunc_sat_f64_s(), - Instr::I64TruncSatF64U => self.visit_i64_trunc_sat_f64_u(), - Instr::I32Extend8S => self.visit_i32_extend8_s(), - Instr::I32Extend16S => self.visit_i32_extend16_s(), - Instr::I64Extend8S => self.visit_i64_extend8_s(), - Instr::I64Extend16S => self.visit_i64_extend16_s(), - Instr::I64Extend32S => self.visit_i64_extend32_s(), - Instr::StackAlloc { max_stack_height } => { - self.value_stack.reserve(max_stack_height as usize)?; - self.next_instr(); - } - } - } - } - - /// Executes a generic Wasm `store[N_{s|u}]` operation. - /// - /// # Note - /// - /// This can be used to emulate the following Wasm operands: - /// - /// - `{i32, i64, f32, f64}.load` - /// - `{i32, i64}.load8_s` - /// - `{i32, i64}.load8_u` - /// - `{i32, i64}.load16_s` - /// - `{i32, i64}.load16_u` - /// - `i64.load32_s` - /// - `i64.load32_u` - #[inline(always)] - fn execute_load_extend( - &mut self, - offset: AddressOffset, - load_extend: WasmLoadOp, - ) -> Result<(), TrapCode> { - self.sp.try_eval_top(|address| { - let memory = self.cache.default_memory_bytes(self.ctx); - let value = load_extend(memory, address, offset.into_inner())?; - Ok(value) - })?; - self.try_next_instr() - } - - /// Executes a generic Wasm `store[N]` operation. - /// - /// # Note - /// - /// This can be used to emulate the following Wasm operands: - /// - /// - `{i32, i64, f32, f64}.store` - /// - `{i32, i64}.store8` - /// - `{i32, i64}.store16` - /// - `i64.store32` - #[inline(always)] - fn execute_store_wrap( - &mut self, - offset: AddressOffset, - store_wrap: WasmStoreOp, - len: u32, - ) -> Result<(), TrapCode> { - let (address, value) = self.sp.pop2(); - let memory = self.cache.default_memory_bytes(self.ctx); - store_wrap(memory, address, offset.into_inner(), value)?; - self.ip.offset(0); - let address = u32::from(address); - let base_address = offset.into_inner() + address; - if let Some(tracer) = self.tracer.as_mut() { - tracer.memory_change( - base_address, - len, - &memory[base_address as usize..(base_address + len) as usize], - ); - } - self.try_next_instr() - } - - /// Executes an infallible unary `wasmi` instruction. - #[inline(always)] - fn execute_unary(&mut self, f: fn(UntypedValue) -> UntypedValue) { - self.sp.eval_top(f); - self.next_instr() - } - - /// Executes a fallible unary `wasmi` instruction. - #[inline(always)] - fn try_execute_unary( - &mut self, - f: fn(UntypedValue) -> Result, - ) -> Result<(), TrapCode> { - self.sp.try_eval_top(f)?; - self.try_next_instr() - } - - /// Executes an infallible binary `wasmi` instruction. - #[inline(always)] - fn execute_binary(&mut self, f: fn(UntypedValue, UntypedValue) -> UntypedValue) { - self.sp.eval_top2(f); - self.next_instr() - } - - /// Executes a fallible binary `wasmi` instruction. - #[inline(always)] - fn try_execute_binary( - &mut self, - f: fn(UntypedValue, UntypedValue) -> Result, - ) -> Result<(), TrapCode> { - self.sp.try_eval_top2(f)?; - self.try_next_instr() - } - - /// Shifts the instruction pointer to the next instruction. - #[inline(always)] - fn next_instr(&mut self) { - self.ip.add(1) - } - - /// Shifts the instruction pointer to the next instruction. - /// - /// Has a parameter `skip` to denote how many instruction words - /// to skip to reach the next actual instruction. - /// - /// # Note - /// - /// This is used by `wasmi` instructions that have a fixed - /// encoding size of two instruction words such as [`Instruction::Br`]. - #[inline(always)] - fn next_instr_at(&mut self, skip: usize) { - self.ip.add(skip) - } - - /// Shifts the instruction pointer to the next instruction and returns `Ok(())`. - /// - /// # Note - /// - /// This is a convenience function for fallible instructions. - #[inline(always)] - fn try_next_instr(&mut self) -> Result<(), TrapCode> { - self.next_instr(); - Ok(()) - } - - /// Shifts the instruction pointer to the next instruction and returns `Ok(())`. - /// - /// Has a parameter `skip` to denote how many instruction words - /// to skip to reach the next actual instruction. - /// - /// # Note - /// - /// This is a convenience function for fallible instructions. - #[inline(always)] - fn try_next_instr_at(&mut self, skip: usize) -> Result<(), TrapCode> { - self.next_instr_at(skip); - Ok(()) - } - - /// Branches and adjusts the value stack. - /// - /// # Note - /// - /// Offsets the instruction pointer using the given [`BranchOffset`] and - /// adjusts the value stack using the [`DropKeep`]. - #[inline(always)] - fn branch_to(&mut self, offset: BranchOffset) { - self.ip.offset(offset.to_i32() as isize) - } - - /// Branches and adjusts the value stack. - /// - /// # Note - /// - /// Offsets the instruction pointer using the given [`BranchOffset`] and - /// adjusts the value stack using the [`DropKeep`]. - #[inline(always)] - fn branch_to_and_adjust(&mut self, offset: BranchOffset, drop_keep: DropKeep) { - self.sp.drop_keep(drop_keep); - self.branch_to(offset) - } - - /// Synchronizes the current stack pointer with the [`ValueStack`]. - /// - /// # Note - /// - /// For performance reasons we detach the stack pointer form the [`ValueStack`]. - /// Therefore it is necessary to synchronize the [`ValueStack`] upon finishing - /// execution of a sequence of non control flow instructions. - #[inline(always)] - fn sync_stack_ptr(&mut self) { - self.value_stack.sync_stack_ptr(self.sp); - } - - /// Calls the given [`Func`]. - /// - /// This also prepares the instruction pointer and stack pointer for - /// the function call so that the stack and execution state is synchronized - /// with the outer structures. - #[inline(always)] - fn call_func( - &mut self, - skip: usize, - func: &Func, - kind: CallKind, - func_index: u32, - ) -> Result { - self.next_instr_at(skip); - self.sync_stack_ptr(); - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - } - match self.ctx.resolve_func(func) { - FuncEntity::Wasm(wasm_func) => { - let header = self.code_map.header(wasm_func.func_body()); - if let Some(tracer) = self.tracer.as_mut() { - tracer.function_call( - func_index, - header.max_stack_height(), - header.len_locals(), - String::new(), - ); - } - self.value_stack.prepare_wasm_call(header)?; - self.sp = self.value_stack.stack_ptr(); - self.cache.update_instance(wasm_func.instance()); - self.ip = self.code_map.instr_ptr(header.iref()); - Ok(CallOutcome::Continue) - } - FuncEntity::Host(_host_func) => { - self.cache.reset(); - Ok(CallOutcome::Call { - host_func: *func, - instance: *self.cache.instance(), - }) - } - } - } - - /// Calls the given internal [`CompiledFunc`]. - /// - /// This also prepares the instruction pointer and stack pointer for - /// the function call so that the stack and execution state is synchronized - /// with the outer structures. - #[inline(always)] - fn call_func_internal(&mut self, func: CompiledFunc, kind: CallKind) -> Result<(), TrapCode> { - self.next_instr_at(match kind { - CallKind::Nested => 1, - CallKind::Tail => 2, - }); - self.sync_stack_ptr(); - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - } - let header = self.code_map.header(func); - self.value_stack.prepare_wasm_call(header)?; - self.sp = self.value_stack.stack_ptr(); - self.ip = self.code_map.instr_ptr(header.iref()); - Ok(()) - } - - /// Returns to the caller. - /// - /// This also modifies the stack as the caller would expect it - /// and synchronizes the execution state with the outer structures. - #[inline(always)] - fn ret(&mut self, drop_keep: DropKeep) -> ReturnOutcome { - self.sp.drop_keep(drop_keep); - self.sync_stack_ptr(); - match self.call_stack.pop() { - Some(caller) => { - self.ip = caller.ip(); - self.cache.update_instance(caller.instance()); - ReturnOutcome::Wasm - } - None => ReturnOutcome::Host, - } - } - - /// Consume an amount of fuel specified by `delta` if `exec` succeeds. - /// - /// # Note - /// - /// - `delta` is only evaluated if fuel metering is enabled. - /// - `exec` is only evaluated if the remaining fuel is sufficient for amount of required fuel - /// determined by `delta` or if fuel metering is disabled. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with( - &mut self, - delta: impl FnOnce(&FuelCosts) -> u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - match self.get_fuel_consumption_mode() { - None => exec(self), - Some(mode) => self.consume_fuel_with_mode(mode, delta, exec), - } - } - - /// Consume an amount of fuel specified by `delta` and executes `exec`. - /// - /// The `mode` determines when and if the fuel determined by `delta` is charged. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with_mode( - &mut self, - mode: FuelConsumptionMode, - delta: impl FnOnce(&FuelCosts) -> u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - let delta = delta(self.fuel_costs()); - match mode { - FuelConsumptionMode::Lazy => self.consume_fuel_with_lazy(delta, exec), - FuelConsumptionMode::Eager => self.consume_fuel_with_eager(delta, exec), - } - } - - /// Consume an amount of fuel specified by `delta` if `exec` succeeds. - /// - /// Prior to executing `exec` it is checked if enough fuel is remaining - /// determined by `delta`. The fuel is charged only after `exec` has been - /// finished successfully. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with_lazy( - &mut self, - delta: u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - self.ctx.fuel().sufficient_fuel(delta)?; - let result = exec(self)?; - self.ctx - .fuel_mut() - .consume_fuel(delta) - .expect("remaining fuel has already been approved prior"); - Ok(result) - } - - /// Consume an amount of fuel specified by `delta` and executes `exec`. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with_eager( - &mut self, - delta: u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - self.ctx.fuel_mut().consume_fuel(delta)?; - exec(self) - } - - /// Returns a shared reference to the [`FuelCosts`] of the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - #[inline] - fn fuel_costs(&self) -> &FuelCosts { - self.ctx.engine().config().fuel_costs() - } - - /// Returns the [`FuelConsumptionMode`] of the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - #[inline] - fn get_fuel_consumption_mode(&self) -> Option { - self.ctx.engine().config().get_fuel_consumption_mode() - } - - /// Executes a `call_indirect` or `return_call_indirect` instruction. - #[inline(always)] - fn execute_call_indirect( - &mut self, - skip: usize, - table: TableIdx, - func_index: u32, - func_type: SignatureIdx, - kind: CallKind, - ) -> Result { - let table = self.cache.get_table(self.ctx, table); - let funcref = self - .ctx - .resolve_table(&table) - .get_untyped(func_index) - .map(FuncRef::from) - .ok_or(TrapCode::TableOutOfBounds)?; - let func = funcref.func().ok_or(TrapCode::IndirectCallToNull)?; - // for rWASM we do signature check using special additional opcode - let is_rwasm = self.ctx.engine().config().get_rwasm_config().is_some(); - if !is_rwasm { - let actual_signature = self.ctx.resolve_func(func).ty_dedup(); - let expected_signature = self - .ctx - .resolve_instance(self.cache.instance()) - .get_signature(func_type.to_u32()) - .unwrap_or_else(|| { - panic!("missing signature for call_indirect at index: {func_type:?}") - }); - if actual_signature != expected_signature { - return Err(TrapCode::BadSignature).map_err(Into::into); - } - } - self.call_func(skip, func, kind, func_index) - } -} - -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - #[inline(always)] - fn visit_unreachable(&mut self) -> Result<(), TrapCode> { - Err(TrapCode::UnreachableCodeReached).map_err(Into::into) - } - - #[inline(always)] - fn visit_consume_fuel(&mut self, block_fuel: BlockFuel) -> Result<(), TrapCode> { - // We do not have to check if fuel metering is enabled since - // these `wasmi` instructions are only generated if fuel metering - // is enabled to begin with. - if self.ctx.engine().config().get_consume_fuel() { - // We need to do the check for rWASM, because there is a mode where we don't have - // fuel even if application is compiled with fuel support - self.ctx.fuel_mut().consume_fuel(block_fuel.to_u64())?; - } - self.try_next_instr() - } - - /// Fetches the [`DropKeep`] parameter for an instruction. - /// - /// # Note - /// - /// - This is done by encoding an [`Instruction::Return`] instruction word following the actual - /// instruction where the [`DropKeep`] paremeter belongs to. - /// - This is required for some instructions that do not fit into a single instruction word and - /// store a [`DropKeep`] value in another instruction word. - fn fetch_drop_keep(&self, offset: usize) -> DropKeep { - let mut addr: InstructionPtr = self.ip; - addr.add(offset); - match addr.get() { - Instruction::Return(drop_keep) => *drop_keep, - _ => unreachable!("expected Return instruction word at this point"), - } - } - - /// Fetches the [`TableIdx`] parameter for an instruction. - /// - /// # Note - /// - /// - This is done by encoding an [`Instruction::TableGet`] instruction word following the - /// actual instruction where the [`TableIdx`] paremeter belongs to. - /// - This is required for some instructions that do not fit into a single instruction word and - /// store a [`TableIdx`] value in another instruction word. - fn fetch_table_idx(&mut self, offset: usize) -> TableIdx { - let mut addr: InstructionPtr = self.ip; - addr.add(offset); - let table_idx = match addr.get() { - Instruction::TableGet(table_idx) => *table_idx, - _ => unreachable!("expected TableGet instruction word at this point"), - }; - if let Some(tracer) = self.tracer.as_mut() { - tracer.remember_next_table(table_idx); - } - table_idx - } - - #[inline(always)] - fn visit_br(&mut self, offset: BranchOffset) { - self.branch_to(offset) - } - - #[inline(always)] - fn visit_br_if_eqz(&mut self, offset: BranchOffset) { - let condition = self.sp.pop_as(); - if condition { - self.next_instr() - } else { - self.branch_to(offset) - } - } - - #[inline(always)] - fn visit_br_if_nez(&mut self, offset: BranchOffset) { - let condition = self.sp.pop_as(); - if condition { - self.branch_to(offset) - } else { - self.next_instr() - } - } - - #[inline(always)] - fn visit_br_adjust(&mut self, offset: BranchOffset) { - let drop_keep = self.fetch_drop_keep(1); - self.branch_to_and_adjust(offset, drop_keep) - } - - #[inline(always)] - fn visit_br_adjust_if_nez(&mut self, offset: BranchOffset) { - let condition = self.sp.pop_as(); - if condition { - let drop_keep = self.fetch_drop_keep(1); - self.branch_to_and_adjust(offset, drop_keep) - } else { - self.next_instr_at(2) - } - } - - #[inline(always)] - fn visit_return_if_nez(&mut self, drop_keep: DropKeep) -> ReturnOutcome { - let condition = self.sp.pop_as(); - if condition { - self.ret(drop_keep) - } else { - self.next_instr(); - ReturnOutcome::Wasm - } - } - - #[inline(always)] - fn visit_br_table(&mut self, targets: BranchTableTargets) { - let index: u32 = self.sp.pop_as(); - // The index of the default target which is the last target of the slice. - let max_index = targets.to_usize() - 1; - // A normalized index will always yield a target without panicking. - let normalized_index = cmp::min(index as usize, max_index); - // Update `pc`: - self.ip.add(2 * normalized_index + 1); - } - - #[inline(always)] - fn visit_ret(&mut self, drop_keep: DropKeep) -> ReturnOutcome { - self.ret(drop_keep) - } - - #[inline(always)] - fn visit_local_get(&mut self, local_depth: LocalDepth) { - let value = self.sp.nth_back(local_depth.to_usize()); - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_local_set(&mut self, local_depth: LocalDepth) { - let new_value = self.sp.pop(); - self.sp.set_nth_back(local_depth.to_usize(), new_value); - self.next_instr() - } - - #[inline(always)] - fn visit_local_tee(&mut self, local_depth: LocalDepth) { - let new_value = self.sp.last(); - self.sp.set_nth_back(local_depth.to_usize(), new_value); - self.next_instr() - } - - #[inline(always)] - fn visit_global_get(&mut self, global_index: GlobalIdx) { - let global_value = self.cache.get_global(self.ctx, global_index); - self.sp.push(global_value); - self.next_instr() - } - - #[inline(always)] - fn visit_global_set(&mut self, global_index: GlobalIdx) { - let new_value = self.sp.pop(); - self.cache.set_global(self.ctx, global_index, new_value); - self.next_instr() - } - - #[inline(always)] - fn visit_return_call_internal(&mut self, compiled_func: CompiledFunc) -> Result<(), TrapCode> { - let drop_keep = self.fetch_drop_keep(1); - self.sp.drop_keep(drop_keep); - self.call_func_internal(compiled_func, CallKind::Tail) - } - - #[inline(always)] - fn visit_return_call(&mut self, func_index: FuncIdx) -> Result { - let drop_keep = self.fetch_drop_keep(1); - self.sp.drop_keep(drop_keep); - let callee = self.cache.get_func(self.ctx, func_index); - self.call_func(2, &callee, CallKind::Tail, func_index.to_u32()) - } - - #[inline(always)] - fn visit_return_call_indirect( - &mut self, - func_type: SignatureIdx, - ) -> Result { - let drop_keep = self.fetch_drop_keep(1); - let table = self.fetch_table_idx(2); - let func_index: u32 = self.sp.pop_as(); - self.sp.drop_keep(drop_keep); - // for rWASM, let's store func type on the stack - if self.ctx.engine().config().get_rwasm_config().is_some() { - self.last_signature = Some(func_type); - } - self.execute_call_indirect(3, table, func_index, func_type, CallKind::Tail) - } - - #[inline(always)] - fn visit_call_internal(&mut self, compiled_func: CompiledFunc) -> Result<(), TrapCode> { - self.call_func_internal(compiled_func, CallKind::Nested) - } - - #[inline(always)] - fn visit_call(&mut self, func_index: FuncIdx) -> Result { - if self.ctx.engine().config().get_rwasm_wrap_import_funcs() { - let wrapped_func_index = self.ctx.wrap_stored(func_index); - let func_entity = self - .ctx - .engine() - .resolve_trampoline(wrapped_func_index) - .ok_or(TrapCode::UnresolvedFunction)?; - self.next_instr_at(1); - self.sync_stack_ptr(); - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - self.cache.reset(); - Ok(CallOutcome::Call { - host_func: func_entity, - instance: *self.cache.instance(), - }) - } else { - let callee = self.cache.get_func(self.ctx, func_index); - self.call_func(1, &callee, CallKind::Nested, func_index.to_u32()) - } - } - - #[inline(always)] - fn visit_call_indirect(&mut self, func_type: SignatureIdx) -> Result { - let table = self.fetch_table_idx(1); - let func_index: u32 = self.sp.pop_as(); - // for rWASM, let's store func type on the stack - if self.ctx.engine().config().get_rwasm_config().is_some() { - self.last_signature = Some(func_type); - } - self.execute_call_indirect(2, table, func_index, func_type, CallKind::Nested) - } - - #[inline(always)] - fn visit_signature_check(&mut self, expected_signature: SignatureIdx) -> Result<(), TrapCode> { - debug_assert!( - self.ctx.engine().config().get_rwasm_config().is_some(), - "this instruction can be used only in rWASM mode" - ); - if let Some(actual_signature) = self.last_signature.take() { - if actual_signature != expected_signature { - return Err(TrapCode::BadSignature).map_err(Into::into); - } - } - self.next_instr(); - Ok(()) - } - - #[inline(always)] - fn visit_i32_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_i64_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_f32_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_f64_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_const(&mut self, cref: ConstRef) { - let value = self - .const_pool - .get(cref) - .unwrap_or_else(|| unreachable!("missing constant value for const reference")); - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_drop(&mut self) { - self.sp.drop(); - self.next_instr() - } - - #[inline(always)] - fn visit_select(&mut self) { - self.sp.eval_top3(|e1, e2, e3| { - let condition = >::from(e3); - if condition { - e1 - } else { - e2 - } - }); - self.next_instr() - } - - #[inline(always)] - fn visit_memory_size(&mut self) { - let memory = self.cache.default_memory(self.ctx); - let result: u32 = self.ctx.resolve_memory(memory).current_pages().into(); - self.sp.push_as(result); - self.next_instr() - } - - #[inline(always)] - fn visit_memory_grow( - &mut self, - resource_limiter: &mut ResourceLimiterRef<'ctx>, - ) -> Result<(), TrapCode> { - let delta: u32 = self.sp.pop_as(); - let delta = match Pages::new(delta) { - Some(pages) => pages, - None => { - // Cannot grow memory so we push the expected error value. - self.sp.push_as(INVALID_GROWTH_ERRCODE); - return self.try_next_instr(); - } - }; - let result = self.consume_fuel_with( - |costs| { - let delta_in_bytes = delta.to_bytes().unwrap_or(0) as u64; - costs.fuel_for_bytes(delta_in_bytes) - }, - |this| { - let memory = this.cache.default_memory(this.ctx); - let new_pages = this - .ctx - .resolve_memory_mut(memory) - .grow(delta, resource_limiter) - .map(u32::from)?; - // The `memory.grow` operation might have invalidated the cached - // linear memory so we need to reset it in order for the cache to - // reload in case it is used again. - this.cache.reset_default_memory_bytes(); - Ok(new_pages) - }, - ); - let result = match result { - Ok(result) => result, - Err(EntityGrowError::InvalidGrow) => INVALID_GROWTH_ERRCODE, - Err(EntityGrowError::TrapCode(trap_code)) => return Err(trap_code), - }; - self.sp.push_as(result); - self.try_next_instr() - } - - #[inline(always)] - fn visit_memory_fill(&mut self) -> Result<(), TrapCode> { - // The `n`, `val` and `d` variable bindings are extracted from the Wasm specification. - let (d, val, n) = self.sp.pop3(); - let n = i32::from(n) as usize; - let offset = i32::from(d) as usize; - let byte = u8::from(val); - self.consume_fuel_with( - |costs| costs.fuel_for_bytes(n as u64), - |this| { - let memory = this - .cache - .default_memory_bytes(this.ctx) - .get_mut(offset..) - .and_then(|memory| memory.get_mut(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - memory.fill(byte); - if let Some(tracer) = this.tracer.as_mut() { - tracer.memory_change(offset as u32, n as u32, memory); - } - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_memory_copy(&mut self) -> Result<(), TrapCode> { - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let n = i32::from(n) as usize; - let src_offset = i32::from(s) as usize; - let dst_offset = i32::from(d) as usize; - self.consume_fuel_with( - |costs| costs.fuel_for_bytes(n as u64), - |this| { - let data = this.cache.default_memory_bytes(this.ctx); - // These accesses just perform the bounds checks required by the Wasm spec. - data.get(src_offset..) - .and_then(|memory| memory.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - data.get(dst_offset..) - .and_then(|memory| memory.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - data.copy_within(src_offset..src_offset.wrapping_add(n), dst_offset); - if let Some(tracer) = this.tracer.as_mut() { - tracer.memory_change( - dst_offset as u32, - n as u32, - &data[dst_offset..(dst_offset + n)], - ); - } - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_memory_init(&mut self, mut segment: DataSegmentIdx) -> Result<(), TrapCode> { - // we use some tricky structure for rWASM to determine what data segments dropped - let is_empty_segment = if self.ctx.engine().config().get_rwasm_config().is_some() { - // increase segment index, because the first index is used for the global data section - let (_, data) = self - .cache - .get_default_memory_and_data_segment(self.ctx, segment); - // since we have only one data segment then rewrite index with 0 - segment = DataSegmentIdx::from(0); - data.len() == 0 - } else { - false - }; - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let n = i32::from(n) as usize; - let src_offset = i32::from(s) as usize; - let dst_offset = i32::from(d) as usize; - self.consume_fuel_with( - |costs| costs.fuel_for_bytes(n as u64), - |this| { - let (memory, mut data) = this - .cache - .get_default_memory_and_data_segment(this.ctx, segment); - if is_empty_segment { - data = &[] - } - let memory = memory - .get_mut(dst_offset..) - .and_then(|memory| memory.get_mut(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - let data = data - .get(src_offset..) - .and_then(|data| data.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - memory.copy_from_slice(data); - if let Some(tracer) = this.tracer.as_mut() { - tracer.global_memory(dst_offset as u32, n as u32, memory); - } - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_data_drop(&mut self, segment_index: DataSegmentIdx) { - let segment = self - .cache - .get_data_segment(self.ctx, segment_index.to_u32()); - self.ctx.resolve_data_segment_mut(&segment).drop_bytes(); - self.next_instr(); - } - - #[inline(always)] - fn visit_table_size(&mut self, table_index: TableIdx) { - let table = self.cache.get_table(self.ctx, table_index); - let size = self.ctx.resolve_table(&table).size(); - self.sp.push_as(size); - self.next_instr() - } - - #[inline(always)] - fn visit_table_grow( - &mut self, - table_index: TableIdx, - resource_limiter: &mut ResourceLimiterRef<'ctx>, - ) -> Result<(), TrapCode> { - let (init, delta) = self.sp.pop2(); - let delta: u32 = delta.into(); - let result = self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(delta)), - |this| { - let table = this.cache.get_table(this.ctx, table_index); - this.ctx - .resolve_table_mut(&table) - .grow_untyped(delta, init, resource_limiter) - }, - ); - let result = match result { - Ok(result) => result, - Err(EntityGrowError::InvalidGrow) => INVALID_GROWTH_ERRCODE, - Err(EntityGrowError::TrapCode(trap_code)) => return Err(trap_code), - }; - self.sp.push_as(result); - if let Some(tracer) = self.tracer.as_mut() { - tracer.table_size_change(table_index.to_u32(), init.as_u32(), delta); - } - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_fill(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (i, val, n) = self.sp.pop3(); - let dst: u32 = i.into(); - let len: u32 = n.into(); - self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(len)), - |this| { - let table = this.cache.get_table(this.ctx, table_index); - this.ctx - .resolve_table_mut(&table) - .fill_untyped(dst, val, len)?; - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_get(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { - self.sp.try_eval_top(|index| { - let index: u32 = index.into(); - let table = self.cache.get_table(self.ctx, table_index); - self.ctx - .resolve_table(&table) - .get_untyped(index) - .ok_or(TrapCode::TableOutOfBounds) - })?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_set(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { - let (index, value) = self.sp.pop2(); - let index: u32 = index.into(); - let table = self.cache.get_table(self.ctx, table_index); - self.ctx - .resolve_table_mut(&table) - .set_untyped(index, value) - .map_err(|_| TrapCode::TableOutOfBounds)?; - if let Some(tracer) = self.tracer.as_mut() { - tracer.table_change(table_index.to_u32(), index, value); - } - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_copy(&mut self, dst: TableIdx) -> Result<(), TrapCode> { - let src = self.fetch_table_idx(1); - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let len = u32::from(n); - let src_index = u32::from(s); - let dst_index = u32::from(d); - self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(len)), - |this| { - // Query both tables and check if they are the same: - let dst = this.cache.get_table(this.ctx, dst); - let src = this.cache.get_table(this.ctx, src); - if Table::eq(&dst, &src) { - // Copy within the same table: - let table = this.ctx.resolve_table_mut(&dst); - table.copy_within(dst_index, src_index, len)?; - } else { - // Copy from one table to another table: - let (dst, src) = this.ctx.resolve_table_pair_mut(&dst, &src); - TableEntity::copy(dst, dst_index, src, src_index, len)?; - } - Ok(()) - }, - )?; - self.try_next_instr_at(2) - } - - #[inline(always)] - fn visit_table_init(&mut self, mut elem: ElementSegmentIdx) -> Result<(), TrapCode> { - let table_idx = self.fetch_table_idx(1); - // we use some tricky structure for rWASM to determine what element segments dropped - let is_empty_segment = if self.ctx.engine().config().get_rwasm_config().is_some() { - // increase segment index, because the first index is used for the global element - // segment - let (_, _, element) = self - .cache - .get_table_and_element_segment(self.ctx, table_idx, elem); - // since we have only one element segment then rewrite index with 0 - elem = ElementSegmentIdx::from(0); - element.items.is_none() - } else { - false - }; - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let len = u32::from(n); - let src_index = u32::from(s); - let dst_index = u32::from(d); - self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(len)), - |this| { - let (instance, table, mut element) = this - .cache - .get_table_and_element_segment(this.ctx, table_idx, elem); - let empty_element_segment = ElementSegmentEntity::empty(element.ty()); - if is_empty_segment { - element = &empty_element_segment; - } - table.init(dst_index, element, src_index, len, |func_index| { - let func_index = self - .code_map - .resolve_function_by_offset(func_index as usize) - .map(|v| v.into_usize() as u32) - .unwrap_or(func_index); - let func = instance - .get_func(func_index) - .unwrap_or_else(|| panic!("missing function at index {func_index}")); - Some(func) - })?; - Ok(()) - }, - )?; - self.try_next_instr_at(2) - } - - #[inline(always)] - fn visit_element_drop(&mut self, segment_index: ElementSegmentIdx) { - let segment = self.cache.get_element_segment(self.ctx, segment_index); - self.ctx.resolve_element_segment_mut(&segment).drop_items(); - self.next_instr(); - } - - #[inline(always)] - fn visit_ref_func(&mut self, func_index: FuncIdx) -> Result<(), TrapCode> { - let func_index: FuncIdx = self - .code_map - .resolve_function_by_offset(func_index.to_u32() as usize) - .map(|v| v.to_u32().into()) - .unwrap_or(func_index); - let func = self.cache.get_func(self.ctx, func_index); - let funcref = FuncRef::new(func); - self.sp.push_as(funcref); - self.next_instr(); - Ok(()) - } -} - -macro_rules! impl_visit_load { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident( - &mut self, - offset: AddressOffset, - ) -> Result<(), TrapCode> { - self.execute_load_extend(offset, UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_load! { - fn visit_i32_load(i32_load); - fn visit_i64_load(i64_load); - fn visit_f32_load(f32_load); - fn visit_f64_load(f64_load); - - fn visit_i32_load_i8_s(i32_load8_s); - fn visit_i32_load_i8_u(i32_load8_u); - fn visit_i32_load_i16_s(i32_load16_s); - fn visit_i32_load_i16_u(i32_load16_u); - - fn visit_i64_load_i8_s(i64_load8_s); - fn visit_i64_load_i8_u(i64_load8_u); - fn visit_i64_load_i16_s(i64_load16_s); - fn visit_i64_load_i16_u(i64_load16_u); - fn visit_i64_load_i32_s(i64_load32_s); - fn visit_i64_load_i32_u(i64_load32_u); - } -} - -macro_rules! impl_visit_store { - ( $( fn $visit_ident:ident($untyped_ident:ident, $type_size:literal); )* ) => { - $( - #[inline(always)] - fn $visit_ident( - &mut self, - offset: AddressOffset, - ) -> Result<(), TrapCode> { - self.execute_store_wrap(offset, UntypedValue::$untyped_ident, $type_size) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_store! { - fn visit_i32_store(i32_store, 4); - fn visit_i64_store(i64_store, 8); - fn visit_f32_store(f32_store, 4); - fn visit_f64_store(f64_store, 8); - - fn visit_i32_store_8(i32_store8, 1); - fn visit_i32_store_16(i32_store16, 2); - - fn visit_i64_store_8(i64_store8, 1); - fn visit_i64_store_16(i64_store16, 2); - fn visit_i64_store_32(i64_store32, 4); - } -} - -macro_rules! impl_visit_unary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) { - self.execute_unary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_unary! { - fn visit_i32_eqz(i32_eqz); - fn visit_i64_eqz(i64_eqz); - - fn visit_i32_clz(i32_clz); - fn visit_i32_ctz(i32_ctz); - fn visit_i32_popcnt(i32_popcnt); - - fn visit_i64_clz(i64_clz); - fn visit_i64_ctz(i64_ctz); - fn visit_i64_popcnt(i64_popcnt); - - fn visit_f32_abs(f32_abs); - fn visit_f32_neg(f32_neg); - fn visit_f32_ceil(f32_ceil); - fn visit_f32_floor(f32_floor); - fn visit_f32_trunc(f32_trunc); - fn visit_f32_nearest(f32_nearest); - fn visit_f32_sqrt(f32_sqrt); - - fn visit_f64_abs(f64_abs); - fn visit_f64_neg(f64_neg); - fn visit_f64_ceil(f64_ceil); - fn visit_f64_floor(f64_floor); - fn visit_f64_trunc(f64_trunc); - fn visit_f64_nearest(f64_nearest); - fn visit_f64_sqrt(f64_sqrt); - - fn visit_i32_wrap_i64(i32_wrap_i64); - fn visit_i64_extend_i32_s(i64_extend_i32_s); - fn visit_i64_extend_i32_u(i64_extend_i32_u); - - fn visit_f32_convert_i32_s(f32_convert_i32_s); - fn visit_f32_convert_i32_u(f32_convert_i32_u); - fn visit_f32_convert_i64_s(f32_convert_i64_s); - fn visit_f32_convert_i64_u(f32_convert_i64_u); - fn visit_f32_demote_f64(f32_demote_f64); - fn visit_f64_convert_i32_s(f64_convert_i32_s); - fn visit_f64_convert_i32_u(f64_convert_i32_u); - fn visit_f64_convert_i64_s(f64_convert_i64_s); - fn visit_f64_convert_i64_u(f64_convert_i64_u); - fn visit_f64_promote_f32(f64_promote_f32); - - fn visit_i32_extend8_s(i32_extend8_s); - fn visit_i32_extend16_s(i32_extend16_s); - fn visit_i64_extend8_s(i64_extend8_s); - fn visit_i64_extend16_s(i64_extend16_s); - fn visit_i64_extend32_s(i64_extend32_s); - - fn visit_i32_trunc_sat_f32_s(i32_trunc_sat_f32_s); - fn visit_i32_trunc_sat_f32_u(i32_trunc_sat_f32_u); - fn visit_i32_trunc_sat_f64_s(i32_trunc_sat_f64_s); - fn visit_i32_trunc_sat_f64_u(i32_trunc_sat_f64_u); - fn visit_i64_trunc_sat_f32_s(i64_trunc_sat_f32_s); - fn visit_i64_trunc_sat_f32_u(i64_trunc_sat_f32_u); - fn visit_i64_trunc_sat_f64_s(i64_trunc_sat_f64_s); - fn visit_i64_trunc_sat_f64_u(i64_trunc_sat_f64_u); - } -} - -macro_rules! impl_visit_fallible_unary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) -> Result<(), TrapCode> { - self.try_execute_unary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_fallible_unary! { - fn visit_i32_trunc_f32_s(i32_trunc_f32_s); - fn visit_i32_trunc_f32_u(i32_trunc_f32_u); - fn visit_i32_trunc_f64_s(i32_trunc_f64_s); - fn visit_i32_trunc_f64_u(i32_trunc_f64_u); - - fn visit_i64_trunc_f32_s(i64_trunc_f32_s); - fn visit_i64_trunc_f32_u(i64_trunc_f32_u); - fn visit_i64_trunc_f64_s(i64_trunc_f64_s); - fn visit_i64_trunc_f64_u(i64_trunc_f64_u); - } -} - -macro_rules! impl_visit_binary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) { - self.execute_binary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_binary! { - fn visit_i32_eq(i32_eq); - fn visit_i32_ne(i32_ne); - fn visit_i32_lt_s(i32_lt_s); - fn visit_i32_lt_u(i32_lt_u); - fn visit_i32_gt_s(i32_gt_s); - fn visit_i32_gt_u(i32_gt_u); - fn visit_i32_le_s(i32_le_s); - fn visit_i32_le_u(i32_le_u); - fn visit_i32_ge_s(i32_ge_s); - fn visit_i32_ge_u(i32_ge_u); - - fn visit_i64_eq(i64_eq); - fn visit_i64_ne(i64_ne); - fn visit_i64_lt_s(i64_lt_s); - fn visit_i64_lt_u(i64_lt_u); - fn visit_i64_gt_s(i64_gt_s); - fn visit_i64_gt_u(i64_gt_u); - fn visit_i64_le_s(i64_le_s); - fn visit_i64_le_u(i64_le_u); - fn visit_i64_ge_s(i64_ge_s); - fn visit_i64_ge_u(i64_ge_u); - - fn visit_f32_eq(f32_eq); - fn visit_f32_ne(f32_ne); - fn visit_f32_lt(f32_lt); - fn visit_f32_gt(f32_gt); - fn visit_f32_le(f32_le); - fn visit_f32_ge(f32_ge); - - fn visit_f64_eq(f64_eq); - fn visit_f64_ne(f64_ne); - fn visit_f64_lt(f64_lt); - fn visit_f64_gt(f64_gt); - fn visit_f64_le(f64_le); - fn visit_f64_ge(f64_ge); - - fn visit_i32_add(i32_add); - fn visit_i32_sub(i32_sub); - fn visit_i32_mul(i32_mul); - fn visit_i32_and(i32_and); - fn visit_i32_or(i32_or); - fn visit_i32_xor(i32_xor); - fn visit_i32_shl(i32_shl); - fn visit_i32_shr_s(i32_shr_s); - fn visit_i32_shr_u(i32_shr_u); - fn visit_i32_rotl(i32_rotl); - fn visit_i32_rotr(i32_rotr); - - fn visit_i64_add(i64_add); - fn visit_i64_sub(i64_sub); - fn visit_i64_mul(i64_mul); - fn visit_i64_and(i64_and); - fn visit_i64_or(i64_or); - fn visit_i64_xor(i64_xor); - fn visit_i64_shl(i64_shl); - fn visit_i64_shr_s(i64_shr_s); - fn visit_i64_shr_u(i64_shr_u); - fn visit_i64_rotl(i64_rotl); - fn visit_i64_rotr(i64_rotr); - - fn visit_f32_add(f32_add); - fn visit_f32_sub(f32_sub); - fn visit_f32_mul(f32_mul); - fn visit_f32_div(f32_div); - fn visit_f32_min(f32_min); - fn visit_f32_max(f32_max); - fn visit_f32_copysign(f32_copysign); - - fn visit_f64_add(f64_add); - fn visit_f64_sub(f64_sub); - fn visit_f64_mul(f64_mul); - fn visit_f64_div(f64_div); - fn visit_f64_min(f64_min); - fn visit_f64_max(f64_max); - fn visit_f64_copysign(f64_copysign); - } -} - -macro_rules! impl_visit_fallible_binary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) -> Result<(), TrapCode> { - self.try_execute_binary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_fallible_binary! { - fn visit_i32_div_s(i32_div_s); - fn visit_i32_div_u(i32_div_u); - fn visit_i32_rem_s(i32_rem_s); - fn visit_i32_rem_u(i32_rem_u); - - fn visit_i64_div_s(i64_div_s); - fn visit_i64_div_u(i64_div_u); - fn visit_i64_rem_s(i64_rem_s); - fn visit_i64_rem_u(i64_rem_u); - } -} diff --git a/legacy/src/engine/func_args.rs b/legacy/src/engine/func_args.rs deleted file mode 100644 index 2c0a16f67..000000000 --- a/legacy/src/engine/func_args.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! API using the Rust type system to guide host function trampoline execution. - -use crate::{ - core::{DecodeUntypedSlice, EncodeUntypedSlice, UntypedError, UntypedValue}, - value::WithType, - Value, -}; -use core::cmp; - -/// Used to decode host function parameters. -#[derive(Debug)] -pub struct FuncParams<'a> { - /// Slice holding the raw (encoded but untyped) parameters - /// of the host function invocation before the call and the - /// results of the host function invocation after the call. - /// - /// Therefore the length of the slice must be large enough - /// to hold all parameters and all results but not both at - /// the same time. - params_results: &'a mut [UntypedValue], - /// The length of the expected parameters of the function invocation. - len_params: usize, - /// The length of the expected results of the function invocation. - len_results: usize, -} - -/// Used to encode host function results. -#[derive(Debug)] -pub struct FuncResults<'a> { - results: &'a mut [UntypedValue], -} - -impl<'a> FuncResults<'a> { - /// Create new [`FuncResults`] from the given `results` slice. - fn new(results: &'a mut [UntypedValue]) -> Self { - Self { results } - } - - /// Encodes the results of the host function invocation as `T`. - /// - /// # Panics - /// - /// If the number of results dictated by `T` does not match the expected amount. - pub fn encode_results(self, values: T) -> FuncFinished - where - T: EncodeUntypedSlice, - { - UntypedValue::encode_slice::(self.results, values) - .unwrap_or_else(|error| panic!("encountered unexpected invalid tuple length: {error}")); - FuncFinished {} - } - - /// Encodes the results of the host function invocation given the `values` slice. - /// - /// # Panics - /// - /// If the number of expected results does not match the length of `values`. - pub fn encode_results_from_slice(self, values: &[Value]) -> Result { - assert_eq!(self.results.len(), values.len()); - self.results.iter_mut().zip(values).for_each(|(dst, src)| { - *dst = src.clone().into(); - }); - Ok(FuncFinished {}) - } -} - -/// Used to guarantee by the type system that this API has been used correctly. -/// -/// Ensures at compile time that host functions always call -/// [`FuncParams::decode_params`] or [`FuncParams::decode_params_into_slice`] -/// followed by -/// [`FuncResults::encode_results`] or [`FuncResults::encode_results_from_slice`] -/// at the end of their execution. -#[derive(Debug)] -pub struct FuncFinished {} - -impl<'a> FuncParams<'a> { - /// Create new [`FuncParams`]. - /// - /// # Panics - /// - /// If the length of hte `params_results` slice does not match the maximum - /// of the `len_params` and `Len_results`. - pub(super) fn new( - params_results: &'a mut [UntypedValue], - len_params: usize, - len_results: usize, - ) -> Self { - assert_eq!(params_results.len(), cmp::max(len_params, len_results)); - Self { - params_results, - len_params, - len_results, - } - } - - /// Returns a slice over the untyped function parameters. - fn params(&self) -> &[UntypedValue] { - &self.params_results[..self.len_params] - } - - /// Decodes and returns the executed host function parameters as `T`. - /// - /// # Panics - /// - /// If the number of function parameters dictated by `T` does not match. - pub fn decode_params(self) -> (T, FuncResults<'a>) - where - T: DecodeUntypedSlice, - { - let decoded = UntypedValue::decode_slice::(self.params()) - .unwrap_or_else(|error| panic!("encountered unexpected invalid tuple length: {error}")); - let results = self.into_func_results(); - (decoded, results) - } - - /// Decodes and stores the executed host functions parameters into `values`. - /// - /// # Panics - /// - /// If the number of host function parameters and items in `values` does not match. - pub fn decode_params_into_slice( - self, - values: &mut [Value], - ) -> Result, UntypedError> { - assert_eq!(self.params().len(), values.len()); - self.params().iter().zip(values).for_each(|(src, dst)| { - *dst = src.with_type(dst.ty()); - }); - let results = self.into_func_results(); - Ok(results) - } - - /// Consumes `self` to return the [`FuncResults`] out of it. - fn into_func_results(self) -> FuncResults<'a> { - FuncResults::new(&mut self.params_results[..self.len_results]) - } -} diff --git a/legacy/src/engine/func_builder/control_frame.rs b/legacy/src/engine/func_builder/control_frame.rs deleted file mode 100644 index b80ce4fe8..000000000 --- a/legacy/src/engine/func_builder/control_frame.rs +++ /dev/null @@ -1,425 +0,0 @@ -use super::{labels::LabelRef, Instr}; -use crate::module::BlockType; - -/// A Wasm `block` control flow frame. -#[derive(Debug, Copy, Clone)] -pub struct BlockControlFrame { - /// The type of the [`BlockControlFrame`]. - block_type: BlockType, - /// The value stack height upon entering the [`BlockControlFrame`]. - stack_height: u32, - /// Label representing the end of the [`BlockControlFrame`]. - end_label: LabelRef, - /// Instruction to consume fuel upon entering the basic block if fuel metering is enabled. - /// - /// # Note - /// - /// This might be a reference to the consume fuel instruction of the parent - /// [`ControlFrame`] of the [`BlockControlFrame`]. - consume_fuel: Option, -} - -impl BlockControlFrame { - /// Creates a new [`BlockControlFrame`]. - pub fn new( - block_type: BlockType, - end_label: LabelRef, - stack_height: u32, - consume_fuel: Option, - ) -> Self { - Self { - block_type, - stack_height, - end_label, - consume_fuel, - } - } - - /// Returns the label for the branch destination of the [`BlockControlFrame`]. - /// - /// # Note - /// - /// Branches to [`BlockControlFrame`] jump to the end of the frame. - pub fn branch_destination(&self) -> LabelRef { - self.end_label - } - - /// Returns the label to the end of the [`BlockControlFrame`]. - pub fn end_label(&self) -> LabelRef { - self.end_label - } - - /// Returns the value stack height upon entering the [`BlockControlFrame`]. - pub fn stack_height(&self) -> u32 { - self.stack_height - } - - /// Returns the [`BlockType`] of the [`BlockControlFrame`]. - pub fn block_type(&self) -> BlockType { - self.block_type - } - - /// Returns a reference to the [`ConsumeFuel`] instruction of the [`BlockControlFrame`] if any. - /// - /// Returns `None` if fuel metering is disabled. - /// - /// # Note - /// - /// A [`BlockControlFrame`] might share its [`ConsumeFuel`] instruction with its child - /// [`BlockControlFrame`]. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn consume_fuel_instr(&self) -> Option { - self.consume_fuel - } -} - -/// A Wasm `loop` control flow frame. -#[derive(Debug, Copy, Clone)] -pub struct LoopControlFrame { - /// The type of the [`LoopControlFrame`]. - block_type: BlockType, - /// The value stack height upon entering the [`LoopControlFrame`]. - stack_height: u32, - /// Label representing the head of the [`LoopControlFrame`]. - head_label: LabelRef, - /// Instruction to consume fuel upon entering the basic block if fuel metering is enabled. - /// - /// # Note - /// - /// This must be `Some` if fuel metering is enabled and `None` otherwise. - consume_fuel: Option, -} - -impl LoopControlFrame { - /// Creates a new [`LoopControlFrame`]. - pub fn new( - block_type: BlockType, - head_label: LabelRef, - stack_height: u32, - consume_fuel: Option, - ) -> Self { - Self { - block_type, - stack_height, - head_label, - consume_fuel, - } - } - - /// Returns the label for the branch destination of the [`LoopControlFrame`]. - /// - /// # Note - /// - /// Branches to [`LoopControlFrame`] jump to the head of the loop. - pub fn branch_destination(&self) -> LabelRef { - self.head_label - } - - /// Returns the value stack height upon entering the [`LoopControlFrame`]. - pub fn stack_height(&self) -> u32 { - self.stack_height - } - - /// Returns the [`BlockType`] of the [`LoopControlFrame`]. - pub fn block_type(&self) -> BlockType { - self.block_type - } - - /// Returns a reference to the [`ConsumeFuel`] instruction of the [`BlockControlFrame`] if any. - /// - /// Returns `None` if fuel metering is disabled. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn consume_fuel_instr(&self) -> Option { - self.consume_fuel - } -} - -/// A Wasm `if` and `else` control flow frames. -#[derive(Debug, Copy, Clone)] -pub struct IfControlFrame { - /// The type of the [`IfControlFrame`]. - block_type: BlockType, - /// The value stack height upon entering the [`IfControlFrame`]. - stack_height: u32, - /// Label representing the end of the [`IfControlFrame`]. - end_label: LabelRef, - /// Label representing the optional `else` branch of the [`IfControlFrame`]. - else_label: LabelRef, - /// End of `then` branch is reachable. - /// - /// # Note - /// - /// - This is `None` upon entering the `if` control flow frame. Once the optional `else` case - /// or the `end` of the `if` control flow frame is reached this field will be computed. - /// - This information is important to know how to continue after a diverging `if` control flow - /// frame. - /// - An `end_of_else_is_reachable` field is not needed since it will be easily computed once - /// the translation reaches the end of the `if`. - end_of_then_is_reachable: Option, - /// Instruction to consume fuel upon entering the basic block if fuel metering is enabled. - /// - /// This is used for both `then` and `else` blocks. When entering the `else` - /// block this field is updated to represent the [`ConsumeFuel`] instruction - /// of the `else` block instead of the `then` block. This is possible because - /// only one of them is needed at the same time during translation. - /// - /// # Note - /// - /// This must be `Some` if fuel metering is enabled and `None` otherwise. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - consume_fuel: Option, -} - -impl IfControlFrame { - /// Creates a new [`IfControlFrame`]. - pub fn new( - block_type: BlockType, - end_label: LabelRef, - else_label: LabelRef, - stack_height: u32, - consume_fuel: Option, - ) -> Self { - assert_ne!( - end_label, else_label, - "end and else labels must be different" - ); - Self { - block_type, - stack_height, - end_label, - else_label, - end_of_then_is_reachable: None, - consume_fuel, - } - } - - /// Returns the label for the branch destination of the [`IfControlFrame`]. - /// - /// # Note - /// - /// Branches to [`IfControlFrame`] jump to the end of the if and else frame. - pub fn branch_destination(&self) -> LabelRef { - self.end_label - } - - /// Returns the label to the end of the [`IfControlFrame`]. - pub fn end_label(&self) -> LabelRef { - self.end_label - } - - /// Returns the label to the optional `else` of the [`IfControlFrame`]. - pub fn else_label(&self) -> LabelRef { - self.else_label - } - - /// Returns the value stack height upon entering the [`IfControlFrame`]. - pub fn stack_height(&self) -> u32 { - self.stack_height - } - - /// Returns the [`BlockType`] of the [`IfControlFrame`]. - pub fn block_type(&self) -> BlockType { - self.block_type - } - - /// Updates the reachability of the end of the `then` branch. - /// - /// # Panics - /// - /// If this information has already been provided prior. - pub fn update_end_of_then_reachability(&mut self, reachable: bool) { - assert!(self.end_of_then_is_reachable.is_none()); - self.end_of_then_is_reachable = Some(reachable); - } - - /// Returns a reference to the [`ConsumeFuel`] instruction of the [`BlockControlFrame`] if any. - /// - /// Returns `None` if fuel metering is disabled. - /// - /// # Note - /// - /// This returns the [`ConsumeFuel`] instruction for both `then` and `else` blocks. - /// When entering the `if` block it represents the [`ConsumeFuel`] instruction until - /// the `else` block entered. This is possible because only one of them is needed - /// at the same time during translation. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn consume_fuel_instr(&self) -> Option { - self.consume_fuel - } - - /// Updates the [`ConsumeFuel`] instruction for when the `else` block is entered. - /// - /// # Note - /// - /// This is required since the `consume_fuel` field represents the [`ConsumeFuel`] - /// instruction for both `then` and `else` blocks. This is possible because only one - /// of them is needed at the same time during translation. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn update_consume_fuel_instr(&mut self, instr: Instr) { - assert!( - self.consume_fuel.is_some(), - "can only update the consume fuel instruction if it existed before" - ); - self.consume_fuel = Some(instr); - } -} - -/// An unreachable control flow frame of any kind. -#[derive(Debug, Copy, Clone)] -pub struct UnreachableControlFrame { - /// The non-SSA input and output types of the unreachable control frame. - pub block_type: BlockType, - /// The kind of the unreachable control flow frame. - pub kind: ControlFrameKind, -} - -/// The kind of a control flow frame. -#[derive(Debug, Copy, Clone)] -pub enum ControlFrameKind { - /// A basic `block` control flow frame. - Block, - /// A `loop` control flow frame. - Loop, - /// An `if` and `else` block control flow frame. - If, -} - -impl UnreachableControlFrame { - /// Creates a new [`UnreachableControlFrame`] with the given type and kind. - pub fn new(kind: ControlFrameKind, block_type: BlockType) -> Self { - Self { block_type, kind } - } - - /// Returns the [`ControlFrameKind`] of the [`UnreachableControlFrame`]. - pub fn kind(&self) -> ControlFrameKind { - self.kind - } - - /// Returns the [`BlockType`] of the [`IfControlFrame`]. - pub fn block_type(&self) -> BlockType { - self.block_type - } -} - -/// A control flow frame. -#[derive(Debug, Copy, Clone)] -pub enum ControlFrame { - /// Basic block control frame. - Block(BlockControlFrame), - /// Loop control frame. - Loop(LoopControlFrame), - /// If and else control frame. - If(IfControlFrame), - /// An unreachable control frame. - Unreachable(UnreachableControlFrame), -} - -impl From for ControlFrame { - fn from(frame: BlockControlFrame) -> Self { - Self::Block(frame) - } -} - -impl From for ControlFrame { - fn from(frame: LoopControlFrame) -> Self { - Self::Loop(frame) - } -} - -impl From for ControlFrame { - fn from(frame: IfControlFrame) -> Self { - Self::If(frame) - } -} - -impl From for ControlFrame { - fn from(frame: UnreachableControlFrame) -> Self { - Self::Unreachable(frame) - } -} - -impl ControlFrame { - /// Returns the [`ControlFrameKind`] of the [`ControlFrame`]. - pub fn kind(&self) -> ControlFrameKind { - match self { - ControlFrame::Block(_) => ControlFrameKind::Block, - ControlFrame::Loop(_) => ControlFrameKind::Loop, - ControlFrame::If(_) => ControlFrameKind::If, - ControlFrame::Unreachable(frame) => frame.kind(), - } - } - - /// Returns the label for the branch destination of the [`ControlFrame`]. - pub fn branch_destination(&self) -> LabelRef { - match self { - Self::Block(frame) => frame.branch_destination(), - Self::Loop(frame) => frame.branch_destination(), - Self::If(frame) => frame.branch_destination(), - Self::Unreachable(frame) => panic!( - "tried to get `branch_destination` for an unreachable control frame: {frame:?}" - ), - } - } - - /// Returns a label which should be resolved at the `End` Wasm opcode. - /// - /// All [`ControlFrame`] kinds have it except [`ControlFrame::Loop`]. - /// In order to a [`ControlFrame::Loop`] to branch outside it is required - /// to be wrapped in another control frame such as [`ControlFrame::Block`]. - pub fn end_label(&self) -> LabelRef { - match self { - Self::Block(frame) => frame.end_label(), - Self::If(frame) => frame.end_label(), - Self::Loop(frame) => { - panic!("tried to get `end_label` for a loop control frame: {frame:?}") - } - Self::Unreachable(frame) => { - panic!("tried to get `end_label` for an unreachable control frame: {frame:?}") - } - } - } - - /// Returns the value stack height upon entering the control flow frame. - pub fn stack_height(&self) -> Option { - match self { - Self::Block(frame) => Some(frame.stack_height()), - Self::Loop(frame) => Some(frame.stack_height()), - Self::If(frame) => Some(frame.stack_height()), - Self::Unreachable(_frame) => None, - } - } - - /// Returns the [`BlockType`] of the control flow frame. - pub fn block_type(&self) -> BlockType { - match self { - Self::Block(frame) => frame.block_type(), - Self::Loop(frame) => frame.block_type(), - Self::If(frame) => frame.block_type(), - Self::Unreachable(frame) => frame.block_type(), - } - } - - /// Returns `true` if the control flow frame is reachable. - pub fn is_reachable(&self) -> bool { - !matches!(self, ControlFrame::Unreachable(_)) - } - - /// Returns a reference to the [`ConsumeFuel`] instruction of the [`ControlFrame`] if any. - /// - /// Returns `None` if fuel metering is disabled. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn consume_fuel_instr(&self) -> Option { - match self { - ControlFrame::Block(frame) => frame.consume_fuel_instr(), - ControlFrame::Loop(frame) => frame.consume_fuel_instr(), - ControlFrame::If(frame) => frame.consume_fuel_instr(), - ControlFrame::Unreachable(_) => None, - } - } -} diff --git a/legacy/src/engine/func_builder/control_stack.rs b/legacy/src/engine/func_builder/control_stack.rs deleted file mode 100644 index d63bfafda..000000000 --- a/legacy/src/engine/func_builder/control_stack.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::ControlFrame; -use alloc::vec::Vec; - -/// The stack of control flow frames. -#[derive(Debug, Default)] -pub struct ControlFlowStack { - frames: Vec, -} - -impl ControlFlowStack { - /// Resets the [`ControlFlowStack`] to allow for reuse. - pub fn reset(&mut self) { - self.frames.clear() - } - - /// Returns `true` if `relative_depth` points to the first control flow frame. - pub fn is_root(&self, relative_depth: u32) -> bool { - debug_assert!(!self.is_empty()); - relative_depth as usize == self.len() - 1 - } - - /// Returns the current depth of the stack of the [`ControlFlowStack`]. - pub fn len(&self) -> usize { - self.frames.len() - } - - /// Returns `true` if the [`ControlFlowStack`] is empty. - pub fn is_empty(&self) -> bool { - self.frames.len() == 0 - } - - /// Pushes a new control flow frame to the [`ControlFlowStack`]. - pub fn push_frame(&mut self, frame: T) - where - T: Into, - { - self.frames.push(frame.into()) - } - - /// Pops the last control flow frame from the [`ControlFlowStack`]. - /// - /// # Panics - /// - /// If the [`ControlFlowStack`] is empty. - pub fn pop_frame(&mut self) -> ControlFrame { - self.frames - .pop() - .expect("tried to pop control flow frame from empty control flow stack") - } - - /// Returns the last control flow frame on the control stack. - pub fn last(&self) -> &ControlFrame { - self.frames.last().expect( - "tried to exclusively peek the last control flow \ - frame from an empty control flow stack", - ) - } - - /// Returns a shared reference to the control flow frame at the given `depth`. - /// - /// A `depth` of 0 is equal to calling [`ControlFlowStack::last`]. - /// - /// # Panics - /// - /// If `depth` exceeds the length of the stack of control flow frames. - pub fn nth_back(&self, depth: u32) -> &ControlFrame { - let len = self.len(); - self.frames - .iter() - .nth_back(depth as usize) - .unwrap_or_else(|| { - panic!( - "tried to peek the {depth}-th control flow frame \ - but there are only {len} control flow frames", - ) - }) - } -} diff --git a/legacy/src/engine/func_builder/error.rs b/legacy/src/engine/func_builder/error.rs deleted file mode 100644 index be30b78b2..000000000 --- a/legacy/src/engine/func_builder/error.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::engine::bytecode::DropKeepError; -use alloc::boxed::Box; -use core::fmt::{self, Display}; - -/// An error that may occur upon parsing, validating and translating Wasm. -#[derive(Debug)] -pub struct TranslationError { - /// The inner error type encapsulating internal error state. - inner: Box, -} - -impl TranslationError { - /// Create a new [`TranslationError`] from the inner variant. - #[cold] - #[inline] - pub fn new(inner: TranslationErrorInner) -> Self { - Self { - inner: Box::new(inner), - } - } - - /// Creates a new error indicating an unsupported Wasm block type. - pub fn unsupported_block_type(block_type: wasmparser::BlockType) -> Self { - Self { - inner: Box::new(TranslationErrorInner::UnsupportedBlockType(block_type)), - } - } - - /// Creates a new error indicating an unsupported Wasm value type. - pub fn unsupported_value_type(value_type: wasmparser::ValType) -> Self { - Self { - inner: Box::new(TranslationErrorInner::UnsupportedValueType(value_type)), - } - } -} - -impl From for TranslationError { - fn from(error: wasmparser::BinaryReaderError) -> Self { - Self { - inner: Box::new(TranslationErrorInner::Validate(error)), - } - } -} - -impl From for TranslationError { - fn from(error: DropKeepError) -> Self { - Self { - inner: Box::new(TranslationErrorInner::DropKeep(error)), - } - } -} - -impl Display for TranslationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &*self.inner { - TranslationErrorInner::Validate(error) => error.fmt(f), - TranslationErrorInner::UnsupportedBlockType(error) => { - write!(f, "encountered unsupported Wasm block type: {error:?}") - } - TranslationErrorInner::UnsupportedValueType(error) => { - write!(f, "encountered unsupported Wasm value type: {error:?}") - } - TranslationErrorInner::DropKeep(error) => error.fmt(f), - TranslationErrorInner::BranchTableTargetsOutOfBounds => { - write!( - f, - "branch table targets are out of bounds for wasmi bytecode" - ) - } - TranslationErrorInner::ConstRefOutOfBounds => { - write!( - f, - "constant reference index is out of bounds for wasmi bytecode" - ) - } - TranslationErrorInner::BranchOffsetOutOfBounds => { - write!(f, "branching offset is out of bounds for wasmi bytecode") - } - TranslationErrorInner::BlockFuelOutOfBounds => { - write!( - f, - "fuel required to execute a block is out of bounds for wasmi bytecode" - ) - } - } - } -} - -/// The inner error type encapsulating internal [`TranslationError`] state. -#[derive(Debug)] -pub enum TranslationErrorInner { - /// There was either a problem parsing a Wasm input OR validating a Wasm input. - Validate(wasmparser::BinaryReaderError), - /// Encountered an unsupported Wasm block type. - UnsupportedBlockType(wasmparser::BlockType), - /// Encountered an unsupported Wasm value type. - UnsupportedValueType(wasmparser::ValType), - /// An error with limitations of `DropKeep`. - DropKeep(DropKeepError), - /// When using too many branch table targets. - BranchTableTargetsOutOfBounds, - /// Branching offset out of bounds. - BranchOffsetOutOfBounds, - /// Fuel required for a block is out of bounds. - BlockFuelOutOfBounds, - /// The constant reference index is out of bounds. - ConstRefOutOfBounds, -} diff --git a/legacy/src/engine/func_builder/inst_builder.rs b/legacy/src/engine/func_builder/inst_builder.rs deleted file mode 100644 index ea3c2b2ac..000000000 --- a/legacy/src/engine/func_builder/inst_builder.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! Abstractions to build up instructions forming Wasm function bodies. - -use super::{ - labels::{LabelRef, LabelRegistry}, - TranslationError, -}; -use crate::engine::{ - bytecode::{BranchOffset, FuncIdx, InstrMeta, Instruction}, - CompiledFunc, - DropKeep, - Engine, -}; -use alloc::vec::Vec; - -/// A reference to an instruction of the partially -/// constructed function body of the [`InstructionsBuilder`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Instr(u32); - -impl Instr { - /// Creates an [`Instr`] from the given `usize` value. - /// - /// # Note - /// - /// This intentionally is an API intended for test purposes only. - /// - /// # Panics - /// - /// If the `value` exceeds limitations for [`Instr`]. - pub fn from_usize(value: usize) -> Self { - let value = value.try_into().unwrap_or_else(|error| { - panic!("invalid index {value} for instruction reference: {error}") - }); - Self(value) - } - - /// Returns an `usize` representation of the instruction index. - pub fn into_usize(self) -> usize { - self.0 as usize - } - - /// Creates an [`Instr`] form the given `u32` value. - pub fn from_u32(value: u32) -> Self { - Self(value) - } - - /// Returns an `u32` representation of the instruction index. - pub fn into_u32(self) -> u32 { - self.0 - } -} - -/// The relative depth of a Wasm branching target. -#[derive(Debug, Copy, Clone)] -pub struct RelativeDepth(u32); - -impl RelativeDepth { - /// Returns the relative depth as `u32`. - pub fn into_u32(self) -> u32 { - self.0 - } - - /// Creates a relative depth from the given `u32` value. - pub fn from_u32(relative_depth: u32) -> Self { - Self(relative_depth) - } -} - -/// An instruction builder. -/// -/// Allows to incrementally and efficiently build up the instructions -/// of a Wasm function body. -/// Can be reused to build multiple functions consecutively. -#[derive(Debug, Default)] -pub struct InstructionsBuilder { - /// The instructions of the partially constructed function body. - insts: Vec, - metas: Vec, - /// All labels and their uses. - labels: LabelRegistry, - /// Instruction meta state (pc and opcode number) - temp_meta: InstrMeta, -} - -impl InstructionsBuilder { - /// Resets the [`InstructionsBuilder`] to allow for reuse. - pub fn reset(&mut self) { - self.insts.clear(); - self.labels.reset(); - } - - /// Returns the current instruction pointer as index. - pub fn current_pc(&self) -> Instr { - Instr::from_usize(self.insts.len()) - } - - /// Creates a new unresolved label and returns an index to it. - pub fn new_label(&mut self) -> LabelRef { - self.labels.new_label() - } - - /// Resolve the label at the current instruction position. - /// - /// Does nothing if the label has already been resolved. - /// - /// # Note - /// - /// This is used at a position of the Wasm bytecode where it is clear that - /// the given label can be resolved properly. - /// This usually takes place when encountering the Wasm `End` operand for example. - pub fn pin_label_if_unpinned(&mut self, label: LabelRef) { - self.labels.try_pin_label(label, self.current_pc()) - } - - /// Resolve the label at the current instruction position. - /// - /// # Note - /// - /// This is used at a position of the Wasm bytecode where it is clear that - /// the given label can be resolved properly. - /// This usually takes place when encountering the Wasm `End` operand for example. - /// - /// # Panics - /// - /// If the label has already been resolved. - pub fn pin_label(&mut self, label: LabelRef) { - self.labels - .pin_label(label, self.current_pc()) - .unwrap_or_else(|err| panic!("failed to pin label: {err}")); - } - - /// Pushes the internal instruction bytecode to the [`InstructionsBuilder`]. - /// - /// Returns an [`Instr`] to refer to the pushed instruction. - pub fn push_inst(&mut self, inst: Instruction) -> Instr { - let idx = self.current_pc(); - self.insts.push(inst); - self.metas.push(self.temp_meta); - idx - } - - /// Pushes an [`Instruction::BrAdjust`] to the [`InstructionsBuilder`]. - /// - /// Returns an [`Instr`] to refer to the pushed instruction. - pub fn push_br_adjust_instr( - &mut self, - branch_offset: BranchOffset, - drop_keep: DropKeep, - ) -> Instr { - let idx = self.push_inst(Instruction::BrAdjust(branch_offset)); - self.push_inst(Instruction::Return(drop_keep)); - idx - } - - /// Pushes an [`Instruction::BrAdjustIfNez`] to the [`InstructionsBuilder`]. - /// - /// Returns an [`Instr`] to refer to the pushed instruction. - pub fn push_br_adjust_nez_instr( - &mut self, - branch_offset: BranchOffset, - drop_keep: DropKeep, - ) -> Instr { - let idx = self.push_inst(Instruction::BrAdjustIfNez(branch_offset)); - self.push_inst(Instruction::Return(drop_keep)); - idx - } - - /// Try resolving the `label` for the currently constructed instruction. - /// - /// Returns an uninitialized [`BranchOffset`] if the `label` cannot yet - /// be resolved and defers resolution to later. - pub fn try_resolve_label(&mut self, label: LabelRef) -> Result { - let user = self.current_pc(); - self.try_resolve_label_for(label, user) - } - - pub fn register_meta(&mut self, pc: usize, opcode: u16) { - self.temp_meta = InstrMeta::new(pc, opcode, self.metas.len()); - } - - /// Try resolving the `label` for the given `instr`. - /// - /// Returns an uninitialized [`BranchOffset`] if the `label` cannot yet - /// be resolved and defers resolution to later. - pub fn try_resolve_label_for( - &mut self, - label: LabelRef, - instr: Instr, - ) -> Result { - self.labels.try_resolve_label(label, instr) - } - - /// Finishes construction of the function body instructions. - /// - /// # Note - /// - /// This feeds the built-up instructions of the function body - /// into the [`Engine`] so that the [`Engine`] is - /// aware of the Wasm function existence. Returns a [`CompiledFunc`] - /// reference that allows to retrieve the instructions. - pub fn finish( - &mut self, - engine: &Engine, - func: CompiledFunc, - len_locals: usize, - local_stack_height: usize, - ) -> Result<(), TranslationError> { - self.update_branch_offsets()?; - if engine.config().get_rwasm_config().is_some() { - self.update_max_stack_height(local_stack_height, len_locals); - } - assert_eq!( - self.insts.len(), - self.metas.len(), - "instr and meta length mismatch" - ); - engine.init_func( - func, - len_locals, - local_stack_height, - self.insts.drain(..), - self.metas.drain(..), - ); - Ok(()) - } - - pub fn finalize(mut self) -> Result<(Vec, Vec), TranslationError> { - self.update_branch_offsets()?; - assert_eq!( - self.insts.len(), - self.metas.len(), - "instr and meta length mismatch" - ); - Ok((self.insts, self.metas)) - } - - pub fn last(&self) -> Option<&Instruction> { - self.insts.last() - } - - pub fn last_nth_mut(&mut self, n: usize) -> Option<&mut Instruction> { - self.insts.iter_mut().rev().nth(n) - } - - pub fn len(&self) -> usize { - self.insts.len() - } - - pub fn instrs(&self) -> &Vec { - &self.insts - } - - /// Updates the branch offsets of all branch instructions inplace. - /// - /// # Panics - /// - /// If this is used before all branching labels have been pinned. - fn update_branch_offsets(&mut self) -> Result<(), TranslationError> { - for (user, offset) in self.labels.resolved_users() { - self.insts[user.into_usize()].update_branch_offset(offset?); - } - Ok(()) - } - - fn update_max_stack_height(&mut self, max_stack_height_value: usize, _num_locals_value: usize) { - let mut iter = self.insts.iter_mut().take(3); - loop { - let opcode = iter.next().unwrap(); - match opcode { - Instruction::ConsumeFuel(_) | Instruction::SignatureCheck(_) => {} - Instruction::StackAlloc { max_stack_height } => { - *max_stack_height = max_stack_height_value as u32; - return; - } - _ => unreachable!("rwasm: not allowed opcode"), - } - } - } - - /// Adds the given `delta` amount of fuel to the [`ConsumeFuel`] instruction `instr`. - /// - /// # Panics - /// - /// - If `instr` does not resolve to a [`ConsumeFuel`] instruction. - /// - If the amount of consumed fuel for `instr` overflows. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn bump_fuel_consumption( - &mut self, - instr: Instr, - delta: u64, - ) -> Result<(), TranslationError> { - self.insts[instr.into_usize()].bump_fuel_consumption(delta) - } -} - -impl Instruction { - pub fn get_jump_offset(&self) -> Option { - match self { - Instruction::Br(offset) => Some(*offset), - Instruction::BrIfEqz(offset) => Some(*offset), - Instruction::BrIfNez(offset) => Some(*offset), - Instruction::BrAdjust(offset) => Some(*offset), - Instruction::BrAdjustIfNez(offset) => Some(*offset), - _ => None, - } - } - - pub fn update_call_index(&mut self, new_index: u32) { - match self { - Instruction::ReturnCall(func) => *func = FuncIdx::from(new_index), - Instruction::Call(func) => *func = FuncIdx::from(new_index), - Instruction::ReturnCallInternal(func) => *func = CompiledFunc::from(new_index), - Instruction::CallInternal(func) => *func = CompiledFunc::from(new_index), - Instruction::RefFunc(func) => *func = FuncIdx::from(new_index), - _ => panic!("tried to update call index of a non-call instruction: {self:?}"), - } - } - - /// Updates the [`BranchOffset`] for the branch [`Instruction]. - /// - /// # Panics - /// - /// If `self` is not a branch [`Instruction`]. - pub fn update_branch_offset>(&mut self, new_offset: I) { - let new_offset: BranchOffset = new_offset.into(); - match self { - Instruction::Br(offset) - | Instruction::BrIfEqz(offset) - | Instruction::BrIfNez(offset) - | Instruction::BrAdjust(offset) - | Instruction::BrAdjustIfNez(offset) => *offset = new_offset, - _ => panic!("tried to update branch offset of a non-branch instruction: {self:?}"), - } - } -} diff --git a/legacy/src/engine/func_builder/labels.rs b/legacy/src/engine/func_builder/labels.rs deleted file mode 100644 index 02b8802c8..000000000 --- a/legacy/src/engine/func_builder/labels.rs +++ /dev/null @@ -1,212 +0,0 @@ -use super::{Instr, TranslationError}; -use crate::engine::bytecode::BranchOffset; -use alloc::vec::Vec; -use core::{ - fmt::{self, Display}, - slice::Iter as SliceIter, -}; - -/// A label during the `wasmi` compilation process. -#[derive(Debug, Copy, Clone)] -pub enum Label { - /// The label has already been pinned to a particular [`Instr`]. - Pinned(Instr), - /// The label is still unpinned. - Unpinned, -} - -/// A reference to an [`Label`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct LabelRef(u32); - -impl LabelRef { - /// Returns the `usize` value of the [`LabelRef`]. - #[inline] - pub(crate) fn into_usize(self) -> usize { - self.0 as usize - } - - pub(crate) fn new(label: u32) -> LabelRef { - LabelRef(label) - } -} - -/// The label registry. -/// -/// Allows to allocate new labels pin them and resolve pinned ones. -#[derive(Debug, Default)] -pub struct LabelRegistry { - labels: Vec